From b57af7f8d194617e965d9713a767cc012774a2b9 Mon Sep 17 00:00:00 2001 From: Daniel Balint Date: Mon, 24 Aug 2026 15:55:54 +0000 Subject: [PATCH 0001/2286] platform/chrome: cros_ec: Sync host command header with EC Sync host command header with the ChromeOS EC firmware. This primarily adds the host command, MKBP event, and related data structures for power monitoring so that the API is available for other drivers. Signed-off-by: Daniel Balint Link: https://lore.kernel.org/r/20260824155554.1046334-1-dbalint@google.com Signed-off-by: Tzung-Bi Shih --- .../linux/platform_data/cros_ec_commands.h | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 749d985e9da2db..a7bd69d8352227 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -1344,6 +1344,26 @@ enum ec_feature_code { * The EC supports UCSI PPM. */ EC_FEATURE_UCSI_PPM = 54, + /* + * The EC supports Strauss keyboard. + */ + EC_FEATURE_STRAUSS = 55, + /* + * The EC supports PoE. + */ + EC_FEATURE_POE = 56, + /* + * The EC supports a hybrid boost charger + */ + EC_FEATURE_CHARGER_HYBRID_POWER_BOOST = 57, + /* + * Support signaling new console logs via host event + */ + EC_FEATURE_CONSOLE_LOG_EVENT = 58, + /* + * The EC supports power monitoring + */ + EC_FEATURE_PWRMON = 59, }; #define EC_FEATURE_MASK_0(event_code) BIT(event_code % 32) @@ -3516,6 +3536,9 @@ enum ec_mkbp_event { /* Peripheral device charger event */ EC_MKBP_EVENT_PCHG = 12, + /* Power monitor telemetry event */ + EC_MKBP_EVENT_PWRMON = 13, + /* Number of MKBP events */ EC_MKBP_EVENT_COUNT, }; @@ -3599,6 +3622,12 @@ union __ec_align_offset1 ec_response_get_next_data_v3 { uint32_t cec_events; uint8_t cec_message[16]; + + struct __ec_todo_packed { + int64_t value; + uint32_t samples; + uint8_t channel_id; + } pwrmon_data; }; BUILD_ASSERT(sizeof(union ec_response_get_next_data_v3) == 18); @@ -6589,6 +6618,49 @@ struct ec_params_usb_pd_mux_ack { uint8_t port; /* USB-C port number */ } __ec_align1; +/** + * Power monitoring. Used to read power consumptions on rails + */ +#define EC_CMD_PWRMON 0x0608 + +enum ec_pwrmon_cmd { + EC_PWRMON_GET_CHANNEL_COUNT = 0, + EC_PWRMON_DUMP_INFO = 1, + EC_PWRMON_SET_RATE = 2, + EC_PWRMON_GET_RATE = 3, + EC_PWRMON_START = 4, + EC_PWRMON_STOP = 5, + EC_PWRMON_LATCH = 6, +}; + +struct ec_params_pwrmon { + uint8_t cmd; + union { + uint16_t set_rate; + uint8_t channel_id; + } __ec_align2; + + /* + * The following commands have no args: + * + * start, stop, latch + * + */ +} __ec_align4; + +struct pwrmon_dump_info { + uint8_t channel_id; + char channel_name[32]; +} __ec_align4; + +struct ec_response_pwrmon { + union { + uint16_t sample_rate; + uint8_t channel_count; + struct pwrmon_dump_info dump_info; + } __ec_align4; +} __ec_align4; + /*****************************************************************************/ /* * Reserve a range of host commands for board-specific, experimental, or From 30b3853ab3b54e22edf3372fd6aaf35d52d667e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 18 Aug 2026 18:33:02 +0200 Subject: [PATCH 0002/2286] selftests/nolibc: stop treating skipped tests as warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nolibc testsuite will always have some tests which get skipped. Due to this the full testsuite is always marked as 'warning', even though all testcases succeeded as expected. Stop treating skipped tests as warning. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Link: https://patch.msgid.link/20260818-nolibc-test-result-warning-v1-1-d15cdda84ccd@weissschuh.net --- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index f70c8dfca01869..c790f937fe0aa2 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -225,7 +225,7 @@ include $(srctree)/tools/scripts/Makefile.include REPORT ?= awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{if (!f) printf("\n"); f++; print;} /\[SKIPPED\][\r]*$$/{s++} \ /^Total number of errors:/{done++} \ END{ printf("\n%3d test(s): %3d passed, %3d skipped, %3d failed => status: ", p+s+f, p, s, f); \ - if (f || !p || !done) printf("failure\n"); else if (s) printf("warning\n"); else printf("success\n");; \ + if (f || !p || !done) printf("failure\n"); else printf("success\n");; \ printf("\nSee all results in %s\n", ARGV[1]); }' # Execute the toplevel kernel Makefile From 836cecd18332a3152f09e79b02e613b3982c2e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:51 +0200 Subject: [PATCH 0003/2286] tools/nolibc: split the architecture list into multiple lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list is getting long, split it up for easier additions. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-1-6bc3be591f09@weissschuh.net --- tools/include/nolibc/Makefile | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index 6d213d372bf835..b5897b3964f86f 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -17,7 +17,22 @@ endif # it defaults to this nolibc directory. OUTPUT ?= $(CURDIR)/ -architectures := alpha arm arm64 loongarch m68k mips openrisc parisc powerpc riscv s390 sh sparc x86 +architectures := \ + alpha \ + arm \ + arm64 \ + loongarch \ + m68k \ + mips \ + openrisc \ + parisc \ + powerpc \ + riscv \ + s390 \ + sh \ + sparc \ + x86 \ + arch_files := arch.h $(addsuffix .h, $(addprefix arch-, $(architectures))) all_files := \ alloca.h \ From 4b4f56583850ba13f576f1e34f1b9e217c003773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:52 +0200 Subject: [PATCH 0004/2286] selftests/nolibc: prepare for clang-only architectures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contrary to other architectures currently supported by nolibc, hexagon can only be built with clang. Prepare run-tests.sh to allow for this. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-2-6bc3be591f09@weissschuh.net --- tools/testing/selftests/nolibc/run-tests.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index dc0b1649c6419e..5732b956ead9b9 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -132,6 +132,12 @@ crosstool_abi() { esac } +need_gcc() { + case "$1" in + *) echo "1";; + esac +} + download_crosstool() { arch="$(crosstool_arch "$1")" abi="$(crosstool_abi "$1")" @@ -164,14 +170,18 @@ test_arch() { arch=$1 ct_arch=$(crosstool_arch "$arch") ct_abi=$(crosstool_abi "$1") + gcc="$(need_gcc "$1")" + cross_compile= - if [ ! -d "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/." ]; then + if [ -n "$gcc" ] && [ ! -d "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/." ]; then echo "No toolchain found in ${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}." echo "Did you install the toolchains or set the correct arch ? Rerun with -h for help." return 1 fi - cross_compile=$(realpath "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/${ct_arch}-${ct_abi}-") + if [ -n "$gcc" ]; then + cross_compile=$(realpath "${download_location}gcc-${crosstool_version}-nolibc/${ct_arch}-${ct_abi}/bin/${ct_arch}-${ct_abi}-") + fi build_dir="${build_location}/${arch}" if [ "$werror" -ne 0 ]; then CFLAGS_EXTRA="$CFLAGS_EXTRA -Werror -Wl,--fatal-warnings" From 3842a1f486a05c12b2a8712546858e6872431292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 08:31:53 +0200 Subject: [PATCH 0005/2286] tools/nolibc: add support for hexagon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A mostly straightforward new architecture with a few quirks: * Only qemu-user is supported for testing. * Clang is required for compilation. * -fsanitize=undefined is broken. Signed-off-by: Thomas Weißschuh Acked-by: Willy Tarreau Reviewed-by: Brian Cain Link: https://patch.msgid.link/20260819-nolibc-hexagon-v1-3-6bc3be591f09@weissschuh.net --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/arch-hexagon.h | 164 ++++++++++++++++++ tools/include/nolibc/arch.h | 2 + .../testing/selftests/nolibc/Makefile.nolibc | 8 + tools/testing/selftests/nolibc/run-tests.sh | 6 + 5 files changed, 181 insertions(+) create mode 100644 tools/include/nolibc/arch-hexagon.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index b5897b3964f86f..c4a0879e41407f 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -21,6 +21,7 @@ architectures := \ alpha \ arm \ arm64 \ + hexagon \ loongarch \ m68k \ mips \ diff --git a/tools/include/nolibc/arch-hexagon.h b/tools/include/nolibc/arch-hexagon.h new file mode 100644 index 00000000000000..0767054882f3e5 --- /dev/null +++ b/tools/include/nolibc/arch-hexagon.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * hexagon specific definitions for NOLIBC + * Copyright (C) 2026 Thomas Weißschuh + */ + +#ifndef _NOLIBC_ARCH_HEXAGON_H +#define _NOLIBC_ARCH_HEXAGON_H + +#include + +#include "compiler.h" +#include "crt.h" + +/* + * Syscalls for hexagon: + * - syscall number is passed in r6 + * - arguments are in r0, r1, r2, r3, r4, r5 + * - the system call is performed by calling trap0(#1) + * - syscall return value is in r0 + */ + +#define _NOLIBC_SYSCALL_CLOBBERLIST "memory" + +#define __nolibc_syscall0(num) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0"); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "=r"(_arg1) \ + : "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall1(num, arg1) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall2(num, arg1, arg2) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall3(num, arg1, arg2, arg3) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall4(num, arg1, arg2, arg3, arg4) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall5(num, arg1, arg2, arg3, arg4, arg5) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + register long _arg5 __asm__ ("r4") = (long)(arg5); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#define __nolibc_syscall6(num, arg1, arg2, arg3, arg4, arg5, arg6) \ +({ \ + register long _num __asm__ ("r6") = (num); \ + register long _arg1 __asm__ ("r0") = (long)(arg1); \ + register long _arg2 __asm__ ("r1") = (long)(arg2); \ + register long _arg3 __asm__ ("r2") = (long)(arg3); \ + register long _arg4 __asm__ ("r3") = (long)(arg4); \ + register long _arg5 __asm__ ("r4") = (long)(arg5); \ + register long _arg6 __asm__ ("r5") = (long)(arg6); \ + \ + __asm__ volatile ( \ + "trap0(#1)\n" \ + : "+r"(_arg1) \ + : "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), "r"(_arg6), \ + "r"(_num) \ + : _NOLIBC_SYSCALL_CLOBBERLIST \ + ); \ + _arg1; \ +}) + +#ifndef NOLIBC_NO_RUNTIME +/* startup code */ +void __attribute__((weak, noreturn)) +__nolibc_entrypoint __nolibc_no_stack_protector +_start(void) +{ + __asm__ volatile ( + "r0 = sp\n" /* save stack pointer to r0, as arg1 of _start_c */ + "call _start_c\n" /* transfer to c runtime */ + ); + __nolibc_entrypoint_epilogue(); +} +#endif /* NOLIBC_NO_RUNTIME */ + +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) +{ + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +} +#define _sys_ftruncate64 _sys_ftruncate64 + +#endif /* _NOLIBC_ARCH_HEXAGON_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 06cc2dbe7a3305..24e0ec4fb2db17 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -34,6 +34,8 @@ #include "arch-parisc.h" #elif defined(__alpha__) #include "arch-alpha.h" +#elif defined(__hexagon__) +#include "arch-hexagon.h" #else #error Unsupported Architecture #endif diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index c790f937fe0aa2..d8b71cbfc1735c 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -206,6 +206,8 @@ CFLAGS_mips64be = -EB -mabi=64 -march=mips64r2 CFLAGS_loongarch = $(if $(LLVM),-fuse-ld=lld) CFLAGS_sparc32 = $(call cc-option,-m32) -mcpu=v8 CFLAGS_sh4 = -ml -m4 +# github.com/llvm/llvm-project/issues/216765 +CFLAGS_hexagon = -fno-sanitize=undefined ifeq ($(origin XARCH),command line) CFLAGS_XARCH = $(CFLAGS_$(XARCH)) endif @@ -222,6 +224,12 @@ LDFLAGS := # Modify CFLAGS based on LLVM= include $(srctree)/tools/scripts/Makefile.include +ifeq ($(ARCH),hexagon) +# tools/scripts/Makefile.include requires CROSS_COMPILE which does not exist for hexagon +CFLAGS += --target=hexagon-linux-musl +CLANG_CROSS_FLAGS += --target=hexagon-linux-musl +endif + REPORT ?= awk '/\[OK\][\r]*$$/{p++} /\[FAIL\][\r]*$$/{if (!f) printf("\n"); f++; print;} /\[SKIPPED\][\r]*$$/{s++} \ /^Total number of errors:/{done++} \ END{ printf("\n%3d test(s): %3d passed, %3d skipped, %3d failed => status: ", p+s+f, p, s, f); \ diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index 5732b956ead9b9..3da806e2b302ba 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -31,6 +31,7 @@ all_archs=( sh4 parisc32 alpha + hexagon ) archs="${all_archs[@]}" @@ -134,6 +135,7 @@ crosstool_abi() { need_gcc() { case "$1" in + hexagon);; *) echo "1";; esac } @@ -212,6 +214,10 @@ test_arch() { echo "Unsupported configuration" return fi + if [ "$arch" = "hexagon" ] && [ -z "$llvm" -o "$test_mode" = "system" ]; then + echo "Unsupported configuration" + return + fi mkdir -p "$build_dir" swallow_output "${MAKE[@]}" defconfig From 4c00540add56942d621a941481cf9d0fb7dcec0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 25 Aug 2026 22:41:33 +0200 Subject: [PATCH 0006/2286] selftests/nolibc: switch around the skip conditions in run-tests.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the potentially long list of affected architectures to the end of the conditional to make the logic easier to understand. Suggested-by: David Laight Link: https://lore.kernel.org/lkml/20260824091958.7810094f@pumpkin/ Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/run-tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index 3da806e2b302ba..af2a6f510d1db2 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -206,15 +206,15 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ] && [ "$llvm" = "1" ]; then + if [ "$llvm" = "1" ] && [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ]; then echo "Unsupported configuration" return fi - if [ "$arch" = "x32" ] && [ "$test_mode" = "user" ]; then + if [ "$test_mode" = "user" ] && [ "$arch" = "x32" ]; then echo "Unsupported configuration" return fi - if [ "$arch" = "hexagon" ] && [ -z "$llvm" -o "$test_mode" = "system" ]; then + if [ -z "$llvm" -o "$test_mode" = "system" ] && [ "$arch" = "hexagon" ]; then echo "Unsupported configuration" return fi From c0da8b53603ad84f622b2c5f6c1a69f7d4b4b8bc Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 28 Aug 2026 19:32:04 +0900 Subject: [PATCH 0007/2286] tools/nolibc: Add sendfile() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is useful because it allows copying data up to 2GB from one fd to another without a buffer in userspace and with a single syscall. For 32bit kernels we use the sendfile64 syscall to allow for 64bit offsets to work. Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260828103205.384589-2-daniel@thingy.jp Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/Makefile | 1 + tools/include/nolibc/nolibc.h | 1 + tools/include/nolibc/sys/sendfile.h | 41 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 tools/include/nolibc/sys/sendfile.h diff --git a/tools/include/nolibc/Makefile b/tools/include/nolibc/Makefile index c4a0879e41407f..0880be0f6ef587 100644 --- a/tools/include/nolibc/Makefile +++ b/tools/include/nolibc/Makefile @@ -75,6 +75,7 @@ all_files := \ sys/reboot.h \ sys/resource.h \ sys/select.h \ + sys/sendfile.h \ sys/stat.h \ sys/syscall.h \ sys/sysmacros.h \ diff --git a/tools/include/nolibc/nolibc.h b/tools/include/nolibc/nolibc.h index faa94f24728165..352a207bbedc65 100644 --- a/tools/include/nolibc/nolibc.h +++ b/tools/include/nolibc/nolibc.h @@ -106,6 +106,7 @@ #include "sys/reboot.h" #include "sys/resource.h" #include "sys/select.h" +#include "sys/sendfile.h" #include "sys/stat.h" #include "sys/syscall.h" #include "sys/sysmacros.h" diff --git a/tools/include/nolibc/sys/sendfile.h b/tools/include/nolibc/sys/sendfile.h new file mode 100644 index 00000000000000..ac7d8584771847 --- /dev/null +++ b/tools/include/nolibc/sys/sendfile.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: LGPL-2.1 OR MIT */ +/* + * sendfile for NOLIBC + * Copyright (C) 2026 Daniel Palmer + */ + +/* make sure to include all global symbols */ +#include "../nolibc.h" + +#ifndef _NOLIBC_SYS_SENDFILE_H +#define _NOLIBC_SYS_SENDFILE_H + +#include "../sys.h" +#include + +/* + * ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); + */ + +static __attribute__((unused)) +ssize_t _sys_sendfile(int out_fd, int in_fd, off_t *offset, size_t count) +{ + __nolibc_static_assert(sizeof(*offset) == sizeof(__kernel_loff_t)); + +#ifdef __NR_sendfile64 + /* 32-bit applications use sendfile64 so offset is treated as a __kernel_loff_t */ + return __nolibc_syscall4(__NR_sendfile64, out_fd, in_fd, offset, count); +#else + __nolibc_static_assert(sizeof(*offset) == sizeof(__kernel_off_t)); + + return __nolibc_syscall4(__NR_sendfile, out_fd, in_fd, offset, count); +#endif +} + +static __attribute__((unused)) +ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count) +{ + return __sysret(_sys_sendfile(out_fd, in_fd, offset, count)); +} + +#endif /* _NOLIBC_SYS_SENDFILE_H */ From 9c47af906bc655c8f45aaf1f156656239c2c5073 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 28 Aug 2026 19:32:05 +0900 Subject: [PATCH 0008/2286] selftests/nolibc: Add basic test for sendfile() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Basic smoke test for sendfile(). Signed-off-by: Daniel Palmer Link: https://patch.msgid.link/20260828103205.384589-3-daniel@thingy.jp [Thomas: add '#include sys/syscall.h', simplify test cleanup a bit] Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index ed860b0a15a1e5..0a8fe5100b7f58 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1563,6 +1564,72 @@ int test_large_file(void) return ret; } +int test_sendfile(void) +{ + char data_in[] = "This is some data"; + const size_t data_sz = sizeof(data_in); + char data_out[data_sz + 10]; + int in_fd, out_fd; + int ret; + + /* Create two tmp files */ + in_fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + out_fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + + if (in_fd == -1 || out_fd == -1) { + ret = __LINE__; + goto out; + } + + /* Populate the "in" file */ + ret = write(in_fd, data_in, data_sz); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Rewind the "in" file */ + if (lseek(in_fd, 0, SEEK_SET)) { + ret = __LINE__; + goto out; + } + + /* Use sendfile() to copy "in" to "out" */ + ret = sendfile(out_fd, in_fd, NULL, data_sz + 5); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Rewind the "out" file */ + if (lseek(out_fd, 0, SEEK_SET)) { + ret = __LINE__; + goto out; + } + + /* Read back the transferred data */ + ret = read(out_fd, data_out, sizeof(data_out)); + if (ret != data_sz) { + ret = __LINE__; + goto out; + } + + /* Check we have the same data in both files */ + if (memcmp(data_out, data_in, data_sz)) { + ret = __LINE__; + goto out; + } + + /* Test passed */ + ret = 0; + +out: + close(out_fd); + close(in_fd); + + return ret; +} + /* Run syscall tests between IDs and . * Return 0 on success, non-zero on failure. */ @@ -1684,6 +1751,7 @@ int run_syscall(int min, int max) CASE_TEST(select_null); EXPECT_SYSZR(1, ({ struct timeval tv = { 0 }; select(0, NULL, NULL, NULL, &tv); })); break; CASE_TEST(select_stdout); EXPECT_SYSNE(1, ({ fd_set fds; FD_ZERO(&fds); FD_SET(1, &fds); select(2, NULL, &fds, NULL, NULL); }), -1); break; CASE_TEST(select_fault); EXPECT_SYSER(1, select(1, (void *)1, NULL, NULL, 0), -1, EFAULT); break; + CASE_TEST(sendfile); EXPECT_SYSZR(1, test_sendfile()); break; CASE_TEST(stat_blah); EXPECT_SYSER(1, stat("/proc/self/blah", &stat_buf), -1, ENOENT); break; CASE_TEST(stat_fault); EXPECT_SYSER(1, stat(NULL, &stat_buf), -1, EFAULT); break; CASE_TEST(stat_rdev); EXPECT_SYSZR(1, ({ int ret = stat("/dev/null", &stat_buf); ret ?: stat_buf.st_rdev != makedev(1, 3); })); break; From 801ade22c4fc078ea9b11e1f3fe3aae8b8bef886 Mon Sep 17 00:00:00 2001 From: Daniel Schwierzeck Date: Fri, 21 Aug 2026 10:45:39 +0200 Subject: [PATCH 0009/2286] cpufreq: airoha: fix probe failure with correct error check dev_pm_domain_attach_list() returns the number of attached PM domains on success (a positive count), or a negative error code on failure. The driver checks "if (ret)" which treats a successful attachment of one or more PM domains as an error, causing the probe to fail with return code 1. Fix by checking "if (ret < 0)" like all other users of this API. Cc: stable@vger.kernel.org Fixes: 84cf9e541ccc ("cpufreq: airoha: Add EN7581 CPUFreq SMCCC driver") Signed-off-by: Daniel Schwierzeck Signed-off-by: Christian Marangi Reviewed-by: Zhongqiu Han Signed-off-by: Viresh Kumar --- drivers/cpufreq/airoha-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/airoha-cpufreq.c b/drivers/cpufreq/airoha-cpufreq.c index 3e7770860d13c8..8283c528d0c145 100644 --- a/drivers/cpufreq/airoha-cpufreq.c +++ b/drivers/cpufreq/airoha-cpufreq.c @@ -64,7 +64,7 @@ static int airoha_cpufreq_probe(struct platform_device *pdev) /* Attach PM for OPP */ ret = dev_pm_domain_attach_list(cpu_dev, &attach_data, &priv->pd_list); - if (ret) + if (ret < 0) goto clear_opp_config; cpufreq_dt = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); From 79c8aa4da3d5453dd956d2170d1c9c716311ea06 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 13 Aug 2026 04:40:26 +0200 Subject: [PATCH 0010/2286] microblaze: Fix MB_MANAGER build with older binutils Commit 88707ebe77e2 ("microblaze: Add custom break vector handler for mb manager") added the suspend mnemonic to the MB Manager break handler. GNU as only learned this mnemonic in binutils 2.42. Linux supports binutils 2.30, where enabling CONFIG_MB_MANAGER fails: arch/microblaze/kernel/entry.S:946: Error: unknown opcode "suspend" The suspend mnemonic is an alias for mbar 24, and both encode as 0xbb020004. Use the underlying instruction, which GNU as 2.30 already supports. Fixes: 88707ebe77e2 ("microblaze: Add custom break vector handler for mb manager") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202310310151.UhB396Qs-lkp@intel.com/ Suggested-by: Michal Simek Cc: stable@vger.kernel.org Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260813024026.5402-1-kmehltretter@gmail.com Signed-off-by: Michal Simek --- arch/microblaze/kernel/entry.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index 582d7256d81547..ad44341a5dbc72 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -943,7 +943,7 @@ ret_from_break: mbar 1 mbar 2 bri 4 - suspend + mbar 24 /* suspend */ nop #endif From e44c5ef7a05c5cfc996be1aafedfdee3edd96c4c Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:48 +0200 Subject: [PATCH 0011/2286] microblaze: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Link: https://patch.msgid.link/20260801191002.1383835-2-Julia.Lawall@inria.fr Signed-off-by: Michal Simek --- arch/microblaze/kernel/cpu/cpuinfo-pvr-full.c | 2 +- arch/microblaze/kernel/cpu/cpuinfo-static.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/microblaze/kernel/cpu/cpuinfo-pvr-full.c b/arch/microblaze/kernel/cpu/cpuinfo-pvr-full.c index c7ee51b0900e69..1cc3fd9846ad1e 100644 --- a/arch/microblaze/kernel/cpu/cpuinfo-pvr-full.c +++ b/arch/microblaze/kernel/cpu/cpuinfo-pvr-full.c @@ -23,7 +23,7 @@ #define CI(c, p) { ci->c = PVR_##p(pvr); } #define err_printk(x) \ - pr_err("ERROR: Microblaze " x "-different for PVR and DTS\n"); + pr_err("ERROR: Microblaze " x "-different for PVR and DTS\n") void set_cpuinfo_pvr_full(struct cpuinfo *ci, struct device_node *cpu) { diff --git a/arch/microblaze/kernel/cpu/cpuinfo-static.c b/arch/microblaze/kernel/cpu/cpuinfo-static.c index 03da36dc6d9c92..1e4d30b6cb01a8 100644 --- a/arch/microblaze/kernel/cpu/cpuinfo-static.c +++ b/arch/microblaze/kernel/cpu/cpuinfo-static.c @@ -18,7 +18,7 @@ static const char family_string[] = CONFIG_XILINX_MICROBLAZE0_FAMILY; static const char cpu_ver_string[] = CONFIG_XILINX_MICROBLAZE0_HW_VER; #define err_printk(x) \ - pr_err("ERROR: Microblaze " x "-different for kernel and DTS\n"); + pr_err("ERROR: Microblaze " x "-different for kernel and DTS\n") void __init set_cpuinfo_static(struct cpuinfo *ci, struct device_node *cpu) { From 1c7df6fa838a46f0d5090ef134f67511f0592f33 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Wed, 12 Aug 2026 16:44:38 -0500 Subject: [PATCH 0012/2286] devtmpfs: restore mount option reconfiguration on get_tree commit cb0e0a8bf4e1 ("devtmpfs: replace ->mount with ->get_tree in public instance") converted the public devtmpfs instance to the new mount API but broke the ability to reconfigure the shared superblock options via subsequent mount() calls, as had been fixed once in a6097180d884 ("devtmpfs regression fix: reconfigure on each mount") This makes boot-time defaults permanent. Fix this using an approach suggested by Al Viro: at init_fs_context time, create a reconfigure-mode context via fs_context_for_reconfigure() and save it in fc->fs_private. The devtmpfs parse ops can then use this context to parse options appropriately for the underlying filesystem. This also removes the devtmpfs_configure_context() one-time ops-copying approach and the global mutable ops table it required. Fixes: cb0e0a8bf4e1 ("devtmpfs: replace ->mount with ->get_tree in public instance") Reported-by: Simon Liebold Cc: stable@vger.kernel.org Suggested-by: Al Viro Signed-off-by: Eric Sandeen Link: https://patch.msgid.link/b1bd3b15-78ea-4487-b6a8-aad0b05dd3bb@redhat.com Tested-by: Simon Liebold Signed-off-by: Christian Brauner (Amutable) --- drivers/base/devtmpfs.c | 108 ++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index aef0fcc6aba1e0..11c70888f38be5 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -72,39 +72,90 @@ static struct file_system_type internal_fs_type = { .kill_sb = kill_anon_super, }; -/* Simply take a ref on the existing mount */ +struct devtmpfs_context { + struct fs_context *fc; +}; + +static void devtmpfs_free(struct fs_context *fc) +{ + struct devtmpfs_context *ctx = fc->fs_private; + + if (ctx) { + put_fs_context(ctx->fc); + kfree(ctx); + } +} + +static int devtmpfs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct devtmpfs_context *ctx = fc->fs_private; + + return ctx->fc->ops->parse_param(ctx->fc, param); +} + +static int devtmpfs_parse_monolithic(struct fs_context *fc, void *data) +{ + struct devtmpfs_context *ctx = fc->fs_private; + + if (ctx->fc->ops->parse_monolithic) + return ctx->fc->ops->parse_monolithic(ctx->fc, data); + return generic_parse_monolithic(ctx->fc, data); +} + static int devtmpfs_get_tree(struct fs_context *fc) { + struct devtmpfs_context *ctx = fc->fs_private; struct super_block *sb = mnt->mnt_sb; + int err; atomic_inc(&sb->s_active); down_write(&sb->s_umount); + + if (ctx->fc->ops->reconfigure) { + err = ctx->fc->ops->reconfigure(ctx->fc); + if (err) { + deactivate_locked_super(sb); + return err; + } + } + fc->root = dget(sb->s_root); return 0; } -/* Ops are filled in during init depending on underlying shmem or ramfs type */ -static struct fs_context_operations devtmpfs_context_ops = {}; +static const struct fs_context_operations devtmpfs_context_ops = { + .free = devtmpfs_free, + .parse_param = devtmpfs_parse_param, + .parse_monolithic = devtmpfs_parse_monolithic, + .get_tree = devtmpfs_get_tree, +}; -/* Call the underlying initialization and set to our ops */ static int devtmpfs_init_fs_context(struct fs_context *fc) { - int ret; -#ifdef CONFIG_TMPFS - ret = shmem_init_fs_context(fc); -#else - ret = ramfs_init_fs_context(fc); -#endif - if (ret < 0) - return ret; + struct devtmpfs_context *ctx; + int err; + + ctx = kzalloc_obj(struct devtmpfs_context); + if (!ctx) + return -ENOMEM; + + /* Each mount will reconfigure the shared superblock w/ new options */ + ctx->fc = fs_context_for_reconfigure(mnt->mnt_root, + mnt->mnt_sb->s_flags, MS_RMT_MASK); + if (IS_ERR(ctx->fc)) { + err = PTR_ERR(ctx->fc); + kfree(ctx); + return err; + } + fc->fs_private = ctx; fc->ops = &devtmpfs_context_ops; return 0; } static struct file_system_type dev_fs_type = { - .name = "devtmpfs", + .name = "devtmpfs", .init_fs_context = devtmpfs_init_fs_context, }; @@ -442,31 +493,6 @@ static int __ref devtmpfsd(void *p) return 0; } -/* - * Get the underlying (shmem/ramfs) context ops to build ours - */ -static int devtmpfs_configure_context(void) -{ - struct fs_context *fc; - - fc = fs_context_for_reconfigure(mnt->mnt_root, mnt->mnt_sb->s_flags, - MS_RMT_MASK); - if (IS_ERR(fc)) - return PTR_ERR(fc); - - /* Set up devtmpfs_context_ops based on underlying type */ - devtmpfs_context_ops.free = fc->ops->free; - devtmpfs_context_ops.dup = fc->ops->dup; - devtmpfs_context_ops.parse_param = fc->ops->parse_param; - devtmpfs_context_ops.parse_monolithic = fc->ops->parse_monolithic; - devtmpfs_context_ops.get_tree = &devtmpfs_get_tree; - devtmpfs_context_ops.reconfigure = fc->ops->reconfigure; - - put_fs_context(fc); - - return 0; -} - /* * Create devtmpfs instance, driver-core devices will add their device * nodes here. @@ -482,12 +508,6 @@ int __init devtmpfs_init(void) return PTR_ERR(mnt); } - err = devtmpfs_configure_context(); - if (err) { - pr_err("unable to configure devtmpfs type %d\n", err); - return err; - } - err = register_filesystem(&dev_fs_type); if (err) { pr_err("unable to register devtmpfs type %d\n", err); From 5550456dad376f1c215f38f019f129b9218a740e Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Thu, 21 May 2026 15:47:56 +0530 Subject: [PATCH 0013/2286] selftests/filesystems: Move file_stressor to dedicated subdirectory The file_stressor test requires a 30-minute timeout (1800 seconds) due to its 15-minute runtime plus setup/teardown overhead. This is significantly longer than the default 45-second timeout used by other filesystem tests. Move file_stressor into its own subdirectory (filesystems/file_stressor/) with a dedicated settings file to isolate its timeout configuration. This prevents the long timeout from affecting other fast-running tests in the filesystems/ directory. This restructuring follows the pattern used by other selftests with special requirements and ensures proper timeout handling in CI/CD environments while maintaining the default 45-second timeout for other filesystem tests. Reported-by: kernel test robot Closes: https://lore.kernel.org/all/202605192100.DvRgEGZ3-lkp@intel.com/ Signed-off-by: Disha Goel Link: https://patch.msgid.link/20260521101758.14639-1-disgoel@linux.ibm.com Tested-by: Yeswanth Krishna Reviewed-by: Ojaswin Mujoo Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/filesystems/.gitignore | 1 - tools/testing/selftests/filesystems/Makefile | 2 +- .../testing/selftests/filesystems/file_stressor/.gitignore | 2 ++ tools/testing/selftests/filesystems/file_stressor/Makefile | 6 ++++++ .../filesystems/{ => file_stressor}/file_stressor.c | 0 tools/testing/selftests/filesystems/file_stressor/settings | 3 +++ 7 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tools/testing/selftests/filesystems/file_stressor/.gitignore create mode 100644 tools/testing/selftests/filesystems/file_stressor/Makefile rename tools/testing/selftests/filesystems/{ => file_stressor}/file_stressor.c (100%) create mode 100644 tools/testing/selftests/filesystems/file_stressor/settings diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 2d960626750e3e..08f228765a930f 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -37,6 +37,7 @@ TARGETS += filesystems/binderfs TARGETS += filesystems/epoll TARGETS += filesystems/failfs TARGETS += filesystems/fat +TARGETS += filesystems/file_stressor TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 9eb185fb2f9ddb..57f5bbdbedff1e 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -2,7 +2,6 @@ dnotify_test devpts_pts fclog -file_stressor anon_inode_test kernfs_test idmapped_tmpfile diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 03be337c1f351a..bc4bfb67758902 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += $(KHDR_INCLUDES) -TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test +TEST_GEN_PROGS := devpts_pts anon_inode_test kernfs_test fclog ustat_test TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test diff --git a/tools/testing/selftests/filesystems/file_stressor/.gitignore b/tools/testing/selftests/filesystems/file_stressor/.gitignore new file mode 100644 index 00000000000000..1eb3f40077d3f1 --- /dev/null +++ b/tools/testing/selftests/filesystems/file_stressor/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +file_stressor diff --git a/tools/testing/selftests/filesystems/file_stressor/Makefile b/tools/testing/selftests/filesystems/file_stressor/Makefile new file mode 100644 index 00000000000000..88c8231ac14435 --- /dev/null +++ b/tools/testing/selftests/filesystems/file_stressor/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS += $(KHDR_INCLUDES) +TEST_GEN_PROGS := file_stressor + +include ../../lib.mk diff --git a/tools/testing/selftests/filesystems/file_stressor.c b/tools/testing/selftests/filesystems/file_stressor/file_stressor.c similarity index 100% rename from tools/testing/selftests/filesystems/file_stressor.c rename to tools/testing/selftests/filesystems/file_stressor/file_stressor.c diff --git a/tools/testing/selftests/filesystems/file_stressor/settings b/tools/testing/selftests/filesystems/file_stressor/settings new file mode 100644 index 00000000000000..b675ca93f93691 --- /dev/null +++ b/tools/testing/selftests/filesystems/file_stressor/settings @@ -0,0 +1,3 @@ +# Timeout for file_stressor test +# The test runs for 900 seconds (15 minutes) plus setup/teardown time +timeout=1800 From 0ef1e65c9f63c37d4665869263e9736281d9773b Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Thu, 20 Aug 2026 22:05:04 -0700 Subject: [PATCH 0014/2286] kernfs: Use VFS lookup context in d_revalidate() The VFS supplies a stable parent inode and expected name to the revalidation callback. Use them instead of recovering the same information from mutable dentry fields. Compare the name using its explicit length because it may point into the pathname and need not be terminated at name->len. This also prepares the callback for lockless operation. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260821050507.2161607-2-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 82bbaeb326aab5..541bb5525437b0 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1171,23 +1171,19 @@ struct kernfs_node *kernfs_create_empty_dir(struct kernfs_node *parent, static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, struct dentry *dentry, unsigned int flags) { - struct kernfs_node *kn, *parent; + struct kernfs_node *kn, *kn_parent; + struct kernfs_node *parent = dir->i_private; struct kernfs_root *root; + const char *kn_name; if (flags & LOOKUP_RCU) return -ECHILD; /* Negative hashed dentry? */ if (d_really_is_negative(dentry)) { - /* If the kernfs parent node has changed discard and - * proceed to ->lookup. - * - * There's nothing special needed here when getting the - * dentry parent, even if a concurrent rename is in - * progress. That's because the dentry is negative so - * it can only be the target of the rename and it will - * be doing a d_move() not a replace. Consequently the - * dentry d_parent won't change over the d_move(). + /* + * If the kernfs parent node has changed discard and proceed to + * ->lookup. * * Also kernfs negative dentries transitioning from * negative to positive during revalidate won't happen @@ -1195,14 +1191,11 @@ static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, * changes and the lookup re-done so that a new positive * dentry can be properly created. */ - root = kernfs_root_from_sb(dentry->d_sb); + root = kernfs_root(parent); down_read(&root->kernfs_rwsem); - parent = kernfs_dentry_node(dentry->d_parent); - if (parent) { - if (kernfs_dir_changed(parent, dentry)) { - up_read(&root->kernfs_rwsem); - return 0; - } + if (kernfs_dir_changed(parent, dentry)) { + up_read(&root->kernfs_rwsem); + return 0; } up_read(&root->kernfs_rwsem); @@ -1220,18 +1213,20 @@ static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, if (!kernfs_active(kn)) goto out_bad; - parent = kernfs_parent(kn); + kn_parent = kernfs_parent(kn); /* The kernfs node has been moved? */ - if (kernfs_dentry_node(dentry->d_parent) != parent) + if (parent != kn_parent) goto out_bad; /* The kernfs node has been renamed */ - if (strcmp(dentry->d_name.name, kernfs_rcu_name(kn)) != 0) + kn_name = kernfs_rcu_name(kn); + if (name->len != strlen(kn_name) || + memcmp(name->name, kn_name, name->len)) goto out_bad; /* The kernfs node has been moved to a different namespace */ - if (parent && kernfs_ns_enabled(parent) && - kernfs_ns_id(kernfs_info(dentry->d_sb)->ns) != kernfs_ns_id(kn->ns)) + if (kn_parent && kernfs_ns_enabled(kn_parent) && + kernfs_ns_id(kernfs_info(dir->i_sb)->ns) != kernfs_ns_id(kn->ns)) goto out_bad; up_read(&root->kernfs_rwsem); From ea1c84cca6ab94c7680a68a2560fcf992f3728ba Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Thu, 20 Aug 2026 22:05:05 -0700 Subject: [PATCH 0015/2286] kernfs: Prepare directory revisions for lockless reads Negative dentry revalidation only needs to sample the parent directory generation and compare it with the value recorded at lookup time. Annotate those accesses with READ_ONCE() and WRITE_ONCE() so the comparison can safely move outside kernfs_rwsem. Revision updates remain serialized by kernfs_rwsem. Assert that writer contract in kernfs_inc_rev(); the read half of the increment stays plain because the semaphore excludes other writers. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260821050507.2161607-3-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/kernfs-internal.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index aa784b540b3616..20a0cf42ba8d40 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -147,20 +147,19 @@ static inline struct kernfs_node *kernfs_dentry_node(struct dentry *dentry) static inline void kernfs_set_rev(struct kernfs_node *parent, struct dentry *dentry) { - dentry->d_time = parent->dir.rev; + WRITE_ONCE(dentry->d_time, READ_ONCE(parent->dir.rev)); } static inline void kernfs_inc_rev(struct kernfs_node *parent) { - parent->dir.rev++; + lockdep_assert_held_write(&parent->dir.root->kernfs_rwsem); + WRITE_ONCE(parent->dir.rev, parent->dir.rev + 1); } static inline bool kernfs_dir_changed(struct kernfs_node *parent, struct dentry *dentry) { - if (parent->dir.rev != dentry->d_time) - return true; - return false; + return READ_ONCE(parent->dir.rev) != READ_ONCE(dentry->d_time); } extern const struct super_operations kernfs_sops; From 4c291d7d3dbd449ead467219b00f1a669358da38 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Thu, 20 Aug 2026 22:05:06 -0700 Subject: [PATCH 0016/2286] kernfs: Avoid namespace dereference in d_revalidate() Commit 1fe989e1c42a ("kernfs: use namespace id instead of pointer for hashing and comparison") changed dentry revalidation to compare namespace IDs along with the comparisons that determine visible directory ordering. Dereferencing a namespace tag that kernfs_rename_ns() can replace is not suitable once dentry revalidation stops taking kernfs_rwsem. Use pointer equality for this non-user-visible equality check instead. Namespace IDs uniquely identify namespace objects, so pointer and ID equality cannot disagree for valid tags. Hashing and directory ordering continue to use IDs. kn->ns becomes a lockless read in the next commit, so mark both sides of it now. The read is in kernfs_dop_revalidate(); the stores that can run while the node is visible are the two in kernfs_rename_ns(). The remaining stores, in kernfs_create_dir_ns(), kernfs_create_empty_dir() and kernfs_create_link(), all precede kernfs_add_one() and need no marking. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260821050507.2161607-4-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 541bb5525437b0..27949b0e027c33 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1226,7 +1226,7 @@ static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, /* The kernfs node has been moved to a different namespace */ if (kn_parent && kernfs_ns_enabled(kn_parent) && - kernfs_ns_id(kernfs_info(dir->i_sb)->ns) != kernfs_ns_id(kn->ns)) + kernfs_info(dir->i_sb)->ns != READ_ONCE(kn->ns)) goto out_bad; up_read(&root->kernfs_rwsem); @@ -1873,7 +1873,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, rcu_assign_pointer(kn->__parent, new_parent); - kn->ns = new_ns; + WRITE_ONCE(kn->ns, new_ns); if (new_name) rcu_assign_pointer(kn->name, new_name); @@ -1881,7 +1881,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, kernfs_put(old_parent); } else { /* name assignment is RCU protected, parent is the same */ - kn->ns = new_ns; + WRITE_ONCE(kn->ns, new_ns); if (new_name) rcu_assign_pointer(kn->name, new_name); } From 8c2a5e65a977685056513677c45d4f08d87ef081 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Thu, 20 Aug 2026 22:05:07 -0700 Subject: [PATCH 0017/2286] kernfs: Remove kernfs_rwsem from dentry revalidation kernfs_dop_revalidate() takes kernfs_rwsem for read once per path component of every walk into a kernfs mount. Linux rwsems do not permit reader lock stealing once a writer is queued, so a single writer parks the whole incoming reader stream in uninterruptible sleep, stalling cgroup-polling daemons for minutes. Nothing the callback reads requires the semaphore. kn->active is an atomic_t that kernfs_find_and_get_node_by_id() already tests through __kernfs_active(); kn->__parent and kn->name are RCU pointers whose old values are freed only after a grace period; kn->ns is now compared rather than dereferenced; parent->dir.rev was annotated earlier in this series. What the semaphore does provide is a coherent snapshot, and that is not needed. ->d_revalidate() answers a question about a single instant, and the answer is already stale when it returns: a rename landing just after up_read() gives the same outcome as one observed mid-read. A lockless reader can only return "valid" for the (parent, name, namespace) triple identifying the dentry it was handed, and that triple was true when the dentry was instantiated, so it reports a genuine past state exactly as the locked version did. Removal is backstopped by kernfs_get_active() failing in the subsequent open(). Take an RCU read lock instead. kernfs_parent() and kernfs_rcu_name() work unchanged: the condition in their rcu_dereference_check() is an alternative to holding the RCU read lock, not an extra requirement. The negative dentry path needs nothing, as @dir pins the parent. The namespace check can use @parent directly once the preceding check establishes it equals kernfs_parent(kn), so the kn_parent local and its NULL test go away. kernfs_ns_enabled() reads @parent->flags, which KERNFS_ACTIVATED and KERNFS_REMOVING update as a plain read-modify-write under kernfs_rwsem. Those bits are not read here and KERNFS_NS cannot change once the directory has children, so mark the read data_race() rather than READ_ONCE(), which would not silence KCSAN against the unmarked writers anyway. kernfs_iop_permission() still forces every walk out of RCU-walk before children are revalidated, so lifting the LOOKUP_RCU bail here would have no observable effect; it is left to the series fixing that path. Readers walking cgroupfs and sysfs against concurrent cgroup and netdev churn: kernfs_rwsem read acquisitions drop from 48,593,360 to 1,280,280, and kernfs_dop_revalidate() no longer appears among its contention sites. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260821050507.2161607-5-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 27949b0e027c33..cd7a8ff8b6b212 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1171,9 +1171,8 @@ struct kernfs_node *kernfs_create_empty_dir(struct kernfs_node *parent, static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, struct dentry *dentry, unsigned int flags) { - struct kernfs_node *kn, *kn_parent; struct kernfs_node *parent = dir->i_private; - struct kernfs_root *root; + struct kernfs_node *kn; const char *kn_name; if (flags & LOOKUP_RCU) @@ -1191,49 +1190,41 @@ static int kernfs_dop_revalidate(struct inode *dir, const struct qstr *name, * changes and the lookup re-done so that a new positive * dentry can be properly created. */ - root = kernfs_root(parent); - down_read(&root->kernfs_rwsem); - if (kernfs_dir_changed(parent, dentry)) { - up_read(&root->kernfs_rwsem); - return 0; - } - up_read(&root->kernfs_rwsem); - - /* The kernfs parent node hasn't changed, leave the - * dentry negative and return success. - */ - return 1; + return !kernfs_dir_changed(parent, dentry); } kn = kernfs_dentry_node(dentry); - root = kernfs_root(kn); - down_read(&root->kernfs_rwsem); + + guard(rcu)(); /* The kernfs node has been deactivated */ - if (!kernfs_active(kn)) - goto out_bad; + if (!__kernfs_active(kn)) + return 0; - kn_parent = kernfs_parent(kn); /* The kernfs node has been moved? */ - if (parent != kn_parent) - goto out_bad; + if (kernfs_parent(kn) != parent) + return 0; /* The kernfs node has been renamed */ kn_name = kernfs_rcu_name(kn); if (name->len != strlen(kn_name) || memcmp(name->name, kn_name, name->len)) - goto out_bad; + return 0; - /* The kernfs node has been moved to a different namespace */ - if (kn_parent && kernfs_ns_enabled(kn_parent) && + /* + * The kernfs node has been moved to a different namespace. + * + * KERNFS_NS is set by kernfs_enable_ns() while @parent still has no + * children, so it cannot change while a child of @parent is being + * revalidated. The other bits in that word, KERNFS_ACTIVATED and + * KERNFS_REMOVING, are updated under kernfs_rwsem and are not read + * here, so racing with them is intentional and harmless. + */ + if (data_race(kernfs_ns_enabled(parent)) && kernfs_info(dir->i_sb)->ns != READ_ONCE(kn->ns)) - goto out_bad; + return 0; - up_read(&root->kernfs_rwsem); return 1; -out_bad: - up_read(&root->kernfs_rwsem); - return 0; } const struct dentry_operations kernfs_dops = { From 22b7f491be8f93ff72de0a3fe02b8ee9e64d3f21 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 24 Aug 2026 14:08:16 +0200 Subject: [PATCH 0018/2286] signal: allow taks to temporarily block TIF_NOTIFY_SIGNAL TIF_NOTIFY_SIGNAL is used to kick a task in uninterruptible sleep to return to userspace and run task work and then go back to sleep. This mechanism works well but breaks e.g., coredumps. dump_interrupted() only allows fatal signals to interrupt a coredump and the whole regular write path going to actual filesystems is impervious to TIF_NOTIFY_SIGNAL as well. Add PF_NO_NOTIFY_SIGNAL and helpers to raise and restore it. This is the same approach as memalloc_nofs_save(). signal_pending() will not report a fake pending signal via TIF_NOTIFY_SIGNAL if inside a PF_NO_NOTIFY_SIGNAL section. No functional changes. Link: https://patch.msgid.link/20260824-work-tif_notify_signal-v2-1-6609e42b3157@kernel.org Reviewed-by: Oleg Nesterov Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/sched.h | 2 +- include/linux/sched/signal.h | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 8b3d47a325cca9..e431c4195bf0cc 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1816,7 +1816,7 @@ extern struct pid *cad_pid; * I am cleaning dirty pages from some other bdi. */ #define PF_KTHREAD 0x00200000 /* I am a kernel thread */ #define PF_RANDOMIZE 0x00400000 /* Randomize virtual address space */ -#define PF__HOLE__00800000 0x00800000 +#define PF_NO_NOTIFY_SIGNAL 0x00800000 /* see no_notify_signal_save() */ #define PF__HOLE__01000000 0x01000000 #define PF__HOLE__02000000 0x02000000 #define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */ diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index 584ae88b435e9e..6164995da98098 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -2,6 +2,7 @@ #ifndef _LINUX_SCHED_SIGNAL_H #define _LINUX_SCHED_SIGNAL_H +#include #include #include #include @@ -384,14 +385,36 @@ static inline int task_sigpending(struct task_struct *p) return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING)); } +/* Prevent TIF_NOTIFY_SIGNAL from interrupting this task. */ +static inline unsigned int no_notify_signal_save(void) +{ + unsigned int flags = current->flags; + + current->flags |= PF_NO_NOTIFY_SIGNAL; + return flags; +} + +/* Restore the previous PF_NO_NOTIFY_SIGNAL state. */ +static inline void no_notify_signal_restore(unsigned int flags) +{ + current_restore_flags(flags, PF_NO_NOTIFY_SIGNAL); +} + +DEFINE_LOCK_GUARD_0(no_notify_signal, + _T->flags = no_notify_signal_save(), + no_notify_signal_restore(_T->flags), + unsigned int flags) + static inline int signal_pending(struct task_struct *p) { /* * TIF_NOTIFY_SIGNAL isn't really a signal, but it requires the same * behavior in terms of ensuring that we break out of wait loops - * so that notify signal callbacks can be processed. + * so that notify signal callbacks can be processed. Not for a task + * that asked not to be interrupted by it, see no_notify_signal_save(). */ - if (unlikely(test_tsk_thread_flag(p, TIF_NOTIFY_SIGNAL))) + if (unlikely(test_tsk_thread_flag(p, TIF_NOTIFY_SIGNAL)) && + likely(!(READ_ONCE(p->flags) & PF_NO_NOTIFY_SIGNAL))) return 1; return task_sigpending(p); } From a58d647acfdc62561f3570d114025b3bf38ae41b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 24 Aug 2026 14:08:17 +0200 Subject: [PATCH 0019/2286] coredump: prevent TIF_NOTIFY_SIGNAL from interrupting coredumps TIF_NOTIFY_SIGNAL is used to kick a task in uninterruptible sleep to return to userspace and run task work and then go back to sleep. This mechanism works well but breaks coredumps. dump_interrupted() only allows fatal signals to interrupt a coredump and the whole regular write path going to actual filesystems is impervious to TIF_NOTIFY_SIGNAL as well. However, both the usermodehelper pipe and the coredump socket will bail early on TIF_NOTIFY_SIGNAL. This affects the following codepaths: - coredump_sock_recv() -> unix_stream_read_generic() The request/ack handshake is abandoned before any coredump data is sent. - anon_pipe_write() returning -ERESTARTSYS Once the pipe is full this truncates the coredump. - unix_stream_sendmsg() returning -ERESTARTSYS Once the send buffer is full this truncates the coredump. - coredump_sock_wait() -> __kernel_read() This reports a failure that didn't happen. - wait_for_dump_helpers() -> wait_event_interruptible() This stops waiting for the coredump helpers. Such truncation is entirely invisible to userspace and all uapi bits still indicate that a successful coredump happened. A crashing process with a bunch of file backed mappings and io_uring thrown in loses most of the coredump data. If the NT_FILE note goes past PAGE_SIZE mappings it's gonzo. TIF_NOTIFY_SIGNAL is sent by io_uring for the common case. And it uses poll without sleeping so a completion callback runs task_work_add() from interrupt context against the task that submitted the request. This is the task that is running the coredump. Since that task hasn't set work_exited (it hasn't exited yet after all) TIF_NOTIFY_SIGNAL keeps reappearing. A coredumping task doesn't return to userspace. The task work is run at exit. So interrupting it doesn't buy anything and just loses the coredump which is quite valuable. Note that this isn't specific to io_uring. There's also klp_send_signals(), bpf_task_work_schedule_signal(), landlock's tsync and then technically, kthread_stop() and the printk kunit test set the bit raw. Link: https://patch.msgid.link/20260824-work-tif_notify_signal-v2-2-6609e42b3157@kernel.org Fixes: 12db8b690010 ("entry: Add support for TIF_NOTIFY_SIGNAL") Reviewed-by: Oleg Nesterov Cc: stable@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/coredump.c b/fs/coredump.c index ac3cd74808c640..7c0f276c3e0d6a 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1191,6 +1191,8 @@ void vfs_coredump(const kernel_siginfo_t *siginfo) if (coredump_wait(siginfo->si_signo, &core_state) < 0) return; + /* Task work must not cut the dump short, see signal_pending(). */ + guard(no_notify_signal)(); scoped_with_creds(cred) do_coredump(&cn, &cprm, &argv, &argc, binfmt); coredump_cleanup(&cn, &cprm); From 7ac8d57a436c84a67effd3ba0bb7ec7017b385bc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 24 Aug 2026 14:08:18 +0200 Subject: [PATCH 0020/2286] selftests/coredump: test that TIF_NOTIFY_SIGNAL doesn't truncate a coredump Add a test that verifies that a coredump cannot be cut short by TIF_NOTIFY_SIGNAL through io_uring running task work for uninterruptible tasks. Link: https://patch.msgid.link/20260824-work-tif_notify_signal-v2-3-6609e42b3157@kernel.org Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/coredump/Makefile | 7 +- .../coredump/coredump_notify_signal.h | 29 ++ .../coredump/coredump_notify_signal_helper.c | 46 +++ .../coredump/coredump_notify_signal_test.c | 245 ++++++++++++++ .../selftests/coredump/coredump_test.h | 1 + .../coredump/coredump_test_helpers.c | 311 ++++++++++++++++++ 6 files changed, 638 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/coredump/coredump_notify_signal.h create mode 100644 tools/testing/selftests/coredump/coredump_notify_signal_helper.c create mode 100644 tools/testing/selftests/coredump/coredump_notify_signal_test.c diff --git a/tools/testing/selftests/coredump/Makefile b/tools/testing/selftests/coredump/Makefile index dece1a31d561b0..728f3c342eb923 100644 --- a/tools/testing/selftests/coredump/Makefile +++ b/tools/testing/selftests/coredump/Makefile @@ -3,7 +3,10 @@ CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) TEST_GEN_PROGS := stackdump_test \ coredump_socket_test \ - coredump_socket_protocol_test + coredump_socket_protocol_test \ + coredump_notify_signal_test +# Spawned by the kernel as the |helper, not a test of its own. +TEST_GEN_FILES := coredump_notify_signal_helper TEST_FILES := stackdump include ../lib.mk @@ -11,3 +14,5 @@ include ../lib.mk $(OUTPUT)/stackdump_test: coredump_test_helpers.c $(OUTPUT)/coredump_socket_test: coredump_test_helpers.c $(OUTPUT)/coredump_socket_protocol_test: coredump_test_helpers.c +$(OUTPUT)/coredump_notify_signal_test: coredump_test_helpers.c +$(OUTPUT)/coredump_notify_signal_helper: coredump_test_helpers.c diff --git a/tools/testing/selftests/coredump/coredump_notify_signal.h b/tools/testing/selftests/coredump/coredump_notify_signal.h new file mode 100644 index 00000000000000..92868a43425cbc --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_notify_signal.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __COREDUMP_NOTIFY_SIGNAL_H +#define __COREDUMP_NOTIFY_SIGNAL_H + +#include +#include + +/* + * Define a bunch of constants we need. We create a situation where the + * NT_FILE note blows past 200K. That's way beyond the default 64K + * pipe ring and past the ~36K an af_unix skb holds. So we force a write + * to come back short. + */ +#define NOTIFY_SIGNAL_MAP_COUNT 4000 +#define NOTIFY_SIGNAL_ANON_BYTES (4UL << 20) +#define NOTIFY_SIGNAL_STALL_US 200000 +#define NOTIFY_SIGNAL_MAPFILE "/tmp/coredump.notify_signal.mapfile" +#define NOTIFY_SIGNAL_TRIGGER "/tmp/coredump.notify_signal.trigger" +#define NOTIFY_SIGNAL_CORE_FILE "/tmp/coredump.notify_signal.core" +#define NOTIFY_SIGNAL_CORE_TMPFILE "/tmp/coredump.notify_signal.core.tmp" +#define NOTIFY_SIGNAL_SOCKET "/tmp/coredump.notify_signal.socket" + +void crashing_child_notify_signal(void); +bool coredump_io_uring_available(void); +ssize_t recv_coredump_notify_signal(int fd, int fd_out, bool arm); +long long coredump_expected_size(const char *path); + +#endif /* __COREDUMP_NOTIFY_SIGNAL_H */ diff --git a/tools/testing/selftests/coredump/coredump_notify_signal_helper.c b/tools/testing/selftests/coredump/coredump_notify_signal_helper.c new file mode 100644 index 00000000000000..849f5c1ea7365b --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_notify_signal_helper.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * The |helper half of coredump_notify_signal_test. The kernel spawns this + * with the coredump on stdin, so it cannot be part of the test binary. + * It saves the dump and, once the notes have started, trips the fifo the + * crashing task is polling so TIF_NOTIFY_SIGNAL is raised while the note + * write is in flight. + */ + +#include +#include +#include +#include + +#include "coredump_notify_signal.h" + +int main(int argc, char *argv[]) +{ + int fd_core_file; + ssize_t ret; + + fd_core_file = open(NOTIFY_SIGNAL_CORE_TMPFILE, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); + if (fd_core_file < 0) { + fprintf(stderr, "%s: open failed: %m\n", argv[0]); + return EXIT_FAILURE; + } + + ret = recv_coredump_notify_signal(STDIN_FILENO, fd_core_file, true); + close(fd_core_file); + if (ret < 0) + goto err; + + /* The test polls for this name, so only create it once it is whole. */ + if (rename(NOTIFY_SIGNAL_CORE_TMPFILE, NOTIFY_SIGNAL_CORE_FILE)) { + fprintf(stderr, "%s: rename failed: %m\n", argv[0]); + goto err; + } + + return EXIT_SUCCESS; + +err: + unlink(NOTIFY_SIGNAL_CORE_TMPFILE); + return EXIT_FAILURE; +} diff --git a/tools/testing/selftests/coredump/coredump_notify_signal_test.c b/tools/testing/selftests/coredump/coredump_notify_signal_test.c new file mode 100644 index 00000000000000..4a98ab141c419f --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_notify_signal_test.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * A coredump is meant to be interrupted by SIGKILL and by the freezer and + * by nothing else. dump_interrupted() says so, but the blocking waits + * underneath it test signal_pending(), which is also true for + * TIF_NOTIFY_SIGNAL. A crashing task that has an io_uring completion land + * on it mid-dump therefore keeps dumping while every wait it enters bails + * out at once, and the dump is silently cut short. Nothing reports it: + * binfmt_elf sets has_dumped before it writes anything, so WCOREDUMP() + * says the dump worked. + * + * A coredump note is the only dump_emit() that exceeds what the transport + * takes in one go, so it is the one write that is certain to block. Both + * tests crash a child holding enough file backed mappings for its NT_FILE + * note to run past that, arm an io_uring poll on it, and trip the poll + * while the note is being written. What comes out has to be the whole + * dump. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "coredump_test.h" + +FIXTURE_SETUP(coredump) +{ + FILE *file; + int ret; + + self->pid_coredump_server = -ESRCH; + self->fd_tmpfs_detached = -1; + file = fopen("/proc/sys/kernel/core_pattern", "r"); + ASSERT_NE(NULL, file); + + ret = fread(self->original_core_pattern, 1, + sizeof(self->original_core_pattern), file); + ASSERT_TRUE(ret || feof(file)); + ASSERT_LT(ret, sizeof(self->original_core_pattern)); + + self->original_core_pattern[ret] = '\0'; + self->fd_tmpfs_detached = create_detached_tmpfs(); + ASSERT_GE(self->fd_tmpfs_detached, 0); + + ret = fclose(file); + ASSERT_EQ(0, ret); + + /* A stale core file from a killed previous run would fake a pass. */ + unlink(NOTIFY_SIGNAL_CORE_FILE); + /* And a stale socket would fail the server's bind. */ + unlink(NOTIFY_SIGNAL_SOCKET); + unlink(NOTIFY_SIGNAL_TRIGGER); + ASSERT_EQ(mkfifo(NOTIFY_SIGNAL_TRIGGER, 0600), 0); +} + +FIXTURE_TEARDOWN(coredump) +{ + const char *reason; + FILE *file; + int ret, status; + + if (self->pid_coredump_server > 0) { + kill(self->pid_coredump_server, SIGTERM); + waitpid(self->pid_coredump_server, &status, 0); + } + unlink(NOTIFY_SIGNAL_CORE_FILE); + unlink(NOTIFY_SIGNAL_CORE_TMPFILE); + unlink(NOTIFY_SIGNAL_SOCKET); + unlink(NOTIFY_SIGNAL_TRIGGER); + unlink(NOTIFY_SIGNAL_MAPFILE); + + file = fopen("/proc/sys/kernel/core_pattern", "w"); + if (!file) { + reason = "Unable to open core_pattern"; + goto fail; + } + + ret = fprintf(file, "%s", self->original_core_pattern); + if (ret < 0) { + reason = "Unable to write to core_pattern"; + goto fail; + } + + ret = fclose(file); + if (ret) { + reason = "Unable to close core_pattern"; + goto fail; + } + + if (self->fd_tmpfs_detached >= 0) { + ret = close(self->fd_tmpfs_detached); + if (ret < 0) { + reason = "Unable to close detached tmpfs"; + goto fail; + } + self->fd_tmpfs_detached = -1; + } + + return; +fail: + /* This should never happen */ + fprintf(stderr, "Failed to cleanup coredump test: %s\n", reason); +} + +/* Check that what the helper or the server saved is the whole dump. */ +static void check_whole_coredump(struct __test_metadata *const _metadata) +{ + long long expected; + struct stat st; + + expected = coredump_expected_size(NOTIFY_SIGNAL_CORE_FILE); + ASSERT_GT(expected, 0); + ASSERT_EQ(stat(NOTIFY_SIGNAL_CORE_FILE, &st), 0); + ASSERT_EQ((long long)st.st_size, expected); +} + +/* + * The dump goes to a |helper, so the reader is a separate program the + * kernel spawns. It saves what it received to NOTIFY_SIGNAL_CORE_FILE. + */ +TEST_F(coredump, notify_signal_pipe) +{ + char pattern[PATH_MAX], helper[PATH_MAX], *p; + struct stat st; + int status, i; + pid_t pid; + ssize_t n; + + if (!coredump_io_uring_available()) + SKIP(return, "io_uring not available"); + + n = readlink("/proc/self/exe", helper, sizeof(helper) - 1); + ASSERT_GT(n, 0); + helper[n] = '\0'; + p = strstr(helper, "coredump_notify_signal_test"); + ASSERT_NE(p, NULL); + ASSERT_LE((size_t)(p - helper) + sizeof("coredump_notify_signal_helper"), + sizeof(helper)); + strcpy(p, "coredump_notify_signal_helper"); + if (access(helper, X_OK)) + SKIP(return, "coredump_notify_signal_helper not built"); + + ASSERT_LT(snprintf(pattern, sizeof(pattern), "|%s", helper), + (int)sizeof(pattern)); + ASSERT_TRUE(set_core_pattern(pattern)); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_notify_signal(); + + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFSIGNALED(status)); + + /* The kernel does not wait for the helper, so poll for it. */ + for (i = 0; i < 100; i++) { + if (!stat(NOTIFY_SIGNAL_CORE_FILE, &st) && st.st_size) + break; + usleep(100000); + } + + check_whole_coredump(_metadata); +} + +/* The same thing with the dump going to a coredump socket. */ +TEST_F(coredump, notify_signal_socket) +{ + pid_t pid, pid_coredump_server; + int ipc_sockets[2], status; + char pattern[PATH_MAX]; + char c; + + if (!coredump_io_uring_available()) + SKIP(return, "io_uring not available"); + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, + ipc_sockets), 0); + ASSERT_LT(snprintf(pattern, sizeof(pattern), "@%s", + NOTIFY_SIGNAL_SOCKET), (int)sizeof(pattern)); + ASSERT_TRUE(set_core_pattern(pattern)); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_core_file = -1; + int exit_code = EXIT_FAILURE; + + close(ipc_sockets[0]); + + fd_server = create_and_listen_unix_socket(NOTIFY_SIGNAL_SOCKET); + if (fd_server < 0) + goto out; + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_core_file = open(NOTIFY_SIGNAL_CORE_FILE, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + 0600); + if (fd_core_file < 0) + goto out; + + if (recv_coredump_notify_signal(fd_coredump, fd_core_file, + true) < 0) + goto out; + + exit_code = EXIT_SUCCESS; +out: + if (fd_core_file >= 0) + close(fd_core_file); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_notify_signal(); + + ASSERT_EQ(waitpid(pid, &status, 0), pid); + ASSERT_TRUE(WIFSIGNALED(status)); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + + check_whole_coredump(_metadata); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h index ed47f01fa53c52..06f997ba47fd9a 100644 --- a/tools/testing/selftests/coredump/coredump_test.h +++ b/tools/testing/selftests/coredump/coredump_test.h @@ -9,6 +9,7 @@ #include "../kselftest_harness.h" #include "../pidfd/pidfd.h" +#include "coredump_notify_signal.h" #ifndef PAGE_SIZE #define PAGE_SIZE 4096 diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 2a20faf9cb0ad3..f8d416180d5b37 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1,11 +1,18 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include +#include #include #include #include +#include +#include +#include +#include #include #include +#include #include #include #include @@ -13,7 +20,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -21,6 +30,7 @@ #include "../filesystems/wrappers.h" #include "../pidfd/pidfd.h" +#include "coredump_notify_signal.h" /* Forward declarations to avoid including harness header */ struct __test_metadata; @@ -381,3 +391,304 @@ void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_fil close(fd_coredump); _exit(exit_code); } + +/* + * TIF_NOTIFY_SIGNAL coredump helpers. + * + * __dump_emit() takes anything short of a full write as the end of the + * dump, so every emit that blocks on a full transport can lose the rest + * of it. The NT_FILE note is the one emit that is certain to block, + * because it is the only one larger than the transport, so these helpers + * build a note large enough for that and then raise TIF_NOTIFY_SIGNAL on + * the dumping task while that write is in flight. + */ + +static int io_uring_setup_raw(unsigned int entries, struct io_uring_params *p) +{ + return syscall(__NR_io_uring_setup, entries, p); +} + +/* io_uring reads poll32_events back through swahw32() on big endian. */ +static __u32 notify_poll_mask(__u32 events) +{ +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN + return __swahw32(events); +#else + return events; +#endif +} + +bool coredump_io_uring_available(void) +{ + struct io_uring_params p = {}; + int fd; + + fd = io_uring_setup_raw(1, &p); + if (fd < 0) + return false; + close(fd); + return true; +} + +/* + * Arm a poll on @fd. io_uring leaves ctx->notify_method at TWA_SIGNAL + * unless the ring asks for SQPOLL or COOP_TASKRUN, so the completion runs + * set_notify_signal() against the task that submitted it. That is us, and + * we are about to become the coredumping task. + */ +static int arm_poll_notify(int trigger_fd) +{ + struct io_uring_params p = {}; + unsigned int *sq_tail, *sq_array; + struct io_uring_sqe *sqes; + size_t sqring_sz; + void *sq; + int ring; + + ring = io_uring_setup_raw(8, &p); + if (ring < 0) + return -1; + + sqring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned int); + sq = mmap(NULL, sqring_sz, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, ring, IORING_OFF_SQ_RING); + if (sq == MAP_FAILED) + return -1; + + sqes = mmap(NULL, p.sq_entries * sizeof(*sqes), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, ring, IORING_OFF_SQES); + if (sqes == MAP_FAILED) + return -1; + + sq_tail = (unsigned int *)((char *)sq + p.sq_off.tail); + sq_array = (unsigned int *)((char *)sq + p.sq_off.array); + + memset(&sqes[0], 0, sizeof(sqes[0])); + sqes[0].opcode = IORING_OP_POLL_ADD; + sqes[0].fd = trigger_fd; + sqes[0].poll32_events = notify_poll_mask(POLLIN); + + sq_array[0] = 0; + __atomic_store_n(sq_tail, 1, __ATOMIC_RELEASE); + + if (syscall(__NR_io_uring_enter, ring, 1, 0, 0, NULL, 0) < 0) + return -1; + + /* Deliberately leaked, we are about to crash. */ + return 0; +} + +/* + * Adjacent mappings with identical flags and contiguous file offsets are + * merged into one VMA, which would collapse NT_FILE back to nothing, so + * alternate the protection to keep every mapping an entry of its own. + * Not PROT_EXEC, /tmp is often mounted noexec. Nothing is ever written + * through these so they get no anon_vma and stay out of the dump itself. + */ +static int make_file_mappings(void) +{ + long pgsz = sysconf(_SC_PAGESIZE); + int fd, i; + + fd = open(NOTIFY_SIGNAL_MAPFILE, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); + if (fd < 0) + return 0; + if (ftruncate(fd, (off_t)NOTIFY_SIGNAL_MAP_COUNT * pgsz)) { + close(fd); + return 0; + } + + for (i = 0; i < NOTIFY_SIGNAL_MAP_COUNT; i++) { + int prot = (i & 1) ? PROT_READ : (PROT_READ | PROT_WRITE); + + if (mmap(NULL, pgsz, prot, MAP_PRIVATE, fd, + (off_t)i * pgsz) == MAP_FAILED) + break; + } + close(fd); + return i; +} + +void crashing_child_notify_signal(void) +{ + long pgsz = sysconf(_SC_PAGESIZE); + unsigned char *p; + int trigger_fd; + unsigned long off; + + /* Open the read side first so the reader's open() cannot block. */ + trigger_fd = open(NOTIFY_SIGNAL_TRIGGER, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + + /* Exit rather than crash: a dump without the poll armed proves nothing. */ + if (trigger_fd < 0) + _exit(EXIT_FAILURE); + + if (make_file_mappings() < NOTIFY_SIGNAL_MAP_COUNT) + _exit(EXIT_FAILURE); + + p = mmap(NULL, NOTIFY_SIGNAL_ANON_BYTES, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) + _exit(EXIT_FAILURE); + for (off = 0; off < NOTIFY_SIGNAL_ANON_BYTES; off += pgsz) + p[off] = 1; + + if (arm_poll_notify(trigger_fd)) + _exit(EXIT_FAILURE); + + /* crash on purpose */ + *(volatile int *)NULL = 0; + _exit(EXIT_FAILURE); +} + +static int pull_trigger(void) +{ + int fd; + + fd = open(NOTIFY_SIGNAL_TRIGGER, O_WRONLY | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "%s: open failed: %m\n", __func__); + return -1; + } + if (write(fd, "x", 1) != 1) { + fprintf(stderr, "%s: write failed: %m\n", __func__); + close(fd); + return -1; + } + close(fd); + return 0; +} + +/* + * phdr[0] is the PT_NOTE entry: elf_core_dump() emits it right after the + * ELF header. Its p_offset is where the notes begin. + */ +static long long note_offset(const unsigned char *hdr) +{ + ElfW(Phdr) ph; + ElfW(Ehdr) eh; + + memcpy(&eh, hdr, sizeof(eh)); + if (memcmp(eh.e_ident, ELFMAG, SELFMAG) || eh.e_type != ET_CORE) + return -1; + memcpy(&ph, hdr + sizeof(eh), sizeof(ph)); + if (ph.p_type != PT_NOTE) + return -1; + return (long long)ph.p_offset; +} + +/* + * Drain a coredump off @fd, counting what arrives. Once the notes have + * started the kernel is inside the one big note write, so poke the fifo + * the crashing task is polling and stop reading, which keeps the + * transport full and the write blocked with a partial count when the + * wakeup lands. Then carry on to end of file. + * + * What arrives is written to @fd_out when that is not negative. + * Returns the number of bytes received, or -1. Failing to trip the fifo + * is an error too: a dump that was never interrupted proves nothing. + */ +ssize_t recv_coredump_notify_signal(int fd, int fd_out, bool arm) +{ + unsigned char hdr[sizeof(ElfW(Ehdr)) + sizeof(ElfW(Phdr))]; + static char buf[64 << 10]; + long pgsz = sysconf(_SC_PAGESIZE); + long long note_off = 0; + size_t hdrlen = 0; + ssize_t total = 0; + bool armed = false; + + for (;;) { + ssize_t n = read(fd, buf, sizeof(buf)); + + if (n < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (n == 0) + break; + if (fd_out >= 0 && write(fd_out, buf, n) != n) + return -1; + + if (hdrlen < sizeof(hdr)) { + size_t want = sizeof(hdr) - hdrlen; + + if (want > (size_t)n) + want = (size_t)n; + memcpy(hdr + hdrlen, buf, want); + hdrlen += want; + if (hdrlen == sizeof(hdr)) + note_off = note_offset(hdr); + } + + total += n; + + if (arm && !armed && note_off > 0 && + total > note_off + (long long)pgsz) { + if (pull_trigger()) + return -1; + armed = true; + usleep(NOTIFY_SIGNAL_STALL_US); + continue; + } + } + + if (arm && !armed) + return -1; + + return total; +} + +/* + * How large the dump was meant to be. The ELF header and the program + * headers are the first thing emitted, so even a truncated dump says how + * far it should have run: the end is max(p_offset + p_filesz). + * + * That end is exact even when the last segment ends in a hole: + * coredump_write() flushes the pending cprm->to_skip with a final one + * byte emit and __dump_skip() writes zeroes for transports that cannot + * seek, so a whole dump carries every byte the headers promise. + */ +long long coredump_expected_size(const char *path) +{ + ElfW(Phdr) *phdr = NULL; + long long expected = 0; + unsigned int nphdr, i; + ElfW(Ehdr) eh; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -1; + if (read(fd, &eh, sizeof(eh)) != sizeof(eh)) + goto err; + if (memcmp(eh.e_ident, ELFMAG, SELFMAG) || eh.e_type != ET_CORE) + goto err; + if (!eh.e_phnum || eh.e_phentsize != sizeof(*phdr)) + goto err; + + nphdr = eh.e_phnum; + phdr = calloc(nphdr, sizeof(*phdr)); + if (!phdr) + goto err; + if (pread(fd, phdr, (size_t)nphdr * sizeof(*phdr), (off_t)eh.e_phoff) != + (ssize_t)((size_t)nphdr * sizeof(*phdr))) + goto err; + + for (i = 0; i < nphdr; i++) { + long long end = (long long)phdr[i].p_offset + + (long long)phdr[i].p_filesz; + if (end > expected) + expected = end; + } + + free(phdr); + close(fd); + return expected; +err: + free(phdr); + close(fd); + return -1; +} From d0ae35565f5d588b5912e0a5355ada4ca72d2c02 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 24 Aug 2026 14:08:19 +0200 Subject: [PATCH 0021/2286] smb: prevent TIF_NOTIFY_SIGNAL from interrupting smb blocks every signal during some sends: sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &oldmask); An incomplete or partial send would cause the connection to become out of sync. That in turn would cause the session to be torn down and reconnected. In commit 00be6f26a2a7 ("smb: client: transport: avoid reconnects triggered by pending task work") smb hand-rolled its own solution. Have it use the new no_notify_signal_save() critical section instead. This stops io_uring from cancelling a send in flight but that's just like it is today and it is bounded by sk_sndtimeo at around 15s. Link: https://patch.msgid.link/20260824-work-tif_notify_signal-v2-4-6609e42b3157@kernel.org Acked-by: Paulo Alcantara Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/smb/client/transport.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index fdf4e50c27ceb4..35fa699569e965 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -22,7 +22,6 @@ #include #include #include -#include #include "cifsglob.h" #include "cifsproto.h" #include "cifs_debug.h" @@ -172,15 +171,11 @@ smb_send_kvec(struct TCP_Server_Info *server, struct msghdr *smb_msg, * after the retries we will kill the socket and * reconnect which may clear the network problem. * - * Even if regular signals are masked, EINTR might be - * propagated from sk_stream_wait_memory() to here when - * TIF_NOTIFY_SIGNAL is used for task work. For example, - * certain io_uring completions will use that. Treat - * having EINTR with pending task work the same as EAGAIN - * to avoid unnecessary reconnects. + * Task work must not abort the send, see signal_pending(). */ - rc = sock_sendmsg(ssocket, smb_msg); - if (rc == -EAGAIN || unlikely(rc == -EINTR && task_work_pending(current))) { + scoped_guard(no_notify_signal) + rc = sock_sendmsg(ssocket, smb_msg); + if (rc == -EAGAIN) { retries++; if (retries >= 14 || (!server->noblocksnd && (retries > 2))) { From 5c0985dd3c32fc2bbd0e8beaf13cf52cd97376a2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 24 Aug 2026 14:08:20 +0200 Subject: [PATCH 0022/2286] pid_namespace: prevent TIF_NOTIFY_SIGNAL from interrupting the reaper Since commit 7fea700e04bd ("zap_pid_ns_processes: clear TIF_NOTIFY_SIGNAL along with TIF_SIGPENDING") zap_pid_ns_processes() clears TIF_NOTIFY_SIGNAL before every kernel_wait4() call. Otherwise kernel_wait4() doesn't sleep and returns -ERESTARTSYS if signal_pending() is true and the reaper busy-waits. But clearing TIF_NOTIFY_SIGNAL doesn't fix it. It can get raised again. For example, klp_send_signals() sets it raw against any task that hasn't transitioned yet. And it keeps sending it until the livepatch finishes. The sleep loop waiting for pid_allocated to drop has the same problem and it doesn't even clear anything. So a schedule() in TASK_INTERRUPTIBLE returns immediately. Use a no_notify_signal guard. signal_pending() will ignore TIF_NOTIFY_SIGNAL and the pid namespace reaper sleeps until a child exits or free_pid() wakes it. Link: https://patch.msgid.link/20260824-work-tif_notify_signal-v2-5-6609e42b3157@kernel.org Fixes: 7fea700e04bd ("zap_pid_ns_processes: clear TIF_NOTIFY_SIGNAL along with TIF_SIGPENDING") Reviewed-by: Oleg Nesterov Cc: stable@vger.kernel.org Suggested-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/pid_namespace.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/pid_namespace.c b/kernel/pid_namespace.c index d36afc58ee1db7..8bc9edb40b7822 100644 --- a/kernel/pid_namespace.c +++ b/kernel/pid_namespace.c @@ -238,9 +238,10 @@ void zap_pid_ns_processes(struct pid_namespace *pid_ns) * kernel_wait4() will also block until our children traced from the * parent namespace are detached and become EXIT_DEAD. */ + /* Task work must not busy-loop the reaper, see signal_pending(). */ + guard(no_notify_signal)(); do { clear_thread_flag(TIF_SIGPENDING); - clear_thread_flag(TIF_NOTIFY_SIGNAL); rc = kernel_wait4(-1, NULL, __WALL, NULL); } while (rc != -ECHILD); From 1aea0c4a34c7bc6ccec257923bcc15a443efef98 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Wed, 19 Aug 2026 10:48:58 +0200 Subject: [PATCH 0023/2286] iomap: remove the unused ifs parameter from ifs_find_dirty_range commit fed9c62d28b7 ("iomap: use find_next_bit() for dirty bitmap scanning") removed the use of ifs in ifs_find_dirty_range(). Remove the unused parameter. Signed-off-by: Pankaj Raghav Link: https://patch.msgid.link/20260819084858.778285-1-p.raghav@samsung.com Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 0a5ebfda90f12e..e11f1327bd986e 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -143,8 +143,8 @@ static unsigned ifs_next_clean_block(struct folio *folio, blks + start_blk) - blks; } -static unsigned ifs_find_dirty_range(struct folio *folio, - struct iomap_folio_state *ifs, u64 *range_start, u64 range_end) +static unsigned ifs_find_dirty_range(struct folio *folio, u64 *range_start, + u64 range_end) { struct inode *inode = folio->mapping->host; unsigned start_blk = @@ -176,7 +176,7 @@ static unsigned iomap_find_dirty_range(struct folio *folio, u64 *range_start, return 0; if (ifs) - return ifs_find_dirty_range(folio, ifs, range_start, range_end); + return ifs_find_dirty_range(folio, range_start, range_end); return range_end - *range_start; } From 55beb606b5a10ce8c7c5e488eec712f090372b0b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 26 Aug 2026 17:55:46 +0200 Subject: [PATCH 0024/2286] binfmt_misc: only let punctuation delimit a register string Currently we accept arbitrary delimiters which really makes no sense and from looking around it's an unused feature. Everytime we add a flag the number of possible delimiter shrinks and it makes extensions much more hazardous than they need to be. Stop it and only accept punctional as delimiters. This extends commit 8e85d50ba111 ("binfmt_misc: reject a flag character as the field delimiter"). Letters, digits, whitespace, control characters, bytes above 0x7f and the backslash now get -EINVAL. Everything deployed uses ':' anyway. Link: https://patch.msgid.link/20260826-work-binfmt_misc-delim-v1-1-43618adf8599@kernel.org Reviewed-by: Farid Zakaria Suggested-by: Farid Zakaria Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_misc.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/binfmt_misc.c b/fs/binfmt_misc.c index ddfd3aa57ac873..809c91d21b3980 100644 --- a/fs/binfmt_misc.c +++ b/fs/binfmt_misc.c @@ -100,6 +100,13 @@ static const struct binfmt_misc_flag *misc_flag_by_char(const char c) return NULL; } +static bool misc_valid_delim(const char c) +{ + if (!isascii(c) || !ispunct(c)) + return false; + return c != '\\'; +} + struct binfmt_misc_entry { struct hlist_node node; unsigned long flags; /* type, status, etc. */ @@ -872,10 +879,9 @@ static struct binfmt_misc_entry *create_entry(const char __user *buffer, del = *p++; /* delimiter */ - pr_debug("register: delim: %#x {%c}\n", del, del); + pr_debug("register: delim: %#x\n", del); - /* A flag-char delimiter runs the flag scan off the buffer. */ - if (misc_flag_by_char(del)) + if (!misc_valid_delim(del)) return ERR_PTR(-EINVAL); /* Pad the buffer with the delim to simplify parsing below. */ From 6b7262392ad6b4d61d7dbf3b8dc8fbe946ac51ba Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 26 Aug 2026 17:55:47 +0200 Subject: [PATCH 0025/2286] selftests/exec: test delimiter restrictions Test that the new delimiter restrictions work. Link: https://patch.msgid.link/20260826-work-binfmt_misc-delim-v1-2-43618adf8599@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/exec/Makefile | 4 + .../selftests/exec/binfmt_misc_delim.c | 127 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 tools/testing/selftests/exec/binfmt_misc_delim.c diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile index b640af8f02b511..2220ed345e920b 100644 --- a/tools/testing/selftests/exec/Makefile +++ b/tools/testing/selftests/exec/Makefile @@ -45,6 +45,10 @@ TEST_GEN_FILES += binfmt_transparent_interp TEST_GEN_PROGS += binfmt_misc_loader TEST_GEN_FILES += binfmt_loader_payload binfmt_loader_payload_static +# Only ASCII punctuation delimits the fields of a register string, so a new +# flag character cannot change which strings register. No bpf toolchain. +TEST_GEN_PROGS += binfmt_misc_delim + # binfmt_misc bpf-backed ('B') handler test: a libbpf harness plus its # struct_ops objects and the test interpreter/app it routes between. Only # built when clang, bpftool, the vmlinux BTF and libbpf are all present diff --git a/tools/testing/selftests/exec/binfmt_misc_delim.c b/tools/testing/selftests/exec/binfmt_misc_delim.c new file mode 100644 index 00000000000000..ffc17cb7854502 --- /dev/null +++ b/tools/testing/selftests/exec/binfmt_misc_delim.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test which characters may delimit the fields of a register string. + */ +#define _GNU_SOURCE +#include +#include + +#include "binfmt_misc_common.h" +#include "kselftest_harness.h" + +#define ENTRY "bmdelim" +/* Shares no character with the sets below, or a refusal proves nothing. */ +#define MAGIC "bmmagic" +#define INTERP "/bin/true" + +/* + * ASCII punctuation without '\' and '/'. The backslash is refused because + * it would cut a magic that uses \x to escape short. '/' is accepted + * but cannot delimit a rule that names an absolute interpreter. + */ +#define PUNCTUATION "!\"#$%&'()*+,-.:;<=>?@[]^_`{|}~" + +/* 'M', 'E' and 'B' name types, 'P' through 'D' are the flags. */ +#define LETTERS "MEBPOCFTLDqz" +#define DIGITS "0157" +#define WHITESPACE " \t\n" +#define CONTROL "\001\033\177" +#define NON_ASCII "\200\244\377" + +/* ':bmdelim:E::bmmagic::/bin/true:' with @del in place of every ':'. */ +static int register_with(char del) +{ + char rule[128]; + + snprintf(rule, sizeof(rule), "%c%s%cE%c%c%s%c%c%s%c", del, ENTRY, del, + del, del, MAGIC, del, del, INTERP, del); + return write_reg(rule); +} + +/* No character of @set may delimit a register string. */ +static void expect_refused(struct __test_metadata *_metadata, const char *set) +{ + const char *d; + + for (d = set; *d; d++) { + int rc = register_with(*d); + + EXPECT_EQ(rc, -1) + TH_LOG("%#x delimited a register string", + (unsigned char)*d); + if (rc == 0) { + unregister(ENTRY); + continue; + } + EXPECT_EQ(errno, EINVAL); + } +} + +FIXTURE(delim) { +}; + +FIXTURE_SETUP(delim) +{ + if (getuid() != 0) + SKIP(return, "test must be run as root"); + if (!binfmt_misc_available()) + SKIP(return, "no binfmt_misc"); + + /* A kernel without the allow-list takes any character but a flag. */ + if (register_with('q') == 0) { + unregister(ENTRY); + SKIP(return, "kernel without the delimiter allow-list"); + } +} + +FIXTURE_TEARDOWN(delim) +{ + unregister(ENTRY); +} + +/* Punctuation delimits, which is all anything deployed ever uses. */ +TEST_F(delim, punctuation_accepted) +{ + const char *d; + + for (d = PUNCTUATION; *d; d++) { + EXPECT_EQ(register_with(*d), 0) + TH_LOG("'%c' refused with errno %d", *d, errno); + unregister(ENTRY); + } +} + +/* Letters name the types and the flags, so none of them can delimit. */ +TEST_F(delim, letters_refused) +{ + expect_refused(_metadata, LETTERS); +} + +/* The offset field is written in digits. */ +TEST_F(delim, digits_refused) +{ + expect_refused(_metadata, DIGITS); +} + +TEST_F(delim, whitespace_refused) +{ + expect_refused(_metadata, WHITESPACE); +} + +TEST_F(delim, control_refused) +{ + expect_refused(_metadata, CONTROL); +} + +TEST_F(delim, non_ascii_refused) +{ + expect_refused(_metadata, NON_ASCII); +} + +/* The escape character would cut every magic that uses one short. */ +TEST_F(delim, backslash_refused) +{ + expect_refused(_metadata, "\\"); +} + +TEST_HARNESS_MAIN From cd9cbd151573a6170f0a8f0fc97e3834e35012c6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 26 Aug 2026 17:55:48 +0200 Subject: [PATCH 0026/2286] binfmt_misc: document the field delimiter Document the field delimiter. Link: https://patch.msgid.link/20260826-work-binfmt_misc-delim-v1-3-43618adf8599@kernel.org Reviewed-by: Farid Zakaria Signed-off-by: Christian Brauner (Amutable) --- Documentation/admin-guide/binfmt-misc.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/admin-guide/binfmt-misc.rst b/Documentation/admin-guide/binfmt-misc.rst index d26b63a27c259b..9e84b877d06dea 100644 --- a/Documentation/admin-guide/binfmt-misc.rst +++ b/Documentation/admin-guide/binfmt-misc.rst @@ -19,6 +19,9 @@ To actually register a new binary type, you have to set up a string looking like ``:name:type:offset:magic:mask:interpreter:flags`` (where you can choose the ``:`` upon your needs) and echo it to ``/proc/sys/fs/binfmt_misc/register``. +The first character of the string is its field delimiter and can be any +ASCII punctuation character other than the backslash ``\``. + Here is what the fields mean: - ``name`` From a61fb39759d719a4f4966742b4d28387e33d81c4 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 31 Aug 2026 11:17:14 +0200 Subject: [PATCH 0027/2286] udf: Make sure there's enough space for LVID implementation use field udf_lvid_valid() verifies that the LVID has enough space for all the tables and the reported length of ImplementationUse field. However it doesn't make sure the reported length is large enough to contain logicalVolIntegrityDescImpUse structure which OSTA specification requires. This can thus lead to accesses beyond the end of LVID buffer for suitably corrupted UDF filesystem. Add the length check. Reported-by: Yunpeng Tian Reported-by: Gongming Wang Reported-by: Mingda Zhang Reported-by: Qinrun Dai Cc: stable@vger.kernel.org Fixes: 781d2a9a2fc7 ("udf: Check LVID earlier") Signed-off-by: Jan Kara --- fs/udf/super.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/udf/super.c b/fs/udf/super.c index 2ba5973ef4dd45..5351755aca3ecc 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c @@ -1617,7 +1617,10 @@ static bool udf_lvid_valid(struct super_block *sb, parts = le32_to_cpu(lvid->numOfPartitions); impuselen = le32_to_cpu(lvid->lengthOfImpUse); - if (parts >= sb->s_blocksize || impuselen >= sb->s_blocksize || + if (sizeof(struct logicalVolIntegrityDescImpUse) > impuselen || + impuselen >= sb->s_blocksize) + return false; + if (parts >= sb->s_blocksize || sizeof(struct logicalVolIntegrityDesc) + impuselen + 2 * parts * sizeof(u32) > sb->s_blocksize) return false; From f8f42dbbb9c6056a8ce50942ceb242c46cf91815 Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Mon, 3 Aug 2026 20:38:37 +0530 Subject: [PATCH 0028/2286] selftests/filesystems: skip listxattr kernfs test if xattrs are present kernfs_test assumes that flistxattr() on /sys/kernel/warn_count always returns an empty list. However, systems with SELinux enabled may expose security.selinux xattr via listxattr() during policy load, which makes the test fail even though kernfs is behaving correctly. Skip the xattr content check if any xattrs are present, while still verifying that flistxattr() succeeds (returns >= 0) on a kernfs file. The existing user.foo getxattr check remains unchanged. This avoids false failures on SELinux-enabled systems while preserving the original purpose of the test. Tested-by: Yeswanth Krishna Signed-off-by: Disha Goel Link: https://patch.msgid.link/20260803150837.91604-1-disgoel@linux.ibm.com Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/filesystems/kernfs_test.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/kernfs_test.c b/tools/testing/selftests/filesystems/kernfs_test.c index 84c2b910a60db0..ca13646c93281f 100644 --- a/tools/testing/selftests/filesystems/kernfs_test.c +++ b/tools/testing/selftests/filesystems/kernfs_test.c @@ -12,12 +12,24 @@ TEST(kernfs_listxattr) { + ssize_t len; int fd; - /* Read-only file that can never have any extended attributes set. */ + /* Read-only file that can never have any extended attributes set. + * However, on systems with SELinux enabled, security.selinux xattr + * may be present. Skip the content check if any xattrs are found. + */ fd = open("/sys/kernel/warn_count", O_RDONLY | O_CLOEXEC); ASSERT_GE(fd, 0); - ASSERT_EQ(flistxattr(fd, NULL, 0), 0); + + len = flistxattr(fd, NULL, 0); + ASSERT_GE(len, 0); + + if (len > 0) { + close(fd); + SKIP(return, "xattrs present on /sys/kernel/warn_count, skipping xattr content check"); + } + EXPECT_EQ(close(fd), 0); } From 398e3fa789aa5ffddc8689d8cbc7b31e1824130c Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Thu, 13 Aug 2026 22:38:43 +0900 Subject: [PATCH 0029/2286] aio: prevent eventfd/epoll recursion on poll aio_poll_wake() can complete a keyed poll request inline while the provider waitqueue lock is held. If the polled file is an epoll instance and the AIO result eventfd is watched by that same instance, signaling the result can feed back into epoll and try to take the provider waitqueue lock again. For example, a timerfd wake can follow this path: timerfd -> ep_poll_callback -> aio_poll_wake -> aio_complete -> eventfd_signal -> ep_poll_callback -> ep_poll_safewake The second ep_poll_safewake() recursively acquires ep->poll_wait.lock. eventfd_signal_allowed() does not prevent this because the wake chain did not begin in eventfd, so the task's eventfd recursion bit is still clear when aio_poll_wake() decides to complete inline. An unprivileged process can construct this graph and make a CPU spin on the recursive lock. DEBUG_SPINLOCK reports "BUG: spinlock recursion", and lockdep reports the same epoll waitqueue lock as both held and requested. Always defer eventfd-backed poll completions through the existing aio_poll_put_work() path. At this point the request has already been detached from the waitqueue and active request list, so the work item can publish the completion after the provider callback releases its lock. Poll completions without a result eventfd remain inline. Fixes: e8693bcfa0b4 ("aio: allow direct aio poll comletions for keyed wakeups") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260813133843.2933127-1-4ncienth@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/aio.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/aio.c b/fs/aio.c index d78acc69f48701..92241a24d9e288 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -1869,7 +1869,12 @@ static int aio_poll_wake(struct wait_queue_entry *wait, unsigned mode, int sync, list_del_init(&req->wait.entry); list_del(&iocb->ki_list); iocb->ki_res.res = mangle_poll(mask); - if (iocb->ki_eventfd && !eventfd_signal_allowed()) { + /* + * We hold an arbitrary provider waitqueue lock here. Signaling a + * result eventfd can feed back through epoll and try to take the same + * lock again. Defer all eventfd-backed poll completions. + */ + if (iocb->ki_eventfd) { iocb = NULL; INIT_WORK(&req->work, aio_poll_put_work); schedule_work(&req->work); From 938d97b27fe746f84dded853a80637820796ac94 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Thu, 13 Aug 2026 15:04:04 +0530 Subject: [PATCH 0030/2286] fs: fix comment typo in bpf_fs_kfuncs.c Fix "Rmove" -> "Remove" in the kernel-doc comments for bpf_set_dentry_xattr_locked() and bpf_set_dentry_xattr(). No functional change. Signed-off-by: Manush Prajwal Link: https://patch.msgid.link/20260813093404.1278-1-manushprajwal555@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/bpf_fs_kfuncs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 6cb8772679782d..fdf85e5239695d 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -237,7 +237,7 @@ int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str, * @dentry: dentry to get xattr from * @name__str: name of the xattr * - * Rmove xattr *name__str* of *dentry*. + * Remove xattr *name__str* of *dentry*. * * For security reasons, only *name__str* with prefix "security.bpf." * is allowed. @@ -305,7 +305,7 @@ __bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__st * @dentry: dentry to get xattr from * @name__str: name of the xattr * - * Rmove xattr *name__str* of *dentry*. + * Remove xattr *name__str* of *dentry*. * * For security reasons, only *name__str* with prefix "security.bpf." * is allowed. From b573f7966695470dd9f5659d32681e7e2821d426 Mon Sep 17 00:00:00 2001 From: Mahad Ibrahim Date: Wed, 19 Aug 2026 16:17:38 +0000 Subject: [PATCH 0031/2286] selftests/filesystems: Add .gitignore for fscontext_ns_test fscontext_ns_test is listed in TEST_GEN_PROGS and is built in the fscontext_ns directory, but the directory has no .gitignore. The binary is thus left untracked by git after being compiled. git status lists: Untracked files: (use "git add ..." to include in what will be committed) tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test Every test directory must include a .gitignore which contains all the generated objects, as per the documentation and convention. Add .gitignore to ignore fscontext_ns_test. Fixes: d64ba78b749a ("selftests/filesystems: test completing a context from another user namespace") Signed-off-by: Mahad Ibrahim Link: https://patch.msgid.link/20260819161738.3517-1-mahad.ibrahim.dev@gmail.com Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/filesystems/fscontext_ns/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fscontext_ns/.gitignore diff --git a/tools/testing/selftests/filesystems/fscontext_ns/.gitignore b/tools/testing/selftests/filesystems/fscontext_ns/.gitignore new file mode 100644 index 00000000000000..a632905257a335 --- /dev/null +++ b/tools/testing/selftests/filesystems/fscontext_ns/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +fscontext_ns_test From 270b35c9871a4ec40482252a9fb270089ec337f2 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Thu, 13 Aug 2026 15:04:30 +0530 Subject: [PATCH 0032/2286] fs: fix comment typo in fs-writeback.c Fix "paramters" -> "parameters" in the foreign-inode-detection comment above wb_wait_for_completion(). No functional change. Signed-off-by: Manush Prajwal Link: https://patch.msgid.link/20260813093431.410-1-manushprajwal555@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index e744f9f9d43fae..48ee2d55075e51 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -233,7 +233,7 @@ void wb_wait_for_completion(struct wb_completion *done) * Parameters for foreign inode detection, see wbc_detach_inode() to see * how they're used. * - * These paramters are inherently heuristical as the detection target + * These parameters are inherently heuristical as the detection target * itself is fuzzy. All we want to do is detaching an inode from the * current owner if it's being written to by some other cgroups too much. * From 8f0aec91bb9eb21928e1aee8cfd80318871083aa Mon Sep 17 00:00:00 2001 From: Mark Sercombe Date: Fri, 14 Aug 2026 01:14:55 +0200 Subject: [PATCH 0033/2286] fs/aio: fix comment typos Fix two spelling mistakes in code comments. This is a comment only change with no functional impact. Signed-off-by: Mark Sercombe Link: https://patch.msgid.link/20260813231455.558829-1-sercombe.joel.mark@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/aio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/aio.c b/fs/aio.c index 92241a24d9e288..22affd3e9dbc2b 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -936,7 +936,7 @@ static int kill_ioctx(struct mm_struct *mm, struct kioctx *ctx, /* * exit_aio: called when the last user of mm goes away. At this point, there is - * no way for any new requests to be submited or any of the io_* syscalls to be + * no way for any new requests to be submitted or any of the io_* syscalls to be * called on the context. * * There may be outstanding kiocbs, but free_ioctx() will explicitly wait on @@ -1280,7 +1280,7 @@ static long aio_read_events_ring(struct kioctx *ctx, * The mutex can block and wake us up and that will cause * wait_event_interruptible_hrtimeout() to schedule without sleeping * and repeat. This should be rare enough that it doesn't cause - * peformance issues. See the comment in read_events() for more detail. + * performance issues. See the comment in read_events() for more detail. */ sched_annotate_sleep(); mutex_lock(&ctx->ring_lock); From e07df64b616723f30c6caa100adcfa3dfb8a19a6 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Thu, 13 Aug 2026 15:06:03 +0530 Subject: [PATCH 0034/2286] fs: fix comment typo in inode.c Fix "funtion" -> "function" in find_inode_nowait()'s comment. No functional change. Signed-off-by: Manush Prajwal Link: https://patch.msgid.link/20260813093603.1971-1-manushprajwal555@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a315..817fd525449590 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1771,7 +1771,7 @@ EXPORT_SYMBOL(ilookup); * function must never block --- find_inode() can block in * __wait_on_freeing_inode() --- or when the caller can not increment * the reference count because the resulting iput() might cause an - * inode eviction. The tradeoff is that the @match funtion must be + * inode eviction. The tradeoff is that the @match function must be * very carefully implemented. */ struct inode *find_inode_nowait(struct super_block *sb, From fec2c3c0744bf3ce2aaafb1fd3fd5289cfd4814c Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Fri, 28 Aug 2026 17:16:58 +0800 Subject: [PATCH 0035/2286] userns: remove dead key_put() on the create_user_ns() error path create_user_ns() jumps to fail_keyring when setup_userns_sysctls() fails. At that point ns was freshly allocated with kmem_cache_zalloc() and ns->persistent_keyring_register is only ever assigned later, lazily, from key_get_persistent() (security/keys/persistent.c). The key_put() therefore always receives NULL. Remove the dead call. Signed-off-by: Tao Cui Link: https://patch.msgid.link/20260828091659.358700-2-cui.tao@linux.dev Reviewed-by: Jan Kara Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/user_namespace.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 0bed462e9b2a24..e9e04ce167df72 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -162,9 +162,6 @@ int create_user_ns(struct cred *new) ns_tree_add(ns); return 0; fail_keyring: -#ifdef CONFIG_PERSISTENT_KEYRINGS - key_put(ns->persistent_keyring_register); -#endif ns_common_free(ns); fail_free: kmem_cache_free(user_ns_cachep, ns); From eb85469f0dd69ca145d63117450e14c31f67f698 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 24 Aug 2026 13:23:59 +0900 Subject: [PATCH 0036/2286] ipc/mqueue: release notification resources during inode eviction mqueue_flush_file() removes an mq_notify() registration only when the closing task belongs to the thread group stored in notify_owner. A task in a separate thread group created with CLONE_FILES can register SIGEV_THREAD notification and exit without closing the shared file table. If another thread group then unlinks and last-closes the queue, ->flush() skips the registration and inode eviction loses the only pointers to its resources. The orphaned registration permanently retains the notification skb, its netlink socket, a pid reference and a user namespace reference. An unprivileged process can repeat the sequence with new queues and sockets. No inode users remain during eviction. Remove any stale registration there after dropping info->lock, since netlink_sendskb() may release the final socket reference. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Link: https://patch.msgid.link/20260824042359.925145-1-4ncienth@gmail.com Signed-off-by: Christian Brauner (Amutable) --- ipc/mqueue.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipc/mqueue.c b/ipc/mqueue.c index d1dd36a651b0d0..d1a1965c98118c 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -528,6 +528,13 @@ static void mqueue_evict_inode(struct inode *inode) list_add_tail(&msg->m_list, &tmp_msg); kfree(info->node_cache); spin_unlock(&info->lock); + /* + * A shared file table can let the notification owner exit without + * running ->flush(). No users of the inode remain during eviction, so + * tear down any stale notification after dropping info->lock because + * netlink_sendskb() may release the final socket reference. + */ + remove_notification(info); list_for_each_entry_safe(msg, nmsg, &tmp_msg, m_list) { list_del(&msg->m_list); From b09cfc161840ad3544812ad17537b4dc0e40506e Mon Sep 17 00:00:00 2001 From: Richard Guy Briggs Date: Tue, 18 Aug 2026 14:31:39 -0400 Subject: [PATCH 0037/2286] audit: free proctitle in context so it can be re-set by fork on exec_binprm Between the actual process startup (fork systemd) and the executable file replacement (exec), systemd sets a temporary file name (executable file name in parentheses). If an auditable system call occurs at this point, the audit context will latch the temporary process name into the cache. This name will not change again. The patch clears proctitle into the audit cache when the exec call is made, allowing the new process name to be latched. Suggested-by: Roman Dolgikh Link: https://github.com/linux-audit/audit-kernel/issues/170 Signed-off-by: Richard Guy Briggs Reviewed-by: Ricardo Robaina Reviewed-by: Bradley Morgan Signed-off-by: Paul Moore --- kernel/auditsc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 2b9ce0b525111f..ee7e53d2cd5231 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -2601,6 +2601,8 @@ void __audit_bprm(struct linux_binprm *bprm) { struct audit_context *context = audit_context(); + /* clear proctitle in audit context to allow replacement */ + audit_proctitle_free(context); context->type = AUDIT_EXECVE; context->execve.argc = bprm->argc; } From d50ba2ab3744ecfd74a67eefffbadf613ebe8fa5 Mon Sep 17 00:00:00 2001 From: Ricardo Robaina Date: Fri, 7 Aug 2026 11:05:21 -0300 Subject: [PATCH 0038/2286] audit: add missing unlikely hint to audit_dummy_context() wrappers The inline wrappers: audit_log_kern_module(), audit_tk_injoffset(), and audit_ntp_log() check audit_dummy_context() without unlikely(), unlike every other wrapper in the file. Add the missing annotations for consistency. Signed-off-by: Ricardo Robaina Signed-off-by: Paul Moore --- include/linux/audit.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/audit.h b/include/linux/audit.h index 45abb3722d304c..18b44a1c5397db 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -547,7 +547,7 @@ static inline void audit_openat2_how(struct open_how *how) static inline void audit_log_kern_module(const char *name) { - if (!audit_dummy_context()) + if (unlikely(!audit_dummy_context())) __audit_log_kern_module(name); } @@ -563,7 +563,7 @@ static inline void audit_tk_injoffset(struct timespec64 offset) if (offset.tv_sec == 0 && offset.tv_nsec == 0) return; - if (!audit_dummy_context()) + if (unlikely(!audit_dummy_context())) __audit_tk_injoffset(offset); } @@ -586,7 +586,7 @@ static inline void audit_ntp_set_new(struct audit_ntp_data *ad, static inline void audit_ntp_log(const struct audit_ntp_data *ad) { - if (!audit_dummy_context()) + if (unlikely(!audit_dummy_context())) __audit_ntp_log(ad); } From fb3048e03dfb267a1d36551057f15e9f179082f9 Mon Sep 17 00:00:00 2001 From: Ricardo Robaina Date: Mon, 10 Aug 2026 09:57:27 -0300 Subject: [PATCH 0039/2286] audit: use copied skb length in kauditd_send_multicast_skb() The netlink header's nlmsg_len was being set using the original skb instead of the local copy passed to nlmsg_multicast(). While skb->len and copy->len are identical at this point following skb_copy(), it is conceptually cleaner and safer to use the length of the skb being actively modified and sent. Thus, this is a minor cleanup only, no functional change expected. Signed-off-by: Ricardo Robaina Reviewed-by: Bradley Morgan Signed-off-by: Paul Moore --- kernel/audit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/audit.c b/kernel/audit.c index 9412af9144bc9a..631d6d4a23cfde 100644 --- a/kernel/audit.c +++ b/kernel/audit.c @@ -871,7 +871,7 @@ static void kauditd_send_multicast_skb(struct sk_buff *skb) if (!copy) return; nlh = nlmsg_hdr(copy); - nlh->nlmsg_len = skb->len; + nlh->nlmsg_len = copy->len; nlmsg_multicast(sock, copy, 0, AUDIT_NLGRP_READLOG, GFP_KERNEL); } From fd8bef4162319b67d97832e8109965b183909e94 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 7 Aug 2026 15:13:43 -0400 Subject: [PATCH 0040/2286] selinux: tighten policydb_context_isvalid() checks policydb_context_isvalid() bounds each of user, role, and type but does not require the value to be defined. A crafted policy can declare more identifiers that it defines, so an initial SID or ocontext context whose type is not defined can be inserted into the sidtab and later reach type_attribute_bounds_av() from context_struct_compute_av(), thereby reaching the BUG_ON(!type_val_to_struct[type - 1]). A user with no datum can reach context_struct_to_string() and thereby trigger strlen(NULL). The user and role datums are already NULL-checked further down but only when role is not OBJECT_R_VAL, so an object context can carry an undefined value through. Use policydb_{role,user,type}_isvalid(), which check both the range and that the value has a name, in place of the inline range checks. The name and datum arrays are populated by *_index(), so a value with a name also has a datum. This runs after policydb_index(), so the arrays are populated. The now-redundant NULL guards on role and usrdatum in the OBJECT_R_VAL block can then be dropped. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index b75c977c6673db..4342258cb148fd 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1037,13 +1037,13 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) const struct role_datum *role; const struct user_datum *usrdatum; - if (!c->role || c->role > p->p_roles.nprim) + if (!policydb_role_isvalid(p, c->role)) return false; - if (!c->user || c->user > p->p_users.nprim) + if (!policydb_user_isvalid(p, c->user)) return false; - if (!c->type || c->type > p->p_types.nprim) + if (!policydb_simpletype_isvalid(p, c->type)) return false; if (c->role != OBJECT_R_VAL) { @@ -1051,7 +1051,7 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) * Role must be authorized for the type. */ role = p->role_val_to_struct[c->role - 1]; - if (!role || !ebitmap_get_bit(&role->types, c->type - 1)) + if (!ebitmap_get_bit(&role->types, c->type - 1)) /* role may not be associated with type */ return false; @@ -1059,9 +1059,6 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) * User must be authorized for the role. */ usrdatum = p->user_val_to_struct[c->user - 1]; - if (!usrdatum) - return false; - if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1)) /* user may not be associated with role */ return false; From fc68b6a45f1668bbe03264343115d1c97013c7ea Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Tue, 25 Aug 2026 12:23:54 -0400 Subject: [PATCH 0041/2286] selinux: convert selinuxfs inode numbers from unsigned long to u64 Starting with commit 0b2600f81cef ("treewide: change inode->i_ino from unsigned long to u64") inode numbers were changed from an 'unsigned long' type to a 'u64' but somehow selinuxfs was lost in the conversion. This patch corrects this oversight and converts selinuxfs to 'u64' inode numbers. Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/selinuxfs.c | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index c7d91476971cb5..292302eb60f3a1 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -75,8 +75,8 @@ struct selinux_fs_info { char **bool_pending_names; int *bool_pending_values; struct dentry *class_dir; - unsigned long last_class_ino; - unsigned long last_ino; + u64 last_class_ino; + u64 last_ino; struct super_block *sb; }; @@ -304,15 +304,14 @@ static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_ int **bool_pending_values); static int sel_make_classes(struct selinux_policy *newpolicy, struct dentry *class_dir, - unsigned long *last_class_ino); + u64 *last_class_ino); /* declaration for sel_make_class_dirs */ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, - unsigned long *ino); + u64 *ino); /* declaration for sel_make_policy_nodes */ -static struct dentry *sel_make_swapover_dir(struct super_block *sb, - unsigned long *ino); +static struct dentry *sel_make_swapover_dir(struct super_block *sb, u64 *ino); static ssize_t sel_read_mls(struct file *filp, char __user *buf, size_t count, loff_t *ppos) @@ -467,7 +466,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi, unsigned int bool_num = 0; char **bool_names = NULL; int *bool_values = NULL; - unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ + u64 tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ tmp_parent = sel_make_swapover_dir(fsi->sb, &tmp_ino); if (IS_ERR(tmp_parent)) @@ -1568,22 +1567,22 @@ static int sel_make_initcon_files(struct dentry *dir) return err; } -static inline unsigned long sel_class_to_ino(u16 class) +static inline u64 sel_class_to_ino(u16 class) { return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; } -static inline u16 sel_ino_to_class(unsigned long ino) +static inline u16 sel_ino_to_class(u64 ino) { return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1); } -static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) +static inline u64 sel_perm_to_ino(u16 class, u32 perm) { return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET; } -static inline u32 sel_ino_to_perm(unsigned long ino) +static inline u32 sel_ino_to_perm(u64 ino) { return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); } @@ -1591,7 +1590,7 @@ static inline u32 sel_ino_to_perm(unsigned long ino) static ssize_t sel_read_class(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - unsigned long ino = file_inode(file)->i_ino; + u64 ino = file_inode(file)->i_ino; char res[TMPBUFLEN]; ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_class(ino)); return simple_read_from_buffer(buf, count, ppos, res, len); @@ -1605,7 +1604,7 @@ static const struct file_operations sel_class_ops = { static ssize_t sel_read_perm(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - unsigned long ino = file_inode(file)->i_ino; + u64 ino = file_inode(file)->i_ino; char res[TMPBUFLEN]; ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino)); return simple_read_from_buffer(buf, count, ppos, res, len); @@ -1622,7 +1621,7 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf, int value; char tmpbuf[TMPBUFLEN]; ssize_t length; - unsigned long i_ino = file_inode(file)->i_ino; + u64 i_ino = file_inode(file)->i_ino; value = security_policycap_supported(i_ino & SEL_INO_MASK); length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); @@ -1698,7 +1697,7 @@ static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, static int sel_make_classes(struct selinux_policy *newpolicy, struct dentry *class_dir, - unsigned long *last_class_ino) + u64 *last_class_ino) { u32 i, nclasses; int rc; @@ -1763,7 +1762,7 @@ static int sel_make_policycap(struct dentry *dir) } static struct dentry *sel_make_dir(struct dentry *dir, const char *name, - unsigned long *ino) + u64 *ino) { struct inode *inode; @@ -1792,8 +1791,7 @@ static const struct inode_operations swapover_dir_inode_operations = { .permission = reject_all, }; -static struct dentry *sel_make_swapover_dir(struct super_block *sb, - unsigned long *ino) +static struct dentry *sel_make_swapover_dir(struct super_block *sb, u64 *ino) { struct dentry *dentry; struct inode *inode; From 3b0744a6358624d46f7ee2573faca2b7d92cf32b Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 28 Aug 2026 16:23:50 -0400 Subject: [PATCH 0042/2286] selinux: remove unused mls_setup_user_range() mls_setup_user_range() was a helper for security_get_user_sids(), which was removed by commit ad1ac3d740cc6b8 ("selinux: prune /sys/fs/selinux/user"). Since mls_setup_user_range() has no remaining users, remove it too and any other helpers that only it was using. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/mls.c | 38 --------------------------------- security/selinux/ss/mls.h | 3 --- security/selinux/ss/mls_types.h | 3 --- 3 files changed, 44 deletions(-) diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 40c62600650e8e..b48f8256dd4cf9 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -400,44 +400,6 @@ int mls_range_set(struct context *context, struct mls_range *range) return rc; } -int mls_setup_user_range(struct policydb *p, struct context *fromcon, - struct user_datum *user, struct context *usercon) -{ - if (p->mls_enabled) { - struct mls_level *fromcon_sen = &(fromcon->range.level[0]); - struct mls_level *fromcon_clr = &(fromcon->range.level[1]); - struct mls_level *user_low = &(user->range.level[0]); - struct mls_level *user_clr = &(user->range.level[1]); - struct mls_level *user_def = &(user->dfltlevel); - struct mls_level *usercon_sen = &(usercon->range.level[0]); - struct mls_level *usercon_clr = &(usercon->range.level[1]); - - /* Honor the user's default level if we can */ - if (mls_level_between(user_def, fromcon_sen, fromcon_clr)) - *usercon_sen = *user_def; - else if (mls_level_between(fromcon_sen, user_def, user_clr)) - *usercon_sen = *fromcon_sen; - else if (mls_level_between(fromcon_clr, user_low, user_def)) - *usercon_sen = *user_low; - else - return -EINVAL; - - /* Lower the clearance of available contexts - if the clearance of "fromcon" is lower than - that of the user's default clearance (but - only if the "fromcon" clearance dominates - the user's computed sensitivity level) */ - if (mls_level_dom(user_clr, fromcon_clr)) - *usercon_clr = *fromcon_clr; - else if (mls_level_dom(fromcon_clr, user_clr)) - *usercon_clr = *user_clr; - else - return -EINVAL; - } - - return 0; -} - /* * Convert the MLS fields in the security context * structure `oldc' from the values specified in the diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h index 93cde1b22992ca..c107b5182fb7b3 100644 --- a/security/selinux/ss/mls.h +++ b/security/selinux/ss/mls.h @@ -46,9 +46,6 @@ int mls_compute_sid(struct policydb *p, struct context *scontext, struct context *tcontext, u16 tclass, u32 specified, struct context *newcontext, bool sock); -int mls_setup_user_range(struct policydb *p, struct context *fromcon, - struct user_datum *user, struct context *usercon); - #ifdef CONFIG_NETLABEL void mls_export_netlbl_lvl(struct policydb *p, struct context *context, struct netlbl_lsm_secattr *secattr); diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h index 51df2ebd12115e..d36a03f104c231 100644 --- a/security/selinux/ss/mls_types.h +++ b/security/selinux/ss/mls_types.h @@ -42,9 +42,6 @@ static inline int mls_level_dom(const struct mls_level *l1, #define mls_level_incomp(l1, l2) \ (!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) -#define mls_level_between(l1, l2, l3) \ - (mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) - #define mls_range_contains(r1, r2) \ (mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ mls_level_dom(&(r1).level[1], &(r2).level[1])) From b030ffaedc1d638f232f6992406cdcdcc82d5602 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Thu, 6 Aug 2026 22:15:54 +0200 Subject: [PATCH 0043/2286] cred: clarify that task_struct::cred is only for the current task The `cred` field in task_struct is currently marked as __rcu, but that's not true: It can point to credentials from access_override_creds(), which do not get freed with RCU delay. What actually protects task_struct::cred is that accessing it is only permitted for the current task (except for setting up a child during fork() or tearing down a dead process). (There is currently code in Smack that violates this rule, but that's a bug and causes UAF, I have sent a separate fix for that.) Clarify this, remove the __rcu marker, and remove RCU helpers from all accesses to this field. Signed-off-by: Jann Horn Reviewed-by: Serge Hallyn [PM: style tweak in security_init(), applied fixup from JH] Signed-off-by: Paul Moore --- include/linux/cred.h | 17 +++++++++++------ include/linux/sched.h | 8 ++++++-- init/init_task.c | 2 +- kernel/auditsc.c | 5 +++-- kernel/cred.c | 2 +- security/lsm_init.c | 3 +-- 6 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/linux/cred.h b/include/linux/cred.h index 6ef1750c93e2a4..49c26af3734908 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -180,12 +180,18 @@ static inline bool cap_ambient_invariant_ok(const struct cred *cred) static inline const struct cred *override_creds(const struct cred *override_cred) { - return rcu_replace_pointer(current->cred, override_cred, 1); + const struct cred *old = current->cred; + + current->cred = override_cred; + return old; } static inline const struct cred *revert_creds(const struct cred *revert_cred) { - return rcu_replace_pointer(current->cred, revert_cred, 1); + const struct cred *override_cred = current->cred; + + current->cred = revert_cred; + return override_cred; } DEFINE_CLASS(override_creds, @@ -293,11 +299,10 @@ DEFINE_FREE(put_cred, struct cred *, if (!IS_ERR_OR_NULL(_T)) put_cred(_T)) /** * current_cred - Access the current task's subjective credentials * - * Access the subjective credentials of the current task. RCU-safe, - * since nobody else can modify it. + * Access the subjective credentials of the current task. + * Nobody else can modify it. */ -#define current_cred() \ - rcu_dereference_protected(current->cred, 1) +#define current_cred() (current->cred) /** * current_real_cred - Access the current task's objective credentials diff --git a/include/linux/sched.h b/include/linux/sched.h index 8b3d47a325cca9..50c7157fee8239 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1172,8 +1172,12 @@ struct task_struct { /* Objective and real subjective task credentials (COW): */ const struct cred __rcu *real_cred; - /* Effective (overridable) subjective task credentials (COW): */ - const struct cred __rcu *cred; + /* + * Effective (overridable) subjective task credentials (COW). + * Only accessible for the current task and during task creation/freeing. + * This pointer is not managed by RCU! + */ + const struct cred *cred; #ifdef CONFIG_KEYS /* Cached requested key. */ diff --git a/init/init_task.c b/init/init_task.c index adb207cd987c53..ce7c2b07d855d2 100644 --- a/init/init_task.c +++ b/init/init_task.c @@ -160,7 +160,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { .sibling = LIST_HEAD_INIT(init_task.sibling), .group_leader = &init_task, RCU_POINTER_INITIALIZER(real_cred, &init_cred), - RCU_POINTER_INITIALIZER(cred, &init_cred), + .cred = &init_cred, .comm = INIT_TASK_COMM, .thread = INIT_THREAD, .real_fs = &init_fs, diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 2b9ce0b525111f..1b9cf25291e03f 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -459,7 +459,7 @@ static int audit_field_compare(struct task_struct *tsk, * * If task_creation is true, this is an explicit indication that we are * filtering a task rule at task creation time. This and tsk == current are - * the only situations where tsk->cred may be accessed without an rcu read lock. + * the only situations where tsk->cred may be accessed. */ static int audit_filter_rules(struct task_struct *tsk, struct audit_krule *rule, @@ -476,7 +476,8 @@ static int audit_filter_rules(struct task_struct *tsk, if (ctx && rule->prio <= ctx->prio) return 0; - cred = rcu_dereference_check(tsk->cred, tsk == current || task_creation); + WARN_ON(tsk != current && !task_creation); + cred = tsk->cred; for (i = 0; i < rule->field_count; i++) { struct audit_field *f = &rule->fields[i]; diff --git a/kernel/cred.c b/kernel/cred.c index 3df4e15bd67ffe..0bd6a58bc12dc1 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -414,7 +414,7 @@ int commit_creds(struct cred *new) inc_rlimit_ucounts(new->ucounts, UCOUNT_RLIMIT_NPROC, 1); rcu_assign_pointer(task->real_cred, new); - rcu_assign_pointer(task->cred, new); + task->cred = new; if (new->user != old->user || new->user_ns != old->user_ns) dec_rlimit_ucounts(old->ucounts, UCOUNT_RLIMIT_NPROC, 1); if (new->user_ns != old->user_ns) diff --git a/security/lsm_init.c b/security/lsm_init.c index a1ad641811de06..04b18d06ba591d 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -476,8 +476,7 @@ int __init security_init(void) blob_sizes.lbs_inode, 0, SLAB_PANIC, NULL); - if (lsm_cred_alloc((struct cred *)unrcu_pointer(current->cred), - GFP_KERNEL)) + if (lsm_cred_alloc((struct cred *)current->cred, GFP_KERNEL)) panic("early LSM cred alloc failed\n"); if (lsm_task_alloc(current)) panic("early LSM task alloc failed\n"); From 4f238e6e1c2cc985ca6885549e05de8fc37ad4fe Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 26 Jul 2026 18:13:46 +0200 Subject: [PATCH 0044/2286] ns: Free anonymous mount namespaces via ns_common_free() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit free_mnt_ns() skipped ns_common_free() for anonymous mount namespaces (the "if (!is_anon_ns(ns))" guard) because they carry the reserved inum MNT_NS_ANON_INO, which proc_free_inum() must never release. A following change needs ns_common_free() to run for every namespace, to release per-namespace state attached during __ns_common_init(). Move the reserved-inum decision into __ns_common_free() and let free_mnt_ns() call ns_common_free() unconditionally. __ns_common_free() is shared by all namespace types, so it gates proc_free_inum() on ns->inum > MNT_NS_INO_SPECIAL_MAX (a new alias for MNT_NS_ANON_INO) rather than the mount-specific is_anon_ns(). The reserved inums (MNT_NS_ANON_INO and the *_NS_INIT_INO values just above it) belong to namespaces that are never freed, except the anonymous mount namespace; dynamically allocated inums are >= PROC_DYNAMIC_FIRST. So the comparison frees every dynamic inum and skips exactly the anonymous mount namespace, matching the previous guard. Cc: Günther Noack Cc: Paul Moore Signed-off-by: Christian Brauner Co-developed-by: Mickaël Salaün Signed-off-by: Mickaël Salaün Reviewed-by: Christian Brauner (Amutable) Signed-off-by: Paul Moore --- fs/namespace.c | 3 +-- include/uapi/linux/nsfs.h | 1 + kernel/nscommon.c | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b335..8d4009cfbf5ba9 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4193,8 +4193,7 @@ static void dec_mnt_namespaces(struct ucounts *ucounts) static void free_mnt_ns(struct mnt_namespace *ns) { - if (!is_anon_ns(ns)) - ns_common_free(ns); + ns_common_free(ns); dec_mnt_namespaces(ns->ucounts); mnt_ns_tree_remove(ns); } diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index 007fed5971b468..e5909266ec3fbc 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -55,6 +55,7 @@ enum init_ns_ino { MNT_NS_INIT_INO = 0xEFFFFFF8U, #ifdef __KERNEL__ MNT_NS_ANON_INO = 0xEFFFFFF7U, + MNT_NS_INO_SPECIAL_MAX = MNT_NS_ANON_INO, #endif }; diff --git a/kernel/nscommon.c b/kernel/nscommon.c index 3166c1fd844afd..e6f623e1bc371c 100644 --- a/kernel/nscommon.c +++ b/kernel/nscommon.c @@ -91,7 +91,8 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope void __ns_common_free(struct ns_common *ns) { - proc_free_inum(ns->inum); + if (ns->inum > MNT_NS_INO_SPECIAL_MAX) + proc_free_inum(ns->inum); } struct ns_common *__must_check ns_owner(struct ns_common *ns) From f675d2e9556963133b71df68739b771bf51c3a67 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sun, 26 Jul 2026 18:13:47 +0200 Subject: [PATCH 0045/2286] lsm: add LSM blob and hooks for namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All namespace types now share the same ns_common infrastructure. Extend this to include a security blob so LSMs can start managing namespaces uniformly without having to add one-off hooks or security fields to every individual namespace type. Add a ns_security pointer to ns_common and the corresponding lbs_ns blob size to lsm_blob_sizes. Allocation and freeing hooks are called from the common __ns_common_init() and __ns_common_free() paths so every namespace type gets covered in one go. All information about the namespace type and the appropriate casting helpers to get at the containing namespace are available via ns_common making it straightforward for LSMs to differentiate when they need to. A namespace_install hook is called from validate_ns() during setns(2) giving LSMs a chance to enforce policy on namespace transitions. The LSM check runs before ns->ops->install() so the security module can deny the operation before any type-specific installation effects. Individual namespace types can still have their own specialized security hooks when needed. This is just the common baseline that makes it easy to track and manage namespaces from the security side without requiring every namespace type to reinvent the wheel. Cc: Günther Noack Cc: Paul Moore Cc: Serge E. Hallyn Signed-off-by: Christian Brauner Link: https://lore.kernel.org/r/20260216-work-security-namespace-v1-1-075c28758e1f@kernel.org Signed-off-by: Mickaël Salaün [PM: subject tweak] Signed-off-by: Paul Moore --- include/linux/lsm_hook_defs.h | 3 ++ include/linux/lsm_hooks.h | 1 + include/linux/ns/ns_common_types.h | 3 ++ include/linux/security.h | 20 +++++++++ kernel/nscommon.c | 14 ++++++ kernel/nsproxy.c | 6 +++ security/lsm_init.c | 2 + security/security.c | 72 ++++++++++++++++++++++++++++++ 8 files changed, 121 insertions(+) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 65c9609ec20770..0da6c7e8f65957 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -265,6 +265,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2, LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p, struct inode *inode) LSM_HOOK(int, 0, userns_create, const struct cred *cred) +LSM_HOOK(int, 0, namespace_init, struct ns_common *ns) +LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns) +LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns) LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag) LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp, struct lsm_prop *prop) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index c4488c4a6d8a99..13621e9e233e6b 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -112,6 +112,7 @@ struct lsm_blob_sizes { unsigned int lbs_ipc; unsigned int lbs_key; unsigned int lbs_msg_msg; + unsigned int lbs_ns; unsigned int lbs_perf_event; unsigned int lbs_task; unsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */ diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h index ea45c54e4435ed..5cfe0ce3c8818e 100644 --- a/include/linux/ns/ns_common_types.h +++ b/include/linux/ns/ns_common_types.h @@ -116,6 +116,9 @@ struct ns_common { struct dentry *stashed; const struct proc_ns_operations *ops; unsigned int inum; +#ifdef CONFIG_SECURITY + void *ns_security; +#endif union { struct ns_tree; struct rcu_head ns_rcu; diff --git a/include/linux/security.h b/include/linux/security.h index 153e9043058f88..bf002ed14ac83c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -67,6 +67,7 @@ enum fs_value_type; struct watch; struct watch_notification; struct lsm_ctx; +struct nsset; /* Default (no) options for the capable function */ #define CAP_OPT_NONE 0x0 @@ -80,6 +81,7 @@ struct lsm_ctx; struct ctl_table; struct audit_krule; +struct ns_common; struct user_namespace; struct timezone; @@ -540,6 +542,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); void security_task_to_inode(struct task_struct *p, struct inode *inode); int security_create_user_ns(const struct cred *cred); +int security_namespace_init(struct ns_common *ns); +void security_namespace_free(struct ns_common *ns); +int security_namespace_install(const struct nsset *nsset, struct ns_common *ns); int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag); void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop); int security_msg_msg_alloc(struct msg_msg *msg); @@ -1431,6 +1436,21 @@ static inline int security_create_user_ns(const struct cred *cred) return 0; } +static inline int security_namespace_init(struct ns_common *ns) +{ + return 0; +} + +static inline void security_namespace_free(struct ns_common *ns) +{ +} + +static inline int security_namespace_install(const struct nsset *nsset, + struct ns_common *ns) +{ + return 0; +} + static inline int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag) { diff --git a/kernel/nscommon.c b/kernel/nscommon.c index e6f623e1bc371c..e72426bba29ab1 100644 --- a/kernel/nscommon.c +++ b/kernel/nscommon.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope refcount_set(&ns->__ns_ref, 1); ns->stashed = NULL; +#ifdef CONFIG_SECURITY + ns->ns_security = NULL; +#endif ns->ops = ops; ns->ns_id = 0; ns->ns_type = ns_type; @@ -77,6 +81,14 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope ret = proc_alloc_inum(&ns->inum); if (ret) return ret; + + ret = security_namespace_init(ns); + if (ret) { + if (!inum) + proc_free_inum(ns->inum); + return ret; + } + /* * Tree ref starts at 0. It's incremented when namespace enters * active use (installed in nsproxy) and decremented when all @@ -91,6 +103,8 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope void __ns_common_free(struct ns_common *ns) { + security_namespace_free(ns); + if (ns->inum > MNT_NS_INO_SPECIAL_MAX) proc_free_inum(ns->inum); } diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c index d9d3d5973bf523..0f1b208d8eeff1 100644 --- a/kernel/nsproxy.c +++ b/kernel/nsproxy.c @@ -385,6 +385,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset) static inline int validate_ns(struct nsset *nsset, struct ns_common *ns) { + int ret; + + ret = security_namespace_install(nsset, ns); + if (ret) + return ret; + return ns->ops->install(nsset, ns); } diff --git a/security/lsm_init.c b/security/lsm_init.c index 04b18d06ba591d..751da523bb425a 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -303,6 +303,7 @@ static void __init lsm_prepare(struct lsm_info *lsm) lsm_blob_size_update(&blobs->lbs_ipc, &blob_sizes.lbs_ipc); lsm_blob_size_update(&blobs->lbs_key, &blob_sizes.lbs_key); lsm_blob_size_update(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg); + lsm_blob_size_update(&blobs->lbs_ns, &blob_sizes.lbs_ns); lsm_blob_size_update(&blobs->lbs_perf_event, &blob_sizes.lbs_perf_event); lsm_blob_size_update(&blobs->lbs_sock, &blob_sizes.lbs_sock); @@ -450,6 +451,7 @@ int __init security_init(void) lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc); lsm_pr("blob(key) size %d\n", blob_sizes.lbs_key); lsm_pr("blob(msg_msg)_size %d\n", blob_sizes.lbs_msg_msg); + lsm_pr("blob(ns) size %d\n", blob_sizes.lbs_ns); lsm_pr("blob(sock) size %d\n", blob_sizes.lbs_sock); lsm_pr("blob(superblock) size %d\n", blob_sizes.lbs_superblock); lsm_pr("blob(perf_event) size %d\n", blob_sizes.lbs_perf_event); diff --git a/security/security.c b/security/security.c index 2ee276ab15c5bc..b284e24e3771ab 100644 --- a/security/security.c +++ b/security/security.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -381,6 +382,19 @@ static int lsm_superblock_alloc(struct super_block *sb) GFP_KERNEL); } +/** + * lsm_ns_alloc - allocate a composite namespace blob + * @ns: the namespace that needs a blob + * + * Allocate the namespace blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_ns_alloc(struct ns_common *ns) +{ + return lsm_blob_alloc(&ns->ns_security, blob_sizes.lbs_ns, GFP_KERNEL); +} + /** * lsm_fill_user_ctx - Fill a user space lsm_ctx structure * @uctx: a userspace LSM context to be filled @@ -3357,6 +3371,64 @@ int security_create_user_ns(const struct cred *cred) return call_int_hook(userns_create, cred); } +/** + * security_namespace_init() - Initialize LSM security data for a namespace + * @ns: the namespace being initialized + * + * Initialize the LSM security blob attached to the namespace. The namespace type + * is available via ns->ns_type, and the owning user namespace (if any) + * via ns->ops->owner(ns). + * + * Return: Returns 0 if successful, otherwise < 0 error code. + */ +int security_namespace_init(struct ns_common *ns) +{ + int rc; + + rc = lsm_ns_alloc(ns); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(namespace_init, ns); + if (unlikely(rc)) + security_namespace_free(ns); + + return rc; +} + +/** + * security_namespace_free() - Release LSM security data from a namespace + * @ns: the namespace being freed + * + * Release security data attached to the namespace. Called before the + * namespace structure is freed. + */ +void security_namespace_free(struct ns_common *ns) +{ + if (!ns->ns_security) + return; + + call_void_hook(namespace_free, ns); + + kfree(ns->ns_security); + ns->ns_security = NULL; +} + +/** + * security_namespace_install() - Check permission to install a namespace + * @nsset: the target nsset being configured + * @ns: the namespace being installed + * + * Check permission before allowing a namespace to be installed into the + * process's set of namespaces via setns(2). + * + * Return: Returns 0 if permission is granted, otherwise < 0 error code. + */ +int security_namespace_install(const struct nsset *nsset, struct ns_common *ns) +{ + return call_int_hook(namespace_install, nsset, ns); +} + /** * security_ipc_permission() - Check if sysv ipc access is allowed * @ipcp: ipc permission structure From e26307d83048803274837abf33f65396a88e21ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Sun, 26 Jul 2026 18:13:48 +0200 Subject: [PATCH 0046/2286] lsm: add LSM_AUDIT_DATA_NS for namespace audit records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new LSM audit data type LSM_AUDIT_DATA_NS that logs namespace information in audit records. Two fields are provided: - namespace_type: the CLONE_NEW* flag identifying the namespace type, logged in hexadecimal. - namespace_id: the unique 64-bit namespace identifier, retrievable from userspace via NS_GET_ID or listns(2). Unlike the proc inode number (inum), namespace_id is never recycled. For namespace creation denials, namespace_id is 0 because the namespace does not exist yet. A new audit data type is needed because no existing LSM_AUDIT_DATA_* type carries namespace information. The closest alternatives (e.g. LSM_AUDIT_DATA_TASK or LSM_AUDIT_DATA_NONE with custom strings) would either lose the namespace type or require ad-hoc formatting that bypasses the structured audit data union. Cc: Günther Noack Cc: Paul Moore Reviewed-by: Christian Brauner Reviewed-by: Günther Noack Signed-off-by: Mickaël Salaün [PM: corrected audit fields in the description, subject tweaks] Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 5 +++++ security/lsm_audit.c | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 584db296e43b20..526a8e7471c80c 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -78,6 +78,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_NOTIFICATION 16 #define LSM_AUDIT_DATA_ANONINODE 17 #define LSM_AUDIT_DATA_NLMSGTYPE 18 +#define LSM_AUDIT_DATA_NS 19 union { struct path path; struct dentry *dentry; @@ -100,6 +101,10 @@ struct common_audit_data { int reason; const char *anonclass; u16 nlmsg_type; + struct { + u32 ns_type; + u64 ns_id; + } ns; } u; /* this union contains LSM specific data */ union { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 737f5a263a8f79..404ccbbbf94cbe 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -403,6 +403,10 @@ void audit_log_lsm_data(struct audit_buffer *ab, case LSM_AUDIT_DATA_NLMSGTYPE: audit_log_format(ab, " nl-msgtype=%hu", a->u.nlmsg_type); break; + case LSM_AUDIT_DATA_NS: + audit_log_format(ab, " namespace_type=0x%x namespace_id=%llu", + a->u.ns.ns_type, a->u.ns.ns_id); + break; } /* switch (a->type) */ } From 11d73af7b0830f278cca77804d2aa802525ec243 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Thu, 13 Aug 2026 13:52:13 -0400 Subject: [PATCH 0047/2286] lsm: update the BUILD_BUG_ON() in audit_log_lsm_data() On 32-bit systems the newly added namespace fields in the common_audit_data struct exceed the BUILD_BUG_ON(sizeof(void *) * 2) size check in audit_log_lsm_data(). Convert the size check to two u64 types to both fit the new fields and move to a size that is not architecture dependent. Fixes: bb20ca26f960 ("lsm: add LSM_AUDIT_DATA_NS for namespace audit records") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608131232.Owej9tlx-lkp@intel.com/ Signed-off-by: Paul Moore --- security/lsm_audit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 404ccbbbf94cbe..29116ef2986b67 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -182,7 +182,7 @@ void audit_log_lsm_data(struct audit_buffer *ab, * start making this union too large! See struct lsm_network_audit * as an example of how to deal with large data. */ - BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2); + BUILD_BUG_ON(sizeof(a->u) > (sizeof(u64) * 2)); switch (a->type) { case LSM_AUDIT_DATA_NONE: From f3efc8d3567ba16d349410df9046fcfc367b4bda Mon Sep 17 00:00:00 2001 From: Cai Xinchen Date: Tue, 7 Jul 2026 08:06:14 +0000 Subject: [PATCH 0048/2286] lsm: delete duplicate assignment in lsm_prepare() Delete a duplicate assignment of the LSM blob size variable in lsm_prepare(). Signed-off-by: Cai Xinchen [PM: subject and description fixes] Signed-off-by: Paul Moore --- security/lsm_init.c | 1 - 1 file changed, 1 deletion(-) diff --git a/security/lsm_init.c b/security/lsm_init.c index 751da523bb425a..dbda7771013b8f 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -290,7 +290,6 @@ static void __init lsm_prepare(struct lsm_info *lsm) return; /* Register the LSM blob sizes. */ - blobs = lsm->blobs; lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred); lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file); lsm_blob_size_update(&blobs->lbs_backing_file, From 3a84fc1a21577dbc8da7e4b6a49c9d8b07c07000 Mon Sep 17 00:00:00 2001 From: Cai Xinchen Date: Tue, 7 Jul 2026 08:06:15 +0000 Subject: [PATCH 0049/2286] lsm: don't call security_backing_file_free() multiple times I found the following path: alloc_empty_backing-file init_file(&ff->file, xxx) -> file_ref_init(&f->f_ref, 1); // only 1 error = init_backing_file -> security_backing_file_alloc -> rc = call_int_hook(backing_file_alloc, ...) if (unlikely(rc)) security_backing_file_free(backing_file); // first call if (unlikely(error)) { fput(&ff->file); -> if (unlikely(file_ref_put(&file->f_ref))) // zero __fput_deferred(file); -> ____fput -> __fput -> file_free(file); -> backing_file_free(backing_file(f)); -> security_backing_file_free(&ff->file); // second call Currently, only SELinux has the lsm backing_file_alloc hook, and it always return 0. When security_backing_file_free is called for the first time, the blobs pointer is set to NULL. Therefore, double free will not occur in the code. Fixes: 6af36aeb147a ("lsm: add backing_file LSM hooks") Signed-off-by: Cai Xinchen [PM: subject line tweaks] Signed-off-by: Paul Moore --- security/security.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/security.c b/security/security.c index b284e24e3771ab..ecfb875f0c69dd 100644 --- a/security/security.c +++ b/security/security.c @@ -2500,9 +2500,8 @@ void security_backing_file_free(struct file *backing_file) { void *blob = backing_file_security(backing_file); - call_void_hook(backing_file_free, backing_file); - if (blob) { + call_void_hook(backing_file_free, backing_file); backing_file_set_security(backing_file, NULL); kmem_cache_free(lsm_backing_file_cache, blob); } From 722c81401776e4917df4822cdb0362d84e410794 Mon Sep 17 00:00:00 2001 From: Drif Abdelmalek Mohamed Said Date: Tue, 18 Aug 2026 22:46:03 +0100 Subject: [PATCH 0050/2286] docs: filesystems: proc: fix typos Fix five typographical errors in the /proc filesystem documentation. Signed-off-by: Drif Abdelmalek Mohamed Said Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260818214604.20349-1-drifabdelmalekmohamedsaid@gmail.com> --- Documentation/filesystems/proc.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst index c102b62023cdc1..78a632fea2bf5c 100644 --- a/Documentation/filesystems/proc.rst +++ b/Documentation/filesystems/proc.rst @@ -440,7 +440,7 @@ ioctl()-based API that gives ability to flexibly and efficiently query and filter individual VMAs. This interface is binary and is meant for more efficient and easy programmatic use. `struct procmap_query`, defined in linux/fs.h UAPI header, serves as an input/output argument to the -`PROCMAP_QUERY` ioctl() command. See comments in linus/fs.h UAPI header for +`PROCMAP_QUERY` ioctl() command. See comments in linux/fs.h UAPI header for details on query semantics, supported flags, data returned, and general API usage information. @@ -576,14 +576,14 @@ encoded manner. The codes are the following: == ============================================================= rd readable - wr writeable + wr writable ex executable sh shared mr may read mw may write me may execute ms may share - gd stack segment growns down + gd stack segment grows down pf pure PFN range lo pages are locked in memory io memory mapped I/O area @@ -719,7 +719,7 @@ size, in KB, that is backing the mapping up. Note that some kernel configurations do not track the precise number of times a page part of a larger allocation (e.g., THP) is mapped. In these -configurations, "mapmax" might corresponds to the average number of mappings +configurations, "mapmax" might correspond to the average number of mappings per page in such a larger allocation instead. 1.2 Kernel data @@ -2008,7 +2008,7 @@ For more information on mount propagation see: These files provide a method to access a task's comm value. It also allows for a task to set its own or one of its thread siblings comm value. The comm value is limited in size compared to the cmdline value, so writing anything longer -then the kernel's TASK_COMM_LEN (currently 16 chars, including the NUL +than the kernel's TASK_COMM_LEN (currently 16 chars, including the NUL terminator) will result in a truncated comm value. From 4be78eadd1ab986153a69a1061b03584f169d10b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 19 Aug 2026 08:57:14 +0200 Subject: [PATCH 0051/2286] XArray: document that tags persist after storing new elements This is a very useful feature, document it. Signed-off-by: Christoph Hellwig Signed-off-by: Jonathan Corbet Message-ID: <20260819065714.1358822-1-hch@lst.de> --- Documentation/core-api/xarray.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/core-api/xarray.rst b/Documentation/core-api/xarray.rst index c6c91cbd0c3ce4..9a0d930a8c79dd 100644 --- a/Documentation/core-api/xarray.rst +++ b/Documentation/core-api/xarray.rst @@ -127,6 +127,8 @@ by using xa_set_mark() and remove the mark from an entry by calling xa_clear_mark(). You can ask whether any entry in the XArray has a particular mark set by calling xa_marked(). Erasing an entry from the XArray causes all marks associated with that entry to be cleared. +Storing a new entry that replaces an existing entry keeps the marks +associated with the index intact. Setting or clearing a mark on any index of a multi-index entry will affect all indices covered by that entry. Querying the mark on any From f4fda73b0f784eb87867735e5fd92362c54fae83 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 10:22:47 +0200 Subject: [PATCH 0052/2286] docs: parport: Fix FIFO Kconfig symbol The ReST conversion shortened CONFIG_PARPORT_PC_FIFO to the nonexistent CONFIG_FIFO symbol. Restore the full name. Fixes: e095f0711b49 ("Documentation/parport.txt: convert to ReST markup") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822082248.89651-1-kmehltretter@gmail.com> --- Documentation/admin-guide/parport.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/parport.rst b/Documentation/admin-guide/parport.rst index ad3f9b8a11e19d..4aef45262bd49f 100644 --- a/Documentation/admin-guide/parport.rst +++ b/Documentation/admin-guide/parport.rst @@ -273,7 +273,7 @@ If that works fine, try with ``io=0x378 irq=7`` (adjust for your hardware), to make it use interrupt-driven in-software protocol. If **that** works fine, then one of the hardware modes isn't working -right. Enable ``CONFIG_FIFO`` (no, it isn't a module option, +right. Enable ``CONFIG_PARPORT_PC_FIFO`` (no, it isn't a module option, and yes, it should be), set the port to ECP mode in the BIOS and note the DMA channel, and try with:: From eb52e615f23790915eb1d260cef3363906d4bd5c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:45 +0200 Subject: [PATCH 0053/2286] docs: cpu_hotplug: Fix multi-instance removal calls The function is named cpuhp_state_remove_instance(), not cpuhp_remove_instance(). Fixes: c9871c800f65 ("Documentation: core-api/cpuhotplug: Rewrite the API section") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-2-kmehltretter@gmail.com> --- Documentation/core-api/cpu_hotplug.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/core-api/cpu_hotplug.rst b/Documentation/core-api/cpu_hotplug.rst index 6de26d1c6a9ad5..f8b3f59a3d5f71 100644 --- a/Documentation/core-api/cpu_hotplug.rst +++ b/Documentation/core-api/cpu_hotplug.rst @@ -607,9 +607,9 @@ ONLINE section for notifications on online and offline operation:: if (ret) return ret; .... - cpuhp_remove_instance(state, &inst1->node); + cpuhp_state_remove_instance(state, &inst1->node); .... - cpuhp_remove_instance(state, &inst2->node); + cpuhp_state_remove_instance(state, &inst2->node); .... cpuhp_remove_multi_state(state); From df72403da9ec8e18086c71c4c3bc608856dfa023 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:46 +0200 Subject: [PATCH 0054/2286] docs: irq-affinity: Restore grep in example The command is missing grep between the pipe and its pattern. Fixes: 18404756765c ("genirq: Expose default irq affinity mask (take 3)") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-3-kmehltretter@gmail.com> --- Documentation/core-api/irq/irq-affinity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/irq/irq-affinity.rst b/Documentation/core-api/irq/irq-affinity.rst index 9cb460cf60b616..671604c7395fe1 100644 --- a/Documentation/core-api/irq/irq-affinity.rst +++ b/Documentation/core-api/irq/irq-affinity.rst @@ -53,7 +53,7 @@ Now lets restrict that IRQ to CPU(4-7). --- hell ping statistics --- 2779 packets transmitted, 2777 packets received, 0% packet loss round-trip min/avg/max = 0.1/0.5/585.4 ms - [root@moon 44]# cat /proc/interrupts | 'CPU\|44:' + [root@moon 44]# cat /proc/interrupts | grep 'CPU\|44:' CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 44: 1068 1785 1785 1783 1784 1069 1070 1069 IO-APIC-level eth1 From df5489eae615d81da9036b8709f7c169f2da7945 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:47 +0200 Subject: [PATCH 0055/2286] docs: dma-attributes: Fix dma_alloc_attrs() name dma_alloc_attr() does not exist; the allocation API is dma_alloc_attrs(). Fixes: d5724f172fd1 ("common: DMA-mapping: add DMA_ATTR_NO_KERNEL_MAPPING attribute") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Acked-by: Marek Szyprowski Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-4-kmehltretter@gmail.com> --- Documentation/core-api/dma-attributes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/dma-attributes.rst b/Documentation/core-api/dma-attributes.rst index eee743184acdec..b07126b5b61675 100644 --- a/Documentation/core-api/dma-attributes.rst +++ b/Documentation/core-api/dma-attributes.rst @@ -34,7 +34,7 @@ such mapping is non-trivial task and consumes very limited resources (like kernel virtual address space or dma consistent address space). Buffers allocated with this attribute can be only passed to user space by calling dma_mmap_attrs(). By using this API, you are guaranteeing -that you won't dereference the pointer returned by dma_alloc_attr(). You +that you won't dereference the pointer returned by dma_alloc_attrs(). You can treat it as a cookie that must be passed to dma_mmap_attrs() and dma_free_attrs(). Make sure that both of these also get this attribute set on each call. From 9fcbaa9e4ce7af17524b6c1d173e7a77eaad728d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:48 +0200 Subject: [PATCH 0056/2286] docs: swiotlb: Fix maximum mapping size helper The DMA helper is named dma_max_mapping_size(), not dma_map_mapping_size(). Fixes: c93f261dfc39 ("Documentation/core-api: add swiotlb documentation") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-5-kmehltretter@gmail.com> --- Documentation/core-api/swiotlb.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/swiotlb.rst b/Documentation/core-api/swiotlb.rst index 71b4e4c27eb5c0..211cc3499315a2 100644 --- a/Documentation/core-api/swiotlb.rst +++ b/Documentation/core-api/swiotlb.rst @@ -107,7 +107,7 @@ A single allocation from swiotlb is limited to IO_TLB_SIZE * IO_TLB_SEGSIZE bytes, which is 256 KiB with current definitions. When a device's DMA settings are such that the device might use swiotlb, the maximum size of a DMA segment must be limited to that 256 KiB. This value is communicated to higher-level -kernel code via dma_map_mapping_size() and swiotlb_max_mapping_size(). If the +kernel code via dma_max_mapping_size() and swiotlb_max_mapping_size(). If the higher-level code fails to account for this limit, it may make requests that are too large for swiotlb, and get a "swiotlb full" error. From 55c03a9ae22aa14262a5acca202df33e9c42dcfc Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:49 +0200 Subject: [PATCH 0057/2286] docs: this_cpu_ops: Fix SRCU helper names Use the actual srcu_read_lock_nmisafe() and atomic_long_inc() helper names. Fixes: df0cee43114b ("docs: Improve discussion of this_cpu_ptr(), add raw_cpu_ptr()") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Paul E. McKenney Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-6-kmehltretter@gmail.com> --- Documentation/core-api/this_cpu_ops.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/core-api/this_cpu_ops.rst b/Documentation/core-api/this_cpu_ops.rst index 533ac5dd575076..367706d1714b1c 100644 --- a/Documentation/core-api/this_cpu_ops.rst +++ b/Documentation/core-api/this_cpu_ops.rst @@ -150,10 +150,10 @@ preemptible code are addressed by raw_cpu_ptr(), but such use cases need to handle cases where two different CPUs are accessing the same per cpu variable, which might well be that of a third CPU. These use cases are typically performance optimizations. For example, SRCU implements a pair -of counters as a pair of per-CPU variables, and rcu_read_lock_nmisafe() +of counters as a pair of per-CPU variables, and srcu_read_lock_nmisafe() uses raw_cpu_ptr() to get a pointer to some CPU's counter, and uses -atomic_inc_long() to handle migration between the raw_cpu_ptr() and -the atomic_inc_long(). +atomic_long_inc() to handle migration between the raw_cpu_ptr() and +the atomic_long_inc(). Per cpu variables and offsets ----------------------------- From 9f3a1837849c21df0d3b63b0b67744d27d3fadd1 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:50 +0200 Subject: [PATCH 0058/2286] docs: xarray: Fix xas_try_split() name The iterative split helper is xas_try_split(), not xas_try_alloc(). Fixes: 3fec86f8aa8c ("xarray: add xas_try_split() to split a multi-index entry") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Zi Yan Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-7-kmehltretter@gmail.com> --- Documentation/core-api/xarray.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/xarray.rst b/Documentation/core-api/xarray.rst index 9a0d930a8c79dd..82876fbb3bf289 100644 --- a/Documentation/core-api/xarray.rst +++ b/Documentation/core-api/xarray.rst @@ -492,7 +492,7 @@ entry at every index to ``NULL`` and dissolve the tie. A multi-index entry can be split into entries occupying smaller ranges by calling xas_split_alloc() without the xa_lock held, followed by taking the lock and calling xas_split() or calling xas_try_split() with xa_lock. The -difference between xas_split_alloc()+xas_split() and xas_try_alloc() is +difference between xas_split_alloc()+xas_split() and xas_try_split() is that xas_split_alloc() + xas_split() split the entry from the original order to the new order in one shot uniformly, whereas xas_try_split() iteratively splits the entry containing the index non-uniformly. From a5f506ada43fa009e3c425172f94ce2bc42d4f66 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 22 Aug 2026 14:16:51 +0200 Subject: [PATCH 0059/2286] docs: debugobjects: Fix debug_object_init() name The initialization helper is debug_object_init(), not debug_objects_init(). Fixes: 691cc54c7d28 ("debugobjects: add documentation") Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260822121651.1559-8-kmehltretter@gmail.com> --- Documentation/core-api/debug-objects.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/debug-objects.rst b/Documentation/core-api/debug-objects.rst index ac926fd55a64a9..708775fc581b74 100644 --- a/Documentation/core-api/debug-objects.rst +++ b/Documentation/core-api/debug-objects.rst @@ -129,7 +129,7 @@ When the real object is not yet tracked by debugobjects then the fixup_activate function is called if available. This is necessary to allow the legitimate activation of statically allocated and initialized objects. The fixup function checks whether the object is valid and calls -the debug_objects_init() function to initialize the tracking of this +the debug_object_init() function to initialize the tracking of this object. When the activation is legitimate, then the state of the associated From 4a0be1a25d98672391e8ad2a0802f08609397274 Mon Sep 17 00:00:00 2001 From: Nguyen Duc Thinh Date: Mon, 24 Aug 2026 20:38:26 +0700 Subject: [PATCH 0060/2286] Documentation: process: Capitalize Git, Quilt, and Mercurial consistently Update various process documentation files to properly capitalize "Git", "Quilt", and "Mercurial" as proper nouns when referring to the source management tools themselves. Instances explicitly referring to literal command-line invocations (such as "git log" or "git bisect") have been left lowercase. Signed-off-by: Nguyen Duc Thinh Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260824133826.13934-1-ducthinh100812@gmail.com> --- Documentation/process/1.Intro.rst | 2 +- Documentation/process/2.Process.rst | 18 ++++----- Documentation/process/3.Early-stage.rst | 2 +- Documentation/process/5.Posting.rst | 10 ++--- Documentation/process/7.AdvancedTopics.rst | 36 +++++++++--------- Documentation/process/backporting.rst | 18 ++++----- .../process/embargoed-hardware-issues.rst | 2 +- .../process/maintainer-pgp-guide.rst | 38 +++++++++---------- 8 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Documentation/process/1.Intro.rst b/Documentation/process/1.Intro.rst index 2c93caea069f0a..847fbe76b6a445 100644 --- a/Documentation/process/1.Intro.rst +++ b/Documentation/process/1.Intro.rst @@ -42,7 +42,7 @@ avoid problems at this important stage. Developers are cautioned against assuming that the job is done when a patch is merged into the mainline. :ref:`development_advancedtopics` introduces a couple of "advanced" topics: -managing patches with git and reviewing patches posted by others. +managing patches with Git and reviewing patches posted by others. :ref:`development_conclusion` concludes the document with pointers to sources for more information on kernel development. diff --git a/Documentation/process/2.Process.rst b/Documentation/process/2.Process.rst index 77f3f80e7cd7e1..d09fa23d6c42ae 100644 --- a/Documentation/process/2.Process.rst +++ b/Documentation/process/2.Process.rst @@ -223,8 +223,8 @@ of the kernel they manage; they are the ones who will (usually) accept a patch for inclusion into the mainline kernel. Subsystem maintainers each manage their own version of the kernel source -tree, usually (but certainly not always) using the git source management -tool. Tools like git (and related tools like quilt or mercurial) allow +tree, usually (but certainly not always) using the Git source management +tool. Tools like Git (and related tools like Quilt or Mercurial) allow maintainers to track a list of patches, including authorship information and other metadata. At any given time, the maintainer can identify which patches in his or her repository are not found in the mainline. @@ -343,13 +343,13 @@ are well beyond the scope of this document, but there is space for a few pointers. By far the dominant source code management system used by the kernel -community is git. Git is one of a number of distributed version control +community is Git. Git is one of a number of distributed version control systems being developed in the free software community. It is well tuned for kernel development, in that it performs quite well when dealing with large repositories and large numbers of patches. It also has a reputation for being difficult to learn and use, though it has gotten better over -time. Some sort of familiarity with git is almost a requirement for kernel -developers; even if they do not use it for their own work, they'll need git +time. Some sort of familiarity with Git is almost a requirement for kernel +developers; even if they do not use it for their own work, they'll need Git to keep up with what other developers (and the mainline) are doing. Git is now packaged by almost all Linux distributions. There is a home @@ -359,12 +359,12 @@ page at: That page has pointers to documentation and tutorials. -Among the kernel developers who do not use git, the most popular choice is +Among the kernel developers who do not use Git, the most popular choice is almost certainly Mercurial: https://www.selenic.com/mercurial/ -Mercurial shares many features with git, but it provides an interface which +Mercurial shares many features with Git, but it provides an interface which many find easier to use. The other tool worth knowing about is Quilt: @@ -374,9 +374,9 @@ The other tool worth knowing about is Quilt: Quilt is a patch management system, rather than a source code management system. It does not track history over time; it is, instead, oriented toward tracking a specific set of changes against an evolving code base. -Some major subsystem maintainers use quilt to manage patches intended to go +Some major subsystem maintainers use Quilt to manage patches intended to go upstream. For the management of certain kinds of trees (-mm, for example), -quilt is the best tool for the job. +Quilt is the best tool for the job. Mailing lists diff --git a/Documentation/process/3.Early-stage.rst b/Documentation/process/3.Early-stage.rst index 894a920041c624..87fa7875e3369e 100644 --- a/Documentation/process/3.Early-stage.rst +++ b/Documentation/process/3.Early-stage.rst @@ -138,7 +138,7 @@ the place to start. That file tends to not always be up to date, though, and not all subsystems are represented there. The person listed in the MAINTAINERS file may, in fact, not be the person who is actually acting in that role currently. So, when there is doubt about who to contact, a -useful trick is to use git (and "git log" in particular) to see who is +useful trick is to use Git (and "git log" in particular) to see who is currently active within the subsystem of interest. Look at who is writing patches, and who, if anybody, is attaching Signed-off-by lines to those patches. Those are the people who will be best placed to help with a new diff --git a/Documentation/process/5.Posting.rst b/Documentation/process/5.Posting.rst index 07d7dbed13ec6a..b8a449980452eb 100644 --- a/Documentation/process/5.Posting.rst +++ b/Documentation/process/5.Posting.rst @@ -21,7 +21,7 @@ There is a constant temptation to avoid posting patches before they are completely "ready." For simple patches, that is not a problem. If the work being done is complex, though, there is a lot to be gained by getting feedback from the community before the work is complete. So you should -consider posting in-progress work, or even making a git tree available so +consider posting in-progress work, or even making a Git tree available so that interested developers can catch up with your work at any time. When posting code which is not yet considered ready for inclusion, it is a @@ -71,7 +71,7 @@ even in the short term. Patches must be prepared against a specific version of the kernel. As a general rule, a patch should be based on the current mainline as found in -Linus's git tree. When basing on mainline, start with a well-known release +Linus's Git tree. When basing on mainline, start with a well-known release point - a stable or -rc release - rather than branching off the mainline at an arbitrary spot. @@ -320,7 +320,7 @@ copies should go to: the MAINTAINERS file is the first place to look for these people. - Other developers who have been working in the same area - especially - those who might be working there now. Using git to see who else has + those who might be working there now. Using Git to see who else has modified the files you are working on can be helpful. - If you are responding to a bug report or a feature request, copy the @@ -362,7 +362,7 @@ that the patches, themselves, have complete changelog information. In general, the second and following parts of a multi-part patch should be sent as a reply to the first part so that they all thread together at the -receiving end. Tools like git and quilt have commands to mail out a set of +receiving end. Tools like Git and Quilt have commands to mail out a set of patches with the proper threading. If you have a long series, though, and -are using git, please stay away from the --chain-reply-to option to avoid +are using Git, please stay away from the --chain-reply-to option to avoid creating exceptionally deep nesting. diff --git a/Documentation/process/7.AdvancedTopics.rst b/Documentation/process/7.AdvancedTopics.rst index 185651d87f2af6..066655e5d536ce 100644 --- a/Documentation/process/7.AdvancedTopics.rst +++ b/Documentation/process/7.AdvancedTopics.rst @@ -8,7 +8,7 @@ works. There is still more to learn, however! This section will cover a number of topics which can be helpful for developers wanting to become a regular part of the Linux kernel development process. -Managing patches with git +Managing patches with Git ------------------------- The use of distributed version control for the kernel began in early 2002, @@ -17,17 +17,17 @@ application. While BitKeeper was controversial, the approach to software version management it embodied most certainly was not. Distributed version control enabled an immediate acceleration of the kernel development project. In current times, there are several free alternatives to -BitKeeper. For better or for worse, the kernel project has settled on git +BitKeeper. For better or for worse, the kernel project has settled on Git as its tool of choice. -Managing patches with git can make life much easier for the developer, +Managing patches with Git can make life much easier for the developer, especially as the volume of those patches grows. Git also has its rough edges and poses certain hazards; it is a young and powerful tool which is still being civilized by its developers. This document will not attempt to -teach the reader how to use git; that would be sufficient material for a -long document in its own right. Instead, the focus here will be on how git +teach the reader how to use Git; that would be sufficient material for a +long document in its own right. Instead, the focus here will be on how Git fits into the kernel development process in particular. Developers who -wish to come up to speed with git will find more information at: +wish to come up to speed with Git will find more information at: https://git-scm.com/ @@ -36,20 +36,20 @@ wish to come up to speed with git will find more information at: and on various tutorials found on the web. The first order of business is to read the above sites and get a solid -understanding of how git works before trying to use it to make patches +understanding of how Git works before trying to use it to make patches available to others. A git-using developer should be able to obtain a copy of the mainline repository, explore the revision history, commit changes to -the tree, use branches, etc. An understanding of git's tools for the +the tree, use branches, etc. An understanding of Git's tools for the rewriting of history (such as rebase) is also useful. Git comes with its -own terminology and concepts; a new user of git should know about refs, +own terminology and concepts; a new user of Git should know about refs, remote branches, the index, fast-forward merges, pushes and pulls, detached heads, etc. It can all be a little intimidating at the outset, but the concepts are not that hard to grasp with a bit of study. -Using git to generate patches for submission by email can be a good +Using Git to generate patches for submission by email can be a good exercise while coming up to speed. -When you are ready to start putting up git trees for others to look at, you +When you are ready to start putting up Git trees for others to look at, you will, of course, need a server that can be pulled from. Setting up such a server with git-daemon is relatively straightforward if you have a system which is accessible to the Internet. Otherwise, free, public hosting sites @@ -57,9 +57,9 @@ which is accessible to the Internet. Otherwise, free, public hosting sites developers can get an account on kernel.org, but those are not easy to come by; see https://kernel.org/faq/ for more information. -The normal git workflow involves the use of a lot of branches. Each line +The normal Git workflow involves the use of a lot of branches. Each line of development can be separated into a separate "topic branch" and -maintained independently. Branches in git are cheap, there is no reason to +maintained independently. Branches in Git are cheap, there is no reason to not make free use of them. And, in any case, you should not do your development in any branch which you intend to ask others to pull from. Publicly-available branches should be created with care; merge in patches @@ -72,7 +72,7 @@ say, or which has some other sort of obvious bug) can be fixed in place or made to disappear from the history entirely. A patch series can be rewritten as if it had been written on top of today's mainline, even though you have been working on it for months. Changes can be transparently -shifted from one branch to another. And so on. Judicious use of git's +shifted from one branch to another. And so on. Judicious use of Git's ability to revise history can help in the creation of clean patch sets with fewer problems. @@ -111,16 +111,16 @@ perform test merges in a private branch. The git "rerere" tool can be useful in such situations; it remembers how merge conflicts were resolved so that you don't have to do the same work twice. -One of the biggest recurring complaints about tools like git is this: the +One of the biggest recurring complaints about tools like Git is this: the mass movement of patches from one repository to another makes it easy to slip in ill-advised changes which go into the mainline below the review radar. Kernel developers tend to get unhappy when they see that kind of -thing happening; putting up a git tree with unreviewed or off-topic patches +thing happening; putting up a Git tree with unreviewed or off-topic patches can affect your ability to get trees pulled in the future. Quoting Linus: :: - You can send me patches, but for me to pull a git patch from you, I + You can send me patches, but for me to pull a Git patch from you, I need to know that you know what you're doing, and I need to be able to trust things *without* then having to go and check every individual change by hand. @@ -130,7 +130,7 @@ can affect your ability to get trees pulled in the future. Quoting Linus: To avoid this kind of situation, ensure that all patches within a given branch stick closely to the associated topic; a "driver fixes" branch should not be making changes to the core memory management code. And, most -importantly, do not use a git tree to bypass the review process. Post an +importantly, do not use a Git tree to bypass the review process. Post an occasional summary of the tree to the relevant list, and, when the time is right, request that the tree be included in linux-next. diff --git a/Documentation/process/backporting.rst b/Documentation/process/backporting.rst index 0de9eacd46a73a..abc5f8925a7893 100644 --- a/Documentation/process/backporting.rst +++ b/Documentation/process/backporting.rst @@ -40,8 +40,8 @@ edit the patch to make it apply. It is strongly recommended to instead find an appropriate base version where the patch applies cleanly and *then* cherry-pick it over to your -destination tree, as this will make git output conflict markers and let -you resolve conflicts with the help of git and any other conflict +destination tree, as this will make Git output conflict markers and let +you resolve conflicts with the help of Git and any other conflict resolution tools you might prefer to use. For example, if you want to apply a patch that just arrived on LKML to an older stable kernel, you can apply it to the most recent mainline kernel and then cherry-pick it @@ -54,7 +54,7 @@ problem with applying the patch to the "wrong" base is that it may pull in more unrelated changes in the context of the diff when cherry-picking it to the older branch. -A good reason to prefer ``git cherry-pick`` over ``git am`` is that git +A good reason to prefer ``git cherry-pick`` over ``git am`` is that Git knows the precise history of an existing commit, so it will know when code has moved around and changed the line numbers; this in turn makes it less likely to apply the patch to the wrong place (which can result @@ -69,7 +69,7 @@ article will assume that you are doing a plain ``git cherry-pick``. .. _b4: https://people.kernel.org/monsieuricon/introducing-b4-and-patch-attestation .. _b4 presentation: https://youtu.be/mF10hgVIx9o?t=2996 -Once you have the patch in git, you can go ahead and cherry-pick it into +Once you have the patch in Git, you can go ahead and cherry-pick it into your source tree. Don't forget to cherry-pick with ``-x`` if you want a written record of where the patch came from! @@ -101,7 +101,7 @@ backporting from contains patches not in the branch you are backporting to. However, the reverse is also possible. In any case, the result is a conflict that needs to be resolved. -If your attempted cherry-pick fails with a conflict, git automatically +If your attempted cherry-pick fails with a conflict, Git automatically edits the files to include so-called conflict markers showing you where the conflict is and how the two branches have diverged. Resolving the conflict typically means editing the end result in such a way that it @@ -128,7 +128,7 @@ pointers to various tools that you could use: - `IntelliJ `__ - `VSCode `__ -To configure git to work with these, see ``git mergetool --help`` or +To configure Git to work with these, see ``git mergetool --help`` or the official `git-mergetool documentation`_. .. _git-mergetool documentation: https://git-scm.com/docs/git-mergetool @@ -326,7 +326,7 @@ style, which looks like this:: this is what the patch wants it to be after being applied >>>>>>> (title) -As you can see, this has 3 parts instead of 2, and includes what git +As you can see, this has 3 parts instead of 2, and includes what Git expected to find there but didn't. It is *highly recommended* to use this conflict style as it makes it much clearer what the patch actually changed; i.e., it allows you to compare the before-and-after versions @@ -382,7 +382,7 @@ Dealing with file renames One of the most annoying things that can happen while backporting a patch is discovering that one of the files being patched has been -renamed, as that typically means git won't even put in conflict markers, +renamed, as that typically means Git won't even put in conflict markers, but will just throw up its hands and say (paraphrased): "Unmerged path! You do the work..." @@ -393,7 +393,7 @@ other hand, if the change is big or complicated, you definitely don't want to do it by hand. As a first pass, you can try something like this, which will lower the -rename detection threshold to 30% (by default, git uses 50%, meaning +rename detection threshold to 30% (by default, Git uses 50%, meaning that two files need to have at least 50% in common for it to consider an add-delete pair to be a potential rename):: diff --git a/Documentation/process/embargoed-hardware-issues.rst b/Documentation/process/embargoed-hardware-issues.rst index d07f16c3c7b82a..2c244a6d14634e 100644 --- a/Documentation/process/embargoed-hardware-issues.rst +++ b/Documentation/process/embargoed-hardware-issues.rst @@ -187,7 +187,7 @@ security issues in the past. The mailing list operates in the same way as normal Linux development. Patches are posted, discussed, and reviewed and if agreed upon, applied to -a non-public git repository which is only accessible to the participating +a non-public Git repository which is only accessible to the participating developers via a secure connection. The repository contains the main development branch against the mainline kernel and backport branches for stable kernel versions as necessary. diff --git a/Documentation/process/maintainer-pgp-guide.rst b/Documentation/process/maintainer-pgp-guide.rst index 652dfbe64102c3..9f6fff2a78e247 100644 --- a/Documentation/process/maintainer-pgp-guide.rst +++ b/Documentation/process/maintainer-pgp-guide.rst @@ -25,16 +25,16 @@ communication channels between developers via PGP-signed email exchange. The Linux kernel source code is available in two main formats: -- Distributed source repositories (git) +- Distributed source repositories (Git) - Periodic release snapshots (tarballs) -Both git repositories and tarballs carry PGP signatures of the kernel +Both Git repositories and tarballs carry PGP signatures of the kernel developers who create official kernel releases. These signatures offer a cryptographic guarantee that downloadable versions made available via kernel.org or any other mirrors are identical to what these developers have on their workstations. To this end: -- git repositories provide PGP signatures on all tags +- Git repositories provide PGP signatures on all tags - tarballs provide detached PGP signatures with all downloads .. _devs_not_infra: @@ -660,12 +660,12 @@ impersonate you without having access to your PGP keys. .. _`nothing to do with it`: https://github.com/jayphelps/git-blame-someone-else -Configure git to use your PGP key +Configure Git to use your PGP key --------------------------------- If you only have one secret key in your keyring, then you don't really need to do anything extra, as it becomes your default key. However, if -you happen to have multiple secret keys, you can tell git which key +you happen to have multiple secret keys, you can tell Git which key should be used (``[fpr]`` is the fingerprint of your key):: $ git config --global user.signingKey [fpr] @@ -677,8 +677,8 @@ To create a signed tag, pass the ``-s`` switch to the tag command:: $ git tag -s [tagname] -Our recommendation is to always sign git tags, as this allows other -developers to ensure that the git repository they are pulling from has +Our recommendation is to always sign Git tags, as this allows other +developers to ensure that the Git repository they are pulling from has not been maliciously altered. How to verify signed tags @@ -689,7 +689,7 @@ To verify a signed tag, use the ``verify-tag`` command:: $ git verify-tag [tagname] If you are pulling a tag from another fork of the project repository, -git should automatically verify the signature at the tip you're pulling +Git should automatically verify the signature at the tip you're pulling and show you the results during the merge operation:: $ git pull [url] tags/sometag @@ -703,15 +703,15 @@ The merge message will contain something like this:: # gpg: Signature made [...] # gpg: Good signature from [...] -If you are verifying someone else's git tag, you will first need to +If you are verifying someone else's Git tag, you will first need to import their PGP key. Please refer to the ":ref:`verify_identities`" section below. -Configure git to always sign annotated tags +Configure Git to always sign annotated tags ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Chances are, if you're creating an annotated tag, you'll want to sign -it. To force git to always sign annotated tags, you can set a global +it. To force Git to always sign annotated tags, you can set a global configuration option:: $ git config --global tag.forceSignAnnotated true @@ -722,15 +722,15 @@ How to work with signed commits It is also possible to create signed commits, but they have limited usefulness in Linux kernel development. The kernel contribution workflow relies on sending in patches, and converting commits to patches does not -preserve git commit signatures. Furthermore, when rebasing your own +preserve Git commit signatures. Furthermore, when rebasing your own repository on a newer upstream, PGP commit signatures will end up discarded. For this reason, most kernel developers don't bother signing their commits and will ignore signed commits in any external repositories that they rely upon in their work. -That said, if you have your working git tree publicly available at some -git hosting service (kernel.org, infradead.org, ozlabs.org, or others), -then the recommendation is that you sign all your git commits even if +That said, if you have your working Git tree publicly available at some +Git hosting service (kernel.org, infradead.org, ozlabs.org, or others), +then the recommendation is that you sign all your Git commits even if upstream developers do not directly benefit from this practice. We recommend this for the following reasons: @@ -752,10 +752,10 @@ command (it's capital ``-S`` due to collision with another flag):: $ git commit -S -Configure git to always sign commits +Configure Git to always sign commits ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can tell git to always sign commits:: +You can tell Git to always sign commits:: git config --global commit.gpgSign true @@ -790,7 +790,7 @@ Installing and configuring patatt Patatt is packaged for many distributions already, so please check there first. You can also install it from pypi using "``pip install patatt``". -If you already have your PGP key configured with git (via the +If you already have your PGP key configured with Git (via the ``user.signingKey`` configuration parameter), then patatt requires no further configuration. You can start signing your patches by installing the git-send-email hook in the repository you want:: @@ -902,7 +902,7 @@ the new default in GnuPG v2). To set it, add (or modify) the Using the kernel.org web of trust repository -------------------------------------------- -Kernel.org maintains a git repository with developers' public keys as a +Kernel.org maintains a Git repository with developers' public keys as a replacement for replicating keyserver networks that have gone mostly dark in the past few years. The full documentation for how to set up that repository as your source of public keys can be found here: From 13d48be7a07249ea720a0f97b088411521db0013 Mon Sep 17 00:00:00 2001 From: Liang Hao Date: Fri, 21 Aug 2026 00:38:58 +0800 Subject: [PATCH 0061/2286] docs: real-time: mention the hrtimer sleeper HARD path The Timers section documents the PREEMPT_RT softirq default and HRTIMER_MODE_HARD, but not the sleeper helper used by userspace sleeps. Describe that path, and rename "ktimersd" to "ktimers" to match ksoftirqd naming in this document. No code or behaviour change. Signed-off-by: Liang Hao Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Jonathan Corbet Message-ID: <20260820163858.27984-1-haohlliang@gmail.com> --- Documentation/core-api/real-time/differences.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/core-api/real-time/differences.rst b/Documentation/core-api/real-time/differences.rst index a129570dab5a3f..6f5be9f0da2e7a 100644 --- a/Documentation/core-api/real-time/differences.rst +++ b/Documentation/core-api/real-time/differences.rst @@ -119,12 +119,20 @@ timers initialized with the HRTIMER_MODE_SOFT flag, which are executed in softirq context. On a PREEMPT_RT kernel, this behavior is reversed: hrtimers are executed in -softirq context by default, typically within the ktimersd thread. This thread +softirq context by default, typically within the ktimers thread. This thread runs at the lowest real-time priority, ensuring it executes before any SCHED_OTHER tasks but does not interfere with higher-priority real-time threads. To explicitly request execution in hard interrupt context on PREEMPT_RT, the timer must be marked with the HRTIMER_MODE_HARD flag. +Userland sleepers usually deploy a hrtimer to guarantee a precise wakeup +time. The timer is initialized with hrtimer_setup_sleeper_on_stack(), which +distinguishes between real-time and regular tasks. The hrtimer of a task +without a real-time priority is handled in soft interrupt context, but for +real-time priorities HRTIMER_MODE_HARD is used. This ensures that real-time +tasks are woken up as soon as possible while ordinary tasks cannot block the +CPU with a thundering herd of wakeups. + Memory allocation ----------------- From ab2704c2a884028fd12d455cf27a9585aefe2961 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Wed, 26 Aug 2026 16:29:19 +0300 Subject: [PATCH 0062/2286] docs: sysctl: timer_migration is for hrtimer only Since commit b2cf7507e1864 ("timers: Always queue timers on the local CPU") __mod_timer() no longer depends on timers_migration_enabled. Only high-resolution timer migration from isolated to housekeeping CPUs depends on it. Update and clarify the documentation accordingly. Signed-off-by: Costa Shulyupin Reviewed-by: Bradley Morgan Signed-off-by: Jonathan Corbet Message-ID: <20260826132921.294142-1-costa.shul@redhat.com> --- Documentation/admin-guide/sysctl/kernel.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index b6328cd0f43e94..fb38d654846d9c 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1609,8 +1609,10 @@ If a value outside of this range is written to ``threads-max`` an timer_migration =============== -When set to a non-zero value, attempt to migrate timers away from idle cpus to -allow them to remain in low power states longer. +When set to a non-zero value, attempt to migrate high-resolution timers from +nohz isolated (nohz_full) to housekeeping CPUs. +See Documentation/admin-guide/cpu-isolation.rst +and Documentation/admin-guide/kernel-parameters.rst. Default is set (1). From f9f31f5f13b5e5459e2d3322fe6b5e19d511919a Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 16 Aug 2026 22:53:54 +0100 Subject: [PATCH 0063/2286] kconfig: error out for recursive range Currently, it is possible to create recursive ranges with Kconfig, such as: config C int "Enable C" default 1 range A B config A int "Enable A" default 2 range B C config B int "Enable B" default 3 range A C The current outcome of this example above, is that all 3 options are set to 2. There is currently no warning or error if a user attempts this. Several attributes are already checked for recursion, including 'depends on', 'select', and 'imply', and we can extend this to 'range' for numeric options (int and hex types). Recursive ranges are currently not used anywhere in the tree, so it is already safe to make this a hard error. Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Julian Braha Link: https://patch.msgid.link/20260816215354.1499059-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 5 +++- .../kconfig/tests/err_recursive_dep/Kconfig | 24 +++++++++++++++++++ .../tests/err_recursive_dep/expected_stderr | 13 ++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 7e81b3676ee944..e82f330fb8ee77 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -1196,6 +1196,7 @@ static struct symbol *sym_check_expr_deps(const struct expr *e) case E_GTH: case E_LEQ: case E_LTH: + case E_RANGE: case E_UNEQUAL: sym = sym_check_deps(e->left.sym); if (sym) @@ -1243,7 +1244,9 @@ static struct symbol *sym_check_sym_deps(struct symbol *sym) sym2 = sym_check_expr_deps(prop->visible.expr); if (sym2) break; - if (prop->type != P_DEFAULT || sym_is_choice(sym)) + if (sym_is_choice(sym)) + continue; + if (prop->type != P_DEFAULT && prop->type != P_RANGE) continue; stack.expr = &prop->expr; sym2 = sym_check_expr_deps(prop->expr); diff --git a/scripts/kconfig/tests/err_recursive_dep/Kconfig b/scripts/kconfig/tests/err_recursive_dep/Kconfig index ebdb3ffd87170a..bd1ff92bcab247 100644 --- a/scripts/kconfig/tests/err_recursive_dep/Kconfig +++ b/scripts/kconfig/tests/err_recursive_dep/Kconfig @@ -61,3 +61,27 @@ config G bool "G" endmenu + +# depends on and range + +config H1 + int + depends on H2 < H3 + +config H2 + int + range H3 H1 + +config H3 + int + range H1 H2 + +# range + +config I1 + int + range I2 I2 + +config I2 + int + range I1 I1 diff --git a/scripts/kconfig/tests/err_recursive_dep/expected_stderr b/scripts/kconfig/tests/err_recursive_dep/expected_stderr index fc2e860af082cb..08f8430876782b 100644 --- a/scripts/kconfig/tests/err_recursive_dep/expected_stderr +++ b/scripts/kconfig/tests/err_recursive_dep/expected_stderr @@ -36,3 +36,16 @@ error: recursive dependency detected! symbol G depends on G For a resolution refer to Documentation/kbuild/kconfig-language.rst subsection "Kconfig recursive dependency limitations" + +error: recursive dependency detected! + symbol H1 depends on H2 + symbol H2 range value contains H3 + symbol H3 range value contains H1 +For a resolution refer to Documentation/kbuild/kconfig-language.rst +subsection "Kconfig recursive dependency limitations" + +error: recursive dependency detected! + symbol I1 range value contains I2 + symbol I2 range value contains I1 +For a resolution refer to Documentation/kbuild/kconfig-language.rst +subsection "Kconfig recursive dependency limitations" From 72c442e5bad35552d902312402121682f05220ac Mon Sep 17 00:00:00 2001 From: Bhaskar Chowdhury Date: Wed, 19 Aug 2026 02:13:51 +0530 Subject: [PATCH 0064/2286] scripts/mkcompile_h: Update LINUX_COMPILE_HOST command substitution syntax The assignment of LINUX_COMPILE_HOST uses the classic backtick style for its command substitution but the rest of the script uses the more common '$()'. Update LINUX_COMPILE_HOST to match for consistency. Signed-off-by: Bhaskar Chowdhury Link: https://patch.msgid.link/20260818204804.2985008-1-unixbhaskar@gmail.com Signed-off-by: Nathan Chancellor --- scripts/mkcompile_h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mkcompile_h b/scripts/mkcompile_h index 2596f78e52ef95..bf0d5669d9bcd5 100755 --- a/scripts/mkcompile_h +++ b/scripts/mkcompile_h @@ -11,7 +11,7 @@ else LINUX_COMPILE_BY=$KBUILD_BUILD_USER fi if test -z "$KBUILD_BUILD_HOST"; then - LINUX_COMPILE_HOST=`uname -n` + LINUX_COMPILE_HOST=$(uname -n) else LINUX_COMPILE_HOST=$KBUILD_BUILD_HOST fi From ba0e6a6f0fe7a8ea2a35603cdf85e39d9239d492 Mon Sep 17 00:00:00 2001 From: Yuntao Wang Date: Sun, 23 Aug 2026 17:49:13 +0800 Subject: [PATCH 0065/2286] setlocalversion: make tag local to try_tag() The tag variable is only used by try_tag(), but is currently declared local to scm_version() and modified by try_tag(). Make tag local to try_tag() instead and remove the unused declaration from scm_version(). No functional changes. Signed-off-by: Yuntao Wang Link: https://patch.msgid.link/20260823094913.421144-1-yuntao.wang@linux.dev Signed-off-by: Nathan Chancellor --- scripts/setlocalversion | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/setlocalversion b/scripts/setlocalversion index 28169d7e143b69..f264c2460ad779 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -33,7 +33,7 @@ if test $# -gt 0 -o ! -d "$srctree"; then fi try_tag() { - tag="$1" + local tag="$1" # Is $tag an annotated tag? if [ "$(git cat-file -t "$tag" 2> /dev/null)" != tag ]; then @@ -59,7 +59,6 @@ scm_version() { local short=false local no_dirty=false - local tag while [ $# -gt 0 ]; do From 7c1bcd3f127f3ba1a35faa15374222b6062c9712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 19 Aug 2026 09:53:38 +0200 Subject: [PATCH 0066/2286] kbuild: use prefix-map for host and user progs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Host and user progs may also use __FILE__. Make sure it shows up canonicalized, as in regular kernel code. Signed-off-by: Thomas Weißschuh Reviewed-by: Nicolas Schier Tested-by: Nicolas Schier Link: https://patch.msgid.link/20260819-kbuild-prefix-map-v1-1-f5168b96fac0@linutronix.de Signed-off-by: Nathan Chancellor --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4ad67b737af7ad..570f0c68aadd89 100644 --- a/Makefile +++ b/Makefile @@ -1199,7 +1199,10 @@ export CFLAGS_GCOV # change __FILE__ to the relative path to the source directory ifdef building_out_of_srctree -KBUILD_CPPFLAGS += -fmacro-prefix-map=$(srcroot)/= +CFLAGS_PREFIX_MAP := -fmacro-prefix-map=$(srcroot)/= +KBUILD_CPPFLAGS += $(CFLAGS_PREFIX_MAP) +KBUILD_USERCFLAGS += $(CFLAGS_PREFIX_MAP) +KBUILD_HOSTCFLAGS += $(CFLAGS_PREFIX_MAP) ifeq ($(call rustc-option-yn, --remap-path-scope=macro),y) KBUILD_RUSTFLAGS += --remap-path-prefix=$(srcroot)/= --remap-path-scope=macro endif From c55749415623a58eba3eb527da023d29b43fbf5d Mon Sep 17 00:00:00 2001 From: Rob Barnes Date: Mon, 31 Aug 2026 18:51:26 -0600 Subject: [PATCH 0067/2286] platform/chrome: cros_ec_proto: Fix deferred response When cros_ec_wait_until_complete() succeeds after an EC_RES_IN_PROGRESS status, it previously returned the 4-byte transfer size of EC_CMD_GET_COMMS_STATUS. For 0-byte payload commands (such as EC_CMD_FLASH_ERASE), userspace received 4 bytes instead of 0, causing response size validation errors. For commands expecting non-zero response payloads, the kernel exited without issuing EC_CMD_RESEND_RESPONSE, leaving response buffers uninitialized. Refactor cros_ec_wait_until_complete() to pass orig_msg, issue EC_CMD_RESEND_RESPONSE into orig_msg->data when insize > 0, and return 0 for 0-byte response commands. Fixes: 2c7589af3c4d ("mfd: cros_ec: add proto v3 skeleton") Signed-off-by: Rob Barnes Link: https://lore.kernel.org/r/20260901005126.3344929-1-robbarnes@google.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_proto.c | 32 +++- drivers/platform/chrome/cros_ec_proto_test.c | 158 ++++++++++++++++++- 2 files changed, 183 insertions(+), 7 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 724d1313f6b213..b13312c1b093fb 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -138,7 +138,8 @@ static int cros_ec_xfer_command(struct cros_ec_device *ec_dev, struct cros_ec_co return ret; } -static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, uint32_t *result) +static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, + struct cros_ec_command *orig_msg) { DEFINE_RAW_FLEX(struct cros_ec_command, msg, data, sizeof(struct ec_response_get_comms_status)); @@ -161,7 +162,7 @@ static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, uint32_t * if (ret < 0) return ret; - *result = msg->result; + orig_msg->result = msg->result; if (msg->result != EC_RES_SUCCESS) return ret; @@ -170,8 +171,31 @@ static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, uint32_t * break; } - if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) + if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) { + u32 orig_cmd, orig_outsize, orig_version; + + /* If no response payload is expected, return 0. */ + if (orig_msg->insize == 0) + return 0; + + /* + * Request the response using EC_CMD_RESEND_RESPONSE. + * Restore the original message fields so it appears + * to be a direct response to the original command. + */ + orig_cmd = orig_msg->command; + orig_outsize = orig_msg->outsize; + orig_version = orig_msg->version; + + orig_msg->command = EC_CMD_RESEND_RESPONSE; + orig_msg->outsize = 0; + orig_msg->version = 0; + ret = cros_ec_xfer_command(ec_dev, orig_msg); + orig_msg->command = orig_cmd; + orig_msg->outsize = orig_outsize; + orig_msg->version = orig_version; return ret; + } } if (i >= EC_COMMAND_RETRIES) @@ -185,7 +209,7 @@ static int cros_ec_send_command(struct cros_ec_device *ec_dev, struct cros_ec_co int ret = cros_ec_xfer_command(ec_dev, msg); if (msg->result == EC_RES_IN_PROGRESS) - ret = cros_ec_wait_until_complete(ec_dev, &msg->result); + ret = cros_ec_wait_until_complete(ec_dev, msg); return ret; } diff --git a/drivers/platform/chrome/cros_ec_proto_test.c b/drivers/platform/chrome/cros_ec_proto_test.c index 3f281996a6863c..cbaaf98d6877e0 100644 --- a/drivers/platform/chrome/cros_ec_proto_test.c +++ b/drivers/platform/chrome/cros_ec_proto_test.c @@ -1744,7 +1744,7 @@ static void cros_ec_proto_test_cmd_xfer_protocol_v2_no_op(struct kunit *test) KUNIT_EXPECT_EQ(test, ret, -EIO); } -static void cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test) +static void cros_ec_proto_test_cmd_xfer_in_progress_payload_0byte(struct kunit *test) { struct cros_ec_proto_test_priv *priv = test->priv; struct cros_ec_device *ec_dev = &priv->ec_dev; @@ -1774,7 +1774,7 @@ static void cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test) } ret = cros_ec_cmd_xfer(ec_dev, &msg); - KUNIT_EXPECT_EQ(test, ret, sizeof(struct ec_response_get_comms_status)); + KUNIT_EXPECT_EQ(test, ret, 0); KUNIT_EXPECT_EQ(test, msg.result, EC_RES_SUCCESS); @@ -1799,6 +1799,156 @@ static void cros_ec_proto_test_cmd_xfer_in_progress_normal(struct kunit *test) KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 2); } +static void cros_ec_proto_test_cmd_xfer_in_progress_payload_4bytes(struct kunit *test) +{ + struct cros_ec_proto_test_priv *priv = test->priv; + struct cros_ec_device *ec_dev = &priv->ec_dev; + struct ec_xfer_mock *mock; + u8 resp_data[4] = {0x11, 0x22, 0x33, 0x44}; + struct { + struct cros_ec_command msg; + u8 data[4]; + } buf; + struct cros_ec_command *msg = &buf.msg; + int ret; + + memset(&buf, 0, sizeof(buf)); + msg->version = 1; + msg->command = 0x1234; + msg->insize = sizeof(buf.data); + + ec_dev->max_response = sizeof(buf.data); + ec_dev->pkt_xfer = cros_kunit_ec_pkt_xfer_mock; + + /* For initial command returning EC_RES_IN_PROGRESS. */ + { + mock = cros_kunit_ec_xfer_mock_addx(test, 0, EC_RES_IN_PROGRESS, 0); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + } + + /* For EC_CMD_GET_COMMS_STATUS status polling. */ + { + struct ec_response_get_comms_status *data; + + mock = cros_kunit_ec_xfer_mock_add(test, sizeof(*data)); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + + data = (struct ec_response_get_comms_status *)mock->o_data; + data->flags = 0; + } + + /* For EC_CMD_RESEND_RESPONSE returning 4 bytes payload. */ + { + mock = cros_kunit_ec_xfer_mock_add(test, sizeof(resp_data)); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + memcpy(mock->o_data, resp_data, sizeof(resp_data)); + } + + ret = cros_ec_cmd_xfer(ec_dev, msg); + KUNIT_EXPECT_EQ(test, ret, 4); + KUNIT_EXPECT_EQ(test, msg->result, EC_RES_SUCCESS); + KUNIT_EXPECT_EQ(test, msg->command, 0x1234); + KUNIT_EXPECT_EQ(test, msg->insize, sizeof(buf.data)); + KUNIT_EXPECT_EQ(test, msg->version, 1); + KUNIT_EXPECT_EQ(test, memcmp(msg->data, resp_data, sizeof(resp_data)), 0); + + /* Verify mock sequence */ + { + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_GET_COMMS_STATUS); + + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_RESEND_RESPONSE); + KUNIT_EXPECT_EQ(test, mock->msg.outsize, 0); + KUNIT_EXPECT_EQ(test, mock->msg.version, 0); + KUNIT_EXPECT_EQ(test, mock->msg.insize, 4); + } + + KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 3); +} + +static void cros_ec_proto_test_cmd_xfer_in_progress_payload_gt4bytes(struct kunit *test) +{ + struct cros_ec_proto_test_priv *priv = test->priv; + struct cros_ec_device *ec_dev = &priv->ec_dev; + struct ec_xfer_mock *mock; + u8 resp_data[16]; + struct { + struct cros_ec_command msg; + u8 data[16]; + } buf; + struct cros_ec_command *msg = &buf.msg; + int ret, i; + + for (i = 0; i < sizeof(resp_data); ++i) + resp_data[i] = (u8)(i + 1); + + memset(&buf, 0, sizeof(buf)); + msg->version = 0; + msg->command = 0x5678; + msg->insize = sizeof(buf.data); + + ec_dev->max_response = sizeof(buf.data); + ec_dev->pkt_xfer = cros_kunit_ec_pkt_xfer_mock; + + /* For initial command returning EC_RES_IN_PROGRESS. */ + { + mock = cros_kunit_ec_xfer_mock_addx(test, 0, EC_RES_IN_PROGRESS, 0); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + } + + /* For EC_CMD_GET_COMMS_STATUS status polling. */ + { + struct ec_response_get_comms_status *data; + + mock = cros_kunit_ec_xfer_mock_add(test, sizeof(*data)); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + + data = (struct ec_response_get_comms_status *)mock->o_data; + data->flags = 0; + } + + /* For EC_CMD_RESEND_RESPONSE returning 16 bytes payload. */ + { + mock = cros_kunit_ec_xfer_mock_add(test, sizeof(resp_data)); + KUNIT_ASSERT_PTR_NE(test, mock, NULL); + memcpy(mock->o_data, resp_data, sizeof(resp_data)); + } + + ret = cros_ec_cmd_xfer(ec_dev, msg); + KUNIT_EXPECT_EQ(test, ret, 16); + KUNIT_EXPECT_EQ(test, msg->result, EC_RES_SUCCESS); + KUNIT_EXPECT_EQ(test, msg->command, 0x5678); + KUNIT_EXPECT_EQ(test, msg->outsize, 0); + KUNIT_EXPECT_EQ(test, msg->insize, sizeof(buf.data)); + KUNIT_EXPECT_EQ(test, msg->version, 0); + KUNIT_EXPECT_EQ(test, memcmp(msg->data, resp_data, sizeof(resp_data)), 0); + + /* Verify mock sequence */ + { + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_GET_COMMS_STATUS); + + mock = cros_kunit_ec_xfer_mock_next(); + KUNIT_EXPECT_PTR_NE(test, mock, NULL); + KUNIT_EXPECT_EQ(test, mock->msg.command, EC_CMD_RESEND_RESPONSE); + KUNIT_EXPECT_EQ(test, mock->msg.outsize, 0); + KUNIT_EXPECT_EQ(test, mock->msg.version, 0); + KUNIT_EXPECT_EQ(test, mock->msg.insize, 16); + } + + KUNIT_EXPECT_EQ(test, cros_kunit_ec_pkt_xfer_mock_called, 3); +} + static void cros_ec_proto_test_cmd_xfer_in_progress_retries_eagain(struct kunit *test) { struct cros_ec_proto_test_priv *priv = test->priv; @@ -2703,7 +2853,9 @@ static struct kunit_case cros_ec_proto_test_cases[] = { KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v3_no_op), KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v2_normal), KUNIT_CASE(cros_ec_proto_test_cmd_xfer_protocol_v2_no_op), - KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_normal), + KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_0byte), + KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_4bytes), + KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_payload_gt4bytes), KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_retries_eagain), KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_retries_status_processing), KUNIT_CASE(cros_ec_proto_test_cmd_xfer_in_progress_xfer_error), From 9a2390959685ac35a58c80c8016120849a2babf6 Mon Sep 17 00:00:00 2001 From: Yunpeng Tian Date: Mon, 31 Aug 2026 17:31:16 +0200 Subject: [PATCH 0068/2286] udf: validate the device specification EA before using it udf_get_extendedattr() bounds an extended attribute only against struct genericFormat, which is 12 bytes. A type-12 device specification is struct deviceSpec, which is 24 bytes plus a flexible impUse array. Thus a corrupted filesystem can contain extended attribute that is accepted by udf_get_extendedattr() but udf_write_inode() or udf_read_inode() may access beyond the end of provided buffer. Provide a udf_device_spec_valid() check to validate correctness of extended attribute that is a deviceSpec. While here, check the result of udf_add_extendedattr() before dereferencing it as it can return NULL in case of error. [JK: Removed duplicated validity checks] Reported-by: Yunpeng Tian Reported-by: Gongming Wang Reported-by: Mingda Zhang Reported-by: Qinrun Dai Cc: stable@vger.kernel.org Signed-off-by: Yunpeng Tian Signed-off-by: Jan Kara --- fs/udf/inode.c | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/fs/udf/inode.c b/fs/udf/inode.c index e45e546a739a90..baf9845ac06b06 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -1336,6 +1336,26 @@ int udf_setsize(struct inode *inode, loff_t newsize) return err; } +/* + * Verify validity of struct deviceSpec on disk. udf_get_extendedattr() has + * already verified the generic header and made sure attribute fits in the + * inode so we just have to make sure attribute space is large enough for + * deviceSpec struct and required impUse information. + */ +static bool udf_device_spec_valid(struct deviceSpec *dsea) +{ + u32 attr_length, imp_use_length; + + attr_length = le32_to_cpu(dsea->attrLength); + imp_use_length = le32_to_cpu(dsea->impUseLength); + if (attr_length < sizeof(struct deviceSpec) || + imp_use_length < sizeof(struct regid) || + imp_use_length > attr_length - sizeof(struct deviceSpec)) + return false; + + return true; +} + /* * Maximum length of linked list formed by ICB hierarchy. The chosen number is * arbitrary - just that we hopefully don't limit any real use of rewritten @@ -1654,13 +1674,12 @@ static int udf_read_inode(struct inode *inode, bool hidden_inode) if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode)) { struct deviceSpec *dsea = (struct deviceSpec *)udf_get_extendedattr(inode, 12, 1); - if (dsea) { - init_special_inode(inode, inode->i_mode, + + if (!dsea || !udf_device_spec_valid(dsea)) + goto out; + init_special_inode(inode, inode->i_mode, MKDEV(le32_to_cpu(dsea->majorDeviceIdent), le32_to_cpu(dsea->minorDeviceIdent))); - /* Developer ID ??? */ - } else - goto out; } ret = 0; out: @@ -1757,6 +1776,7 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) struct udf_sb_info *sbi = UDF_SB(inode->i_sb); unsigned char blocksize_bits = inode->i_sb->s_blocksize_bits; struct udf_inode_info *iinfo = UDF_I(inode); + int err; bh = sb_getblk(inode->i_sb, udf_get_lb_pblock(inode->i_sb, &iinfo->i_location, 0)); @@ -1821,6 +1841,10 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) udf_add_extendedattr(inode, sizeof(struct deviceSpec) + sizeof(struct regid), 12, 0x3); + if (!dsea) { + err = -ENOSPC; + goto out_unlock; + } dsea->attrType = cpu_to_le32(12); dsea->attrSubtype = 1; dsea->attrLength = cpu_to_le32( @@ -1828,6 +1852,10 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) sizeof(struct regid)); dsea->impUseLength = cpu_to_le32(sizeof(struct regid)); } + if (!udf_device_spec_valid(dsea)) { + err = -EFSCORRUPTED; + goto out_unlock; + } eid = (struct regid *)dsea->impUse; memset(eid, 0, sizeof(*eid)); strcpy(eid->ident, UDF_ID_DEVELOPER); @@ -1962,6 +1990,11 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) set_inode_metadata_writeback(inode); return 0; + +out_unlock: + unlock_buffer(bh); + brelse(bh); + return err; } struct inode *__udf_iget(struct super_block *sb, struct kernel_lb_addr *ino, From 127b0d87649f946198b80f05ae62123dd87bc6f4 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Tue, 1 Sep 2026 10:55:38 +0200 Subject: [PATCH 0069/2286] udf: Avoid confusion of missing and corrupted extended attributes udf_get_extendedattr() returns NULL either when extended attribute was not found or when some corrupted attribute was found. Similarly udf_add_extendedattr() returns NULL when extended attributes are corrupted or when there's no space to insert the extended attribute. Make both functions return proper error codes to discern these cases and handle them in the callers. Signed-off-by: Jan Kara --- fs/udf/inode.c | 23 ++++++++++++++++------- fs/udf/misc.c | 15 ++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/fs/udf/inode.c b/fs/udf/inode.c index baf9845ac06b06..8babc2fbce9a10 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -1675,8 +1675,15 @@ static int udf_read_inode(struct inode *inode, bool hidden_inode) struct deviceSpec *dsea = (struct deviceSpec *)udf_get_extendedattr(inode, 12, 1); - if (!dsea || !udf_device_spec_valid(dsea)) + if (IS_ERR(dsea)) { + ret = PTR_ERR(dsea); goto out; + } + /* Device inodes must have a device spec attribute */ + if (!dsea || !udf_device_spec_valid(dsea)) { + ret = -EFSCORRUPTED; + goto out; + } init_special_inode(inode, inode->i_mode, MKDEV(le32_to_cpu(dsea->majorDeviceIdent), le32_to_cpu(dsea->minorDeviceIdent))); @@ -1836,13 +1843,19 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) struct regid *eid; struct deviceSpec *dsea = (struct deviceSpec *)udf_get_extendedattr(inode, 12, 1); + + /* Validity of extended attrs was checked on load */ + if (WARN_ON_ONCE(IS_ERR(dsea))) { + err = PTR_ERR(dsea); + goto out_unlock; + } if (!dsea) { dsea = (struct deviceSpec *) udf_add_extendedattr(inode, sizeof(struct deviceSpec) + sizeof(struct regid), 12, 0x3); - if (!dsea) { - err = -ENOSPC; + if (IS_ERR(dsea)) { + err = PTR_ERR(dsea); goto out_unlock; } dsea->attrType = cpu_to_le32(12); @@ -1852,10 +1865,6 @@ int udf_write_inode(struct inode *inode, struct writeback_control *wbc) sizeof(struct regid)); dsea->impUseLength = cpu_to_le32(sizeof(struct regid)); } - if (!udf_device_spec_valid(dsea)) { - err = -EFSCORRUPTED; - goto out_unlock; - } eid = (struct regid *)dsea->impUse; memset(eid, 0, sizeof(*eid)); strcpy(eid->ident, UDF_ID_DEVELOPER); diff --git a/fs/udf/misc.c b/fs/udf/misc.c index 6928e378fbbdcb..a2084dfbfbd66f 100644 --- a/fs/udf/misc.c +++ b/fs/udf/misc.c @@ -58,7 +58,7 @@ struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size, cpu_to_le16(TAG_IDENT_EAHD) || le32_to_cpu(eahd->descTag.tagLocation) != iinfo->i_location.logicalBlockNum) - return NULL; + return ERR_PTR(-EFSCORRUPTED); } else { struct udf_sb_info *sbi = UDF_SB(inode->i_sb); @@ -122,7 +122,7 @@ struct genericFormat *udf_add_extendedattr(struct inode *inode, uint32_t size, return (struct genericFormat *)&ea[offset]; } - return NULL; + return ERR_PTR(-ENOSPC); } struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type, @@ -144,7 +144,7 @@ struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type, cpu_to_le16(TAG_IDENT_EAHD) || le32_to_cpu(eahd->descTag.tagLocation) != iinfo->i_location.logicalBlockNum) - return NULL; + return ERR_PTR(-EFSCORRUPTED); if (type < 2048) offset = sizeof(struct extendedAttrHeaderDesc); @@ -153,16 +153,17 @@ struct genericFormat *udf_get_extendedattr(struct inode *inode, uint32_t type, else offset = le32_to_cpu(eahd->appAttrLocation); - while (offset + sizeof(*gaf) < iinfo->i_lenEAttr) { + while (offset < + iinfo->i_lenEAttr - sizeof(struct genericFormat)) { uint32_t attrLength; gaf = (struct genericFormat *)&ea[offset]; attrLength = le32_to_cpu(gaf->attrLength); /* Detect undersized elements and buffer overflows */ - if ((attrLength < sizeof(*gaf)) || - (attrLength > (iinfo->i_lenEAttr - offset))) - break; + if (attrLength < sizeof(struct genericFormat) || + attrLength > iinfo->i_lenEAttr - offset) + return ERR_PTR(-EFSCORRUPTED); if (le32_to_cpu(gaf->attrType) == type && gaf->attrSubtype == subtype) From 730e01d932496f7bd536fc185d3aab6646dea0f4 Mon Sep 17 00:00:00 2001 From: Ramin Moussavi Date: Fri, 21 Aug 2026 17:18:05 +0200 Subject: [PATCH 0070/2286] microblaze: wire up sigaltstack sigaltstack is wired to sys_ni_syscall - microblaze is the only architecture without it - although the microblaze signal delivery code fully supports the alternate signal stack: get_sigframe() picks the stack via sigsp(), setup_rt_frame() saves it with __save_altstack() and sys_rt_sigreturn() calls restore_altstack(). Wire it up to sys_sigaltstack. Tested on qemu petalogix-s3adsp1800 (microblazeel) with the uClibc-ng test suite: the five sigaltstack-dependent NPTL tests (tst-cancel20/21, tst-cancelx20/21, tst-signal6) pass; before this change sigaltstack() returned ENOSYS. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ramin Moussavi Tested-by: Waldemar Brodkorb Link: https://patch.msgid.link/20260821151809.1233057-2-ramin.moussavi@yacoub.de Signed-off-by: Michal Simek --- arch/microblaze/kernel/syscalls/syscall.tbl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl index c55a5c96f49b82..76d7cbe2a133c4 100644 --- a/arch/microblaze/kernel/syscalls/syscall.tbl +++ b/arch/microblaze/kernel/syscalls/syscall.tbl @@ -193,7 +193,7 @@ 183 common getcwd sys_getcwd 184 common capget sys_capget 185 common capset sys_capset -186 common sigaltstack sys_ni_syscall +186 common sigaltstack sys_sigaltstack 187 common sendfile sys_sendfile 188 common getpmsg sys_ni_syscall 189 common putpmsg sys_ni_syscall From c5947d28c20955de897eeb98430f96db0494f7d7 Mon Sep 17 00:00:00 2001 From: Ramin Moussavi Date: Fri, 21 Aug 2026 17:18:06 +0200 Subject: [PATCH 0071/2286] microblaze: reserve the ABI argument-home area in the signal frame The MicroBlaze procedure call standard reserves [r1+0] for the return address and lets a callee store its incoming register arguments r5..r10 into the caller-provided home slots at [r1+4]..[r1+24]. When the kernel enters a signal handler it sets r1 to point at struct rt_sigframe, whose leading member is the siginfo prepared for the handler. A handler that homes its arguments - which an unoptimised (-O0) build always does - therefore overwrites the first 24 bytes of that siginfo, so an SA_SIGINFO handler reads corrupted values. The stores stay within siginfo (128 bytes) and do not reach the ucontext behind it. Reserve the home area by making a seven-word gap -- the return-address slot plus the six argument slots, 28 bytes -- the first member of struct rt_sigframe, so the handler's argument stores land in scratch space instead of clobbering siginfo. Tested on qemu-system-microblazeel (petalogix-s3adsp1800) with the uClibc-ng NPTL test suite: tst-timer4, tst-timer5, tst-mqueue5 and tst-signal6 pass; before this change they failed because the handler clobbered the signal frame. Fixes: 2148daa9c45f ("microblaze_v8: Signal support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Ramin Moussavi Tested-by: Waldemar Brodkorb Link: https://patch.msgid.link/20260821151809.1233057-3-ramin.moussavi@yacoub.de Signed-off-by: Michal Simek --- arch/microblaze/kernel/signal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/microblaze/kernel/signal.c b/arch/microblaze/kernel/signal.c index c78a0ff480660d..4956014a993797 100644 --- a/arch/microblaze/kernel/signal.c +++ b/arch/microblaze/kernel/signal.c @@ -49,6 +49,14 @@ struct sigframe { }; struct rt_sigframe { + /* + * Home area for the handler's register arguments: the MicroBlaze + * ABI reserves [r1+0] for the return address and lets the callee + * store r5..r10 at [r1+4]..[r1+24], and r1 points at this frame + * when the handler is entered. Without the gap those stores + * corrupt frame->info. + */ + unsigned long abi_gap[7]; struct siginfo info; struct ucontext uc; unsigned long tramp[2]; /* signal trampoline */ From da6829138a39fa3b9ec318ca54ff89af9d94d350 Mon Sep 17 00:00:00 2001 From: Ramin Moussavi Date: Fri, 21 Aug 2026 17:18:07 +0200 Subject: [PATCH 0072/2286] microblaze: don't clobber r3/r4 restored by rt_sigreturn ret_from_trap begins by storing the system call return values r3 and r4 back into the saved user pt_regs. That is right for an ordinary system call, but sys_rt_sigreturn() returns through the same path and has to be transparent: restore_sigcontext() has just filled pt_regs from the signal frame, and every register must reach userspace exactly as saved. The two stores overwrite the restored r3/r4 with whatever the C function left in those registers. Only r4 is actually lost, and that follows from how sigreturn is written everywhere rather than from chance. Architectures have sys_rt_sigreturn() return the restored return-value register precisely so that this writeback stores the restored value -- arm returns regs->ARM_r0, riscv and csky regs->a0, arc regs->r0 -- and microblaze does the same through *rval_p = regs->r3 in restore_sigcontext(). But the entry macros treat r3 and r4 as a pair, so ret_from_trap writes back two registers while a C function has only one return value; nothing carries the restored r4. Commit 791d0a169b91 ("microblaze: Fix sys_rt_sigreturn_wrapper") introduced this in v2.6.37. The old wrapper saved r3/r4 before the call and reloaded them from pt_regs afterwards; switching brlid to brid removed the only point where they were reloaded, and the stores have been overwriting them ever since. Fix it the way the sibling paths already behave: ret_from_irq and ret_from_exc restore the full register set and do not perform these stores at all. Label the instruction after the stores ret_from_trap_no_rval and enter there, biasing r15 by -8 so that the ABI return "rtsd r15, 8" lands on it; naming the entry rather than computing ret_from_trap + 8 keeps it correct if the number of stores ever changes. Restoring the old save/reload would work too, but costs four memory accesses and a branch to undo damage that is better not done. Any value the compiler keeps in r4 across a signal is lost. The tightest windows are the lwx/swx compare-and-swap retry loops gcc emits for atomics: in uClibc-ng's libc.so alone, 50 of 364 such loops hold the address in r4. This is not specific to one libc -- musl passes the address as "r"(p), and glibc has no microblaze atomic-machine.h and so uses gcc's __atomic builtins, which expand the same way. Triggering it needs threads plus a signal storm, which is why it survived 15 years. Reproduced on qemu-system-microblazeel (petalogix-s3adsp1800) by running the uClibc-ng NPTL test tst-eintr1 40 times against an unchanged userspace: on v7.2 built with gcc 16.2.0, 23 of 40 iterations died with SIGSEGV without this patch and 0 of 40 with it; on v7.0 built with gcc 12.5.0 the same comparison gave 6 of 40 against 0 of 40. The register dump of a failing iteration shows the signature: a zero r4 and a fault at address 0, while r3 came back intact. Fixes: 791d0a169b91 ("microblaze: Fix sys_rt_sigreturn_wrapper") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Ramin Moussavi Tested-by: Waldemar Brodkorb Link: https://patch.msgid.link/20260821151809.1233057-4-ramin.moussavi@yacoub.de Signed-off-by: Michal Simek --- arch/microblaze/kernel/entry.S | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index ad44341a5dbc72..efa4ab056fb5ff 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -434,7 +434,13 @@ C_ENTRY(_user_exception): C_ENTRY(ret_from_trap): swi r3, r1, PT_R3 swi r4, r1, PT_R4 - +/* + * Entry point for returns that must not store r3/r4 back into pt_regs, + * i.e. rt_sigreturn, which has already restored them from the signal + * context. Reached as "rtsd r15, 8" with r15 set to this label minus 8, + * so it stays correct if the number of stores above ever changes. + */ +ret_from_trap_no_rval: lwi r11, r1, PT_MODE; /* See if returning to kernel mode, if so, skip resched &c. */ bnei r11, 2f; @@ -518,6 +524,15 @@ C_ENTRY(ret_from_kernel_thread): C_ENTRY(sys_rt_sigreturn_wrapper): addik r30, r0, 0 /* no restarts */ + /* + * rt_sigreturn restores the full register set from the signal + * context, so it must skip the r3/r4 syscall-return stores at the + * head of ret_from_trap which would otherwise overwrite the + * just-restored r3/r4. Every C function returns with "rtsd r15, 8" + * -- the ABI return, where the 8 skips the caller's branch and its + * delay slot -- so bias r15 by -8 to land on ret_from_trap_no_rval. + */ + addik r15, r0, ret_from_trap_no_rval - 8 brid sys_rt_sigreturn /* Do real work */ addik r5, r1, 0; /* add user context as 1st arg */ From c35d40a3efd2e575ef51a761e983f2bf7113be6c Mon Sep 17 00:00:00 2001 From: Ramin Moussavi Date: Fri, 21 Aug 2026 17:18:08 +0200 Subject: [PATCH 0073/2286] microblaze: restore the ABI argument home area below pt_regs (PTO) The MicroBlaze ABI has the caller reserve stack space for the arguments it passes in registers: REG_PARM_STACK_SPACE is 24 and OUTGOING_REG_PARM_STACK_SPACE is 1 in the gcc backend, so a callee may write to [caller_sp + 4, caller_sp + 28). The kernel calls C functions from entry.S with r1 pointing at pt_regs, handing the callee license to spill its incoming arguments over the saved registers -- the syscall dispatch is the worst case, where the first argument slot is PT_R1, the saved user stack pointer. This was latent until GCC 15: since 3b9b8d6cfdf5 ("ira: Scale save/restore costs of callee save registers with block frequency") the allocator prefers spilling incoming arguments over copying them into callee-saved registers, and a kernel built with gcc >= 15 (without the TARGET_CALLEE_SAVE_COST workaround some distributions carry) corrupts PT_R1 on the first syscall: init takes SIGSEGV and the kernel panics. The kernel had exactly this reservation until 2011: commit 6e83557c38b4 ("microblaze: Remove r0_ram pointer and PTO alignment") removed STATE_SAVE_ARG_SPACE and with it the PTO offset, as part of cleaning up what was thought to be copied-from-v850 leftovers. Restore it: the frame is STATE_SAVE_SIZE = PT_SIZE + PTO, r1 stays at the frame base through every asm-to-C call, and the saved registers are reached at r1 + PTO + PT_*. PTO is 28 rather than the historic 24, which was one word short: FIRST_PARM_OFFSET is 4 and REG_PARM_STACK_SPACE is 24, so the area spans [sp+4, sp+28) and needs 28 bytes. With 24 the last argument slot overlapped pt_regs' r0 -- harmless only because r0 is the constant-zero register. STACK_BOUNDARY is 32 bits, so 28 needs no further rounding. Two places deliberately keep their mainline addressing. hw_exception_handler.S needs no offset change: its real-mode handler saves into the standalone pt_pool_space buffer rather than a stack frame, and _unaligned_data_exception works through a pointer to pt_regs in r7, where plain PT_* offsets are already right. And the user-SP reload after popping the frame keeps PT_R1 - PT_SIZE because PTO cancels there: (PTO + PT_R1) - (PT_SIZE + PTO) = PT_R1 - PT_SIZE. Both are exactly as the pre-2011 code had them. The instruction count is unchanged -- the same instructions with different immediates, no per-call reservation and no trampolines. The cost is 28 bytes more kernel stack per saved frame. Fixes: 6e83557c38b4 ("microblaze: Remove r0_ram pointer and PTO alignment") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Ramin Moussavi Tested-by: Waldemar Brodkorb Link: https://patch.msgid.link/20260821151809.1233057-5-ramin.moussavi@yacoub.de Signed-off-by: Michal Simek --- arch/microblaze/include/asm/entry.h | 13 + arch/microblaze/include/asm/processor.h | 2 +- arch/microblaze/kernel/entry.S | 329 +++++++++--------- arch/microblaze/kernel/hw_exception_handler.S | 5 + arch/microblaze/kernel/process.c | 5 +- 5 files changed, 189 insertions(+), 165 deletions(-) diff --git a/arch/microblaze/include/asm/entry.h b/arch/microblaze/include/asm/entry.h index 9efadf12397ca8..05acf4d7bc4b18 100644 --- a/arch/microblaze/include/asm/entry.h +++ b/arch/microblaze/include/asm/entry.h @@ -21,6 +21,19 @@ #define PER_CPU(var) var +/* + * The MicroBlaze ABI has the caller reserve an argument home area: + * REG_PARM_STACK_SPACE is 24 and OUTGOING_REG_PARM_STACK_SPACE is 1, so a + * callee may write to [caller_sp + 4, caller_sp + 28). The kernel calls C + * with r1 at the frame base, so reserve that area below pt_regs and reach + * the saved registers through PTO. This restores what 6e83557c38b4 + * ("microblaze: Remove r0_ram pointer and PTO alignment") removed; the old + * value of 24 was one word short and left the last argument slot + * overlapping pt_regs' r0. + */ +#define PTO 28 +#define STATE_SAVE_SIZE (PT_SIZE + PTO) + # ifndef __ASSEMBLER__ DECLARE_PER_CPU(unsigned int, KSP); /* Saved kernel stack pointer */ DECLARE_PER_CPU(unsigned int, KM); /* Kernel/user mode */ diff --git a/arch/microblaze/include/asm/processor.h b/arch/microblaze/include/asm/processor.h index d59bdfffca7cc0..595c24db272838 100644 --- a/arch/microblaze/include/asm/processor.h +++ b/arch/microblaze/include/asm/processor.h @@ -73,7 +73,7 @@ unsigned long __get_wchan(struct task_struct *p); # define task_regs(task) ((struct pt_regs *)task_tos(task) - 1) # define task_pt_regs_plus_args(tsk) \ - ((void *)task_pt_regs(tsk)) + (((void *)task_pt_regs(tsk)) - PTO) # define task_sp(task) (task_regs(task)->r1) # define task_pc(task) (task_regs(task)->pc) diff --git a/arch/microblaze/kernel/entry.S b/arch/microblaze/kernel/entry.S index efa4ab056fb5ff..ebc8bcb98c5220 100644 --- a/arch/microblaze/kernel/entry.S +++ b/arch/microblaze/kernel/entry.S @@ -177,78 +177,78 @@ syscall_debug_table: 1: #define SAVE_REGS \ - swi r2, r1, PT_R2; /* Save SDA */ \ - swi r3, r1, PT_R3; \ - swi r4, r1, PT_R4; \ - swi r5, r1, PT_R5; \ - swi r6, r1, PT_R6; \ - swi r7, r1, PT_R7; \ - swi r8, r1, PT_R8; \ - swi r9, r1, PT_R9; \ - swi r10, r1, PT_R10; \ - swi r11, r1, PT_R11; /* save clobbered regs after rval */\ - swi r12, r1, PT_R12; \ - swi r13, r1, PT_R13; /* Save SDA2 */ \ - swi r14, r1, PT_PC; /* PC, before IRQ/trap */ \ - swi r15, r1, PT_R15; /* Save LP */ \ - swi r16, r1, PT_R16; \ - swi r17, r1, PT_R17; \ - swi r18, r1, PT_R18; /* Save asm scratch reg */ \ - swi r19, r1, PT_R19; \ - swi r20, r1, PT_R20; \ - swi r21, r1, PT_R21; \ - swi r22, r1, PT_R22; \ - swi r23, r1, PT_R23; \ - swi r24, r1, PT_R24; \ - swi r25, r1, PT_R25; \ - swi r26, r1, PT_R26; \ - swi r27, r1, PT_R27; \ - swi r28, r1, PT_R28; \ - swi r29, r1, PT_R29; \ - swi r30, r1, PT_R30; \ - swi r31, r1, PT_R31; /* Save current task reg */ \ + swi r2, r1, PTO+PT_R2; /* Save SDA */ \ + swi r3, r1, PTO+PT_R3; \ + swi r4, r1, PTO+PT_R4; \ + swi r5, r1, PTO+PT_R5; \ + swi r6, r1, PTO+PT_R6; \ + swi r7, r1, PTO+PT_R7; \ + swi r8, r1, PTO+PT_R8; \ + swi r9, r1, PTO+PT_R9; \ + swi r10, r1, PTO+PT_R10; \ + swi r11, r1, PTO+PT_R11; /* save clobbered regs after rval */\ + swi r12, r1, PTO+PT_R12; \ + swi r13, r1, PTO+PT_R13; /* Save SDA2 */ \ + swi r14, r1, PTO+PT_PC; /* PC, before IRQ/trap */ \ + swi r15, r1, PTO+PT_R15; /* Save LP */ \ + swi r16, r1, PTO+PT_R16; \ + swi r17, r1, PTO+PT_R17; \ + swi r18, r1, PTO+PT_R18; /* Save asm scratch reg */ \ + swi r19, r1, PTO+PT_R19; \ + swi r20, r1, PTO+PT_R20; \ + swi r21, r1, PTO+PT_R21; \ + swi r22, r1, PTO+PT_R22; \ + swi r23, r1, PTO+PT_R23; \ + swi r24, r1, PTO+PT_R24; \ + swi r25, r1, PTO+PT_R25; \ + swi r26, r1, PTO+PT_R26; \ + swi r27, r1, PTO+PT_R27; \ + swi r28, r1, PTO+PT_R28; \ + swi r29, r1, PTO+PT_R29; \ + swi r30, r1, PTO+PT_R30; \ + swi r31, r1, PTO+PT_R31; /* Save current task reg */ \ mfs r11, rmsr; /* save MSR */ \ - swi r11, r1, PT_MSR; + swi r11, r1, PTO+PT_MSR; #define RESTORE_REGS_GP \ - lwi r2, r1, PT_R2; /* restore SDA */ \ - lwi r3, r1, PT_R3; \ - lwi r4, r1, PT_R4; \ - lwi r5, r1, PT_R5; \ - lwi r6, r1, PT_R6; \ - lwi r7, r1, PT_R7; \ - lwi r8, r1, PT_R8; \ - lwi r9, r1, PT_R9; \ - lwi r10, r1, PT_R10; \ - lwi r11, r1, PT_R11; /* restore clobbered regs after rval */\ - lwi r12, r1, PT_R12; \ - lwi r13, r1, PT_R13; /* restore SDA2 */ \ - lwi r14, r1, PT_PC; /* RESTORE_LINK PC, before IRQ/trap */\ - lwi r15, r1, PT_R15; /* restore LP */ \ - lwi r16, r1, PT_R16; \ - lwi r17, r1, PT_R17; \ - lwi r18, r1, PT_R18; /* restore asm scratch reg */ \ - lwi r19, r1, PT_R19; \ - lwi r20, r1, PT_R20; \ - lwi r21, r1, PT_R21; \ - lwi r22, r1, PT_R22; \ - lwi r23, r1, PT_R23; \ - lwi r24, r1, PT_R24; \ - lwi r25, r1, PT_R25; \ - lwi r26, r1, PT_R26; \ - lwi r27, r1, PT_R27; \ - lwi r28, r1, PT_R28; \ - lwi r29, r1, PT_R29; \ - lwi r30, r1, PT_R30; \ - lwi r31, r1, PT_R31; /* Restore cur task reg */ + lwi r2, r1, PTO+PT_R2; /* restore SDA */ \ + lwi r3, r1, PTO+PT_R3; \ + lwi r4, r1, PTO+PT_R4; \ + lwi r5, r1, PTO+PT_R5; \ + lwi r6, r1, PTO+PT_R6; \ + lwi r7, r1, PTO+PT_R7; \ + lwi r8, r1, PTO+PT_R8; \ + lwi r9, r1, PTO+PT_R9; \ + lwi r10, r1, PTO+PT_R10; \ + lwi r11, r1, PTO+PT_R11; /* restore clobbered regs after rval */\ + lwi r12, r1, PTO+PT_R12; \ + lwi r13, r1, PTO+PT_R13; /* restore SDA2 */ \ + lwi r14, r1, PTO+PT_PC; /* RESTORE_LINK PC, before IRQ/trap */\ + lwi r15, r1, PTO+PT_R15; /* restore LP */ \ + lwi r16, r1, PTO+PT_R16; \ + lwi r17, r1, PTO+PT_R17; \ + lwi r18, r1, PTO+PT_R18; /* restore asm scratch reg */ \ + lwi r19, r1, PTO+PT_R19; \ + lwi r20, r1, PTO+PT_R20; \ + lwi r21, r1, PTO+PT_R21; \ + lwi r22, r1, PTO+PT_R22; \ + lwi r23, r1, PTO+PT_R23; \ + lwi r24, r1, PTO+PT_R24; \ + lwi r25, r1, PTO+PT_R25; \ + lwi r26, r1, PTO+PT_R26; \ + lwi r27, r1, PTO+PT_R27; \ + lwi r28, r1, PTO+PT_R28; \ + lwi r29, r1, PTO+PT_R29; \ + lwi r30, r1, PTO+PT_R30; \ + lwi r31, r1, PTO+PT_R31; /* Restore cur task reg */ #define RESTORE_REGS \ - lwi r11, r1, PT_MSR; \ + lwi r11, r1, PTO+PT_MSR; \ mts rmsr , r11; \ RESTORE_REGS_GP #define RESTORE_REGS_RTBD \ - lwi r11, r1, PT_MSR; \ + lwi r11, r1, PTO+PT_MSR; \ andni r11, r11, MSR_EIP; /* clear EIP */ \ ori r11, r11, MSR_EE | MSR_BIP; /* set EE and BIP */ \ mts rmsr , r11; \ @@ -265,11 +265,11 @@ syscall_debug_table: lwi r1, r0, TOPHYS(PER_CPU(ENTRY_SP)); \ /* FIXME: I can add these two lines to one */ \ /* tophys(r1,r1); */ \ - /* addik r1, r1, -PT_SIZE; */ \ - addik r1, r1, CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - PT_SIZE; \ + /* addik r1, r1, -STATE_SAVE_SIZE; */ \ + addik r1, r1, CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - STATE_SAVE_SIZE; \ SAVE_REGS \ brid 2f; \ - swi r1, r1, PT_MODE; \ + swi r1, r1, PTO+PT_MODE; \ 1: /* User-mode state save. */ \ lwi r1, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* get saved current */\ tophys(r1,r1); \ @@ -277,12 +277,13 @@ syscall_debug_table: /* MS these three instructions can be added to one */ \ /* addik r1, r1, THREAD_SIZE; */ \ /* tophys(r1,r1); */ \ - /* addik r1, r1, -PT_SIZE; */ \ - addik r1, r1, THREAD_SIZE + CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - PT_SIZE; \ + /* addik r1, r1, -STATE_SAVE_SIZE; */ \ + addik r1, r1, THREAD_SIZE + CONFIG_KERNEL_BASE_ADDR \ + - CONFIG_KERNEL_START - STATE_SAVE_SIZE; \ SAVE_REGS \ lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); \ - swi r11, r1, PT_R1; /* Store user SP. */ \ - swi r0, r1, PT_MODE; /* Was in user-mode. */ \ + swi r11, r1, PTO+PT_R1; /* Store user SP. */ \ + swi r0, r1, PTO+PT_MODE; /* Was in user-mode. */ \ /* MS: I am clearing UMS even in case when I come from kernel space */ \ clear_ums; \ 2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); @@ -292,7 +293,7 @@ syscall_debug_table: .extern cpuinfo C_ENTRY(mb_flush_dcache): - addik r1, r1, -PT_SIZE + addik r1, r1, -STATE_SAVE_SIZE SAVE_REGS addik r3, r0, cpuinfo @@ -305,12 +306,12 @@ C_ENTRY(mb_flush_dcache): addk r9, r9, r8 RESTORE_REGS - addik r1, r1, PT_SIZE + addik r1, r1, STATE_SAVE_SIZE rtsd r15, 8 nop C_ENTRY(mb_invalidate_icache): - addik r1, r1, -PT_SIZE + addik r1, r1, -STATE_SAVE_SIZE SAVE_REGS addik r3, r0, cpuinfo @@ -323,7 +324,7 @@ C_ENTRY(mb_invalidate_icache): addk r9, r9, r8 RESTORE_REGS - addik r1, r1, PT_SIZE + addik r1, r1, STATE_SAVE_SIZE rtsd r15, 8 nop @@ -350,18 +351,18 @@ C_ENTRY(_user_exception): addik r1, r1, THREAD_SIZE; tophys(r1,r1); - addik r1, r1, -PT_SIZE; /* Make room on the stack. */ + addik r1, r1, -STATE_SAVE_SIZE; /* Make room on the stack. */ SAVE_REGS - swi r0, r1, PT_R3 - swi r0, r1, PT_R4 + swi r0, r1, PTO+PT_R3 + swi r0, r1, PTO+PT_R4 - swi r0, r1, PT_MODE; /* Was in user-mode. */ + swi r0, r1, PTO+PT_MODE; /* Was in user-mode. */ lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); - swi r11, r1, PT_R1; /* Store user SP. */ + swi r11, r1, PTO+PT_R1; /* Store user SP. */ clear_ums; 2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); /* Save away the syscall number. */ - swi r12, r1, PT_R0; + swi r12, r1, PTO+PT_R0; tovirt(r1,r1) /* where the trap should return need -8 to adjust for rtsd r15, 8*/ @@ -380,18 +381,18 @@ C_ENTRY(_user_exception): beqi r11, 4f addik r3, r0, -ENOSYS - swi r3, r1, PT_R3 + swi r3, r1, PTO+PT_R3 brlid r15, do_syscall_trace_enter - addik r5, r1, PT_R0 + addik r5, r1, PTO+PT_R0 # do_syscall_trace_enter returns the new syscall nr. addk r12, r0, r3 - lwi r5, r1, PT_R5; - lwi r6, r1, PT_R6; - lwi r7, r1, PT_R7; - lwi r8, r1, PT_R8; - lwi r9, r1, PT_R9; - lwi r10, r1, PT_R10; + lwi r5, r1, PTO+PT_R5; + lwi r6, r1, PTO+PT_R6; + lwi r7, r1, PTO+PT_R7; + lwi r8, r1, PTO+PT_R8; + lwi r9, r1, PTO+PT_R9; + lwi r10, r1, PTO+PT_R10; 4: /* Jump to the appropriate function for the system call number in r12 * (r12 is not preserved), or return an error if r12 is not valid. @@ -432,8 +433,8 @@ C_ENTRY(_user_exception): /* Entry point used to return from a syscall/trap */ /* We re-enable BIP bit before state restore */ C_ENTRY(ret_from_trap): - swi r3, r1, PT_R3 - swi r4, r1, PT_R4 + swi r3, r1, PTO+PT_R3 + swi r4, r1, PTO+PT_R4 /* * Entry point for returns that must not store r3/r4 back into pt_regs, * i.e. rt_sigreturn, which has already restored them from the signal @@ -441,7 +442,7 @@ C_ENTRY(ret_from_trap): * so it stays correct if the number of stores above ever changes. */ ret_from_trap_no_rval: - lwi r11, r1, PT_MODE; + lwi r11, r1, PTO+PT_MODE; /* See if returning to kernel mode, if so, skip resched &c. */ bnei r11, 2f; /* We're returning to user mode, so check for various conditions that @@ -453,7 +454,7 @@ ret_from_trap_no_rval: beqi r11, 1f brlid r15, do_syscall_trace_leave - addik r5, r1, PT_R0 + addik r5, r1, PTO+PT_R0 1: /* We're returning to user mode, so check for various conditions that * trigger rescheduling. */ @@ -472,7 +473,7 @@ ret_from_trap_no_rval: andi r11, r19, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME; beqi r11, 4f; /* Signals to handle, handle them */ - addik r5, r1, 0; /* Arg 1: struct pt_regs *regs */ + addik r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ bralid r15, do_notify_resume; /* Handle any signals */ add r6, r30, r0; /* Arg 2: int in_syscall */ add r30, r0, r0 /* no more restarts */ @@ -484,7 +485,11 @@ ret_from_trap_no_rval: VM_OFF; tophys(r1,r1); RESTORE_REGS_RTBD; - addik r1, r1, PT_SIZE /* Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ + /* + * Deliberately PT_SIZE, not STATE_SAVE_SIZE: r1 is back at the stack + * top, so PTO cancels and the saved SP sits at PT_R1 - PT_SIZE. + */ lwi r1, r1, PT_R1 - PT_SIZE;/* Restore user stack pointer. */ bri 6f; @@ -493,7 +498,7 @@ ret_from_trap_no_rval: VM_OFF; tophys(r1,r1); RESTORE_REGS_RTBD; - addik r1, r1, PT_SIZE /* Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ tovirt(r1,r1); 6: TRAP_return: /* Make global symbol for debugging */ @@ -534,7 +539,7 @@ C_ENTRY(sys_rt_sigreturn_wrapper): */ addik r15, r0, ret_from_trap_no_rval - 8 brid sys_rt_sigreturn /* Do real work */ - addik r5, r1, 0; /* add user context as 1st arg */ + addik r5, r1, PTO; /* add user context as 1st arg */ /* * HW EXCEPTION rutine start @@ -545,7 +550,7 @@ C_ENTRY(full_exception_trap): addik r17, r17, -4 SAVE_STATE /* Save registers */ /* PC, before IRQ/trap - this is one instruction above */ - swi r17, r1, PT_PC; + swi r17, r1, PTO+PT_PC; tovirt(r1,r1) /* FIXME this can be store directly in PT_ESR reg. * I tested it but there is a fault */ @@ -555,7 +560,7 @@ C_ENTRY(full_exception_trap): mfs r7, rfsr; /* save FSR */ mts rfsr, r0; /* Clear sticky fsr */ rted r0, full_exception - addik r5, r1, 0 /* parameter struct pt_regs * regs */ + addik r5, r1, PTO /* parameter struct pt_regs * regs */ /* * Unaligned data trap. @@ -581,14 +586,14 @@ C_ENTRY(unaligned_data_trap): lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); SAVE_STATE /* Save registers.*/ /* PC, before IRQ/trap - this is one instruction above */ - swi r17, r1, PT_PC; + swi r17, r1, PTO+PT_PC; tovirt(r1,r1) /* where the trap should return need -8 to adjust for rtsd r15, 8 */ addik r15, r0, ret_from_exc-8 mfs r3, resr /* ESR */ mfs r4, rear /* EAR */ rtbd r0, _unaligned_data_exception - addik r7, r1, 0 /* parameter struct pt_regs * regs */ + addik r7, r1, PTO /* parameter struct pt_regs * regs */ /* * Page fault traps. @@ -611,30 +616,30 @@ C_ENTRY(unaligned_data_trap): C_ENTRY(page_fault_data_trap): SAVE_STATE /* Save registers.*/ /* PC, before IRQ/trap - this is one instruction above */ - swi r17, r1, PT_PC; + swi r17, r1, PTO+PT_PC; tovirt(r1,r1) /* where the trap should return need -8 to adjust for rtsd r15, 8 */ addik r15, r0, ret_from_exc-8 mfs r6, rear /* parameter unsigned long address */ mfs r7, resr /* parameter unsigned long error_code */ rted r0, do_page_fault - addik r5, r1, 0 /* parameter struct pt_regs * regs */ + addik r5, r1, PTO /* parameter struct pt_regs * regs */ C_ENTRY(page_fault_instr_trap): SAVE_STATE /* Save registers.*/ /* PC, before IRQ/trap - this is one instruction above */ - swi r17, r1, PT_PC; + swi r17, r1, PTO+PT_PC; tovirt(r1,r1) /* where the trap should return need -8 to adjust for rtsd r15, 8 */ addik r15, r0, ret_from_exc-8 mfs r6, rear /* parameter unsigned long address */ ori r7, r0, 0 /* parameter unsigned long error_code */ rted r0, do_page_fault - addik r5, r1, 0 /* parameter struct pt_regs * regs */ + addik r5, r1, PTO /* parameter struct pt_regs * regs */ /* Entry point used to return from an exception. */ C_ENTRY(ret_from_exc): - lwi r11, r1, PT_MODE; + lwi r11, r1, PTO+PT_MODE; bnei r11, 2f; /* See if returning to kernel mode, */ /* ... if so, skip resched &c. */ @@ -666,7 +671,7 @@ C_ENTRY(ret_from_exc): * complete register state. Here we save anything not saved by * the normal entry sequence, so that it may be safely restored * (in a possibly modified form) after do_notify_resume returns. */ - addik r5, r1, 0; /* Arg 1: struct pt_regs *regs */ + addik r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ bralid r15, do_notify_resume; /* Handle any signals */ addi r6, r0, 0; /* Arg 2: int in_syscall */ bri 1b @@ -678,7 +683,7 @@ C_ENTRY(ret_from_exc): tophys(r1,r1); RESTORE_REGS_RTBD; - addik r1, r1, PT_SIZE /* Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ lwi r1, r1, PT_R1 - PT_SIZE; /* Restore user stack pointer. */ bri 6f; @@ -687,7 +692,7 @@ C_ENTRY(ret_from_exc): VM_OFF; tophys(r1,r1); RESTORE_REGS_RTBD; - addik r1, r1, PT_SIZE /* Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space. */ tovirt(r1,r1); 6: @@ -720,10 +725,10 @@ C_ENTRY(_interrupt): tophys(r1,r1); /* MS: I have in r1 physical address where stack is */ /* save registers */ /* MS: Make room on the stack -> activation record */ - addik r1, r1, -PT_SIZE; + addik r1, r1, -STATE_SAVE_SIZE; SAVE_REGS brid 2f; - swi r1, r1, PT_MODE; /* 0 - user mode, 1 - kernel mode */ + swi r1, r1, PTO+PT_MODE; /* 0 - user mode, 1 - kernel mode */ 1: /* User-mode state save. */ /* MS: get the saved current */ @@ -733,23 +738,23 @@ C_ENTRY(_interrupt): addik r1, r1, THREAD_SIZE; tophys(r1,r1); /* save registers */ - addik r1, r1, -PT_SIZE; + addik r1, r1, -STATE_SAVE_SIZE; SAVE_REGS /* calculate mode */ - swi r0, r1, PT_MODE; + swi r0, r1, PTO+PT_MODE; lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); - swi r11, r1, PT_R1; + swi r11, r1, PTO+PT_R1; clear_ums; 2: lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); tovirt(r1,r1) addik r15, r0, irq_call; irq_call:rtbd r0, do_IRQ; - addik r5, r1, 0; + addik r5, r1, PTO; /* MS: we are in virtual mode */ ret_from_irq: - lwi r11, r1, PT_MODE; + lwi r11, r1, PTO+PT_MODE; bnei r11, 2f; 1: @@ -765,7 +770,7 @@ ret_from_irq: 5: andi r11, r19, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME; beqid r11, no_intr_resched /* Handle a signal return; Pending signals should be in r18. */ - addik r5, r1, 0; /* Arg 1: struct pt_regs *regs */ + addik r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ bralid r15, do_notify_resume; /* Handle any signals */ addi r6, r0, 0; /* Arg 2: int in_syscall */ bri 1b @@ -778,7 +783,7 @@ no_intr_resched: VM_OFF; tophys(r1,r1); RESTORE_REGS - addik r1, r1, PT_SIZE /* MS: Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* MS: Clean up stack space. */ lwi r1, r1, PT_R1 - PT_SIZE; bri 6f; /* MS: Return to kernel state. */ @@ -801,7 +806,7 @@ restore: VM_OFF /* MS: turn off MMU */ tophys(r1,r1) RESTORE_REGS - addik r1, r1, PT_SIZE /* MS: Clean up stack space. */ + addik r1, r1, STATE_SAVE_SIZE /* MS: Clean up stack space. */ tovirt(r1,r1); 6: IRQ_return: /* MS: Make global symbol for debugging */ @@ -820,29 +825,29 @@ C_ENTRY(_xtmr_manager_reset): lwi r1, r0, xmb_manager_stackpointer /* Restore MSR */ - lwi r2, r1, PT_MSR + lwi r2, r1, PTO+PT_MSR mts rmsr, r2 bri 4 /* restore Special purpose registers */ - lwi r2, r1, PT_PID + lwi r2, r1, PTO+PT_PID mts rpid, r2 - lwi r2, r1, PT_TLBI + lwi r2, r1, PTO+PT_TLBI mts rtlbx, r2 - lwi r2, r1, PT_ZPR + lwi r2, r1, PTO+PT_ZPR mts rzpr, r2 #if CONFIG_XILINX_MICROBLAZE0_USE_FPU - lwi r2, r1, PT_FSR + lwi r2, r1, PTO+PT_FSR mts rfsr, r2 #endif /* restore all the tlb's */ addik r3, r0, TOPHYS(tlb_skip) - addik r6, r0, PT_TLBL0 - addik r7, r0, PT_TLBH0 + addik r6, r0, PTO+PT_TLBL0 + addik r7, r0, PTO+PT_TLBH0 restore_tlb: add r6, r6, r1 add r7, r7, r1 @@ -868,9 +873,9 @@ ret_from_reset: VM_OFF /* MS: Restore all regs */ RESTORE_REGS - lwi r14, r1, PT_R14 - lwi r16, r1, PT_PC - addik r1, r1, PT_SIZE + 36 + lwi r14, r1, PTO+PT_R14 + lwi r16, r1, PTO+PT_PC + addik r1, r1, STATE_SAVE_SIZE + 36 rtbd r16, 0 nop @@ -885,11 +890,11 @@ C_ENTRY(_xmb_manager_break): * Reserve memory in the stack for context store/restore * (which includes memory for storing tlbs (max two tlbs)) */ - addik r1, r1, -PT_SIZE - 36 + addik r1, r1, -STATE_SAVE_SIZE - 36 swi r1, r0, xmb_manager_stackpointer SAVE_REGS - swi r14, r1, PT_R14 /* rewrite saved R14 value */ - swi r16, r1, PT_PC; /* PC and r16 are the same */ + swi r14, r1, PTO+PT_R14 /* rewrite saved R14 value */ + swi r16, r1, PTO+PT_PC; /* PC and r16 are the same */ lwi r6, r0, TOPHYS(xmb_manager_baseaddr) lwi r7, r0, TOPHYS(xmb_manager_crval) @@ -903,25 +908,25 @@ C_ENTRY(_xmb_manager_break): /* Save the special purpose registers */ mfs r2, rpid - swi r2, r1, PT_PID + swi r2, r1, PTO+PT_PID mfs r2, rtlbx - swi r2, r1, PT_TLBI + swi r2, r1, PTO+PT_TLBI mfs r2, rzpr - swi r2, r1, PT_ZPR + swi r2, r1, PTO+PT_ZPR #if CONFIG_XILINX_MICROBLAZE0_USE_FPU mfs r2, rfsr - swi r2, r1, PT_FSR + swi r2, r1, PTO+PT_FSR #endif mfs r2, rmsr - swi r2, r1, PT_MSR + swi r2, r1, PTO+PT_MSR /* Save all the tlb's */ addik r3, r0, TOPHYS(tlb_skip) - addik r6, r0, PT_TLBL0 - addik r7, r0, PT_TLBH0 + addik r6, r0, PTO+PT_TLBL0 + addik r7, r0, PTO+PT_TLBH0 save_tlb: add r6, r6, r1 add r7, r7, r1 @@ -978,28 +983,28 @@ C_ENTRY(_debug_exception): lwi r1, r0, TOPHYS(PER_CPU(ENTRY_SP)); /* Reload kernel stack-ptr*/ /* BIP bit is set on entry, no interrupts can occur */ - addik r1, r1, CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - PT_SIZE; + addik r1, r1, CONFIG_KERNEL_BASE_ADDR - CONFIG_KERNEL_START - STATE_SAVE_SIZE; SAVE_REGS; /* save all regs to pt_reg structure */ - swi r0, r1, PT_R0; /* R0 must be saved too */ - swi r14, r1, PT_R14 /* rewrite saved R14 value */ - swi r16, r1, PT_PC; /* PC and r16 are the same */ + swi r0, r1, PTO+PT_R0; /* R0 must be saved too */ + swi r14, r1, PTO+PT_R14 /* rewrite saved R14 value */ + swi r16, r1, PTO+PT_PC; /* PC and r16 are the same */ /* save special purpose registers to pt_regs */ mfs r11, rear; - swi r11, r1, PT_EAR; + swi r11, r1, PTO+PT_EAR; mfs r11, resr; - swi r11, r1, PT_ESR; + swi r11, r1, PTO+PT_ESR; mfs r11, rfsr; - swi r11, r1, PT_FSR; + swi r11, r1, PTO+PT_FSR; /* stack pointer is in physical address at it is decrease - * by PT_SIZE but we need to get correct R1 value */ - addik r11, r1, CONFIG_KERNEL_START - CONFIG_KERNEL_BASE_ADDR + PT_SIZE; - swi r11, r1, PT_R1 + * by STATE_SAVE_SIZE but we need to get correct R1 value */ + addik r11, r1, CONFIG_KERNEL_START - CONFIG_KERNEL_BASE_ADDR + STATE_SAVE_SIZE; + swi r11, r1, PTO+PT_R1 /* MS: r31 - current pointer isn't changed */ tovirt(r1,r1) #ifdef CONFIG_KGDB - addi r5, r1, 0 /* pass pt_reg address as the first arg */ + addi r5, r1, PTO /* pass pt_reg address as the first arg */ addik r15, r0, dbtrap_call; /* return address */ rtbd r0, microblaze_kgdb_break nop; @@ -1015,16 +1020,16 @@ C_ENTRY(_debug_exception): addik r1, r1, THREAD_SIZE; /* calculate kernel stack pointer */ tophys(r1,r1); - addik r1, r1, -PT_SIZE; /* Make room on the stack. */ + addik r1, r1, -STATE_SAVE_SIZE; /* Make room on the stack. */ SAVE_REGS; - swi r16, r1, PT_PC; /* Save LP */ - swi r0, r1, PT_MODE; /* Was in user-mode. */ + swi r16, r1, PTO+PT_PC; /* Save LP */ + swi r0, r1, PTO+PT_MODE; /* Was in user-mode. */ lwi r11, r0, TOPHYS(PER_CPU(ENTRY_SP)); - swi r11, r1, PT_R1; /* Store user SP. */ + swi r11, r1, PTO+PT_R1; /* Store user SP. */ lwi CURRENT_TASK, r0, TOPHYS(PER_CPU(CURRENT_SAVE)); tovirt(r1,r1) set_vms; - addik r5, r1, 0; + addik r5, r1, PTO; addik r15, r0, dbtrap_call; dbtrap_call: /* Return point for kernel/user entry + 8 because of rtsd r15, 8 */ rtbd r0, sw_exception @@ -1032,7 +1037,7 @@ dbtrap_call: /* Return point for kernel/user entry + 8 because of rtsd r15, 8 */ /* MS: The first instruction for the second part of the gdb/kgdb */ set_bip; /* Ints masked for state restore */ - lwi r11, r1, PT_MODE; + lwi r11, r1, PTO+PT_MODE; bnei r11, 2f; /* MS: Return to user space - gdb */ 1: @@ -1051,7 +1056,7 @@ dbtrap_call: /* Return point for kernel/user entry + 8 because of rtsd r15, 8 */ 5: andi r11, r19, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME; beqi r11, 4f; /* Signals to handle, handle them */ - addik r5, r1, 0; /* Arg 1: struct pt_regs *regs */ + addik r5, r1, PTO; /* Arg 1: struct pt_regs *regs */ bralid r15, do_notify_resume; /* Handle any signals */ addi r6, r0, 0; /* Arg 2: int in_syscall */ bri 1b @@ -1062,7 +1067,7 @@ dbtrap_call: /* Return point for kernel/user entry + 8 because of rtsd r15, 8 */ tophys(r1,r1); /* MS: Restore all regs */ RESTORE_REGS_RTBD - addik r1, r1, PT_SIZE /* Clean up stack space */ + addik r1, r1, STATE_SAVE_SIZE /* Clean up stack space */ lwi r1, r1, PT_R1 - PT_SIZE; /* Restore user stack pointer */ DBTRAP_return_user: /* MS: Make global symbol for debugging */ rtbd r16, 0; /* MS: Instructions to return from a debug trap */ @@ -1073,9 +1078,9 @@ DBTRAP_return_user: /* MS: Make global symbol for debugging */ tophys(r1,r1); /* MS: Restore all regs */ RESTORE_REGS_RTBD - lwi r14, r1, PT_R14; - lwi r16, r1, PT_PC; - addik r1, r1, PT_SIZE; /* MS: Clean up stack space */ + lwi r14, r1, PTO+PT_R14; + lwi r16, r1, PTO+PT_PC; + addik r1, r1, STATE_SAVE_SIZE; /* MS: Clean up stack space */ tovirt(r1,r1); DBTRAP_return_kernel: /* MS: Make global symbol for debugging */ rtbd r16, 0; /* MS: Instructions to return from a debug trap */ @@ -1173,7 +1178,7 @@ ENTRY(_switch_to) .ent xmb_inject_err .type xmb_inject_err, @function xmb_inject_err: - addik r1, r1, -PT_SIZE + addik r1, r1, -STATE_SAVE_SIZE SAVE_REGS /* Switch to real mode */ @@ -1197,7 +1202,7 @@ xmb_inject_err: nop; 1: RESTORE_REGS - addik r1, r1, PT_SIZE + addik r1, r1, STATE_SAVE_SIZE rtsd r15, 8; nop; .end xmb_inject_err diff --git a/arch/microblaze/kernel/hw_exception_handler.S b/arch/microblaze/kernel/hw_exception_handler.S index 07ea23965f8175..4693916be52672 100644 --- a/arch/microblaze/kernel/hw_exception_handler.S +++ b/arch/microblaze/kernel/hw_exception_handler.S @@ -313,6 +313,11 @@ _MB_HW_ExceptionVectorTable: .align 4 .ent _hw_exception_handler _hw_exception_handler: + /* + * This handler saves into the static pt_pool_space buffer, not into a + * stack frame, so the plain PT_* offsets are right here -- do not add + * the PTO of entry.S's frames. + */ swi r1, r0, TOPHYS(pt_pool_space + PT_R1); /* GET_SP */ /* Save date to kernel memory. Here is the problem * when you came from user space */ diff --git a/arch/microblaze/kernel/process.c b/arch/microblaze/kernel/process.c index 6cbf642d7b801d..090e1697ea7aee 100644 --- a/arch/microblaze/kernel/process.c +++ b/arch/microblaze/kernel/process.c @@ -19,6 +19,7 @@ #include #include #include +#include void show_regs(struct pt_regs *regs) { @@ -65,7 +66,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) * the registers. That's OK for a brand new thread.*/ memset(childregs, 0, sizeof(struct pt_regs)); memset(&ti->cpu_context, 0, sizeof(struct cpu_context)); - ti->cpu_context.r1 = (unsigned long)childregs; + ti->cpu_context.r1 = (unsigned long)childregs - PTO; ti->cpu_context.r20 = (unsigned long)args->fn; ti->cpu_context.r19 = (unsigned long)args->fn_arg; childregs->pt_mode = 1; @@ -79,7 +80,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) childregs->r1 = usp; memset(&ti->cpu_context, 0, sizeof(struct cpu_context)); - ti->cpu_context.r1 = (unsigned long)childregs; + ti->cpu_context.r1 = (unsigned long)childregs - PTO; childregs->msr |= MSR_UMS; /* we should consider the fact that childregs is a copy of the parent From ca35dd21a5f8343f47a23e4e5b3f0ffccf76a4ae Mon Sep 17 00:00:00 2001 From: Sam Price Date: Fri, 21 Aug 2026 17:18:09 +0200 Subject: [PATCH 0074/2286] microblaze: preserve the MSR carry flags across signals setup_sigcontext() and restore_sigcontext() copy r0-r31, pc, ear, esr and fsr to and from the signal frame but never touch MSR. The interrupted MSR is therefore dropped from the signal context entirely: the handler's ucontext does not expose it, a handler cannot adjust the resumed arithmetic flags through uc_mcontext.regs.msr, and -- because restore_sigcontext() leaves regs->msr as whatever the rt_sigreturn trap left in it -- the interrupted context resumes with the carry produced by the syscall entry path (_user_exception does "addi r14, r14, 4", which writes carry), not with its own. Other architectures round-trip the user-visible flags through the signal frame (arm's cpsr, csky's carry, x86's eflags), so a handler can both read and adjust the resumed flags; purely privileged status registers such as riscv's sstatus are left out. MicroBlaze should do the same for the user-writable bits. Concretely, MSR[C] (carry) is lost across signal delivery. Code that keeps a live carry across a point where a signal can be delivered -- for example an lwx/swx compare-and-swap retry loop, between the swx and the carry test -- resumes with the handler's carry and mis-evaluates the result; the same failure class as the rt_sigreturn r3/r4 clobber, reached through a different register. Demonstrated under qemu-system-microblazeel (machine petalogix-s3adsp1800): a handler that sets MSR_C in uc_mcontext.regs.msr has no effect before this change (0 of 132 in-window signals propagated) and takes effect after (130 of 132). Save MSR in setup_sigcontext() so the handler's ucontext exposes it. The signal frame is user-writable, so restore_sigcontext() must not restore it verbatim: MicroBlaze packs the user-writable carry (MSR_C, MSR_CC) and the privileged control bits (MSR_UM, MSR_VM, MSR_IE, MSR_EE, ...) into the one register, and a verbatim restore would let userspace alter privileged return state -- rtbd derives the resumed mode from MSR_UMS/MSR_VMS. Restore only MSR_C | MSR_CC from the frame and keep the rest from the current regs->msr. This mirrors x86's restore_sigcontext(), which masks the restored EFLAGS to FIX_EFLAGS for the same reason; arches whose status register is purely privileged (e.g. riscv sstatus) simply do not restore it at all. Comment on the exposed MSR reworded: it is not read-only, restore_sigcontext() applies MSR_C|MSR_CC from the frame. Author's Signed-off-by kept; he agreed to comment and changelog cleanups. Fixes: 2148daa9c45f ("microblaze_v8: Signal support") Cc: stable@vger.kernel.org Signed-off-by: Sam Price Signed-off-by: Ramin Moussavi Tested-by: Waldemar Brodkorb Link: https://patch.msgid.link/20260821151809.1233057-6-ramin.moussavi@yacoub.de Signed-off-by: Michal Simek --- arch/microblaze/kernel/signal.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/arch/microblaze/kernel/signal.c b/arch/microblaze/kernel/signal.c index 4956014a993797..6bbc16f98d5ebb 100644 --- a/arch/microblaze/kernel/signal.c +++ b/arch/microblaze/kernel/signal.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,22 @@ static int restore_sigcontext(struct pt_regs *regs, COPY(pc); COPY(ear); COPY(esr); COPY(fsr); #undef COPY + /* + * The frame is user-writable, so restore only the user-writable + * status flags (carry) and keep the kernel-controlled MSR bits + * (UMS/VMS/IE/EE/...) from regs->msr: rtbd derives the resumed mode + * from UMS/VMS, so a verbatim restore would hand userspace the + * privileged return state. Same idea as x86 masking the restored + * EFLAGS to FIX_EFLAGS. + */ + { + unsigned long msr; + + err |= __get_user(msr, &sc->regs.msr); + regs->msr = (regs->msr & ~(MSR_C | MSR_CC)) | + (msr & (MSR_C | MSR_CC)); + } + *rval_p = regs->r3; return err; @@ -140,6 +157,7 @@ setup_sigcontext(struct sigcontext __user *sc, struct pt_regs *regs, COPY(r26); COPY(r27); COPY(r28); COPY(r29); COPY(r30); COPY(r31); COPY(pc); COPY(ear); COPY(esr); COPY(fsr); + COPY(msr); /* restore_sigcontext() accepts only carry state */ #undef COPY err |= __put_user(mask, &sc->oldmask); From aeedf85beac51c7cd8f7bf4c918a0104dfdb8b78 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 May 2026 20:40:59 +0200 Subject: [PATCH 0075/2286] microblaze: CCF: fix refcount leak Every value returned from of_clk_get() is supposed to be cleaned up via clk_put() once not needed anymore. Fixes: c1120542b99a ("microblaze: Add support for CCF") Signed-off-by: Alexander A. Klimov Link: https://patch.msgid.link/20260526184105.18962-4-grandmaster@al2klimov.de Signed-off-by: Michal Simek --- arch/microblaze/kernel/cpu/cpuinfo.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/microblaze/kernel/cpu/cpuinfo.c b/arch/microblaze/kernel/cpu/cpuinfo.c index cd9b4450763b0b..32c508e85408ac 100644 --- a/arch/microblaze/kernel/cpu/cpuinfo.c +++ b/arch/microblaze/kernel/cpu/cpuinfo.c @@ -133,6 +133,7 @@ void __init setup_cpuinfo_clk(void) cpuinfo.cpu_clock_freq = fcpu(cpu, "timebase-frequency"); } else { cpuinfo.cpu_clock_freq = clk_get_rate(clk); + clk_put(clk); } if (!cpuinfo.cpu_clock_freq) { From 8518fd17ccef4a7d77877ccb0403b722106ad1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Jean?= Date: Fri, 21 Aug 2026 10:30:27 +0000 Subject: [PATCH 0076/2286] microblaze: prevent ptrace writes to MSR and pt_mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit arch_ptrace() accepts PTRACE_POKEUSR for every aligned offset below PT_SIZE. This includes the saved machine status register and pt_mode, which are kernel-owned execution state rather than general registers. On return from an exception, rtbd copies MSR_UMS to the live MSR_UM bit. An unprivileged same-UID tracer can therefore clear MSR_UMS in its tracee's saved register frame and make the tracee resume in supervisor mode. pt_mode also controls whether later entry paths classify the frame as kernel or user state. Reject PTRACE_POKEUSR writes to both fields while retaining read access. Fixes: 2b4384542691 ("microblaze_v8: ptrace support") Assisted-by: Codex:gpt-5 Signed-off-by: Jérémy Jean Link: https://patch.msgid.link/20260821103026.3627292-2-Jeremy.Jean@oss.cyber.gouv.fr Signed-off-by: Michal Simek --- arch/microblaze/kernel/ptrace.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/microblaze/kernel/ptrace.c b/arch/microblaze/kernel/ptrace.c index 236264e932d64f..ac1f0503216b42 100644 --- a/arch/microblaze/kernel/ptrace.c +++ b/arch/microblaze/kernel/ptrace.c @@ -101,6 +101,9 @@ long arch_ptrace(struct task_struct *child, long request, } else { rval = -EIO; } + } else if (request == PTRACE_POKEUSR && + (addr == PT_MSR || addr == PT_MODE)) { + rval = -EIO; } else if (addr < PT_SIZE && (addr & 0x3) == 0) { microblaze_reg_t *reg_addr = reg_save_addr(addr, child); if (request == PTRACE_PEEKUSR) From e76ed86ee88f68f19a3ada75de2192d7fbb3ac99 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:29 -0400 Subject: [PATCH 0077/2286] ext2: mark s_next_generation as guarded by s_next_gen_lock s_next_generation is only ever modified while holding s_next_gen_lock (in ext2_new_inode()), so annotate it with __guarded_by() for Clang's context analysis. The only other write is the initialisation in ext2_fill_super(), which runs before the superblock is live. No concurrent access should be possible. Convert the spinlock initialization to use scoped_guard(spinlock_init, ...) and place the write under the guard to prevent a warning. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-2-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/ext2.h | 2 +- fs/ext2/super.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 5642451bf19173..690452808d24b8 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -93,7 +93,7 @@ struct ext2_sb_info { int s_inode_size; int s_first_ino; spinlock_t s_next_gen_lock; - u32 s_next_generation; + u32 s_next_generation __guarded_by(&s_next_gen_lock); unsigned long s_dir_count; u8 *s_debts; struct percpu_counter s_freeblocks_counter; diff --git a/fs/ext2/super.c b/fs/ext2/super.c index a40f530872a407..3d64b28745d3a4 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -1126,8 +1126,9 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc) goto failed_mount2; } sbi->s_gdb_count = db_count; - sbi->s_next_generation = get_random_u32(); spin_lock_init(&sbi->s_next_gen_lock); + scoped_guard(spinlock, &sbi->s_next_gen_lock) + sbi->s_next_generation = get_random_u32(); /* per filesystem reservation list head & lock */ spin_lock_init(&sbi->s_rsv_window_lock); From eb34d143711ac8607ab6abdc6f0b7f13fd8f8287 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:30 -0400 Subject: [PATCH 0078/2286] ext2: annotate ext2_update_dynamic_rev() as requiring s_lock ext2_update_dynamic_rev() modifies several fields within 'struct ext2_super_block' and is already documented as requiring s_lock. Express that with __must_hold() for Clang's context analysis. Both callers ext2_xattr_update_super_block() and __ext2_write_inode() already hold s_lock. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-3-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/ext2.h | 3 ++- fs/ext2/super.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 690452808d24b8..2ace014ed43cc1 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -761,7 +761,8 @@ extern __printf(3, 4) void ext2_error(struct super_block *, const char *, const char *, ...); extern __printf(3, 4) void ext2_msg(struct super_block *, const char *, const char *, ...); -extern void ext2_update_dynamic_rev (struct super_block *sb); +extern void ext2_update_dynamic_rev(struct super_block *sb) + __must_hold(&EXT2_SB(sb)->s_lock); extern void ext2_sync_super(struct super_block *sb, struct ext2_super_block *es, int wait); diff --git a/fs/ext2/super.c b/fs/ext2/super.c index 3d64b28745d3a4..27f32637d1b83a 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -127,6 +127,7 @@ void ext2_msg(struct super_block *sb, const char *prefix, * This must be called with sbi->s_lock held. */ void ext2_update_dynamic_rev(struct super_block *sb) + __must_hold(&EXT2_SB(sb)->s_lock) { struct ext2_super_block *es = EXT2_SB(sb)->s_es; From 3d0c4287b70c880ab15196dfe4e4f9f9eb1dc9b3 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:31 -0400 Subject: [PATCH 0079/2286] ext2: mark statfs overhead cache as guarded by s_lock s_overhead_last and s_blocks_last cache the filesystem overhead computation and are only ever accessed in ext2_statfs() under s_lock. Annotate them with __guarded_by() for Clang's context analysis. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-4-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/ext2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 2ace014ed43cc1..67cf556aaf44b6 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -77,8 +77,8 @@ struct ext2_sb_info { unsigned long s_gdb_count; /* Number of group descriptor blocks */ unsigned long s_desc_per_block; /* Number of group descriptors per block */ unsigned long s_groups_count; /* Number of groups in the fs */ - unsigned long s_overhead_last; /* Last calculated overhead */ - unsigned long s_blocks_last; /* Last seen block count */ + unsigned long s_overhead_last __guarded_by(&s_lock); /* Last calculated overhead */ + unsigned long s_blocks_last __guarded_by(&s_lock); /* Last seen block count */ struct buffer_head * s_sbh; /* Buffer containing the super block */ struct ext2_super_block * s_es; /* Pointer to the super block in the buffer */ struct buffer_head ** s_group_desc; From 023051c2d0f1d782da1bd0b09092f31592b46f3a Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:32 -0400 Subject: [PATCH 0080/2286] ext2: mark s_mount_state as guarded by s_lock s_mount_state tracks the filesystem's mount/error state and is protected by s_lock everywhere it is accessed. Annotate it with __guarded_by() for Clang's context analysis. ext2_setup_super() reads s_mount_state and is called with s_lock held from the remount path, so annotate it with __must_hold(). There are two accesses (the initial read in ext2_fill_super() and the setup_super() call) in the mount flow. This is before the superblock is live, so no concurrent access should be possible. Convert the spinlock initialization to use guard(spinlock_init) to prevent warnings on the writes later in initialization. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-5-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/ext2.h | 2 +- fs/ext2/super.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 67cf556aaf44b6..800200b342c4be 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -86,7 +86,7 @@ struct ext2_sb_info { unsigned long s_sb_block; kuid_t s_resuid; kgid_t s_resgid; - unsigned short s_mount_state; + unsigned short s_mount_state __guarded_by(&s_lock); unsigned short s_pad; int s_addr_per_block_bits; int s_desc_per_block_bits; diff --git a/fs/ext2/super.c b/fs/ext2/super.c index 27f32637d1b83a..4d9e5b43f59a69 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -634,6 +634,7 @@ static int ext2_parse_param(struct fs_context *fc, struct fs_parameter *param) static int ext2_setup_super (struct super_block * sb, struct ext2_super_block * es, int read_only) + __must_hold(&EXT2_SB(sb)->s_lock) { int res = 0; struct ext2_sb_info *sbi = EXT2_SB(sb); @@ -895,7 +896,7 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc) sb->s_fs_info = sbi; sbi->s_sb_block = sb_block; - spin_lock_init(&sbi->s_lock); + guard(spinlock_init)(&EXT2_SB(sb)->s_lock); ret = -EINVAL; /* From 7aa90b3c8ef8f936f7db310bc9ec60eba26cd726 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:33 -0400 Subject: [PATCH 0081/2286] ext2: annotate ext2_init_block_alloc_info() as requiring truncate_mutex ext2_init_block_alloc_info() sets up the inode's block reservation info and is already documented as needing truncate_mutex protection. Express that with __must_hold() for Clang's context analysis. Both callers (ext2_get_blocks() and ext2_ioctl()) already hold truncate_mutex. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-6-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/balloc.c | 1 + fs/ext2/ext2.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c index adf0f31fbddd16..53c91cb38bdee9 100644 --- a/fs/ext2/balloc.c +++ b/fs/ext2/balloc.c @@ -414,6 +414,7 @@ static inline int rsv_is_empty(struct ext2_reserve_window *rsv) * Needs truncate_mutex protection prior to calling this function. */ void ext2_init_block_alloc_info(struct inode *inode) + __must_hold(&EXT2_I(inode)->truncate_mutex) { struct ext2_inode_info *ei = EXT2_I(inode); struct ext2_block_alloc_info *block_i; diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 800200b342c4be..4b1622eec2af17 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -710,7 +710,8 @@ extern struct ext2_group_desc * ext2_get_group_desc(struct super_block * sb, struct buffer_head ** bh); extern void ext2_discard_reservation (struct inode *); extern int ext2_should_retry_alloc(struct super_block *sb, int *retries); -extern void ext2_init_block_alloc_info(struct inode *); +extern void ext2_init_block_alloc_info(struct inode *inode) + __must_hold(&EXT2_I(inode)->truncate_mutex); extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv); /* dir.c */ From d8c906454fde090a45edaf136f93aa2af720e5fa Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:34 -0400 Subject: [PATCH 0082/2286] ext2: annotate block-mapping helpers as requiring truncate_mutex The indirect-block mapping helpers in inode.c all run under the inode's truncate_mutex. ext2_find_goal(), ext2_alloc_blocks(), ext2_alloc_branch() and ext2_splice_branch() are reached only from ext2_get_blocks() while it holds the mutex. ext2_find_shared(), ext2_free_data() and ext2_free_branches() are called from __ext2_truncate_blocks() while it holds the mutex. Add __must_hold() annotations for the Clang's context analysis to reflect this. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-7-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/inode.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index a9245f0cda4d46..1a1ea1fd485b9a 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -327,6 +327,7 @@ static ext2_fsblk_t ext2_find_near(struct inode *inode, Indirect *ind) static inline ext2_fsblk_t ext2_find_goal(struct inode *inode, long block, Indirect *partial) + __must_hold(&EXT2_I(inode)->truncate_mutex) { struct ext2_block_alloc_info *block_i; @@ -397,6 +398,7 @@ ext2_blks_to_allocate(Indirect * branch, int k, unsigned long blks, static int ext2_alloc_blocks(struct inode *inode, ext2_fsblk_t goal, int indirect_blks, int blks, ext2_fsblk_t new_blocks[4], int *err) + __must_hold(&EXT2_I(inode)->truncate_mutex) { int target, i; unsigned long count = 0; @@ -477,6 +479,7 @@ static int ext2_alloc_blocks(struct inode *inode, static int ext2_alloc_branch(struct inode *inode, int indirect_blks, int *blks, ext2_fsblk_t goal, int *offsets, Indirect *branch) + __must_hold(&EXT2_I(inode)->truncate_mutex) { int blocksize = inode->i_sb->s_blocksize; int i, n = 0; @@ -558,6 +561,7 @@ static int ext2_alloc_branch(struct inode *inode, */ static void ext2_splice_branch(struct inode *inode, long block, Indirect *where, int num, int blks) + __must_hold(&EXT2_I(inode)->truncate_mutex) { int i; struct ext2_block_alloc_info *block_i; @@ -1002,6 +1006,7 @@ static Indirect *ext2_find_shared(struct inode *inode, int offsets[4], Indirect chain[4], __le32 *top) + __must_hold(&EXT2_I(inode)->truncate_mutex) { Indirect *partial, *p; int k, err; @@ -1057,6 +1062,7 @@ static Indirect *ext2_find_shared(struct inode *inode, * appropriately. */ static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q) + __must_hold(&EXT2_I(inode)->truncate_mutex) { ext2_fsblk_t block_to_free = 0, count = 0; ext2_fsblk_t nr; @@ -1097,6 +1103,7 @@ static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q) * appropriately. */ static void ext2_free_branches(struct inode *inode, __le32 *p, __le32 *q, int depth) + __must_hold(&EXT2_I(inode)->truncate_mutex) { struct buffer_head * bh; ext2_fsblk_t nr; From ca12826b4aac898318e7359bf7a1b1532ae02d2d Mon Sep 17 00:00:00 2001 From: Stanislav Kinsburskii Date: Thu, 6 Aug 2026 18:01:20 -0700 Subject: [PATCH 0083/2286] audit: Fix filter rule accounting after automatic removal The audit_n_rules and audit_signals counters are incremented when filter rules are installed and decremented by the explicit rule deletion path. Rules can also disappear when a watch or tree is removed, or when an LSM rule cannot be reconstructed, but those paths do not update the counters. As a result, audit_n_rules can remain nonzero after the last applicable rule has gone away, causing subsequent syscalls to allocate non-dummy audit contexts unnecessarily. A stale audit_signals value similarly causes unnecessary signal auditing work. This can be reproduced for an inode watch with: mkdir /tmp/audit-n-rules-bench touch /tmp/audit-n-rules-bench/watched auditctl -w /tmp/audit-n-rules-bench/watched -p r \ -k audit_n_rules_bench rm /tmp/audit-n-rules-bench/watched rmdir /tmp/audit-n-rules-bench The rm updates the watch after its inode disappears, and the rmdir causes audit_remove_parent_watches() to remove the rule. For an audit tree, the kill_rules() path can be reproduced with: mkdir /tmp/audit-kill-rules auditctl -a always,exit -F arch=b64 \ -F dir=/tmp/audit-kill-rules -F perm=r \ -k audit_kill_rules_test rmdir /tmp/audit-kill-rules In both cases, auditctl -l reports no rules after the directory is removed. Factor the existing counter updates into common rule insertion and removal helpers and call the removal helper from every automatic removal path. All of these updates remain serialized by audit_filter_mutex. Fixes: 471a5c7c8391 ("[PATCH] introduce audit rules counter") Fixes: e54dc2431d74 ("[PATCH] audit signal recipients") Signed-off-by: Stanislav Kinsburskii [PM: removed "audit_bench" section as that is not being upstreamed] Signed-off-by: Paul Moore --- kernel/audit.h | 5 +++ kernel/audit_tree.c | 1 + kernel/audit_watch.c | 2 ++ kernel/auditfilter.c | 86 ++++++++++++++++++++++---------------------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/kernel/audit.h b/kernel/audit.h index 92d5e723d570b6..3176da46484320 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -272,6 +272,9 @@ extern void audit_put_tty(struct tty_struct *tty); /* audit watch/mark/tree functions */ extern unsigned int audit_serial(void); #ifdef CONFIG_AUDITSYSCALL +void audit_rule_account(const struct audit_krule *rule); +void audit_rule_unaccount(const struct audit_krule *rule); + extern int auditsc_get_stamp(struct audit_context *ctx, struct audit_stamp *stamp); @@ -315,6 +318,8 @@ extern void audit_filter_inodes(struct task_struct *tsk, struct audit_context *ctx); extern struct list_head *audit_killed_trees(void); #else /* CONFIG_AUDITSYSCALL */ +#define audit_rule_account(...) do { } while (0) +#define audit_rule_unaccount(...) do { } while (0) #define auditsc_get_stamp(c, s) 0 #define audit_put_watch(w) do { } while (0) #define audit_get_watch(w) do { } while (0) diff --git a/kernel/audit_tree.c b/kernel/audit_tree.c index 1ed19b7759129a..2d68d2ec2b2a10 100644 --- a/kernel/audit_tree.c +++ b/kernel/audit_tree.c @@ -558,6 +558,7 @@ static void kill_rules(struct audit_context *context, struct audit_tree *tree) rule->tree = NULL; list_del_rcu(&entry->list); list_del(&entry->rule.list); + audit_rule_unaccount(rule); call_rcu(&entry->rcu, audit_free_rule_rcu); } } diff --git a/kernel/audit_watch.c b/kernel/audit_watch.c index 4ac8a91e9ba8ff..28fab822ca0c66 100644 --- a/kernel/audit_watch.c +++ b/kernel/audit_watch.c @@ -284,6 +284,7 @@ static void audit_update_watch(struct audit_parent *parent, nentry = audit_dupe_rule(&oentry->rule, ctx); if (IS_ERR(nentry)) { list_del(&oentry->rule.list); + audit_rule_unaccount(r); audit_panic("error updating watch, removing"); } else { int h = audit_hash_ino(ino); @@ -336,6 +337,7 @@ static void audit_remove_parent_watches(struct audit_parent *parent) list_del(&r->rlist); list_del(&r->list); list_del_rcu(&e->list); + audit_rule_unaccount(r); call_rcu(&e->rcu, audit_free_rule_rcu); } audit_remove_watch(w); diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c index f526456252148f..cf91ba6f3617b1 100644 --- a/kernel/auditfilter.c +++ b/kernel/auditfilter.c @@ -196,7 +196,7 @@ int audit_match_class(int class, unsigned int syscall) } #ifdef CONFIG_AUDITSYSCALL -static inline int audit_match_class_bits(int class, u32 *mask) +static inline int audit_match_class_bits(int class, const u32 *mask) { int i; @@ -208,30 +208,62 @@ static inline int audit_match_class_bits(int class, u32 *mask) return 1; } -static int audit_match_signal(struct audit_entry *entry) +static int audit_match_signal(const struct audit_krule *rule) { - struct audit_field *arch = entry->rule.arch_f; + struct audit_field *arch = rule->arch_f; if (!arch) { /* When arch is unspecified, we must check both masks on biarch * as syscall number alone is ambiguous. */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL, - entry->rule.mask) && + rule->mask) && audit_match_class_bits(AUDIT_CLASS_SIGNAL_32, - entry->rule.mask)); + rule->mask)); } switch (audit_classify_arch(arch->val)) { case 0: /* native */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL, - entry->rule.mask)); + rule->mask)); case 1: /* 32bit on biarch */ return (audit_match_class_bits(AUDIT_CLASS_SIGNAL_32, - entry->rule.mask)); + rule->mask)); default: return 1; } } + +static bool audit_rule_counts_syscalls(const struct audit_krule *rule) +{ + switch (rule->listnr) { + case AUDIT_FILTER_USER: + case AUDIT_FILTER_EXCLUDE: + case AUDIT_FILTER_FS: + return false; + default: + return true; + } +} + +void audit_rule_account(const struct audit_krule *rule) +{ + lockdep_assert_held(&audit_filter_mutex); + + if (audit_rule_counts_syscalls(rule)) + audit_n_rules++; + if (!audit_match_signal(rule)) + audit_signals++; +} + +void audit_rule_unaccount(const struct audit_krule *rule) +{ + lockdep_assert_held(&audit_filter_mutex); + + if (audit_rule_counts_syscalls(rule)) + audit_n_rules--; + if (!audit_match_signal(rule)) + audit_signals--; +} #endif /* Common user-space to kernel rule translation. */ @@ -943,17 +975,6 @@ static inline int audit_add_rule(struct audit_entry *entry) struct audit_tree *tree = entry->rule.tree; struct list_head *list; int err = 0; -#ifdef CONFIG_AUDITSYSCALL - int dont_count = 0; - - /* If any of these, don't count towards total */ - switch (entry->rule.listnr) { - case AUDIT_FILTER_USER: - case AUDIT_FILTER_EXCLUDE: - case AUDIT_FILTER_FS: - dont_count = 1; - } -#endif mutex_lock(&audit_filter_mutex); e = audit_find_rule(entry, &list); @@ -1007,13 +1028,7 @@ static inline int audit_add_rule(struct audit_entry *entry) &audit_rules_list[entry->rule.listnr]); list_add_tail_rcu(&entry->list, list); } -#ifdef CONFIG_AUDITSYSCALL - if (!dont_count) - audit_n_rules++; - - if (!audit_match_signal(entry)) - audit_signals++; -#endif + audit_rule_account(&entry->rule); mutex_unlock(&audit_filter_mutex); return err; @@ -1025,17 +1040,6 @@ int audit_del_rule(struct audit_entry *entry) struct audit_entry *e; struct list_head *list; int ret = 0; -#ifdef CONFIG_AUDITSYSCALL - int dont_count = 0; - - /* If any of these, don't count towards total */ - switch (entry->rule.listnr) { - case AUDIT_FILTER_USER: - case AUDIT_FILTER_EXCLUDE: - case AUDIT_FILTER_FS: - dont_count = 1; - } -#endif mutex_lock(&audit_filter_mutex); e = audit_find_rule(entry, &list); @@ -1057,14 +1061,7 @@ int audit_del_rule(struct audit_entry *entry) if (e->rule.exe) audit_remove_mark_rule(&e->rule); -#ifdef CONFIG_AUDITSYSCALL - if (!dont_count) - audit_n_rules--; - - if (!audit_match_signal(entry)) - audit_signals--; -#endif - + audit_rule_unaccount(&e->rule); call_rcu(&e->rcu, audit_free_rule_rcu); out: @@ -1427,6 +1424,7 @@ static int update_lsm_rule(struct audit_krule *r) list_del(&r->rlist); list_del_rcu(&entry->list); list_del(&r->list); + audit_rule_unaccount(r); } else { if (r->watch || r->tree) list_replace_init(&r->rlist, &nentry->rule.rlist); From 51794b107d54b3c9a8ebbe12a3b81d8495bd482c Mon Sep 17 00:00:00 2001 From: Jaihind Yadav Date: Thu, 20 Aug 2026 11:31:16 +0530 Subject: [PATCH 0084/2286] scripts/gcc-plugins: suppress recorded GCC switches for extmod builds With GCC plugins enabled, external module builds can record -fplugin=... paths in DWARF producer metadata via -grecord-gcc-switches, leaking host/build specific paths and triggering QA checks. Handle this in scripts/Makefile.gcc-plugins for KBUILD_EXTMOD builds by adding -gno-record-gcc-switches to plugin cflags. This keeps plugin behavior unchanged while avoiding absolute plugin path leakage in external module debug metadata. Suggested-by: Nathan Chancellor Link: https://lore.kernel.org/all/20260817185146.GC1249844@ax162/ Signed-off-by: Jaihind Yadav Link: https://patch.msgid.link/20260820060116.786173-1-jaihindy@qti.qualcomm.com Signed-off-by: Nathan Chancellor --- scripts/Makefile.gcc-plugins | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins index b0e1423b09c213..9b98995805712f 100644 --- a/scripts/Makefile.gcc-plugins +++ b/scripts/Makefile.gcc-plugins @@ -8,6 +8,10 @@ ifdef CONFIG_GCC_PLUGIN_LATENT_ENTROPY endif export DISABLE_LATENT_ENTROPY_PLUGIN +# Disable recording GCC options in debug info when building external modules to +# avoid leaking absolute host build paths +gcc-plugin-cflags-$(if $(KBUILD_EXTMOD),y) += -gno-record-gcc-switches + # All the plugin CFLAGS are collected here in case a build target needs to # filter them out of the KBUILD_CFLAGS. GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y)) -DGCC_PLUGINS From ae1d2082d93bc04604dc08e9b7f9cdba5e0c28e6 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 21 Aug 2026 18:32:31 -0700 Subject: [PATCH 0085/2286] x86/bugs: Adapt SRSO mitigation to Zen6 Zen6 has BTB protection which isolates the different contexts (user/kernel, guest/host) from one another. This makes the SafeRET mitigation there unnecessary leaving the user/user and guest/guest attack vectors open, whose protection is handled by the Spectre v2 mitigation setting to do IBPB on a context switch. Detect that setting and report it with a new mitigation string. Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260822013231.1109255-1-bp@kernel.org --- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/bugs.c | 18 ++++++++++++++++-- arch/x86/kernel/cpu/scattered.c | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f9217..ee7066ca3f2cba 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -430,6 +430,7 @@ #define X86_FEATURE_SUCCOR (17*32+ 1) /* "succor" Uncorrectable error containment and recovery */ #define X86_FEATURE_CPPC_PERF_PRIO (17*32+ 2) /* CPPC Floor Perf support */ #define X86_FEATURE_SMCA (17*32+ 3) /* "smca" Scalable MCA */ +#define X86_FEATURE_BTB_CTX_ISOLATION (17*32+ 4) /* AMD: Branch predictions contexts isolated */ /* Intel-defined CPU features, CPUID level 0x00000007:0 (EDX), word 18 */ #define X86_FEATURE_AVX512_4VNNIW (18*32+ 2) /* "avx512_4vnniw" AVX-512 Neural Network Instructions */ diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c index 56eac5611c3189..1b2381da4d8319 100644 --- a/arch/x86/kernel/cpu/bugs.c +++ b/arch/x86/kernel/cpu/bugs.c @@ -1175,6 +1175,7 @@ enum srso_mitigation { SRSO_MITIGATION_IBPB, SRSO_MITIGATION_IBPB_ON_VMEXIT, SRSO_MITIGATION_BP_SPEC_REDUCE, + SRSO_MITIGATION_USER_IBPB, }; static enum srso_mitigation srso_mitigation __ro_after_init = SRSO_MITIGATION_AUTO; @@ -2908,7 +2909,8 @@ static const char * const srso_strings[] = { [SRSO_MITIGATION_SAFE_RET] = "Mitigation: Safe RET", [SRSO_MITIGATION_IBPB] = "Mitigation: IBPB", [SRSO_MITIGATION_IBPB_ON_VMEXIT] = "Mitigation: IBPB on VMEXIT only", - [SRSO_MITIGATION_BP_SPEC_REDUCE] = "Mitigation: Reduced Speculation" + [SRSO_MITIGATION_BP_SPEC_REDUCE] = "Mitigation: Reduced Speculation", + [SRSO_MITIGATION_USER_IBPB] = "Mitigation: IBPB on context switch", }; static int __init srso_parse_cmdline(char *str) @@ -2948,7 +2950,9 @@ static void __init srso_select_mitigation(void) * required. Otherwise the 'microcode' mitigation is sufficient * to protect the user->user and guest->guest vectors. */ - if (cpu_attack_vector_mitigated(CPU_MITIGATE_GUEST_HOST) || + if ((cpu_attack_vector_mitigated(CPU_MITIGATE_GUEST_HOST) && + !boot_cpu_has(X86_FEATURE_BTB_CTX_ISOLATION)) + || (cpu_attack_vector_mitigated(CPU_MITIGATE_USER_KERNEL) && !boot_cpu_has(X86_FEATURE_SRSO_USER_KERNEL_NO))) { srso_mitigation = SRSO_MITIGATION_SAFE_RET; @@ -3024,6 +3028,16 @@ static void __init srso_update_mitigation(void) boot_cpu_has(X86_FEATURE_IBPB_BRTYPE)) srso_mitigation = SRSO_MITIGATION_IBPB; + /* + * See if IBPB on context switch is the only thing needed to address + * GUEST/GUEST and USER/USER vectors. + */ + if (srso_mitigation == SRSO_MITIGATION_MICROCODE && + boot_cpu_has(X86_FEATURE_SRSO_USER_KERNEL_NO) && + boot_cpu_has(X86_FEATURE_BTB_CTX_ISOLATION) && + spectre_v2_user_ibpb != SPECTRE_V2_USER_NONE) + srso_mitigation = SRSO_MITIGATION_USER_IBPB; + pr_info("%s\n", srso_strings[srso_mitigation]); } diff --git a/arch/x86/kernel/cpu/scattered.c b/arch/x86/kernel/cpu/scattered.c index 8665a6474806c9..41b4880be7ed7c 100644 --- a/arch/x86/kernel/cpu/scattered.c +++ b/arch/x86/kernel/cpu/scattered.c @@ -64,6 +64,7 @@ static const struct cpuid_bit cpuid_bits[] = { { X86_FEATURE_AMD_WORKLOAD_CLASS, CPUID_EAX, 22, 0x80000021, 0 }, { X86_FEATURE_TSA_SQ_NO, CPUID_ECX, 1, 0x80000021, 0 }, { X86_FEATURE_TSA_L1_NO, CPUID_ECX, 2, 0x80000021, 0 }, + { X86_FEATURE_BTB_CTX_ISOLATION, CPUID_ECX, 8, 0x80000021, 0 }, { X86_FEATURE_PERFMON_V2, CPUID_EAX, 0, 0x80000022, 0 }, { X86_FEATURE_AMD_LBR_V2, CPUID_EAX, 1, 0x80000022, 0 }, { X86_FEATURE_AMD_LBR_PMC_FREEZE, CPUID_EAX, 2, 0x80000022, 0 }, From 3dd95f077371f5def65a91c4bb13bd574a62bc3a Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Fri, 7 Aug 2026 03:52:08 +0000 Subject: [PATCH 0086/2286] sched/core: Don't steal a proxy-exec donor try_steal_cookie() avoids stealing src->core_pick and src->curr before moving a task with the same cookie via move_queued_task_locked(). With proxy-exec, src->donor is the current scheduling context and may differ from src->curr. Stealing it migrates a task that the source rq still treats as current, leaving src's scheduler state for that task stale. For CFS this means cfs_rq->curr points at the stolen entity, and the next pick on the source rq hits the WARN_ON_ONCE in put_prev_entity(). Commit 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()") tweaked the fair class logic so that the donor task isn't migrated away while we're running the proxy. Do it similarly for try_steal_cookie() and skip src->donor as well. Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()") Signed-off-by: Vasily Gorbik Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260807035232.1881495-3-jstultz@google.com --- kernel/sched/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 74724501c62337..7adbdccc98c630 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6509,7 +6509,7 @@ static bool try_steal_cookie(int this, int that) return false; do { - if (p == src->core_pick || p == src->curr) + if (p == src->core_pick || p == src->curr || p == src->donor) goto next; if (!is_cpu_allowed(p, this)) From 9be817f991e2eba140669d350df2a07d81f26fc8 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 7 Aug 2026 03:52:09 +0000 Subject: [PATCH 0087/2286] sched/core: Avoid migrating blocked_on tasks Its not useful to migrate blocked_on tasks from other runqueues as the proxy logic will just migrate it back to the owner's rq. So skip blocked_on tasks here. Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260807035232.1881495-4-jstultz@google.com --- kernel/sched/core.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 7adbdccc98c630..0f06f8d6647d5b 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6512,6 +6512,9 @@ static bool try_steal_cookie(int this, int that) if (p == src->core_pick || p == src->curr || p == src->donor) goto next; + if (task_is_blocked(p)) + goto next; + if (!is_cpu_allowed(p, this)) goto next; From 09351db90a28ee6a82dc873609edfde02da5415d Mon Sep 17 00:00:00 2001 From: Vasily Gorbik Date: Fri, 7 Aug 2026 03:52:10 +0000 Subject: [PATCH 0088/2286] sched/core: Don't proxy-exec unmatched cookie lock owners Core scheduling chooses a core-wide cookie before __schedule() installs the next task. With proxy-exec enabled, that task becomes the donor/scheduling context, and find_proxy_task() may then replace the execution context with the runnable mutex owner. If its cookie differs from the selected core cookie, running it would bypass core scheduling's cookie selection. When the final mutex owner found by find_proxy_task() does not match the selected core cookie, stop proxying the donor. If the current execution context is already in the blocked chain, fall back to idle like the existing proxy-exec retry paths do. Otherwise deactivate the donor and let __schedule() pick again. The mutex owner can be picked later under its own cookie. Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()") [jstultz: Added tweak to ensure we deactivate donor, not runnable owner] Reported-by: K Prateek Nayak Signed-off-by: Vasily Gorbik Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/lkml/10282ce9-f4ae-498f-9b57-f4e1e61fffbc@amd.com/ Link: https://patch.msgid.link/20260807035232.1881495-5-jstultz@google.com --- kernel/sched/core.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0f06f8d6647d5b..0e6306df3ea9a2 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7046,6 +7046,14 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) owner->blocked_donor = p; } WARN_ON_ONCE(owner && !owner->on_rq); + + if (owner && !sched_cpu_cookie_match(rq, owner)) { + if (curr_in_chain) + return proxy_resched_idle(rq); + p = donor; /* Deactivate the donor, not the runnable owner */ + clear_task_blocked_on(p, NULL); + goto deactivate; + } return owner; deactivate: From 1f880513859336b1d4f9731d3a67d22d0b3b6aad Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 7 Aug 2026 03:52:11 +0000 Subject: [PATCH 0089/2286] sched: Switch rq->next_class in proxy_reset_donor() Similar to commit 04f80f8b12a0 ("sched: Switch rq->next_class on proxy_resched_idle()"), we should also set the rq->next_class when we call proxy_reset_donor(). Fixes: f13beb010e4a ("sched: Have try_to_wake_up() handle return-migration for PROXY_WAKING case") Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260807035232.1881495-6-jstultz@google.com --- kernel/sched/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0e6306df3ea9a2..f80b6078726083 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3766,6 +3766,7 @@ static inline void proxy_reset_donor(struct rq *rq) WARN_ON_ONCE(rq->donor == rq->curr); put_prev_set_next_task(rq, rq->donor, rq->curr); + rq->next_class = rq->curr->sched_class; rq_set_donor(rq, rq->curr); zap_balance_callbacks(rq); resched_curr(rq); From 6b73a09e943fe84778672362bfbd1cbb3fca9a8a Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 7 Aug 2026 03:52:12 +0000 Subject: [PATCH 0090/2286] sched: Break out core of attach_tasks() helper into sched.h Pull the core of attach_tasks() out into sched.h so it can be used more generically. Suggested-by: K Prateek Nayak Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260807035232.1881495-7-jstultz@google.com --- kernel/sched/fair.c | 16 +--------------- kernel/sched/sched.h | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index ade1eceb39b806..b8bd308c2d5b12 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -11194,21 +11194,7 @@ static int detach_tasks(struct lb_env *env) */ static void attach_tasks(struct lb_env *env) { - struct list_head *tasks = &env->tasks; - struct task_struct *p; - struct rq_flags rf; - - rq_lock(env->dst_rq, &rf); - update_rq_clock(env->dst_rq); - - while (!list_empty(tasks)) { - p = list_first_entry(tasks, struct task_struct, se.group_node); - list_del_init(&p->se.group_node); - - attach_task(env->dst_rq, p); - } - - rq_unlock(env->dst_rq, &rf); + __attach_tasks(env->dst_rq, &env->tasks); } #ifdef CONFIG_NO_HZ_COMMON diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index e656c7059bf864..81f17e62328f26 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -3139,6 +3139,25 @@ static inline void attach_one_task(struct rq *rq, struct task_struct *p) attach_task(rq, p); } +/* + * __attach_tasks() - attaches a list of tasks (using se.group_node) to + * the new rq + */ +static inline void __attach_tasks(struct rq *rq, struct list_head *tasks) +{ + guard(rq_lock)(rq); + update_rq_clock(rq); + + while (!list_empty(tasks)) { + struct task_struct *p; + + p = list_first_entry(tasks, struct task_struct, se.group_node); + list_del_init(&p->se.group_node); + + attach_task(rq, p); + } +} + #ifdef CONFIG_PREEMPT_RT # define SCHED_NR_MIGRATE_BREAK 8 #else From 772d9ffbfd2627779ed9b73155585e63c940bcec Mon Sep 17 00:00:00 2001 From: John Stultz Date: Fri, 7 Aug 2026 03:52:13 +0000 Subject: [PATCH 0091/2286] sched: Migrate whole chain in proxy_migrate_task() Instead of migrating one task each time through find_proxy_task(), we can walk up the blocked_donor ptrs and migrate the entire current chain in one go. This was broken out of earlier patches and held back while the series was being stabilized, but I wanted to re-introduce it. Signed-off-by: John Stultz Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260807035232.1881495-8-jstultz@google.com --- kernel/sched/core.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f80b6078726083..98410431caef9b 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6861,9 +6861,9 @@ static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf, __must_hold(__rq_lockp(rq)) { struct rq *target_rq = cpu_rq(target_cpu); + LIST_HEAD(migrate_list); lockdep_assert_rq_held(rq); - WARN_ON(p == rq->curr); /* * Since we are migrating a blocked donor, it could be rq->donor, * and we want to make sure there aren't any references from this @@ -6876,13 +6876,20 @@ static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf, * before we release the lock. */ proxy_resched_idle(rq); - - deactivate_task(rq, p, DEQUEUE_NOCLOCK); - proxy_set_task_cpu(p, target_cpu); - + for (; p; p = p->blocked_donor) { + WARN_ON(p == rq->curr); + deactivate_task(rq, p, DEQUEUE_NOCLOCK); + proxy_set_task_cpu(p, target_cpu); + /* + * We can re-use se.group_node to migrate the thing, + * because @p is deactivated (won't be balanced) and + * we hold the rq_lock. + */ + list_add(&p->se.group_node, &migrate_list); + } proxy_release_rq_lock(rq, rf); - attach_one_task(target_rq, p); + __attach_tasks(target_rq, &migrate_list); proxy_reacquire_rq_lock(rq, rf); } From d3d16750693b540299cd105968174556012aba09 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:26 +0100 Subject: [PATCH 0092/2286] sched: dynamic: Make PREEMPT_DYNAMIC depend on ARCH_HAS_PREEMPT_LAZY On architectures which select ARCH_HAS_PREEMPT_LAZY, it has not been possible to select the NONE and VOLUNTARY preemption models since commit: 7dadeaa6e851 ("sched: Further restrict the preemption modes") ... which was merged in v7.0. All architectures which currently support PREEMPT_DYNAMIC select ARCH_HAS_PREEMPT_LAZY: [mark@lakrids:~/src/linux]% git describe HEAD v7.2-rc1-1-g871a4586ea2e1 [mark@lakrids:~/src/linux]% git grep 'select HAVE_PREEMPT_DYNAMIC_' -- arch arch/arm64/Kconfig: select HAVE_PREEMPT_DYNAMIC_KEY arch/loongarch/Kconfig: select HAVE_PREEMPT_DYNAMIC_KEY arch/powerpc/Kconfig: select HAVE_PREEMPT_DYNAMIC_KEY arch/riscv/Kconfig: select HAVE_PREEMPT_DYNAMIC_KEY arch/s390/Kconfig: select HAVE_PREEMPT_DYNAMIC_KEY arch/x86/Kconfig: select HAVE_PREEMPT_DYNAMIC_CALL [mark@lakrids:~/src/linux]% git grep 'select ARCH_HAS_PREEMPT_LAZY' -- arch arch/arm64/Kconfig: select ARCH_HAS_PREEMPT_LAZY arch/loongarch/Kconfig: select ARCH_HAS_PREEMPT_LAZY arch/powerpc/Kconfig: select ARCH_HAS_PREEMPT_LAZY arch/riscv/Kconfig: select ARCH_HAS_PREEMPT_LAZY arch/s390/Kconfig: select ARCH_HAS_PREEMPT_LAZY arch/x86/Kconfig: select ARCH_HAS_PREEMPT_LAZY ... and hence in practice PREEMPT_DYNAMIC no longer supports the NONE or VOLUNTARY preemption models. Make this official: have PREEMPT_DYNAMIC depend on ARCH_HAS_PREEMPT_LAZY, and remove the trivially unreachable code. Further simplifications will be made in subsequent patches. Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Reviewed-by: Jinjie Ruan Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-2-mark.rutland@arm.com --- kernel/Kconfig.preempt | 1 + kernel/sched/core.c | 63 ++---------------------------------------- 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt index f294dad43bd713..68f321841c2e3c 100644 --- a/kernel/Kconfig.preempt +++ b/kernel/Kconfig.preempt @@ -133,6 +133,7 @@ config PREEMPTION config PREEMPT_DYNAMIC bool "Preemption behaviour defined on boot" depends on HAVE_PREEMPT_DYNAMIC + depends on ARCH_HAS_PREEMPT_LAZY select JUMP_LABEL if HAVE_PREEMPT_DYNAMIC_KEY select PREEMPT_BUILD default y if HAVE_PREEMPT_DYNAMIC_CALL diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 98410431caef9b..84e7685d19300c 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7931,20 +7931,10 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); * * * NONE: - * cond_resched <- __cond_resched - * might_resched <- RET0 - * preempt_schedule <- NOP - * preempt_schedule_notrace <- NOP - * irqentry_exit_cond_resched <- NOP - * dynamic_preempt_lazy <- false + * (unselectable) * * VOLUNTARY: - * cond_resched <- __cond_resched - * might_resched <- __cond_resched - * preempt_schedule <- NOP - * preempt_schedule_notrace <- NOP - * irqentry_exit_cond_resched <- NOP - * dynamic_preempt_lazy <- false + * (unselectable) * * FULL: * cond_resched <- RET0 @@ -7975,21 +7965,11 @@ int preempt_dynamic_mode = preempt_dynamic_undefined; int sched_dynamic_mode(const char *str) { -# if !(defined(CONFIG_PREEMPT_RT) || defined(CONFIG_ARCH_HAS_PREEMPT_LAZY)) - if (!strcmp(str, "none")) - return preempt_dynamic_none; - - if (!strcmp(str, "voluntary")) - return preempt_dynamic_voluntary; -# endif - if (!strcmp(str, "full")) return preempt_dynamic_full; -# ifdef CONFIG_ARCH_HAS_PREEMPT_LAZY if (!strcmp(str, "lazy")) return preempt_dynamic_lazy; -# endif return -EINVAL; } @@ -8011,40 +7991,7 @@ static DEFINE_MUTEX(sched_dynamic_mutex); static void __sched_dynamic_update(int mode) { - /* - * Avoid {NONE,VOLUNTARY} -> FULL transitions from ever ending up in - * the ZERO state, which is invalid. - */ - preempt_dynamic_enable(cond_resched); - preempt_dynamic_enable(might_resched); - preempt_dynamic_enable(preempt_schedule); - preempt_dynamic_enable(preempt_schedule_notrace); - preempt_dynamic_enable(irqentry_exit_cond_resched); - preempt_dynamic_key_disable(preempt_lazy); - switch (mode) { - case preempt_dynamic_none: - preempt_dynamic_enable(cond_resched); - preempt_dynamic_disable(might_resched); - preempt_dynamic_disable(preempt_schedule); - preempt_dynamic_disable(preempt_schedule_notrace); - preempt_dynamic_disable(irqentry_exit_cond_resched); - preempt_dynamic_key_disable(preempt_lazy); - if (mode != preempt_dynamic_mode) - pr_info("Dynamic Preempt: none\n"); - break; - - case preempt_dynamic_voluntary: - preempt_dynamic_enable(cond_resched); - preempt_dynamic_enable(might_resched); - preempt_dynamic_disable(preempt_schedule); - preempt_dynamic_disable(preempt_schedule_notrace); - preempt_dynamic_disable(irqentry_exit_cond_resched); - preempt_dynamic_key_disable(preempt_lazy); - if (mode != preempt_dynamic_mode) - pr_info("Dynamic Preempt: voluntary\n"); - break; - case preempt_dynamic_full: preempt_dynamic_disable(cond_resched); preempt_dynamic_disable(might_resched); @@ -8094,11 +8041,7 @@ __setup("preempt=", setup_preempt_mode); static void __init preempt_dynamic_init(void) { if (preempt_dynamic_mode == preempt_dynamic_undefined) { - if (IS_ENABLED(CONFIG_PREEMPT_NONE)) { - sched_dynamic_update(preempt_dynamic_none); - } else if (IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY)) { - sched_dynamic_update(preempt_dynamic_voluntary); - } else if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { + if (IS_ENABLED(CONFIG_PREEMPT_LAZY)) { sched_dynamic_update(preempt_dynamic_lazy); } else { /* Default static call setting, nothing to do */ From 88e0b3bb9930605ad7e5d3ddac7d9875325bebf0 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:27 +0100 Subject: [PATCH 0093/2286] sched: dynamic: Simplify {cond,might}_resched() PREEMPT_DYNAMIC is now limited to the FULL and LAZY preemption models. In either model, both cond_resched() and might_resched() are always disabled and do nothing. Remove the unnecessary code for these when PREEMPT_DYNAMIC is selected. Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Reviewed-by: Jinjie Ruan Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-3-mark.rutland@arm.com --- include/linux/kernel.h | 20 ------------------- include/linux/sched.h | 31 +++-------------------------- kernel/sched/core.c | 44 +----------------------------------------- 3 files changed, 4 insertions(+), 91 deletions(-) diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 24414c79e59abe..4b11d1dc0a6746 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -43,30 +43,10 @@ struct completion; struct user; #ifdef CONFIG_PREEMPT_VOLUNTARY_BUILD - extern int __cond_resched(void); # define might_resched() __cond_resched() - -#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) - -extern int __cond_resched(void); - -DECLARE_STATIC_CALL(might_resched, __cond_resched); - -static __always_inline void might_resched(void) -{ - static_call_mod(might_resched)(); -} - -#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) - -extern int dynamic_might_resched(void); -# define might_resched() dynamic_might_resched() - #else - # define might_resched() do { } while (0) - #endif /* CONFIG_PREEMPT_* */ #ifdef CONFIG_DEBUG_ATOMIC_SLEEP diff --git a/include/linux/sched.h b/include/linux/sched.h index 8b3d47a325cca9..d7cc77181ef98e 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2134,44 +2134,19 @@ static inline void set_need_resched_current(void) * value indicates whether a reschedule was done in fact. * cond_resched_lock() will drop the spinlock before scheduling, */ -#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) +#if !defined(CONFIG_PREEMPTION) extern int __cond_resched(void); -#if defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) - -DECLARE_STATIC_CALL(cond_resched, __cond_resched); - -static __always_inline int _cond_resched(void) -{ - return static_call_mod(cond_resched)(); -} - -#elif defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) - -extern int dynamic_cond_resched(void); - -static __always_inline int _cond_resched(void) -{ - return dynamic_cond_resched(); -} - -#else /* !CONFIG_PREEMPTION */ - static inline int _cond_resched(void) { return __cond_resched(); } - -#endif /* PREEMPT_DYNAMIC && CONFIG_HAVE_PREEMPT_DYNAMIC_CALL */ - -#else /* CONFIG_PREEMPTION && !CONFIG_PREEMPT_DYNAMIC */ - +#else static inline int _cond_resched(void) { return 0; } - -#endif /* !CONFIG_PREEMPTION || CONFIG_PREEMPT_DYNAMIC */ +#endif #define cond_resched() ({ \ __might_resched(__FILE__, __LINE__, 0); \ diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 84e7685d19300c..0060efcf5d8daa 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7794,7 +7794,7 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task) } #endif /* CONFIG_RT_MUTEXES */ -#if !defined(CONFIG_PREEMPTION) || defined(CONFIG_PREEMPT_DYNAMIC) +#if !defined(CONFIG_PREEMPTION) int __sched __cond_resched(void) { if (should_resched(0) && !irqs_disabled()) { @@ -7822,38 +7822,6 @@ int __sched __cond_resched(void) EXPORT_SYMBOL(__cond_resched); #endif -#ifdef CONFIG_PREEMPT_DYNAMIC -# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL -# define cond_resched_dynamic_enabled __cond_resched -# define cond_resched_dynamic_disabled ((void *)&__static_call_return0) -DEFINE_STATIC_CALL_RET0(cond_resched, __cond_resched); -EXPORT_STATIC_CALL_TRAMP(cond_resched); - -# define might_resched_dynamic_enabled __cond_resched -# define might_resched_dynamic_disabled ((void *)&__static_call_return0) -DEFINE_STATIC_CALL_RET0(might_resched, __cond_resched); -EXPORT_STATIC_CALL_TRAMP(might_resched); -# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -static DEFINE_STATIC_KEY_FALSE(sk_dynamic_cond_resched); -int __sched dynamic_cond_resched(void) -{ - if (!static_branch_unlikely(&sk_dynamic_cond_resched)) - return 0; - return __cond_resched(); -} -EXPORT_SYMBOL(dynamic_cond_resched); - -static DEFINE_STATIC_KEY_FALSE(sk_dynamic_might_resched); -int __sched dynamic_might_resched(void) -{ - if (!static_branch_unlikely(&sk_dynamic_might_resched)) - return 0; - return __cond_resched(); -} -EXPORT_SYMBOL(dynamic_might_resched); -# endif -#endif /* CONFIG_PREEMPT_DYNAMIC */ - /* * __cond_resched_lock() - if a reschedule is pending, drop the given lock, * call schedule, and on return reacquire the lock. @@ -7923,8 +7891,6 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); # endif /* - * SC:cond_resched - * SC:might_resched * SC:preempt_schedule * SC:preempt_schedule_notrace * SC:irqentry_exit_cond_resched @@ -7937,16 +7903,12 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); * (unselectable) * * FULL: - * cond_resched <- RET0 - * might_resched <- RET0 * preempt_schedule <- preempt_schedule * preempt_schedule_notrace <- preempt_schedule_notrace * irqentry_exit_cond_resched <- irqentry_exit_cond_resched * dynamic_preempt_lazy <- false * * LAZY: - * cond_resched <- RET0 - * might_resched <- RET0 * preempt_schedule <- preempt_schedule * preempt_schedule_notrace <- preempt_schedule_notrace * irqentry_exit_cond_resched <- irqentry_exit_cond_resched @@ -7993,8 +7955,6 @@ static void __sched_dynamic_update(int mode) { switch (mode) { case preempt_dynamic_full: - preempt_dynamic_disable(cond_resched); - preempt_dynamic_disable(might_resched); preempt_dynamic_enable(preempt_schedule); preempt_dynamic_enable(preempt_schedule_notrace); preempt_dynamic_enable(irqentry_exit_cond_resched); @@ -8004,8 +7964,6 @@ static void __sched_dynamic_update(int mode) break; case preempt_dynamic_lazy: - preempt_dynamic_disable(cond_resched); - preempt_dynamic_disable(might_resched); preempt_dynamic_enable(preempt_schedule); preempt_dynamic_enable(preempt_schedule_notrace); preempt_dynamic_enable(irqentry_exit_cond_resched); From b9d267b9d632feec9ddd1ce12078b7113f429a51 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:28 +0100 Subject: [PATCH 0094/2286] sched: dynamic: Simplify preempt_schedule{,_notrace}() PREEMPT_DYNAMIC is now limited to the FULL and LAZY preemption models. In either model, both preempt_schedule() and preempt_schedule_notrace() are always called and never disabled. Remove the unnecessary code for these when PREEMPT_DYNAMIC is selected. Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Reviewed-by: Jinjie Ruan Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-4-mark.rutland@arm.com --- arch/arm64/include/asm/preempt.h | 10 ------ arch/s390/include/asm/preempt.h | 11 ------- arch/x86/include/asm/preempt.h | 28 ----------------- include/asm-generic/preempt.h | 10 ------ kernel/sched/core.c | 52 -------------------------------- 5 files changed, 111 deletions(-) diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h index 9ecc2766a9f218..e86e850e9b17c2 100644 --- a/arch/arm64/include/asm/preempt.h +++ b/arch/arm64/include/asm/preempt.h @@ -104,19 +104,9 @@ static inline bool should_resched(int preempt_offset) void preempt_schedule(void); void preempt_schedule_notrace(void); -#ifdef CONFIG_PREEMPT_DYNAMIC - -void dynamic_preempt_schedule(void); -#define __preempt_schedule() dynamic_preempt_schedule() -void dynamic_preempt_schedule_notrace(void); -#define __preempt_schedule_notrace() dynamic_preempt_schedule_notrace() - -#else /* CONFIG_PREEMPT_DYNAMIC */ - #define __preempt_schedule() preempt_schedule() #define __preempt_schedule_notrace() preempt_schedule_notrace() -#endif /* CONFIG_PREEMPT_DYNAMIC */ #endif /* CONFIG_PREEMPTION */ #endif /* __ASM_PREEMPT_H */ diff --git a/arch/s390/include/asm/preempt.h b/arch/s390/include/asm/preempt.h index 5560d5fca2a30c..60c6f019ec4573 100644 --- a/arch/s390/include/asm/preempt.h +++ b/arch/s390/include/asm/preempt.h @@ -155,20 +155,9 @@ static __always_inline int __preempt_count_sub_return(int val) void preempt_schedule(void); void preempt_schedule_notrace(void); -#ifdef CONFIG_PREEMPT_DYNAMIC - -void dynamic_preempt_schedule(void); -void dynamic_preempt_schedule_notrace(void); -#define __preempt_schedule() dynamic_preempt_schedule() -#define __preempt_schedule_notrace() dynamic_preempt_schedule_notrace() - -#else /* CONFIG_PREEMPT_DYNAMIC */ - #define __preempt_schedule() preempt_schedule() #define __preempt_schedule_notrace() preempt_schedule_notrace() -#endif /* CONFIG_PREEMPT_DYNAMIC */ - #endif /* CONFIG_PREEMPTION */ #endif /* __ASM_PREEMPT_H */ diff --git a/arch/x86/include/asm/preempt.h b/arch/x86/include/asm/preempt.h index fafb6f8cdac3f1..d16d9c2f7b0866 100644 --- a/arch/x86/include/asm/preempt.h +++ b/arch/x86/include/asm/preempt.h @@ -137,43 +137,15 @@ static __always_inline bool should_resched(int preempt_offset) extern asmlinkage void preempt_schedule(void); extern asmlinkage void preempt_schedule_thunk(void); -#define preempt_schedule_dynamic_enabled preempt_schedule_thunk -#define preempt_schedule_dynamic_disabled NULL - extern asmlinkage void preempt_schedule_notrace(void); extern asmlinkage void preempt_schedule_notrace_thunk(void); -#define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace_thunk -#define preempt_schedule_notrace_dynamic_disabled NULL - -#ifdef CONFIG_PREEMPT_DYNAMIC - -DECLARE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled); - -#define __preempt_schedule() \ -do { \ - __STATIC_CALL_MOD_ADDRESSABLE(preempt_schedule); \ - asm volatile ("call " STATIC_CALL_TRAMP_STR(preempt_schedule) : ASM_CALL_CONSTRAINT); \ -} while (0) - -DECLARE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled); - -#define __preempt_schedule_notrace() \ -do { \ - __STATIC_CALL_MOD_ADDRESSABLE(preempt_schedule_notrace); \ - asm volatile ("call " STATIC_CALL_TRAMP_STR(preempt_schedule_notrace) : ASM_CALL_CONSTRAINT); \ -} while (0) - -#else /* PREEMPT_DYNAMIC */ - #define __preempt_schedule() \ asm volatile ("call preempt_schedule_thunk" : ASM_CALL_CONSTRAINT); #define __preempt_schedule_notrace() \ asm volatile ("call preempt_schedule_notrace_thunk" : ASM_CALL_CONSTRAINT); -#endif /* PREEMPT_DYNAMIC */ - #endif /* PREEMPTION */ #undef __pc_op diff --git a/include/asm-generic/preempt.h b/include/asm-generic/preempt.h index c8683c046615dd..1adddeab8545a1 100644 --- a/include/asm-generic/preempt.h +++ b/include/asm-generic/preempt.h @@ -96,19 +96,9 @@ static __always_inline bool should_resched(int preempt_offset) extern asmlinkage void preempt_schedule(void); extern asmlinkage void preempt_schedule_notrace(void); -#if defined(CONFIG_PREEMPT_DYNAMIC) && defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) - -void dynamic_preempt_schedule(void); -void dynamic_preempt_schedule_notrace(void); -#define __preempt_schedule() dynamic_preempt_schedule() -#define __preempt_schedule_notrace() dynamic_preempt_schedule_notrace() - -#else /* !CONFIG_PREEMPT_DYNAMIC || !CONFIG_HAVE_PREEMPT_DYNAMIC_KEY*/ - #define __preempt_schedule() preempt_schedule() #define __preempt_schedule_notrace() preempt_schedule_notrace() -#endif /* CONFIG_PREEMPT_DYNAMIC && CONFIG_HAVE_PREEMPT_DYNAMIC_KEY*/ #endif /* CONFIG_PREEMPTION */ #endif /* __ASM_PREEMPT_H */ diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0060efcf5d8daa..844f37465d624e 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7500,27 +7500,6 @@ asmlinkage __visible void __sched notrace preempt_schedule(void) NOKPROBE_SYMBOL(preempt_schedule); EXPORT_SYMBOL(preempt_schedule); -#ifdef CONFIG_PREEMPT_DYNAMIC -# ifdef CONFIG_HAVE_PREEMPT_DYNAMIC_CALL -# ifndef preempt_schedule_dynamic_enabled -# define preempt_schedule_dynamic_enabled preempt_schedule -# define preempt_schedule_dynamic_disabled NULL -# endif -DEFINE_STATIC_CALL(preempt_schedule, preempt_schedule_dynamic_enabled); -EXPORT_STATIC_CALL_TRAMP(preempt_schedule); -# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule); -void __sched notrace dynamic_preempt_schedule(void) -{ - if (!static_branch_unlikely(&sk_dynamic_preempt_schedule)) - return; - preempt_schedule(); -} -NOKPROBE_SYMBOL(dynamic_preempt_schedule); -EXPORT_SYMBOL(dynamic_preempt_schedule); -# endif -#endif /* CONFIG_PREEMPT_DYNAMIC */ - /** * preempt_schedule_notrace - preempt_schedule called by tracing * @@ -7573,27 +7552,6 @@ asmlinkage __visible void __sched notrace preempt_schedule_notrace(void) } EXPORT_SYMBOL_GPL(preempt_schedule_notrace); -#ifdef CONFIG_PREEMPT_DYNAMIC -# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) -# ifndef preempt_schedule_notrace_dynamic_enabled -# define preempt_schedule_notrace_dynamic_enabled preempt_schedule_notrace -# define preempt_schedule_notrace_dynamic_disabled NULL -# endif -DEFINE_STATIC_CALL(preempt_schedule_notrace, preempt_schedule_notrace_dynamic_enabled); -EXPORT_STATIC_CALL_TRAMP(preempt_schedule_notrace); -# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -static DEFINE_STATIC_KEY_TRUE(sk_dynamic_preempt_schedule_notrace); -void __sched notrace dynamic_preempt_schedule_notrace(void) -{ - if (!static_branch_unlikely(&sk_dynamic_preempt_schedule_notrace)) - return; - preempt_schedule_notrace(); -} -NOKPROBE_SYMBOL(dynamic_preempt_schedule_notrace); -EXPORT_SYMBOL(dynamic_preempt_schedule_notrace); -# endif -#endif - #endif /* CONFIG_PREEMPTION */ /* @@ -7891,8 +7849,6 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); # endif /* - * SC:preempt_schedule - * SC:preempt_schedule_notrace * SC:irqentry_exit_cond_resched * * @@ -7903,14 +7859,10 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); * (unselectable) * * FULL: - * preempt_schedule <- preempt_schedule - * preempt_schedule_notrace <- preempt_schedule_notrace * irqentry_exit_cond_resched <- irqentry_exit_cond_resched * dynamic_preempt_lazy <- false * * LAZY: - * preempt_schedule <- preempt_schedule - * preempt_schedule_notrace <- preempt_schedule_notrace * irqentry_exit_cond_resched <- irqentry_exit_cond_resched * dynamic_preempt_lazy <- true */ @@ -7955,8 +7907,6 @@ static void __sched_dynamic_update(int mode) { switch (mode) { case preempt_dynamic_full: - preempt_dynamic_enable(preempt_schedule); - preempt_dynamic_enable(preempt_schedule_notrace); preempt_dynamic_enable(irqentry_exit_cond_resched); preempt_dynamic_key_disable(preempt_lazy); if (mode != preempt_dynamic_mode) @@ -7964,8 +7914,6 @@ static void __sched_dynamic_update(int mode) break; case preempt_dynamic_lazy: - preempt_dynamic_enable(preempt_schedule); - preempt_dynamic_enable(preempt_schedule_notrace); preempt_dynamic_enable(irqentry_exit_cond_resched); preempt_dynamic_key_enable(preempt_lazy); if (mode != preempt_dynamic_mode) From aa4178f63847f86e3b27b9e53ee8ccbd6ed6bf8c Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:29 +0100 Subject: [PATCH 0095/2286] sched: dynamic: Simplify irqentry_exit_cond_resched() PREEMPT_DYNAMIC is now limited to the FULL and LAZY preemption models. In either model, irqentry_exit_cond_resched() is always called and never disabled. Remove the unnecessary code for this when PREEMPT_DYNAMIC is selected. Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Reviewed-by: Jinjie Ruan Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-5-mark.rutland@arm.com --- include/linux/irq-entry-common.h | 17 +---------------- kernel/entry/common.c | 17 ++--------------- kernel/sched/core.c | 7 ------- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/include/linux/irq-entry-common.h b/include/linux/irq-entry-common.h index 0bb6c03481fada..2be273bb68f00f 100644 --- a/include/linux/irq-entry-common.h +++ b/include/linux/irq-entry-common.h @@ -346,22 +346,7 @@ typedef struct irqentry_state { * * Conditional reschedule with additional sanity checks. */ -void raw_irqentry_exit_cond_resched(void); - -#ifdef CONFIG_PREEMPT_DYNAMIC -#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) -#define irqentry_exit_cond_resched_dynamic_enabled raw_irqentry_exit_cond_resched -#define irqentry_exit_cond_resched_dynamic_disabled NULL -DECLARE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched); -#define irqentry_exit_cond_resched() static_call(irqentry_exit_cond_resched)() -#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -DECLARE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched); -void dynamic_irqentry_exit_cond_resched(void); -#define irqentry_exit_cond_resched() dynamic_irqentry_exit_cond_resched() -#endif -#else /* CONFIG_PREEMPT_DYNAMIC */ -#define irqentry_exit_cond_resched() raw_irqentry_exit_cond_resched() -#endif /* CONFIG_PREEMPT_DYNAMIC */ +void irqentry_exit_cond_resched(void); /** * irqentry_enter_from_kernel_mode - Establish state before invoking the irq handler diff --git a/kernel/entry/common.c b/kernel/entry/common.c index e3d381fd3d2514..e234b04373feaf 100644 --- a/kernel/entry/common.c +++ b/kernel/entry/common.c @@ -123,7 +123,7 @@ noinstr irqentry_state_t irqentry_enter(struct pt_regs *regs) /** * arch_irqentry_exit_need_resched - Architecture specific need resched function * - * Invoked from raw_irqentry_exit_cond_resched() to check if resched is needed. + * Invoked from irqentry_exit_cond_resched() to check if resched is needed. * Defaults return true. * * The main purpose is to permit arch to avoid preemption of a task from an IRQ. @@ -134,7 +134,7 @@ static inline bool arch_irqentry_exit_need_resched(void); static inline bool arch_irqentry_exit_need_resched(void) { return true; } #endif -void raw_irqentry_exit_cond_resched(void) +void irqentry_exit_cond_resched(void) { if (!preempt_count()) { /* Sanity check RCU and thread stack */ @@ -145,19 +145,6 @@ void raw_irqentry_exit_cond_resched(void) preempt_schedule_irq(); } } -#ifdef CONFIG_PREEMPT_DYNAMIC -#if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) -DEFINE_STATIC_CALL(irqentry_exit_cond_resched, raw_irqentry_exit_cond_resched); -#elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -DEFINE_STATIC_KEY_TRUE(sk_dynamic_irqentry_exit_cond_resched); -void dynamic_irqentry_exit_cond_resched(void) -{ - if (!static_branch_unlikely(&sk_dynamic_irqentry_exit_cond_resched)) - return; - raw_irqentry_exit_cond_resched(); -} -#endif -#endif noinstr void irqentry_exit(struct pt_regs *regs, irqentry_state_t state) { diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 844f37465d624e..0e9022f2d05d4a 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7849,9 +7849,6 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); # endif /* - * SC:irqentry_exit_cond_resched - * - * * NONE: * (unselectable) * @@ -7859,11 +7856,9 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); * (unselectable) * * FULL: - * irqentry_exit_cond_resched <- irqentry_exit_cond_resched * dynamic_preempt_lazy <- false * * LAZY: - * irqentry_exit_cond_resched <- irqentry_exit_cond_resched * dynamic_preempt_lazy <- true */ @@ -7907,14 +7902,12 @@ static void __sched_dynamic_update(int mode) { switch (mode) { case preempt_dynamic_full: - preempt_dynamic_enable(irqentry_exit_cond_resched); preempt_dynamic_key_disable(preempt_lazy); if (mode != preempt_dynamic_mode) pr_info("Dynamic Preempt: full\n"); break; case preempt_dynamic_lazy: - preempt_dynamic_enable(irqentry_exit_cond_resched); preempt_dynamic_key_enable(preempt_lazy); if (mode != preempt_dynamic_mode) pr_info("Dynamic Preempt: lazy\n"); From 5b9a28eeed37513778ca6b636253aa8dcf39d55f Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:30 +0100 Subject: [PATCH 0096/2286] sched: dynamic: Remove HAVE_PREEMPT_DYNAMIC_{CALL,KEY} PREEMPT_DYNAMIC is now limited to the FULL and LAZY preemption models. Switching model only changes the behaviour of dynamic_preempt_lazy(), which uses a static key. There are no other static calls or static keys, and so there's no need for HAVE_PREEMPT_DYNAMIC_CALL or HAVE_PREEMPT_DYNAMIC_KEY. The static key used by dynamic_preempt_lazy() is entirely local to kernel/sched/core.c, and any architecture which selects ARCH_HAS_PREEMPT_LAZY implements the necessary support. Remove PREEMPT_DYNAMIC's dependencies on HAVE_PREEMPT_DYNAMIC_CALL and HAVE_PREEMPT_DYNAMIC_KEY entirely, leaving the dependency on ARCH_HAS_PREEMPT_LAZY. For architectures which previously selected HAVE_PREEMPT_DYNAMIC_KEY, PREEMPT_DYNAMIC will now be selected by default, matching x86. As the runtime impact is limited to dynamic_preempt_lazy(), this shouldn't be as concerning as previously (e.g. where calls to {cond,might}_resched() stubs could introduce a measurable penalty). As all of this can work without jump labels, JUMP_LABEL is not selected explicitly. It is obviously preferable to have JUMP_LABEL enabled, but this is not functionally necessary. Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jinjie Ruan Reviewed-by: Shrikanth Hegde Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-6-mark.rutland@arm.com --- arch/Kconfig | 38 -------------------------------------- arch/arm64/Kconfig | 1 - arch/loongarch/Kconfig | 1 - arch/powerpc/Kconfig | 1 - arch/riscv/Kconfig | 1 - arch/s390/Kconfig | 1 - arch/x86/Kconfig | 1 - kernel/Kconfig.preempt | 8 ++------ kernel/sched/core.c | 10 ---------- 9 files changed, 2 insertions(+), 60 deletions(-) diff --git a/arch/Kconfig b/arch/Kconfig index 45c65777236231..c0a6a13b06a1f5 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -1712,44 +1712,6 @@ config HAVE_STATIC_CALL_INLINE depends on HAVE_STATIC_CALL select OBJTOOL -config HAVE_PREEMPT_DYNAMIC - bool - -config HAVE_PREEMPT_DYNAMIC_CALL - bool - depends on HAVE_STATIC_CALL - select HAVE_PREEMPT_DYNAMIC - help - An architecture should select this if it can handle the preemption - model being selected at boot time using static calls. - - Where an architecture selects HAVE_STATIC_CALL_INLINE, any call to a - preemption function will be patched directly. - - Where an architecture does not select HAVE_STATIC_CALL_INLINE, any - call to a preemption function will go through a trampoline, and the - trampoline will be patched. - - It is strongly advised to support inline static call to avoid any - overhead. - -config HAVE_PREEMPT_DYNAMIC_KEY - bool - depends on HAVE_ARCH_JUMP_LABEL - select HAVE_PREEMPT_DYNAMIC - help - An architecture should select this if it can handle the preemption - model being selected at boot time using static keys. - - Each preemption function will be given an early return based on a - static key. This should have slightly lower overhead than non-inline - static calls, as this effectively inlines each trampoline into the - start of its callee. This may avoid redundant work, and may - integrate better with CFI schemes. - - This will have greater overhead than using inline static calls as - the call to the preemption function cannot be entirely elided. - config ARCH_WANT_LD_ORPHAN_WARN bool help diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..876ef299e16932 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -217,7 +217,6 @@ config ARM64 select HAVE_PERF_EVENTS_NMI if ARM64_PSEUDO_NMI select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP - select HAVE_PREEMPT_DYNAMIC_KEY select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RELIABLE_STACKTRACE select HAVE_POSIX_CPU_TIMERS_TASK_WORK diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index a21f51e5815e96..9480ccf73b2621 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -170,7 +170,6 @@ config LOONGARCH select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP select HAVE_POSIX_CPU_TIMERS_TASK_WORK - select HAVE_PREEMPT_DYNAMIC_KEY select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RELIABLE_STACKTRACE if UNWINDER_ORC select HAVE_RETHOOK diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 2580e27e432874..143381d267ce4e 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -279,7 +279,6 @@ config PPC select HAVE_PERF_EVENTS_NMI if PPC64 select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP - select HAVE_PREEMPT_DYNAMIC_KEY select HAVE_POSIX_CPU_TIMERS_TASK_WORK select HAVE_RETHOOK if KPROBES select HAVE_REGS_AND_STACK_ACCESS_API diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index f8e26c4bed2bae..66096a050f8862 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -195,7 +195,6 @@ config RISCV select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP select HAVE_POSIX_CPU_TIMERS_TASK_WORK - select HAVE_PREEMPT_DYNAMIC_KEY select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RETHOOK select HAVE_RSEQ diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 4b51bc6e8948d7..2682a2bacc5851 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -246,7 +246,6 @@ config S390 select HAVE_PERF_REGS select HAVE_PERF_USER_STACK_DUMP select HAVE_POSIX_CPU_TIMERS_TASK_WORK - select HAVE_PREEMPT_DYNAMIC_KEY select HAVE_REGS_AND_STACK_ACCESS_API select HAVE_RELIABLE_STACKTRACE select HAVE_RETHOOK diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 15fd9ec5ecacb7..5a84998b602184 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -297,7 +297,6 @@ config X86 select HAVE_STACK_VALIDATION if HAVE_OBJTOOL select HAVE_STATIC_CALL select HAVE_STATIC_CALL_INLINE if HAVE_OBJTOOL - select HAVE_PREEMPT_DYNAMIC_CALL select HAVE_RSEQ select HAVE_RUST if X86_64 select HAVE_SYSCALL_TRACEPOINTS diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt index 68f321841c2e3c..985aea617cfe72 100644 --- a/kernel/Kconfig.preempt +++ b/kernel/Kconfig.preempt @@ -132,11 +132,9 @@ config PREEMPTION config PREEMPT_DYNAMIC bool "Preemption behaviour defined on boot" - depends on HAVE_PREEMPT_DYNAMIC depends on ARCH_HAS_PREEMPT_LAZY - select JUMP_LABEL if HAVE_PREEMPT_DYNAMIC_KEY select PREEMPT_BUILD - default y if HAVE_PREEMPT_DYNAMIC_CALL + default y help This option allows to define the preemption model on the kernel command line parameter and thus override the default preemption @@ -146,9 +144,7 @@ config PREEMPT_DYNAMIC provide a pre-built kernel binary to reduce the number of kernel flavors they offer while still offering different usecases. - The runtime overhead is negligible with HAVE_STATIC_CALL_INLINE enabled - but if runtime patching is not available for the specific architecture - then the potential overhead should be considered. + The runtime overhead is negligible. Interesting if you want the same pre-built kernel should be used for both Server and Desktop workloads. diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0e9022f2d05d4a..05923200ef40ff 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7886,16 +7886,6 @@ int sched_dynamic_mode(const char *str) # define preempt_dynamic_key_enable(f) static_key_enable(&sk_dynamic_##f.key) # define preempt_dynamic_key_disable(f) static_key_disable(&sk_dynamic_##f.key) -# if defined(CONFIG_HAVE_PREEMPT_DYNAMIC_CALL) -# define preempt_dynamic_enable(f) static_call_update(f, f##_dynamic_enabled) -# define preempt_dynamic_disable(f) static_call_update(f, f##_dynamic_disabled) -# elif defined(CONFIG_HAVE_PREEMPT_DYNAMIC_KEY) -# define preempt_dynamic_enable(f) preempt_dynamic_key_enable(f) -# define preempt_dynamic_disable(f) preempt_dynamic_key_disable(f) -# else -# error "Unsupported PREEMPT_DYNAMIC mechanism" -# endif - static DEFINE_MUTEX(sched_dynamic_mutex); static void __sched_dynamic_update(int mode) From 9650ce11f2e377310332d87a902dc4df3f59b905 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Mon, 3 Aug 2026 20:17:31 +0100 Subject: [PATCH 0097/2286] sched: dynamic: Simplify preempt model accessors PREEMPT_DYNAMIC is now limited to the FULL and LAZY preemption models. When CONFIG_PREEMPT_DYNAMIC=y, the NONE and VOLUNTARY models cannot be used, and neither CONFIG_PREEMPT_NONE nor CONFIG_PREEMPT_VOLUNTARY can be selected. Simplify the preempt model accessors accordingly: remove the out-of-line implementations of preempt_model_none() and preempt_model_voluntary(), and remove the preempt_dynamic_{none,voluntary} values. There are no current users of preempt_model_voluntary(), but I've kept it around for consistency, with every scheduler model having its own preempt_model_*() accessor. Suggested-by: Shrikanth Hegde Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Jinjie Ruan Reviewed-by: Shrikanth Hegde Tested-by: Mete Durlu Tested-by: Shrikanth Hegde Tested-by: Jinjie Ruan Link: https://patch.msgid.link/20260803191731.3244294-7-mark.rutland@arm.com --- include/linux/preempt.h | 20 ++++++++++---------- kernel/sched/core.c | 4 ---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/include/linux/preempt.h b/include/linux/preempt.h index 8299657f0f8662..40310675a7cc45 100644 --- a/include/linux/preempt.h +++ b/include/linux/preempt.h @@ -502,21 +502,11 @@ DEFINE_LOCK_GUARD_0(preempt_notrace, preempt_disable_notrace(), preempt_enable_n #ifdef CONFIG_PREEMPT_DYNAMIC -extern bool preempt_model_none(void); -extern bool preempt_model_voluntary(void); extern bool preempt_model_full(void); extern bool preempt_model_lazy(void); #else -static inline bool preempt_model_none(void) -{ - return IS_ENABLED(CONFIG_PREEMPT_NONE); -} -static inline bool preempt_model_voluntary(void) -{ - return IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY); -} static inline bool preempt_model_full(void) { return IS_ENABLED(CONFIG_PREEMPT); @@ -529,6 +519,16 @@ static inline bool preempt_model_lazy(void) #endif +static inline bool preempt_model_none(void) +{ + return IS_ENABLED(CONFIG_PREEMPT_NONE); +} + +static inline bool preempt_model_voluntary(void) +{ + return IS_ENABLED(CONFIG_PREEMPT_VOLUNTARY); +} + static inline bool preempt_model_rt(void) { return IS_ENABLED(CONFIG_PREEMPT_RT); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 05923200ef40ff..c9dd6f4d42a429 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7864,8 +7864,6 @@ EXPORT_SYMBOL(__cond_resched_rwlock_write); enum { preempt_dynamic_undefined = -1, - preempt_dynamic_none, - preempt_dynamic_voluntary, preempt_dynamic_full, preempt_dynamic_lazy, }; @@ -7950,8 +7948,6 @@ static void __init preempt_dynamic_init(void) } \ EXPORT_SYMBOL_GPL(preempt_model_##mode) -PREEMPT_MODEL_ACCESSOR(none); -PREEMPT_MODEL_ACCESSOR(voluntary); PREEMPT_MODEL_ACCESSOR(full); PREEMPT_MODEL_ACCESSOR(lazy); From a5576ebce9201e2abc820348a494e666de8efe64 Mon Sep 17 00:00:00 2001 From: Hongyan Xia Date: Wed, 19 Aug 2026 08:12:09 +0000 Subject: [PATCH 0098/2286] sched: Convert paravirt_steal to new static key APIs paravirt_steal_rq_enabled and paravirt_steal_enabled use raw static_key APIs which are now deprecated. Use the new API instead. No functional change. Signed-off-by: Hongyan Xia Signed-off-by: Peter Zijlstra (Intel) Acked-by: Juergen Gross Link: https://patch.msgid.link/20260819081207.12150-1-hongyan.xia@transsion.com --- arch/arm64/kernel/paravirt.c | 4 ++-- arch/loongarch/kernel/paravirt.c | 4 ++-- arch/powerpc/platforms/pseries/setup.c | 4 ++-- arch/riscv/kernel/paravirt.c | 4 ++-- arch/x86/kernel/cpu/vmware.c | 4 ++-- arch/x86/kernel/kvm.c | 4 ++-- drivers/xen/time.c | 4 ++-- include/linux/sched/cputime.h | 6 +++--- kernel/sched/core.c | 4 ++-- kernel/sched/cputime.c | 4 ++-- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/arch/arm64/kernel/paravirt.c b/arch/arm64/kernel/paravirt.c index 572efb96b23fec..30bf61d031eb7e 100644 --- a/arch/arm64/kernel/paravirt.c +++ b/arch/arm64/kernel/paravirt.c @@ -157,9 +157,9 @@ int __init pv_time_init(void) static_call_update(pv_steal_clock, para_steal_clock); - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); pr_info("using stolen time PV\n"); diff --git a/arch/loongarch/kernel/paravirt.c b/arch/loongarch/kernel/paravirt.c index 10821cce554c0f..e8965a3f808241 100644 --- a/arch/loongarch/kernel/paravirt.c +++ b/arch/loongarch/kernel/paravirt.c @@ -308,10 +308,10 @@ int __init pv_time_init(void) static_call_update(pv_steal_clock, paravt_steal_clock); - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); #endif if (static_key_enabled(&virt_preempt_key)) diff --git a/arch/powerpc/platforms/pseries/setup.c b/arch/powerpc/platforms/pseries/setup.c index f29e7547c99566..c181df4dec7003 100644 --- a/arch/powerpc/platforms/pseries/setup.c +++ b/arch/powerpc/platforms/pseries/setup.c @@ -876,9 +876,9 @@ static void __init pSeries_setup_arch(void) static_branch_enable(&shared_processor); pv_spinlocks_init(); #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); #endif } diff --git a/arch/riscv/kernel/paravirt.c b/arch/riscv/kernel/paravirt.c index 5f56be79cd062a..9c13a6f1ea2afe 100644 --- a/arch/riscv/kernel/paravirt.c +++ b/arch/riscv/kernel/paravirt.c @@ -116,9 +116,9 @@ int __init pv_time_init(void) static_call_update(pv_steal_clock, pv_time_steal_clock); - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); pr_info("Computing paravirt steal-time\n"); diff --git a/arch/x86/kernel/cpu/vmware.c b/arch/x86/kernel/cpu/vmware.c index 34b73573b108d7..f7ab9e7902cfe5 100644 --- a/arch/x86/kernel/cpu/vmware.c +++ b/arch/x86/kernel/cpu/vmware.c @@ -328,9 +328,9 @@ static int vmware_cpu_down_prepare(unsigned int cpu) static __init int activate_jump_labels(void) { if (has_steal_clock) { - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); } return 0; diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index 6b0a5861ccb8eb..2ce01fdb8b4b9c 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -1054,9 +1054,9 @@ const __initconst struct hypervisor_x86 x86_hyper_kvm = { static __init int activate_jump_labels(void) { if (has_steal_clock) { - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (steal_acc) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); } return 0; diff --git a/drivers/xen/time.c b/drivers/xen/time.c index a2be0a4d45b0ba..a02d48a2aa684e 100644 --- a/drivers/xen/time.c +++ b/drivers/xen/time.c @@ -169,7 +169,7 @@ void __init xen_time_setup_guest(void) static_call_update(pv_steal_clock, xen_steal_clock); - static_key_slow_inc(¶virt_steal_enabled); + static_branch_inc(¶virt_steal_enabled); if (xen_runstate_remote) - static_key_slow_inc(¶virt_steal_rq_enabled); + static_branch_inc(¶virt_steal_rq_enabled); } diff --git a/include/linux/sched/cputime.h b/include/linux/sched/cputime.h index e90efaf6d26e90..694126411dfea6 100644 --- a/include/linux/sched/cputime.h +++ b/include/linux/sched/cputime.h @@ -182,9 +182,9 @@ extern unsigned long long task_sched_runtime(struct task_struct *task); #ifdef CONFIG_PARAVIRT -struct static_key; -extern struct static_key paravirt_steal_enabled; -extern struct static_key paravirt_steal_rq_enabled; +#include +DECLARE_STATIC_KEY_FALSE(paravirt_steal_enabled); +DECLARE_STATIC_KEY_FALSE(paravirt_steal_rq_enabled); #ifdef CONFIG_HAVE_PV_STEAL_CLOCK_GEN u64 dummy_steal_clock(int cpu); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index c9dd6f4d42a429..4e9776681d0ea8 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -805,7 +805,7 @@ struct rq *_task_rq_lock(struct task_struct *p, struct rq_flags *rf) /* Use CONFIG_PARAVIRT as this will avoid more #ifdef in arch code. */ #ifdef CONFIG_PARAVIRT -struct static_key paravirt_steal_rq_enabled; +DEFINE_STATIC_KEY_FALSE(paravirt_steal_rq_enabled); #endif static void update_rq_clock_task(struct rq *rq, s64 delta) @@ -844,7 +844,7 @@ static void update_rq_clock_task(struct rq *rq, s64 delta) } #endif #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING - if (static_key_false((¶virt_steal_rq_enabled))) { + if (static_branch_unlikely(¶virt_steal_rq_enabled)) { u64 prev_steal; steal = prev_steal = paravirt_steal_clock(cpu_of(rq)); diff --git a/kernel/sched/cputime.c b/kernel/sched/cputime.c index 06bddaa738e520..f16970ca81d08a 100644 --- a/kernel/sched/cputime.c +++ b/kernel/sched/cputime.c @@ -255,7 +255,7 @@ void __account_forceidle_time(struct task_struct *p, u64 delta) * occasion account more time than the calling functions think elapsed. */ #ifdef CONFIG_PARAVIRT -struct static_key paravirt_steal_enabled; +DEFINE_STATIC_KEY_FALSE(paravirt_steal_enabled); #ifdef CONFIG_HAVE_PV_STEAL_CLOCK_GEN static u64 native_steal_clock(int cpu) @@ -270,7 +270,7 @@ DEFINE_STATIC_CALL(pv_steal_clock, native_steal_clock); static __always_inline u64 steal_account_process_time(u64 maxtime) { #ifdef CONFIG_PARAVIRT - if (static_key_false(¶virt_steal_enabled)) { + if (static_branch_unlikely(¶virt_steal_enabled)) { u64 steal; steal = paravirt_steal_clock(smp_processor_id()); From 2a672daa4b272d092e193eb40376b4a8063e25fa Mon Sep 17 00:00:00 2001 From: Hongyan Xia Date: Wed, 19 Aug 2026 08:13:14 +0000 Subject: [PATCH 0099/2286] sched/feat: Use the new static key API for sched_feat Both raw static keys and static_key_true/false() have been deprecated. Migrate to new API. It's a bit tricky to convert the sched_feat array to the new API because the new one has two different types for true and false keys. Use a union to wrap it. No functional change. Signed-off-by: Hongyan Xia Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260819081312.12447-1-hongyan.xia@transsion.com --- kernel/sched/debug.c | 10 +++++----- kernel/sched/sched.h | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index 72236db67983b3..9883e4c4976bf2 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -73,13 +73,13 @@ static int sched_feat_show(struct seq_file *m, void *v) #ifdef CONFIG_JUMP_LABEL -#define jump_label_key__true STATIC_KEY_INIT_TRUE -#define jump_label_key__false STATIC_KEY_INIT_FALSE +#define jump_label_key__true { .key_true = STATIC_KEY_TRUE_INIT } +#define jump_label_key__false { .key_false = STATIC_KEY_FALSE_INIT } #define SCHED_FEAT(name, enabled) \ jump_label_key__##enabled , -struct static_key sched_feat_keys[__SCHED_FEAT_NR] = { +union sched_feat_key sched_feat_keys[__SCHED_FEAT_NR] = { #include "features.h" }; @@ -87,12 +87,12 @@ struct static_key sched_feat_keys[__SCHED_FEAT_NR] = { static void sched_feat_disable(int i) { - static_key_disable_cpuslocked(&sched_feat_keys[i]); + static_branch_disable_cpuslocked(&sched_feat_keys[i].key_true); } static void sched_feat_enable(int i) { - static_key_enable_cpuslocked(&sched_feat_keys[i]); + static_branch_enable_cpuslocked(&sched_feat_keys[i].key_false); } #else /* !CONFIG_JUMP_LABEL: */ static void sched_feat_disable(int i) { }; diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 81f17e62328f26..6c3ad70e58b8e1 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2447,16 +2447,25 @@ extern __read_mostly unsigned int sysctl_sched_features; #ifdef CONFIG_JUMP_LABEL -#define SCHED_FEAT(name, enabled) \ -static __always_inline bool static_branch_##name(struct static_key *key) \ -{ \ - return static_key_##enabled(key); \ +union sched_feat_key { + struct static_key_true key_true; + struct static_key_false key_false; +}; + +#define sched_feat_branch_true(key) static_branch_likely(&(key)->key_true) +#define sched_feat_branch_false(key) static_branch_unlikely(&(key)->key_false) + +#define SCHED_FEAT(name, enabled) \ +static __always_inline bool \ +static_branch_##name(union sched_feat_key *key) \ +{ \ + return sched_feat_branch_##enabled(key); \ } #include "features.h" #undef SCHED_FEAT -extern struct static_key sched_feat_keys[__SCHED_FEAT_NR]; +extern union sched_feat_key sched_feat_keys[__SCHED_FEAT_NR]; #define sched_feat(x) (static_branch_##x(&sched_feat_keys[__SCHED_FEAT_##x])) #else /* !CONFIG_JUMP_LABEL: */ From f549101187c875c2016072cadcb909d228532ae8 Mon Sep 17 00:00:00 2001 From: Liang Hao Date: Sun, 16 Aug 2026 11:26:43 +0800 Subject: [PATCH 0100/2286] sched/deadline: check start_dl_timer expiry with ktime_before() Replace ktime_us_delta() < 0 with ktime_before() so the past-expiry test uses the same resolution as act/now. Signed-off-by: Liang Hao Signed-off-by: Peter Zijlstra (Intel) Acked-by: Juri Lelli Link: https://patch.msgid.link/20260816032643.44969-1-haohlliang@gmail.com --- kernel/sched/deadline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index 0663c00c41c042..de6a361a87c77c 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -1097,7 +1097,7 @@ static int start_dl_timer(struct sched_dl_entity *dl_se) * chosen as the deadline is too small, don't even try to * start the timer in the past! */ - if (ktime_us_delta(act, now) < 0) + if (ktime_before(act, now)) return 0; /* From 879eaa76e6086ab4aff77a669bfb50b7db03e0b4 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Wed, 19 Aug 2026 11:01:58 +0300 Subject: [PATCH 0101/2286] sched: Remove unneeded function type cast in do_balance_callbacks() It's a trivial and non-functional change, the removed explicit function type cast does not convert the function type of struct balance_callback member (*func), and the cast can be removed. Signed-off-by: Vladimir Zapolskiy Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260819080159.105433-1-vz@kernel.org --- kernel/sched/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 4e9776681d0ea8..545f49491bcde5 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5143,7 +5143,7 @@ static void do_balance_callbacks(struct rq *rq, struct balance_callback *head) lockdep_assert_rq_held(rq); while (head) { - func = (void (*)(struct rq *))head->func; + func = head->func; next = head->next; head->next = NULL; head = next; From 44e33a5aaa2de3f5cf2701d6f4b0a7c63d4fe602 Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Tue, 1 Sep 2026 18:48:03 +0100 Subject: [PATCH 0102/2286] firmware: Rename google firmware directory to coreboot Most drivers in drivers/firmware/google are coreboot table drivers rather than Google-specific firmware drivers. Rename the directory to coreboot and update the Kconfig prompt, build path and maintainer entry. Rename the generic firmware Kconfig symbols to CONFIG_COREBOOT_* at the same time so new configurations describe the firmware interface rather than one vendor. Keep the old CONFIG_GOOGLE_* names as transitional symbols so existing configurations keep selecting the same drivers during the rename. Leave defconfig updates to a separate patch so the configuration churn is easy to review independently. Suggested-by: Julius Werner Signed-off-by: Sean Rhodes Link: https://lore.kernel.org/r/0a81e0d8e15c13f6a65c45ce476e37b33564ad61.1788284852.git.sean@starlabs.systems Signed-off-by: Tzung-Bi Shih --- MAINTAINERS | 20 ++--- drivers/firmware/Kconfig | 2 +- drivers/firmware/Makefile | 2 +- drivers/firmware/{google => coreboot}/Kconfig | 74 ++++++++++++++----- .../firmware/{google => coreboot}/Makefile | 10 +-- drivers/firmware/{google => coreboot}/cbmem.c | 0 .../{google => coreboot}/coreboot_table.c | 0 .../{google => coreboot}/coreboot_table.h | 0 .../framebuffer-coreboot.c | 0 drivers/firmware/{google => coreboot}/gsmi.c | 0 .../memconsole-coreboot.c | 0 .../memconsole-x86-legacy.c | 0 .../{google => coreboot}/memconsole.c | 0 .../{google => coreboot}/memconsole.h | 6 +- drivers/firmware/{google => coreboot}/vpd.c | 0 .../{google => coreboot}/vpd_decode.c | 0 .../{google => coreboot}/vpd_decode.h | 0 17 files changed, 76 insertions(+), 38 deletions(-) rename drivers/firmware/{google => coreboot}/Kconfig (64%) rename drivers/firmware/{google => coreboot}/Makefile (51%) rename drivers/firmware/{google => coreboot}/cbmem.c (100%) rename drivers/firmware/{google => coreboot}/coreboot_table.c (100%) rename drivers/firmware/{google => coreboot}/coreboot_table.h (100%) rename drivers/firmware/{google => coreboot}/framebuffer-coreboot.c (100%) rename drivers/firmware/{google => coreboot}/gsmi.c (100%) rename drivers/firmware/{google => coreboot}/memconsole-coreboot.c (100%) rename drivers/firmware/{google => coreboot}/memconsole-x86-legacy.c (100%) rename drivers/firmware/{google => coreboot}/memconsole.c (100%) rename drivers/firmware/{google => coreboot}/memconsole.h (82%) rename drivers/firmware/{google => coreboot}/vpd.c (100%) rename drivers/firmware/{google => coreboot}/vpd_decode.c (100%) rename drivers/firmware/{google => coreboot}/vpd_decode.h (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..8ba5c6b0d54336 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6716,6 +6716,16 @@ F: tools/testing/selftests/cgroup/test_hugetlb_memcg.c F: tools/testing/selftests/cgroup/test_kmem.c F: tools/testing/selftests/cgroup/test_memcontrol.c +COREBOOT FIRMWARE DRIVERS +M: Tzung-Bi Shih +R: Brian Norris +R: Julius Werner +L: chrome-platform@lists.linux.dev +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git +F: drivers/firmware/coreboot/ +F: include/linux/coreboot.h + CORETEMP HARDWARE MONITORING DRIVER L: linux-hwmon@vger.kernel.org S: Orphan @@ -11139,16 +11149,6 @@ S: Maintained F: Documentation/networking/device_drivers/ethernet/google/gve.rst F: drivers/net/ethernet/google -GOOGLE FIRMWARE DRIVERS -M: Tzung-Bi Shih -R: Brian Norris -R: Julius Werner -L: chrome-platform@lists.linux.dev -S: Maintained -T: git git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux.git -F: drivers/firmware/google/ -F: include/linux/coreboot.h - GOOGLE TENSOR SoC SUPPORT M: Peter Griffin R: André Draszik diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index b7cc11e4fbfa65..e07a7ce5c3cc83 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -299,7 +299,7 @@ endif # TURRIS_MOX_RWTM source "drivers/firmware/arm_ffa/Kconfig" source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/cirrus/Kconfig" -source "drivers/firmware/google/Kconfig" +source "drivers/firmware/coreboot/Kconfig" source "drivers/firmware/efi/Kconfig" source "drivers/firmware/imx/Kconfig" source "drivers/firmware/meson/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index be46f1e1dc77fa..a37008dfcde5b7 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -30,7 +30,7 @@ obj-y += broadcom/ obj-y += cirrus/ obj-y += meson/ obj-y += microchip/ -obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ +obj-$(CONFIG_COREBOOT_FIRMWARE) += coreboot/ obj-y += efi/ obj-y += imx/ obj-y += psci/ diff --git a/drivers/firmware/google/Kconfig b/drivers/firmware/coreboot/Kconfig similarity index 64% rename from drivers/firmware/google/Kconfig rename to drivers/firmware/coreboot/Kconfig index b78c644fa2539a..d273bd836245eb 100644 --- a/drivers/firmware/google/Kconfig +++ b/drivers/firmware/coreboot/Kconfig @@ -1,13 +1,16 @@ # SPDX-License-Identifier: GPL-2.0-only -menuconfig GOOGLE_FIRMWARE - bool "Google Firmware Drivers" +menuconfig COREBOOT_FIRMWARE + bool "coreboot firmware drivers" + default GOOGLE_FIRMWARE default n help - These firmware drivers are used by Google servers, - Chromebooks and other devices using coreboot firmware. + These firmware drivers are used by devices running coreboot + firmware, including Chromebooks and some Google servers. + They include shared support for coreboot table entries and + drivers for CBMEM, framebuffer, firmware console and VPD records. If in doubt, say "N". -if GOOGLE_FIRMWARE +if COREBOOT_FIRMWARE config GOOGLE_SMI tristate "SMI interface for Google platforms" @@ -19,9 +22,10 @@ config GOOGLE_SMI driver provides an interface for reading and writing NVRAM variables. -config GOOGLE_CBMEM +config COREBOOT_CBMEM tristate "CBMEM entries in sysfs" - depends on GOOGLE_COREBOOT_TABLE + depends on COREBOOT_TABLE + default GOOGLE_CBMEM help CBMEM is a downwards-growing memory region created by the Coreboot BIOS containing tagged data structures from the @@ -34,9 +38,10 @@ config GOOGLE_CBMEM memory for each entry in sysfs under /sys/bus/coreboot/devices/cbmem-. -config GOOGLE_COREBOOT_TABLE +config COREBOOT_TABLE tristate "Coreboot Table Access" depends on HAS_IOMEM && (ACPI || OF) + default GOOGLE_COREBOOT_TABLE help This option enables the coreboot_table module, which provides other firmware modules access to the coreboot table. The coreboot table @@ -44,42 +49,75 @@ config GOOGLE_COREBOOT_TABLE device tree node /firmware/coreboot. If unsure say N. -config GOOGLE_MEMCONSOLE +config COREBOOT_MEMCONSOLE_COMMON tristate - depends on GOOGLE_MEMCONSOLE_X86_LEGACY || GOOGLE_MEMCONSOLE_COREBOOT + depends on GOOGLE_MEMCONSOLE_X86_LEGACY || COREBOOT_MEMCONSOLE + default GOOGLE_MEMCONSOLE config GOOGLE_MEMCONSOLE_X86_LEGACY tristate "Firmware Memory Console - X86 Legacy support" depends on X86 && ACPI && DMI - select GOOGLE_MEMCONSOLE + select COREBOOT_MEMCONSOLE_COMMON help This option enables the kernel to search for a firmware log in the EBDA on Google servers. If found, this log is exported to userland in the file /sys/firmware/log. + This driver is limited to x86 systems that expose the legacy + Google EBDA console format. -config GOOGLE_FRAMEBUFFER_COREBOOT +config COREBOOT_FRAMEBUFFER tristate "Coreboot Framebuffer" - depends on GOOGLE_COREBOOT_TABLE + depends on COREBOOT_TABLE + default GOOGLE_FRAMEBUFFER_COREBOOT help This option enables the kernel to search for a framebuffer in the coreboot table. If found, it is registered with a platform device of type coreboot-framebuffer. Using the old device of type simple-framebuffer is deprecated. -config GOOGLE_MEMCONSOLE_COREBOOT +config COREBOOT_MEMCONSOLE tristate "Firmware Memory Console" - depends on GOOGLE_COREBOOT_TABLE - select GOOGLE_MEMCONSOLE + depends on COREBOOT_TABLE + select COREBOOT_MEMCONSOLE_COMMON + default GOOGLE_MEMCONSOLE_COREBOOT help This option enables the kernel to search for a firmware log in the coreboot table. If found, this log is exported to userland in the file /sys/firmware/log. + This is the coreboot-table backed variant of the firmware memory + console driver. config GOOGLE_VPD tristate "Vital Product Data" - depends on GOOGLE_COREBOOT_TABLE + depends on COREBOOT_TABLE help This option enables the kernel to expose the content of Google VPD under /sys/firmware/vpd. + VPD records carry firmware-provided key/value data used by + Google devices. + +endif # COREBOOT_FIRMWARE -endif # GOOGLE_FIRMWARE +config GOOGLE_FIRMWARE + bool + transitional + +config GOOGLE_CBMEM + tristate + transitional + +config GOOGLE_COREBOOT_TABLE + tristate + transitional + +config GOOGLE_MEMCONSOLE + tristate + transitional + +config GOOGLE_FRAMEBUFFER_COREBOOT + tristate + transitional + +config GOOGLE_MEMCONSOLE_COREBOOT + tristate + transitional diff --git a/drivers/firmware/google/Makefile b/drivers/firmware/coreboot/Makefile similarity index 51% rename from drivers/firmware/google/Makefile rename to drivers/firmware/coreboot/Makefile index 8151e323cc4346..e681f274417abb 100644 --- a/drivers/firmware/google/Makefile +++ b/drivers/firmware/coreboot/Makefile @@ -1,14 +1,14 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_GOOGLE_SMI) += gsmi.o -obj-$(CONFIG_GOOGLE_COREBOOT_TABLE) += coreboot_table.o -obj-$(CONFIG_GOOGLE_FRAMEBUFFER_COREBOOT) += framebuffer-coreboot.o -obj-$(CONFIG_GOOGLE_MEMCONSOLE) += memconsole.o -obj-$(CONFIG_GOOGLE_MEMCONSOLE_COREBOOT) += memconsole-coreboot.o +obj-$(CONFIG_COREBOOT_TABLE) += coreboot_table.o +obj-$(CONFIG_COREBOOT_FRAMEBUFFER) += framebuffer-coreboot.o +obj-$(CONFIG_COREBOOT_MEMCONSOLE_COMMON) += memconsole.o +obj-$(CONFIG_COREBOOT_MEMCONSOLE) += memconsole-coreboot.o obj-$(CONFIG_GOOGLE_MEMCONSOLE_X86_LEGACY) += memconsole-x86-legacy.o # Must come after coreboot_table.o, as this driver depends on that bus type. -obj-$(CONFIG_GOOGLE_CBMEM) += cbmem.o +obj-$(CONFIG_COREBOOT_CBMEM) += cbmem.o vpd-sysfs-y := vpd.o vpd_decode.o obj-$(CONFIG_GOOGLE_VPD) += vpd-sysfs.o diff --git a/drivers/firmware/google/cbmem.c b/drivers/firmware/coreboot/cbmem.c similarity index 100% rename from drivers/firmware/google/cbmem.c rename to drivers/firmware/coreboot/cbmem.c diff --git a/drivers/firmware/google/coreboot_table.c b/drivers/firmware/coreboot/coreboot_table.c similarity index 100% rename from drivers/firmware/google/coreboot_table.c rename to drivers/firmware/coreboot/coreboot_table.c diff --git a/drivers/firmware/google/coreboot_table.h b/drivers/firmware/coreboot/coreboot_table.h similarity index 100% rename from drivers/firmware/google/coreboot_table.h rename to drivers/firmware/coreboot/coreboot_table.h diff --git a/drivers/firmware/google/framebuffer-coreboot.c b/drivers/firmware/coreboot/framebuffer-coreboot.c similarity index 100% rename from drivers/firmware/google/framebuffer-coreboot.c rename to drivers/firmware/coreboot/framebuffer-coreboot.c diff --git a/drivers/firmware/google/gsmi.c b/drivers/firmware/coreboot/gsmi.c similarity index 100% rename from drivers/firmware/google/gsmi.c rename to drivers/firmware/coreboot/gsmi.c diff --git a/drivers/firmware/google/memconsole-coreboot.c b/drivers/firmware/coreboot/memconsole-coreboot.c similarity index 100% rename from drivers/firmware/google/memconsole-coreboot.c rename to drivers/firmware/coreboot/memconsole-coreboot.c diff --git a/drivers/firmware/google/memconsole-x86-legacy.c b/drivers/firmware/coreboot/memconsole-x86-legacy.c similarity index 100% rename from drivers/firmware/google/memconsole-x86-legacy.c rename to drivers/firmware/coreboot/memconsole-x86-legacy.c diff --git a/drivers/firmware/google/memconsole.c b/drivers/firmware/coreboot/memconsole.c similarity index 100% rename from drivers/firmware/google/memconsole.c rename to drivers/firmware/coreboot/memconsole.c diff --git a/drivers/firmware/google/memconsole.h b/drivers/firmware/coreboot/memconsole.h similarity index 82% rename from drivers/firmware/google/memconsole.h rename to drivers/firmware/coreboot/memconsole.h index aaff2b72b606e4..58899c2abaaaa6 100644 --- a/drivers/firmware/google/memconsole.h +++ b/drivers/firmware/coreboot/memconsole.h @@ -7,8 +7,8 @@ * Copyright 2017 Google Inc. */ -#ifndef __FIRMWARE_GOOGLE_MEMCONSOLE_H -#define __FIRMWARE_GOOGLE_MEMCONSOLE_H +#ifndef __FIRMWARE_COREBOOT_MEMCONSOLE_H +#define __FIRMWARE_COREBOOT_MEMCONSOLE_H #include @@ -33,4 +33,4 @@ int memconsole_sysfs_init(void); */ void memconsole_exit(void); -#endif /* __FIRMWARE_GOOGLE_MEMCONSOLE_H */ +#endif /* __FIRMWARE_COREBOOT_MEMCONSOLE_H */ diff --git a/drivers/firmware/google/vpd.c b/drivers/firmware/coreboot/vpd.c similarity index 100% rename from drivers/firmware/google/vpd.c rename to drivers/firmware/coreboot/vpd.c diff --git a/drivers/firmware/google/vpd_decode.c b/drivers/firmware/coreboot/vpd_decode.c similarity index 100% rename from drivers/firmware/google/vpd_decode.c rename to drivers/firmware/coreboot/vpd_decode.c diff --git a/drivers/firmware/google/vpd_decode.h b/drivers/firmware/coreboot/vpd_decode.h similarity index 100% rename from drivers/firmware/google/vpd_decode.h rename to drivers/firmware/coreboot/vpd_decode.h From ef9293b3b797228fead10b55ed6bfb99bb7976b4 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 2 Sep 2026 11:16:37 +0100 Subject: [PATCH 0103/2286] sched: dynamic: Fix preemption model strings We recently removed the enum values 'preempt_dynamic_none' and 'preempt_dynamic_voluntary', but forgot to update preempt_modes[] accordingly. Due to this, preempt_model_str() and sched_dynamic_show() produce incorrect output, with the wrong preemption model string in backtraces, and empty output for /sys/kernel/debug/sched/preempt. Update preempt_modes[] to account for the removed enum values. Given that CONFIG_PREEMPT_DYNAMIC now depends on CONFIG_ARCH_HAS_PREEMPT_LAZY, and given that only 'preempt_dynamic_full' and 'preempt_dynamic_lazy' remain, sched_dynamic_show() no longer needs to skip any model strings. Fixes: 9650ce11f2e3 ("sched: dynamic: Simplify preempt model accessors") Reported-by: Jinjie Ruan Signed-off-by: Mark Rutland Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260902101637.232129-1-mark.rutland@arm.com --- kernel/sched/core.c | 2 +- kernel/sched/debug.c | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 545f49491bcde5..5a61650f12b7d5 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7960,7 +7960,7 @@ static inline void preempt_dynamic_init(void) { } #endif /* CONFIG_PREEMPT_DYNAMIC */ const char *preempt_modes[] = { - "none", "voluntary", "full", "lazy", NULL, + "full", "lazy", NULL, }; const char *preempt_model_str(void) diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index 9883e4c4976bf2..fda10b3fa249a9 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -280,16 +280,10 @@ static ssize_t sched_dynamic_write(struct file *filp, const char __user *ubuf, static int sched_dynamic_show(struct seq_file *m, void *v) { - int i = (IS_ENABLED(CONFIG_PREEMPT_RT) || IS_ENABLED(CONFIG_ARCH_HAS_PREEMPT_LAZY)) * 2; int mode = READ_ONCE(preempt_dynamic_mode); - int j; - /* Count entries in NULL terminated preempt_modes */ - for (j = 0; preempt_modes[j]; j++) - ; - j -= !IS_ENABLED(CONFIG_ARCH_HAS_PREEMPT_LAZY); - - for (; i < j; i++) { + /* Stop at NULL terminator */ + for (int i = 0; preempt_modes[i]; i++) { if (mode == i) seq_puts(m, "("); seq_puts(m, preempt_modes[i]); From 9106892e27cad34c87ab1c5631d9c101237bbee1 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:09 +0800 Subject: [PATCH 0104/2286] perf/x86: Move hybrid PMU initialization before x86_pmu_starting_cpu() The current approach initializes hybrid PMU structures immediately before registering them. This is risky as it can lead to key fields, such as 'capabilities', being inadvertently overwritten. Although no issues have arisen so far, this method is not ideal. It makes the PMU structure fields susceptible to being overwritten, especially with future changes that might initialize fields like 'capabilities' within init_hybrid_pmu() called by x86_pmu_starting_cpu(). To mitigate this potential problem, move the default hybrid structure initialization before calling x86_pmu_starting_cpu(). Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-2-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 8b3ea0adb9655c..75358a29722295 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -2218,8 +2218,20 @@ static int __init init_hw_perf_events(void) pmu.attr_update = x86_pmu.attr_update; - if (!is_hybrid()) + if (!is_hybrid()) { x86_pmu_show_pmu_cap(NULL); + } else { + int i; + + /* + * Init default ops. + * Must be called before registering x86_pmu_starting_cpu(), + * otherwise some key PMU fields, e.g., capabilities + * initialized in x86_pmu_starting_cpu(), would be overwritten. + */ + for (i = 0; i < x86_pmu.num_hybrid_pmus; i++) + x86_pmu.hybrid_pmu[i].pmu = pmu; + } if (!x86_pmu.read) x86_pmu.read = _x86_pmu_read; @@ -2266,7 +2278,6 @@ static int __init init_hw_perf_events(void) for (i = 0; i < x86_pmu.num_hybrid_pmus; i++) { hybrid_pmu = &x86_pmu.hybrid_pmu[i]; - hybrid_pmu->pmu = pmu; hybrid_pmu->pmu.type = -1; hybrid_pmu->pmu.attr_update = x86_pmu.attr_update; hybrid_pmu->pmu.capabilities |= PERF_PMU_CAP_EXTENDED_HW_TYPE; From 62e074f55f4162e1898e7c1807f1501226a9c393 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:10 +0800 Subject: [PATCH 0105/2286] perf/x86/intel: Enable large PEBS sampling for XMMs Modern PEBS hardware supports directly sampling XMM registers, then large PEBS can be enabled for XMM registers just like other GPRs. Reported-by: Xudong Hao Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-3-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index cc13164d948f20..e412e281266d99 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -4699,7 +4699,8 @@ static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) flags &= ~PERF_SAMPLE_REGS_USER; if (event->attr.sample_regs_user & ~PEBS_GP_REGS) flags &= ~PERF_SAMPLE_REGS_USER; - if (event->attr.sample_regs_intr & ~PEBS_GP_REGS) + if (event->attr.sample_regs_intr & + ~(PEBS_GP_REGS | PERF_REG_EXTENDED_MASK)) flags &= ~PERF_SAMPLE_REGS_INTR; return flags; } From bbdf84fcedfa4f1e570e12e7657f7095f6e7d340 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:11 +0800 Subject: [PATCH 0106/2286] perf/x86/intel: Convert x86_perf_regs to per-cpu variables Currently, the drain_pebs() helpers, e.g., intel_pmu_drain_arch_pebs() define an on-stack x86_perf_regs. Upcoming patches will add new fields like *ymm_regs and *zmm_regs to the x86_perf_regs structure to support sampling for these SIMD registers. This would increase the stack size consumed by these helpers, potentially triggering the warning: "the frame size of 1048 bytes is larger than 1024 bytes [-Wframe-larger-than=]". To eliminate this warning, convert x86_perf_regs to per-cpu variables. Please note drain_pebs() can't be interrupted by other NMIs since either it's already in NMI context or PMU is already disabled. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-4-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 8940f0292229f5..d592a72796af20 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -2917,6 +2917,8 @@ __intel_pmu_pebs_last_event(struct perf_event *event, } } +static DEFINE_PER_CPU(struct x86_perf_regs, x86_pebs_regs); + static __always_inline void __intel_pmu_pebs_events(struct perf_event *event, struct pt_regs *iregs, @@ -2926,8 +2928,8 @@ __intel_pmu_pebs_events(struct perf_event *event, setup_fn setup_sample) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); - struct x86_perf_regs perf_regs; - struct pt_regs *regs = &perf_regs.regs; + struct x86_perf_regs *perf_regs = this_cpu_ptr(&x86_pebs_regs); + struct pt_regs *regs = &perf_regs->regs; void *at = get_next_pebs_record_by_bit(base, top, bit); int cnt = count; @@ -3175,8 +3177,8 @@ static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_d void *last[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS]; struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct debug_store *ds = cpuc->ds; - struct x86_perf_regs perf_regs; - struct pt_regs *regs = &perf_regs.regs; + struct x86_perf_regs *perf_regs = this_cpu_ptr(&x86_pebs_regs); + struct pt_regs *regs = &perf_regs->regs; struct pebs_basic *basic; void *base, *at, *top; u64 mask; @@ -3226,8 +3228,8 @@ static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs, void *last[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS]; struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); union arch_pebs_index index; - struct x86_perf_regs perf_regs; - struct pt_regs *regs = &perf_regs.regs; + struct x86_perf_regs *perf_regs = this_cpu_ptr(&x86_pebs_regs); + struct pt_regs *regs = &perf_regs->regs; void *base, *at, *top; u64 mask; From cb388bd530cc518b0e245fb4eabc40f3de538e4f Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:12 +0800 Subject: [PATCH 0107/2286] perf: Eliminate duplicate arch-specific function definitions Define default common __weak functions for perf_reg_value(), perf_reg_validate(), perf_reg_abi() and perf_get_regs_user(). This helps to eliminate the duplicated arch-specific definitions. No function changes intended. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-5-dapeng1.mi@linux.intel.com --- arch/arm/kernel/perf_regs.c | 6 ------ arch/arm64/kernel/perf_regs.c | 6 ------ arch/csky/kernel/perf_regs.c | 6 ------ arch/loongarch/kernel/perf_regs.c | 6 ------ arch/mips/kernel/perf_regs.c | 6 ------ arch/parisc/kernel/perf_regs.c | 6 ------ arch/riscv/kernel/perf_regs.c | 6 ------ arch/x86/kernel/perf_regs.c | 6 ------ include/linux/perf_regs.h | 32 ++++++------------------------- kernel/events/core.c | 22 +++++++++++++++++++++ 10 files changed, 28 insertions(+), 74 deletions(-) diff --git a/arch/arm/kernel/perf_regs.c b/arch/arm/kernel/perf_regs.c index 0529f90395c94d..d575a4c3ca56f1 100644 --- a/arch/arm/kernel/perf_regs.c +++ b/arch/arm/kernel/perf_regs.c @@ -31,9 +31,3 @@ u64 perf_reg_abi(struct task_struct *task) return PERF_SAMPLE_REGS_ABI_32; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/arm64/kernel/perf_regs.c b/arch/arm64/kernel/perf_regs.c index b4eece3eb17d03..70e2f13f587f59 100644 --- a/arch/arm64/kernel/perf_regs.c +++ b/arch/arm64/kernel/perf_regs.c @@ -98,9 +98,3 @@ u64 perf_reg_abi(struct task_struct *task) return PERF_SAMPLE_REGS_ABI_64; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/csky/kernel/perf_regs.c b/arch/csky/kernel/perf_regs.c index 09b7f88a2d6ad4..94601f37b596d2 100644 --- a/arch/csky/kernel/perf_regs.c +++ b/arch/csky/kernel/perf_regs.c @@ -31,9 +31,3 @@ u64 perf_reg_abi(struct task_struct *task) return PERF_SAMPLE_REGS_ABI_32; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/loongarch/kernel/perf_regs.c b/arch/loongarch/kernel/perf_regs.c index 263ac4ab5af684..8dd604f017454f 100644 --- a/arch/loongarch/kernel/perf_regs.c +++ b/arch/loongarch/kernel/perf_regs.c @@ -45,9 +45,3 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return regs->regs[idx]; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/mips/kernel/perf_regs.c b/arch/mips/kernel/perf_regs.c index e686780d1647af..7736d3c5ebd2c8 100644 --- a/arch/mips/kernel/perf_regs.c +++ b/arch/mips/kernel/perf_regs.c @@ -60,9 +60,3 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return (s64)v; /* Sign extend if 32-bit. */ } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/parisc/kernel/perf_regs.c b/arch/parisc/kernel/perf_regs.c index 10a1a5f06a185b..b9fe1f2fcb9b18 100644 --- a/arch/parisc/kernel/perf_regs.c +++ b/arch/parisc/kernel/perf_regs.c @@ -53,9 +53,3 @@ u64 perf_reg_abi(struct task_struct *task) return PERF_SAMPLE_REGS_ABI_64; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/riscv/kernel/perf_regs.c b/arch/riscv/kernel/perf_regs.c index fd304a248de68a..3bba8deababbf4 100644 --- a/arch/riscv/kernel/perf_regs.c +++ b/arch/riscv/kernel/perf_regs.c @@ -35,9 +35,3 @@ u64 perf_reg_abi(struct task_struct *task) #endif } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 624703af80a199..81204cb7f72393 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -100,12 +100,6 @@ u64 perf_reg_abi(struct task_struct *task) return PERF_SAMPLE_REGS_ABI_32; } -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} #else /* CONFIG_X86_64 */ #define REG_NOSUPPORT ((1ULL << PERF_REG_X86_DS) | \ (1ULL << PERF_REG_X86_ES) | \ diff --git a/include/linux/perf_regs.h b/include/linux/perf_regs.h index f632c5725f162f..144bcc3ff19fe5 100644 --- a/include/linux/perf_regs.h +++ b/include/linux/perf_regs.h @@ -9,6 +9,12 @@ struct perf_regs { struct pt_regs *regs; }; +u64 perf_reg_value(struct pt_regs *regs, int idx); +int perf_reg_validate(u64 mask); +u64 perf_reg_abi(struct task_struct *task); +void perf_get_regs_user(struct perf_regs *regs_user, + struct pt_regs *regs); + #ifdef CONFIG_HAVE_PERF_REGS #include @@ -16,35 +22,9 @@ struct perf_regs { #define PERF_REG_EXTENDED_MASK 0 #endif -u64 perf_reg_value(struct pt_regs *regs, int idx); -int perf_reg_validate(u64 mask); -u64 perf_reg_abi(struct task_struct *task); -void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs); #else #define PERF_REG_EXTENDED_MASK 0 -static inline u64 perf_reg_value(struct pt_regs *regs, int idx) -{ - return 0; -} - -static inline int perf_reg_validate(u64 mask) -{ - return mask ? -ENOSYS : 0; -} - -static inline u64 perf_reg_abi(struct task_struct *task) -{ - return PERF_SAMPLE_REGS_ABI_NONE; -} - -static inline void perf_get_regs_user(struct perf_regs *regs_user, - struct pt_regs *regs) -{ - regs_user->regs = task_pt_regs(current); - regs_user->abi = perf_reg_abi(current); -} #endif /* CONFIG_HAVE_PERF_REGS */ #endif /* _LINUX_PERF_REGS_H */ diff --git a/kernel/events/core.c b/kernel/events/core.c index a6c8e38a311042..c84340ee2982c3 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7818,6 +7818,28 @@ unsigned long perf_instruction_pointer(struct perf_event *event, 0 : perf_arch_instruction_pointer(regs); } +u64 __weak perf_reg_value(struct pt_regs *regs, int idx) +{ + return 0; +} + +int __weak perf_reg_validate(u64 mask) +{ + return mask ? -ENOSYS : 0; +} + +u64 __weak perf_reg_abi(struct task_struct *task) +{ + return PERF_SAMPLE_REGS_ABI_NONE; +} + +void __weak perf_get_regs_user(struct perf_regs *regs_user, + struct pt_regs *regs) +{ + regs_user->regs = task_pt_regs(current); + regs_user->abi = perf_reg_abi(current); +} + static void perf_output_sample_regs(struct perf_output_handle *handle, struct pt_regs *regs, u64 mask) From 9c05620b49718544ba163c2ccc3c5a6b1f0bc8b0 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:13 +0800 Subject: [PATCH 0108/2286] perf/x86: Use x86_perf_regs in NMI handlers Support for sampling additional register state in NMI context (e.g. vector registers and SSP) requires an x86-specific register container. The generic pt_regs structure cannot represent all of the required x86 register data, so switch x86 NMI handlers to x86_perf_regs. pt_regs is still passed to x86_pmu_handle_irq(), so there is no functional change to existing handling. AMD IBS NMI handling does not use x86_pmu_handle_irq(), so this conversion does not apply to IBS. IBS support for extended register sampling can be added separately in follow-up patches. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-6-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 5 ++++- arch/x86/xen/pmu.c | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 75358a29722295..3368270c0ad16a 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -1800,9 +1800,11 @@ void perf_put_guest_lvtpc(void) EXPORT_SYMBOL_FOR_KVM(perf_put_guest_lvtpc); #endif /* CONFIG_PERF_GUEST_MEDIATED_PMU */ +static DEFINE_PER_CPU(struct x86_perf_regs, x86_intr_regs); static int perf_event_nmi_handler(unsigned int cmd, struct pt_regs *regs) { + struct x86_perf_regs *x86_regs = this_cpu_ptr(&x86_intr_regs); u64 start_clock; u64 finish_clock; int ret; @@ -1826,7 +1828,8 @@ perf_event_nmi_handler(unsigned int cmd, struct pt_regs *regs) return NMI_DONE; start_clock = sched_clock(); - ret = static_call(x86_pmu_handle_irq)(regs); + x86_regs->regs = *regs; + ret = static_call(x86_pmu_handle_irq)(&x86_regs->regs); finish_clock = sched_clock(); perf_sample_event_took(finish_clock - start_clock); diff --git a/arch/x86/xen/pmu.c b/arch/x86/xen/pmu.c index 5f50a3ee08f509..3f4dd3f50f56bc 100644 --- a/arch/x86/xen/pmu.c +++ b/arch/x86/xen/pmu.c @@ -456,12 +456,14 @@ static void xen_convert_regs(const struct xen_pmu_regs *xen_regs, } } +static DEFINE_PER_CPU(struct x86_perf_regs, x86_xen_intr_regs); irqreturn_t xen_pmu_irq_handler(int irq, void *dev_id) { int err, ret = IRQ_NONE; struct pt_regs regs = {0}; const struct xen_pmu_data *xenpmu_data = get_xenpmu_data(); uint8_t xenpmu_flags = get_xenpmu_flags(); + struct x86_perf_regs *x86_regs = this_cpu_ptr(&x86_xen_intr_regs); if (!xenpmu_data) { pr_warn_once("%s: pmudata not initialized\n", __func__); @@ -472,7 +474,8 @@ irqreturn_t xen_pmu_irq_handler(int irq, void *dev_id) xenpmu_flags | XENPMU_IRQ_PROCESSING; xen_convert_regs(&xenpmu_data->pmu.r.regs, ®s, xenpmu_data->pmu.pmu_flags); - if (x86_pmu.handle_irq(®s)) + x86_regs->regs = regs; + if (x86_pmu.handle_irq(&x86_regs->regs)) ret = IRQ_HANDLED; /* Write out cached context to HW */ From 03b89c9f202ee75c9997a8421f0e09a5590c3878 Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Mon, 24 Aug 2026 16:27:14 +0800 Subject: [PATCH 0109/2286] x86/fpu/xstate: Add xsaves_nmi() helper Add xsaves_nmi() to save supported xsave states in NMI handler. This function is similar to xsaves(), but should only be called within the NMI handler. This function returns the actual register contents at the moment the NMI occurs. Currently the perf subsystem is the sole user of this helper. It uses this function to snapshot SIMD (XMM/YMM/ZMM) and APX eGPRs registers which would be added in subsequent patches. Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-7-dapeng1.mi@linux.intel.com --- arch/x86/include/asm/fpu/xstate.h | 1 + arch/x86/kernel/fpu/xstate.c | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/arch/x86/include/asm/fpu/xstate.h b/arch/x86/include/asm/fpu/xstate.h index 7a7dc9d5602750..38fa8ff2655937 100644 --- a/arch/x86/include/asm/fpu/xstate.h +++ b/arch/x86/include/asm/fpu/xstate.h @@ -110,6 +110,7 @@ int xfeature_size(int xfeature_nr); void xsaves(struct xregs_state *xsave, u64 mask); void xrstors(struct xregs_state *xsave, u64 mask); +void xsaves_nmi(struct xregs_state *xsave, u64 mask); int xfd_enable_feature(u64 xfd_err); diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index a7b6524a9dea22..d39c01546dee79 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -1474,6 +1474,29 @@ void xrstors(struct xregs_state *xstate, u64 mask) WARN_ON_ONCE(err); } +/** + * xsaves_nmi - Save selected components to a kernel xstate buffer in NMI + * @xstate: Pointer to the buffer + * @mask: Feature mask to select the components to save + * + * This function is similar to xsaves(), but should only be called within + * the NMI handler. This function returns the actual register contents at + * the moment the NMI occurs. + * + * Currently, the perf subsystem is the sole user of this helper. It uses + * the function to snapshot SIMD (XMM/YMM/ZMM) and APX eGPRs registers. + */ +void xsaves_nmi(struct xregs_state *xstate, u64 mask) +{ + int err; + + if (!in_nmi()) + return; + + XSTATE_OP(XSAVES, xstate, (u32)mask, (u32)(mask >> 32), err); + WARN_ON_ONCE(err); +} + #if IS_ENABLED(CONFIG_KVM) void fpstate_clear_xstate_component(struct fpstate *fpstate, unsigned int xfeature) { From 450d73dc5f9188ade87fe2d2d967cf18e63b280f Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:15 +0800 Subject: [PATCH 0110/2286] x86/fpu: Add update_fpu_state_and_flag() helper Add update_fpu_state_and_flag() as suggested by Peter and Dave. The helper saves user FPU state and then sets TIF_NEED_FPU_LOAD, ensuring the task FPU state is saved whenever the flag is set. Subsequent patches will use this guarantee in NMI context by checking TIF_NEED_FPU_LOAD before retrieving user FPU state from the saved task FPU state. Also add barrier() in the host/guest FPU state switch path and move clearing of fpu->__task_fpstate after switching back to host state. So fpu->__task_fpstate is always observed as host FPU state when non-NULL. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/all/20251204154721.GB2619703@noisy.programming.kicks-ass.net/ Link: https://patch.msgid.link/20260824082731.1013973-8-dapeng1.mi@linux.intel.com --- arch/x86/include/asm/fpu/sched.h | 5 +++-- arch/x86/kernel/fpu/core.c | 33 ++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/arch/x86/include/asm/fpu/sched.h b/arch/x86/include/asm/fpu/sched.h index 89004f4ca208da..dcb2fa5f06d682 100644 --- a/arch/x86/include/asm/fpu/sched.h +++ b/arch/x86/include/asm/fpu/sched.h @@ -10,6 +10,8 @@ #include extern void save_fpregs_to_fpstate(struct fpu *fpu); +extern void update_fpu_state_and_flag(struct fpu *fpu, + struct task_struct *task); extern void fpu__drop(struct task_struct *tsk); extern int fpu_clone(struct task_struct *dst, u64 clone_flags, bool minimal, unsigned long shstk_addr); @@ -36,8 +38,7 @@ static inline void switch_fpu(struct task_struct *old, int cpu) !(old->flags & (PF_KTHREAD | PF_USER_WORKER))) { struct fpu *old_fpu = x86_task_fpu(old); - set_tsk_thread_flag(old, TIF_NEED_FPU_LOAD); - save_fpregs_to_fpstate(old_fpu); + update_fpu_state_and_flag(old_fpu, old); /* * The save operation preserved register state, so the * fpu_fpregs_owner_ctx is still @old_fpu. Store the diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c index d1aeecd57f5edb..047244e04d2f38 100644 --- a/arch/x86/kernel/fpu/core.c +++ b/arch/x86/kernel/fpu/core.c @@ -213,6 +213,19 @@ void restore_fpregs_from_fpstate(struct fpstate *fpstate, u64 mask) } } +/* + * Save the FPU register state in fpu->fpstate->regs and set + * TIF_NEED_FPU_LOAD subsequently. + * + * Must be called with fpregs_lock() held, ensuring flag + * TIF_NEED_FPU_LOAD is set last. + */ +void update_fpu_state_and_flag(struct fpu *fpu, struct task_struct *task) +{ + save_fpregs_to_fpstate(fpu); + set_tsk_thread_flag(task, TIF_NEED_FPU_LOAD); +} + void fpu_reset_from_exception_fixup(void) { restore_fpregs_from_fpstate(&init_fpstate, XFEATURE_MASK_FPSTATE); @@ -383,13 +396,13 @@ int fpu_swap_kvm_fpstate(struct fpu_guest *guest_fpu, bool enter_guest) /* Swap fpstate */ if (enter_guest) { - fpu->__task_fpstate = cur_fps; + WRITE_ONCE(fpu->__task_fpstate, cur_fps); + barrier(); fpu->fpstate = guest_fps; guest_fps->in_use = true; } else { guest_fps->in_use = false; fpu->fpstate = fpu->__task_fpstate; - fpu->__task_fpstate = NULL; } cur_fps = fpu->fpstate; @@ -406,6 +419,16 @@ int fpu_swap_kvm_fpstate(struct fpu_guest *guest_fpu, bool enter_guest) xfd_update_state(cur_fps); } + /* + * Clear fpu->__task_fpstate after switching back to host state. + * A non-NULL __task_fpstate means guest state is still resident in + * hardware; reset it only once host state has been restored. + */ + if (!enter_guest) { + barrier(); + WRITE_ONCE(fpu->__task_fpstate, NULL); + } + fpregs_mark_activate(); fpregs_unlock(); return 0; @@ -481,10 +504,8 @@ void kernel_fpu_begin_mask(unsigned int kfpu_mask) this_cpu_write(kernel_fpu_allowed, false); if (!(current->flags & (PF_KTHREAD | PF_USER_WORKER)) && - !test_thread_flag(TIF_NEED_FPU_LOAD)) { - set_thread_flag(TIF_NEED_FPU_LOAD); - save_fpregs_to_fpstate(x86_task_fpu(current)); - } + !test_thread_flag(TIF_NEED_FPU_LOAD)) + update_fpu_state_and_flag(x86_task_fpu(current), current); __cpu_invalidate_fpregs_state(); /* Put sane initial values into the control registers. */ From 05fe8825796e6b5557656c6c984486f0e66154fe Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Mon, 24 Aug 2026 16:27:16 +0800 Subject: [PATCH 0111/2286] perf: Move and enhance has_extended_regs() for arch-specific use Move has_extended_regs() to include/linux/perf_event.h so it can be used by arch-specific code. While moving it, enhance the check logic and rename it to event_has_extended_regs() to match existing perf event helper naming. Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-9-dapeng1.mi@linux.intel.com --- include/linux/perf_event.h | 10 ++++++++++ kernel/events/core.c | 8 +------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 5842552294c195..264785adaefd08 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1534,6 +1534,16 @@ perf_event__output_id_sample(struct perf_event *event, extern void perf_log_lost_samples(struct perf_event *event, u64 lost); +static inline bool event_has_extended_regs(struct perf_event *event) +{ + struct perf_event_attr *attr = &event->attr; + + return ((attr->sample_type & PERF_SAMPLE_REGS_USER) && + (attr->sample_regs_user & PERF_REG_EXTENDED_MASK)) || + ((attr->sample_type & PERF_SAMPLE_REGS_INTR) && + (attr->sample_regs_intr & PERF_REG_EXTENDED_MASK)); +} + static inline bool event_has_any_exclude_flag(struct perf_event *event) { struct perf_event_attr *attr = &event->attr; diff --git a/kernel/events/core.c b/kernel/events/core.c index c84340ee2982c3..5a0ae02edaf788 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -13127,12 +13127,6 @@ int perf_pmu_unregister(struct pmu *pmu) } EXPORT_SYMBOL_GPL(perf_pmu_unregister); -static inline bool has_extended_regs(struct perf_event *event) -{ - return (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK) || - (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK); -} - static int perf_try_init_event(struct pmu *pmu, struct perf_event *event) { struct perf_event_context *ctx = NULL; @@ -13167,7 +13161,7 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event) goto err_pmu; if (!(pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS) && - has_extended_regs(event)) { + event_has_extended_regs(event)) { ret = -EOPNOTSUPP; goto err_destroy; } From c09466807467860f4d92b05bd4ca7db96f654ed0 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:17 +0800 Subject: [PATCH 0112/2286] perf/x86/intel: Centralize PERF_PMU_CAP_EXTENDED_REGS updates PERF_PMU_CAP_EXTENDED_REGS is currently updated in multiple paths: intel_ds_pebs_init() and __intel_update_pmu_caps() for adaptive and architectural PEBS. This split makes the capability flow harder to reason about and is awkward for upcoming XSAVES-based SIMD register sampling, which also needs consistent capability setup and validation. Refactor the update logic into a single helper, __intel_update_pmu_xregs_caps(), so adaptive and architectural PEBS share one implementation. Call this helper from intel_update_pmu_caps() to keep PMU capability handling centralized in one place and avoid duplicated or conflicting updates. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-10-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 16 ++++++++-------- arch/x86/events/intel/ds.c | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index e412e281266d99..44332ecd54812c 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6162,11 +6162,14 @@ static inline bool intel_pmu_broken_perf_cap(void) return false; } -static inline void __intel_update_pmu_caps(struct pmu *pmu) +static inline void __intel_update_pmu_xregs_caps(struct pmu *pmu) { struct pmu *dest_pmu = pmu ? pmu : x86_get_pmu(smp_processor_id()); + u64 caps = hybrid(pmu, arch_pebs_cap).caps; - if (hybrid(pmu, arch_pebs_cap).caps & ARCH_PEBS_VECR_XMM) + if ((x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_XMM)) || + (!x86_pmu.arch_pebs && x86_pmu.intel_cap.pebs_format >= 4 && + x86_pmu.intel_cap.pebs_baseline)) dest_pmu->capabilities |= PERF_PMU_CAP_EXTENDED_REGS; } @@ -6238,12 +6241,10 @@ static void update_pmu_cap_from_perfmonext(struct pmu *pmu) hybrid(pmu, arch_pebs_cap).counters = pebs_mask; hybrid(pmu, arch_pebs_cap).pdists = pdists_mask; - if (WARN_ON((pebs_mask | pdists_mask) & ~cntrs_mask)) { + if (WARN_ON((pebs_mask | pdists_mask) & ~cntrs_mask)) x86_pmu.arch_pebs = 0; - } else { - __intel_update_pmu_caps(pmu); + else __intel_update_large_pebs_flags(pmu); - } } else { WARN_ON(x86_pmu.arch_pebs == 1); x86_pmu.arch_pebs = 0; @@ -6267,6 +6268,7 @@ static void intel_update_pmu_caps(struct pmu *pmu) hybrid_pmu(pmu)->pmu_type == hybrid_big) hybrid(pmu, intel_cap).perf_metrics = 1; } + __intel_update_pmu_xregs_caps(pmu); } static void intel_pmu_check_hybrid_pmus(struct x86_hybrid_pmu *pmu) @@ -6420,8 +6422,6 @@ static void intel_pmu_cpu_starting(int cpu) } } - __intel_update_pmu_caps(cpuc->pmu); - if (!cpuc->shared_regs) return; diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index d592a72796af20..e18906a3b31de9 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -3396,7 +3396,6 @@ static void __init intel_ds_pebs_init(void) x86_pmu.flags |= PMU_FL_PEBS_ALL; x86_pmu.pebs_capable = ~0ULL; pebs_qual = "-baseline"; - x86_get_pmu(smp_processor_id())->capabilities |= PERF_PMU_CAP_EXTENDED_REGS; } else { /* Only basic record supported */ x86_pmu.large_pebs_flags &= From b84c962836848f4687a55e6ad219d8ee861a94ed Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:18 +0800 Subject: [PATCH 0113/2286] perf/x86: Enable XMM register sampling for non-PEBS events Previously, XMM register sampling was only available for PEBS events starting from Icelake. Extend the support to non-PEBS events using the xsaves instruction, thereby completing the feature set. To implement this, a 64-byte aligned buffer is required. A per-CPU ext_regs_buf is introduced to store SIMD and other registers, with an approximate size of 2K. The buffer is allocated using kzalloc_node(), ensuring natural and 64-byte alignment for all kmalloc() allocations with powers of 2. XMM sampling for non-PEBS events is supported in the REGS_INTR case. Support for REGS_USER will be added in a subsequent patch. For PEBS events, XMM register sampling data is directly retrieved from PEBS records. Future support for additional vector registers (YMM/ZMM/OPMASK) is planned. An ext_regs_mask is added to track the supported vector register groups. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-11-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 185 ++++++++++++++++++++++++++++-- arch/x86/events/intel/core.c | 24 +++- arch/x86/events/intel/ds.c | 8 +- arch/x86/events/perf_event.h | 13 +++ arch/x86/include/asm/fpu/xstate.h | 2 + arch/x86/include/asm/perf_event.h | 5 +- arch/x86/kernel/fpu/xstate.c | 2 +- 7 files changed, 219 insertions(+), 20 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 3368270c0ad16a..1c3e2bfa2673a4 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -408,6 +408,53 @@ set_ext_hw_attr(struct hw_perf_event *hwc, struct perf_event *event) return x86_pmu_extra_regs(val, event); } +static DEFINE_PER_CPU(struct xregs_state *, ext_regs_buf); + +static void release_ext_regs_buffers(void) +{ + int cpu; + + if (!x86_pmu.ext_regs_mask) + return; + + for_each_possible_cpu(cpu) { + kfree(per_cpu(ext_regs_buf, cpu)); + per_cpu(ext_regs_buf, cpu) = NULL; + } +} + +static void reserve_ext_regs_buffers(void) +{ + bool compacted = cpu_feature_enabled(X86_FEATURE_XCOMPACTED); + unsigned int size; + int cpu; + + if (!x86_pmu.ext_regs_mask) + return; + + /* Add 64 bytes to satisfy the XSAVE area's 64-byte alignment. */ + size = xstate_calculate_size(x86_pmu.ext_regs_mask, compacted) + 64; + + for_each_possible_cpu(cpu) { + per_cpu(ext_regs_buf, cpu) = kzalloc_node(size, GFP_KERNEL, + cpu_to_node(cpu)); + if (WARN_ON_ONCE(!per_cpu(ext_regs_buf, cpu))) + goto err; + } + + return; + +err: + release_ext_regs_buffers(); +} + +static inline struct xregs_state *get_ext_regs_buf(int cpu) +{ + void *buf = per_cpu(ext_regs_buf, cpu); + + return buf ? PTR_ALIGN(buf, 64) : NULL; +} + int x86_reserve_hardware(void) { int err = 0; @@ -420,6 +467,7 @@ int x86_reserve_hardware(void) } else { reserve_ds_buffers(); reserve_lbr_buffers(); + reserve_ext_regs_buffers(); } } if (!err) @@ -436,6 +484,7 @@ void x86_release_hardware(void) release_pmc_hardware(); release_ds_buffers(); release_lbr_buffers(); + release_ext_regs_buffers(); mutex_unlock(&pmc_reserve_mutex); } } @@ -653,18 +702,31 @@ int x86_pmu_hw_config(struct perf_event *event) return -EINVAL; } - /* sample_regs_user never support XMM registers */ - if (unlikely(event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK)) - return -EINVAL; - /* - * Besides the general purpose registers, XMM registers may - * be collected in PEBS on some platforms, e.g. Icelake - */ - if (unlikely(event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK)) { - if (!(event->pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS)) - return -EINVAL; + if (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) { + /* + * Besides the general purpose registers, XMM registers may + * be collected as well. + */ + if (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK) { + if (!(event->pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS)) + return -EINVAL; + + if (event->attr.precise_ip) { + u64 caps = hybrid(event->pmu, arch_pebs_cap).caps; - if (!event->attr.precise_ip) + if (x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) + return -EINVAL; + if (!x86_pmu.arch_pebs && !x86_pmu.intel_cap.pebs_baseline) + return -EINVAL; + } + if (!get_ext_regs_buf(raw_smp_processor_id())) + return -ENOMEM; + } + } + + if (event->attr.sample_type & PERF_SAMPLE_REGS_USER) { + /* XMM registers sampling for REGS_USER is not supported yet. */ + if (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK) return -EINVAL; } @@ -1717,6 +1779,107 @@ static void x86_pmu_del(struct perf_event *event, int flags) static_call_cond(x86_pmu_del)(event); } +void x86_pmu_clear_perf_regs(struct pt_regs *regs) +{ + struct x86_perf_regs *perf_regs = container_of(regs, struct x86_perf_regs, regs); + + perf_regs->xmm_regs = NULL; +} + +static void update_perf_regs(struct x86_perf_regs *perf_regs, + struct xregs_state *xsave, u64 bitmap) +{ + u64 mask; + + if (!xsave) + return; + + /* Restrict to features actually saved by XSAVES */ + mask = bitmap & xsave->header.xfeatures; + + if (mask & XFEATURE_MASK_SSE) + perf_regs->xmm_space = xsave->i387.xmm_space; +} + +/* + * The x86 specific variant of perf_sample_regs_intr(). + * It would be extended to add more SIMD registers sampling support + * in later patches. + */ +static void x86_pmu_update_regs_intr(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs, + bool exclude_kernel) +{ + if (exclude_kernel && !user_mode(regs)) { + data->regs_intr.regs = NULL; + data->regs_intr.abi = PERF_SAMPLE_REGS_ABI_NONE; + } else { + data->regs_intr.regs = regs; + data->regs_intr.abi = perf_reg_abi(current); + } + + data->dyn_size += sizeof(u64); + if (data->regs_intr.regs) { + data->dyn_size += hweight64(event->attr.sample_regs_intr) * + sizeof(u64); + } + + /* + * Set PERF_SAMPLE_REGS_INTR to bypass perf_sample_regs_intr() call + * in perf_prepare_sample() function. + */ + data->sample_flags |= PERF_SAMPLE_REGS_INTR; +} + +static void x86_pmu_sample_xregs(struct perf_event *event, + struct perf_sample_data *data, + bool from_pebs) +{ + struct xregs_state *xsave = get_ext_regs_buf(smp_processor_id()); + u64 sample_type = event->attr.sample_type; + struct x86_perf_regs *perf_regs; + u64 intr_mask = 0; + + if (WARN_ON_ONCE(!xsave) || !in_nmi()) + return; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && data->regs_intr.regs) { + if (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK) + intr_mask |= XFEATURE_MASK_SSE; + + intr_mask &= x86_pmu.ext_regs_mask; + intr_mask = from_pebs ? 0 : intr_mask; + } + + if (intr_mask) { + perf_regs = container_of(data->regs_intr.regs, + struct x86_perf_regs, regs); + xsave->header.xfeatures = 0; + xsaves_nmi(xsave, intr_mask); + update_perf_regs(perf_regs, xsave, intr_mask); + } +} + +void x86_pmu_update_perf_regs(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs, + bool from_pebs) +{ + u64 sample_type = event->attr.sample_type; + + if (!((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK))) + return; + + if (sample_type & PERF_SAMPLE_REGS_INTR) { + x86_pmu_update_regs_intr(event, data, regs, + event->attr.exclude_kernel); + } + + x86_pmu_sample_xregs(event, data, from_pebs); +} + int x86_pmu_handle_irq(struct pt_regs *regs) { struct perf_sample_data data; diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 44332ecd54812c..ddf72614e739c3 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3925,6 +3925,9 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status) if (has_branch_stack(event)) intel_pmu_lbr_save_brstack(&data, cpuc, event); + x86_pmu_clear_perf_regs(regs); + x86_pmu_update_perf_regs(event, &data, regs, false); + perf_event_overflow(event, &data, regs); } @@ -6165,12 +6168,23 @@ static inline bool intel_pmu_broken_perf_cap(void) static inline void __intel_update_pmu_xregs_caps(struct pmu *pmu) { struct pmu *dest_pmu = pmu ? pmu : x86_get_pmu(smp_processor_id()); - u64 caps = hybrid(pmu, arch_pebs_cap).caps; - if ((x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_XMM)) || - (!x86_pmu.arch_pebs && x86_pmu.intel_cap.pebs_format >= 4 && - x86_pmu.intel_cap.pebs_baseline)) - dest_pmu->capabilities |= PERF_PMU_CAP_EXTENDED_REGS; + /* Only support the extension when XSAVES is available. */ + if (!boot_cpu_has(X86_FEATURE_XSAVES)) + return; + + if (!boot_cpu_has(X86_FEATURE_XMM) || + !cpu_has_xfeatures(XFEATURE_MASK_SSE, NULL)) + return; + + /* + * On current hybrid platforms, P-cores and E-cores expose the same + * XSAVE feature set. Therefore, using the global x86_pmu.ext_regs_mask + * is sufficient to represent the hardware-supported XSAVE features. + */ + x86_pmu.ext_regs_mask |= XFEATURE_MASK_SSE; + + dest_pmu->capabilities |= PERF_PMU_CAP_EXTENDED_REGS; } static inline void __intel_update_large_pebs_flags(struct pmu *pmu) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index e18906a3b31de9..e14f8c22ffb5aa 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -2515,7 +2515,7 @@ static void setup_pebs_adaptive_sample_data(struct perf_event *event, return; perf_regs = container_of(regs, struct x86_perf_regs, regs); - perf_regs->xmm_regs = NULL; + x86_pmu_clear_perf_regs(regs); format_group = basic->format_group; @@ -2600,6 +2600,8 @@ static void setup_pebs_adaptive_sample_data(struct perf_event *event, next_record += nr * sizeof(u64); } + x86_pmu_update_perf_regs(event, data, regs, true); + WARN_ONCE(next_record != __pebs + basic->format_size, "PEBS record size %u, expected %llu, config %llx\n", basic->format_size, @@ -2632,7 +2634,7 @@ static void setup_arch_pebs_sample_data(struct perf_event *event, return; perf_regs = container_of(regs, struct x86_perf_regs, regs); - perf_regs->xmm_regs = NULL; + x86_pmu_clear_perf_regs(regs); __setup_perf_sample_data(event, iregs, data); @@ -2734,6 +2736,8 @@ static void setup_arch_pebs_sample_data(struct perf_event *event, at = at + header->size; goto again; } + + x86_pmu_update_perf_regs(event, data, regs, true); } static inline void * diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 71ed5b2acea224..f1ab72094bacad 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1028,6 +1028,12 @@ struct x86_pmu { struct extra_reg *extra_regs; unsigned int flags; + /* + * Extended regs, e.g., vector registers + * Utilize the same format as the XFEATURE_MASK_* + */ + u64 ext_regs_mask; + /* * Intel host/guest support (KVM) */ @@ -1315,6 +1321,13 @@ void x86_pmu_enable_event(struct perf_event *event); int x86_pmu_handle_irq(struct pt_regs *regs); +void x86_pmu_clear_perf_regs(struct pt_regs *regs); + +void x86_pmu_update_perf_regs(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs, + bool from_pebs); + void x86_pmu_show_pmu_cap(struct pmu *pmu); static inline int x86_pmu_num_counters(struct pmu *pmu) diff --git a/arch/x86/include/asm/fpu/xstate.h b/arch/x86/include/asm/fpu/xstate.h index 38fa8ff2655937..19dec5f0b1c711 100644 --- a/arch/x86/include/asm/fpu/xstate.h +++ b/arch/x86/include/asm/fpu/xstate.h @@ -112,6 +112,8 @@ void xsaves(struct xregs_state *xsave, u64 mask); void xrstors(struct xregs_state *xsave, u64 mask); void xsaves_nmi(struct xregs_state *xsave, u64 mask); +unsigned int xstate_calculate_size(u64 xfeatures, bool compacted); + int xfd_enable_feature(u64 xfd_err); #ifdef CONFIG_X86_64 diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 1eb13673e889f9..619e0ae915e1e5 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -728,7 +728,10 @@ extern void perf_events_lapic_init(void); struct pt_regs; struct x86_perf_regs { struct pt_regs regs; - u64 *xmm_regs; + union { + u64 *xmm_regs; + u32 *xmm_space; /* for xsaves */ + }; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index d39c01546dee79..3e7f5fb5bfafed 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -587,7 +587,7 @@ static bool __init check_xstate_against_struct(int nr) return true; } -static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted) +unsigned int xstate_calculate_size(u64 xfeatures, bool compacted) { unsigned int topmost = fls64(xfeatures) - 1; unsigned int offset, i; From edd9aec51213debb788d50ad8d7aeaac59fde6d6 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:19 +0800 Subject: [PATCH 0114/2286] perf/x86: Enable XMM register sampling for REGS_USER case Support XMM register sampling for the REGS_USER case. To handle simultaneous sampling of XMM registers for both REGS_INTR and REGS_USER cases, a per-CPU x86_user_regs is introduced to store REGS_USER-specific XMM registers. This prevents REGS_USER-specific XMM register data from being overwritten by REGS_INTR-specific data if they share the same x86_perf_regs structure. To sample user-space XMM registers, the x86_pmu_update_user_xregs() helper function is added. It checks if the user-space FPU state has been cached into task FPU state. If so, the user-space XMM register data can be directly retrieved from the cached task FPU state, as the corresponding hardware registers have been cleared or switched to kernel-space state or guest state. Otherwise, the data could be read from the hardware registers using the xsaves instruction. For PEBS events, x86_pmu_update_user_xregs() checks if the PEBS-sampled XMM register data belongs to user-space. If so, no further action is needed. Otherwise, the user-space XMM register data needs to be re-sampled using the same method as for non-PEBS events. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-12-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 154 ++++++++++++++++++++++++++++++++--- arch/x86/events/intel/core.c | 6 +- arch/x86/events/intel/ds.c | 5 +- 3 files changed, 147 insertions(+), 18 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 1c3e2bfa2673a4..20140643e21c9d 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -702,12 +702,12 @@ int x86_pmu_hw_config(struct perf_event *event) return -EINVAL; } - if (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) { + if (event->attr.sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) { /* * Besides the general purpose registers, XMM registers may * be collected as well. */ - if (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK) { + if (event_has_extended_regs(event)) { if (!(event->pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS)) return -EINVAL; @@ -724,12 +724,6 @@ int x86_pmu_hw_config(struct perf_event *event) } } - if (event->attr.sample_type & PERF_SAMPLE_REGS_USER) { - /* XMM registers sampling for REGS_USER is not supported yet. */ - if (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK) - return -EINVAL; - } - return x86_setup_perfctr(event); } @@ -1832,14 +1826,129 @@ static void x86_pmu_update_regs_intr(struct perf_event *event, data->sample_flags |= PERF_SAMPLE_REGS_INTR; } +static DEFINE_PER_CPU(struct x86_perf_regs, x86_user_regs); + +static void x86_pmu_get_regs_user(struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct x86_perf_regs *x86_regs_user = this_cpu_ptr(&x86_user_regs); + struct perf_regs regs_user; + + x86_pmu_clear_perf_regs(&x86_regs_user->regs); + + perf_get_regs_user(®s_user, regs); + data->regs_user.abi = regs_user.abi; + if (regs_user.regs) { + x86_regs_user->regs = *regs_user.regs; + data->regs_user.regs = &x86_regs_user->regs; + } else { + data->regs_user.regs = NULL; + } +} + +/* + * The x86 specific variant of perf_sample_regs_user(). + * Update data->regs_user fields for extended registers (e.g., SIMD). + */ +static void x86_pmu_update_regs_user(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct x86_perf_regs *x86_regs_user = this_cpu_ptr(&x86_user_regs); + struct perf_event_attr *attr = &event->attr; + struct x86_perf_regs *perf_regs; + + /* + * PERF_SAMPLE_REGS_INTR and PERF_SAMPLE_REGS_USER can both be + * requested for one event. Keep user regs in a separate x86_perf_regs + * instance, so intr-reg collection does not overwrite user-reg data. + */ + if (user_mode(regs)) { + x86_pmu_clear_perf_regs(&x86_regs_user->regs); + perf_regs = container_of(regs, struct x86_perf_regs, regs); + /* Copy all sampled regs data to x86_regs_user. */ + *x86_regs_user = *perf_regs; + data->regs_user.regs = &x86_regs_user->regs; + data->regs_user.abi = perf_reg_abi(current); + } else if (is_user_task(current)) { + x86_pmu_get_regs_user(data, regs); + } else { + data->regs_user.abi = PERF_SAMPLE_REGS_ABI_NONE; + data->regs_user.regs = NULL; + } + + data->dyn_size += sizeof(u64); + if (data->regs_user.regs) + data->dyn_size += hweight64(attr->sample_regs_user) * sizeof(u64); + + /* + * Set PERF_SAMPLE_REGS_USER to bypass perf_sample_regs_user() call + * in perf_prepare_sample() function. + */ + data->sample_flags |= PERF_SAMPLE_REGS_USER; +} + +/* + * This function retrieves cached user-space fpu registers (XMM/YMM/ZMM). + * If TIF_NEED_FPU_LOAD is set or PMI hits into guest, it indicates that + * the user-space FPU state is cached. Otherwise, the data should be read + * directly from the hardware registers. + */ +static inline u64 x86_pmu_update_user_xregs(struct perf_sample_data *data, + struct pt_regs *regs, + u64 mask, bool from_pebs) +{ + struct x86_perf_regs *perf_regs; + struct xregs_state *xsave; + struct fpu *fpu; + struct fpstate *fps; + u64 user_mask = mask; + + if (!is_user_task(current)) + return 0; + + if (data->regs_user.abi == PERF_SAMPLE_REGS_ABI_NONE) + return 0; + + /* + * If PEBS hits kernel space, need to re-sample extended + * registers for user space. + */ + if (user_mode(regs)) + user_mask = from_pebs ? 0 : mask; + + fpu = x86_task_fpu(current); + fps = READ_ONCE(fpu->__task_fpstate); + /* + * If fpu->__task_fpstate is set, it points to the cached user + * FPU state (e.g. with KVM guest-state swapping). Otherwise, + * when TIF_NEED_FPU_LOAD is set, fpu->fpstate holds the cached + * user state. If neither is true, the user state is live in hardware. + */ + if (user_mask && (test_thread_flag(TIF_NEED_FPU_LOAD) || fps)) { + perf_regs = container_of(data->regs_user.regs, + struct x86_perf_regs, regs); + if (!fps) + fps = fpu->fpstate; + xsave = &fps->regs.xsave; + + update_perf_regs(perf_regs, xsave, user_mask); + return 0; + } + + return user_mask; +} + static void x86_pmu_sample_xregs(struct perf_event *event, struct perf_sample_data *data, + struct pt_regs *regs, bool from_pebs) { struct xregs_state *xsave = get_ext_regs_buf(smp_processor_id()); u64 sample_type = event->attr.sample_type; struct x86_perf_regs *perf_regs; u64 intr_mask = 0; + u64 user_mask = 0; if (WARN_ON_ONCE(!xsave) || !in_nmi()) return; @@ -1852,13 +1961,31 @@ static void x86_pmu_sample_xregs(struct perf_event *event, intr_mask = from_pebs ? 0 : intr_mask; } + if ((sample_type & PERF_SAMPLE_REGS_USER) && data->regs_user.regs) { + if (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK) + user_mask |= XFEATURE_MASK_SSE; + + user_mask &= x86_pmu.ext_regs_mask; + user_mask = x86_pmu_update_user_xregs(data, regs, + user_mask, from_pebs); + } + + if (user_mask | intr_mask) { + xsave->header.xfeatures = 0; + xsaves_nmi(xsave, user_mask | intr_mask); + } + if (intr_mask) { perf_regs = container_of(data->regs_intr.regs, struct x86_perf_regs, regs); - xsave->header.xfeatures = 0; - xsaves_nmi(xsave, intr_mask); update_perf_regs(perf_regs, xsave, intr_mask); } + + if (user_mask) { + perf_regs = container_of(data->regs_user.regs, + struct x86_perf_regs, regs); + update_perf_regs(perf_regs, xsave, user_mask); + } } void x86_pmu_update_perf_regs(struct perf_event *event, @@ -1868,16 +1995,17 @@ void x86_pmu_update_perf_regs(struct perf_event *event, { u64 sample_type = event->attr.sample_type; - if (!((sample_type & PERF_SAMPLE_REGS_INTR) && - (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK))) + if (!event_has_extended_regs(event)) return; if (sample_type & PERF_SAMPLE_REGS_INTR) { x86_pmu_update_regs_intr(event, data, regs, event->attr.exclude_kernel); } + if (sample_type & PERF_SAMPLE_REGS_USER) + x86_pmu_update_regs_user(event, data, regs); - x86_pmu_sample_xregs(event, data, from_pebs); + x86_pmu_sample_xregs(event, data, regs, from_pebs); } int x86_pmu_handle_irq(struct pt_regs *regs) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index ddf72614e739c3..5cb80833c7edd0 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -4695,15 +4695,15 @@ static void intel_pebs_aliases_skl(struct perf_event *event) static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) { unsigned long flags = x86_pmu.large_pebs_flags; + u64 gprs_mask = PEBS_GP_REGS | PERF_REG_EXTENDED_MASK; if (event->attr.use_clockid) flags &= ~PERF_SAMPLE_TIME; if (!event->attr.exclude_kernel) flags &= ~PERF_SAMPLE_REGS_USER; - if (event->attr.sample_regs_user & ~PEBS_GP_REGS) + if (event->attr.sample_regs_user & ~gprs_mask) flags &= ~PERF_SAMPLE_REGS_USER; - if (event->attr.sample_regs_intr & - ~(PEBS_GP_REGS | PERF_REG_EXTENDED_MASK)) + if (event->attr.sample_regs_intr & ~gprs_mask) flags &= ~PERF_SAMPLE_REGS_INTR; return flags; } diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index e14f8c22ffb5aa..d216234803b829 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1733,8 +1733,7 @@ static u64 pebs_update_adaptive_cfg(struct perf_event *event) if (gprs || (attr->precise_ip < 2) || tsx_weight) pebs_data_cfg |= PEBS_DATACFG_GP; - if ((sample_type & PERF_SAMPLE_REGS_INTR) && - (attr->sample_regs_intr & PERF_REG_EXTENDED_MASK)) + if (event_has_extended_regs(event)) pebs_data_cfg |= PEBS_DATACFG_XMMS; if (sample_type & PERF_SAMPLE_BRANCH_STACK) { @@ -2937,6 +2936,8 @@ __intel_pmu_pebs_events(struct perf_event *event, void *at = get_next_pebs_record_by_bit(base, top, bit); int cnt = count; + x86_pmu_clear_perf_regs(regs); + if (!iregs) iregs = &dummy_iregs; From 918b7d6d1729952d51da4fa81db7b366a207de8f Mon Sep 17 00:00:00 2001 From: Kan Liang Date: Mon, 24 Aug 2026 16:27:20 +0800 Subject: [PATCH 0115/2286] perf: Add sampling support for SIMD registers Users may be interested in sampling SIMD registers during profiling. The current sample_regs_* structure does not have sufficient space for all SIMD registers. To address this, new attribute fields sample_simd_{pred,vec}_reg_* are added to struct perf_event_attr to represent the SIMD registers that are expected to be sampled. Currently, the perf/x86 code supports XMM registers in sample_regs_*. To unify the configuration of SIMD registers and ensure a consistent method for configuring XMM and other SIMD registers, a new event attribute field, sample_simd_regs_enabled, is introduced. When sample_simd_regs_enabled is set, it indicates that all SIMD registers, including XMM, will be represented by the newly introduced sample_simd_{pred|vec}_reg_* fields. The original XMM space in sample_regs_* is reserved for future uses. Since SIMD registers are wider than 64 bits, a new output format is introduced. The number and width of SIMD registers are dumped first, followed by the register values. The number and width are based on the user's configuration. A new ABI, PERF_SAMPLE_REGS_ABI_SIMD, is added to indicate the new format. The enum perf_sample_regs_abi is now a bitmap. This change should not impact existing tools, as the version and bitmap remain the same for values 1 and 2. Additionally, two new __weak functions are introduced: - perf_simd_reg_value(): Retrieves the value of the requested SIMD register. - perf_simd_reg_validate(): Validates the configuration of the SIMD registers. A new flag, PERF_PMU_CAP_SIMD_REGS, is added to indicate that the PMU supports SIMD register dumping. An error is generated if sample_simd_{pred|vec}_reg_* is mistakenly set for a PMU that does not support this capability. Suggested-by: Peter Zijlstra (Intel) Signed-off-by: Kan Liang Co-developed-by: Dapeng Mi Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-13-dapeng1.mi@linux.intel.com --- include/linux/perf_event.h | 12 +++ include/linux/perf_regs.h | 4 + include/uapi/linux/perf_event.h | 49 ++++++++++- kernel/events/core.c | 141 +++++++++++++++++++++++++++++--- 4 files changed, 192 insertions(+), 14 deletions(-) diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 264785adaefd08..a2e7eae268cdea 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -306,6 +306,7 @@ struct perf_event_pmu_context; #define PERF_PMU_CAP_AUX_PAUSE 0x0200 #define PERF_PMU_CAP_AUX_PREFER_LARGE 0x0400 #define PERF_PMU_CAP_MEDIATED_VPMU 0x0800 +#define PERF_PMU_CAP_SIMD_REGS 0x1000 /** * pmu::scope @@ -1534,6 +1535,17 @@ perf_event__output_id_sample(struct perf_event *event, extern void perf_log_lost_samples(struct perf_event *event, u64 lost); +static inline bool event_has_simd_regs(struct perf_event *event) +{ + struct perf_event_attr *attr = &event->attr; + + if (!(event->attr.sample_type & + (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER))) + return false; + + return attr->sample_simd_regs_enabled != 0; +} + static inline bool event_has_extended_regs(struct perf_event *event) { struct perf_event_attr *attr = &event->attr; diff --git a/include/linux/perf_regs.h b/include/linux/perf_regs.h index 144bcc3ff19fe5..518f28c6a7d4be 100644 --- a/include/linux/perf_regs.h +++ b/include/linux/perf_regs.h @@ -14,6 +14,10 @@ int perf_reg_validate(u64 mask); u64 perf_reg_abi(struct task_struct *task); void perf_get_regs_user(struct perf_regs *regs_user, struct pt_regs *regs); +int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, + u16 pred_qwords, u32 pred_mask); +u64 perf_simd_reg_value(struct pt_regs *regs, int idx, + u16 qwords_idx, bool pred); #ifdef CONFIG_HAVE_PERF_REGS #include diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h index fd10aa8d697f25..c49fc76292f798 100644 --- a/include/uapi/linux/perf_event.h +++ b/include/uapi/linux/perf_event.h @@ -314,8 +314,9 @@ enum { */ enum perf_sample_regs_abi { PERF_SAMPLE_REGS_ABI_NONE = 0, - PERF_SAMPLE_REGS_ABI_32 = 1, - PERF_SAMPLE_REGS_ABI_64 = 2, + PERF_SAMPLE_REGS_ABI_32 = (1 << 0), + PERF_SAMPLE_REGS_ABI_64 = (1 << 1), + PERF_SAMPLE_REGS_ABI_SIMD = (1 << 2), }; /* @@ -383,6 +384,7 @@ enum perf_event_read_format { #define PERF_ATTR_SIZE_VER7 128 /* Add: sig_data */ #define PERF_ATTR_SIZE_VER8 136 /* Add: config3 */ #define PERF_ATTR_SIZE_VER9 144 /* add: config4 */ +#define PERF_ATTR_SIZE_VER10 176 /* Add: sample_simd_{vec|pred}_reg_* */ /* * 'struct perf_event_attr' contains various attributes that define @@ -547,6 +549,29 @@ struct perf_event_attr { __u64 config3; /* extension of config2 */ __u64 config4; /* extension of config3 */ + + /* + * Defines the sampling SIMD/PRED(predicate) registers bitmap and + * qwords (8 bytes) length. + * + * sample_simd_regs_enabled != 0 indicates there are SIMD/PRED + * registers to be sampled, the SIMD/PRED registers bitmap and + * qwords length are represented in + * sample_simd_{vec|pred}_reg_{intr|user} and + * sample_simd_{vec|pred}_reg_qwords fields separately. + * + * sample_simd_regs_enabled == 0 indicates no SIMD/PRED registers + * are sampled. + */ + __u16 sample_simd_regs_enabled; + __u16 sample_simd_pred_reg_qwords; + __u16 sample_simd_vec_reg_qwords; + __u16 __reserved_4; + + __u32 sample_simd_pred_reg_intr; + __u32 sample_simd_pred_reg_user; + __u64 sample_simd_vec_reg_intr; + __u64 sample_simd_vec_reg_user; }; /* @@ -1020,7 +1045,15 @@ enum perf_event_type { * } && PERF_SAMPLE_BRANCH_STACK * * { u64 abi; # enum perf_sample_regs_abi - * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_USER + * u64 regs[weight(mask)]; + * struct { + * u64 nr_vectors; # 0 ... weight(sample_simd_vec_reg_user) + * u64 vector_qwords; # 0 ... sample_simd_vec_reg_qwords + * u64 nr_pred; # 0 ... weight(sample_simd_pred_reg_user) + * u64 pred_qwords; # 0 ... sample_simd_pred_reg_qwords + * u64 data[nr_vectors * vector_qwords + nr_pred * pred_qwords]; + * } && (abi & PERF_SAMPLE_REGS_ABI_SIMD) + * } && PERF_SAMPLE_REGS_USER * * { u64 size; * char data[size]; @@ -1047,7 +1080,15 @@ enum perf_event_type { * { u64 data_src; } && PERF_SAMPLE_DATA_SRC * { u64 transaction; } && PERF_SAMPLE_TRANSACTION * { u64 abi; # enum perf_sample_regs_abi - * u64 regs[weight(mask)]; } && PERF_SAMPLE_REGS_INTR + * u64 regs[weight(mask)]; + * struct { + * u64 nr_vectors; # 0 ... weight(sample_simd_vec_reg_intr) + * u64 vector_qwords; # 0 ... sample_simd_vec_reg_qwords + * u64 nr_pred; # 0 ... weight(sample_simd_pred_reg_intr) + * u64 pred_qwords; # 0 ... sample_simd_pred_reg_qwords + * u64 data[nr_vectors * vector_qwords + nr_pred * pred_qwords]; + * } && (abi & PERF_SAMPLE_REGS_ABI_SIMD) + * } && PERF_SAMPLE_REGS_INTR * { u64 phys_addr;} && PERF_SAMPLE_PHYS_ADDR * { u64 cgroup;} && PERF_SAMPLE_CGROUP * { u64 data_page_size;} && PERF_SAMPLE_DATA_PAGE_SIZE diff --git a/kernel/events/core.c b/kernel/events/core.c index 5a0ae02edaf788..3a87c6adcded9f 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7840,22 +7840,60 @@ void __weak perf_get_regs_user(struct perf_regs *regs_user, regs_user->abi = perf_reg_abi(current); } +#define word_for_each_set_bit(bit, val) \ + for (unsigned long long __v = (val); \ + __v && ((bit = __builtin_ctzll(__v)), 1); \ + __v &= __v - 1) + static void perf_output_sample_regs(struct perf_output_handle *handle, struct pt_regs *regs, u64 mask) { int bit; - DECLARE_BITMAP(_mask, 64); - - bitmap_from_u64(_mask, mask); - for_each_set_bit(bit, _mask, sizeof(mask) * BITS_PER_BYTE) { - u64 val; - val = perf_reg_value(regs, bit); + word_for_each_set_bit(bit, mask) { + u64 val = perf_reg_value(regs, bit); perf_output_put(handle, val); } } +static void +perf_output_sample_simd_regs(struct perf_output_handle *handle, + struct perf_event *event, + struct pt_regs *regs, + u64 mask, u32 pred_mask) +{ + u64 pred_qwords = event->attr.sample_simd_pred_reg_qwords; + u64 vec_qwords = event->attr.sample_simd_vec_reg_qwords; + u64 nr_vectors = hweight64(mask); + u64 nr_pred = hweight32(pred_mask); + int bit; + + perf_output_put(handle, nr_vectors); + perf_output_put(handle, vec_qwords); + perf_output_put(handle, nr_pred); + perf_output_put(handle, pred_qwords); + + if (nr_vectors) { + word_for_each_set_bit(bit, mask) { + for (int i = 0; i < vec_qwords; i++) { + u64 val = perf_simd_reg_value(regs, bit, + i, false); + perf_output_put(handle, val); + } + } + } + if (nr_pred) { + word_for_each_set_bit(bit, pred_mask) { + for (int i = 0; i < pred_qwords; i++) { + u64 val = perf_simd_reg_value(regs, bit, + i, true); + perf_output_put(handle, val); + } + } + } +} + static void perf_sample_regs_user(struct perf_regs *regs_user, struct pt_regs *regs) { @@ -7889,6 +7927,17 @@ static void perf_sample_regs_intr(struct perf_regs *regs_intr, } } +int __weak perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, + u16 pred_qwords, u32 pred_mask) +{ + return -EINVAL; +} + +u64 __weak perf_simd_reg_value(struct pt_regs *regs, int idx, + u16 qwords_idx, bool pred) +{ + return 0; +} /* * Get remaining task size from user stack pointer. @@ -8419,10 +8468,17 @@ void perf_output_sample(struct perf_output_handle *handle, perf_output_put(handle, abi); if (abi) { - u64 mask = event->attr.sample_regs_user; + struct perf_event_attr *attr = &event->attr; + u64 mask = attr->sample_regs_user; perf_output_sample_regs(handle, data->regs_user.regs, mask); + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) { + perf_output_sample_simd_regs(handle, event, + data->regs_user.regs, + attr->sample_simd_vec_reg_user, + attr->sample_simd_pred_reg_user); + } } } @@ -8450,11 +8506,18 @@ void perf_output_sample(struct perf_output_handle *handle, perf_output_put(handle, abi); if (abi) { - u64 mask = event->attr.sample_regs_intr; + struct perf_event_attr *attr = &event->attr; + u64 mask = attr->sample_regs_intr; perf_output_sample_regs(handle, data->regs_intr.regs, mask); + if (abi & PERF_SAMPLE_REGS_ABI_SIMD) { + perf_output_sample_simd_regs(handle, event, + data->regs_intr.regs, + attr->sample_simd_vec_reg_intr, + attr->sample_simd_pred_reg_intr); + } } } @@ -8657,6 +8720,29 @@ static __always_inline u64 __cond_set(u64 flags, u64 s, u64 d) return d * !!(flags & s); } +static u64 perf_update_xregs_size(struct perf_event *event, bool intr) +{ + u16 pred_qwords = event->attr.sample_simd_pred_reg_qwords; + u16 vec_qwords = event->attr.sample_simd_vec_reg_qwords; + u64 pred_mask; + u64 mask; + int size; + + if (intr) { + mask = event->attr.sample_simd_vec_reg_intr; + pred_mask = event->attr.sample_simd_pred_reg_intr; + } else { + mask = event->attr.sample_simd_vec_reg_user; + pred_mask = event->attr.sample_simd_pred_reg_user; + } + + size = sizeof(u64) * 4; + size += (hweight64(mask) * vec_qwords + + hweight64(pred_mask) * pred_qwords) * sizeof(u64); + + return size; +} + void perf_prepare_sample(struct perf_sample_data *data, struct perf_event *event, struct pt_regs *regs) @@ -8719,7 +8805,12 @@ void perf_prepare_sample(struct perf_sample_data *data, if (data->regs_user.regs) { u64 mask = event->attr.sample_regs_user; + size += hweight64(mask) * sizeof(u64); + if (event_has_simd_regs(event)) { + size += perf_update_xregs_size(event, false); + data->regs_user.abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } } data->dyn_size += size; @@ -8784,6 +8875,10 @@ void perf_prepare_sample(struct perf_sample_data *data, u64 mask = event->attr.sample_regs_intr; size += hweight64(mask) * sizeof(u64); + if (event_has_simd_regs(event)) { + size += perf_update_xregs_size(event, true); + data->regs_intr.abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } } data->dyn_size += size; @@ -13160,6 +13255,12 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event) if (ret) goto err_pmu; + if (!(pmu->capabilities & PERF_PMU_CAP_SIMD_REGS) && + event_has_simd_regs(event)) { + ret = -EOPNOTSUPP; + goto err_destroy; + } + if (!(pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS) && event_has_extended_regs(event)) { ret = -EOPNOTSUPP; @@ -13656,7 +13757,8 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, attr->size = size; - if (attr->__reserved_1 || attr->__reserved_2 || attr->__reserved_3) + if (attr->__reserved_1 || attr->__reserved_2 || + attr->__reserved_3 || attr->__reserved_4) return -EINVAL; if (attr->sample_type & ~(PERF_SAMPLE_MAX-1)) @@ -13705,6 +13807,14 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, ret = perf_reg_validate(attr->sample_regs_user); if (ret) return ret; + if (attr->sample_simd_regs_enabled) { + ret = perf_simd_reg_validate(attr->sample_simd_vec_reg_qwords, + attr->sample_simd_vec_reg_user, + attr->sample_simd_pred_reg_qwords, + attr->sample_simd_pred_reg_user); + if (ret) + return ret; + } } if (attr->sample_type & PERF_SAMPLE_STACK_USER) { @@ -13725,8 +13835,19 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, if (!attr->sample_max_stack) attr->sample_max_stack = sysctl_perf_event_max_stack; - if (attr->sample_type & PERF_SAMPLE_REGS_INTR) + if (attr->sample_type & PERF_SAMPLE_REGS_INTR) { ret = perf_reg_validate(attr->sample_regs_intr); + if (ret) + return ret; + if (attr->sample_simd_regs_enabled) { + ret = perf_simd_reg_validate(attr->sample_simd_vec_reg_qwords, + attr->sample_simd_vec_reg_intr, + attr->sample_simd_pred_reg_qwords, + attr->sample_simd_pred_reg_intr); + if (ret) + return ret; + } + } #ifndef CONFIG_CGROUP_PERF if (attr->sample_type & PERF_SAMPLE_CGROUP) From e9d76ada769c45e5363306d22cb10b323f6dc4b5 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:21 +0800 Subject: [PATCH 0116/2286] perf/x86: Support XMM sampling using sample_simd_vec_reg_* fields Support sampling of XMM registers using the sample_simd_vec_reg_* fields. When sample_simd_regs_enabled is set, the original XMM space in the sample_regs_* field is treated as reserved. An INVAL error will be reported to user space if any bit is set in the original XMM space while sample_simd_regs_enabled is set. The perf_reg_value function requires ABI information to understand the layout of sample_regs. To accommodate this, a new abi field is introduced in the struct x86_perf_regs to represent ABI information. Additionally, the x86 specific perf_simd_reg_value() function is implemented to retrieve the XMM register values. XMM sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-14-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 84 +++++++++++++++++++++++---- arch/x86/events/intel/ds.c | 6 +- arch/x86/events/perf_event.h | 36 ++++++++++++ arch/x86/include/asm/perf_event.h | 1 + arch/x86/include/uapi/asm/perf_regs.h | 15 +++++ arch/x86/kernel/perf_regs.c | 75 +++++++++++++++++++++++- include/linux/perf_event.h | 1 + kernel/events/core.c | 2 +- 8 files changed, 204 insertions(+), 16 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 20140643e21c9d..f85163b95cfc6c 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -632,6 +632,34 @@ int x86_pmu_max_precise(struct pmu *pmu) return precise; } +static int pebs_simd_regs_validate(struct perf_event *event) +{ + u64 caps = hybrid(event->pmu, arch_pebs_cap).caps; + + if (event_needs_xmm(event) && + !x86_pmu.arch_pebs && !x86_pmu.intel_cap.pebs_baseline) + return -EINVAL; + if (event_needs_xmm(event) && + x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) + return -EINVAL; + + return 0; +} + +static int event_simd_regs_validate(struct perf_event *event) +{ + if (!get_ext_regs_buf(raw_smp_processor_id())) + return -ENOMEM; + /* sample_simd_regs_enabled repurposes legacy XMM reg-mask slots. */ + if (event_has_extended_regs(event)) + return -EINVAL; + if (event_needs_xmm(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_SSE)) + return -EINVAL; + + return 0; +} + int x86_pmu_hw_config(struct perf_event *event) { if (event->attr.precise_ip) { @@ -703,11 +731,21 @@ int x86_pmu_hw_config(struct perf_event *event) } if (event->attr.sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) { - /* - * Besides the general purpose registers, XMM registers may - * be collected as well. - */ - if (event_has_extended_regs(event)) { + int ret; + + if (event->attr.sample_simd_regs_enabled) { + if (!(event->pmu->capabilities & PERF_PMU_CAP_SIMD_REGS)) + return -EINVAL; + + if (event->attr.precise_ip) { + ret = pebs_simd_regs_validate(event); + if (ret) + return ret; + } + ret = event_simd_regs_validate(event); + if (ret) + return ret; + } else if (event_has_extended_regs(event)) { if (!(event->pmu->capabilities & PERF_PMU_CAP_EXTENDED_REGS)) return -EINVAL; @@ -721,6 +759,8 @@ int x86_pmu_hw_config(struct perf_event *event) } if (!get_ext_regs_buf(raw_smp_processor_id())) return -ENOMEM; + if (!(x86_pmu.ext_regs_mask & XFEATURE_MASK_SSE)) + return -EINVAL; } } @@ -1777,6 +1817,7 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) { struct x86_perf_regs *perf_regs = container_of(regs, struct x86_perf_regs, regs); + perf_regs->abi = PERF_SAMPLE_REGS_ABI_NONE; perf_regs->xmm_regs = NULL; } @@ -1797,14 +1838,15 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, /* * The x86 specific variant of perf_sample_regs_intr(). - * It would be extended to add more SIMD registers sampling support - * in later patches. + * Update data->regs_intr fields for extended registers (e.g., SIMD). */ static void x86_pmu_update_regs_intr(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs, bool exclude_kernel) { + struct x86_perf_regs *perf_regs; + if (exclude_kernel && !user_mode(regs)) { data->regs_intr.regs = NULL; data->regs_intr.abi = PERF_SAMPLE_REGS_ABI_NONE; @@ -1817,6 +1859,14 @@ static void x86_pmu_update_regs_intr(struct perf_event *event, if (data->regs_intr.regs) { data->dyn_size += hweight64(event->attr.sample_regs_intr) * sizeof(u64); + if (event_has_simd_regs(event)) { + data->dyn_size += perf_update_xregs_size(event, true); + data->regs_intr.abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } + + perf_regs = container_of(data->regs_intr.regs, + struct x86_perf_regs, regs); + perf_regs->abi = data->regs_intr.abi; } /* @@ -1878,8 +1928,15 @@ static void x86_pmu_update_regs_user(struct perf_event *event, } data->dyn_size += sizeof(u64); - if (data->regs_user.regs) + if (data->regs_user.regs) { data->dyn_size += hweight64(attr->sample_regs_user) * sizeof(u64); + if (event_has_simd_regs(event)) { + data->dyn_size += perf_update_xregs_size(event, false); + data->regs_user.abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } + + x86_regs_user->abi = data->regs_user.abi; + } /* * Set PERF_SAMPLE_REGS_USER to bypass perf_sample_regs_user() call @@ -1954,7 +2011,7 @@ static void x86_pmu_sample_xregs(struct perf_event *event, return; if ((sample_type & PERF_SAMPLE_REGS_INTR) && data->regs_intr.regs) { - if (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK) + if (__event_needs_xmm(event, PERF_SAMPLE_REGS_INTR)) intr_mask |= XFEATURE_MASK_SSE; intr_mask &= x86_pmu.ext_regs_mask; @@ -1962,7 +2019,7 @@ static void x86_pmu_sample_xregs(struct perf_event *event, } if ((sample_type & PERF_SAMPLE_REGS_USER) && data->regs_user.regs) { - if (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK) + if (__event_needs_xmm(event, PERF_SAMPLE_REGS_USER)) user_mask |= XFEATURE_MASK_SSE; user_mask &= x86_pmu.ext_regs_mask; @@ -1995,7 +2052,12 @@ void x86_pmu_update_perf_regs(struct perf_event *event, { u64 sample_type = event->attr.sample_type; - if (!event_has_extended_regs(event)) + if (!(sample_type & + (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER))) + return; + + if (!event_needs_xmm(event) && + !event_has_simd_regs(event)) return; if (sample_type & PERF_SAMPLE_REGS_INTR) { diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index d216234803b829..84caae35b86e11 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1733,8 +1733,10 @@ static u64 pebs_update_adaptive_cfg(struct perf_event *event) if (gprs || (attr->precise_ip < 2) || tsx_weight) pebs_data_cfg |= PEBS_DATACFG_GP; - if (event_has_extended_regs(event)) - pebs_data_cfg |= PEBS_DATACFG_XMMS; + if (sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) { + if (event_needs_xmm(event)) + pebs_data_cfg |= PEBS_DATACFG_XMMS; + } if (sample_type & PERF_SAMPLE_BRANCH_STACK) { /* diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index f1ab72094bacad..57a8263bd7bf4c 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -147,6 +147,42 @@ static inline bool is_acr_self_reload_event(struct perf_event *event) return test_bit(hwc->idx, (unsigned long *)&hwc->config1); } +static inline bool __event_needs_xmm(struct perf_event *event, u64 sample_type) +{ + if (event->attr.sample_simd_regs_enabled) { + if (event->attr.sample_simd_vec_reg_qwords < PERF_X86_XMM_QWORDS) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_simd_vec_reg_user > 0)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_simd_vec_reg_intr > 0)) + return true; + } else { + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_regs_user & PERF_REG_EXTENDED_MASK)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & PERF_REG_EXTENDED_MASK)) + return true; + } + + return false; +} + +static inline bool event_needs_xmm(struct perf_event *event) +{ + return __event_needs_xmm(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 619e0ae915e1e5..a2b2123d008e77 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -728,6 +728,7 @@ extern void perf_events_lapic_init(void); struct pt_regs; struct x86_perf_regs { struct pt_regs regs; + u64 abi; union { u64 *xmm_regs; u32 *xmm_space; /* for xsaves */ diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index 7c9d2bb3833bd2..edb35408e4ccab 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -2,6 +2,8 @@ #ifndef _ASM_X86_PERF_REGS_H #define _ASM_X86_PERF_REGS_H +#include + enum perf_event_x86_regs { PERF_REG_X86_AX, PERF_REG_X86_BX, @@ -55,4 +57,17 @@ enum perf_event_x86_regs { #define PERF_REG_EXTENDED_MASK (~((1ULL << PERF_REG_X86_XMM0) - 1)) +enum { + PERF_X86_SIMD_XMM_REGS = 16, + PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_XMM_REGS, +}; + +#define PERF_X86_SIMD_VEC_MASK __GENMASK_ULL(PERF_X86_SIMD_VEC_REGS_MAX - 1, 0) + +enum { + /* 1 qword = 8 bytes */ + PERF_X86_XMM_QWORDS = 2, + PERF_X86_SIMD_QWORDS_MAX = PERF_X86_XMM_QWORDS, +}; + #endif /* _ASM_X86_PERF_REGS_H */ diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 81204cb7f72393..bccf0fc2751167 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -63,6 +63,9 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) if (idx >= PERF_REG_X86_XMM0 && idx < PERF_REG_X86_XMM_MAX) { perf_regs = container_of(regs, struct x86_perf_regs, regs); + /* SIMD registers are moved to dedicated sample_simd_vec_reg */ + if (perf_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) + return 0; if (!perf_regs->xmm_regs) return 0; return perf_regs->xmm_regs[idx - PERF_REG_X86_XMM0]; @@ -74,6 +77,72 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return regs_get_register(regs, pt_regs_offset[idx]); } +u64 perf_simd_reg_value(struct pt_regs *regs, int idx, + u16 qwords_idx, bool pred) +{ + struct x86_perf_regs *perf_regs = + container_of(regs, struct x86_perf_regs, regs); + + if (!(perf_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD)) + return 0; + + if (pred) + return 0; + + if (WARN_ON_ONCE(idx >= PERF_X86_SIMD_VEC_REGS_MAX || + qwords_idx >= PERF_X86_SIMD_QWORDS_MAX)) + return 0; + + if (qwords_idx < PERF_X86_XMM_QWORDS) { + if (!perf_regs->xmm_regs) + return 0; + return perf_regs->xmm_regs[idx * PERF_X86_XMM_QWORDS + + qwords_idx]; + } + + return 0; +} + +int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, + u16 pred_qwords, u32 pred_mask) +{ + unsigned long mask; + u64 size; + + if (!vec_qwords && !pred_qwords) { + if (vec_mask || pred_mask) + return -EINVAL; + } + + if (vec_qwords) { + if (vec_qwords != PERF_X86_XMM_QWORDS) + return -EINVAL; + if (vec_mask & ~PERF_X86_SIMD_VEC_MASK) + return -EINVAL; + /* Only full-register sampling is allowed. */ + mask = vec_mask; + if (vec_qwords == PERF_X86_XMM_QWORDS && mask && + !bitmap_full(&mask, PERF_X86_SIMD_XMM_REGS)) + return -EINVAL; + } + + /* PRED registers are not supported yet. */ + if (pred_qwords) + return -EINVAL; + + size = sizeof(u64) * 4; + size += (hweight64(vec_mask) * vec_qwords + + hweight32(pred_mask) * pred_qwords) * sizeof(u64); + /* + * INTR_REGS and USR_REGS could be sampled simultaneously, + * so roughly restrict the size to half of U16_MAX. + */ + if (size >= U16_MAX / 2) + return -EINVAL; + + return 0; +} + #define PERF_REG_X86_RESERVED (((1ULL << PERF_REG_X86_XMM0) - 1) & \ ~((1ULL << PERF_REG_X86_MAX) - 1)) @@ -89,7 +158,8 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) int perf_reg_validate(u64 mask) { - if (!mask || (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED))) + /* The mask could be 0 if only the SIMD registers are interested */ + if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) return -EINVAL; return 0; @@ -108,7 +178,8 @@ u64 perf_reg_abi(struct task_struct *task) int perf_reg_validate(u64 mask) { - if (!mask || (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED))) + /* The mask could be 0 if only the SIMD registers are interested */ + if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) return -EINVAL; return 0; diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index a2e7eae268cdea..7cc2345052e096 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1485,6 +1485,7 @@ static inline void perf_clear_branch_entry_bitfields(struct perf_branch_entry *b br->reserved = 0; } +extern u64 perf_update_xregs_size(struct perf_event *event, bool intr); extern void perf_output_sample(struct perf_output_handle *handle, struct perf_event_header *header, struct perf_sample_data *data, diff --git a/kernel/events/core.c b/kernel/events/core.c index 3a87c6adcded9f..2f84f50183699c 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -8720,7 +8720,7 @@ static __always_inline u64 __cond_set(u64 flags, u64 s, u64 d) return d * !!(flags & s); } -static u64 perf_update_xregs_size(struct perf_event *event, bool intr) +u64 perf_update_xregs_size(struct perf_event *event, bool intr) { u16 pred_qwords = event->attr.sample_simd_pred_reg_qwords; u16 vec_qwords = event->attr.sample_simd_vec_reg_qwords; From b76210d3214730fd5d79387684ae0c56dd580229 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:22 +0800 Subject: [PATCH 0117/2286] perf/x86: Support YMM sampling using sample_simd_vec_reg_* fields Support sampling of YMM registers via the sample_simd_vec_reg_* fields. Each YMM register consists of 4 u64 words, assembled from two halves: XMM (the lower 2 u64 words) and YMMH (the upper 2 u64 words). Although both XMM and YMMH data can be retrieved with a single xsaves instruction, they are stored in separate locations. The perf_simd_reg_value() function is responsible for assembling these halves into a complete YMM register for output to userspace. Additionally, sample_simd_vec_reg_qwords should be set to 4 to indicate YMM sampling. YMM sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-15-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 29 +++++++++++++++++++++------ arch/x86/events/perf_event.h | 26 ++++++++++++++++++++++++ arch/x86/include/asm/perf_event.h | 4 ++++ arch/x86/include/uapi/asm/perf_regs.h | 6 ++++-- arch/x86/kernel/perf_regs.c | 13 +++++++++++- 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index f85163b95cfc6c..3039a311e733d2 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -642,6 +642,9 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) return -EINVAL; + /* PEBS does not support YMM registers sampling yet. */ + if (event_needs_ymm(event)) + return -EINVAL; return 0; } @@ -656,6 +659,9 @@ static int event_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_SSE)) return -EINVAL; + if (event_needs_ymm(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_YMM)) + return -EINVAL; return 0; } @@ -1819,6 +1825,7 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) perf_regs->abi = PERF_SAMPLE_REGS_ABI_NONE; perf_regs->xmm_regs = NULL; + perf_regs->ymmh_regs = NULL; } static void update_perf_regs(struct x86_perf_regs *perf_regs, @@ -1834,6 +1841,8 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, if (mask & XFEATURE_MASK_SSE) perf_regs->xmm_space = xsave->i387.xmm_space; + if (mask & XFEATURE_MASK_YMM) + perf_regs->ymmh = get_xsave_addr(xsave, XFEATURE_YMM); } /* @@ -1996,6 +2005,18 @@ static inline u64 x86_pmu_update_user_xregs(struct perf_sample_data *data, return user_mask; } +static u64 get_simd_sample_mask(struct perf_event *event, u64 sample_type) +{ + u64 mask = 0; + + if (__event_needs_xmm(event, sample_type)) + mask |= XFEATURE_MASK_SSE; + if (__event_needs_ymm(event, sample_type)) + mask |= XFEATURE_MASK_YMM; + + return mask; +} + static void x86_pmu_sample_xregs(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs, @@ -2011,17 +2032,13 @@ static void x86_pmu_sample_xregs(struct perf_event *event, return; if ((sample_type & PERF_SAMPLE_REGS_INTR) && data->regs_intr.regs) { - if (__event_needs_xmm(event, PERF_SAMPLE_REGS_INTR)) - intr_mask |= XFEATURE_MASK_SSE; - + intr_mask |= get_simd_sample_mask(event, PERF_SAMPLE_REGS_INTR); intr_mask &= x86_pmu.ext_regs_mask; intr_mask = from_pebs ? 0 : intr_mask; } if ((sample_type & PERF_SAMPLE_REGS_USER) && data->regs_user.regs) { - if (__event_needs_xmm(event, PERF_SAMPLE_REGS_USER)) - user_mask |= XFEATURE_MASK_SSE; - + user_mask |= get_simd_sample_mask(event, PERF_SAMPLE_REGS_USER); user_mask &= x86_pmu.ext_regs_mask; user_mask = x86_pmu_update_user_xregs(data, regs, user_mask, from_pebs); diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 57a8263bd7bf4c..423835e4a8647d 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -183,6 +183,32 @@ static inline bool event_needs_xmm(struct perf_event *event) PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); } +static inline bool __event_needs_ymm(struct perf_event *event, u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + if (event->attr.sample_simd_vec_reg_qwords < PERF_X86_YMM_QWORDS) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_simd_vec_reg_user > 0)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_simd_vec_reg_intr > 0)) + return true; + + return false; +} + +static inline bool event_needs_ymm(struct perf_event *event) +{ + return __event_needs_ymm(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index a2b2123d008e77..da77845e1f0290 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -733,6 +733,10 @@ struct x86_perf_regs { u64 *xmm_regs; u32 *xmm_space; /* for xsaves */ }; + union { + u64 *ymmh_regs; + struct ymmh_struct *ymmh; + }; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index edb35408e4ccab..d544f6d79871cd 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -59,7 +59,8 @@ enum perf_event_x86_regs { enum { PERF_X86_SIMD_XMM_REGS = 16, - PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_XMM_REGS, + PERF_X86_SIMD_YMM_REGS = 16, + PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_YMM_REGS, }; #define PERF_X86_SIMD_VEC_MASK __GENMASK_ULL(PERF_X86_SIMD_VEC_REGS_MAX - 1, 0) @@ -67,7 +68,8 @@ enum { enum { /* 1 qword = 8 bytes */ PERF_X86_XMM_QWORDS = 2, - PERF_X86_SIMD_QWORDS_MAX = PERF_X86_XMM_QWORDS, + PERF_X86_YMM_QWORDS = 4, + PERF_X86_SIMD_QWORDS_MAX = PERF_X86_YMM_QWORDS, }; #endif /* _ASM_X86_PERF_REGS_H */ diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index bccf0fc2751167..0076974498eee5 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -77,6 +77,8 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return regs_get_register(regs, pt_regs_offset[idx]); } +#define PERF_X86_YMMH_QWORDS (PERF_X86_YMM_QWORDS / 2) + u64 perf_simd_reg_value(struct pt_regs *regs, int idx, u16 qwords_idx, bool pred) { @@ -98,6 +100,11 @@ u64 perf_simd_reg_value(struct pt_regs *regs, int idx, return 0; return perf_regs->xmm_regs[idx * PERF_X86_XMM_QWORDS + qwords_idx]; + } else if (qwords_idx < PERF_X86_YMM_QWORDS) { + if (!perf_regs->ymmh_regs) + return 0; + return perf_regs->ymmh_regs[idx * PERF_X86_YMMH_QWORDS + + qwords_idx - PERF_X86_XMM_QWORDS]; } return 0; @@ -115,7 +122,8 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, } if (vec_qwords) { - if (vec_qwords != PERF_X86_XMM_QWORDS) + if (vec_qwords != PERF_X86_XMM_QWORDS && + vec_qwords != PERF_X86_YMM_QWORDS) return -EINVAL; if (vec_mask & ~PERF_X86_SIMD_VEC_MASK) return -EINVAL; @@ -124,6 +132,9 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, if (vec_qwords == PERF_X86_XMM_QWORDS && mask && !bitmap_full(&mask, PERF_X86_SIMD_XMM_REGS)) return -EINVAL; + if (vec_qwords == PERF_X86_YMM_QWORDS && mask && + !bitmap_full(&mask, PERF_X86_SIMD_YMM_REGS)) + return -EINVAL; } /* PRED registers are not supported yet. */ From 3807f6996a0bd9515ff69f3040d6e47f7b20e7a5 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:23 +0800 Subject: [PATCH 0118/2286] perf/x86: Support ZMM sampling using sample_simd_vec_reg_* fields Support sampling of ZMM registers via the sample_simd_vec_reg_* fields. Each ZMM register consists of 8 u64 words. Current x86 hardware supports up to 32 ZMM registers. For ZMM registers from ZMM0 to ZMM15, they are assembled from three parts: XMM (the lower 2 u64 words), YMMH (the middle 2 u64 words), and ZMMH (the upper 4 u64 words). The perf_simd_reg_value() function is responsible for assembling these three parts into a complete ZMM register for output to userspace. For ZMM registers ZMM16 to ZMM31, each register can be read as a whole and directly outputted to userspace. Additionally, sample_simd_vec_reg_qwords should be set to 8 to indicate ZMM sampling. ZMM sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-16-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 22 ++++++++++- arch/x86/events/perf_event.h | 54 +++++++++++++++++++++++++++ arch/x86/include/asm/perf_event.h | 8 ++++ arch/x86/include/uapi/asm/perf_regs.h | 8 +++- arch/x86/kernel/perf_regs.c | 19 +++++++++- 5 files changed, 106 insertions(+), 5 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 3039a311e733d2..b0e3f1b9fa19a9 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -642,8 +642,10 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) return -EINVAL; - /* PEBS does not support YMM registers sampling yet. */ - if (event_needs_ymm(event)) + /* PEBS does not support YMM/ZMM registers sampling yet. */ + if (event_needs_ymm(event) || + event_needs_low16_zmm(event) || + event_needs_high16_zmm(event)) return -EINVAL; return 0; @@ -662,6 +664,12 @@ static int event_simd_regs_validate(struct perf_event *event) if (event_needs_ymm(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_YMM)) return -EINVAL; + if (event_needs_low16_zmm(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_ZMM_Hi256)) + return -EINVAL; + if (event_needs_high16_zmm(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_Hi16_ZMM)) + return -EINVAL; return 0; } @@ -1826,6 +1834,8 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) perf_regs->abi = PERF_SAMPLE_REGS_ABI_NONE; perf_regs->xmm_regs = NULL; perf_regs->ymmh_regs = NULL; + perf_regs->zmmh_regs = NULL; + perf_regs->h16zmm_regs = NULL; } static void update_perf_regs(struct x86_perf_regs *perf_regs, @@ -1843,6 +1853,10 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, perf_regs->xmm_space = xsave->i387.xmm_space; if (mask & XFEATURE_MASK_YMM) perf_regs->ymmh = get_xsave_addr(xsave, XFEATURE_YMM); + if (mask & XFEATURE_MASK_ZMM_Hi256) + perf_regs->zmmh = get_xsave_addr(xsave, XFEATURE_ZMM_Hi256); + if (mask & XFEATURE_MASK_Hi16_ZMM) + perf_regs->h16zmm = get_xsave_addr(xsave, XFEATURE_Hi16_ZMM); } /* @@ -2013,6 +2027,10 @@ static u64 get_simd_sample_mask(struct perf_event *event, u64 sample_type) mask |= XFEATURE_MASK_SSE; if (__event_needs_ymm(event, sample_type)) mask |= XFEATURE_MASK_YMM; + if (__event_needs_low16_zmm(event, sample_type)) + mask |= XFEATURE_MASK_ZMM_Hi256; + if (__event_needs_high16_zmm(event, sample_type)) + mask |= XFEATURE_MASK_Hi16_ZMM; return mask; } diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 423835e4a8647d..8ee69ec6af9e7c 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -209,6 +209,60 @@ static inline bool event_needs_ymm(struct perf_event *event) PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); } +static inline bool __event_needs_low16_zmm(struct perf_event *event, + u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + if (event->attr.sample_simd_vec_reg_qwords < PERF_X86_ZMM_QWORDS) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_simd_vec_reg_user > 0)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_simd_vec_reg_intr > 0)) + return true; + + return false; +} + +static inline bool event_needs_low16_zmm(struct perf_event *event) +{ + return __event_needs_low16_zmm(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + +static inline bool __event_needs_high16_zmm(struct perf_event *event, + u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + if (event->attr.sample_simd_vec_reg_qwords < PERF_X86_ZMM_QWORDS) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (fls64(event->attr.sample_simd_vec_reg_user) > PERF_X86_H16ZMM_BASE)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (fls64(event->attr.sample_simd_vec_reg_intr) > PERF_X86_H16ZMM_BASE)) + return true; + + return false; +} + +static inline bool event_needs_high16_zmm(struct perf_event *event) +{ + return __event_needs_high16_zmm(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index da77845e1f0290..75394c4e8bc379 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -737,6 +737,14 @@ struct x86_perf_regs { u64 *ymmh_regs; struct ymmh_struct *ymmh; }; + union { + u64 *zmmh_regs; + struct avx_512_zmm_uppers_state *zmmh; + }; + union { + u64 *h16zmm_regs; + struct avx_512_hi16_state *h16zmm; + }; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index d544f6d79871cd..b88d0b6822fd07 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -60,16 +60,20 @@ enum perf_event_x86_regs { enum { PERF_X86_SIMD_XMM_REGS = 16, PERF_X86_SIMD_YMM_REGS = 16, - PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_YMM_REGS, + PERF_X86_SIMD_ZMM_REGS = 32, + PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_ZMM_REGS, }; #define PERF_X86_SIMD_VEC_MASK __GENMASK_ULL(PERF_X86_SIMD_VEC_REGS_MAX - 1, 0) +#define PERF_X86_H16ZMM_BASE 16 + enum { /* 1 qword = 8 bytes */ PERF_X86_XMM_QWORDS = 2, PERF_X86_YMM_QWORDS = 4, - PERF_X86_SIMD_QWORDS_MAX = PERF_X86_YMM_QWORDS, + PERF_X86_ZMM_QWORDS = 8, + PERF_X86_SIMD_QWORDS_MAX = PERF_X86_ZMM_QWORDS, }; #endif /* _ASM_X86_PERF_REGS_H */ diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 0076974498eee5..93370d46578606 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -78,6 +78,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) } #define PERF_X86_YMMH_QWORDS (PERF_X86_YMM_QWORDS / 2) +#define PERF_X86_ZMMH_QWORDS (PERF_X86_ZMM_QWORDS / 2) u64 perf_simd_reg_value(struct pt_regs *regs, int idx, u16 qwords_idx, bool pred) @@ -95,6 +96,13 @@ u64 perf_simd_reg_value(struct pt_regs *regs, int idx, qwords_idx >= PERF_X86_SIMD_QWORDS_MAX)) return 0; + if (idx >= PERF_X86_H16ZMM_BASE) { + if (!perf_regs->h16zmm_regs) + return 0; + return perf_regs->h16zmm_regs[(idx - PERF_X86_H16ZMM_BASE) * + PERF_X86_ZMM_QWORDS + qwords_idx]; + } + if (qwords_idx < PERF_X86_XMM_QWORDS) { if (!perf_regs->xmm_regs) return 0; @@ -105,6 +113,11 @@ u64 perf_simd_reg_value(struct pt_regs *regs, int idx, return 0; return perf_regs->ymmh_regs[idx * PERF_X86_YMMH_QWORDS + qwords_idx - PERF_X86_XMM_QWORDS]; + } else if (qwords_idx < PERF_X86_ZMM_QWORDS) { + if (!perf_regs->zmmh_regs) + return 0; + return perf_regs->zmmh_regs[idx * PERF_X86_ZMMH_QWORDS + + qwords_idx - PERF_X86_YMM_QWORDS]; } return 0; @@ -123,7 +136,8 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, if (vec_qwords) { if (vec_qwords != PERF_X86_XMM_QWORDS && - vec_qwords != PERF_X86_YMM_QWORDS) + vec_qwords != PERF_X86_YMM_QWORDS && + vec_qwords != PERF_X86_ZMM_QWORDS) return -EINVAL; if (vec_mask & ~PERF_X86_SIMD_VEC_MASK) return -EINVAL; @@ -135,6 +149,9 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, if (vec_qwords == PERF_X86_YMM_QWORDS && mask && !bitmap_full(&mask, PERF_X86_SIMD_YMM_REGS)) return -EINVAL; + if (vec_qwords == PERF_X86_ZMM_QWORDS && mask && + !bitmap_full(&mask, PERF_X86_SIMD_ZMM_REGS)) + return -EINVAL; } /* PRED registers are not supported yet. */ From 74d55a827e31bc8a75fbc530951c6266d043ecc0 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:24 +0800 Subject: [PATCH 0119/2286] perf/x86: Support OPMASK sampling using sample_simd_pred_reg_* fields Support sampling of OPMASK registers via the sample_simd_pred_reg_* fields. Each OPMASK register consists of 1 u64 word. Current x86 hardware supports 8 OPMASK registers. The perf_simd_reg_value() function is responsible for outputting OPMASK value to userspace. Additionally, sample_simd_pred_reg_qwords should be set to 1 to indicate OPMASK sampling. OPMASK sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-17-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 13 +++++++++++-- arch/x86/events/perf_event.h | 27 +++++++++++++++++++++++++++ arch/x86/include/asm/perf_event.h | 4 ++++ arch/x86/include/uapi/asm/perf_regs.h | 5 +++++ arch/x86/kernel/perf_regs.c | 24 +++++++++++++++++++----- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index b0e3f1b9fa19a9..9bfb1810a9fe52 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -642,10 +642,11 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) return -EINVAL; - /* PEBS does not support YMM/ZMM registers sampling yet. */ + /* PEBS does not support YMM/ZMM/OPMASK registers sampling yet. */ if (event_needs_ymm(event) || event_needs_low16_zmm(event) || - event_needs_high16_zmm(event)) + event_needs_high16_zmm(event) || + event_needs_opmask(event)) return -EINVAL; return 0; @@ -670,6 +671,9 @@ static int event_simd_regs_validate(struct perf_event *event) if (event_needs_high16_zmm(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_Hi16_ZMM)) return -EINVAL; + if (event_needs_opmask(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_OPMASK)) + return -EINVAL; return 0; } @@ -1836,6 +1840,7 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) perf_regs->ymmh_regs = NULL; perf_regs->zmmh_regs = NULL; perf_regs->h16zmm_regs = NULL; + perf_regs->opmask_regs = NULL; } static void update_perf_regs(struct x86_perf_regs *perf_regs, @@ -1857,6 +1862,8 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, perf_regs->zmmh = get_xsave_addr(xsave, XFEATURE_ZMM_Hi256); if (mask & XFEATURE_MASK_Hi16_ZMM) perf_regs->h16zmm = get_xsave_addr(xsave, XFEATURE_Hi16_ZMM); + if (mask & XFEATURE_MASK_OPMASK) + perf_regs->opmask = get_xsave_addr(xsave, XFEATURE_OPMASK); } /* @@ -2031,6 +2038,8 @@ static u64 get_simd_sample_mask(struct perf_event *event, u64 sample_type) mask |= XFEATURE_MASK_ZMM_Hi256; if (__event_needs_high16_zmm(event, sample_type)) mask |= XFEATURE_MASK_Hi16_ZMM; + if (__event_needs_opmask(event, sample_type)) + mask |= XFEATURE_MASK_OPMASK; return mask; } diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 8ee69ec6af9e7c..8a74b7d2c130c7 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -263,6 +263,33 @@ static inline bool event_needs_high16_zmm(struct perf_event *event) PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); } +static inline bool __event_needs_opmask(struct perf_event *event, + u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + if (event->attr.sample_simd_pred_reg_qwords != PERF_X86_OPMASK_QWORDS) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_simd_pred_reg_user > 0)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_simd_pred_reg_intr > 0)) + return true; + + return false; +} + +static inline bool event_needs_opmask(struct perf_event *event) +{ + return __event_needs_opmask(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 75394c4e8bc379..49112e097e996a 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -745,6 +745,10 @@ struct x86_perf_regs { u64 *h16zmm_regs; struct avx_512_hi16_state *h16zmm; }; + union { + u64 *opmask_regs; + struct avx_512_opmask_state *opmask; + }; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index b88d0b6822fd07..61aec60623f164 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -62,14 +62,19 @@ enum { PERF_X86_SIMD_YMM_REGS = 16, PERF_X86_SIMD_ZMM_REGS = 32, PERF_X86_SIMD_VEC_REGS_MAX = PERF_X86_SIMD_ZMM_REGS, + + PERF_X86_SIMD_OPMASK_REGS = 8, + PERF_X86_SIMD_PRED_REGS_MAX = PERF_X86_SIMD_OPMASK_REGS, }; +#define PERF_X86_SIMD_PRED_MASK __GENMASK(PERF_X86_SIMD_PRED_REGS_MAX - 1, 0) #define PERF_X86_SIMD_VEC_MASK __GENMASK_ULL(PERF_X86_SIMD_VEC_REGS_MAX - 1, 0) #define PERF_X86_H16ZMM_BASE 16 enum { /* 1 qword = 8 bytes */ + PERF_X86_OPMASK_QWORDS = 1, PERF_X86_XMM_QWORDS = 2, PERF_X86_YMM_QWORDS = 4, PERF_X86_ZMM_QWORDS = 8, diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 93370d46578606..87297da5e44b34 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -89,8 +89,14 @@ u64 perf_simd_reg_value(struct pt_regs *regs, int idx, if (!(perf_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD)) return 0; - if (pred) - return 0; + if (pred) { + if (WARN_ON_ONCE(idx >= PERF_X86_SIMD_PRED_REGS_MAX || + qwords_idx >= PERF_X86_OPMASK_QWORDS)) + return 0; + if (!perf_regs->opmask_regs) + return 0; + return perf_regs->opmask_regs[idx]; + } if (WARN_ON_ONCE(idx >= PERF_X86_SIMD_VEC_REGS_MAX || qwords_idx >= PERF_X86_SIMD_QWORDS_MAX)) @@ -154,9 +160,17 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, return -EINVAL; } - /* PRED registers are not supported yet. */ - if (pred_qwords) - return -EINVAL; + if (pred_qwords) { + if (pred_qwords != PERF_X86_OPMASK_QWORDS) + return -EINVAL; + if (pred_mask & ~PERF_X86_SIMD_PRED_MASK) + return -EINVAL; + /* Only full-register sampling is allowed. */ + mask = pred_mask; + if (pred_qwords == PERF_X86_OPMASK_QWORDS && mask && + !bitmap_full(&mask, PERF_X86_SIMD_OPMASK_REGS)) + return -EINVAL; + } size = sizeof(u64) * 4; size += (hweight64(vec_mask) * vec_qwords + From a2c64c74c02957ca11975b5355e70210f7b934ad Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:25 +0800 Subject: [PATCH 0120/2286] perf: Enhance perf_reg_validate() with simd_enabled argument The upcoming patch will support x86 APX eGPRs sampling by using the reclaimed XMM register space to represent eGPRs in sample_regs_* fields. To differentiate between XMM and eGPRs in sample_regs_* fields, an additional argument, simd_enabled, is introduced to the perf_reg_validate() helper. If simd_enabled is set to 1, it indicates that eGPRs are represented in sample_regs_* fields for the x86 platform; otherwise, XMM registers are represented. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-18-dapeng1.mi@linux.intel.com --- arch/arm/kernel/perf_regs.c | 2 +- arch/arm64/kernel/perf_regs.c | 2 +- arch/csky/kernel/perf_regs.c | 2 +- arch/loongarch/kernel/perf_regs.c | 2 +- arch/mips/kernel/perf_regs.c | 2 +- arch/parisc/kernel/perf_regs.c | 2 +- arch/powerpc/perf/perf_regs.c | 2 +- arch/riscv/kernel/perf_regs.c | 2 +- arch/s390/kernel/perf_regs.c | 2 +- arch/x86/kernel/perf_regs.c | 4 ++-- include/linux/perf_regs.h | 2 +- kernel/events/core.c | 8 +++++--- 12 files changed, 17 insertions(+), 15 deletions(-) diff --git a/arch/arm/kernel/perf_regs.c b/arch/arm/kernel/perf_regs.c index d575a4c3ca56f1..838d701adf4d88 100644 --- a/arch/arm/kernel/perf_regs.c +++ b/arch/arm/kernel/perf_regs.c @@ -18,7 +18,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1ULL << PERF_REG_ARM_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/arm64/kernel/perf_regs.c b/arch/arm64/kernel/perf_regs.c index 70e2f13f587f59..71a3e0238de419 100644 --- a/arch/arm64/kernel/perf_regs.c +++ b/arch/arm64/kernel/perf_regs.c @@ -77,7 +77,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1ULL << PERF_REG_ARM64_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { u64 reserved_mask = REG_RESERVED; diff --git a/arch/csky/kernel/perf_regs.c b/arch/csky/kernel/perf_regs.c index 94601f37b596d2..c932a96afc5642 100644 --- a/arch/csky/kernel/perf_regs.c +++ b/arch/csky/kernel/perf_regs.c @@ -18,7 +18,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1ULL << PERF_REG_CSKY_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/loongarch/kernel/perf_regs.c b/arch/loongarch/kernel/perf_regs.c index 8dd604f017454f..164514f40ae00a 100644 --- a/arch/loongarch/kernel/perf_regs.c +++ b/arch/loongarch/kernel/perf_regs.c @@ -25,7 +25,7 @@ u64 perf_reg_abi(struct task_struct *tsk) } #endif /* CONFIG_32BIT */ -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask) return -EINVAL; diff --git a/arch/mips/kernel/perf_regs.c b/arch/mips/kernel/perf_regs.c index 7736d3c5ebd2c8..00a5201dbd5db0 100644 --- a/arch/mips/kernel/perf_regs.c +++ b/arch/mips/kernel/perf_regs.c @@ -28,7 +28,7 @@ u64 perf_reg_abi(struct task_struct *tsk) } #endif /* CONFIG_32BIT */ -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask) return -EINVAL; diff --git a/arch/parisc/kernel/perf_regs.c b/arch/parisc/kernel/perf_regs.c index b9fe1f2fcb9b18..4f21aab5405ccc 100644 --- a/arch/parisc/kernel/perf_regs.c +++ b/arch/parisc/kernel/perf_regs.c @@ -34,7 +34,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1ULL << PERF_REG_PARISC_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/powerpc/perf/perf_regs.c b/arch/powerpc/perf/perf_regs.c index 350dccb0143c77..a01d8a903640d4 100644 --- a/arch/powerpc/perf/perf_regs.c +++ b/arch/powerpc/perf/perf_regs.c @@ -125,7 +125,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return regs_get_register(regs, pt_regs_offset[idx]); } -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/riscv/kernel/perf_regs.c b/arch/riscv/kernel/perf_regs.c index 3bba8deababbf4..1ecc8760b88b51 100644 --- a/arch/riscv/kernel/perf_regs.c +++ b/arch/riscv/kernel/perf_regs.c @@ -18,7 +18,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1ULL << PERF_REG_RISCV_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/s390/kernel/perf_regs.c b/arch/s390/kernel/perf_regs.c index 7b305f1456f825..6496fd23c54077 100644 --- a/arch/s390/kernel/perf_regs.c +++ b/arch/s390/kernel/perf_regs.c @@ -34,7 +34,7 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) #define REG_RESERVED (~((1UL << PERF_REG_S390_MAX) - 1)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { if (!mask || mask & REG_RESERVED) return -EINVAL; diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 87297da5e44b34..494e06501210ed 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -198,7 +198,7 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, (1ULL << PERF_REG_X86_R14) | \ (1ULL << PERF_REG_X86_R15)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { /* The mask could be 0 if only the SIMD registers are interested */ if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) @@ -218,7 +218,7 @@ u64 perf_reg_abi(struct task_struct *task) (1ULL << PERF_REG_X86_FS) | \ (1ULL << PERF_REG_X86_GS)) -int perf_reg_validate(u64 mask) +int perf_reg_validate(u64 mask, bool simd_enabled) { /* The mask could be 0 if only the SIMD registers are interested */ if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) diff --git a/include/linux/perf_regs.h b/include/linux/perf_regs.h index 518f28c6a7d4be..09dbc2fc38592f 100644 --- a/include/linux/perf_regs.h +++ b/include/linux/perf_regs.h @@ -10,7 +10,7 @@ struct perf_regs { }; u64 perf_reg_value(struct pt_regs *regs, int idx); -int perf_reg_validate(u64 mask); +int perf_reg_validate(u64 mask, bool simd_enabled); u64 perf_reg_abi(struct task_struct *task); void perf_get_regs_user(struct perf_regs *regs_user, struct pt_regs *regs); diff --git a/kernel/events/core.c b/kernel/events/core.c index 2f84f50183699c..e053d8c8a79411 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -7823,7 +7823,7 @@ u64 __weak perf_reg_value(struct pt_regs *regs, int idx) return 0; } -int __weak perf_reg_validate(u64 mask) +int __weak perf_reg_validate(u64 mask, bool simd_enabled) { return mask ? -ENOSYS : 0; } @@ -13804,7 +13804,8 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, } if (attr->sample_type & PERF_SAMPLE_REGS_USER) { - ret = perf_reg_validate(attr->sample_regs_user); + ret = perf_reg_validate(attr->sample_regs_user, + attr->sample_simd_regs_enabled); if (ret) return ret; if (attr->sample_simd_regs_enabled) { @@ -13836,7 +13837,8 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, attr->sample_max_stack = sysctl_perf_event_max_stack; if (attr->sample_type & PERF_SAMPLE_REGS_INTR) { - ret = perf_reg_validate(attr->sample_regs_intr); + ret = perf_reg_validate(attr->sample_regs_intr, + attr->sample_simd_regs_enabled); if (ret) return ret; if (attr->sample_simd_regs_enabled) { From 578460d9af8d5816eff59642a8bb11945026b8d1 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:26 +0800 Subject: [PATCH 0121/2286] perf/x86: Support eGPRs sampling using sample_regs_* fields Support sampling of APX eGPRs (R16 ~ R31) via the sample_regs_* fields. To sample eGPRs, the sample_simd_regs_enabled field must be set. This allows the spare space (reclaimed from the original XMM space) in the sample_regs_* fields to be used for representing eGPRs. The perf_reg_value() function needs to check if the PERF_SAMPLE_REGS_ABI_SIMD flag is set first, and then determine whether to output eGPRs or legacy XMM registers to userspace. The perf_reg_validate() function first checks the simd_enabled argument to determine if the eGPRs bitmap is represented in sample_regs_* fields. It then validates the eGPRs bitmap accordingly. Currently, eGPRs sampling is only supported on the x86_64 architecture, as APX is only available on x86_64 platforms. APX eGPRs sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Suggested-by: Peter Zijlstra (Intel) Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-19-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 25 +++++++++++--- arch/x86/events/intel/core.c | 10 ++++-- arch/x86/events/perf_event.h | 25 ++++++++++++++ arch/x86/include/asm/perf_event.h | 4 +++ arch/x86/include/uapi/asm/perf_regs.h | 26 ++++++++++++++ arch/x86/kernel/perf_regs.c | 50 +++++++++++++++++---------- 6 files changed, 115 insertions(+), 25 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 9bfb1810a9fe52..9cd79ac5570e3b 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -642,11 +642,12 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) return -EINVAL; - /* PEBS does not support YMM/ZMM/OPMASK registers sampling yet. */ + /* PEBS does not support YMM/ZMM/OPMASK/eGPR registers sampling yet. */ if (event_needs_ymm(event) || event_needs_low16_zmm(event) || event_needs_high16_zmm(event) || - event_needs_opmask(event)) + event_needs_opmask(event) || + event_needs_egprs(event)) return -EINVAL; return 0; @@ -654,10 +655,21 @@ static int pebs_simd_regs_validate(struct perf_event *event) static int event_simd_regs_validate(struct perf_event *event) { + u64 reserved = ~GENMASK_ULL(PERF_REG_MISC_MAX - 1, 0); + if (!get_ext_regs_buf(raw_smp_processor_id())) return -ENOMEM; - /* sample_simd_regs_enabled repurposes legacy XMM reg-mask slots. */ - if (event_has_extended_regs(event)) + /* + * The XMM space in the perf_event_x86_regs is reclaimed + * for eGPRs and other general registers. + */ + if (((event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & reserved)) || + ((event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_regs_user & reserved))) + return -EINVAL; + if (event_needs_egprs(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_APX)) return -EINVAL; if (event_needs_xmm(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_SSE)) @@ -1841,6 +1853,7 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) perf_regs->zmmh_regs = NULL; perf_regs->h16zmm_regs = NULL; perf_regs->opmask_regs = NULL; + perf_regs->egpr_regs = NULL; } static void update_perf_regs(struct x86_perf_regs *perf_regs, @@ -1864,6 +1877,8 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, perf_regs->h16zmm = get_xsave_addr(xsave, XFEATURE_Hi16_ZMM); if (mask & XFEATURE_MASK_OPMASK) perf_regs->opmask = get_xsave_addr(xsave, XFEATURE_OPMASK); + if (mask & XFEATURE_MASK_APX) + perf_regs->egpr = get_xsave_addr(xsave, XFEATURE_APX); } /* @@ -2040,6 +2055,8 @@ static u64 get_simd_sample_mask(struct perf_event *event, u64 sample_type) mask |= XFEATURE_MASK_Hi16_ZMM; if (__event_needs_opmask(event, sample_type)) mask |= XFEATURE_MASK_OPMASK; + if (__event_needs_egprs(event, sample_type)) + mask |= XFEATURE_MASK_APX; return mask; } diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 5cb80833c7edd0..b2aa0bc7c97e7e 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -4695,15 +4695,19 @@ static void intel_pebs_aliases_skl(struct perf_event *event) static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) { unsigned long flags = x86_pmu.large_pebs_flags; - u64 gprs_mask = PEBS_GP_REGS | PERF_REG_EXTENDED_MASK; + u64 gprs_mask = event->attr.sample_simd_regs_enabled ? + PEBS_GP_REGS | PERF_X86_EGPRS_MASK : + PEBS_GP_REGS | PERF_REG_EXTENDED_MASK; if (event->attr.use_clockid) flags &= ~PERF_SAMPLE_TIME; if (!event->attr.exclude_kernel) flags &= ~PERF_SAMPLE_REGS_USER; - if (event->attr.sample_regs_user & ~gprs_mask) + if ((event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_regs_user & ~gprs_mask)) flags &= ~PERF_SAMPLE_REGS_USER; - if (event->attr.sample_regs_intr & ~gprs_mask) + if ((event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & ~gprs_mask)) flags &= ~PERF_SAMPLE_REGS_INTR; return flags; } diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 8a74b7d2c130c7..c052a38b8733d4 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -290,6 +290,31 @@ static inline bool event_needs_opmask(struct perf_event *event) PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); } +static inline bool __event_needs_egprs(struct perf_event *event, + u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_regs_user & PERF_X86_EGPRS_MASK)) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & PERF_X86_EGPRS_MASK)) + return true; + + return false; +} + +static inline bool event_needs_egprs(struct perf_event *event) +{ + return __event_needs_egprs(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 49112e097e996a..bc05f8c1746432 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -749,6 +749,10 @@ struct x86_perf_regs { u64 *opmask_regs; struct avx_512_opmask_state *opmask; }; + union { + u64 *egpr_regs; + struct apx_state *egpr; + }; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index 61aec60623f164..977831bd7a9de8 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -29,9 +29,34 @@ enum perf_event_x86_regs { PERF_REG_X86_R13, PERF_REG_X86_R14, PERF_REG_X86_R15, + /* + * The eGPRs and XMM have overlaps. Only one can be used + * at a time. The ABI PERF_SAMPLE_REGS_ABI_SIMD is used to + * distinguish which one is used. If PERF_SAMPLE_REGS_ABI_SIMD + * is set, then eGPRs is used, otherwise, XMM is used. + * + * Extended GPRs (eGPRs) + */ + PERF_REG_X86_R16, + PERF_REG_X86_R17, + PERF_REG_X86_R18, + PERF_REG_X86_R19, + PERF_REG_X86_R20, + PERF_REG_X86_R21, + PERF_REG_X86_R22, + PERF_REG_X86_R23, + PERF_REG_X86_R24, + PERF_REG_X86_R25, + PERF_REG_X86_R26, + PERF_REG_X86_R27, + PERF_REG_X86_R28, + PERF_REG_X86_R29, + PERF_REG_X86_R30, + PERF_REG_X86_R31, /* These are the limits for the GPRs. */ PERF_REG_X86_32_MAX = PERF_REG_X86_GS + 1, PERF_REG_X86_64_MAX = PERF_REG_X86_R15 + 1, + PERF_REG_MISC_MAX = PERF_REG_X86_R31 + 1, /* These all need two bits set because they are 128bit */ PERF_REG_X86_XMM0 = 32, @@ -56,6 +81,7 @@ enum perf_event_x86_regs { }; #define PERF_REG_EXTENDED_MASK (~((1ULL << PERF_REG_X86_XMM0) - 1)) +#define PERF_X86_EGPRS_MASK __GENMASK_ULL(PERF_REG_X86_R31, PERF_REG_X86_R16) enum { PERF_X86_SIMD_XMM_REGS = 16, diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 494e06501210ed..7d4233ed859cd1 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -61,14 +61,24 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) { struct x86_perf_regs *perf_regs; - if (idx >= PERF_REG_X86_XMM0 && idx < PERF_REG_X86_XMM_MAX) { + if (idx > PERF_REG_X86_R15) { perf_regs = container_of(regs, struct x86_perf_regs, regs); - /* SIMD registers are moved to dedicated sample_simd_vec_reg */ - if (perf_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) + if (perf_regs->abi == PERF_SAMPLE_REGS_ABI_NONE) return 0; - if (!perf_regs->xmm_regs) - return 0; - return perf_regs->xmm_regs[idx - PERF_REG_X86_XMM0]; + + if (perf_regs->abi & PERF_SAMPLE_REGS_ABI_SIMD) { + if (idx <= PERF_REG_X86_R31) { + if (!perf_regs->egpr_regs) + return 0; + return perf_regs->egpr_regs[idx - PERF_REG_X86_R16]; + } + } else { + if (idx >= PERF_REG_X86_XMM0 && idx < PERF_REG_X86_XMM_MAX) { + if (!perf_regs->xmm_regs) + return 0; + return perf_regs->xmm_regs[idx - PERF_REG_X86_XMM0]; + } + } } if (WARN_ON_ONCE(idx >= ARRAY_SIZE(pt_regs_offset))) @@ -185,23 +195,22 @@ int perf_simd_reg_validate(u16 vec_qwords, u64 vec_mask, return 0; } -#define PERF_REG_X86_RESERVED (((1ULL << PERF_REG_X86_XMM0) - 1) & \ - ~((1ULL << PERF_REG_X86_MAX) - 1)) +#define PERF_REG_X86_RESERVED (GENMASK_ULL(PERF_REG_X86_XMM0 - 1, PERF_REG_X86_AX) & \ + ~GENMASK_ULL(PERF_REG_X86_R15, PERF_REG_X86_AX)) +#define PERF_REG_X86_EXT_RESERVED (~GENMASK_ULL(PERF_REG_MISC_MAX - 1, PERF_REG_X86_AX)) #ifdef CONFIG_X86_32 -#define REG_NOSUPPORT ((1ULL << PERF_REG_X86_R8) | \ - (1ULL << PERF_REG_X86_R9) | \ - (1ULL << PERF_REG_X86_R10) | \ - (1ULL << PERF_REG_X86_R11) | \ - (1ULL << PERF_REG_X86_R12) | \ - (1ULL << PERF_REG_X86_R13) | \ - (1ULL << PERF_REG_X86_R14) | \ - (1ULL << PERF_REG_X86_R15)) +#define REG_NOSUPPORT GENMASK_ULL(PERF_REG_X86_R15, PERF_REG_X86_R8) int perf_reg_validate(u64 mask, bool simd_enabled) { + if (!simd_enabled && + (!mask || (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)))) + return -EINVAL; + /* The mask could be 0 if only the SIMD registers are interested */ - if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) + if (simd_enabled && + (mask & ~GENMASK_ULL(PERF_REG_X86_GS, PERF_REG_X86_AX))) return -EINVAL; return 0; @@ -220,8 +229,13 @@ u64 perf_reg_abi(struct task_struct *task) int perf_reg_validate(u64 mask, bool simd_enabled) { + if (!simd_enabled && + (!mask || (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)))) + return -EINVAL; + /* The mask could be 0 if only the SIMD registers are interested */ - if (mask & (REG_NOSUPPORT | PERF_REG_X86_RESERVED)) + if (simd_enabled && + (mask & (REG_NOSUPPORT | PERF_REG_X86_EXT_RESERVED))) return -EINVAL; return 0; From 098cdd582a9baa7e4bcb26e80a22ebfddc74293a Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:27 +0800 Subject: [PATCH 0122/2286] perf/x86: Support SSP sampling using sample_regs_* fields Support sampling of CET SSP register via the sample_regs_* fields. To sample SSP, the sample_simd_regs_enabled field must be set. This allows the spare space (reclaimed from the original XMM space) in the sample_regs_* fields to be used for representing SSP. Similar to eGPRs sampling, the perf_reg_value() function needs to check if the PERF_SAMPLE_REGS_ABI_SIMD flag is set first, and then determine whether to output SSP or legacy XMM registers to userspace. Additionally, arch-PEBS supports sampling SSP, which is placed into the GPRs group. Also enables arch-PEBS-based SSP sampling. Currently, SSP sampling is only supported on the x86_64 architecture, as CET is only available on x86_64 platforms. SSP sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-20-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 15 +++++++++++++++ arch/x86/events/intel/core.c | 3 ++- arch/x86/events/intel/ds.c | 13 +++++++++++-- arch/x86/events/perf_event.h | 25 +++++++++++++++++++++++++ arch/x86/include/asm/perf_event.h | 1 + arch/x86/include/uapi/asm/perf_regs.h | 7 ++++--- arch/x86/kernel/perf_regs.c | 5 +++++ 7 files changed, 63 insertions(+), 6 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 9cd79ac5570e3b..caa5a0fb3ed891 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -642,6 +642,10 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_xmm(event) && x86_pmu.arch_pebs && !(caps & ARCH_PEBS_VECR_XMM)) return -EINVAL; + + if (event_needs_ssp(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_GPR))) + return -EINVAL; /* PEBS does not support YMM/ZMM/OPMASK/eGPR registers sampling yet. */ if (event_needs_ymm(event) || event_needs_low16_zmm(event) || @@ -671,6 +675,9 @@ static int event_simd_regs_validate(struct perf_event *event) if (event_needs_egprs(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_APX)) return -EINVAL; + if (event_needs_ssp(event) && + !(x86_pmu.ext_regs_mask & XFEATURE_MASK_CET_USER)) + return -EINVAL; if (event_needs_xmm(event) && !(x86_pmu.ext_regs_mask & XFEATURE_MASK_SSE)) return -EINVAL; @@ -1854,11 +1861,13 @@ void x86_pmu_clear_perf_regs(struct pt_regs *regs) perf_regs->h16zmm_regs = NULL; perf_regs->opmask_regs = NULL; perf_regs->egpr_regs = NULL; + perf_regs->ssp = NULL; } static void update_perf_regs(struct x86_perf_regs *perf_regs, struct xregs_state *xsave, u64 bitmap) { + struct cet_user_state *cet; u64 mask; if (!xsave) @@ -1879,6 +1888,10 @@ static void update_perf_regs(struct x86_perf_regs *perf_regs, perf_regs->opmask = get_xsave_addr(xsave, XFEATURE_OPMASK); if (mask & XFEATURE_MASK_APX) perf_regs->egpr = get_xsave_addr(xsave, XFEATURE_APX); + if (mask & XFEATURE_MASK_CET_USER) { + cet = get_xsave_addr(xsave, XFEATURE_CET_USER); + perf_regs->ssp = cet ? &cet->user_ssp : NULL; + } } /* @@ -2057,6 +2070,8 @@ static u64 get_simd_sample_mask(struct perf_event *event, u64 sample_type) mask |= XFEATURE_MASK_OPMASK; if (__event_needs_egprs(event, sample_type)) mask |= XFEATURE_MASK_APX; + if (__event_needs_ssp(event, sample_type)) + mask |= XFEATURE_MASK_CET_USER; return mask; } diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index b2aa0bc7c97e7e..6538ffe46f747f 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -4696,7 +4696,8 @@ static unsigned long intel_pmu_large_pebs_flags(struct perf_event *event) { unsigned long flags = x86_pmu.large_pebs_flags; u64 gprs_mask = event->attr.sample_simd_regs_enabled ? - PEBS_GP_REGS | PERF_X86_EGPRS_MASK : + PEBS_GP_REGS | PERF_X86_EGPRS_MASK | + BIT_ULL(PERF_REG_X86_SSP) : PEBS_GP_REGS | PERF_REG_EXTENDED_MASK; if (event->attr.use_clockid) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 84caae35b86e11..59f61e51c545a9 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1707,6 +1707,7 @@ static u64 pebs_update_adaptive_cfg(struct perf_event *event) u64 sample_type = attr->sample_type; u64 pebs_data_cfg = 0; bool gprs, tsx_weight; + u64 xgprs_mask; if (!(sample_type & ~(PERF_SAMPLE_IP|PERF_SAMPLE_TIME)) && attr->precise_ip > 1) @@ -1721,10 +1722,13 @@ static u64 pebs_update_adaptive_cfg(struct perf_event *event) * + precise_ip < 2 for the non event IP * + For RTM TSX weight we need GPRs for the abort code. */ + xgprs_mask = event->attr.sample_simd_regs_enabled ? + PEBS_GP_REGS | BIT_ULL(PERF_REG_X86_SSP) : + PEBS_GP_REGS; gprs = ((sample_type & PERF_SAMPLE_REGS_INTR) && - (attr->sample_regs_intr & PEBS_GP_REGS)) || + (attr->sample_regs_intr & xgprs_mask)) || ((sample_type & PERF_SAMPLE_REGS_USER) && - (attr->sample_regs_user & PEBS_GP_REGS)); + (attr->sample_regs_user & xgprs_mask)); tsx_weight = (sample_type & PERF_SAMPLE_WEIGHT_TYPE) && ((attr->config & INTEL_ARCH_EVENT_MASK) == @@ -2673,6 +2677,11 @@ static void setup_arch_pebs_sample_data(struct perf_event *event, __setup_pebs_gpr_group(event, regs, (struct pebs_gprs *)gprs, sample_type); + + /* Currently only user space mode enables SSP. */ + if (user_mode(regs) && (sample_type & + (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER))) + perf_regs->ssp = &gprs->ssp; } if (header->aux) { diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index c052a38b8733d4..1503ab68eca698 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -315,6 +315,31 @@ static inline bool event_needs_egprs(struct perf_event *event) PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); } +static inline bool __event_needs_ssp(struct perf_event *event, + u64 sample_type) +{ + if (!event->attr.sample_simd_regs_enabled) + return false; + + if ((sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_type & PERF_SAMPLE_REGS_USER) && + (event->attr.sample_regs_user & BIT_ULL(PERF_REG_X86_SSP))) + return true; + + if ((sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) && + (event->attr.sample_regs_intr & BIT_ULL(PERF_REG_X86_SSP))) + return true; + + return false; +} + +static inline bool event_needs_ssp(struct perf_event *event) +{ + return __event_needs_ssp(event, + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER); +} + struct amd_nb { int nb_id; /* NorthBridge id */ int refcnt; /* reference count */ diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index bc05f8c1746432..4302ef39c42e07 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -753,6 +753,7 @@ struct x86_perf_regs { u64 *egpr_regs; struct apx_state *egpr; }; + u64 *ssp; }; extern unsigned long perf_arch_instruction_pointer(struct pt_regs *regs); diff --git a/arch/x86/include/uapi/asm/perf_regs.h b/arch/x86/include/uapi/asm/perf_regs.h index 977831bd7a9de8..faaa82df688d8a 100644 --- a/arch/x86/include/uapi/asm/perf_regs.h +++ b/arch/x86/include/uapi/asm/perf_regs.h @@ -30,10 +30,10 @@ enum perf_event_x86_regs { PERF_REG_X86_R14, PERF_REG_X86_R15, /* - * The eGPRs and XMM have overlaps. Only one can be used + * The eGPRs/SSP and XMM have overlaps. Only one can be used * at a time. The ABI PERF_SAMPLE_REGS_ABI_SIMD is used to * distinguish which one is used. If PERF_SAMPLE_REGS_ABI_SIMD - * is set, then eGPRs is used, otherwise, XMM is used. + * is set, then eGPRs/SSP is used, otherwise, XMM is used. * * Extended GPRs (eGPRs) */ @@ -53,10 +53,11 @@ enum perf_event_x86_regs { PERF_REG_X86_R29, PERF_REG_X86_R30, PERF_REG_X86_R31, + PERF_REG_X86_SSP, /* These are the limits for the GPRs. */ PERF_REG_X86_32_MAX = PERF_REG_X86_GS + 1, PERF_REG_X86_64_MAX = PERF_REG_X86_R15 + 1, - PERF_REG_MISC_MAX = PERF_REG_X86_R31 + 1, + PERF_REG_MISC_MAX = PERF_REG_X86_SSP + 1, /* These all need two bits set because they are 128bit */ PERF_REG_X86_XMM0 = 32, diff --git a/arch/x86/kernel/perf_regs.c b/arch/x86/kernel/perf_regs.c index 7d4233ed859cd1..f8952f7c36cce5 100644 --- a/arch/x86/kernel/perf_regs.c +++ b/arch/x86/kernel/perf_regs.c @@ -72,6 +72,11 @@ u64 perf_reg_value(struct pt_regs *regs, int idx) return 0; return perf_regs->egpr_regs[idx - PERF_REG_X86_R16]; } + if (idx == PERF_REG_X86_SSP) { + if (!perf_regs->ssp) + return 0; + return *perf_regs->ssp; + } } else { if (idx >= PERF_REG_X86_XMM0 && idx < PERF_REG_X86_XMM_MAX) { if (!perf_regs->xmm_regs) From c4fabb67a47f8103b80fadfcab3ba17e75784ab5 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:28 +0800 Subject: [PATCH 0123/2286] perf/x86/intel: Support arch-PEBS based SIMD/eGPRs sampling Support arch-PEBS based SIMD/eGPRs/SSP registers sampling. Arch-PEBS supports sampling all of these registers. SIMD and eGPR registers are placed into the XSAVE-Enabled Registers (XER) group with the layout described below. Field Name Registers Used Size XSTATE_BV XINUSE for groups 8 B Reserved Reserved 8 B SSER XMM0-XMM15 16 regs * 16 B = 256 B YMMHIR Upper 128 bits of YMM0-YMM15 16 regs * 16 B = 256 B EGPR R16-R31 16 regs * 8 B = 128 B OPMASKR K0-K7 8 regs * 8 B = 64 B ZMMHIR Upper 256 bits of ZMM0-ZMM15 16 regs * 32 B = 512 B Hi16ZMMR ZMM16-ZMM31 16 regs * 64 B = 1024 B Memory space in the output buffer is allocated for these sub-groups as long as the corresponding Format.XER[55:49] bits in the PEBS record header are set. However, the arch-PEBS hardware engine does not write the sub-group if it is not used (in INIT state). In such cases, the corresponding bit in the XSTATE_BV bitmap is set to 0. Therefore, the XSTATE_BV field is checked to determine if the register data is actually written for each PEBS record. If not, the register data is not outputted to userspace. Additionally, the MSRs IA32_PMC_{GPn|FXm}_CFG_C.[55:49] bits are used to manage which types of these registers need to be sampled. Arch-PEBS based SIMD/eGPRs sampling will be enabled in a subsequent patch that sets PERF_PMU_CAP_SIMD_REGS. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-21-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 20 ++++++--- arch/x86/events/intel/core.c | 15 +++++++ arch/x86/events/intel/ds.c | 67 +++++++++++++++++++++++++++++-- arch/x86/include/asm/msr-index.h | 7 ++++ arch/x86/include/asm/perf_event.h | 8 +++- 5 files changed, 106 insertions(+), 11 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index caa5a0fb3ed891..11799acd8c546c 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -646,12 +646,20 @@ static int pebs_simd_regs_validate(struct perf_event *event) if (event_needs_ssp(event) && !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_GPR))) return -EINVAL; - /* PEBS does not support YMM/ZMM/OPMASK/eGPR registers sampling yet. */ - if (event_needs_ymm(event) || - event_needs_low16_zmm(event) || - event_needs_high16_zmm(event) || - event_needs_opmask(event) || - event_needs_egprs(event)) + if (event_needs_ymm(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_YMMH))) + return -EINVAL; + if (event_needs_egprs(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_EGPRS))) + return -EINVAL; + if (event_needs_opmask(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_OPMASK))) + return -EINVAL; + if (event_needs_low16_zmm(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_ZMMH))) + return -EINVAL; + if (event_needs_high16_zmm(event) && + !(x86_pmu.arch_pebs && (caps & ARCH_PEBS_VECR_H16ZMM))) return -EINVAL; return 0; diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 6538ffe46f747f..f87346200446fb 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3474,6 +3474,21 @@ static void intel_pmu_enable_event_ext(struct perf_event *event) if (pebs_data_cfg & PEBS_DATACFG_XMMS) ext |= ARCH_PEBS_VECR_XMM & cap.caps; + if (pebs_data_cfg & PEBS_DATACFG_YMMHS) + ext |= ARCH_PEBS_VECR_YMMH & cap.caps; + + if (pebs_data_cfg & PEBS_DATACFG_EGPRS) + ext |= ARCH_PEBS_VECR_EGPRS & cap.caps; + + if (pebs_data_cfg & PEBS_DATACFG_OPMASKS) + ext |= ARCH_PEBS_VECR_OPMASK & cap.caps; + + if (pebs_data_cfg & PEBS_DATACFG_ZMMHS) + ext |= ARCH_PEBS_VECR_ZMMH & cap.caps; + + if (pebs_data_cfg & PEBS_DATACFG_H16ZMMS) + ext |= ARCH_PEBS_VECR_H16ZMM & cap.caps; + if (pebs_data_cfg & PEBS_DATACFG_LBRS) ext |= ARCH_PEBS_LBR & cap.caps; diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 59f61e51c545a9..4335ae657dc4f0 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -1740,6 +1740,16 @@ static u64 pebs_update_adaptive_cfg(struct perf_event *event) if (sample_type & (PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_REGS_USER)) { if (event_needs_xmm(event)) pebs_data_cfg |= PEBS_DATACFG_XMMS; + if (x86_pmu.arch_pebs && event_needs_ymm(event)) + pebs_data_cfg |= PEBS_DATACFG_YMMHS; + if (x86_pmu.arch_pebs && event_needs_low16_zmm(event)) + pebs_data_cfg |= PEBS_DATACFG_ZMMHS; + if (x86_pmu.arch_pebs && event_needs_high16_zmm(event)) + pebs_data_cfg |= PEBS_DATACFG_H16ZMMS; + if (x86_pmu.arch_pebs && event_needs_opmask(event)) + pebs_data_cfg |= PEBS_DATACFG_OPMASKS; + if (x86_pmu.arch_pebs && event_needs_egprs(event)) + pebs_data_cfg |= PEBS_DATACFG_EGPRS; } if (sample_type & PERF_SAMPLE_BRANCH_STACK) { @@ -2694,14 +2704,63 @@ static void setup_arch_pebs_sample_data(struct perf_event *event, meminfo->tsx_tuning, ax); } - if (header->xmm) { + if (header->xmm || header->ymmh || header->egpr || + header->opmask || header->zmmh || header->h16zmm) { + struct arch_pebs_xer_header *xer_header = next_record; struct pebs_xmm *xmm; + struct ymmh_struct *ymmh; + struct avx_512_zmm_uppers_state *zmmh; + struct avx_512_hi16_state *h16zmm; + struct avx_512_opmask_state *opmask; + struct apx_state *egpr; next_record += sizeof(struct arch_pebs_xer_header); - xmm = next_record; - perf_regs->xmm_regs = xmm->xmm; - next_record = xmm + 1; + if (header->xmm) { + xmm = next_record; + /* + * Only output XMM regs to user space when arch-PEBS + * really writes data into xstate area. + */ + if (xer_header->xstate & XFEATURE_MASK_SSE) + perf_regs->xmm_regs = xmm->xmm; + next_record = xmm + 1; + } + + if (header->ymmh) { + ymmh = next_record; + if (xer_header->xstate & XFEATURE_MASK_YMM) + perf_regs->ymmh = ymmh; + next_record = ymmh + 1; + } + + if (header->egpr) { + egpr = next_record; + if (xer_header->xstate & XFEATURE_MASK_APX) + perf_regs->egpr = egpr; + next_record = egpr + 1; + } + + if (header->opmask) { + opmask = next_record; + if (xer_header->xstate & XFEATURE_MASK_OPMASK) + perf_regs->opmask = opmask; + next_record = opmask + 1; + } + + if (header->zmmh) { + zmmh = next_record; + if (xer_header->xstate & XFEATURE_MASK_ZMM_Hi256) + perf_regs->zmmh = zmmh; + next_record = zmmh + 1; + } + + if (header->h16zmm) { + h16zmm = next_record; + if (xer_header->xstate & XFEATURE_MASK_Hi16_ZMM) + perf_regs->h16zmm = h16zmm; + next_record = h16zmm + 1; + } } if (header->lbr) { diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h index 3a8e51a0c9e88c..428eb44106a979 100644 --- a/arch/x86/include/asm/msr-index.h +++ b/arch/x86/include/asm/msr-index.h @@ -350,6 +350,13 @@ #define ARCH_PEBS_LBR_SHIFT 40 #define ARCH_PEBS_LBR (0x3ull << ARCH_PEBS_LBR_SHIFT) #define ARCH_PEBS_VECR_XMM BIT_ULL(49) +#define ARCH_PEBS_VECR_YMMH BIT_ULL(50) +#define ARCH_PEBS_VECR_EGPRS BIT_ULL(51) +#define ARCH_PEBS_VECR_OPMASK BIT_ULL(53) +#define ARCH_PEBS_VECR_ZMMH BIT_ULL(54) +#define ARCH_PEBS_VECR_H16ZMM BIT_ULL(55) +#define ARCH_PEBS_VECR_EXT_SHIFT 49 +#define ARCH_PEBS_VECR_EXT (0x7full << ARCH_PEBS_VECR_EXT_SHIFT) #define ARCH_PEBS_GPR BIT_ULL(61) #define ARCH_PEBS_AUX BIT_ULL(62) #define ARCH_PEBS_EN BIT_ULL(63) diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 4302ef39c42e07..12f7db8c57b437 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -150,6 +150,11 @@ #define PEBS_DATACFG_LBRS BIT_ULL(3) #define PEBS_DATACFG_CNTR BIT_ULL(4) #define PEBS_DATACFG_METRICS BIT_ULL(5) +#define PEBS_DATACFG_YMMHS BIT_ULL(6) +#define PEBS_DATACFG_OPMASKS BIT_ULL(7) +#define PEBS_DATACFG_ZMMHS BIT_ULL(8) +#define PEBS_DATACFG_H16ZMMS BIT_ULL(9) +#define PEBS_DATACFG_EGPRS BIT_ULL(10) #define PEBS_DATACFG_LBR_SHIFT 24 #define PEBS_DATACFG_CNTR_SHIFT 32 #define PEBS_DATACFG_CNTR_MASK GENMASK_ULL(15, 0) @@ -547,7 +552,8 @@ struct arch_pebs_header { rsvd3:7, xmm:1, ymmh:1, - rsvd4:2, + egpr:1, + rsvd4:1, opmask:1, zmmh:1, h16zmm:1, From 00cf8daabe2bfd45e7283e44ad7cb3977089259b Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:29 +0800 Subject: [PATCH 0124/2286] perf/x86/intel: Advertise PERF_PMU_CAP_SIMD_REGS capability Set PERF_PMU_CAP_SIMD_REGS once XSAVES-backed extended state beyond XMM is available for sampling, e.g. YMM, ZMM, OPMASK, eGPRs, or SSP. Keep backward compatibility unchanged. When only XMM sampling is available, continue to expose only PERF_PMU_CAP_EXTENDED_REGS. Expose PERF_PMU_CAP_SIMD_REGS only when additional SIMD/predicate register state is supported. Co-developed-by: Kan Liang Signed-off-by: Kan Liang Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-22-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/core.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index f87346200446fb..a919168f997071 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -6204,7 +6204,27 @@ static inline void __intel_update_pmu_xregs_caps(struct pmu *pmu) */ x86_pmu.ext_regs_mask |= XFEATURE_MASK_SSE; + if (boot_cpu_has(X86_FEATURE_AVX) && + cpu_has_xfeatures(XFEATURE_MASK_YMM, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_YMM; + if (boot_cpu_has(X86_FEATURE_APX) && + cpu_has_xfeatures(XFEATURE_MASK_APX, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_APX; + if (boot_cpu_has(X86_FEATURE_AVX512F)) { + if (cpu_has_xfeatures(XFEATURE_MASK_OPMASK, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_OPMASK; + if (cpu_has_xfeatures(XFEATURE_MASK_ZMM_Hi256, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_ZMM_Hi256; + if (cpu_has_xfeatures(XFEATURE_MASK_Hi16_ZMM, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_Hi16_ZMM; + } + if (cpu_feature_enabled(X86_FEATURE_USER_SHSTK) && + cpu_has_xfeatures(XFEATURE_MASK_CET_USER, NULL)) + x86_pmu.ext_regs_mask |= XFEATURE_MASK_CET_USER; + dest_pmu->capabilities |= PERF_PMU_CAP_EXTENDED_REGS; + if (x86_pmu.ext_regs_mask > XFEATURE_MASK_SSE) + dest_pmu->capabilities |= PERF_PMU_CAP_SIMD_REGS; } static inline void __intel_update_large_pebs_flags(struct pmu *pmu) From c34db2f950940fe8732899a8cc9ba12942be3ba9 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:30 +0800 Subject: [PATCH 0125/2286] perf/x86: Activate back-to-back NMI detection for arch-PEBS induced NMIs When two or more identical PEBS events with the same sampling period are programmed on a mix of PDIST and non-PDIST counters, multiple back-to-back NMIs can be triggered. The Linux PMI handler processes the first NMI and clears the GLOBAL_STATUS MSR. If a second NMI is triggered immediately after the first, it is recognized as a "suspicious NMI" because no bits are set in the GLOBAL_STATUS MSR (cleared by the first NMI). This issue does not lead to PEBS data corruption or data loss, but it does result in an annoying warning message. The current NMI handler supports back-to-back NMI detection, but it requires the PMI handler to return the count of actually processed events, which the PEBS handler does not currently do. Thus, modify the PEBS handlers to return the count of actually processed events, thereby activating back-to-back NMI detection and avoiding the "suspicious NMI" warning. Suggested-by: Andi Kleen Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-23-dapeng1.mi@linux.intel.com --- arch/x86/events/core.c | 2 +- arch/x86/events/intel/core.c | 31 +++++++++++++++++--------- arch/x86/events/intel/ds.c | 43 ++++++++++++++++++++++++------------ arch/x86/events/perf_event.h | 2 +- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 11799acd8c546c..ef9b8df2e1f717 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -93,7 +93,7 @@ DEFINE_STATIC_CALL_NULL(x86_pmu_stop_scheduling, *x86_pmu.stop_scheduling); DEFINE_STATIC_CALL_NULL(x86_pmu_sched_task, *x86_pmu.sched_task); -DEFINE_STATIC_CALL_NULL(x86_pmu_drain_pebs, *x86_pmu.drain_pebs); +DEFINE_STATIC_CALL_RET0(x86_pmu_drain_pebs, *x86_pmu.drain_pebs); DEFINE_STATIC_CALL_NULL(x86_pmu_pebs_aliases, *x86_pmu.pebs_aliases); DEFINE_STATIC_CALL_NULL(x86_pmu_filter, *x86_pmu.filter); diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index a919168f997071..4bde7362d7b2aa 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -3760,8 +3760,8 @@ static void intel_pmu_reset(void) * * The contents and other behavior of the guest event do not matter. */ -static void x86_pmu_handle_guest_pebs(struct pt_regs *regs, - struct perf_sample_data *data) +static int x86_pmu_handle_guest_pebs(struct pt_regs *regs, + struct perf_sample_data *data) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); u64 guest_pebs_idxs = cpuc->pebs_enabled & ~cpuc->intel_ctrl_host_mask; @@ -3769,11 +3769,11 @@ static void x86_pmu_handle_guest_pebs(struct pt_regs *regs, int bit; if (!unlikely(perf_guest_state())) - return; + return 0; if (!x86_pmu.pebs_ept || !x86_pmu.pebs_active || !guest_pebs_idxs) - return; + return 0; for_each_set_bit(bit, (unsigned long *)&guest_pebs_idxs, X86_PMC_IDX_MAX) { event = cpuc->events[bit]; @@ -3783,9 +3783,14 @@ static void x86_pmu_handle_guest_pebs(struct pt_regs *regs, perf_sample_data_init(data, 0, event->hw.last_period); perf_event_overflow(event, data, regs); - /* Inject one fake event is enough. */ - break; + /* + * Inject one fake event is enough. + * Returning 1 to inform PMI is handled. + */ + return 1; } + + return 0; } static int handle_pmi_common(struct pt_regs *regs, u64 status) @@ -3834,9 +3839,11 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status) if (__test_and_clear_bit(GLOBAL_STATUS_BUFFER_OVF_BIT, (unsigned long *)&status)) { u64 pebs_enabled = cpuc->pebs_enabled; - handled++; - x86_pmu_handle_guest_pebs(regs, &data); - static_call(x86_pmu_drain_pebs)(regs, &data); + handled += x86_pmu_handle_guest_pebs(regs, &data); + handled += static_call(x86_pmu_drain_pebs)(regs, &data); + /* Ensure no "suspicious NMI" warning for empty PEBS buffer. */ + if (!handled) + handled++; /* * PMI throttle may be triggered, which stops the PEBS event. @@ -3863,8 +3870,10 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status) */ if (__test_and_clear_bit(GLOBAL_STATUS_ARCH_PEBS_THRESHOLD_BIT, (unsigned long *)&status)) { - handled++; - static_call(x86_pmu_drain_pebs)(regs, &data); + handled += static_call(x86_pmu_drain_pebs)(regs, &data); + /* Ensure no "suspicious NMI" warning for empty PEBS buffer. */ + if (!handled) + handled++; if (cpuc->events[INTEL_PMC_IDX_FIXED_SLOTS] && is_pebs_counter_event_group(cpuc->events[INTEL_PMC_IDX_FIXED_SLOTS])) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 4335ae657dc4f0..1b220cd4ca01f7 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -3021,7 +3021,7 @@ __intel_pmu_pebs_events(struct perf_event *event, __intel_pmu_pebs_last_event(event, iregs, regs, data, at, count, setup_sample); } -static void intel_pmu_drain_pebs_core(struct pt_regs *iregs, struct perf_sample_data *data) +static int intel_pmu_drain_pebs_core(struct pt_regs *iregs, struct perf_sample_data *data) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct debug_store *ds = cpuc->ds; @@ -3030,7 +3030,7 @@ static void intel_pmu_drain_pebs_core(struct pt_regs *iregs, struct perf_sample_ int n; if (!x86_pmu.pebs_active) - return; + return 0; at = (struct pebs_record_core *)(unsigned long)ds->pebs_buffer_base; top = (struct pebs_record_core *)(unsigned long)ds->pebs_index; @@ -3041,22 +3041,25 @@ static void intel_pmu_drain_pebs_core(struct pt_regs *iregs, struct perf_sample_ ds->pebs_index = ds->pebs_buffer_base; if (!test_bit(0, cpuc->active_mask)) - return; + return 0; WARN_ON_ONCE(!event); if (!event->attr.precise_ip) - return; + return 0; n = top - at; if (n <= 0) { if (event->hw.flags & PERF_X86_EVENT_AUTO_RELOAD) intel_pmu_save_and_restart_reload(event, 0); - return; + return 0; } __intel_pmu_pebs_events(event, iregs, data, at, top, 0, n, setup_pebs_fixed_sample_data); + + /* PMC0 only */ + return 1; } static void intel_pmu_pebs_event_update_no_drain(struct cpu_hw_events *cpuc, u64 mask) @@ -3079,7 +3082,7 @@ static void intel_pmu_pebs_event_update_no_drain(struct cpu_hw_events *cpuc, u64 } } -static void intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_data *data) +static int intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_data *data) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct debug_store *ds = cpuc->ds; @@ -3088,11 +3091,12 @@ static void intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_d short counts[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS] = {}; short error[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS] = {}; int max_pebs_events = intel_pmu_max_num_pebs(NULL); + u64 events_bitmap = 0; int bit, i, size; u64 mask; if (!x86_pmu.pebs_active) - return; + return 0; base = (struct pebs_record_nhm *)(unsigned long)ds->pebs_buffer_base; top = (struct pebs_record_nhm *)(unsigned long)ds->pebs_index; @@ -3108,7 +3112,7 @@ static void intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_d if (unlikely(base >= top)) { intel_pmu_pebs_event_update_no_drain(cpuc, mask); - return; + return 0; } for (at = base; at < top; at += x86_pmu.pebs_record_size) { @@ -3172,6 +3176,7 @@ static void intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_d if ((counts[bit] == 0) && (error[bit] == 0)) continue; + events_bitmap |= BIT_ULL(bit); event = cpuc->events[bit]; if (WARN_ON_ONCE(!event)) continue; @@ -3193,6 +3198,8 @@ static void intel_pmu_drain_pebs_nhm(struct pt_regs *iregs, struct perf_sample_d setup_pebs_fixed_sample_data); } } + + return hweight64(events_bitmap); } static __always_inline void @@ -3246,7 +3253,7 @@ __intel_pmu_handle_last_pebs_record(struct pt_regs *iregs, } -static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_data *data) +static int intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_data *data) { short counts[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS] = {}; void *last[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS]; @@ -3256,10 +3263,11 @@ static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_d struct pt_regs *regs = &perf_regs->regs; struct pebs_basic *basic; void *base, *at, *top; + u64 events_bitmap = 0; u64 mask; if (!x86_pmu.pebs_active) - return; + return 0; base = (struct pebs_basic *)(unsigned long)ds->pebs_buffer_base; top = (struct pebs_basic *)(unsigned long)ds->pebs_index; @@ -3272,7 +3280,7 @@ static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_d if (unlikely(base >= top)) { intel_pmu_pebs_event_update_no_drain(cpuc, mask); - return; + return 0; } if (!iregs) @@ -3287,6 +3295,7 @@ static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_d continue; pebs_status = mask & basic->applicable_counters; + events_bitmap |= pebs_status; __intel_pmu_handle_pebs_record(iregs, regs, data, at, pebs_status, counts, last, setup_pebs_adaptive_sample_data); @@ -3294,10 +3303,12 @@ static void intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_d __intel_pmu_handle_last_pebs_record(iregs, regs, data, mask, counts, last, setup_pebs_adaptive_sample_data); + + return hweight64(events_bitmap); } -static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs, - struct perf_sample_data *data) +static int intel_pmu_drain_arch_pebs(struct pt_regs *iregs, + struct perf_sample_data *data) { short counts[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS] = {}; void *last[INTEL_PMC_IDX_FIXED + MAX_FIXED_PEBS_EVENTS]; @@ -3306,13 +3317,14 @@ static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs, struct x86_perf_regs *perf_regs = this_cpu_ptr(&x86_pebs_regs); struct pt_regs *regs = &perf_regs->regs; void *base, *at, *top; + u64 events_bitmap = 0; u64 mask; rdmsrq(MSR_IA32_PEBS_INDEX, index.whole); if (unlikely(!index.wr)) { intel_pmu_pebs_event_update_no_drain(cpuc, X86_PMC_IDX_MAX); - return; + return 0; } base = cpuc->pebs_vaddr; @@ -3351,6 +3363,7 @@ static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs, basic = at + sizeof(struct arch_pebs_header); pebs_status = mask & basic->applicable_counters; + events_bitmap |= pebs_status; __intel_pmu_handle_pebs_record(iregs, regs, data, at, pebs_status, counts, last, setup_arch_pebs_sample_data); @@ -3370,6 +3383,8 @@ static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs, __intel_pmu_handle_last_pebs_record(iregs, regs, data, mask, counts, last, setup_arch_pebs_sample_data); + + return hweight64(events_bitmap); } static void __init intel_arch_pebs_init(void) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 1503ab68eca698..c9c52f3ee4fc8c 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1145,7 +1145,7 @@ struct x86_pmu { int pebs_record_size; int pebs_buffer_size; u64 pebs_events_mask; - void (*drain_pebs)(struct pt_regs *regs, struct perf_sample_data *data); + int (*drain_pebs)(struct pt_regs *regs, struct perf_sample_data *data); struct event_constraint *pebs_constraints; void (*pebs_aliases)(struct perf_event *event); u64 (*pebs_latency_data)(struct perf_event *event, u64 status); From 68aca309e49c589f1922f4c5af2370e4df093175 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Mon, 24 Aug 2026 16:27:31 +0800 Subject: [PATCH 0126/2286] perf/x86/intel: Add sanity check for PEBS record/fragment size Prevent potential infinite loops by adding a sanity check for the corrupted PEBS record or fragment size which could happen in theory. If a corrupted PEBS fragment is detected, the entire PEBS record including the corrupted fragment and all subsequent records will be dropped and a NULL PEBS record is reported to user space. This ensures the integrity of PEBS data and prevents infinite loops in setup_arch_pebs_sample_data() again. Please note software has no way to figure out which events are impacted by the corrupted record, so the last record of each event would be discarded for all events if corrupted record is detected even though it may be a well-formed record for some events. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260824082731.1013973-24-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 77 ++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index 1b220cd4ca01f7..2edff9754eb2ef 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -2657,6 +2657,9 @@ static void setup_arch_pebs_sample_data(struct perf_event *event, again: header = at; + if (!header->size) + return; + next_record = at + sizeof(struct arch_pebs_header); if (header->basic) { struct arch_pebs_basic *basic = next_record; @@ -2930,13 +2933,21 @@ __intel_pmu_pebs_last_event(struct perf_event *event, struct pt_regs *iregs, struct pt_regs *regs, struct perf_sample_data *data, - void *at, - int count, + void *at, int count, bool corrupted, setup_fn setup_sample) { struct hw_perf_event *hwc = &event->hw; - setup_sample(event, iregs, at, data, regs); + /* Skip parsing corrupted PEBS record. */ + if (corrupted) { + /* Clear stale register states in previous records. */ + memset(regs, 0, sizeof(*regs)); + x86_pmu_clear_perf_regs(regs); + perf_sample_data_init(data, 0, event->hw.last_period); + } else { + setup_sample(event, iregs, at, data, regs); + } + if (iregs == &dummy_iregs) { /* * The PEBS records may be drained in the non-overflow context, @@ -2954,12 +2965,16 @@ __intel_pmu_pebs_last_event(struct perf_event *event, } if (hwc->flags & PERF_X86_EVENT_AUTO_RELOAD) { - if ((is_pebs_counter_event_group(event))) { - /* - * The value of each sample has been updated when setup - * the corresponding sample data. - */ - perf_event_update_userpage(event); + if (is_pebs_counter_event_group(event)) { + if (corrupted) { + intel_pmu_save_and_restart_reload(event, 1); + } else { + /* + * The value of each sample has been updated + * when setup the corresponding sample data. + */ + perf_event_update_userpage(event); + } } else { /* * Now, auto-reload is only enabled in fixed period mode. @@ -2983,7 +2998,7 @@ __intel_pmu_pebs_last_event(struct perf_event *event, * counters-snapshotting record, only needs to set the new * period for the counter. */ - if (is_pebs_counter_event_group(event)) + if (is_pebs_counter_event_group(event) && !corrupted) static_call(x86_pmu_set_period)(event); else intel_pmu_save_and_restart(event); @@ -3012,13 +3027,15 @@ __intel_pmu_pebs_events(struct perf_event *event, iregs = &dummy_iregs; while (cnt > 1) { - __intel_pmu_pebs_event(event, iregs, regs, data, at, setup_sample); + __intel_pmu_pebs_event(event, iregs, regs, data, + at, setup_sample); at += cpuc->pebs_record_size; at = get_next_pebs_record_by_bit(at, top, bit); cnt--; } - __intel_pmu_pebs_last_event(event, iregs, regs, data, at, count, setup_sample); + __intel_pmu_pebs_last_event(event, iregs, regs, data, at, + count, false, setup_sample); } static int intel_pmu_drain_pebs_core(struct pt_regs *iregs, struct perf_sample_data *data) @@ -3234,23 +3251,28 @@ static __always_inline void __intel_pmu_handle_last_pebs_record(struct pt_regs *iregs, struct pt_regs *regs, struct perf_sample_data *data, - u64 mask, short *counts, void **last, + u64 mask, short *counts, + void **last, bool corrupted, setup_fn setup_sample) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_event *event; + bool handled = false; int bit; for_each_set_bit(bit, (unsigned long *)&mask, X86_PMC_IDX_MAX) { if (!counts[bit]) continue; + handled = true; event = cpuc->events[bit]; - __intel_pmu_pebs_last_event(event, iregs, regs, data, last[bit], - counts[bit], setup_sample); + counts[bit], corrupted, setup_sample); } + /* All records are corrupted, reset sampling period. */ + if (!handled) + intel_pmu_pebs_event_update_no_drain(cpuc, mask); } static int intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_data *data) @@ -3264,6 +3286,7 @@ static int intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_da struct pebs_basic *basic; void *base, *at, *top; u64 events_bitmap = 0; + bool corrupted = false; u64 mask; if (!x86_pmu.pebs_active) @@ -3291,6 +3314,10 @@ static int intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_da u64 pebs_status; basic = at; + if (WARN_ON_ONCE(!basic->format_size)) { + corrupted = true; + break; + } if (basic->format_size != cpuc->pebs_record_size) continue; @@ -3302,7 +3329,7 @@ static int intel_pmu_drain_pebs_icl(struct pt_regs *iregs, struct perf_sample_da } __intel_pmu_handle_last_pebs_record(iregs, regs, data, mask, counts, last, - setup_pebs_adaptive_sample_data); + corrupted, setup_pebs_adaptive_sample_data); return hweight64(events_bitmap); } @@ -3318,6 +3345,7 @@ static int intel_pmu_drain_arch_pebs(struct pt_regs *iregs, struct pt_regs *regs = &perf_regs->regs; void *base, *at, *top; u64 events_bitmap = 0; + bool corrupted = false; u64 mask; rdmsrq(MSR_IA32_PEBS_INDEX, index.whole); @@ -3352,8 +3380,10 @@ static int intel_pmu_drain_arch_pebs(struct pt_regs *iregs, header = at; - if (WARN_ON_ONCE(!header->size)) - break; + if (WARN_ON_ONCE(!header->size)) { + corrupted = true; + goto done; + } /* 1st fragment or single record must have basic group */ if (!header->basic) { @@ -3373,15 +3403,24 @@ static int intel_pmu_drain_arch_pebs(struct pt_regs *iregs, if (!header->size) break; at += header->size; + if (WARN_ON_ONCE(at >= top)) { + corrupted = true; + goto done; + } header = at; } /* Skip last fragment or the single record */ at += header->size; + if (WARN_ON_ONCE(at > top)) { + corrupted = true; + goto done; + } } +done: __intel_pmu_handle_last_pebs_record(iregs, regs, data, mask, - counts, last, + counts, last, corrupted, setup_arch_pebs_sample_data); return hweight64(events_bitmap); From f53374bd432b51d7c7f0a879f0229c7c56e1dd7c Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Tue, 1 Sep 2026 13:47:08 -0400 Subject: [PATCH 0127/2286] dlm: gate dlm_plock device on CAP_SYS_ADMIN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fs/dlm/plock.c registers /dev/dlm_plock via a miscdevice whose file_operations has no .open callback and whose .mode field is unset. The kernel therefore performs no capability check on open, and any process that can open the node becomes an unprivileged plock daemon with full read+write access to the pending-plock queue. Attack chain (when the device node is reachable by an unprivileged opener — see mitigation note below): 1. attacker open("/dev/dlm_plock") succeeds with no cap check 2. dev_read() drains pending plock requests straight to user space, leaking dlm_plock_info fields: fsid, number (resource id / inode), start, end, owner, pid, ex, wait 3. dev_write() matches an attacker-supplied dlm_plock_info against a pending op on recv_list (matched on fsid+number+ owner+pid+start+end+ex+wait) and memcpy()'s it into the in-kernel op, including a forged .rv == 0 4. when a matched op exists, the forged result is then applied: for async ops (op->data != NULL), dlm_plock_callback() runs posix_lock_file(); for sync ops, the requester wakes and proceeds as if the cluster had granted the lock. Either way the requester ends up holding a POSIX lock without a real DLM grant, or sees attacker-chosen rv that breaks lock correctness. With no pending op on recv_list, dev_write() still returns sizeof(info) but does nothing beyond a pr_debug ("dlm dev_write no op ...") — so a syntactically accepted write does not by itself prove an applied grant. Mitigation: on a stock kernel, devtmpfs creates /dev/dlm_plock as 0600 root:root, so steps 1-4 are only reachable where the node is exposed to a less privileged principal — e.g. udev MODE=0666, container bind-mount of the node, or an fd passed via SCM_RIGHTS. The in-kernel capability gap is real regardless of node mode. Reproduction (kernel 7.2.0-rc3, dlm loaded): # ./exploit_h2 # as root [*] node /dev/dlm_plock mode=0600 uid=0 gid=0 [!!!] AUTH BYPASS: opened with no capability check (fd=3) [!!!] FORGE ACCEPTED: kernel accepted forged plock result (rv=0); pending ops are grant-forgeable [VULNERABLE] open + read-leak + grant-forge demonstrated The "FORGE ACCEPTED" line means dev_write() returned sizeof(info), i.e. the write path is reachable and the version check passed; it does not by itself mean a grant was applied. Demonstrating an actual forged grant requires a concurrent plock op on recv_list to match against. $ setpriv --reuid 65534 --regid 65534 ./exploit_h2 [OK ] open denied: Permission denied # devtmpfs 0600, not a kernel cap check Fix: add a .open callback that requires CAP_SYS_ADMIN, and set .mode = 0600 on the miscdevice so the explicit expectation matches the devtmpfs default and survives future defaults. Acked-by: Alexander Aring Signed-off-by: Haofeng Li Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/plock.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/dlm/plock.c b/fs/dlm/plock.c index e9598b3fe5d09e..711e8bc3a46a65 100644 --- a/fs/dlm/plock.c +++ b/fs/dlm/plock.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -477,6 +478,15 @@ int dlm_posix_get(dlm_lockspace_t *lockspace, u64 number, struct file *file, } EXPORT_SYMBOL_GPL(dlm_posix_get); +static int dev_open(struct inode *inode, struct file *file) +{ + /* Userspace plock daemon is a privileged cluster component. */ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + return 0; +} + /* a read copies out one plock request from the send list */ static ssize_t dev_read(struct file *file, char __user *u, size_t count, loff_t *ppos) @@ -598,6 +608,7 @@ static __poll_t dev_poll(struct file *file, poll_table *wait) } static const struct file_operations dev_fops = { + .open = dev_open, .read = dev_read, .write = dev_write, .poll = dev_poll, @@ -608,7 +619,8 @@ static const struct file_operations dev_fops = { static struct miscdevice plock_dev_misc = { .minor = MISC_DYNAMIC_MINOR, .name = DLM_PLOCK_MISC_NAME, - .fops = &dev_fops + .fops = &dev_fops, + .mode = 0600, }; int dlm_plock_init(void) From 8f812a2beac33671eeb68147f3d1cea47f3652f9 Mon Sep 17 00:00:00 2001 From: Haofeng Li Date: Tue, 1 Sep 2026 13:47:09 -0400 Subject: [PATCH 0128/2286] dlm: require CAP_SYS_ADMIN for dlm-monitor device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit monitor_device_open() in fs/dlm/user.c performs only atomic_inc(&dlm_monitor_opened) and sets dlm_monitor_unused = 0; it does no capability check. monitor_device_close() does atomic_dec_and_test(&dlm_monitor_opened) and, when the count reaches zero, calls dlm_stop_lockspaces() — which stops every lockspace on the node. The miscdevice is also registered with no .mode field. Attack chain (when the device node is reachable by an unprivileged opener — see mitigation note below): 1. attacker open("/dev/dlm-monitor") with no cap check; the global counter goes 0 -> 1 2. attacker close(fd); atomic_dec_and_test reaches zero again and dlm_stop_lockspaces() runs -> every DLM lockspace on the local node is stopped. Other cluster members then observe the node losing its lockspaces (membership / recovery side effects), so the impact is not strictly local to GFS2 / OCFS2 / lvmlockd / cluster-md workloads on this node. variant: attacker holds the fd open indefinitely to suppress the intended stop when dlm_controld later closes its own fd (inverse abuse — recovery / shutdown stalls) Mitigation: devtmpfs creates /dev/dlm-monitor as 0600 root:root on a stock kernel, so unprivileged open is blocked by the node mode, not by a kernel cap check. The gap is real wherever the node is reachable (udev MODE=0666, container bind-mount, fd via SCM_RIGHTS, or any setup where dlm_controld shares its monitor fd). Reproduction (kernel 7.2.0-rc3, dlm loaded, no live lockspace): # ./exploit_h3 # as root [*] lockspace devices present: 0 [!!!] AUTH BYPASS: opened /dev/dlm-monitor, no cap check (fd=3) [VULNERABLE] monitor open auth bypass demonstrated $ setpriv --reuid 65534 --regid 65534 ./exploit_h3 [OK ] open denied: Permission denied # node 0600, not cap check The destructive close path is opt-in in the PoX (--i-know-it-stops-lockspaces); we did not drive it here. Driving the close path on a node with active lockspaces would stop them; on this throw-away node there are none, but we keep the opt-in gate so the same PoX is safe to re-run on production-like clusters. Fix: gate monitor_device_open() on capable(CAP_SYS_ADMIN) and set .mode = 0600 on monitor_device, matching the dlm_controld-only intended usage. Acked-by: Alexander Aring Signed-off-by: Haofeng Li Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/user.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/dlm/user.c b/fs/dlm/user.c index a8ed4c8fdc5bdf..cd7e142ca670d1 100644 --- a/fs/dlm/user.c +++ b/fs/dlm/user.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -910,6 +911,10 @@ static int ctl_device_close(struct inode *inode, struct file *file) static int monitor_device_open(struct inode *inode, struct file *file) { + /* dlm_controld is the only expected opener; last close stops LS. */ + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + atomic_inc(&dlm_monitor_opened); dlm_monitor_unused = 0; return 0; @@ -958,6 +963,7 @@ static struct miscdevice monitor_device = { .name = "dlm-monitor", .fops = &monitor_device_fops, .minor = MISC_DYNAMIC_MINOR, + .mode = 0600, }; int __init dlm_user_init(void) From 953753f8ae9b9d159d48acbf7ffec40366e7b0a1 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Tue, 1 Sep 2026 13:47:10 -0400 Subject: [PATCH 0129/2286] dlm: validate userspace lock resource name length The DLM userspace device accepts a flexible resource name after `struct dlm_write_request`. `device_write()` bounded the total write size, but did not verify that `i.lock.namelen` was covered by the bytes actually supplied by the write. A short `DLM_USER_LOCK` request can therefore claim a full `DLM_RESNAME_MAXLEN` resource name while providing no name bytes. The request path later hashes and copies the claimed name length, reading past the `memdup_user_nul()` allocation. Reject non-conversion lock requests whose claimed resource name length exceeds the flexible name payload supplied with the write. Valid lock requests with complete names are unchanged. Track the payload length before compat conversion so 32-bit requests keep using their own request header size. Assisted-by: Codex:gpt-5.5-cyber-preview Acked-by: Alexander Aring Signed-off-by: Samuel Moelius Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/user.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/fs/dlm/user.c b/fs/dlm/user.c index cd7e142ca670d1..0b0a7e1bd10958 100644 --- a/fs/dlm/user.c +++ b/fs/dlm/user.c @@ -512,6 +512,7 @@ static ssize_t device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct dlm_user_proc *proc = file->private_data; + size_t name_payload = 0; struct dlm_write_request *kbuf; int error; @@ -545,6 +546,7 @@ static ssize_t device_write(struct file *file, const char __user *buf, if (count > sizeof(struct dlm_write_request32)) namelen = count - sizeof(struct dlm_write_request32); + name_payload = namelen; k32buf = (struct dlm_write_request32 *)kbuf; @@ -561,7 +563,13 @@ static ssize_t device_write(struct file *file, const char __user *buf, compat_input(kbuf, k32buf, namelen); kfree(k32buf); + } else { + if (count > sizeof(*kbuf)) + name_payload = count - sizeof(*kbuf); } +#else + if (count > sizeof(*kbuf)) + name_payload = count - sizeof(*kbuf); #endif /* do we really need this? can a write happen after a close? */ @@ -571,6 +579,15 @@ static ssize_t device_write(struct file *file, const char __user *buf, goto out_free; } + if (kbuf->cmd == DLM_USER_LOCK && + !(kbuf->i.lock.flags & DLM_LKF_CONVERT)) { + if (kbuf->i.lock.namelen > name_payload || + kbuf->i.lock.namelen > DLM_RESNAME_MAXLEN) { + error = -EINVAL; + goto out_free; + } + } + error = -EINVAL; switch (kbuf->cmd) From d785f7d92f197b72d496bbcf54a160cd54f00263 Mon Sep 17 00:00:00 2001 From: Joseph Qi Date: Tue, 1 Sep 2026 13:47:11 -0400 Subject: [PATCH 0130/2286] dlm: fix buffer overflow from negative len in dlm_search_rsb_tree commit 080e5563f878c ("dlm: validate length in dlm_search_rsb_tree") only checks for len > DLM_RESNAME_MAXLEN, which does not catch negative values. While the input 'len' can be negative and a negative int passed to memcpy() is implicitly converted to a large size_t, causing a stack buffer overflow on the key[] array. Fix this by changing the 'len' parameter type from int to unsigned int. This ensures negative values from callers are implicitly converted to large unsigned values that are caught by the existing len > DLM_RESNAME_MAXLEN check. Acked-by: Alexander Aring Signed-off-by: Joseph Qi Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/lock.c | 6 ++++-- fs/dlm/lock.h | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index c381e102844656..373abdb4354a71 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -622,12 +622,14 @@ static int get_rsb_struct(struct dlm_ls *ls, const void *name, int len, return 0; } -int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, int len, - struct dlm_rsb **r_ret) +int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, + unsigned int len, struct dlm_rsb **r_ret) { char key[DLM_RESNAME_MAXLEN] = {}; + if (len > DLM_RESNAME_MAXLEN) return -EINVAL; + memcpy(key, name, len); *r_ret = rhashtable_lookup_fast(rhash, &key, dlm_rhash_rsb_params); if (*r_ret) diff --git a/fs/dlm/lock.h b/fs/dlm/lock.h index b23d7b854ed468..c75975937331cb 100644 --- a/fs/dlm/lock.h +++ b/fs/dlm/lock.h @@ -31,8 +31,8 @@ void resume_scan_timer(struct dlm_ls *ls); int dlm_master_lookup(struct dlm_ls *ls, int from_nodeid, const char *name, int len, unsigned int flags, int *r_nodeid, int *result); -int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, int len, - struct dlm_rsb **r_ret); +int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, + unsigned int len, struct dlm_rsb **r_ret); void dlm_recover_purge(struct dlm_ls *ls, const struct list_head *root_list); void dlm_purge_mstcpy_locks(struct dlm_rsb *r); From 7bf1aca6d626944091a5cb218b4e487547edff0a Mon Sep 17 00:00:00 2001 From: Danila Chernetsov Date: Tue, 1 Sep 2026 13:47:12 -0400 Subject: [PATCH 0131/2286] dlm: validate lock modes in recovery messages The DLM recovery path restores lock state from rcom_lock messages received from remote nodes. The lock modes in these messages are copied directly into the local lkb state without validating that they are within the valid DLM lock mode range. The rest of the DLM code assumes that lkb_rqmode and lkb_grmode contain valid lock modes. In particular, LVB callback handling in dlm_may_skip_callback() uses lock modes as indexes into the dlm_lvb_operations array: dlm_lvb_operations[prev_mode + 1][mode + 1] An invalid lock mode received during recovery could therefore result in an out-of-bounds read during subsequent LVB callback processing. Validate rl_rqmode and rl_grmode before storing them into the local LKB state. This preserves the lock mode invariant required by the rest of the DLM code. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: e7fd41792fc0 ("[DLM] The core of the DLM for GFS2/CLVM") Acked-by: Alexander Aring Signed-off-by: Danila Chernetsov Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/lock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index 373abdb4354a71..99c7a8c4e61226 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -5531,6 +5531,10 @@ static int receive_rcom_lock_args(struct dlm_ls *ls, struct dlm_lkb *lkb, { struct rcom_lock *rl = (struct rcom_lock *) rc->rc_buf; + if (rl->rl_rqmode < DLM_LOCK_IV || rl->rl_rqmode > DLM_LOCK_EX || + rl->rl_grmode < DLM_LOCK_IV || rl->rl_grmode > DLM_LOCK_EX) + return -EINVAL; + lkb->lkb_nodeid = le32_to_cpu(rc->rc_header.h_nodeid); lkb->lkb_ownpid = le32_to_cpu(rl->rl_ownpid); lkb->lkb_remid = le32_to_cpu(rl->rl_lkid); From 19982c99a302ab544c69f95d37ca3070c89762d3 Mon Sep 17 00:00:00 2001 From: Danila Chernetsov Date: Tue, 1 Sep 2026 13:47:13 -0400 Subject: [PATCH 0132/2286] dlm: fix NULL pointer dereference in dlm_dump_rsb_name() The function dlm_dump_rsb_name() is called from receive_rcom_lookup() when a debug dump is requested via a special RCOM_LOOKUP message with rc_id == 0xFFFFFFFF. The resource name passed to dlm_dump_rsb_name() comes from the received message. There is no guarantee that an RSB with this name exists in the local hash table. dlm_search_rsb_tree() returns 0 when the RSB is found and stores a valid pointer in r. When the lookup fails, it returns -EBADR and leaves r NULL. The current error handling is inverted: if (!error) goto out; As a result, dlm_dump_rsb() is called only when the lookup fails and r is NULL, resulting in a NULL pointer dereference. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 2d90354027ad ("dlm: merge toss and keep hash table lists into one list") Acked-by: Alexander Aring Signed-off-by: Danila Chernetsov Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/lock.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index 99c7a8c4e61226..d73c4aed1f8ac1 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -1423,7 +1423,7 @@ void dlm_dump_rsb_name(struct dlm_ls *ls, const char *name, int len) rcu_read_lock(); error = dlm_search_rsb_tree(&ls->ls_rsbtbl, name, len, &r); - if (!error) + if (error) goto out; dlm_dump_rsb(r); From b4e6ae7ba711758c38d5eb7d5a7a0712273bdf6f Mon Sep 17 00:00:00 2001 From: Alexander Aring Date: Tue, 1 Sep 2026 13:47:15 -0400 Subject: [PATCH 0133/2286] dlm: fix variable key length lookup Before commit 6c648035cbe7 ("dlm: switch to use rhashtable for rsbs") the rsb hashtable was dynamic key length. Accidentally it was changed to switch to static key length which can end in different results. We fixing this back to the original behaviour by adding the necessary functionality to rhashtable to handle the objects and lookups as dynamic key lengths. Fixes: 6c648035cbe7 ("dlm: switch to use rhashtable for rsbs") Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/config.c | 30 ++++++++++++++++++++++++++++-- fs/dlm/dlm_internal.h | 5 +++++ fs/dlm/lock.c | 6 ++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/fs/dlm/config.c b/fs/dlm/config.c index 53cd3329304231..6c5c3f049b33a0 100644 --- a/fs/dlm/config.c +++ b/fs/dlm/config.c @@ -64,11 +64,37 @@ static void release_node(struct config_item *); static struct configfs_attribute *comm_attrs[]; static struct configfs_attribute *node_attrs[]; +static u32 rsb_hashfn(const void *data, u32 len, u32 seed) +{ + const struct dlm_rsb_key *key = data; + + return jhash(key->name, key->len, 0); +} + +static u32 rsb_obj_hashfn(const void *data, u32 len, u32 seed) +{ + const struct dlm_rsb *r = data; + + return r->res_hash; +} + +static int rsb_obj_cmpfn(struct rhashtable_compare_arg *arg, const void *obj) +{ + const struct dlm_rsb_key *key = arg->key; + const struct dlm_rsb *r = obj; + + if (key->len != r->res_length) + return -1; + + return memcmp(&r->res_name, key->name, key->len); +} + const struct rhashtable_params dlm_rhash_rsb_params = { .nelem_hint = 3, /* start small */ - .key_len = DLM_RESNAME_MAXLEN, - .key_offset = offsetof(struct dlm_rsb, res_name), .head_offset = offsetof(struct dlm_rsb, res_node), + .hashfn = rsb_hashfn, + .obj_hashfn = rsb_obj_hashfn, + .obj_cmpfn = rsb_obj_cmpfn, .automatic_shrinking = true, }; diff --git a/fs/dlm/dlm_internal.h b/fs/dlm/dlm_internal.h index 9df842421ae06d..74c2e77d1b553e 100644 --- a/fs/dlm/dlm_internal.h +++ b/fs/dlm/dlm_internal.h @@ -340,6 +340,11 @@ struct dlm_rsb { char res_name[DLM_RESNAME_MAXLEN+1]; }; +struct dlm_rsb_key { + char name[DLM_RESNAME_MAXLEN]; + size_t len; +}; + /* dlm_master_lookup() flags */ #define DLM_LU_RECOVER_DIR 1 diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index d73c4aed1f8ac1..2609e4fdeba847 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -625,12 +625,14 @@ static int get_rsb_struct(struct dlm_ls *ls, const void *name, int len, int dlm_search_rsb_tree(struct rhashtable *rhash, const void *name, unsigned int len, struct dlm_rsb **r_ret) { - char key[DLM_RESNAME_MAXLEN] = {}; + struct dlm_rsb_key key = { + .len = len, + }; if (len > DLM_RESNAME_MAXLEN) return -EINVAL; - memcpy(key, name, len); + memcpy(key.name, name, len); *r_ret = rhashtable_lookup_fast(rhash, &key, dlm_rhash_rsb_params); if (*r_ret) return 0; From 82134823a07b4f45096a287f790c169511859617 Mon Sep 17 00:00:00 2001 From: Danilo Krummrich Date: Wed, 26 Aug 2026 15:27:18 +0200 Subject: [PATCH 0134/2286] MAINTAINERS: add Alice Ryhl as co-maintainer for Rust [ALLOC] Add Alice as co-maintainer for the Rust alloc module to share maintenance responsibility. This is also a natural fit, as some of the alloc code overlaps with the mm subsystem. Acked-by: Gary Guo Acked-by: Alice Ryhl Acked-by: Liam R. Howlett (Oracle) Acked-by: Vlastimil Babka (SUSE) Acked-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260826132729.3281308-1-dakr@kernel.org Signed-off-by: Danilo Krummrich --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..1945b1001849aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23973,6 +23973,7 @@ K: \b(?i:rust)\b RUST [ALLOC] M: Danilo Krummrich +M: Alice Ryhl R: Lorenzo Stoakes R: Vlastimil Babka R: Liam R. Howlett From ed9b6a1296f10e4881d93dfe6d76013fbbaeee87 Mon Sep 17 00:00:00 2001 From: Zqiang Date: Tue, 1 Sep 2026 13:47:14 -0400 Subject: [PATCH 0135/2286] dlm: wait for outstanding SRCU callbacks to complete in exit paths The dlm_lowcomms_exit() and dlm_midcomms_exit() iterate over the srcu protected connection and node hash tables and hand each element to call_srcu() for deferred freeing (connection_release() and midcomms_node_release()). call_srcu() is asynchronous: the callbacks are invoked only after an SRCU grace period, which may happen after the exit function has already returned. These exit functions are reached from exit_dlm() on module unload. Once they return, module teardown continues and the module text may be unloaded while call_srcu() callbacks are still pending. When such a callback finally runs, it executes freed module code and touches the static SRCU domains that are being torn down, resulting in a use-after-free. Add an srcu_barrier() after the call_srcu() loop in each exit function to wait for all outstanding callbacks of the respective SRCU domain to complete before returning. In dlm_midcomms_exit() the barrier is issued before dlm_lowcomms_exit() so that node callbacks are drained prior to tearing down the lower layer. Signed-off-by: Zqiang Acked-by: Alexander Aring Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/lowcomms.c | 1 + fs/dlm/midcomms.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/dlm/lowcomms.c b/fs/dlm/lowcomms.c index 2aff1c7c17de49..ea8353c4638d05 100644 --- a/fs/dlm/lowcomms.c +++ b/fs/dlm/lowcomms.c @@ -1984,4 +1984,5 @@ void dlm_lowcomms_exit(void) } } srcu_read_unlock(&connections_srcu, idx); + srcu_barrier(&connections_srcu); } diff --git a/fs/dlm/midcomms.c b/fs/dlm/midcomms.c index 8964164600d2d7..0454315244945d 100644 --- a/fs/dlm/midcomms.c +++ b/fs/dlm/midcomms.c @@ -1178,6 +1178,7 @@ void dlm_midcomms_exit(void) } } srcu_read_unlock(&nodes_srcu, idx); + srcu_barrier(&nodes_srcu); dlm_lowcomms_exit(); } From 518aa9948e4f2ba69b7066371d3ac8e4d31d604e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:33 -0700 Subject: [PATCH 0136/2286] x86/fpu: Check for missing AVX and AVX-512 xstate bits If the CPU declares AVX or AVX-512 support, verify that the corresponding xstate bits are also set. If not, warn and clear them. This eliminates the perceived need for AVX and AVX-512 optimized code in the kernel to call cpu_has_xfeatures(). That has never been universally done, which strongly suggests that it has never really been needed in practice, but this should remove any remaining doubt. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-2-ebiggers@kernel.org --- arch/x86/kernel/fpu/xstate.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index a7b6524a9dea22..97cfd4fb6cc00f 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -806,7 +806,7 @@ static u64 __init guest_default_mask(void) void __init fpu__init_system_xstate(unsigned int legacy_size) { unsigned int eax, ebx, ecx, edx; - u64 xfeatures; + u64 xfeatures, mask; int err; int i; @@ -818,6 +818,8 @@ void __init fpu__init_system_xstate(unsigned int legacy_size) if (!boot_cpu_has(X86_FEATURE_XSAVE)) { pr_info("x86/fpu: x87 FPU will use %s\n", boot_cpu_has(X86_FEATURE_FXSR) ? "FXSAVE" : "FSAVE"); + /* Disable all dependent flags too */ + setup_clear_cpu_cap(X86_FEATURE_XSAVE); return; } @@ -833,7 +835,8 @@ void __init fpu__init_system_xstate(unsigned int legacy_size) cpuid_count(CPUID_LEAF_XSTATE, 1, &eax, &ebx, &ecx, &edx); fpu_kernel_cfg.max_features |= ecx + ((u64)edx << 32); - if ((fpu_kernel_cfg.max_features & XFEATURE_MASK_FPSSE) != XFEATURE_MASK_FPSSE) { + mask = XFEATURE_MASK_FPSSE; + if ((fpu_kernel_cfg.max_features & mask) != mask) { /* * This indicates that something really unexpected happened * with the enumeration. Disable XSAVE and try to continue @@ -844,6 +847,24 @@ void __init fpu__init_system_xstate(unsigned int legacy_size) goto out_disable; } + mask |= XFEATURE_MASK_YMM; + if (boot_cpu_has(X86_FEATURE_AVX)) { + if ((fpu_kernel_cfg.max_features & mask) != mask) { + pr_err(FW_BUG + "x86/fpu: Disabling AVX support due to missing xstate features\n"); + setup_clear_cpu_cap(X86_FEATURE_AVX); + } + } + + mask |= XFEATURE_MASK_AVX512; + if (boot_cpu_has(X86_FEATURE_AVX512F)) { + if ((fpu_kernel_cfg.max_features & mask) != mask) { + pr_err(FW_BUG + "x86/fpu: Disabling AVX-512 support due to missing xstate features\n"); + setup_clear_cpu_cap(X86_FEATURE_AVX512F); + } + } + if (fpu_kernel_cfg.max_features & XFEATURE_MASK_APX && fpu_kernel_cfg.max_features & (XFEATURE_MASK_BNDREGS | XFEATURE_MASK_BNDCSR)) { /* From 6744e454d877e51394ed15e48e25c97dd6c62099 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 19 Aug 2026 10:25:29 +0200 Subject: [PATCH 0137/2286] x86/boot: Use BASE_BAUD and DEFAULT_SERIAL_PORT Replace the hard-coded baud rate and ttyS0 port with the existing BASE_BAUD and DEFAULT_SERIAL_PORT macros. In parse_earlyprintk(), assign the port directly and drop the redundant index variable as well as the bases array to simplify the code. No functional change. Signed-off-by: Thorsten Blum Signed-off-by: Dave Hansen Link: https://patch.msgid.link/20260819082529.116650-3-thorsten.blum@linux.dev --- arch/x86/boot/early_serial_console.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/arch/x86/boot/early_serial_console.c b/arch/x86/boot/early_serial_console.c index 5b83beab89e150..39fcd551fc811d 100644 --- a/arch/x86/boot/early_serial_console.c +++ b/arch/x86/boot/early_serial_console.c @@ -22,6 +22,7 @@ #define DLH 1 /* Divisor latch High */ #define DEFAULT_BAUD 9600 +#define BASE_BAUD (1843200 / 16) static void early_serial_init(int port, int baud) { @@ -33,7 +34,7 @@ static void early_serial_init(int port, int baud) outb(0, port + FCR); /* no fifo */ outb(0x3, port + MCR); /* DTR + RTS */ - divisor = 115200 / baud; + divisor = BASE_BAUD / baud; c = inb(port + LCR); outb(c | DLAB, port + LCR); outb(divisor & 0xff, port + DLL); @@ -74,16 +75,13 @@ static void parse_earlyprintk(void) else pos = e - arg; } else if (!strncmp(arg + pos, "ttyS", 4)) { - static const int bases[] = { 0x3f8, 0x2f8 }; - int idx = 0; - /* += strlen("ttyS"); */ pos += 4; if (arg[pos++] == '1') - idx = 1; - - port = bases[idx]; + port = 0x2f8; /* ttyS1 */ + else + port = DEFAULT_SERIAL_PORT; } if (arg[pos] == ',') @@ -98,7 +96,6 @@ static void parse_earlyprintk(void) early_serial_init(port, baud); } -#define BASE_BAUD (1843200/16) static unsigned int probe_baud(int port) { unsigned char lcr, dll, dlh; From 6664ad1026b558563702f9a1ce637df33e7c001e Mon Sep 17 00:00:00 2001 From: Ionut Nechita Date: Tue, 1 Sep 2026 10:10:40 +0300 Subject: [PATCH 0138/2286] x86/crash: Reserve elfcorehdr for CONFIG_NR_CPUS, not CONFIG_NR_CPUS_DEFAULT NR_CPUS_DEFAULT is purely a Kconfig thing. Its entire purpose in life is to start NR_CPUS at a sane value. There is precisely one (buggy) reference to it outside of Kconfig in the whole kernel: the x86 crash code. That code undersizes the elfcorehdr reservation whenever NR_CPUS exceeds NR_CPUS_DEFAULT, because the header carries one phdr per possible CPU and num_possible_cpus() is bounded by NR_CPUS. kexec_file_load(2) then fails with -EINVAL from sanity_check_segment_list(), and kexec_load(2) silently truncates the elfcorehdr, which surfaces later as a bad or unusable dump. Size the elfcorehdr reservation with NR_CPUS instead. Fixes: ea53ad9cf73b ("x86/crash: add x86 crash hotplug support") Signed-off-by: Ionut Nechita Signed-off-by: Dave Hansen Reviewed-by: Jinjie Ruan Reviewed-by: Bradley Morgan Reviewed-by: Sourabh Jain Acked-by: Baoquan He Link: https://patch.msgid.link/20260901071041.16311-2-ionut.nechita@windriver.com --- arch/x86/kernel/crash.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/crash.c b/arch/x86/kernel/crash.c index e681ec9cf1dc85..e6f23933a6df2f 100644 --- a/arch/x86/kernel/crash.c +++ b/arch/x86/kernel/crash.c @@ -369,9 +369,9 @@ int crash_load_segments(struct kimage *image) * maximum CPUs and maximum memory ranges. */ if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG)) - pnum = 2 + CONFIG_NR_CPUS_DEFAULT + CONFIG_CRASH_MAX_MEMORY_RANGES; + pnum = 2 + CONFIG_NR_CPUS + CONFIG_CRASH_MAX_MEMORY_RANGES; else - pnum += 2 + CONFIG_NR_CPUS_DEFAULT; + pnum += 2 + CONFIG_NR_CPUS; if (pnum < (unsigned long)PN_XNUM) { kbuf.memsz = pnum * sizeof(Elf64_Phdr); @@ -430,7 +430,7 @@ unsigned int arch_crash_get_elfcorehdr_size(void) unsigned int sz; /* kernel_map, VMCOREINFO and maximum CPUs */ - sz = 2 + CONFIG_NR_CPUS_DEFAULT; + sz = 2 + CONFIG_NR_CPUS; if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG)) sz += CONFIG_CRASH_MAX_MEMORY_RANGES; sz *= sizeof(Elf64_Phdr); From d949fa7b1ec54c626b656f873b95d76b8ab0d143 Mon Sep 17 00:00:00 2001 From: Ionut Nechita Date: Tue, 1 Sep 2026 10:10:41 +0300 Subject: [PATCH 0139/2286] crash: Update stale NR_CPUS_DEFAULT references in elfcorehdr sizing docs The elfcorehdr reservation is now sized with NR_CPUS, but two comments still name NR_CPUS_DEFAULT: the one above crash_handle_hotplug_event() and the CRASH_MAX_MEMORY_RANGES help text. Update both. Documentation only, no functional change. Suggested-by: Jinjie Ruan Suggested-by: Bradley Morgan Signed-off-by: Ionut Nechita Signed-off-by: Dave Hansen Reviewed-by: Bradley Morgan Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260901071041.16311-3-ionut.nechita@windriver.com --- kernel/Kconfig.kexec | 2 +- kernel/crash_core.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/Kconfig.kexec b/kernel/Kconfig.kexec index 15632358bcf711..a97ed9605602fb 100644 --- a/kernel/Kconfig.kexec +++ b/kernel/Kconfig.kexec @@ -167,7 +167,7 @@ config CRASH_MAX_MEMORY_RANGES memory regions that the elfcorehdr buffer/segment can accommodate. These regions are obtained via walk_system_ram_res(); eg. the 'System RAM' entries in /proc/iomem. - This value is combined with NR_CPUS_DEFAULT and multiplied by + This value is combined with NR_CPUS and multiplied by sizeof(Elf64_Phdr) to determine the final elfcorehdr memory buffer/ segment size. The value 8192, for example, covers a (sparsely populated) 1TiB system diff --git a/kernel/crash_core.c b/kernel/crash_core.c index 2b36aa9fade064..d0bd2d0cf8990b 100644 --- a/kernel/crash_core.c +++ b/kernel/crash_core.c @@ -648,7 +648,7 @@ int crash_check_hotplug_support(void) * new list of CPUs and memory. To make changes to the elfcorehdr, it * should be large enough to permit a growing number of CPU and Memory * resources. One can estimate the elfcorehdr memory size based on - * NR_CPUS_DEFAULT and CRASH_MAX_MEMORY_RANGES. The elfcorehdr is + * NR_CPUS and CRASH_MAX_MEMORY_RANGES. The elfcorehdr is * excluded from SHA verification by default if the architecture * supports crash hotplug. */ From c23746524fafe992e594d84f432534dabf4daf20 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:35 -0400 Subject: [PATCH 0140/2286] ext2: annotate s_rsv_window_root as requiring s_rsv_window_lock The per-filesystem reservation window rb-tree (s_rsv_window_root) is protected by s_rsv_window_lock. Mark the s_rsv_window_root field with __guarded_by() for Clang's context analysis. The helpers (ext2_rsv_window_add, rsv_window_remove, and find_next_reservable_window) that mutate or walk the tree are all called with the s_rsv_window_lock held. Annotate these helpers with __must_hold(). The accesses in ext2_fill_super() are before the superblock is live. Since no concurrent access should be possible, s_rsv_window_lock is not taken. Convert the spinlock initialization to use scoped_guard(spinlock_init, ...) and place the writes under the guard to prevent warnings. Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-8-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/balloc.c | 3 +++ fs/ext2/ext2.h | 5 +++-- fs/ext2/super.c | 26 ++++++++++++++------------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c index 53c91cb38bdee9..80acc1e1938710 100644 --- a/fs/ext2/balloc.c +++ b/fs/ext2/balloc.c @@ -334,6 +334,7 @@ search_reserve_window(struct rb_root *root, ext2_fsblk_t goal) */ void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv) + __must_hold(&EXT2_SB(sb)->s_rsv_window_lock) { struct rb_root *root = &EXT2_SB(sb)->s_rsv_window_root; struct rb_node *node = &rsv->rsv_node; @@ -373,6 +374,7 @@ void ext2_rsv_window_add(struct super_block *sb, */ static void rsv_window_remove(struct super_block *sb, struct ext2_reserve_window_node *rsv) + __must_hold(&EXT2_SB(sb)->s_rsv_window_lock) { rsv->rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; rsv->rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; @@ -759,6 +761,7 @@ static int find_next_reservable_window( struct super_block * sb, ext2_fsblk_t start_block, ext2_fsblk_t last_block) + __must_hold(&EXT2_SB(sb)->s_rsv_window_lock) { struct rb_node *next; struct ext2_reserve_window_node *rsv, *prev; diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 4b1622eec2af17..7aeb7cfb0cebf3 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -102,7 +102,7 @@ struct ext2_sb_info { struct blockgroup_lock *s_blockgroup_lock; /* root of the per fs reservation window tree */ spinlock_t s_rsv_window_lock; - struct rb_root s_rsv_window_root; + struct rb_root s_rsv_window_root __guarded_by(&s_rsv_window_lock); struct ext2_reserve_window_node s_rsv_window_head; /* * s_lock protects against concurrent modifications of s_mount_state, @@ -712,7 +712,8 @@ extern void ext2_discard_reservation (struct inode *); extern int ext2_should_retry_alloc(struct super_block *sb, int *retries); extern void ext2_init_block_alloc_info(struct inode *inode) __must_hold(&EXT2_I(inode)->truncate_mutex); -extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv); +extern void ext2_rsv_window_add(struct super_block *sb, struct ext2_reserve_window_node *rsv) + __must_hold(&EXT2_SB(sb)->s_rsv_window_lock); /* dir.c */ int ext2_add_link(struct dentry *, struct inode *); diff --git a/fs/ext2/super.c b/fs/ext2/super.c index 4d9e5b43f59a69..fb217ef2d69f07 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -1134,18 +1134,20 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc) /* per filesystem reservation list head & lock */ spin_lock_init(&sbi->s_rsv_window_lock); - sbi->s_rsv_window_root = RB_ROOT; - /* - * Add a single, static dummy reservation to the start of the - * reservation window list --- it gives us a placeholder for - * append-at-start-of-list which makes the allocation logic - * _much_ simpler. - */ - sbi->s_rsv_window_head.rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; - sbi->s_rsv_window_head.rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; - sbi->s_rsv_window_head.rsv_alloc_hit = 0; - sbi->s_rsv_window_head.rsv_goal_size = 0; - ext2_rsv_window_add(sb, &sbi->s_rsv_window_head); + scoped_guard(spinlock, &EXT2_SB(sb)->s_rsv_window_lock) { + sbi->s_rsv_window_root = RB_ROOT; + /* + * Add a single, static dummy reservation to the start of the + * reservation window list --- it gives us a placeholder for + * append-at-start-of-list which makes the allocation logic + * _much_ simpler. + */ + sbi->s_rsv_window_head.rsv_start = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; + sbi->s_rsv_window_head.rsv_end = EXT2_RESERVE_WINDOW_NOT_ALLOCATED; + sbi->s_rsv_window_head.rsv_alloc_hit = 0; + sbi->s_rsv_window_head.rsv_goal_size = 0; + ext2_rsv_window_add(sb, &sbi->s_rsv_window_head); + } err = percpu_counter_init(&sbi->s_freeblocks_counter, ext2_count_free_blocks(sb), GFP_KERNEL); From 9708538ba204d20aa9a5936530862254dcbc6f70 Mon Sep 17 00:00:00 2001 From: Timothy Day Date: Tue, 11 Aug 2026 12:03:36 -0400 Subject: [PATCH 0141/2286] ext2: enable context analysis support for ext2 filesystem Update ext2 Makefile to support context analysis [1]. [1] https://docs.kernel.org/dev-tools/context-analysis.html Signed-off-by: Timothy Day Acked-by: Marco Elver Link: https://patch.msgid.link/20260811160336.782342-9-timday@thelustrecollective.com Signed-off-by: Jan Kara --- fs/ext2/Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/ext2/Makefile b/fs/ext2/Makefile index 8860948ef9ca4e..33db2e9dc90827 100644 --- a/fs/ext2/Makefile +++ b/fs/ext2/Makefile @@ -3,6 +3,8 @@ # Makefile for the linux ext2-filesystem routines. # +CONTEXT_ANALYSIS := y + obj-$(CONFIG_EXT2_FS) += ext2.o ext2-y := balloc.o dir.o file.o ialloc.o inode.o \ From 2a95801ea1a57d4e11976ac7704aa75f67bf5d32 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 04:29:24 -0700 Subject: [PATCH 0142/2286] locking/csd-lock: Pack csd_lock_wait_toolong() state into a struct csd_lock_wait_toolong() has some fields and they are being expanded now, separate them into a structure, that can be easily digestible. This simplify the function aslo, given the fields were passed by reference, and the ts0/ts1 names say nothing about what the two timestamps hold. Pack them into struct csd_wait_state and name the timestamps for what they store, ts_start and ts_report. The local ts2 becomes ts_now. Reporting a further timestamp, such as next patch, then costs a struct member rather than another argument. No functional change. Suggested-by: Dmitry Ilvokhin Signed-off-by: Breno Leitao Reviewed-by: Dmitry Ilvokhin Signed-off-by: Paul E. McKenney Cc: Andrew Morton --- kernel/smp.c | 56 +++++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/kernel/smp.c b/kernel/smp.c index b696bcc60c08fc..07b29524843c14 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -236,50 +236,58 @@ bool csd_lock_is_stuck(void) return !!atomic_read(&n_csd_lock_stuck); } +/* State that csd_lock_wait_toolong() carries across the __csd_lock_wait() loop. */ +struct csd_wait_state { + u64 ts_start; /* When the wait began. */ + u64 ts_report; /* When the last complaint was printed. */ + int bug_id; + unsigned long nmessages; +}; + /* * Complain if too much time spent waiting. Note that only * the CSD_TYPE_SYNC/ASYNC types provide the destination CPU, * so waiting on other types gets much less information. */ -static bool csd_lock_wait_toolong(call_single_data_t *csd, u64 ts0, u64 *ts1, int *bug_id, unsigned long *nmessages) +static bool csd_lock_wait_toolong(call_single_data_t *csd, struct csd_wait_state *state) { int cpu = -1; int cpux; bool firsttime; - u64 ts2, ts_delta; + u64 ts_now, ts_delta; call_single_data_t *cpu_cur_csd; unsigned int flags = READ_ONCE(csd->node.u_flags); unsigned long long csd_lock_timeout_ns = csd_lock_timeout * NSEC_PER_MSEC; if (!(flags & CSD_FLAG_LOCK)) { - if (!unlikely(*bug_id)) + if (!unlikely(state->bug_id)) return true; cpu = csd_lock_wait_getcpu(csd); pr_alert("csd: CSD lock (#%d) got unstuck on CPU#%02d, CPU#%02d released the lock.\n", - *bug_id, raw_smp_processor_id(), cpu); + state->bug_id, raw_smp_processor_id(), cpu); atomic_dec(&n_csd_lock_stuck); return true; } - ts2 = ktime_get_mono_fast_ns(); + ts_now = ktime_get_mono_fast_ns(); /* How long since we last checked for a stuck CSD lock.*/ - ts_delta = ts2 - *ts1; - if (likely(ts_delta <= csd_lock_timeout_ns * (*nmessages + 1) * - (!*nmessages ? 1 : (ilog2(num_online_cpus()) / 2 + 1)) || + ts_delta = ts_now - state->ts_report; + if (likely(ts_delta <= csd_lock_timeout_ns * (state->nmessages + 1) * + (!state->nmessages ? 1 : (ilog2(num_online_cpus()) / 2 + 1)) || csd_lock_timeout_ns == 0)) return false; - if (ts0 > ts2) { + if (state->ts_start > ts_now) { /* Our own sched_clock went backward; don't blame another CPU. */ - ts_delta = ts0 - ts2; + ts_delta = state->ts_start - ts_now; pr_alert("sched_clock on CPU %d went backward by %llu ns\n", raw_smp_processor_id(), ts_delta); - *ts1 = ts2; + state->ts_report = ts_now; return false; } - firsttime = !*bug_id; + firsttime = !state->bug_id; if (firsttime) - *bug_id = atomic_inc_return(&csd_bug_count); + state->bug_id = atomic_inc_return(&csd_bug_count); cpu = csd_lock_wait_getcpu(csd); if (WARN_ONCE(cpu < 0 || cpu >= nr_cpu_ids, "%s: cpu = %d\n", __func__, cpu)) cpux = 0; @@ -293,11 +301,11 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, u64 ts0, u64 *ts1, in */ cpu_cur_csd = smp_load_acquire(&per_cpu(cur_csd, cpux)); /* How long since this CSD lock was stuck. */ - ts_delta = ts2 - ts0; + ts_delta = ts_now - state->ts_start; pr_alert("csd: %s non-responsive CSD lock (#%d) on CPU#%d, waiting %lld ns for CPU#%02d %pS(%ps).\n", - firsttime ? "Detected" : "Continued", *bug_id, raw_smp_processor_id(), (s64)ts_delta, + firsttime ? "Detected" : "Continued", state->bug_id, raw_smp_processor_id(), (s64)ts_delta, cpu, csd->func, csd->info); - (*nmessages)++; + state->nmessages++; if (firsttime) atomic_inc(&n_csd_lock_stuck); /* @@ -308,23 +316,23 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, u64 ts0, u64 *ts1, in BUG_ON(panic_on_ipistall > 0 && (s64)ts_delta > ((s64)panic_on_ipistall * NSEC_PER_MSEC)); if (cpu_cur_csd && csd != cpu_cur_csd) { pr_alert("\tcsd: CSD lock (#%d) handling prior %pS(%ps) request.\n", - *bug_id, READ_ONCE(per_cpu(cur_csd_func, cpux)), + state->bug_id, READ_ONCE(per_cpu(cur_csd_func, cpux)), READ_ONCE(per_cpu(cur_csd_info, cpux))); } else { pr_alert("\tcsd: CSD lock (#%d) %s.\n", - *bug_id, !cpu_cur_csd ? "unresponsive" : "handling this request"); + state->bug_id, !cpu_cur_csd ? "unresponsive" : "handling this request"); } if (cpu >= 0) { if (atomic_cmpxchg_acquire(&per_cpu(trigger_backtrace, cpu), 1, 0)) dump_cpu_task(cpu); if (!cpu_cur_csd) { - pr_alert("csd: Re-sending CSD lock (#%d) IPI from CPU#%02d to CPU#%02d\n", *bug_id, raw_smp_processor_id(), cpu); + pr_alert("csd: Re-sending CSD lock (#%d) IPI from CPU#%02d to CPU#%02d\n", state->bug_id, raw_smp_processor_id(), cpu); arch_send_call_function_single_ipi(cpu); } } if (firsttime) dump_stack(); - *ts1 = ts2; + state->ts_report = ts_now; return false; } @@ -338,15 +346,13 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, u64 ts0, u64 *ts1, in */ static void __csd_lock_wait(call_single_data_t *csd) { - unsigned long nmessages = 0; - int bug_id = 0; - u64 ts0, ts1; + struct csd_wait_state state = {}; guard(preempt)(); - ts1 = ts0 = ktime_get_mono_fast_ns(); + state.ts_report = state.ts_start = ktime_get_mono_fast_ns(); for (;;) { - if (csd_lock_wait_toolong(csd, ts0, &ts1, &bug_id, &nmessages)) + if (csd_lock_wait_toolong(csd, &state)) break; cpu_relax(); } From 09951fe4b237c109ece8cf123006aafc52760e07 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 04:29:25 -0700 Subject: [PATCH 0143/2286] locking/csd-lock: Report how long a stuck CSD lock took to recover The CSD lock debug output is a useful way to catch IPI stalls, but when the lock finally recovers it only says that it did: smp: csd: CSD lock (#1) got unstuck on CPU#32, CPU#123 released the lock. How long the target took to answer is left out, even though csd_lock_wait_toolong() already has the timestamp the wait started from. At Meta's fleet, that line fired 211K times in the last 24 hours, so plenty of stalls get reported with no indication of how long they lasted. Print how long the lock was stuck, and, when an IPI was re-sent, how long after that re-send the target released, as: smp: csd: CSD lock (#1) got unstuck on CPU#00, CPU#01 released the lock after 8000854660 ns, 3000825778 ns after the last IPI re-send. Signed-off-by: Breno Leitao Reviewed-by: Dmitry Ilvokhin Signed-off-by: Paul E. McKenney Cc: Andrew Morton --- kernel/smp.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/kernel/smp.c b/kernel/smp.c index 07b29524843c14..161d09cb0b3462 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -240,10 +240,28 @@ bool csd_lock_is_stuck(void) struct csd_wait_state { u64 ts_start; /* When the wait began. */ u64 ts_report; /* When the last complaint was printed. */ + u64 ts_resend; /* When the last IPI was re-sent, 0 if never. */ int bug_id; unsigned long nmessages; }; +/* + * Report a CSD lock that came back, @ts_unstuck being when the release was + * noticed. Only mention the re-send delta if an IPI was actually re-sent. + */ +static void csd_lock_print_unstuck(struct csd_wait_state *state, int cpu, u64 ts_unstuck) +{ + if (state->ts_resend) + pr_alert("csd: CSD lock (#%d) got unstuck on CPU#%02d, CPU#%02d released the lock after %lld ns, %lld ns after the last IPI re-send.\n", + state->bug_id, raw_smp_processor_id(), cpu, + (s64)(ts_unstuck - state->ts_start), + (s64)(ts_unstuck - state->ts_resend)); + else + pr_alert("csd: CSD lock (#%d) got unstuck on CPU#%02d, CPU#%02d released the lock after %lld ns.\n", + state->bug_id, raw_smp_processor_id(), cpu, + (s64)(ts_unstuck - state->ts_start)); +} + /* * Complain if too much time spent waiting. Note that only * the CSD_TYPE_SYNC/ASYNC types provide the destination CPU, @@ -262,9 +280,9 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, struct csd_wait_state if (!(flags & CSD_FLAG_LOCK)) { if (!unlikely(state->bug_id)) return true; + ts_now = ktime_get_mono_fast_ns(); cpu = csd_lock_wait_getcpu(csd); - pr_alert("csd: CSD lock (#%d) got unstuck on CPU#%02d, CPU#%02d released the lock.\n", - state->bug_id, raw_smp_processor_id(), cpu); + csd_lock_print_unstuck(state, cpu, ts_now); atomic_dec(&n_csd_lock_stuck); return true; } @@ -328,6 +346,7 @@ static bool csd_lock_wait_toolong(call_single_data_t *csd, struct csd_wait_state if (!cpu_cur_csd) { pr_alert("csd: Re-sending CSD lock (#%d) IPI from CPU#%02d to CPU#%02d\n", state->bug_id, raw_smp_processor_id(), cpu); arch_send_call_function_single_ipi(cpu); + state->ts_resend = ktime_get_mono_fast_ns(); } } if (firsttime) From f929e61143a2842e8f8ec9f2319ee19dbb048e32 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 10 Aug 2026 04:29:26 -0700 Subject: [PATCH 0144/2286] lib/test_csd_lock: Add a module to stall a CPU on a CSD lock Add test_csd_lock, a module that keeps one CPU from answering an IPI for as long as its stall_ms parameter says, so that the CSD-lock debug code has a stall to report. With in_handler=1 the CPU stalls inside a CSD handler instead, which is the case where the IPI is not re-sent. The module needs CONFIG_CSD_LOCK_WAIT_DEBUG and csdlock_debug=1. Loading it runs one stall and then fails the load with -EAGAIN, the way test_lockup does, so that nothing is left loaded afterwards. Signed-off-by: Breno Leitao Signed-off-by: Paul E. McKenney Cc: Andrew Morton --- lib/Kconfig.debug | 12 +++ lib/Makefile | 1 + lib/test_csd_lock.c | 173 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 lib/test_csd_lock.c diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625e8..6f4c894c6c1ad8 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1329,6 +1329,18 @@ config WQ_CPU_INTENSIVE_REPORT triggering likely indicates that the work item should be switched to use an unbound workqueue. +config TEST_CSD_LOCK + tristate "Test module to stall a CPU on a CSD lock" + depends on m + depends on CSD_LOCK_WAIT_DEBUG + help + This builds the "test_csd_lock" module, which keeps one CPU from + answering an IPI for as long as its stall_ms parameter says, so + that the CSD-lock debug code has a stall to report. It needs + csdlock_debug=1 to be of any use. + + If unsure, say N. + config TEST_LOCKUP tristate "Test module to generate lockups" depends on m diff --git a/lib/Makefile b/lib/Makefile index dfab958327c5cc..43421c39d21bbf 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -100,6 +100,7 @@ obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o +obj-$(CONFIG_TEST_CSD_LOCK) += test_csd_lock.o obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o obj-$(CONFIG_TEST_HMM) += test_hmm.o obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o diff --git a/lib/test_csd_lock.c b/lib/test_csd_lock.c new file mode 100644 index 00000000000000..30c6c3332c3fc0 --- /dev/null +++ b/lib/test_csd_lock.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Keep one CPU from answering an IPI, so that the CSD-lock debug code in + * kernel/smp.c has a stall to report. + * + * Copyright (c) 2026 Meta Platforms, Inc. and affiliates + * Copyright (c) 2026 Breno Leitao + * + * The target either spins with interrupts disabled, which leaves it idle as + * far as the debug code can tell and gets the IPI re-sent, or spins inside a + * CSD handler, which does not. The recovery message differs between the two. + * + * Loading the module runs one stall, then fails the load with -EAGAIN so + * that nothing is left loaded afterwards: + * + * echo 500 > /sys/module/smp/parameters/csd_lock_timeout + * modprobe test_csd_lock stall_ms=1000 in_handler=0 + * + * csd_lock_timeout has to be below stall_ms for the stall to be reported at + * all, and the report has to come out before the CPU answers, so leave it + * some room. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#define STALL_MS_MAX 10000 + +static unsigned int stall_ms = 1000; +module_param(stall_ms, uint, 0444); +MODULE_PARM_DESC(stall_ms, "Time the target CPU ignores the IPI, in milliseconds."); + +static int stall_cpu = -1; +module_param(stall_cpu, int, 0444); +MODULE_PARM_DESC(stall_cpu, "CPU to stall, or -1 for the first online one."); + +static bool in_handler; +module_param(in_handler, bool, 0444); +MODULE_PARM_DESC(in_handler, "Stall inside a CSD handler instead of with interrupts disabled."); + +static int target_cpu; +static bool target_stalling; +static bool hog_launched; +static struct work_struct irqoff_work; +static struct work_struct sender_work; +static call_single_data_t hog_csd; +static DECLARE_COMPLETION(hog_done); + +static void csd_test_nop(void *unused) +{ +} + +static void csd_test_spin(void) +{ + u64 end = ktime_get_mono_fast_ns() + (u64)stall_ms * NSEC_PER_MSEC; + + while (ktime_get_mono_fast_ns() < end) + cpu_relax(); +} + +/* Nothing is running for the target while interrupts are off, so it gets a new IPI. */ +static void csd_test_irqoff_fn(struct work_struct *work) +{ + local_irq_disable(); + /* Pairs with the load in csd_test_sender_fn(), which waits for this. */ + smp_store_release(&target_stalling, true); + csd_test_spin(); + local_irq_enable(); +} + +/* Here cur_csd stays set on the target, which suppresses the re-send. */ +static void csd_test_hog_fn(void *unused) +{ + /* Pairs with the load in csd_test_sender_fn(), which waits for this. */ + smp_store_release(&target_stalling, true); + csd_test_spin(); + complete(&hog_done); +} + +/* + * Start the stall from here rather than from module init, so that however + * long this work item waits to be scheduled comes off before the target + * stops answering, not out of the middle of the stall. + */ +static void csd_test_sender_fn(struct work_struct *work) +{ + u64 deadline, ts; + int err; + + if (in_handler) { + hog_csd.func = csd_test_hog_fn; + err = smp_call_function_single_async(target_cpu, &hog_csd); + if (err) { + pr_err("cannot queue the CSD handler on CPU%d: %d\n", target_cpu, err); + return; + } + } else { + queue_work_on(target_cpu, system_highpri_wq, &irqoff_work); + } + WRITE_ONCE(hog_launched, true); + + deadline = ktime_get_mono_fast_ns() + (u64)STALL_MS_MAX * NSEC_PER_MSEC; + /* Pairs with the store in the stall functions: send once it is stuck. */ + while (!smp_load_acquire(&target_stalling)) { + if (ktime_get_mono_fast_ns() > deadline) { + pr_err("CPU%d never stopped answering\n", target_cpu); + return; + } + cpu_relax(); + } + + ts = ktime_get_mono_fast_ns(); + smp_call_function_single(target_cpu, csd_test_nop, NULL, 1); + pr_info("CPU%d answered after %llu ns\n", target_cpu, + ktime_get_mono_fast_ns() - ts); +} + +static int __init test_csd_lock_init(void) +{ + int sender_cpu; + int ret = 0; + + if (!stall_ms || stall_ms > STALL_MS_MAX) { + pr_err("stall_ms must be between 1 and %d\n", STALL_MS_MAX); + return -EINVAL; + } + + INIT_WORK(&irqoff_work, csd_test_irqoff_fn); + INIT_WORK(&sender_work, csd_test_sender_fn); + + cpus_read_lock(); + + target_cpu = stall_cpu < 0 ? cpumask_first(cpu_online_mask) : stall_cpu; + sender_cpu = nr_cpu_ids; + if (target_cpu < nr_cpu_ids && cpu_online(target_cpu)) + sender_cpu = cpumask_any_but(cpu_online_mask, target_cpu); + if (sender_cpu >= nr_cpu_ids) { + pr_err("need CPU%d and one other CPU online\n", target_cpu); + ret = -EINVAL; + goto unlock; + } + + pr_info("stalling CPU%d for %u ms %s, IPI from CPU%d\n", target_cpu, stall_ms, + in_handler ? "inside a CSD handler" : "with interrupts disabled", sender_cpu); + + queue_work_on(sender_cpu, system_highpri_wq, &sender_work); + flush_work(&sender_work); + flush_work(&irqoff_work); + + /* The CSD has to be idle again before this module goes away. */ + if (in_handler && READ_ONCE(hog_launched) && + !wait_for_completion_timeout(&hog_done, msecs_to_jiffies(2 * STALL_MS_MAX))) + pr_err("CSD handler on CPU%d never finished\n", target_cpu); + + /* The stall is over and there is nothing left to hold, so go away. */ + ret = -EAGAIN; +unlock: + cpus_read_unlock(); + + return ret; +} +module_init(test_csd_lock_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Breno Leitao "); +MODULE_DESCRIPTION("Test module to stall a CPU on a CSD lock"); From 3f6df9ae1b5e4ae2278200cbf670ff32dbe8f4e1 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Fri, 4 Sep 2026 02:23:58 +0900 Subject: [PATCH 0145/2286] nilfs2: strengthen consistency checks in nilfs_direct_propagate() nilfs_direct_propagate() is responsible for updating the virtual block number associated with a dirty data block in the direct mapping. It is not designed to handle intermediate node blocks. Furthermore, it assumes that the block offset of the passed buffer head is within the range of NILFS_DIRECT_KEY_MAX; otherwise, an out-of-bounds memory access could occur within the inode containing the bmap root. While such inconsistencies stem from bugs, they can cause silent, harmful side effects unless a debug kernel is used - as was the case with a recent slab out-of-bounds access issue found in this function. Therefore, issue a kernel warning via WARN_ON_ONCE() and return -EINVAL for these anomalies, allowing the bmap layer to handle the situation as a filesystem error. Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/direct.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c index b8643d3aa2f88a..64da677dc8d4ff 100644 --- a/fs/nilfs2/direct.c +++ b/fs/nilfs2/direct.c @@ -270,8 +270,14 @@ static int nilfs_direct_propagate(struct nilfs_bmap *bmap, if (!NILFS_BMAP_USE_VBN(bmap)) return 0; + if (WARN_ON_ONCE(buffer_nilfs_node(bh))) + return -EINVAL; + dat = nilfs_bmap_get_dat(bmap); key = nilfs_bmap_data_get_key(bmap, bh); + if (WARN_ON_ONCE(key > NILFS_DIRECT_KEY_MAX)) + return -EINVAL; + ptr = nilfs_direct_get_ptr(bmap, key); if (ptr == NILFS_BMAP_INVALID_PTR) return -EINVAL; From 94235414a0568f260b60b89ec584ff4829d5e8c9 Mon Sep 17 00:00:00 2001 From: Lukas Beckmann Date: Sat, 22 Aug 2026 18:05:00 +0200 Subject: [PATCH 0146/2286] genirq: Discard cpumask of irqaffinity= boot parameter if parsing fails irq_affinity_setup() ignores the return value of cpulist_parse(). This can result in using a "half parsed" cpulist. E.g. on a system with 8 cores, the parameter "irqaffinity=0,1,6-10,4" would result in using only CPU 0 and 1, since the parsing fails at 6-10. Other boot parameters which take a cpulist (e.g. isolcpus=, workqueue.unbound_cpus=) already handle this by falling back to a default. Check the return value of cpulist_parse() and let init_irq_default_affinity() set the default in early_irq_init() later, if parsing fails. Signed-off-by: Lukas Beckmann Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260822160502.4665-1-lbckmnn@mailbox.org --- kernel/irq/irqdesc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c index 3a818f07a1013a..7cbcc3fb9e6d4c 100644 --- a/kernel/irq/irqdesc.c +++ b/kernel/irq/irqdesc.c @@ -28,7 +28,15 @@ static struct lock_class_key irq_desc_lock_class; static int __init irq_affinity_setup(char *str) { alloc_bootmem_cpumask_var(&irq_default_affinity); - cpulist_parse(str, irq_default_affinity); + if (cpulist_parse(str, irq_default_affinity) < 0) { + /* + * Clear the mask so that init_irq_default_affinity() + * defaults it later. + */ + cpumask_clear(irq_default_affinity); + pr_warn("irqaffinity: incorrect CPU range, using default\n"); + return 1; + } /* * Set at least the boot cpu. We don't want to end up with * bugreports caused by random commandline masks From aeebb4884f0e8feb9929f013067488b7f2081f33 Mon Sep 17 00:00:00 2001 From: Paul Dolan Date: Wed, 2 Sep 2026 18:49:39 +0100 Subject: [PATCH 0147/2286] selftests/clone3: tighten CLONE_CLEAR_SIGHAND conflict test The CLONE_CLEAR_SIGHAND test intends to verify that CLONE_CLEAR_SIGHAND and CLONE_SIGHAND are mutually exclusive. However, CLONE_SIGHAND without CLONE_VM is independently invalid, so the test input has another reason to fail with EINVAL. Add CLONE_VM so that CLONE_SIGHAND is otherwise valid, and require clone3() to fail with EINVAL. This ensures the test exercises the intended flag conflict and does not accept an unrelated failure. Fixes: de5287235631 ("tests: test CLONE_CLEAR_SIGHAND") Signed-off-by: Paul Dolan Link: https://patch.msgid.link/20260902174939.15135-1-paul.dolan.dev@gmail.com Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/clone3/clone3_clear_sighand.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/clone3/clone3_clear_sighand.c b/tools/testing/selftests/clone3/clone3_clear_sighand.c index de0c9d62015de8..0ad62b80ada208 100644 --- a/tools/testing/selftests/clone3/clone3_clear_sighand.c +++ b/tools/testing/selftests/clone3/clone3_clear_sighand.c @@ -50,12 +50,12 @@ static void test_clone3_clear_sighand(void) * Check that CLONE_CLEAR_SIGHAND and CLONE_SIGHAND are mutually * exclusive. */ - args.flags |= CLONE_CLEAR_SIGHAND | CLONE_SIGHAND; + args.flags |= CLONE_VM | CLONE_CLEAR_SIGHAND | CLONE_SIGHAND; args.exit_signal = SIGCHLD; pid = sys_clone3(&args, sizeof(args)); - if (pid > 0) + if (pid != -1 || errno != EINVAL) ksft_exit_fail_msg( - "clone3(CLONE_CLEAR_SIGHAND | CLONE_SIGHAND) succeeded\n"); + "clone3(CLONE_CLEAR_SIGHAND | CLONE_SIGHAND) did not fail with EINVAL\n"); act.sa_handler = nop_handler; ret = sigemptyset(&act.sa_mask); From b4ad6eb49532a43ad06c4d59ec5d8c80a012eb3f Mon Sep 17 00:00:00 2001 From: Paul Dolan Date: Wed, 2 Sep 2026 17:26:15 +0100 Subject: [PATCH 0148/2286] selftests/pidfd: fix PPID values in error message The PIDFD_GET_INFO PPID check compares info.ppid against getppid(), but its failure message prints pid and info.pid from the preceding PID check. Print the PPID values being compared so that a failure reports the correct information. Fixes: cdda1f26e74b ("pidfd: add ioctl to retrieve pid info") Signed-off-by: Paul Dolan Link: https://patch.msgid.link/20260902162615.11531-1-paul.dolan.dev@gmail.com Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/pidfd/pidfd_open_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/pidfd/pidfd_open_test.c b/tools/testing/selftests/pidfd/pidfd_open_test.c index 318e6f09c8e0b6..c6698b7cdb479d 100644 --- a/tools/testing/selftests/pidfd/pidfd_open_test.c +++ b/tools/testing/selftests/pidfd/pidfd_open_test.c @@ -168,7 +168,7 @@ int main(int argc, char **argv) } if (info.ppid != getppid()) { ksft_print_msg("ppid %d does not match ppid from ioctl %d\n", - pid, info.pid); + getppid(), info.ppid); goto on_error; } if (info.ruid != getuid()) { From 7b6344a49cf5fed1879ee5c125d01a2535fbd725 Mon Sep 17 00:00:00 2001 From: Tzung-Bi Shih Date: Wed, 19 Aug 2026 05:11:47 +0000 Subject: [PATCH 0149/2286] platform/chrome: cros_ec_proto: Simplify cros_ec_wait_until_complete() There is no need to defer the return out of the loop. Replace the `break` with an immediate `return -EPROTO`. With the `break` removed, execution only reaches the end of the function if it exhausts all EC_COMMAND_RETRIES. The post-loop check is therefore redundant and replaced with an unconditional `return -EAGAIN`. Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_proto.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index b13312c1b093fb..b81b37c7169a50 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -166,10 +166,8 @@ static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, if (msg->result != EC_RES_SUCCESS) return ret; - if (ret == 0) { - ret = -EPROTO; - break; - } + if (ret == 0) + return -EPROTO; if (!(status->flags & EC_COMMS_STATUS_PROCESSING)) { u32 orig_cmd, orig_outsize, orig_version; @@ -198,10 +196,7 @@ static int cros_ec_wait_until_complete(struct cros_ec_device *ec_dev, } } - if (i >= EC_COMMAND_RETRIES) - ret = -EAGAIN; - - return ret; + return -EAGAIN; } static int cros_ec_send_command(struct cros_ec_device *ec_dev, struct cros_ec_command *msg) From c38b381d20273f1f3d0f30c019a6020c34815f83 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Tue, 1 Sep 2026 18:40:50 -0700 Subject: [PATCH 0150/2286] selftests: cover kernfs dentry revalidation kernfs_test only checked two xattr calls. It did not touch the part of kernfs that userspace hits most: the dentry cache. The kernel adds and removes kernfs nodes on its own, with no VFS call behind it, and removal cannot always unhash the dentries that are already cached. So ->d_revalidate() is what keeps the cache honest, let's add tests for it. Add tests that make the kernel change kernfs directly, instead of going through create and unlink: - writing cgroup.subtree_control makes the kernel add and remove files in every child cgroup. That checks two things: a cached negative dentry is dropped once the file appears, and a cached positive one is dropped once the file goes away. - renaming a network interface renames the sysfs node under a dentry that is already cached. - sysfs mounted in a new network namespace must list that namespace's interfaces and not the parent's, which covers the KERNFS_NS tagging code. - many lookups running against concurrent mkdir and rmdir. A lookup may only succeed or fail with an errno that means "it is gone". - open across removal, readdir returning each entry once, and a telldir()/seekdir() round trip, which uses the name hash cookie in kernfs_dir_pos(). Test the other direction too: walking dentries that are already cached must not drop them. That is not just a slowdown. d_invalidate() calls detach_mounts(), so a dentry dropped by mistake also unmounts whatever is mounted under that directory. Both filesystems are needed. cgroup2 has no ->rename and is not namespace tagged. sysfs cannot be mounted with a set of children we control. The config fragment stops a kernel built with kselftest-merge from passing while it runs almost nothing. To confirm the tests really catch a broken backstop, kernfs_dir_changed() was stubbed to return false. 8 of the 11 tests then failed. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260902014050.499002-1-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- MAINTAINERS | 1 + tools/testing/selftests/filesystems/config | 7 + .../selftests/filesystems/kernfs_test.c | 695 +++++++++++++++++- 3 files changed, 702 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/config diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..013e2b9ccc6ba4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14419,6 +14419,7 @@ S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git F: fs/kernfs/ F: include/linux/kernfs.h +F: tools/testing/selftests/filesystems/kernfs_test.c KEXEC M: Andrew Morton diff --git a/tools/testing/selftests/filesystems/config b/tools/testing/selftests/filesystems/config new file mode 100644 index 00000000000000..7231710d5ce77c --- /dev/null +++ b/tools/testing/selftests/filesystems/config @@ -0,0 +1,7 @@ +CONFIG_CGROUPS=y +CONFIG_CGROUP_PIDS=y +CONFIG_NAMESPACES=y +CONFIG_NET=y +CONFIG_NET_NS=y +CONFIG_INET=y +CONFIG_SYSFS=y diff --git a/tools/testing/selftests/filesystems/kernfs_test.c b/tools/testing/selftests/filesystems/kernfs_test.c index 84c2b910a60db0..2178428b9665e2 100644 --- a/tools/testing/selftests/filesystems/kernfs_test.c +++ b/tools/testing/selftests/filesystems/kernfs_test.c @@ -2,8 +2,20 @@ #define _GNU_SOURCE #define __SANE_USERSPACE_TYPES__ +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -34,5 +46,686 @@ TEST(kernfs_getxattr) EXPECT_EQ(close(fd), 0); } -TEST_HARNESS_MAIN +/* + * Exercise the kernfs dentry cache: lookup, revalidation of positive and + * negative dentries, readdir and namespace tagging. + * + * These drive kernfs from kernel context rather than VFS create/unlink, + * which is what ->d_revalidate() exists for: writing cgroup.subtree_control + * adds and removes files in every child cgroup with no VFS operation + * touching those names. + */ + +#define CG_SCRATCH "kernfs_selftest" +#define TEST_IFNAME "kfstest0" + +/* + * Controllers that add a file to each child cgroup when enabled. The probe + * file must be owned by the controller: cgroup_base_files[] entries such as + * cpu.stat exist in every cgroup regardless, and every file the cpu + * controller does own is behind a Kconfig symbol, so cpu is not usable here. + */ +static const struct { + const char *name; + const char *probe_file; +} controllers[] = { + { "memory", "memory.current" }, + { "pids", "pids.current" }, +}; + +static int find_cgroup2_root(char *buf, size_t len) +{ + char line[PATH_MAX * 2]; + FILE *f; + int ret = -1; + + f = fopen("/proc/self/mounts", "re"); + if (!f) + return -1; + + while (fgets(line, sizeof(line), f)) { + char mnt[PATH_MAX], type[64]; + + /* Octal escaping can expand a path fourfold; bound both %s. */ + if (sscanf(line, "%*s %4095s %63s", mnt, type) != 2) + continue; + if (strcmp(type, "cgroup2")) + continue; + if (strlen(mnt) >= len) + break; + strcpy(buf, mnt); + ret = 0; + break; + } + + fclose(f); + return ret; +} + +static int write_file(const char *path, const char *val) +{ + ssize_t len = strlen(val); + int fd, ret; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -1; + ret = write(fd, val, len) == len ? 0 : -1; + close(fd); + return ret; +} + +static bool file_has_word(const char *path, const char *word) +{ + char buf[4096], *tok, *save; + bool found = false; + ssize_t n; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + n = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (n < 0) + return false; + buf[n] = '\0'; + + for (tok = strtok_r(buf, "\n ", &save); tok; + tok = strtok_r(NULL, "\n ", &save)) { + if (!strcmp(tok, word)) { + found = true; + break; + } + } + return found; +} + +static bool path_is_mounted(const char *path) +{ + char line[PATH_MAX * 2]; + bool found = false; + FILE *f; + + f = fopen("/proc/self/mounts", "re"); + if (!f) + return false; + while (fgets(line, sizeof(line), f)) { + char mnt[PATH_MAX]; + + if (sscanf(line, "%*s %4095s", mnt) != 1) + continue; + if (!strcmp(mnt, path)) { + found = true; + break; + } + } + fclose(f); + return found; +} + +/* Shared by the stress tests below. */ +static bool stress_deadline(const struct timespec *end) +{ + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec > end->tv_sec || + (now.tv_sec == end->tv_sec && now.tv_nsec >= end->tv_nsec); +} + +FIXTURE(kernfs_cgroup) +{ + char scratch[PATH_MAX]; /* /kernfs_selftest. */ + char child[PATH_MAX]; /* /child */ + char probe[PATH_MAX]; /* child's controller file */ + char scratch_sc[PATH_MAX]; /* scratch's cgroup.subtree_control */ + char root_sc[PATH_MAX]; /* root's cgroup.subtree_control */ + char enable[32]; /* "+" */ + char disable[32]; /* "-" */ + char mnt[PATH_MAX]; /* our own mount, if we made one */ + bool mounted; + bool enabled_at_root; +}; + +/* A cgroup stays busy briefly after its last task exits. */ +static void rmdir_retry(const char *path) +{ + int i; + + for (i = 0; i < 500; i++) { + if (!rmdir(path) || errno != EBUSY) + return; + usleep(10000); + } +} + +/* + * Undo whatever SETUP managed to do. The harness skips TEARDOWN after a + * failed or skipped SETUP, so SETUP must call this before returning early. + */ +static void kernfs_cgroup_undo(FIXTURE_DATA(kernfs_cgroup) *self) +{ + rmdir_retry(self->child); + rmdir(self->scratch); + if (self->enabled_at_root) + write_file(self->root_sc, self->disable); + if (self->mounted) { + umount2(self->mnt, MNT_DETACH); + rmdir(self->mnt); + } + self->enabled_at_root = false; + self->mounted = false; +} + +FIXTURE_SETUP(kernfs_cgroup) +{ + char root[PATH_MAX], ctl[PATH_MAX]; + const char *probe_file = NULL; + size_t i; + + if (geteuid()) + SKIP(return, "test needs to run as root"); + + /* + * A private mount namespace stops our mounts leaking, but does not + * isolate the cgroup hierarchy: cgroup2 has one default hierarchy + * however many times it is mounted. The scratch cgroups live in the + * host's and must be removed, not discarded with the namespace. + */ + if (unshare(CLONE_NEWNS)) + SKIP(return, "unshare(CLONE_NEWNS): %s", strerror(errno)); + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) + SKIP(return, "make / private: %s", strerror(errno)); + + /* Use an existing cgroup2 mount if there is one, else make our own. */ + if (find_cgroup2_root(root, sizeof(root))) { + strcpy(self->mnt, "/tmp/kernfs_selftest_cg2.XXXXXX"); + if (!mkdtemp(self->mnt)) + SKIP(return, "mkdtemp: %s", strerror(errno)); + if (mount("none", self->mnt, "cgroup2", 0, NULL)) { + rmdir(self->mnt); + SKIP(return, "mount cgroup2: %s", strerror(errno)); + } + self->mounted = true; + strcpy(root, self->mnt); + } + + snprintf(self->root_sc, sizeof(self->root_sc), + "%s/cgroup.subtree_control", root); + snprintf(ctl, sizeof(ctl), "%s/cgroup.controllers", root); + + /* Named after our pid so we cannot collide with anything else. */ + snprintf(self->scratch, sizeof(self->scratch), "%s/%s.%d", root, + CG_SCRATCH, getpid()); + snprintf(self->child, sizeof(self->child), "%s/child", self->scratch); + snprintf(self->scratch_sc, sizeof(self->scratch_sc), + "%s/cgroup.subtree_control", self->scratch); + + for (i = 0; i < ARRAY_SIZE(controllers); i++) { + if (!file_has_word(ctl, controllers[i].name)) + continue; + snprintf(self->enable, sizeof(self->enable), "+%s", + controllers[i].name); + snprintf(self->disable, sizeof(self->disable), "-%s", + controllers[i].name); + probe_file = controllers[i].probe_file; + + /* + * A controller must be in the root's subtree_control before + * it appears in our scratch cgroup. Note if we enabled it, + * so it can be put back. + */ + self->enabled_at_root = !file_has_word(self->root_sc, + controllers[i].name); + if (self->enabled_at_root && + write_file(self->root_sc, self->enable)) { + self->enabled_at_root = false; + probe_file = NULL; + continue; + } + break; + } + if (!probe_file) { + kernfs_cgroup_undo(self); + SKIP(return, "no usable cgroup2 controller"); + } + + snprintf(self->probe, sizeof(self->probe), "%s/%s", self->child, + probe_file); + + /* + * Only an unusable environment may skip. A scratch cgroup named + * after our own pid should always be creatable, so failing to make + * one is a result -- skipping would let a broken kernel look green. + */ + if (mkdir(self->scratch, 0755)) { + int err = errno; + + kernfs_cgroup_undo(self); + if (err == EROFS || err == EACCES || err == EPERM) + SKIP(return, "mkdir %s: %s", self->scratch, + strerror(err)); + ASSERT_EQ(err, 0) TH_LOG("mkdir %s: %s", self->scratch, + strerror(err)); + } + if (mkdir(self->child, 0755)) { + int err = errno; + + kernfs_cgroup_undo(self); + ASSERT_EQ(err, 0) TH_LOG("mkdir %s: %s", self->child, + strerror(err)); + } + + /* + * The tests below need the probe file to appear and disappear with + * the controller, so it must be absent now, before anything enables + * it in the scratch cgroup. Skip rather than fail: a probe file that + * is already there means the table names one the controller does not + * own, not that the kernel is broken. + */ + if (!access(self->probe, F_OK)) { + kernfs_cgroup_undo(self); + SKIP(return, "%s is not owned by the %s controller", + probe_file, self->enable + 1); + } +} + +FIXTURE_TEARDOWN(kernfs_cgroup) +{ + write_file(self->scratch_sc, self->disable); + kernfs_cgroup_undo(self); +} + +/* + * Walking already-cached dentries must not invalidate them. Spurious + * invalidation is not merely slow: d_invalidate() calls detach_mounts(), so + * an unrelated lookup would silently tear down any mount below. + */ +TEST_F(kernfs_cgroup, path_walk_does_not_invalidate) +{ + char src[] = "/tmp/kernfs_selftest_bind.XXXXXX"; + char sub[PATH_MAX], probe[PATH_MAX]; + int i; + + snprintf(sub, sizeof(sub), "%s/sub", self->child); + ASSERT_EQ(mkdir(sub, 0755), 0); + + if (!mkdtemp(src)) { + rmdir(sub); + SKIP(return, "mkdtemp: %s", strerror(errno)); + } + if (mount(src, sub, NULL, MS_BIND, NULL)) { + int err = errno; + + rmdir(sub); + rmdir(src); + SKIP(return, "bind mount onto a cgroup dir: %s", strerror(err)); + } + ASSERT_TRUE(path_is_mounted(sub)); + + /* Walk a sibling path through the same directory, repeatedly. */ + snprintf(probe, sizeof(probe), "%s/cgroup.procs", self->child); + for (i = 0; i < 8; i++) { + int fd = open(probe, O_RDONLY | O_CLOEXEC); + + if (fd >= 0) + close(fd); + } + + EXPECT_TRUE(path_is_mounted(sub)); + + umount2(sub, MNT_DETACH); + rmdir(sub); + rmdir(src); +} + +/* + * A cached negative dentry must be invalidated when the kernel creates the + * name behind the dcache's back. That is what kernfs_dir_changed() and + * kernfs_elem_dir::rev are for. + */ +TEST_F(kernfs_cgroup, negative_dentry_invalidated_by_kernel_create) +{ + struct stat st; + + /* Caches a negative dentry for the probe file. */ + ASSERT_EQ(stat(self->probe, &st), -1); + ASSERT_EQ(errno, ENOENT); + + /* The kernel now creates it, with no VFS operation on that name. */ + ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0); + + EXPECT_EQ(stat(self->probe, &st), 0); +} + +/* The mirror image: a cached positive dentry must go when the node does. */ +TEST_F(kernfs_cgroup, positive_dentry_invalidated_by_kernel_remove) +{ + struct stat st; + + ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0); + /* Caches a positive dentry. */ + ASSERT_EQ(stat(self->probe, &st), 0); + ASSERT_EQ(write_file(self->scratch_sc, self->disable), 0); + + ASSERT_EQ(stat(self->probe, &st), -1); + EXPECT_EQ(errno, ENOENT); +} + +/* Opening a removed node fails; it never returns stale content. */ +TEST_F(kernfs_cgroup, open_after_rmdir_fails) +{ + char path[PATH_MAX]; + char buf[64]; + int fd; + + snprintf(path, sizeof(path), "%s/cgroup.procs", self->child); + + fd = open(path, O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + + ASSERT_EQ(rmdir(self->child), 0); + + /* Lookup by path must fail. */ + EXPECT_EQ(open(path, O_RDONLY | O_CLOEXEC), -1); + EXPECT_EQ(errno, ENOENT); + + /* + * An fd held across removal must fail rather than return stale + * content. rmdir() deactivates the node before it returns, so + * kernfs_seq_start() fails to get an active reference. + */ + EXPECT_LT(read(fd, buf, sizeof(buf)), 0); + EXPECT_EQ(errno, ENODEV); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(mkdir(self->child, 0755), 0); +} + +/* readdir returns every entry exactly once. */ +TEST_F(kernfs_cgroup, readdir_no_duplicates) +{ + char names[512][NAME_MAX + 1]; + struct dirent *de; + int n = 0, i, j; + DIR *d; + + ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0); + + d = opendir(self->child); + ASSERT_NE(d, NULL); + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + ASSERT_LT(n, (int)ARRAY_SIZE(names)); + strncpy(names[n], de->d_name, NAME_MAX); + names[n][NAME_MAX] = '\0'; + n++; + } + closedir(d); + + ASSERT_GT(n, 0); + for (i = 0; i < n; i++) + for (j = i + 1; j < n; j++) + EXPECT_STRNE(names[i], names[j]); +} + +/* + * A telldir() cookie must resolve back to the same entry after seekdir(). + * kernfs encodes the cookie as the node's name hash, so this covers + * kernfs_dir_pos() as well as plain iteration. + */ +TEST_F(kernfs_cgroup, readdir_seekdir_roundtrip) +{ + char names[512][NAME_MAX + 1]; + struct dirent *de; + long pos[512]; + int n = 0, i; + DIR *d; + + ASSERT_EQ(write_file(self->scratch_sc, self->enable), 0); + + d = opendir(self->child); + ASSERT_NE(d, NULL); + + /* Record the cookie *before* reading each entry, with its name. */ + while (1) { + long here = telldir(d); + + de = readdir(d); + if (!de) + break; + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + ASSERT_LT(n, (int)ARRAY_SIZE(pos)); + pos[n] = here; + strncpy(names[n], de->d_name, NAME_MAX); + names[n][NAME_MAX] = '\0'; + n++; + } + ASSERT_GT(n, 0); + + /* Seeking back to a cookie must land on the entry it was taken at. */ + for (i = 0; i < n; i++) { + seekdir(d, pos[i]); + de = readdir(d); + ASSERT_NE(de, NULL); + EXPECT_STREQ(de->d_name, names[i]); + } + + closedir(d); +} + +#define STRESS_SECS 2 +#define STRESS_DIRS 4 +#define STRESS_READERS 4 + +/* + * Hammer lookup against creation and removal. Revalidation holds no lock + * against the writers, so what makes it safe is that every answer it can + * give is one the caller already handles: a reader must only ever see + * success or an errno meaning "it went away", never garbage or a hang. + */ +TEST_F(kernfs_cgroup, lookup_vs_create_remove_stress) +{ + pid_t pids[STRESS_DIRS + STRESS_READERS]; + struct timespec end; + int i, status, n = 0; + + clock_gettime(CLOCK_MONOTONIC, &end); + end.tv_sec += STRESS_SECS; + + for (i = 0; i < STRESS_DIRS; i++) { + pid_t pid = fork(); + + ASSERT_GE(pid, 0); + if (pid == 0) { + char dir[PATH_MAX]; + + snprintf(dir, sizeof(dir), "%s/s%d", self->scratch, i); + while (!stress_deadline(&end)) { + if (mkdir(dir, 0755) && errno != EEXIST) + _exit(10); + if (rmdir(dir) && errno != ENOENT && + errno != EBUSY) + _exit(11); + } + _exit(0); + } + pids[n++] = pid; + } + + for (i = 0; i < STRESS_READERS; i++) { + pid_t pid = fork(); + + ASSERT_GE(pid, 0); + if (pid == 0) { + /* Start each reader on a different directory. */ + unsigned int seq = i; + + while (!stress_deadline(&end)) { + int which = seq++ % STRESS_DIRS; + char path[PATH_MAX]; + struct stat st; + int fd; + + snprintf(path, sizeof(path), + "%s/s%d/cgroup.procs", + self->scratch, which); + + if (stat(path, &st) && errno != ENOENT && + errno != ENODEV) + _exit(20); + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT && errno != ENODEV) + _exit(21); + } else { + close(fd); + } + + if (access(path, F_OK) && errno != ENOENT && + errno != ENODEV) + _exit(22); + } + _exit(0); + } + pids[n++] = pid; + } + + for (i = 0; i < n; i++) { + ASSERT_EQ(waitpid(pids[i], &status, 0), pids[i]); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + } + + for (i = 0; i < STRESS_DIRS; i++) { + char dir[PATH_MAX]; + + snprintf(dir, sizeof(dir), "%s/s%d", self->scratch, i); + rmdir_retry(dir); + } +} + +/* + * sysfs is namespace tagged (KERNFS_NS) and supports rename; cgroup2 does + * neither. Run in a private netns with its own sysfs so the host is + * untouched. + */ +FIXTURE(kernfs_netns) +{ + char mnt[PATH_MAX]; + char net[PATH_MAX]; + bool mounted; +}; + +FIXTURE_SETUP(kernfs_netns) +{ + if (geteuid()) + SKIP(return, "test needs to run as root"); + + if (unshare(CLONE_NEWNS | CLONE_NEWNET)) + SKIP(return, "unshare(CLONE_NEWNS|CLONE_NEWNET): %s", + strerror(errno)); + + /* Don't let our sysfs mount escape into the parent namespace. */ + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + strcpy(self->mnt, "/tmp/kernfs_selftest_sysfs.XXXXXX"); + ASSERT_NE(mkdtemp(self->mnt), NULL); + + if (mount("none", self->mnt, "sysfs", 0, NULL)) { + rmdir(self->mnt); + SKIP(return, "mount sysfs: %s", strerror(errno)); + } + self->mounted = true; + + snprintf(self->net, sizeof(self->net), "%s/class/net", self->mnt); +} + +FIXTURE_TEARDOWN(kernfs_netns) +{ + if (self->mounted) + umount2(self->mnt, MNT_DETACH); + rmdir(self->mnt); +} + +/* + * sysfs must show this network namespace's interfaces, not the parent's. + * + * Do not assume a fresh netns contains only "lo": fallback tunnel devices + * (tunl0, sit0, gre0, ...) are created in every namespace unless + * net.core.fb_tunnels_only_for_init_net is set, so which names appear + * depends on the modules the host has. Check the set instead -- + * if_nametoindex() resolves in the current netns, so every name sysfs shows + * must resolve there, and the counts must agree. + */ +TEST_F(kernfs_netns, ns_tag_isolates_class_net) +{ + struct if_nameindex *idx, *i; + bool found_lo = false; + int n = 0, want = 0; + struct dirent *de; + DIR *d; + + d = opendir(self->net); + ASSERT_NE(d, NULL); + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + EXPECT_NE(if_nametoindex(de->d_name), 0u) + TH_LOG("%s is not in this netns", de->d_name); + if (!strcmp(de->d_name, "lo")) + found_lo = true; + n++; + } + closedir(d); + + idx = if_nameindex(); + ASSERT_NE(idx, NULL); + for (i = idx; i->if_index; i++) + want++; + if_freenameindex(idx); + + EXPECT_TRUE(found_lo); + EXPECT_EQ(n, want); +} + +/* + * After a rename the old name must stop resolving and the new one must + * start, even though both dentries are already cached. + */ +TEST_F(kernfs_netns, rename_is_revalidated) +{ + char old_path[PATH_MAX], new_path[PATH_MAX]; + struct ifreq ifr = {}; + struct stat st; + int sk; + + snprintf(old_path, sizeof(old_path), "%s/lo", self->net); + snprintf(new_path, sizeof(new_path), "%s/%s", self->net, TEST_IFNAME); + + /* Warm both dentries: one positive, one negative. */ + ASSERT_EQ(stat(old_path, &st), 0); + ASSERT_EQ(stat(new_path, &st), -1); + ASSERT_EQ(errno, ENOENT); + + sk = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + ASSERT_GE(sk, 0); + strcpy(ifr.ifr_name, "lo"); + strcpy(ifr.ifr_newname, TEST_IFNAME); + if (ioctl(sk, SIOCSIFNAME, &ifr)) { + close(sk); + SKIP(return, "SIOCSIFNAME: %s", strerror(errno)); + } + close(sk); + + EXPECT_EQ(stat(old_path, &st), -1); + EXPECT_EQ(errno, ENOENT); + EXPECT_EQ(stat(new_path, &st), 0); +} + +TEST_HARNESS_MAIN From 6a35b82f8ef70e0129344ece5bbe5bca76acd886 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 3 Sep 2026 11:05:04 +0200 Subject: [PATCH 0151/2286] ocfs2: Refuse to enable ocfs2 format for another filesystem It is possible to issue Q_QUOTAON quotactl(2) e.g. for ext4 filesystem requesting to enable quota using ocfs2 quota format. With suitable passed arguments this can get upto ocfs2_local_check_quota_file() where the passed superblock is from the ext4 filesystem and that can cause all sorts of interesting things, most likely crashes. As ocfs2 quotas are specific to ocfs2 filesystem, verify we are indeed enabling quotas there. Reviewed-by: Joseph Qi Link: https://patch.msgid.link/20260903090508.1360863-3-jack@suse.cz Signed-off-by: Jan Kara --- fs/ocfs2/quota_local.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/ocfs2/quota_local.c b/fs/ocfs2/quota_local.c index f55810c59b1b11..a2b276bcd0a87a 100644 --- a/fs/ocfs2/quota_local.c +++ b/fs/ocfs2/quota_local.c @@ -171,6 +171,10 @@ static int ocfs2_local_check_quota_file(struct super_block *sb, int type) struct ocfs2_disk_dqheader *dqhead; int status, ret = 0; + /* OCFS2 quota format is supported only for OCFS2 filesystems */ + if (sb->s_magic != OCFS2_SUPER_MAGIC) + goto out_err; + /* First check whether we understand local quota file */ status = ocfs2_read_quota_block(linode, 0, &bh); if (status) { From 9e1483cf2d712012f9f35a13e51dbc58b6c90606 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 3 Sep 2026 11:05:05 +0200 Subject: [PATCH 0152/2286] shmem: Refuse to enable tmpfs format for another filesystem It is possible to issue Q_QUOTAON quotactl(2) e.g. for ext4 filesystem requesting to enable quota using tmpfs quota format. With suitable passed arguments this can actually succeed in enabling tmpfs quota format on top of ext4 filesystem which leads to crashes later in dquot_acquire(). As tmpfs quota format is intended for tmpfs filesystems only, verify we are indeed enabling quotas there. Reported-by: Farhad Alemi Acked-by: Hugh Dickins Link: https://patch.msgid.link/20260903090508.1360863-4-jack@suse.cz Signed-off-by: Jan Kara --- mm/shmem_quota.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mm/shmem_quota.c b/mm/shmem_quota.c index d0b92d6da50f52..a3fa4bd93e753a 100644 --- a/mm/shmem_quota.c +++ b/mm/shmem_quota.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,10 @@ struct quota_id { static int shmem_check_quota_file(struct super_block *sb, int type) { + /* Verify enabling happens on tmpfs superblock */ + if (sb->s_magic != TMPFS_MAGIC) + return 0; + /* There is no real quota file, nothing to do */ return 1; } From e0bec04067a9196ab27e9237fd605fdb4a688c7f Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 28 Aug 2026 16:27:34 +0700 Subject: [PATCH 0153/2286] chardev: check return value of kobject_set_name() in __register_chrdev() The kobject_set_name() function can fail and return -ENOMEM if memory allocation fails. While all other functions called inside __register_chrdev() are properly checked for errors, kobject_set_name() was missing this check. Add the error check for kobject_set_name() and jump to the 'out' label on failure to maintain consistency and ensure proper error handling. Found by manual code inspection. Reviewed-by: Jan Kara Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260828092734.600911-1-phucduc.bui@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/char_dev.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/char_dev.c b/fs/char_dev.c index 00229e25c10f61..5ce5423f6c991c 100644 --- a/fs/char_dev.c +++ b/fs/char_dev.c @@ -280,7 +280,9 @@ int __register_chrdev(unsigned int major, unsigned int baseminor, cdev->owner = fops->owner; cdev->ops = fops; - kobject_set_name(&cdev->kobj, "%s", name); + err = kobject_set_name(&cdev->kobj, "%s", name); + if (err) + goto out; err = cdev_add(cdev, MKDEV(cd->major, baseminor), count); if (err) From e36781a626b2708d42a188f6274f1c7897251f1f Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Fri, 4 Sep 2026 01:29:19 -0700 Subject: [PATCH 0154/2286] bfs: remove the filesystem BFS (not to be confused with BeFS, the BeOS filesystem) is the filesystem UnixWare uses for its boot partition. It has extremely limited functionality, only supporting contiguous files and no subdirectories. Even though the bfs driver is very small and is unlikely to cause future maintenance problems, given that the only type of data stored on such a partition is likely to be kernels and bootloader settings, there is very little reason anyone would want to access it from Linux. Other old Unix filesystems (efs, freevxfs) have been removed recently, and bfs is highly unlikely to have any users, so remove it as well. Retain the UAPI header to be safe. Also update the former maintainer's email address in CREDITS. Signed-off-by: Ethan Nelson-Moore Link: https://patch.msgid.link/20260904082954.48738-1-enelsonmoore@gmail.com Acked-by: Jan Kara Acked-by: Lorenzo Stoakes (ARM) Acked-by: Tigran Aivazian Signed-off-by: Christian Brauner (Amutable) --- CREDITS | 2 +- Documentation/filesystems/befs.rst | 4 +- Documentation/filesystems/bfs.rst | 60 -- Documentation/filesystems/index.rst | 1 - MAINTAINERS | 7 - arch/mips/configs/malta_defconfig | 1 - arch/mips/configs/malta_kvm_defconfig | 1 - arch/mips/configs/maltaup_xpa_defconfig | 1 - arch/mips/configs/rm200_defconfig | 1 - arch/powerpc/configs/fsl-emb-nonhw.config | 1 - arch/powerpc/configs/ppc6xx_defconfig | 1 - fs/Kconfig | 1 - fs/Makefile | 1 - fs/bfs/Kconfig | 21 - fs/bfs/Makefile | 8 - fs/bfs/bfs.h | 69 --- fs/bfs/dir.c | 356 ------------ fs/bfs/file.c | 203 ------- fs/bfs/inode.c | 538 ------------------ .../filesystems/statmount/statmount_test.c | 2 +- 20 files changed, 4 insertions(+), 1275 deletions(-) delete mode 100644 Documentation/filesystems/bfs.rst delete mode 100644 fs/bfs/Kconfig delete mode 100644 fs/bfs/Makefile delete mode 100644 fs/bfs/bfs.h delete mode 100644 fs/bfs/dir.c delete mode 100644 fs/bfs/file.c delete mode 100644 fs/bfs/inode.c diff --git a/CREDITS b/CREDITS index a1455b4710517a..4021f0a412d6ec 100644 --- a/CREDITS +++ b/CREDITS @@ -57,7 +57,7 @@ S: Longford, Ireland S: Sydney, Australia N: Tigran A. Aivazian -E: tigran@aivazian.fsnet.co.uk +E: aivazian.tigran@gmail.com W: http://www.moses.uklinux.net/patches D: BFS filesystem D: Intel IA32 CPU microcode update support diff --git a/Documentation/filesystems/befs.rst b/Documentation/filesystems/befs.rst index a22f603b2938ea..c1dbfe9c95d292 100644 --- a/Documentation/filesystems/befs.rst +++ b/Documentation/filesystems/befs.rst @@ -44,8 +44,8 @@ implementation. Which is it, BFS or BEFS? ========================= Be, Inc said, "BeOS Filesystem is officially called BFS, not BeFS". -But Unixware Boot Filesystem is called bfs, too. And they are already in -the kernel. Because of this naming conflict, on Linux the BeOS +But the UnixWare Boot Filesystem is called bfs, too, and it was already +in the kernel. Because of this naming conflict, on Linux the BeOS filesystem is called befs. How to Install diff --git a/Documentation/filesystems/bfs.rst b/Documentation/filesystems/bfs.rst deleted file mode 100644 index ce14b901880729..00000000000000 --- a/Documentation/filesystems/bfs.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -======================== -BFS Filesystem for Linux -======================== - -The BFS filesystem is used by SCO UnixWare OS for the /stand slice, which -usually contains the kernel image and a few other files required for the -boot process. - -In order to access /stand partition under Linux you obviously need to -know the partition number and the kernel must support UnixWare disk slices -(CONFIG_UNIXWARE_DISKLABEL config option). However BFS support does not -depend on having UnixWare disklabel support because one can also mount -BFS filesystem via loopback:: - - # losetup /dev/loop0 stand.img - # mount -t bfs /dev/loop0 /mnt/stand - -where stand.img is a file containing the image of BFS filesystem. -When you have finished using it and umounted you need to also deallocate -/dev/loop0 device by:: - - # losetup -d /dev/loop0 - -You can simplify mounting by just typing:: - - # mount -t bfs -o loop stand.img /mnt/stand - -this will allocate the first available loopback device (and load loop.o -kernel module if necessary) automatically. If the loopback driver is not -loaded automatically, make sure that you have compiled the module and -that modprobe is functioning. Beware that umount will not deallocate -/dev/loopN device if /etc/mtab file on your system is a symbolic link to -/proc/mounts. You will need to do it manually using "-d" switch of -losetup(8). Read losetup(8) manpage for more info. - -To create the BFS image under UnixWare you need to find out first which -slice contains it. The command prtvtoc(1M) is your friend:: - - # prtvtoc /dev/rdsk/c0b0t0d0s0 - -(assuming your root disk is on target=0, lun=0, bus=0, controller=0). Then you -look for the slice with tag "STAND", which is usually slice 10. With this -information you can use dd(1) to create the BFS image:: - - # umount /stand - # dd if=/dev/rdsk/c0b0t0d0sa of=stand.img bs=512 - -Just in case, you can verify that you have done the right thing by checking -the magic number:: - - # od -Ad -tx4 stand.img | more - -The first 4 bytes should be 0x1badface. - -If you have any patches, questions or suggestions regarding this BFS -implementation please contact the author: - -Tigran Aivazian diff --git a/Documentation/filesystems/index.rst b/Documentation/filesystems/index.rst index 734a45e516675c..7cce7cc28d126f 100644 --- a/Documentation/filesystems/index.rst +++ b/Documentation/filesystems/index.rst @@ -75,7 +75,6 @@ Documentation for filesystem implementations. autofs autofs-mount-control befs - bfs btrfs ceph coda diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..080d42ecd63690 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4627,13 +4627,6 @@ S: Odd Fixes F: Documentation/block/bfq-iosched.rst F: block/bfq-* -BFS FILE SYSTEM -M: "Tigran A. Aivazian" -S: Maintained -F: Documentation/filesystems/bfs.rst -F: fs/bfs/ -F: include/uapi/linux/bfs_fs.h - BITMAP API M: Yury Norov R: Rasmus Villemoes diff --git a/arch/mips/configs/malta_defconfig b/arch/mips/configs/malta_defconfig index 56a8f76dab4140..4fb4d45f3cdce1 100644 --- a/arch/mips/configs/malta_defconfig +++ b/arch/mips/configs/malta_defconfig @@ -333,7 +333,6 @@ CONFIG_AFFS_FS=m CONFIG_HFS_FS=m CONFIG_HFSPLUS_FS=m CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_EFS_FS=m CONFIG_JFFS2_FS=m CONFIG_JFFS2_FS_XATTR=y diff --git a/arch/mips/configs/malta_kvm_defconfig b/arch/mips/configs/malta_kvm_defconfig index 85e95c0ae4107b..3d1c32d3475e78 100644 --- a/arch/mips/configs/malta_kvm_defconfig +++ b/arch/mips/configs/malta_kvm_defconfig @@ -340,7 +340,6 @@ CONFIG_AFFS_FS=m CONFIG_HFS_FS=m CONFIG_HFSPLUS_FS=m CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_EFS_FS=m CONFIG_JFFS2_FS=m CONFIG_JFFS2_FS_XATTR=y diff --git a/arch/mips/configs/maltaup_xpa_defconfig b/arch/mips/configs/maltaup_xpa_defconfig index 0498a011534907..2545168b75bbce 100644 --- a/arch/mips/configs/maltaup_xpa_defconfig +++ b/arch/mips/configs/maltaup_xpa_defconfig @@ -339,7 +339,6 @@ CONFIG_AFFS_FS=m CONFIG_HFS_FS=m CONFIG_HFSPLUS_FS=m CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_EFS_FS=m CONFIG_JFFS2_FS=m CONFIG_JFFS2_FS_XATTR=y diff --git a/arch/mips/configs/rm200_defconfig b/arch/mips/configs/rm200_defconfig index 09e006c7cd7c62..cd2a831614c89e 100644 --- a/arch/mips/configs/rm200_defconfig +++ b/arch/mips/configs/rm200_defconfig @@ -318,7 +318,6 @@ CONFIG_ADFS_FS=m CONFIG_AFFS_FS=m CONFIG_HFS_FS=m CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_EFS_FS=m CONFIG_CRAMFS=m CONFIG_VXFS_FS=m diff --git a/arch/powerpc/configs/fsl-emb-nonhw.config b/arch/powerpc/configs/fsl-emb-nonhw.config index 391c99117ee06b..688051b11ce146 100644 --- a/arch/powerpc/configs/fsl-emb-nonhw.config +++ b/arch/powerpc/configs/fsl-emb-nonhw.config @@ -2,7 +2,6 @@ CONFIG_ADFS_FS=m CONFIG_AFFS_FS=m CONFIG_AUDIT=y CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_BINFMT_MISC=m # CONFIG_BLK_DEV_BSG is not set CONFIG_BLK_DEV_INITRD=y diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index acffc5c17f92d8..8aab229fb90977 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -945,7 +945,6 @@ CONFIG_ECRYPT_FS=m CONFIG_HFS_FS=m CONFIG_HFSPLUS_FS=m CONFIG_BEFS_FS=m -CONFIG_BFS_FS=m CONFIG_EFS_FS=m CONFIG_CRAMFS=m CONFIG_VXFS_FS=m diff --git a/fs/Kconfig b/fs/Kconfig index e05917adcd608e..1cb0b3a8b12ca6 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -314,7 +314,6 @@ source "fs/ecryptfs/Kconfig" source "fs/hfs/Kconfig" source "fs/hfsplus/Kconfig" source "fs/befs/Kconfig" -source "fs/bfs/Kconfig" source "fs/jffs2/Kconfig" # UBIFS File system configuration source "fs/ubifs/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 055dfc23d82b6f..16f1108b64e1ab 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -76,7 +76,6 @@ obj-$(CONFIG_CODA_FS) += coda/ obj-$(CONFIG_MINIX_FS) += minix/ obj-$(CONFIG_FAT_FS) += fat/ obj-$(CONFIG_EXFAT_FS) += exfat/ -obj-$(CONFIG_BFS_FS) += bfs/ obj-$(CONFIG_ISO9660_FS) += isofs/ obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+ obj-$(CONFIG_HFS_FS) += hfs/ diff --git a/fs/bfs/Kconfig b/fs/bfs/Kconfig deleted file mode 100644 index 8e7ef866b62a62..00000000000000 --- a/fs/bfs/Kconfig +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config BFS_FS - tristate "BFS file system support" - depends on BLOCK - select BUFFER_HEAD - help - Boot File System (BFS) is a file system used under SCO UnixWare to - allow the bootloader access to the kernel image and other important - files during the boot process. It is usually mounted under /stand - and corresponds to the slice marked as "STAND" in the UnixWare - partition. You should say Y if you want to read or write the files - on your /stand slice from within Linux. You then also need to say Y - to "UnixWare slices support", below. More information about the BFS - file system is contained in the file - . - - If you don't know what this is about, say N. - - To compile this as a module, choose M here: the module will be called - bfs. Note that the file system of your root partition (the one - containing the directory /) cannot be compiled as a module. diff --git a/fs/bfs/Makefile b/fs/bfs/Makefile deleted file mode 100644 index 2b6bc5eb4de9c5..00000000000000 --- a/fs/bfs/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for BFS filesystem. -# - -obj-$(CONFIG_BFS_FS) += bfs.o - -bfs-objs := inode.o file.o dir.o diff --git a/fs/bfs/bfs.h b/fs/bfs/bfs.h deleted file mode 100644 index b08afe733e6326..00000000000000 --- a/fs/bfs/bfs.h +++ /dev/null @@ -1,69 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * fs/bfs/bfs.h - * Copyright (C) 1999-2018 Tigran Aivazian - */ -#ifndef _FS_BFS_BFS_H -#define _FS_BFS_BFS_H - -#include - -/* In theory BFS supports up to 512 inodes, numbered from 2 (for /) up to 513 inclusive. - In actual fact, attempting to create the 512th inode (i.e. inode No. 513 or file No. 511) - will fail with ENOSPC in bfs_add_entry(): the root directory cannot contain so many entries, counting '..'. - So, mkfs.bfs(8) should really limit its -N option to 511 and not 512. For now, we just print a warning - if a filesystem is mounted with such "impossible to fill up" number of inodes */ -#define BFS_MAX_LASTI 513 - -/* - * BFS file system in-core superblock info - */ -struct bfs_sb_info { - unsigned long si_blocks; - unsigned long si_freeb; - unsigned long si_freei; - unsigned long si_lf_eblk; - unsigned long si_lasti; - DECLARE_BITMAP(si_imap, BFS_MAX_LASTI+1); - struct mutex bfs_lock; -}; - -/* - * BFS file system in-core inode info - */ -struct bfs_inode_info { - unsigned long i_dsk_ino; /* inode number from the disk, can be 0 */ - unsigned long i_sblock; - unsigned long i_eblock; - struct mapping_metadata_bhs i_metadata_bhs; - struct inode vfs_inode; -}; - -static inline struct bfs_sb_info *BFS_SB(struct super_block *sb) -{ - return sb->s_fs_info; -} - -static inline struct bfs_inode_info *BFS_I(struct inode *inode) -{ - return container_of(inode, struct bfs_inode_info, vfs_inode); -} - - -#define printf(format, args...) \ - printk(KERN_ERR "BFS-fs: %s(): " format, __func__, ## args) - -/* inode.c */ -extern struct inode *bfs_iget(struct super_block *sb, unsigned long ino); -extern void bfs_dump_imap(const char *, struct super_block *); - -/* file.c */ -extern const struct inode_operations bfs_file_inops; -extern const struct file_operations bfs_file_operations; -extern const struct address_space_operations bfs_aops; - -/* dir.c */ -extern const struct inode_operations bfs_dir_inops; -extern const struct file_operations bfs_dir_operations; - -#endif /* _FS_BFS_BFS_H */ diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c deleted file mode 100644 index 91a4871fa05191..00000000000000 --- a/fs/bfs/dir.c +++ /dev/null @@ -1,356 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * fs/bfs/dir.c - * BFS directory operations. - * Copyright (C) 1999-2018 Tigran Aivazian - * Made endianness-clean by Andrew Stribblehill 2005 - */ - -#include -#include -#include -#include -#include -#include "bfs.h" - -#undef DEBUG - -#ifdef DEBUG -#define dprintf(x...) printf(x) -#else -#define dprintf(x...) -#endif - -static int bfs_add_entry(struct inode *dir, const struct qstr *child, int ino); -static struct buffer_head *bfs_find_entry(struct inode *dir, - const struct qstr *child, - struct bfs_dirent **res_dir); - -static int bfs_readdir(struct file *f, struct dir_context *ctx) -{ - struct inode *dir = file_inode(f); - struct buffer_head *bh; - struct bfs_dirent *de; - unsigned int offset; - int block; - - if (ctx->pos & (BFS_DIRENT_SIZE - 1)) { - printf("Bad f_pos=%08lx for %s:%08llx\n", - (unsigned long)ctx->pos, - dir->i_sb->s_id, dir->i_ino); - return -EINVAL; - } - - while (ctx->pos < dir->i_size) { - offset = ctx->pos & (BFS_BSIZE - 1); - block = BFS_I(dir)->i_sblock + (ctx->pos >> BFS_BSIZE_BITS); - bh = sb_bread(dir->i_sb, block); - if (!bh) { - ctx->pos += BFS_BSIZE - offset; - continue; - } - do { - de = (struct bfs_dirent *)(bh->b_data + offset); - if (de->ino) { - int size = strnlen(de->name, BFS_NAMELEN); - if (!dir_emit(ctx, de->name, size, - le16_to_cpu(de->ino), - DT_UNKNOWN)) { - brelse(bh); - return 0; - } - } - offset += BFS_DIRENT_SIZE; - ctx->pos += BFS_DIRENT_SIZE; - } while ((offset < BFS_BSIZE) && (ctx->pos < dir->i_size)); - brelse(bh); - } - return 0; -} - -const struct file_operations bfs_dir_operations = { - .read = generic_read_dir, - .iterate_shared = bfs_readdir, - .fsync = simple_fsync, - .llseek = generic_file_llseek, -}; - -static int bfs_create(struct mnt_idmap *idmap, struct inode *dir, - struct dentry *dentry, umode_t mode) -{ - int err; - struct inode *inode; - struct super_block *s = dir->i_sb; - struct bfs_sb_info *info = BFS_SB(s); - unsigned long ino; - - inode = new_inode(s); - if (!inode) - return -ENOMEM; - mutex_lock(&info->bfs_lock); - ino = find_first_zero_bit(info->si_imap, info->si_lasti + 1); - if (ino > info->si_lasti) { - mutex_unlock(&info->bfs_lock); - iput(inode); - return -ENOSPC; - } - set_bit(ino, info->si_imap); - info->si_freei--; - inode_init_owner(&nop_mnt_idmap, inode, dir, mode); - simple_inode_init_ts(inode); - inode->i_blocks = 0; - inode->i_op = &bfs_file_inops; - inode->i_fop = &bfs_file_operations; - inode->i_mapping->a_ops = &bfs_aops; - inode->i_ino = ino; - BFS_I(inode)->i_dsk_ino = ino; - BFS_I(inode)->i_sblock = 0; - BFS_I(inode)->i_eblock = 0; - insert_inode_hash(inode); - mark_inode_dirty(inode); - bfs_dump_imap("create", s); - - err = bfs_add_entry(dir, &dentry->d_name, inode->i_ino); - if (err) { - inode_dec_link_count(inode); - mutex_unlock(&info->bfs_lock); - iput(inode); - return err; - } - mutex_unlock(&info->bfs_lock); - d_instantiate(dentry, inode); - return 0; -} - -static struct dentry *bfs_lookup(struct inode *dir, struct dentry *dentry, - unsigned int flags) -{ - struct inode *inode = NULL; - struct buffer_head *bh; - struct bfs_dirent *de; - struct bfs_sb_info *info = BFS_SB(dir->i_sb); - - if (dentry->d_name.len > BFS_NAMELEN) - return ERR_PTR(-ENAMETOOLONG); - - mutex_lock(&info->bfs_lock); - bh = bfs_find_entry(dir, &dentry->d_name, &de); - if (bh) { - unsigned long ino = (unsigned long)le16_to_cpu(de->ino); - brelse(bh); - inode = bfs_iget(dir->i_sb, ino); - } - mutex_unlock(&info->bfs_lock); - return d_splice_alias(inode, dentry); -} - -static int bfs_link(struct dentry *old, struct inode *dir, - struct dentry *new) -{ - struct inode *inode = d_inode(old); - struct bfs_sb_info *info = BFS_SB(inode->i_sb); - int err; - - mutex_lock(&info->bfs_lock); - err = bfs_add_entry(dir, &new->d_name, inode->i_ino); - if (err) { - mutex_unlock(&info->bfs_lock); - return err; - } - inc_nlink(inode); - inode_set_ctime_current(inode); - mark_inode_dirty(inode); - ihold(inode); - d_instantiate(new, inode); - mutex_unlock(&info->bfs_lock); - return 0; -} - -static int bfs_unlink(struct inode *dir, struct dentry *dentry) -{ - int error = -ENOENT; - struct inode *inode = d_inode(dentry); - struct buffer_head *bh; - struct bfs_dirent *de; - struct bfs_sb_info *info = BFS_SB(inode->i_sb); - - mutex_lock(&info->bfs_lock); - bh = bfs_find_entry(dir, &dentry->d_name, &de); - if (!bh || (le16_to_cpu(de->ino) != inode->i_ino)) - goto out_brelse; - - if (!inode->i_nlink) { - printf("unlinking non-existent file %s:%llu (nlink=%d)\n", - inode->i_sb->s_id, inode->i_ino, - inode->i_nlink); - set_nlink(inode, 1); - } - de->ino = 0; - mmb_mark_buffer_dirty(bh, &BFS_I(dir)->i_metadata_bhs); - inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); - mark_inode_dirty(dir); - inode_set_ctime_to_ts(inode, inode_get_ctime(dir)); - inode_dec_link_count(inode); - error = 0; - -out_brelse: - brelse(bh); - mutex_unlock(&info->bfs_lock); - return error; -} - -static int bfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, - struct dentry *old_dentry, struct inode *new_dir, - struct dentry *new_dentry, unsigned int flags) -{ - struct inode *old_inode, *new_inode; - struct buffer_head *old_bh, *new_bh; - struct bfs_dirent *old_de, *new_de; - struct bfs_sb_info *info; - int error = -ENOENT; - - if (flags & ~RENAME_NOREPLACE) - return -EINVAL; - - old_bh = new_bh = NULL; - old_inode = d_inode(old_dentry); - if (S_ISDIR(old_inode->i_mode)) - return -EINVAL; - - info = BFS_SB(old_inode->i_sb); - - mutex_lock(&info->bfs_lock); - old_bh = bfs_find_entry(old_dir, &old_dentry->d_name, &old_de); - - if (!old_bh || (le16_to_cpu(old_de->ino) != old_inode->i_ino)) - goto end_rename; - - error = -EPERM; - new_inode = d_inode(new_dentry); - new_bh = bfs_find_entry(new_dir, &new_dentry->d_name, &new_de); - - if (new_bh && !new_inode) { - brelse(new_bh); - new_bh = NULL; - } - if (!new_bh) { - error = bfs_add_entry(new_dir, &new_dentry->d_name, - old_inode->i_ino); - if (error) - goto end_rename; - } - old_de->ino = 0; - inode_set_mtime_to_ts(old_dir, inode_set_ctime_current(old_dir)); - mark_inode_dirty(old_dir); - if (new_inode) { - inode_set_ctime_current(new_inode); - inode_dec_link_count(new_inode); - } - mmb_mark_buffer_dirty(old_bh, &BFS_I(old_dir)->i_metadata_bhs); - error = 0; - -end_rename: - mutex_unlock(&info->bfs_lock); - brelse(old_bh); - brelse(new_bh); - return error; -} - -const struct inode_operations bfs_dir_inops = { - .create = bfs_create, - .lookup = bfs_lookup, - .link = bfs_link, - .unlink = bfs_unlink, - .rename = bfs_rename, -}; - -static int bfs_add_entry(struct inode *dir, const struct qstr *child, int ino) -{ - const unsigned char *name = child->name; - int namelen = child->len; - struct buffer_head *bh; - struct bfs_dirent *de; - int block, sblock, eblock, off, pos; - int i; - - dprintf("name=%s, namelen=%d\n", name, namelen); - - sblock = BFS_I(dir)->i_sblock; - eblock = BFS_I(dir)->i_eblock; - for (block = sblock; block <= eblock; block++) { - bh = sb_bread(dir->i_sb, block); - if (!bh) - return -EIO; - for (off = 0; off < BFS_BSIZE; off += BFS_DIRENT_SIZE) { - de = (struct bfs_dirent *)(bh->b_data + off); - if (!de->ino) { - pos = (block - sblock) * BFS_BSIZE + off; - if (pos >= dir->i_size) { - dir->i_size += BFS_DIRENT_SIZE; - inode_set_ctime_current(dir); - } - inode_set_mtime_to_ts(dir, - inode_set_ctime_current(dir)); - mark_inode_dirty(dir); - de->ino = cpu_to_le16((u16)ino); - for (i = 0; i < BFS_NAMELEN; i++) - de->name[i] = - (i < namelen) ? name[i] : 0; - mmb_mark_buffer_dirty(bh, - &BFS_I(dir)->i_metadata_bhs); - brelse(bh); - return 0; - } - } - brelse(bh); - } - return -ENOSPC; -} - -static inline int bfs_namecmp(int len, const unsigned char *name, - const char *buffer) -{ - if ((len < BFS_NAMELEN) && buffer[len]) - return 0; - return !memcmp(name, buffer, len); -} - -static struct buffer_head *bfs_find_entry(struct inode *dir, - const struct qstr *child, - struct bfs_dirent **res_dir) -{ - unsigned long block = 0, offset = 0; - struct buffer_head *bh = NULL; - struct bfs_dirent *de; - const unsigned char *name = child->name; - int namelen = child->len; - - *res_dir = NULL; - if (namelen > BFS_NAMELEN) - return NULL; - - while (block * BFS_BSIZE + offset < dir->i_size) { - if (!bh) { - bh = sb_bread(dir->i_sb, BFS_I(dir)->i_sblock + block); - if (!bh) { - block++; - continue; - } - } - de = (struct bfs_dirent *)(bh->b_data + offset); - offset += BFS_DIRENT_SIZE; - if (le16_to_cpu(de->ino) && - bfs_namecmp(namelen, name, de->name)) { - *res_dir = de; - return bh; - } - if (offset < bh->b_size) - continue; - brelse(bh); - bh = NULL; - offset = 0; - block++; - } - brelse(bh); - return NULL; -} diff --git a/fs/bfs/file.c b/fs/bfs/file.c deleted file mode 100644 index d33d6bde992b8b..00000000000000 --- a/fs/bfs/file.c +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * fs/bfs/file.c - * BFS file operations. - * Copyright (C) 1999-2018 Tigran Aivazian - * - * Make the file block allocation algorithm understand the size - * of the underlying block device. - * Copyright (C) 2007 Dmitri Vorobiev - * - */ - -#include -#include -#include -#include "bfs.h" - -#undef DEBUG - -#ifdef DEBUG -#define dprintf(x...) printf(x) -#else -#define dprintf(x...) -#endif - -const struct file_operations bfs_file_operations = { - .llseek = generic_file_llseek, - .read_iter = generic_file_read_iter, - .write_iter = generic_file_write_iter, - .mmap_prepare = generic_file_mmap_prepare, - .splice_read = filemap_splice_read, -}; - -static int bfs_move_block(unsigned long from, unsigned long to, - struct super_block *sb) -{ - struct buffer_head *bh, *new; - - bh = sb_bread(sb, from); - if (!bh) - return -EIO; - new = sb_getblk(sb, to); - memcpy(new->b_data, bh->b_data, bh->b_size); - mark_buffer_dirty(new); - bforget(bh); - brelse(new); - return 0; -} - -static int bfs_move_blocks(struct super_block *sb, unsigned long start, - unsigned long end, unsigned long where) -{ - unsigned long i; - - dprintf("%08lx-%08lx->%08lx\n", start, end, where); - for (i = start; i <= end; i++) - if(bfs_move_block(i, where + i, sb)) { - dprintf("failed to move block %08lx -> %08lx\n", i, - where + i); - return -EIO; - } - return 0; -} - -static int bfs_get_block(struct inode *inode, sector_t block, - struct buffer_head *bh_result, int create) -{ - unsigned long phys; - int err; - struct super_block *sb = inode->i_sb; - struct bfs_sb_info *info = BFS_SB(sb); - struct bfs_inode_info *bi = BFS_I(inode); - - phys = bi->i_sblock + block; - if (!create) { - if (phys <= bi->i_eblock) { - dprintf("c=%d, b=%08lx, phys=%09lx (granted)\n", - create, (unsigned long)block, phys); - map_bh(bh_result, sb, phys); - } - return 0; - } - - /* - * If the file is not empty and the requested block is within the - * range of blocks allocated for this file, we can grant it. - */ - if (bi->i_sblock && (phys <= bi->i_eblock)) { - dprintf("c=%d, b=%08lx, phys=%08lx (interim block granted)\n", - create, (unsigned long)block, phys); - map_bh(bh_result, sb, phys); - return 0; - } - - /* The file will be extended, so let's see if there is enough space. */ - if (phys >= info->si_blocks) - return -ENOSPC; - - /* The rest has to be protected against itself. */ - mutex_lock(&info->bfs_lock); - - /* - * If the last data block for this file is the last allocated - * block, we can extend the file trivially, without moving it - * anywhere. - */ - if (bi->i_eblock == info->si_lf_eblk) { - dprintf("c=%d, b=%08lx, phys=%08lx (simple extension)\n", - create, (unsigned long)block, phys); - map_bh(bh_result, sb, phys); - info->si_freeb -= phys - bi->i_eblock; - info->si_lf_eblk = bi->i_eblock = phys; - mark_inode_dirty(inode); - err = 0; - goto out; - } - - /* Ok, we have to move this entire file to the next free block. */ - phys = info->si_lf_eblk + 1; - if (phys + block >= info->si_blocks) { - err = -ENOSPC; - goto out; - } - - if (bi->i_sblock) { - err = bfs_move_blocks(inode->i_sb, bi->i_sblock, - bi->i_eblock, phys); - if (err) { - dprintf("failed to move ino=%08lx -> fs corruption\n", - inode->i_ino); - goto out; - } - } else - err = 0; - - dprintf("c=%d, b=%08lx, phys=%08lx (moved)\n", - create, (unsigned long)block, phys); - bi->i_sblock = phys; - phys += block; - info->si_lf_eblk = bi->i_eblock = phys; - - /* - * This assumes nothing can write the inode back while we are here - * and thus update inode->i_blocks! (XXX) - */ - info->si_freeb -= bi->i_eblock - bi->i_sblock + 1 - inode->i_blocks; - mark_inode_dirty(inode); - map_bh(bh_result, sb, phys); -out: - mutex_unlock(&info->bfs_lock); - return err; -} - -static int bfs_writepages(struct address_space *mapping, - struct writeback_control *wbc) -{ - return mpage_writepages(mapping, wbc, bfs_get_block); -} - -static int bfs_read_folio(struct file *file, struct folio *folio) -{ - return block_read_full_folio(folio, bfs_get_block); -} - -static void bfs_write_failed(struct address_space *mapping, loff_t to) -{ - struct inode *inode = mapping->host; - - if (to > inode->i_size) - truncate_pagecache(inode, inode->i_size); -} - -static int bfs_write_begin(const struct kiocb *iocb, - struct address_space *mapping, - loff_t pos, unsigned len, - struct folio **foliop, void **fsdata) -{ - int ret; - - ret = block_write_begin(mapping, pos, len, foliop, bfs_get_block); - if (unlikely(ret)) - bfs_write_failed(mapping, pos + len); - - return ret; -} - -static sector_t bfs_bmap(struct address_space *mapping, sector_t block) -{ - return generic_block_bmap(mapping, block, bfs_get_block); -} - -const struct address_space_operations bfs_aops = { - .dirty_folio = block_dirty_folio, - .invalidate_folio = block_invalidate_folio, - .read_folio = bfs_read_folio, - .writepages = bfs_writepages, - .write_begin = bfs_write_begin, - .write_end = generic_write_end, - .migrate_folio = buffer_migrate_folio, - .bmap = bfs_bmap, -}; - -const struct inode_operations bfs_file_inops; diff --git a/fs/bfs/inode.c b/fs/bfs/inode.c deleted file mode 100644 index 06e3a848b4efd8..00000000000000 --- a/fs/bfs/inode.c +++ /dev/null @@ -1,538 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * fs/bfs/inode.c - * BFS superblock and inode operations. - * Copyright (C) 1999-2018 Tigran Aivazian - * From fs/minix, Copyright (C) 1991, 1992 Linus Torvalds. - * Made endianness-clean by Andrew Stribblehill , 2005. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "bfs.h" - -MODULE_AUTHOR("Tigran Aivazian "); -MODULE_DESCRIPTION("SCO UnixWare BFS filesystem for Linux"); -MODULE_LICENSE("GPL"); - -#undef DEBUG - -#ifdef DEBUG -#define dprintf(x...) printf(x) -#else -#define dprintf(x...) -#endif - -struct inode *bfs_iget(struct super_block *sb, unsigned long ino) -{ - struct bfs_inode *di; - struct inode *inode; - struct buffer_head *bh; - int block, off; - - inode = iget_locked(sb, ino); - if (!inode) - return ERR_PTR(-ENOMEM); - if (!(inode_state_read_once(inode) & I_NEW)) - return inode; - - if ((ino < BFS_ROOT_INO) || (ino > BFS_SB(inode->i_sb)->si_lasti)) { - printf("Bad inode number %s:%08lx\n", inode->i_sb->s_id, ino); - goto error; - } - - block = (ino - BFS_ROOT_INO) / BFS_INODES_PER_BLOCK + 1; - bh = sb_bread(inode->i_sb, block); - if (!bh) { - printf("Unable to read inode %s:%08lx\n", inode->i_sb->s_id, - ino); - goto error; - } - - off = (ino - BFS_ROOT_INO) % BFS_INODES_PER_BLOCK; - di = (struct bfs_inode *)bh->b_data + off; - - /* - * https://martin.hinner.info/fs/bfs/bfs-structure.html explains that - * BFS in SCO UnixWare environment used only lower 9 bits of di->i_mode - * value. This means that, although bfs_write_inode() saves whole - * inode->i_mode bits (which include S_IFMT bits and S_IS{UID,GID,VTX} - * bits), middle 7 bits of di->i_mode value can be garbage when these - * bits were not saved by bfs_write_inode(). - * Since we can't tell whether middle 7 bits are garbage, use only - * lower 12 bits (i.e. tolerate S_IS{UID,GID,VTX} bits possibly being - * garbage) and reconstruct S_IFMT bits for Linux environment from - * di->i_vtype value. - */ - inode->i_mode = 0x00000FFF & le32_to_cpu(di->i_mode); - if (le32_to_cpu(di->i_vtype) == BFS_VDIR) { - inode->i_mode |= S_IFDIR; - inode->i_op = &bfs_dir_inops; - inode->i_fop = &bfs_dir_operations; - } else if (le32_to_cpu(di->i_vtype) == BFS_VREG) { - inode->i_mode |= S_IFREG; - inode->i_op = &bfs_file_inops; - inode->i_fop = &bfs_file_operations; - inode->i_mapping->a_ops = &bfs_aops; - } else { - brelse(bh); - printf("Unknown vtype=%u %s:%08lx\n", - le32_to_cpu(di->i_vtype), inode->i_sb->s_id, ino); - goto error; - } - - BFS_I(inode)->i_sblock = le32_to_cpu(di->i_sblock); - BFS_I(inode)->i_eblock = le32_to_cpu(di->i_eblock); - BFS_I(inode)->i_dsk_ino = le16_to_cpu(di->i_ino); - i_uid_write(inode, le32_to_cpu(di->i_uid)); - i_gid_write(inode, le32_to_cpu(di->i_gid)); - set_nlink(inode, le32_to_cpu(di->i_nlink)); - inode->i_size = BFS_FILESIZE(di); - inode->i_blocks = BFS_FILEBLOCKS(di); - inode_set_atime(inode, le32_to_cpu(di->i_atime), 0); - inode_set_mtime(inode, le32_to_cpu(di->i_mtime), 0); - inode_set_ctime(inode, le32_to_cpu(di->i_ctime), 0); - - brelse(bh); - unlock_new_inode(inode); - return inode; - -error: - iget_failed(inode); - return ERR_PTR(-EIO); -} - -static struct bfs_inode *find_inode(struct super_block *sb, u16 ino, struct buffer_head **p) -{ - if ((ino < BFS_ROOT_INO) || (ino > BFS_SB(sb)->si_lasti)) { - printf("Bad inode number %s:%08x\n", sb->s_id, ino); - return ERR_PTR(-EIO); - } - - ino -= BFS_ROOT_INO; - - *p = sb_bread(sb, 1 + ino / BFS_INODES_PER_BLOCK); - if (!*p) { - printf("Unable to read inode %s:%08x\n", sb->s_id, ino); - return ERR_PTR(-EIO); - } - - return (struct bfs_inode *)(*p)->b_data + ino % BFS_INODES_PER_BLOCK; -} - -static int bfs_write_inode(struct inode *inode, struct writeback_control *wbc) -{ - struct bfs_sb_info *info = BFS_SB(inode->i_sb); - unsigned int ino = (u16)inode->i_ino; - unsigned long i_sblock; - struct bfs_inode *di; - struct buffer_head *bh; - - dprintf("ino=%08x\n", ino); - - di = find_inode(inode->i_sb, ino, &bh); - if (IS_ERR(di)) - return PTR_ERR(di); - - mutex_lock(&info->bfs_lock); - - if (ino == BFS_ROOT_INO) - di->i_vtype = cpu_to_le32(BFS_VDIR); - else - di->i_vtype = cpu_to_le32(BFS_VREG); - - di->i_ino = cpu_to_le16(ino); - di->i_mode = cpu_to_le32(inode->i_mode); - di->i_uid = cpu_to_le32(i_uid_read(inode)); - di->i_gid = cpu_to_le32(i_gid_read(inode)); - di->i_nlink = cpu_to_le32(inode->i_nlink); - di->i_atime = cpu_to_le32(inode_get_atime_sec(inode)); - di->i_mtime = cpu_to_le32(inode_get_mtime_sec(inode)); - di->i_ctime = cpu_to_le32(inode_get_ctime_sec(inode)); - i_sblock = BFS_I(inode)->i_sblock; - di->i_sblock = cpu_to_le32(i_sblock); - di->i_eblock = cpu_to_le32(BFS_I(inode)->i_eblock); - di->i_eoffset = cpu_to_le32(i_sblock * BFS_BSIZE + inode->i_size - 1); - - mark_buffer_dirty(bh); - brelse(bh); - mutex_unlock(&info->bfs_lock); - set_inode_metadata_writeback(inode); - return 0; -} - -static int bfs_sync_inode_metadata(struct inode *inode, - struct writeback_control *wbc) -{ - int err = 0; - struct bfs_inode *di; - struct buffer_head *bh; - - di = find_inode(inode->i_sb, (u16)inode->i_ino, &bh); - if (IS_ERR(di)) - return PTR_ERR(di); - - sync_dirty_buffer(bh); - if (buffer_write_io_error(bh)) { - err = -EIO; - goto out; - } - err = mmb_sync(&BFS_I(inode)->i_metadata_bhs); -out: - brelse(bh); - return err; -} - -static void bfs_evict_inode(struct inode *inode) -{ - unsigned long ino = inode->i_ino; - struct bfs_inode *di; - struct buffer_head *bh; - struct super_block *s = inode->i_sb; - struct bfs_sb_info *info = BFS_SB(s); - struct bfs_inode_info *bi = BFS_I(inode); - - dprintf("ino=%08lx\n", ino); - - truncate_inode_pages_final(&inode->i_data); - if (inode->i_nlink) - mmb_sync(&BFS_I(inode)->i_metadata_bhs); - mmb_invalidate(&BFS_I(inode)->i_metadata_bhs); - clear_inode(inode); - - if (inode->i_nlink) - return; - - di = find_inode(s, inode->i_ino, &bh); - if (IS_ERR(di)) - return; - - mutex_lock(&info->bfs_lock); - /* clear on-disk inode */ - memset(di, 0, sizeof(struct bfs_inode)); - mark_buffer_dirty(bh); - brelse(bh); - - if (bi->i_dsk_ino) { - if (bi->i_sblock) - info->si_freeb += bi->i_eblock + 1 - bi->i_sblock; - info->si_freei++; - clear_bit(ino, info->si_imap); - bfs_dump_imap("evict_inode", s); - } - - /* - * If this was the last file, make the previous block - * "last block of the last file" even if there is no - * real file there, saves us 1 gap. - */ - if (info->si_lf_eblk == bi->i_eblock) - info->si_lf_eblk = bi->i_sblock - 1; - mutex_unlock(&info->bfs_lock); -} - -static void bfs_put_super(struct super_block *s) -{ - struct bfs_sb_info *info = BFS_SB(s); - - if (!info) - return; - - mutex_destroy(&info->bfs_lock); - kfree(info); - s->s_fs_info = NULL; -} - -static int bfs_statfs(struct dentry *dentry, struct kstatfs *buf) -{ - struct super_block *s = dentry->d_sb; - struct bfs_sb_info *info = BFS_SB(s); - u64 id = huge_encode_dev(s->s_bdev->bd_dev); - buf->f_type = BFS_MAGIC; - buf->f_bsize = s->s_blocksize; - buf->f_blocks = info->si_blocks; - buf->f_bfree = buf->f_bavail = info->si_freeb; - buf->f_files = info->si_lasti + 1 - BFS_ROOT_INO; - buf->f_ffree = info->si_freei; - buf->f_fsid = u64_to_fsid(id); - buf->f_namelen = BFS_NAMELEN; - return 0; -} - -static struct kmem_cache *bfs_inode_cachep; - -static struct inode *bfs_alloc_inode(struct super_block *sb) -{ - struct bfs_inode_info *bi; - bi = alloc_inode_sb(sb, bfs_inode_cachep, GFP_KERNEL); - if (!bi) - return NULL; - mmb_init(&bi->i_metadata_bhs, &bi->vfs_inode.i_data); - - return &bi->vfs_inode; -} - -static void bfs_free_inode(struct inode *inode) -{ - kmem_cache_free(bfs_inode_cachep, BFS_I(inode)); -} - -static void init_once(void *foo) -{ - struct bfs_inode_info *bi = foo; - - inode_init_once(&bi->vfs_inode); -} - -static int __init init_inodecache(void) -{ - bfs_inode_cachep = kmem_cache_create("bfs_inode_cache", - sizeof(struct bfs_inode_info), - 0, (SLAB_RECLAIM_ACCOUNT| - SLAB_ACCOUNT), - init_once); - if (bfs_inode_cachep == NULL) - return -ENOMEM; - return 0; -} - -static void destroy_inodecache(void) -{ - /* - * Make sure all delayed rcu free inodes are flushed before we - * destroy cache. - */ - rcu_barrier(); - kmem_cache_destroy(bfs_inode_cachep); -} - -static const struct super_operations bfs_sops = { - .alloc_inode = bfs_alloc_inode, - .free_inode = bfs_free_inode, - .write_inode = bfs_write_inode, - .sync_inode_metadata = bfs_sync_inode_metadata, - .evict_inode = bfs_evict_inode, - .put_super = bfs_put_super, - .statfs = bfs_statfs, -}; - -void bfs_dump_imap(const char *prefix, struct super_block *s) -{ -#ifdef DEBUG - int i; - char *tmpbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); - - if (!tmpbuf) - return; - for (i = BFS_SB(s)->si_lasti; i >= 0; i--) { - if (i > PAGE_SIZE - 100) break; - if (test_bit(i, BFS_SB(s)->si_imap)) - strcat(tmpbuf, "1"); - else - strcat(tmpbuf, "0"); - } - printf("%s: lasti=%08lx <%s>\n", prefix, BFS_SB(s)->si_lasti, tmpbuf); - kfree(tmpbuf); -#endif -} - -static int bfs_fill_super(struct super_block *s, struct fs_context *fc) -{ - struct buffer_head *bh, *sbh; - struct bfs_super_block *bfs_sb; - struct inode *inode; - unsigned i; - struct bfs_sb_info *info; - int ret = -EINVAL; - unsigned long i_sblock, i_eblock, i_eoff, s_size; - int silent = fc->sb_flags & SB_SILENT; - - info = kzalloc_obj(*info); - if (!info) - return -ENOMEM; - mutex_init(&info->bfs_lock); - s->s_fs_info = info; - s->s_time_min = 0; - s->s_time_max = U32_MAX; - - if (!sb_set_blocksize(s, BFS_BSIZE)) - goto out; - - sbh = sb_bread(s, 0); - if (!sbh) - goto out; - bfs_sb = (struct bfs_super_block *)sbh->b_data; - if (le32_to_cpu(bfs_sb->s_magic) != BFS_MAGIC) { - if (!silent) - printf("No BFS filesystem on %s (magic=%08x)\n", s->s_id, le32_to_cpu(bfs_sb->s_magic)); - goto out1; - } - if (BFS_UNCLEAN(bfs_sb, s) && !silent) - printf("%s is unclean, continuing\n", s->s_id); - - s->s_magic = BFS_MAGIC; - - if (le32_to_cpu(bfs_sb->s_start) > le32_to_cpu(bfs_sb->s_end) || - le32_to_cpu(bfs_sb->s_start) < sizeof(struct bfs_super_block) + sizeof(struct bfs_dirent)) { - printf("Superblock is corrupted on %s\n", s->s_id); - goto out1; - } - - info->si_lasti = (le32_to_cpu(bfs_sb->s_start) - BFS_BSIZE) / sizeof(struct bfs_inode) + BFS_ROOT_INO - 1; - if (info->si_lasti == BFS_MAX_LASTI) - printf("NOTE: filesystem %s was created with 512 inodes, the real maximum is 511, mounting anyway\n", s->s_id); - else if (info->si_lasti > BFS_MAX_LASTI) { - printf("Impossible last inode number %lu > %d on %s\n", info->si_lasti, BFS_MAX_LASTI, s->s_id); - goto out1; - } - for (i = 0; i < BFS_ROOT_INO; i++) - set_bit(i, info->si_imap); - - s->s_op = &bfs_sops; - inode = bfs_iget(s, BFS_ROOT_INO); - if (IS_ERR(inode)) { - ret = PTR_ERR(inode); - goto out1; - } - s->s_root = d_make_root(inode); - if (!s->s_root) { - ret = -ENOMEM; - goto out1; - } - - info->si_blocks = (le32_to_cpu(bfs_sb->s_end) + 1) >> BFS_BSIZE_BITS; - info->si_freeb = (le32_to_cpu(bfs_sb->s_end) + 1 - le32_to_cpu(bfs_sb->s_start)) >> BFS_BSIZE_BITS; - info->si_freei = 0; - info->si_lf_eblk = 0; - - /* can we read the last block? */ - bh = sb_bread(s, info->si_blocks - 1); - if (!bh) { - printf("Last block not available on %s: %lu\n", s->s_id, info->si_blocks - 1); - ret = -EIO; - goto out2; - } - brelse(bh); - - bh = NULL; - for (i = BFS_ROOT_INO; i <= info->si_lasti; i++) { - struct bfs_inode *di; - int block = (i - BFS_ROOT_INO) / BFS_INODES_PER_BLOCK + 1; - int off = (i - BFS_ROOT_INO) % BFS_INODES_PER_BLOCK; - unsigned long eblock; - - if (!off) { - brelse(bh); - bh = sb_bread(s, block); - } - - if (!bh) - continue; - - di = (struct bfs_inode *)bh->b_data + off; - - /* test if filesystem is not corrupted */ - - i_eoff = le32_to_cpu(di->i_eoffset); - i_sblock = le32_to_cpu(di->i_sblock); - i_eblock = le32_to_cpu(di->i_eblock); - s_size = le32_to_cpu(bfs_sb->s_end); - - if (i_sblock > info->si_blocks || - i_eblock > info->si_blocks || - i_sblock > i_eblock || - (i_eoff != le32_to_cpu(-1) && i_eoff > s_size) || - i_sblock * BFS_BSIZE > i_eoff) { - - printf("Inode 0x%08x corrupted on %s\n", i, s->s_id); - - brelse(bh); - ret = -EIO; - goto out2; - } - - if (!di->i_ino) { - info->si_freei++; - continue; - } - set_bit(i, info->si_imap); - info->si_freeb -= BFS_FILEBLOCKS(di); - - eblock = le32_to_cpu(di->i_eblock); - if (eblock > info->si_lf_eblk) - info->si_lf_eblk = eblock; - } - brelse(bh); - brelse(sbh); - bfs_dump_imap("fill_super", s); - return 0; - -out2: - dput(s->s_root); - s->s_root = NULL; -out1: - brelse(sbh); -out: - mutex_destroy(&info->bfs_lock); - kfree(info); - s->s_fs_info = NULL; - return ret; -} - -static int bfs_get_tree(struct fs_context *fc) -{ - return get_tree_bdev(fc, bfs_fill_super); -} - -static const struct fs_context_operations bfs_context_ops = { - .get_tree = bfs_get_tree, -}; - -static int bfs_init_fs_context(struct fs_context *fc) -{ - fc->ops = &bfs_context_ops; - - return 0; -} - -static struct file_system_type bfs_fs_type = { - .owner = THIS_MODULE, - .name = "bfs", - .init_fs_context = bfs_init_fs_context, - .kill_sb = kill_block_super, - .fs_flags = FS_REQUIRES_DEV, -}; -MODULE_ALIAS_FS("bfs"); - -static int __init init_bfs_fs(void) -{ - int err = init_inodecache(); - if (err) - goto out1; - err = register_filesystem(&bfs_fs_type); - if (err) - goto out; - return 0; -out: - destroy_inodecache(); -out1: - return err; -} - -static void __exit exit_bfs_fs(void) -{ - unregister_filesystem(&bfs_fs_type); - destroy_inodecache(); -} - -module_init(init_bfs_fs) -module_exit(exit_bfs_fs) diff --git a/tools/testing/selftests/filesystems/statmount/statmount_test.c b/tools/testing/selftests/filesystems/statmount/statmount_test.c index 60c2c544db6a48..52544512edc757 100644 --- a/tools/testing/selftests/filesystems/statmount/statmount_test.c +++ b/tools/testing/selftests/filesystems/statmount/statmount_test.c @@ -17,7 +17,7 @@ static const char *const known_fs[] = { "9p", "adfs", "affs", "afs", "aio", "anon_inodefs", "apparmorfs", - "autofs", "bcachefs", "bdev", "befs", "bfs", "binder", "binfmt_misc", + "autofs", "bcachefs", "bdev", "befs", "binder", "binfmt_misc", "bpf", "btrfs", "btrfs_test_fs", "ceph", "cgroup", "cgroup2", "cifs", "coda", "configfs", "cpuset", "cramfs", "cxl", "dax", "debugfs", "devpts", "devtmpfs", "dmabuf", "drm", "ecryptfs", "efivarfs", "efs", From d54cc3a5272c29129ec53ac9a35ba694df7eea29 Mon Sep 17 00:00:00 2001 From: Shaojie Sun Date: Thu, 3 Sep 2026 14:48:27 +0800 Subject: [PATCH 0155/2286] uts: drop BUG_ON() in copy_utsname() The sole caller, create_new_namespaces(), always passes a valid uts namespace pointer from the task's nsproxy. The other copy_*_ns() helpers, e.g. copy_time_ns() and copy_pid_ns(), don't perform such a check either. Drop the BUG_ON() for consistency with them. Signed-off-by: Shaojie Sun Link: https://patch.msgid.link/20260903064828.126643-3-sunshaojie@kylinos.cn Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/utsname.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/utsname.c b/kernel/utsname.c index ebbfc578a9d308..1ebf87e24607a3 100644 --- a/kernel/utsname.c +++ b/kernel/utsname.c @@ -81,7 +81,6 @@ struct uts_namespace *copy_utsname(u64 flags, { struct uts_namespace *new_ns; - BUG_ON(!old_ns); get_uts_ns(old_ns); if (!(flags & CLONE_NEWUTS)) From 4141e148e629eb9ca161354635ae40de55d8f552 Mon Sep 17 00:00:00 2001 From: Shaojie Sun Date: Thu, 3 Sep 2026 14:48:28 +0800 Subject: [PATCH 0156/2286] fs: namespace: drop BUG_ON() in copy_mnt_ns() The sole caller, create_new_namespaces(), always passes a valid mount namespace pointer from the task's nsproxy. The other copy_*_ns() helpers, e.g. copy_time_ns() and copy_pid_ns(), don't perform such a check either. Drop the BUG_ON() for consistency with them. Signed-off-by: Shaojie Sun Link: https://patch.msgid.link/20260903064828.126643-4-sunshaojie@kylinos.cn Reviewed-by: Tao Cui Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b335..0f35c8c14027c5 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4247,8 +4247,6 @@ struct mnt_namespace *copy_mnt_ns(u64 flags, struct mnt_namespace *ns, struct mount *new; int copy_flags; - BUG_ON(!ns); - if (likely(!(flags & CLONE_NEWNS))) { get_mnt_ns(ns); return ns; From 8d01d6b4c17c406f37a9b6edac93dfb858af30d0 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 3 Sep 2026 02:21:00 +0200 Subject: [PATCH 0157/2286] fs_pin: publish pins with RCU list helpers pin_insert() initializes a pin before adding it under pin_lock. The kill paths read the list heads under RCU without taking that lock. Plain hlist insertion does not publish the earlier initialization to those readers. KCSAN weak-memory checking reported acct_on() callback initialization racing with pin_kill() after the superblock-list publication. The corresponding LKMM model permits the stale read with plain publication; the RCU release/acquire pair forbids it. Use the RCU hlist add and dereference helpers for both pin lists. Fixes: 2798d4ce6160 ("acct: get rid of acct_lock for acct->count") Cc: stable@vger.kernel.org Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260903002100.34822-1-kmehltretter@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fs_pin.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/fs_pin.c b/fs/fs_pin.c index 47ef3c71ce9077..1a508f2167e0a3 100644 --- a/fs/fs_pin.c +++ b/fs/fs_pin.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include "internal.h" @@ -22,8 +23,8 @@ void pin_remove(struct fs_pin *pin) void pin_insert(struct fs_pin *pin, struct vfsmount *m) { spin_lock(&pin_lock); - hlist_add_head(&pin->s_list, &m->mnt_sb->s_pins); - hlist_add_head(&pin->m_list, &real_mount(m)->mnt_pins); + hlist_add_head_rcu(&pin->s_list, &m->mnt_sb->s_pins); + hlist_add_head_rcu(&pin->m_list, &real_mount(m)->mnt_pins); spin_unlock(&pin_lock); } @@ -73,7 +74,7 @@ void mnt_pin_kill(struct mount *m) while (1) { struct hlist_node *p; rcu_read_lock(); - p = READ_ONCE(m->mnt_pins.first); + p = rcu_dereference(hlist_first_rcu(&m->mnt_pins)); if (!p) { rcu_read_unlock(); break; @@ -87,7 +88,7 @@ void group_pin_kill(struct hlist_head *p) while (1) { struct hlist_node *q; rcu_read_lock(); - q = READ_ONCE(p->first); + q = rcu_dereference(hlist_first_rcu(p)); if (!q) { rcu_read_unlock(); break; From 4dda2f40edd811a0637b2036734c594a685767f2 Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Thu, 3 Sep 2026 13:02:07 +0800 Subject: [PATCH 0158/2286] fs/dax: Remove redundant deferred list initialization prep_compound_page() calls prep_compound_head(), which initializes folio->_deferred_list for compound folios with an order greater than one. Therefore, dax_folio_init() initializes the list twice for large DAX folios. Remove the redundant initialization from dax_folio_init(). This leaves the behavior unchanged and keeps compound folio initialization centralized in prep_compound_head(). Signed-off-by: Kaitao Cheng Link: https://patch.msgid.link/20260903050207.7015-1-kaitao.cheng@linux.dev Acked-by: Muchun Song Signed-off-by: Christian Brauner (Amutable) --- fs/dax.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index 6ba50142eeb2fd..dad4efea796404 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -469,8 +469,6 @@ static void dax_folio_init(void *entry) if (order > 0) { prep_compound_page(&folio->page, order); - if (order > 1) - INIT_LIST_HEAD(&folio->_deferred_list); WARN_ON_ONCE(folio_ref_count(folio)); } } From 7d1d1a34d29cb8f1956c7a3c207503e2f18cc0cc Mon Sep 17 00:00:00 2001 From: Tao Cui Date: Thu, 3 Sep 2026 11:36:25 +0800 Subject: [PATCH 0159/2286] devpts: reject max=0 instead of silently lifting the limit mount_opts.max == 0 is consumed as ida_alloc_max(..., max - 1), which wraps to 0xffffffff. The IDA layer treats a negative max as INT_MAX, so "mount -t devpts -o max=0" does not disable ptys - it lifts the per-instance limit entirely and only the global kernel.pty.max still applies. Reject the value at parse time: $ mount -t devpts -o max=0 pt /tmp/pt mount: /tmp/pt: wrong fs type, bad option, bad superblock... No userspace relies on 0 meaning "unlimited": runc and crun never set max=, and LXC, the only runtime that does, omits the property entirely when the configured value is 0. The value has never been validated since the option was introduced, so there is no Fixes tag. Signed-off-by: Tao Cui Link: https://patch.msgid.link/20260903033625.1042796-1-cui.tao@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/devpts/inode.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 9844dcf354ee90..bd1e8eb26edb70 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -249,6 +249,8 @@ static int devpts_parse_param(struct fs_context *fc, struct fs_parameter *param) case Opt_max: if (result.uint_32 > NR_UNIX98_PTY_MAX) return invalf(fc, "max out of range"); + if (result.uint_32 == 0) + return invalf(fc, "max must be greater than 0"); opts->max = result.uint_32; break; } From 669bb5af15d41a21359d00a441490f87a88eb52a Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 2 Sep 2026 18:49:25 +0200 Subject: [PATCH 0160/2286] fs: make sure to call fsnotify_inoderemove() only once when inode is dead Daehyeon reported a race wherein fsnotify_removeinode() can be called when inode still has a live alias. This can lead to several undesired outcomes such as crashes in fsnotify code or avoidance of inode mark notifications. Check that inode is really "dead", meaning no nlink and no more aliases before calling fsnotify_inoderemove() for final cleanup of inode marks. Also do not allow open_by_handle() to add new aliases to a "dead" inode. Ideally, we'd call fsnotify_inoderemove() once last inode reference is dropped to make things simpler. Alas fsnotify inode marks currently hold inode references and thus this doesn't work. Once we teach fsnotify to avoid holding inode references, these games with dead inodes can go as well. Reported-and-tested-by: Daehyeon Ko <4ncienth@gmail.com> Closes: https://lore.kernel.org/linux-fsdevel/20260827045007.3831259-1-4ncienth@gmail.com/ CC: stable@vger.kernel.org Signed-off-by: Amir Goldstein Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260902164924.1333135-2-jack@suse.cz Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index 1b1a81f10da67c..8f17db2724b38a 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -450,6 +450,17 @@ static void dentry_free(struct dentry *dentry) call_rcu(&dentry->d_rcu, __d_free); } +/* + * If inode is unlinked and doesn't have any aliases (i.e., all fds pointing to + * it are closed), it is pretty much dead. Except that file handle lookup could + * still revive it which causes issues to fsnotify. So once inode reaches this + * state we make sure to block creating any new aliases. + */ +static bool inode_notify_dead(struct inode *inode) +{ + return !inode->i_nlink && hlist_empty(&inode->i_dentry); +} + /* * Release the dentry's inode, using the filesystem * d_iput() operation if defined. @@ -459,6 +470,7 @@ static void dentry_unlink_inode(struct dentry * dentry) __releases(dentry->d_inode->i_lock) { struct inode *inode = dentry->d_inode; + bool notify_dead; raw_write_seqcount_begin(&dentry->d_seq); __d_clear_type_and_inode(dentry); @@ -469,9 +481,10 @@ static void dentry_unlink_inode(struct dentry * dentry) */ dentry->waiters = NULL; raw_write_seqcount_end(&dentry->d_seq); + notify_dead = inode_notify_dead(inode); spin_unlock(&dentry->d_lock); spin_unlock(&inode->i_lock); - if (!inode->i_nlink) + if (notify_dead) fsnotify_inoderemove(inode); if (dentry->d_op && dentry->d_op->d_iput) dentry->d_op->d_iput(dentry, inode); @@ -2237,7 +2250,12 @@ static struct dentry *__d_obtain_alias(struct inode *inode, bool disconnected) sb = inode->i_sb; - res = d_find_any_alias(inode); /* existing alias? */ + spin_lock(&inode->i_lock); + if (!inode_notify_dead(inode)) + res = __d_find_any_alias(inode); /* existing alias? */ + else + res = ERR_PTR(-ESTALE); + spin_unlock(&inode->i_lock); if (res) goto out; @@ -2249,7 +2267,10 @@ static struct dentry *__d_obtain_alias(struct inode *inode, bool disconnected) security_d_instantiate(new, inode); spin_lock(&inode->i_lock); - res = __d_find_any_alias(inode); /* recheck under lock */ + if (!inode_notify_dead(inode)) + res = __d_find_any_alias(inode); /* recheck under lock */ + else + res = ERR_PTR(-ESTALE); if (likely(!res)) { /* still no alias, attach a disconnected dentry */ unsigned add_flags = d_flags_for_inode(inode); From 24973c36a6ea34e149c733301940b65c49b2d5d3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 1 Sep 2026 09:53:29 -0400 Subject: [PATCH 0161/2286] splice: emit a single fsnotify access event per NFSD READ nfsd_finish_read() calls fsnotify_access() on both NFSD read paths, because splice_direct_to_actor() does not emit the event itself. The iterator path reaches nfsd_finish_read() from vfs_iocb_iter_read(), which has already emitted one. A READ served without splice therefore emits two access events. NFSD does not use splice for the GSS integrity and privacy services, so a READ on a sec=krb5i or sec=krb5p mount takes the iterator. An inotify watch on that file sees the READ twice. Reported-by: Ameer Hamza Closes: https://lore.kernel.org/linux-nfs/20260818225715.572140-1-ameer.hamza@truenas.com/ Suggested-by: Amir Goldstein Reviewed-by: Amir Goldstein Link: https://lore.kernel.org/linux-nfs/CAOQ4uxgUOqFv6pVmdT_+4DjA0KQoQDCzN2Xz=xm1FyumewHZXg@mail.gmail.com/ Signed-off-by: Chuck Lever Link: https://patch.msgid.link/20260901135329.487666-1-cel@kernel.org Reviewed-by: Jan Kara Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/nfsd/vfs.c | 13 ++-------- fs/splice.c | 58 +++++++++++++++++++++++++++++++----------- include/linux/splice.h | 4 +-- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 8923a9910a08f2..44f3768dd299f8 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1062,7 +1062,6 @@ static __be32 nfsd_finish_read(struct svc_rqst *rqstp, struct svc_fh *fhp, nfsd_stats_io_read_add(nn, fhp->fh_export, host_err); *eof = nfsd_eof_on_read(file, offset, host_err, *count); *count = host_err; - fsnotify_access(file); trace_nfsd_read_io_done(rqstp, fhp, offset, *count); return 0; } else { @@ -1087,19 +1086,11 @@ __be32 nfsd_splice_read(struct svc_rqst *rqstp, struct svc_fh *fhp, struct file *file, loff_t offset, unsigned long *count, u32 *eof) { - struct splice_desc sd = { - .len = 0, - .total_len = *count, - .pos = offset, - .u.data = rqstp, - }; ssize_t host_err; trace_nfsd_read_splice(rqstp, fhp, offset, *count); - host_err = rw_verify_area(READ, file, &offset, *count); - if (!host_err) - host_err = splice_direct_to_actor(file, &sd, - nfsd_direct_splice_actor); + host_err = vfs_splice_to_actor(file, offset, *count, + nfsd_direct_splice_actor, rqstp); return nfsd_finish_read(rqstp, fhp, file, offset, count, eof, host_err); } diff --git a/fs/splice.c b/fs/splice.c index 9d8f63e2fd1abe..96cff31d97e1c7 100644 --- a/fs/splice.c +++ b/fs/splice.c @@ -1009,21 +1009,14 @@ ssize_t vfs_splice_read(struct file *in, loff_t *ppos, } EXPORT_SYMBOL_GPL(vfs_splice_read); -/** - * splice_direct_to_actor - splices data directly between two non-pipes - * @in: file to splice from - * @sd: actor information on where to splice to - * @actor: handles the data splicing - * - * Description: - * This is a special case helper to splice directly between two - * points, without requiring an explicit pipe. Internally an allocated - * pipe is cached in the process, and reused during the lifetime of - * that process. - * +/* + * This is a special case helper to splice directly between two + * points, without requiring an explicit pipe. Internally an allocated + * pipe is cached in the process, and reused during the lifetime of + * that process. */ -ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd, - splice_direct_actor *actor) +static ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd, + splice_direct_actor *actor) { struct pipe_inode_info *pipe; ssize_t ret, bytes; @@ -1147,7 +1140,42 @@ ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd, goto done; } -EXPORT_SYMBOL(splice_direct_to_actor); + +/** + * vfs_splice_to_actor - call an actor on data read from a file + * @in: file to read from + * @pos: file offset + * @count: maximum number of bytes to read + * @actor: callback to process a pipe's worth of data + * @private: private data passed to @actor + * + * Read up to @count worth of data from @in at @pos, and call @actor + * when the hidden pipe used to buffer the data is full. Ensures the + * read is allowed using rw_verify_area() and emits fsnotify access + * events. @in must be seekable (FMODE_LSEEK). + * + * Return: The number of bytes spliced, or a negative errno. + */ +ssize_t vfs_splice_to_actor(struct file *in, loff_t pos, size_t count, + splice_direct_actor *actor, void *private) +{ + struct splice_desc sd = { + .total_len = count, + .pos = pos, + .u.data = private, + }; + ssize_t ret; + + ret = rw_verify_area(READ, in, &sd.pos, sd.total_len); + if (ret < 0) + return ret; + + ret = splice_direct_to_actor(in, &sd, actor); + if (ret >= 0) + fsnotify_access(in); + return ret; +} +EXPORT_SYMBOL(vfs_splice_to_actor); static int direct_splice_actor(struct pipe_inode_info *pipe, struct splice_desc *sd) diff --git a/include/linux/splice.h b/include/linux/splice.h index 9dec4861d09f62..0e6c955dc6ffed 100644 --- a/include/linux/splice.h +++ b/include/linux/splice.h @@ -79,8 +79,8 @@ ssize_t add_to_pipe(struct pipe_inode_info *pipe, struct pipe_buffer *buf); ssize_t vfs_splice_read(struct file *in, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags); -ssize_t splice_direct_to_actor(struct file *file, struct splice_desc *sd, - splice_direct_actor *actor); +ssize_t vfs_splice_to_actor(struct file *file, loff_t pos, size_t count, + splice_direct_actor *actor, void *private); ssize_t do_splice(struct file *in, loff_t *off_in, struct file *out, loff_t *off_out, size_t len, unsigned int flags); ssize_t do_splice_direct(struct file *in, loff_t *ppos, struct file *out, From 1eff23433e73d7da4f9e3529d2701e34962b161f Mon Sep 17 00:00:00 2001 From: YANXIN LI Date: Sat, 18 Jul 2026 19:03:07 +0000 Subject: [PATCH 0162/2286] ufs: use u64 for directory size in ufs_last_byte ufs1_read_inode() and ufs2_read_inode() copy the untrusted 64-bit on-disk size into inode->i_size. ufs_last_byte() then truncates that value to an unsigned int before calculating the valid extent of a directory folio. A directory size of exactly 4 GiB is truncated to zero. In ufs_find_entry(), subtracting the requested record length from that zero forms an endpoint almost 4 GiB beyond the mapped folio. A crafted UFS2 image produces: BUG: KASAN: use-after-free in ufs_find_entry+0x583/0x6e0 [ufs] Read of size 1 ... ufs_find_entry ufs_inode_by_name ufs_lookup __lookup_slow path_lookupat The KASAN classification reflects that the adjacent physical page was free; the source-level operation is an out-of-folio read. A controlled UFS1 test with CONFIG_UFS_FS_WRITE enabled also made ufs_find_entry() return a directory entry from an adjacent anonymous page. unlink() then passed that pointer to ufs_delete_entry(), which cleared the adjacent page's 3 2-bit d_ino. This write result reproduced three out of three times. UFS normally requires a privileged mount path. A relevant boundary is a privileged automounter or image-processing service handling an attacker-supplied filesystem. The write primitive additionally requires UFS1 to be mounted read-write with CONFIG_UFS_FS_WRITE enabled; the read is reachable with read-only UFS2. Keep the intermediate size arithmetic at 64 bits so non-final pages are bounded at PAGE_SIZE. With this change, the same controlled tests reach the real on-disk directory entry and produce no adjacent-page write or KASAN report. A reproducer and full validation logs are available on request. The vulnerable helper is present in v7.2-rc3, v7.1, v6.18.38, v6.12.95, v6.6.111, and upstream master at 1229e2e57a5c. Runtime reproduction and fix validation were performed in an x86-64 QEMU guest running v7.2-rc3 with generic KASAN. Fixes: b71034e5e67d ("[PATCH] ufs: directory and page cache: from blocks to pages") Cc: stable@vger.kernel.org Signed-off-by: YANXIN LI Link: https://patch.msgid.link/20260718190300.867109-1-fadouse@pm.me Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/ufs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ufs/dir.c b/fs/ufs/dir.c index e62fe566767107..69e6d553510ee3 100644 --- a/fs/ufs/dir.c +++ b/fs/ufs/dir.c @@ -213,7 +213,7 @@ static void *ufs_get_folio(struct inode *dir, unsigned long n, static unsigned ufs_last_byte(struct inode *inode, unsigned long page_nr) { - unsigned last_byte = inode->i_size; + u64 last_byte = inode->i_size; last_byte -= page_nr << PAGE_SHIFT; if (last_byte > PAGE_SIZE) From bd7d57a03c4b0fa3b0291ec64d688ebfed5edffc Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 18 Jul 2026 12:34:36 +0800 Subject: [PATCH 0163/2286] irqchip/riscv-imsic: Use GENMASK for base address masks The IMSIC DT binding allows riscv,guest-index-bits up to 7 and riscv,hart-index-bits up to 15. On RV32, guest-index-bits=7 and hart-index-bits=13, together with the 12-bit IMSIC page offset, is a binding-valid layout that consumes a 32-bit low-address mask. The parser accepts this equality case because it rejects only values larger than the remaining bit budget. The base address canonicalization then builds the low-address mask with BIT(sum) - 1. When sum is 32 on RV32, that expression shifts an unsigned long by its full width. Keep the legal equality layout and express the mask with GENMASK(sum - 1, 0) instead. The existing parser bounds guarantee that sum - 1 is below BITS_PER_LONG at both mask sites. Fixes: 21a8f8a0eb35 ("irqchip: Add RISC-V incoming MSI controller early driver") Signed-off-by: Pengpeng Hou Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260718040000.004.929c1ef2-caplitmus-rv@iscas.ac.cn --- drivers/irqchip/irq-riscv-imsic-state.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-riscv-imsic-state.c b/drivers/irqchip/irq-riscv-imsic-state.c index b8d1bbbf42f730..9505ddbd9eec95 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.c +++ b/drivers/irqchip/irq-riscv-imsic-state.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) "riscv-imsic: " fmt #include #include +#include #include #include #include @@ -769,9 +770,9 @@ static int __init imsic_parse_fwnode(struct fwnode_handle *fwnode, return -EINVAL; } global->base_addr = res.start; - global->base_addr &= ~(BIT(global->guest_index_bits + - global->hart_index_bits + - IMSIC_MMIO_PAGE_SHIFT) - 1); + global->base_addr &= ~GENMASK(global->guest_index_bits + + global->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT - 1, 0); global->base_addr &= ~((BIT(global->group_index_bits) - 1) << global->group_index_shift); @@ -850,9 +851,9 @@ int __init imsic_setup_state(struct fwnode_handle *fwnode, void *opaque) } base_addr = mmios[i].start; - base_addr &= ~(BIT(global->guest_index_bits + - global->hart_index_bits + - IMSIC_MMIO_PAGE_SHIFT) - 1); + base_addr &= ~GENMASK(global->guest_index_bits + + global->hart_index_bits + + IMSIC_MMIO_PAGE_SHIFT - 1, 0); base_addr &= ~((BIT(global->group_index_bits) - 1) << global->group_index_shift); if (base_addr != global->base_addr) { From b36bfa477b77901a247e7a192e7404b39e9f522d Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Sat, 25 Jul 2026 16:59:41 +0100 Subject: [PATCH 0164/2286] irqchip/gic: Simplify Maintenance Interrupt ACPI parsing The ACPI spec describes the vgic Maintenance Interrupt with attributes indicating whether the signalling is Level or Edge. While this superficially looks like a sensible thing to do, it doesn't actually make much sense. By construction, this interrupt is Level, and cannot be anything else, as the hypervisor actively needs to interact with the vgic for the level to drop. This indicates a state, and not an event. Get rid of this nonsense, always register the corresponding GSI as Level, and let the user know that they run on quality FW should the interrupt be advertised as Edge. Signed-off-by: Marc Zyngier Signed-off-by: Thomas Gleixner Reviewed-by: Zenghui Yu Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260725155941.114322-1-maz@kernel.org --- drivers/irqchip/irq-gic-v3.c | 10 +++------- drivers/irqchip/irq-gic.c | 8 ++++---- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 6e1fa5b247fc46..b0fca8f01cd408 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2276,7 +2276,6 @@ static struct bool single_redist; int enabled_rdists; u32 maint_irq; - int maint_irq_mode; phys_addr_t vcpu_base; } acpi_data __initdata; @@ -2454,21 +2453,19 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea { struct acpi_madt_generic_interrupt *gicc = (struct acpi_madt_generic_interrupt *)header; - int maint_irq_mode; static int first_madt = true; if (!(gicc->flags & (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE))) return 0; - maint_irq_mode = (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) ? - ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE; + if (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) + pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n"); if (first_madt) { first_madt = false; acpi_data.maint_irq = gicc->vgic_interrupt; - acpi_data.maint_irq_mode = maint_irq_mode; acpi_data.vcpu_base = gicc->gicv_base_address; return 0; @@ -2478,7 +2475,6 @@ static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *hea * The maintenance interrupt and GICV should be the same for every CPU */ if ((acpi_data.maint_irq != gicc->vgic_interrupt) || - (acpi_data.maint_irq_mode != maint_irq_mode) || (acpi_data.vcpu_base != gicc->gicv_base_address)) return -EINVAL; @@ -2511,7 +2507,7 @@ static void __init gic_acpi_setup_kvm_info(void) gic_v3_kvm_info.type = GIC_V3; irq = acpi_register_gsi(NULL, acpi_data.maint_irq, - acpi_data.maint_irq_mode, + ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH); if (irq <= 0) return; diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index f6bc29f515fb79..b2926a3ddaf153 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1527,7 +1527,6 @@ static struct { phys_addr_t cpu_phys_base; u32 maint_irq; - int maint_irq_mode; phys_addr_t vctrl_base; phys_addr_t vcpu_base; } acpi_data __initdata; @@ -1553,10 +1552,11 @@ gic_acpi_parse_madt_cpu(union acpi_subtable_headers *header, if (cpu_base_assigned && gic_cpu_base != acpi_data.cpu_phys_base) return -EINVAL; + if (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) + pr_warn_once(FW_BUG "MI wrongly advertised as Edge-triggered\n"); + acpi_data.cpu_phys_base = gic_cpu_base; acpi_data.maint_irq = processor->vgic_interrupt; - acpi_data.maint_irq_mode = (processor->flags & ACPI_MADT_VGIC_IRQ_MODE) ? - ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE; acpi_data.vctrl_base = processor->gich_base_address; acpi_data.vcpu_base = processor->gicv_base_address; @@ -1616,7 +1616,7 @@ static void __init gic_acpi_setup_kvm_info(void) vcpu_res->end = vcpu_res->start + ACPI_GICV2_VCPU_MEM_SIZE - 1; irq = acpi_register_gsi(NULL, acpi_data.maint_irq, - acpi_data.maint_irq_mode, + ACPI_LEVEL_SENSITIVE, ACPI_ACTIVE_HIGH); if (irq <= 0) return; From 1b852de05f74cfc96c58caad9df4cc1fa2dc7689 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:20 +0200 Subject: [PATCH 0165/2286] irqchip/qcom: Enable compile testing for QCOM_PDC and QCOM_MPM QCOM_PDC and QCOM_MPM do not reference any architecture code, thus should be compile-testable. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-1-466f3c407937@oss.qualcomm.com --- drivers/irqchip/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 20b77fbc51ee03..5a881ddfa3abe3 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -522,7 +522,7 @@ config GOLDFISH_PIC config QCOM_PDC tristate "Qualcomm PDC" - depends on ARCH_QCOM + depends on ARCH_QCOM || COMPILE_TEST select IRQ_DOMAIN_HIERARCHY help Power Domain Controller driver to manage and configure wakeup @@ -530,7 +530,7 @@ config QCOM_PDC config QCOM_MPM tristate "Qualcomm MPM" - depends on ARCH_QCOM + depends on ARCH_QCOM || COMPILE_TEST depends on MAILBOX select IRQ_DOMAIN_HIERARCHY help From eeb693eb3f4c39e181a8a808f02732feb7b31022 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:21 +0200 Subject: [PATCH 0166/2286] irqchip/qcom: Add defaults for desired SoC drivers QCOM_PDC and QCOM_MPM are essential for booting up SoCs and are not really optional for a given platform. Kernel should not ask users choice of drivers when that choice is obvious and known to the developers that answer should be 'yes' or 'module'. Enable these by default whenever a kernel for Qualcomm SoC is built. The change has no impact on arm64 defconfig, but will enable QCOM_PDC for 32-bit ARM multi_v7_defconfig and qcom_defconfig, which is desired because SDX55 uses it. QCOM_MDM is used by 32-bit Qualcomm SoCs, but there is no in-tree user of it, so it does not have to be enabled by default. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-2-466f3c407937@oss.qualcomm.com --- drivers/irqchip/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 5a881ddfa3abe3..a8f8c423b7957b 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -524,6 +524,7 @@ config QCOM_PDC tristate "Qualcomm PDC" depends on ARCH_QCOM || COMPILE_TEST select IRQ_DOMAIN_HIERARCHY + default ARCH_QCOM help Power Domain Controller driver to manage and configure wakeup IRQs for Qualcomm Technologies Inc (QTI) mobile chips. @@ -533,6 +534,7 @@ config QCOM_MPM depends on ARCH_QCOM || COMPILE_TEST depends on MAILBOX select IRQ_DOMAIN_HIERARCHY + default ARCH_QCOM if ARM64 help MSM Power Manager driver to manage and configure wakeup IRQs for Qualcomm Technologies Inc (QTI) mobile chips. From bd94bea7165be450903c6d64bbcb4862e36798f9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 27 Jul 2026 22:01:22 +0200 Subject: [PATCH 0167/2286] arm64: defconfig: Drop redundant Qualcomm irqchip entries QCOM_PDC and QCOM_MPM are enabled by default. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260727-irqchip-qcom-defaults-v2-3-466f3c407937@oss.qualcomm.com --- arch/arm64/configs/defconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index 6ec8d3a69c98e0..978e00891dca81 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -1743,8 +1743,6 @@ CONFIG_PWM_VISCONTI=m CONFIG_PWM_XILINX=m CONFIG_SL28CPLD_INTC=y CONFIG_XILINX_INTC=y -CONFIG_QCOM_PDC=y -CONFIG_QCOM_MPM=y CONFIG_TI_SCI_INTR_IRQCHIP=y CONFIG_TI_SCI_INTA_IRQCHIP=y CONFIG_RESET_GPIO=m From 1ae91dc397eb1dbc0f4fc6b96f2481cbc02fe0f9 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:45 +0000 Subject: [PATCH 0168/2286] riscv: smp: Move enum ipi_message_type to asm/smp.h The IPI message type enumeration (and therefore IPI_MAX) is currently private to arch/riscv/kernel/smp.c. Several IPI providers need to know the exact number of IPIs that the architecture requires, so move the enum into the public header. This is a pure code movement with no functional change. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-2-guoren@kernel.org --- arch/riscv/include/asm/smp.h | 12 ++++++++++++ arch/riscv/kernel/smp.c | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h index 0ecc67641b09d6..bed39fff1f8a78 100644 --- a/arch/riscv/include/asm/smp.h +++ b/arch/riscv/include/asm/smp.h @@ -15,6 +15,18 @@ struct seq_file; extern unsigned long boot_cpu_hartid; +enum ipi_message_type { + IPI_RESCHEDULE, + IPI_CALL_FUNC, + IPI_CPU_STOP, + IPI_CPU_CRASH_STOP, + IPI_IRQ_WORK, + IPI_TIMER, + IPI_CPU_BACKTRACE, + IPI_KGDB_ROUNDUP, + IPI_MAX +}; + #ifdef CONFIG_SMP #include diff --git a/arch/riscv/kernel/smp.c b/arch/riscv/kernel/smp.c index fa66f9c97d748d..8930b62b15e742 100644 --- a/arch/riscv/kernel/smp.c +++ b/arch/riscv/kernel/smp.c @@ -28,18 +28,6 @@ #include #include -enum ipi_message_type { - IPI_RESCHEDULE, - IPI_CALL_FUNC, - IPI_CPU_STOP, - IPI_CPU_CRASH_STOP, - IPI_IRQ_WORK, - IPI_TIMER, - IPI_CPU_BACKTRACE, - IPI_KGDB_ROUNDUP, - IPI_MAX -}; - static const char * const ipi_names[] = { [IPI_RESCHEDULE] = "Rescheduling interrupts", [IPI_CALL_FUNC] = "Function call interrupts", From 2a0d49514f297baee259b19fb5553b538aa67d10 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:46 +0000 Subject: [PATCH 0169/2286] riscv: sbi: Use IPI_MAX for SBI IPI muxing Now that IPI_MAX is visible from , replace the hard-coded BITS_PER_BYTE with the proper symbolic constant in the SBI IPI extension driver. No functional change; IPI_MAX is still 8. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-3-guoren@kernel.org --- arch/riscv/kernel/sbi-ipi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/riscv/kernel/sbi-ipi.c b/arch/riscv/kernel/sbi-ipi.c index 0cc5559c08d8ff..eeec178a9b95bb 100644 --- a/arch/riscv/kernel/sbi-ipi.c +++ b/arch/riscv/kernel/sbi-ipi.c @@ -57,7 +57,7 @@ void __init sbi_ipi_init(void) return; } - virq = ipi_mux_create(BITS_PER_BYTE, sbi_send_ipi); + virq = ipi_mux_create(IPI_MAX, sbi_send_ipi); if (virq <= 0) { pr_err("unable to create muxed IPIs\n"); irq_dispose_mapping(sbi_ipi_virq); @@ -75,7 +75,7 @@ void __init sbi_ipi_init(void) "irqchip/sbi-ipi:starting", sbi_ipi_starting_cpu, NULL); - riscv_ipi_set_virq_range(virq, BITS_PER_BYTE); + riscv_ipi_set_virq_range(virq, IPI_MAX); pr_info("providing IPIs using SBI IPI extension\n"); /* From f6326e26f60259f62ed2b172f7254d673e869cc1 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:47 +0000 Subject: [PATCH 0170/2286] clocksource: clint: Use IPI_MAX for IPI muxing Replace the hard-coded BITS_PER_BYTE with IPI_MAX now that the constant is exported from riscv asm/smp.h. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-4-guoren@kernel.org --- drivers/clocksource/timer-clint.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/clocksource/timer-clint.c b/drivers/clocksource/timer-clint.c index 0bdd9d7ec54583..e56eee7e3781dd 100644 --- a/drivers/clocksource/timer-clint.c +++ b/drivers/clocksource/timer-clint.c @@ -243,7 +243,7 @@ static int __init clint_timer_init_dt(struct device_node *np) } #ifdef CONFIG_SMP - rc = ipi_mux_create(BITS_PER_BYTE, clint_send_ipi); + rc = ipi_mux_create(IPI_MAX, clint_send_ipi); if (rc <= 0) { pr_err("unable to create muxed IPIs\n"); rc = (rc < 0) ? rc : -ENODEV; @@ -251,7 +251,7 @@ static int __init clint_timer_init_dt(struct device_node *np) } irq_set_chained_handler(clint_ipi_irq, clint_ipi_interrupt); - riscv_ipi_set_virq_range(rc, BITS_PER_BYTE); + riscv_ipi_set_virq_range(rc, IPI_MAX); clint_clear_ipi(); #endif From a89546503a0ee87c20ba742a58000bbe4f87fd34 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:48 +0000 Subject: [PATCH 0171/2286] irqchip/aclint-sswi: Use IPI_MAX for IPI muxing Replace the hard-coded BITS_PER_BYTE with IPI_MAX now that the constant is exported from riscv asm/smp.h. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-5-guoren@kernel.org --- drivers/irqchip/irq-aclint-sswi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/irqchip/irq-aclint-sswi.c b/drivers/irqchip/irq-aclint-sswi.c index ca06efd86fa1a7..5e010fc401f7bc 100644 --- a/drivers/irqchip/irq-aclint-sswi.c +++ b/drivers/irqchip/irq-aclint-sswi.c @@ -138,7 +138,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode) } /* Register SSWI irq and handler */ - virq = ipi_mux_create(BITS_PER_BYTE, aclint_sswi_ipi_send); + virq = ipi_mux_create(IPI_MAX, aclint_sswi_ipi_send); if (virq <= 0) { pr_err("unable to create muxed IPIs\n"); irq_dispose_mapping(sswi_ipi_virq); @@ -152,7 +152,7 @@ static int __init aclint_sswi_probe(struct fwnode_handle *fwnode) aclint_sswi_starting_cpu, aclint_sswi_dying_cpu); - riscv_ipi_set_virq_range(virq, BITS_PER_BYTE); + riscv_ipi_set_virq_range(virq, IPI_MAX); return 0; } From 5b104000160dec7f01c292da1082fbe4bdd5f715 Mon Sep 17 00:00:00 2001 From: "GUO Ren (XuanTie)" Date: Sun, 16 Aug 2026 07:00:49 +0000 Subject: [PATCH 0172/2286] irqchip/imsic: Use IPI_MAX instead of IMSIC_NR_IPI IMSIC was defining its own IMSIC_NR_IPI (= 8) which happened to match the architecture's IPI_MAX. Now that IPI_MAX is exported from riscv asm/smp.h, use the architecture constant and drop the private define. This keeps the number of multiplexed IPIs in sync with the rest of the RISC-V IPI infrastructure. Signed-off-by: GUO Ren (XuanTie) Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Reviewed-by: Anup Patel Link: https://patch.msgid.link/20260816070049.2097442-6-guoren@kernel.org --- drivers/irqchip/irq-riscv-imsic-early.c | 4 ++-- drivers/irqchip/irq-riscv-imsic-state.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/irqchip/irq-riscv-imsic-early.c b/drivers/irqchip/irq-riscv-imsic-early.c index 12efd241ce8800..823f5f2ecb3ddd 100644 --- a/drivers/irqchip/irq-riscv-imsic-early.c +++ b/drivers/irqchip/irq-riscv-imsic-early.c @@ -67,12 +67,12 @@ static int __init imsic_ipi_domain_init(void) return 0; /* Create IMSIC IPI multiplexing */ - virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send); + virq = ipi_mux_create(IPI_MAX, imsic_ipi_send); if (virq <= 0) return virq < 0 ? virq : -ENOMEM; /* Set vIRQ range */ - riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI); + riscv_ipi_set_virq_range(virq, IPI_MAX); /* Announce that IMSIC is providing IPIs */ pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID); diff --git a/drivers/irqchip/irq-riscv-imsic-state.h b/drivers/irqchip/irq-riscv-imsic-state.h index c42ee180b3050c..878cc192ccecf0 100644 --- a/drivers/irqchip/irq-riscv-imsic-state.h +++ b/drivers/irqchip/irq-riscv-imsic-state.h @@ -13,7 +13,6 @@ #include #define IMSIC_IPI_ID 1 -#define IMSIC_NR_IPI 8 struct imsic_vector { /* Fixed details of the vector */ From 94713d60707bfe26c7cbc941abe26d1855448435 Mon Sep 17 00:00:00 2001 From: Jiangshan Yi Date: Thu, 3 Sep 2026 18:40:14 +0800 Subject: [PATCH 0173/2286] irqchip/gic-v5: Install root IRQ handler last in gicv5_init_common() set_handle_irq() cannot be undone: once a handler is installed, any further call returns -EBUSY, so the set_handle_irq(NULL) in the error path has never worked. Drop it, and install the root handler only after gicv5_irs_enable() has succeeded. set_handle_irq() only fails if another root handler is already installed, which cannot happen on a GICv5 system. Should it ever fail, the system is unusable: panic instead of unwinding. Signed-off-by: Jiangshan Yi Signed-off-by: Thomas Gleixner Reviewed-by: Lorenzo Pieralisi Reviewed-by: Marc Zyngier Link: https://lore.kernel.org/r/87fqzrmywu.wl-maz@kernel.org Link: https://patch.msgid.link/20260903104014.587068-1-yijiangshan@kylinos.cn --- drivers/irqchip/irq-gic-v5.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index ac2d423b172391..c365f815030052 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -1166,21 +1166,18 @@ static int __init gicv5_init_common(struct fwnode_handle *parent_domain) if (ret) goto out_int; - ret = set_handle_irq(gicv5_handle_irq); + ret = gicv5_irs_enable(); if (ret) goto out_int; - ret = gicv5_irs_enable(); - if (ret) - goto out_handle; + if (set_handle_irq(gicv5_handle_irq)) + panic("GICv5: unable to install root IRQ handler\n"); gicv5_smp_init(); gicv5_irs_its_probe(); return 0; -out_handle: - set_handle_irq(NULL); out_int: gicv5_cpu_disable_interrupts(); gicv5_free_domains(); From 9d6498c113b7132e21998b681748a10eb1c827fa Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:27 +0530 Subject: [PATCH 0174/2286] dt-bindings: interrupt-controller: qcom,pdc: Document Purwa PDC X1P42100 (Purwa) shares the X1E80100 (Hamoa) PDC device, but the hardware register bug addressed in commit e9a48ea4d90b ("irqchip/qcom-pdc: Workaround hardware register bug on X1E80100") is already fixed in X1P42100 silicon. X1E80100 compatible forces the software workaround. Add PDC compatible for purwa as "qcom,x1p42100-pdc" to remove the workaround from Purwa. Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Krzysztof Kozlowski Acked-by: Konrad Dybcio Link: https://patch.msgid.link/20260806-purwa-pdc-v4-1-62467f4b4ddc@oss.qualcomm.com --- .../devicetree/bindings/interrupt-controller/qcom,pdc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml index 8162a49d49a669..b3d19c86a2407f 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,pdc.yaml @@ -58,6 +58,7 @@ properties: - qcom,sm8650-pdc - qcom,sm8750-pdc - qcom,x1e80100-pdc + - qcom,x1p42100-pdc - const: qcom,pdc reg: From 086eac130a946610892d4219b04f8efa9ef28727 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:28 +0530 Subject: [PATCH 0175/2286] irqchip/qcom-pdc: Add purwa compatible for PDC secondary mode X1P42100 (Purwa) and X1E80100 (Hamoa) shares the same PDC and windows firmware sets the PDC to secondary mode for X1P42100 too. Add support to reset the PDC to pass through mode using qcom_scm_io_writel() similar to X1E80100. Make sure x1e_quirk to workaround a hardware bug is set only for X1E80100 as X1P42100 have this fixed in silicon. Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260806-purwa-pdc-v4-2-62467f4b4ddc@oss.qualcomm.com --- drivers/irqchip/qcom-pdc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index ce6d80c7f17a3a..29025a212ece36 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -715,7 +715,10 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare } pdc->x1e_quirk = true; + } + if (of_device_is_compatible(node, "qcom,x1e80100-pdc") || + of_device_is_compatible(node, "qcom,x1p42100-pdc")) { if (!qcom_scm_is_available()) return -EPROBE_DEFER; From 13ca1c0b7d8a3ab1e59cb3e780c1dd7441e22515 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Thu, 6 Aug 2026 14:26:29 +0530 Subject: [PATCH 0176/2286] arm64: dts: qcom: purwa: Drop the Hamoa workaround for PDC X1P42100 (Purwa) shares the X1E80100 (Hamoa) PDC device, but the hardware register bug addressed in commit e9a48ea4d90b ("irqchip/qcom-pdc: Workaround hardware register bug on X1E80100") is already fixed in X1P42100 silicon. X1E80100 compatible forces the software workaround. Use the X1P42100 specific compatible string for the PDC node to remove the workaround. Fixes: f08edb529916 ("arm64: dts: qcom: Add X1P42100 SoC and CRD") Signed-off-by: Maulik Shah Signed-off-by: Thomas Gleixner Reviewed-by: Konrad Dybcio Reviewed-by: Dmitry Baryshkov Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260806-purwa-pdc-v4-3-62467f4b4ddc@oss.qualcomm.com --- arch/arm64/boot/dts/qcom/purwa.dtsi | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/purwa.dtsi b/arch/arm64/boot/dts/qcom/purwa.dtsi index c698e6cb2543ca..4348dd3d1dc590 100644 --- a/arch/arm64/boot/dts/qcom/purwa.dtsi +++ b/arch/arm64/boot/dts/qcom/purwa.dtsi @@ -224,6 +224,11 @@ compatible = "qcom,x1p42100-qmp-gen4x4-pcie-phy"; }; +/* X1P42100 PDC is same as X1E80100, but without hardware register bug */ +&pdc { + compatible = "qcom,x1p42100-pdc", "qcom,pdc"; +}; + &qfprom { gpu_speed_bin: gpu-speed-bin@119 { reg = <0x119 0x2>; From 886c9e80a232393f3d852a78f8933b1d476006cc Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 10:17:21 +0200 Subject: [PATCH 0177/2286] intel_idle: Replace min_t() with the better alternatives Almost always use of min_t() is wrong. Replace it with min() and min3() as all types are int and there is no point to use typed variant of it. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260813081721.2645937-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/idle/intel_idle.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 651408df9c24ac..0c7aa323695017 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -1792,9 +1792,7 @@ static bool __init intel_idle_cst_usable(void) { int cstate, limit; - limit = min_t(int, min_t(int, CPUIDLE_STATE_MAX, max_cstate + 1), - acpi_state_table.count); - + limit = min3(CPUIDLE_STATE_MAX, max_cstate + 1, acpi_state_table.count); for (cstate = 1; cstate < limit; cstate++) { struct acpi_processor_cx *cx = &acpi_state_table.states[cstate]; @@ -1938,7 +1936,7 @@ static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) { - int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + int cstate, limit = min(CPUIDLE_STATE_MAX, acpi_state_table.count); /* * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of @@ -2018,7 +2016,7 @@ static bool __init intel_idle_off_by_default_lpi(unsigned int flags, u32 mwait_h static bool __init intel_idle_off_by_default_cst(unsigned int flags, u32 mwait_hint) { - int cstate, limit = min_t(int, CPUIDLE_STATE_MAX, acpi_state_table.count); + int cstate, limit = min(CPUIDLE_STATE_MAX, acpi_state_table.count); /* * If limit > 0, intel_idle_cst_usable() has returned 'true', so all of From 2094ae12eed8e83dd91a631ac0a582fee5934c36 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Aug 2026 10:26:24 +0200 Subject: [PATCH 0178/2286] intel_idle: Use 2-argument strscpy() Use 2-argument strscpy(), which is not only shorter but also provides an additional check that destination buffer is an array. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260813082624.2650581-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/idle/intel_idle.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 0c7aa323695017..a045a384b949d2 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -1904,7 +1904,7 @@ static void __init intel_idle_init_cstates_acpi_lpi(struct cpuidle_driver *drv) state = &drv->states[drv->state_count++]; scnprintf(state->name, CPUIDLE_NAME_LEN, "C%d_LPI", index + 1); - strscpy(state->desc, lpi_state->desc, CPUIDLE_DESC_LEN); + strscpy(state->desc, lpi_state->desc); state->exit_latency = lpi_state->wake_latency; state->target_residency = lpi_state->min_residency; state->flags = MWAIT2flg(lpi_state->address); @@ -1954,7 +1954,7 @@ static void __init intel_idle_init_cstates_acpi_cst(struct cpuidle_driver *drv) state = &drv->states[drv->state_count++]; snprintf(state->name, CPUIDLE_NAME_LEN, "C%d_ACPI", cstate); - strscpy(state->desc, cx->desc, CPUIDLE_DESC_LEN); + strscpy(state->desc, cx->desc); state->exit_latency = cx->latency; /* * For C1-type C-states use the same number for both the exit From 40989202104e498ff9adfb8c575d6cd159f13988 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 11:01:34 +0200 Subject: [PATCH 0179/2286] thermal: intel: powerclamp: Reject invalid window_size values window_size_set() sets ret to -EINVAL if new_window_size is outside the valid range, but then falls through and still updates window_size to the clamped value. Return -EINVAL immediately and do not update window_size. Also drop the now-redundant clamp() call. Fixes: d6d71ee4a14a ("PM: Introduce Intel PowerClamp Driver") Cc: All applicable Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260815090136.88540-2-thorsten.blum@linux.dev Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index bd7fd98dc310e4..8026651133d5de 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -289,9 +289,10 @@ static int window_size_set(const char *arg, const struct kernel_param *kp) pr_err("Out of recommended window size %lu, between 2-10\n", new_window_size); ret = -EINVAL; + goto exit_win; } - window_size = clamp(new_window_size, 2ul, 10ul); + window_size = new_window_size; smp_mb(); exit_win: From d0a22f0e6464e12d7cdd62ff281d7d081b46b6fe Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 11:24:18 +0200 Subject: [PATCH 0180/2286] thermal: intel: int340x: Drop redundant else in proc_thermal_add() Drop the redundant else branch since ret is immediately overwritten. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260815092419.91246-2-thorsten.blum@linux.dev Signed-off-by: Rafael J. Wysocki --- .../thermal/intel/int340x_thermal/processor_thermal_device.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c index f80dbe2ca7e477..0a007a6413dff6 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c @@ -291,10 +291,8 @@ int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv) } proc_priv->int340x_zone = int340x_thermal_zone_add(adev, get_temp); - if (IS_ERR(proc_priv->int340x_zone)) { + if (IS_ERR(proc_priv->int340x_zone)) return PTR_ERR(proc_priv->int340x_zone); - } else - ret = 0; ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, proc_thermal_notify, From de9424633757e2a0e4df6af713bb71a6efd8b854 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 11:51:41 +0200 Subject: [PATCH 0181/2286] thermal: intel: powerclamp: Drop redundant clamp() in duration_set() duration_set() rejects any new_duration outside the valid range before the assignment, making the clamp() call redundant. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260815095143.3821-2-thorsten.blum@linux.dev Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index 8026651133d5de..c94db9f9e9f776 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -94,7 +94,7 @@ static int duration_set(const char *arg, const struct kernel_param *kp) } mutex_lock(&powerclamp_lock); - duration = clamp(new_duration, 6ul, 25ul) * 1000; + duration = new_duration * 1000; mutex_unlock(&powerclamp_lock); exit: From efa25d2a661e111d8dbaa924e39fdd7c4da8b581 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 19 Aug 2026 10:41:22 -0700 Subject: [PATCH 0182/2286] thermal: intel: intel_tcc_cooling: Add rugged Panther Lake support Add TCC cooling support to rugged Panther Lake. Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260819174122.1184364-1-sathyanarayanan.kuppuswamy@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_tcc_cooling.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thermal/intel/intel_tcc_cooling.c b/drivers/thermal/intel/intel_tcc_cooling.c index 52ea4f7fc9f917..75f6fbec25302e 100644 --- a/drivers/thermal/intel/intel_tcc_cooling.c +++ b/drivers/thermal/intel/intel_tcc_cooling.c @@ -69,6 +69,7 @@ static const struct x86_cpu_id tcc_ids[] __initconst = { X86_MATCH_VFM(INTEL_ARROWLAKE_U, NULL), X86_MATCH_VFM(INTEL_ARROWLAKE_H, NULL), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, NULL), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, NULL), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, NULL), X86_MATCH_VFM(INTEL_NOVALAKE, NULL), X86_MATCH_VFM(INTEL_NOVALAKE_L, NULL), From 3a6a73ea980190a091a822ae54bafabc0dac6a4d Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 20 Aug 2026 17:43:36 +0200 Subject: [PATCH 0183/2286] thermal: intel: powerclamp: Simplify check_invalid() Return the boolean expression directly and use const for the read-only mask parameter. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260820154339.47737-2-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index c94db9f9e9f776..d5b95e69a3a6c1 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -143,12 +143,9 @@ static int allocate_copy_idle_injection_mask(const struct cpumask *copy_mask) } /* Return true if the cpumask and idle percent combination is invalid */ -static bool check_invalid(cpumask_var_t mask, u8 idle) +static bool check_invalid(const struct cpumask *mask, u8 idle) { - if (cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE) - return true; - - return false; + return cpumask_equal(cpu_present_mask, mask) && idle > MAX_ALL_CPU_IDLE; } static int cpumask_set(const char *arg, const struct kernel_param *kp) From e56e2b49c67d132728e52ce493079e83cc914657 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 26 Aug 2026 17:31:43 +0200 Subject: [PATCH 0184/2286] thermal: intel: int340x: Fix temperature selection around 0 C Since commit 7251b9e8a007 ("thermal/intel: Fix intel_tcc_get_temp() to support negative CPU temperature"), intel_tcc_get_temp() can report negative temperatures. proc_thermal_get_zone_temp() still uses *temp as the current maximum and as an implicit "no reading yet" marker. However, this breaks when a CPU reports 0 C, because a subsequent negative reading can overwrite it. Use bool temp_valid to track whether a valid temperature has been read. Initialize *temp with the first valid reading and only update it with warmer readings. Fixes: 7251b9e8a007 ("thermal/intel: Fix intel_tcc_get_temp() to support negative CPU temperature") Cc: 6.3+ # 6.3+ Signed-off-by: Thorsten Blum Acked-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260826153144.299746-3-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- .../intel/int340x_thermal/processor_thermal_device.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c index 0a007a6413dff6..850bfe7c1ede05 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c @@ -179,17 +179,21 @@ static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone, { int cpu; int curr_temp, ret; - - *temp = 0; + bool temp_valid = false; for_each_online_cpu(cpu) { ret = intel_tcc_get_temp(cpu, &curr_temp, false); if (ret < 0) return ret; - if (!*temp || curr_temp > *temp) + if (!temp_valid || curr_temp > *temp) { *temp = curr_temp; + temp_valid = true; + } } + if (!temp_valid) + return -ENODATA; + *temp *= 1000; return 0; From bbc68b683da15e43ba4c2ae05429f2c6264107d9 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 28 Aug 2026 21:45:27 +0200 Subject: [PATCH 0185/2286] thermal: intel: powerclamp: Simplify idle_inject_update() Drop the local update variable and call idle_inject_set_duration() directly instead. Also return !should_skip directly. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260828194530.43370-2-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index d5b95e69a3a6c1..044b185f748b80 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -534,23 +534,17 @@ static struct idle_inject_device *ii_dev; */ static bool idle_inject_update(void) { - bool update = false; - /* We can't sleep in this callback */ if (!mutex_trylock(&powerclamp_lock)) return true; if (!(powerclamp_data.count % powerclamp_data.window_size_now)) { + unsigned int runtime; should_skip = powerclamp_adjust_controls(powerclamp_data.target_ratio, powerclamp_data.guard, powerclamp_data.window_size_now); - update = true; - } - - if (update) { - unsigned int runtime = get_run_time(); - + runtime = get_run_time(); idle_inject_set_duration(ii_dev, runtime, duration); } @@ -558,10 +552,7 @@ static bool idle_inject_update(void) mutex_unlock(&powerclamp_lock); - if (should_skip) - return false; - - return true; + return !should_skip; } /* This function starts idle injection by calling idle_inject_start() */ From 18f64d8ca6dfd93c05bfeeda6171a8493746de23 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 19 Aug 2026 10:40:20 -0700 Subject: [PATCH 0186/2286] powercap: intel_rapl: Add rugged Panther Lake support Add RAPL support for rugged Panther Lake. Signed-off-by: Srinivas Pandruvada Link: https://patch.msgid.link/20260819174020.1184329-1-sathyanarayanan.kuppuswamy@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/powercap/intel_rapl_msr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index a34543e664460b..2c1b799d3c54d0 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -456,6 +456,7 @@ static const struct x86_cpu_id rapl_ids[] = { X86_MATCH_VFM(INTEL_EMERALDRAPIDS_X, &rapl_defaults_spr_server), X86_MATCH_VFM(INTEL_LUNARLAKE_M, &rapl_defaults_core), X86_MATCH_VFM(INTEL_PANTHERLAKE_L, &rapl_defaults_core_pl4_pmu), + X86_MATCH_VFM(INTEL_PANTHERLAKE_R, &rapl_defaults_core_pl4_pmu), X86_MATCH_VFM(INTEL_WILDCATLAKE_L, &rapl_defaults_core_pl4_pmu), X86_MATCH_VFM(INTEL_NOVALAKE, &rapl_defaults_core_pl4), X86_MATCH_VFM(INTEL_NOVALAKE_L, &rapl_defaults_core_pl4), From 60b9d299695513ed60b85a33c3f44cd346893d38 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Thu, 3 Sep 2026 18:07:51 +0200 Subject: [PATCH 0187/2286] thermal: docs: cpu-idle-cooling.rst: Drop unbalanced paren Remove needless '(' from equation. Signed-off-by: Manuel Ebner Reviewed-by: Randy Dunlap [ rjw: Subject adjustments ] Link: https://patch.msgid.link/20260903160751.734704-2-manuelebnerli@mailbox.org Signed-off-by: Rafael J. Wysocki --- Documentation/driver-api/thermal/cpu-idle-cooling.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/driver-api/thermal/cpu-idle-cooling.rst b/Documentation/driver-api/thermal/cpu-idle-cooling.rst index c2a7ca676853f9..b9d4c91a7f1588 100644 --- a/Documentation/driver-api/thermal/cpu-idle-cooling.rst +++ b/Documentation/driver-api/thermal/cpu-idle-cooling.rst @@ -161,7 +161,7 @@ tree. So with the idle injection mechanism, we want an average power specific OPP and idle another amount of time. That could be put in a equation:: - P(opp)target = ((Trunning x (P(opp)running) + (Tidle x P(opp)idle)) / + P(opp)target = (Trunning x (P(opp)running) + (Tidle x P(opp)idle)) / (Trunning + Tidle) ... From a5a612cd3eecbadf135df1e9b2b40bc9f4c28f1c Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:25 -0700 Subject: [PATCH 0188/2286] efi/cper: Reject CPER records with an out-of-range error_data_length cper_estatus_check() sizes each section with acpi_hest_get_record_size(), which adds the firmware-controlled u32 error_data_length to the header size as a signed int (see ). A value in the top sizeof(*gdata) bytes of the u32 range wraps the sum small rather than large, so it slips past the "record_size > data_len" check: against the 72-byte v300 header, 0xffffffb9 sizes the record at 1 and acpi_hest_get_next() walks it a byte at a time, off the end. 0xffffffb8 sizes it at 0 and loops forever. Use check_add_overflow() to reject a sum that will not fit the int those helpers return, since the walk advances by that value. That subsumes the "acpi_hest_get_size(gdata) > data_len" test above it, because record_size is never smaller than the header. The later "len < sizeof(*foo)" guards rely on this per-section upper bound; they are lower bounds only. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=1 Fixes: 45b14a4ffcc1 ("efi: cper: Fix possible out-of-bounds access") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-2-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 06b4fdb59917a1..332c78f2d56177 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -752,7 +752,7 @@ EXPORT_SYMBOL_GPL(cper_estatus_check_header); int cper_estatus_check(const struct acpi_hest_generic_status *estatus) { struct acpi_hest_generic_data *gdata; - unsigned int data_len, record_size; + unsigned int data_len; int rc; rc = cper_estatus_check_header(estatus); @@ -762,10 +762,18 @@ int cper_estatus_check(const struct acpi_hest_generic_status *estatus) data_len = estatus->data_length; apei_estatus_for_each_section(estatus, gdata) { - if (acpi_hest_get_size(gdata) > data_len) + int record_size; + + /* + * The helpers sum these as a signed int, so a + * huge error_data_length wraps small rather than large and the + * walk then advances by that wrapped value. Reject a size an + * int cannot carry. + */ + if (check_add_overflow(acpi_hest_get_size(gdata), + gdata->error_data_length, &record_size)) return -EINVAL; - record_size = acpi_hest_get_record_size(gdata); if (record_size > data_len) return -EINVAL; From 41e25ef5e417c62eb7bf39b187242b283b3ef9f1 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:26 -0700 Subject: [PATCH 0189/2286] efi/cper: Reject an error status block length that wraps a u32 cper_estatus_len() sums the firmware-controlled data_length (or raw_data_offset plus raw_data_length) into a u32. A data_length of 0xffffffec wraps that sum to 0, and a length that reads back short defeats every bound built on it: bert_print_all() passes its "remain < estatus_len" check, then advances "estatus += estatus_len" by zero and loops forever. GHES survives only because __ghes_check_estatus() rejects a length below sizeof(*estatus) first. Reject a length that cannot be expressed in a u32 in cper_estatus_check_header(), which both of today's callers reach: GHES via __ghes_check_estatus() and BERT via cper_estatus_check(). extlog reaches neither yet; a later patch routes it through cper_estatus_check(), whose ELOG_ENTRY_LEN bound needs this to hold. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=1 Fixes: 06d65deade9a ("ACPI, APEI, UEFI Common Platform Error Record (CPER) header") Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-3-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 332c78f2d56177..3dcef618ad39f8 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -745,6 +745,17 @@ int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) return -EINVAL; + /* + * cper_estatus_len() sums these into a u32, and a wrapped sum reads + * back smaller than the record. Reject a length that cannot be + * expressed so no caller is handed the short value. + */ + if ((u64)sizeof(*estatus) + estatus->data_length > U32_MAX) + return -EINVAL; + if (estatus->raw_data_length && + (u64)estatus->raw_data_offset + estatus->raw_data_length > U32_MAX) + return -EINVAL; + return 0; } EXPORT_SYMBOL_GPL(cper_estatus_check_header); From e1fd582a8134150aa10c1f0286dbce00cf63b086 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:27 -0700 Subject: [PATCH 0190/2286] ACPI: extlog: Validate elog record length before walking sections extlog_print() copies a fixed ELOG_ENTRY_LEN (4096) bytes from the elog record into elog_buf, then walks the sections using the firmware-controlled data_length. Nothing keeps data_length inside the buffer, so a malformed record walks the section pointer past elog_buf and reads adjacent memory. Unlike the GHES paths, extlog never calls cper_estatus_check(). Reject a record longer than ELOG_ENTRY_LEN and run cper_estatus_check() before walking the sections. The length test alone is not enough: a wrapped length reads back short and passes it, which cper_estatus_check() catches via the header check added earlier. Drop a malformed record with NOTIFY_DONE and without MCE_HANDLED_EXTLOG, since extlog did not consume it. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=6 Fixes: f6ec01da40e4 ("ACPI: extlog: Handle multiple records") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-4-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 7ad3b36013cc61..046e92ee195f92 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -208,6 +208,15 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, tmp = (struct acpi_hest_generic_status *)elog_buf; + /* + * Bound the length before cper_estatus_check() walks the sections: it + * iterates over data_length, which is not yet known to fit elog_buf. + * cper_estatus_check_header() then rejects a length that wrapped, which + * the bound cannot see. + */ + if (cper_estatus_len(tmp) > ELOG_ENTRY_LEN || cper_estatus_check(tmp)) + return NOTIFY_DONE; + if (!ras_userspace_consumers()) { print_extlog_rcd(NULL, tmp, cpu); goto out; From 179ef6d7f971d72a0f54b7c499daed1b244ee1a5 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:28 -0700 Subject: [PATCH 0191/2286] ACPI: extlog: Defer CXL protocol error handling to avoid lock inversion extlog_print() calls cxl_cper_handle_prot_err() synchronously while the MCE notifier chain rwsem is held, and that path takes the PCI device_lock via guard(device)(). The probe path takes the two in the opposite order, holding device_lock while mce_register_decode_chain() takes the rwsem, so they can deadlock AB-BA. ghes.c already avoids this by posting protocol errors to a kfifo and handling them from a workqueue via cxl_cper_post_prot_err(). Export that function and call it instead. Declare it with the other CONFIG_ACPI_APEI_GHES exports rather than at the end of the header. No #else stub: ACPI_EXTLOG selects ACPI_APEI_GHES, so the only caller cannot exist without it. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-5-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 21 ++------------------- drivers/acpi/apei/ghes.c | 5 +++-- include/acpi/ghes.h | 4 ++++ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 046e92ee195f92..2451362e696dcf 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -163,23 +163,6 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, #endif } -static void -extlog_cxl_cper_handle_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) -{ -#ifdef ACPI_APEI_PCIEAER - struct cxl_cper_prot_err_work_data wd; - - if (cxl_cper_sec_prot_err_valid(prot_err)) - return; - - if (cxl_cper_setup_prot_err_work_data(&wd, prot_err, severity)) - return; - - cxl_cper_handle_prot_err(&wd); -#endif -} - static int extlog_print(struct notifier_block *nb, unsigned long val, void *data) { @@ -244,8 +227,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - extlog_cxl_cper_handle_prot_err(prot_err, - gdata->error_severity); + cxl_cper_post_prot_err(prot_err, + gdata->error_severity); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index fe10ab0e02f689..83558bb10be144 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -752,8 +752,8 @@ static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data, static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; -static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity) { #ifdef CONFIG_ACPI_APEI_PCIEAER struct cxl_cper_prot_err_work_data wd; @@ -777,6 +777,7 @@ static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, schedule_work(cxl_cper_prot_err_work); #endif } +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_post_prot_err, "acpi_extlog"); void cxl_cper_register_prot_err_work(struct work_struct *work) { diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index 8d7e5caef3f18a..be496bc0386fc3 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -85,6 +85,10 @@ int devm_ghes_register_vendor_record_notifier(struct device *dev, struct list_head *ghes_get_devices(void); void ghes_estatus_pool_region_free(unsigned long addr, u32 size); + +struct cxl_cper_sec_prot_err; +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity); #else static inline struct list_head *ghes_get_devices(void) { return NULL; } From 31333d134afb13e3942867d48efbcf8d6b88c849 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:29 -0700 Subject: [PATCH 0192/2286] ACPI: extlog: Avoid populating software AER metadata from raw hardware buffer extlog_print_pcie() casts pcie_err->aer_info straight to struct aer_capability_regs *. That struct embeds struct pcie_tlp_log, whose software-only header_len and flit fields sit at offset 84 - inside the 96-byte aer_info buffer - so the cast fills them with raw firmware bytes. pcie_print_tlp_log() uses both to bound a loop over dw[], and a large header_len walks past the end of the array. Copy into a zeroed local struct, and only as far as aer_info maps onto it: the leading registers and the four Header Log DWORDs. Place the TLP Prefix Log separately, from the offset the hardware keeps it at. The rest stays zero, which covers header_len and flit and keeps the Root Error registers out of the prefix log, where pcie_print_tlp_log() would print them as end-to-end prefixes. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-6-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 2451362e696dcf..1b61788abd5a65 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -137,7 +137,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int severity) { #ifdef ACPI_APEI_PCIEAER - struct aer_capability_regs *aer; + struct aer_capability_regs aer_regs = {}; struct pci_dev *pdev; unsigned int devfn; unsigned int bus; @@ -149,7 +149,21 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, return; aer_severity = cper_severity_to_aer(severity); - aer = (struct aer_capability_regs *)pcie_err->aer_info; + + /* + * struct pcie_tlp_log is larger than the hardware layout, so aer_info + * only maps onto the struct up to the four Header Log DWORDs. Copy that + * much, then place the TLP Prefix Log from where the hardware keeps it. + * Everything else stays zero: nothing reads root_command, root_status or + * the error source IDs, and header_len and flit are software-only. + */ + memcpy(&aer_regs, pcie_err->aer_info, + offsetof(struct aer_capability_regs, header_log) + + PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); + memcpy(aer_regs.header_log.prefix, + pcie_err->aer_info + PCI_ERR_PREFIX_LOG, + sizeof(aer_regs.header_log.prefix)); + domain = pcie_err->device_id.segment; bus = pcie_err->device_id.bus; devfn = PCI_DEVFN(pcie_err->device_id.device, @@ -158,7 +172,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, if (!pdev) return; - pci_print_aer(pdev, aer_severity, aer); + pci_print_aer(pdev, aer_severity, &aer_regs); pci_dev_put(pdev); #endif } From 5eefd849e26506170552e614496d094230650e78 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:30 -0700 Subject: [PATCH 0193/2286] ACPI: extlog: Validate PCIe error section length before payload access extlog_print_pcie() reads pcie_err->validation_bits and device_id and copies the 96-byte aer_info buffer without checking that gdata->error_data_length is big enough for a struct cper_sec_pcie. The cper_estatus_check() call added earlier keeps the read inside the estatus block, but a short section still gets stale adjacent bytes treated as PCIe error data. Reject a section too small to hold the record before touching any field, and warn: a truncated section means firmware is emitting malformed records. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-7-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 1b61788abd5a65..0ab7885e547157 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -134,7 +134,7 @@ static int print_extlog_rcd(const char *pfx, } static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, - int severity) + int severity, u32 len) { #ifdef ACPI_APEI_PCIEAER struct aer_capability_regs aer_regs = {}; @@ -144,6 +144,12 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int aer_severity; int domain; + if (len < sizeof(*pcie_err)) { + pr_warn_ratelimited(FW_WARN + "PCIe error section too small (%u)\n", len); + return; + } + if (!(pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO)) return; @@ -246,7 +252,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); - extlog_print_pcie(pcie_err, gdata->error_severity); + extlog_print_pcie(pcie_err, gdata->error_severity, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); From 44a8d464d79b60883f7bd386049a1ffff32ca690 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:31 -0700 Subject: [PATCH 0194/2286] ACPI: extlog: Fix CONFIG_ACPI_APEI_PCIEAER guard typo The guard reads "#ifdef ACPI_APEI_PCIEAER" rather than "#ifdef CONFIG_ACPI_APEI_PCIEAER". That symbol is never defined, so the extlog PCIe AER handling is always compiled out. Use the CONFIG_ prefixed symbol. Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-8-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 0ab7885e547157..8c17c2719526e8 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -136,7 +136,7 @@ static int print_extlog_rcd(const char *pfx, static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int severity, u32 len) { -#ifdef ACPI_APEI_PCIEAER +#ifdef CONFIG_ACPI_APEI_PCIEAER struct aer_capability_regs aer_regs = {}; struct pci_dev *pdev; unsigned int devfn; From b7a4915a2b43d47f0bbb0b2a4c30e722fa0d3191 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:32 -0700 Subject: [PATCH 0195/2286] ACPI: APEI: GHES: Bound CXL event record copy to the firmware section length cxl_cper_post_event() copies a fixed sizeof(struct cxl_cper_event_rec) out of the firmware CPER section without checking how long the section actually is, so a short one reads past the record. Pass gdata->error_data_length in and reject a section too small to hold the record before the copy. Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/patchset/20260617-topics-ahmtib01-ras_ffh_arm_internal_review-v6-0-91f725174aa0@arm.com?part=6 Fixes: 5e4a264bf8b5 ("acpi/ghes: Process CXL Component Events") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-9-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 83558bb10be144..8f66dfb8edba42 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -824,10 +824,15 @@ static DEFINE_RAW_SPINLOCK(cxl_cper_work_lock); struct work_struct *cxl_cper_work; static void cxl_cper_post_event(enum cxl_event_type event_type, - struct cxl_cper_event_rec *rec) + struct cxl_cper_event_rec *rec, u32 len) { struct cxl_cper_work_data wd; + if (len < sizeof(*rec)) { + pr_err(FW_WARN "CXL CPER section too small (%u)\n", len); + return; + } + if (rec->hdr.length <= sizeof(rec->hdr) || rec->hdr.length > sizeof(*rec)) { pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", @@ -964,15 +969,18 @@ static void ghes_do_proc(struct ghes *ghes, } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec); + cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec); + cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec); + cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); From 8bf58815315aeb60125d26404d6fa7d576d5d518 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:33 -0700 Subject: [PATCH 0196/2286] ACPI: APEI: GHES: Validate CXL protocol error section length before RAS cap copy cxl_cper_setup_prot_err_work_data() locates the RAS Capability block at prot_err + sizeof(*prot_err) + dvsec_len and copies it, but dvsec_len is firmware controlled and never validated, so it can point the copy outside the section. Extend cxl_cper_sec_prot_err_valid() to check that the section can hold the header, and that the header, DVSEC and RAS Capability block together fit the reported section length. Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/patchset/20260617-topics-ahmtib01-ras_ffh_arm_internal_review-v6-0-91f725174aa0@arm.com?part=6 Link: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: 315c2f0b53ba ("acpi/ghes, cper: Recognize and cache CXL Protocol errors") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-10-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 3 ++- drivers/acpi/apei/ghes.c | 7 ++++--- drivers/acpi/apei/ghes_helpers.c | 18 +++++++++++++++++- include/acpi/ghes.h | 2 +- include/cxl/event.h | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 8c17c2719526e8..9e61354a807b33 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -248,7 +248,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, acpi_hest_get_payload(gdata); cxl_cper_post_prot_err(prot_err, - gdata->error_severity); + gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 8f66dfb8edba42..111895c57c2f95 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -753,12 +753,12 @@ static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) + int severity, u32 len) { #ifdef CONFIG_ACPI_APEI_PCIEAER struct cxl_cper_prot_err_work_data wd; - if (cxl_cper_sec_prot_err_valid(prot_err)) + if (cxl_cper_sec_prot_err_valid(prot_err, len)) return; guard(raw_spinlock_irqsave)(&cxl_cper_prot_err_work_lock); @@ -965,7 +965,8 @@ static void ghes_do_proc(struct ghes *ghes, } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - cxl_cper_post_prot_err(prot_err, gdata->error_severity); + cxl_cper_post_prot_err(prot_err, gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes_helpers.c b/drivers/acpi/apei/ghes_helpers.c index bc7111b740afb5..df41b993f4130a 100644 --- a/drivers/acpi/apei/ghes_helpers.c +++ b/drivers/acpi/apei/ghes_helpers.c @@ -5,8 +5,15 @@ #include #include -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { + if (len < sizeof(*prot_err)) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err section too small (%u)\n", + len); + return -EINVAL; + } + if (!(prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS)) { pr_err_ratelimited("CXL CPER invalid agent type\n"); return -EINVAL; @@ -23,6 +30,15 @@ int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) return -EINVAL; } + /* The RAS Capability block sits after a firmware-sized DVSEC. */ + if (sizeof(*prot_err) + prot_err->dvsec_len + + sizeof(struct cxl_ras_capability_regs) > len) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err DVSEC (%u) overruns section (%u)\n", + prot_err->dvsec_len, len); + return -EINVAL; + } + if ((prot_err->agent_type == RCD || prot_err->agent_type == DEVICE || prot_err->agent_type == LD || prot_err->agent_type == FMLD) && !(prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER)) diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index be496bc0386fc3..7acf209061ea78 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -88,7 +88,7 @@ void ghes_estatus_pool_region_free(unsigned long addr, u32 size); struct cxl_cper_sec_prot_err; void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity); + int severity, u32 len); #else static inline struct list_head *ghes_get_devices(void) { return NULL; } diff --git a/include/cxl/event.h b/include/cxl/event.h index b5673384d93066..5779e35310c762 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -312,13 +312,13 @@ static inline int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data #endif #ifdef CONFIG_ACPI_APEI_PCIEAER -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err); +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len); int cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, struct cxl_cper_sec_prot_err *prot_err, int severity); #else static inline int -cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { return -EOPNOTSUPP; } From 92480a2b00a15b0add512b6fc474151ec5340a8e Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:34 -0700 Subject: [PATCH 0197/2286] efi/cper: Read only validated fields in cper_mem_err_pack() cper_mem_err_pack() copies extended, rank, mem_array_handle and mem_dev_handle unconditionally. Those live at offsets 73 to 79, past the end of struct cper_sec_mem_err_old, the 73-byte UEFI 2.1/2.2 layout that older firmware still emits and that cper_estatus_print_section() admits. On such a record the copy reads up to seven bytes past the payload, and off the end of the error status block when that section is the last one. Copy each of the four only when its validation bit is set, and zero it otherwise. Nothing is lost: a 2.1/2.2 record leaves those bits clear, and every consumer of struct cper_mem_err_compact already gates the fields on the same bits. Zeroing also stops callers reading them back out of the uninitialised on-stack struct they pass in. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=7 Fixes: 2dfb7d51a61d ("trace, RAS: Add eMCA trace event interface") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-11-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 3dcef618ad39f8..13b3e72c3b62fa 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -389,10 +389,28 @@ void cper_mem_err_pack(const struct cper_sec_mem_err *mem, cmem->requestor_id = mem->requestor_id; cmem->responder_id = mem->responder_id; cmem->target_id = mem->target_id; - cmem->extended = mem->extended; - cmem->rank = mem->rank; - cmem->mem_array_handle = mem->mem_array_handle; - cmem->mem_dev_handle = mem->mem_dev_handle; + + /* + * These four sit past the end of the UEFI 2.1/2.2 layout, which older + * firmware still emits, so reading them unconditionally runs off a + * short record. Every consumer of the compact record gates them on the + * same validation bits, so leave them zero when firmware does not + * claim them. + */ + cmem->extended = 0; + cmem->rank = 0; + cmem->mem_array_handle = 0; + cmem->mem_dev_handle = 0; + + if (mem->validation_bits & + (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + cmem->extended = mem->extended; + if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) + cmem->rank = mem->rank; + if (mem->validation_bits & CPER_MEM_VALID_CARD_HANDLE) + cmem->mem_array_handle = mem->mem_array_handle; + if (mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + cmem->mem_dev_handle = mem->mem_dev_handle; } EXPORT_SYMBOL_GPL(cper_mem_err_pack); From 10338c9085b4939a428a8dc78d2bbf62c161278b Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:35 -0700 Subject: [PATCH 0198/2286] ACPI: APEI: GHES: Validate memory error section length before payload access ghes_do_proc() hands the CPER_SEC_PLATFORM_MEM payload to the report chain, arch_apei_report_mem_error() and ghes_handle_memory_failure() without checking gdata->error_data_length. All three read validation_bits and physical_addr, at offsets 0 and 16, so a shorter section reads past the record. Check the length once in ghes_do_proc(), before any consumer runs. Take the 73-byte struct cper_sec_mem_err_old as the floor: older firmware legitimately emits that UEFI 2.1/2.2 layout, and it makes validation_bits safe to read. The fields from "extended" on are absent from that layout, so also require whatever length the validation bits claim. Derive it per field rather than demanding the full 80 bytes: rank needs only 76, and the BANK_GROUP and BANK_ADDRESS bits describe "bank" at offset 38, which every record carries. Testing the whole mask against one size, as cper_print_mem() does, gets it wrong both ways. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=7 Fixes: ca104edc1784 ("ACPI, APEI, GHES: Cleanup ghes memory error handling") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-12-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 111895c57c2f95..2077cc90569a06 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -929,6 +929,28 @@ static void ghes_log_hwerr(int sev, guid_t *sec_type) hwerr_log_error_type(HWERR_RECOV_OTHERS); } +/* + * The fields from "extended" on are absent from the 73-byte UEFI 2.1/2.2 + * layout that older firmware still emits. Return the length needed for the + * fields the validation bits claim, so over-claiming is rejected without + * rejecting an honest short record. + */ +static u32 ghes_mem_err_min_len(u64 validation_bits) +{ + u32 len = sizeof(struct cper_sec_mem_err_old); + + if (validation_bits & (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + len = offsetof(struct cper_sec_mem_err, rank); + if (validation_bits & CPER_MEM_VALID_RANK_NUMBER) + len = offsetof(struct cper_sec_mem_err, mem_array_handle); + if (validation_bits & CPER_MEM_VALID_CARD_HANDLE) + len = offsetof(struct cper_sec_mem_err, mem_dev_handle); + if (validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + len = sizeof(struct cper_sec_mem_err); + + return len; +} + static void ghes_do_proc(struct ghes *ghes, const struct acpi_hest_generic_status *estatus) { @@ -954,6 +976,25 @@ static void ghes_do_proc(struct ghes *ghes, if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); + /* + * Check once for all three consumers below. The 73-byte + * UEFI 2.1/2.2 layout is the floor, matching + * cper_estatus_print_section() and making + * validation_bits safe to read. + */ + if (gdata->error_data_length < + sizeof(struct cper_sec_mem_err_old)) + continue; + + /* Then require what the claimed fields actually need. */ + if (gdata->error_data_length < + ghes_mem_err_min_len(mem_err->validation_bits)) { + pr_warn_ratelimited(FW_WARN GHES_PFX + "memory error section too small (%u) for the fields it claims\n", + gdata->error_data_length); + continue; + } + atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); arch_apei_report_mem_error(sev, mem_err); From 9c3ad3bfff20d2d7a0c9614db2a58e78123bf0ce Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:36 -0700 Subject: [PATCH 0199/2286] ACPI: APEI: GHES: Bound AER info copy and sanitize software metadata ghes_handle_aer() copies sizeof(struct aer_capability_regs) out of the fixed 96-byte pcie_err->aer_info. The struct is larger, so the copy reads past the section, and it fills the software-only header_len and flit fields of the embedded struct pcie_tlp_log from firmware bytes. pcie_print_tlp_log() uses both to bound a loop over dw[], so a large value walks past the array. Nothing checks the section can hold a struct cper_sec_pcie either. Validate error_data_length, zero the destination, and map aer_info onto the struct as extlog_print_pcie() does: copy up to the four Header Log DWORDs, then place the TLP Prefix Log from its own offset. The rest stays zero, covering header_len and flit. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=3 Fixes: 7e077e6707b3 ("PCI/ERR: Handle TLP Log in Flit mode") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-13-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 2077cc90569a06..08c985e729d6c7 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -642,11 +642,14 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) #ifdef CONFIG_ACPI_APEI_PCIEAER struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); + if (gdata->error_data_length < sizeof(*pcie_err)) + return; + if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) { + struct aer_capability_regs *aer_info; unsigned int devfn; int aer_severity; - u8 *aer_info; devfn = PCI_DEVFN(pcie_err->device_id.device, pcie_err->device_id.function); @@ -664,13 +667,25 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) sizeof(struct aer_capability_regs)); if (!aer_info) return; - memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs)); + + /* + * Map aer_info onto the struct as extlog_print_pcie() does: + * copy up to the four Header Log DWORDs, then place the TLP + * Prefix Log from where the hardware keeps it. The rest stays + * zero, so firmware cannot drive the pcie_print_tlp_log() loop + * over dw[] out of bounds. + */ + memset(aer_info, 0, sizeof(struct aer_capability_regs)); + memcpy(aer_info, pcie_err->aer_info, + offsetof(struct aer_capability_regs, header_log) + + PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); + memcpy(aer_info->header_log.prefix, + pcie_err->aer_info + PCI_ERR_PREFIX_LOG, + sizeof(aer_info->header_log.prefix)); aer_recover_queue(pcie_err->device_id.segment, pcie_err->device_id.bus, - devfn, aer_severity, - (struct aer_capability_regs *) - aer_info); + devfn, aer_severity, aer_info); } #endif } From a412a81560b3b0ec992872e8ce8858d78c3abd3f Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:37 -0700 Subject: [PATCH 0200/2286] cxl/ras: Make cxl_cper_handle_prot_err() static The extlog caller went away when extlog switched to cxl_cper_post_prot_err(), leaving cxl_cper_prot_err_work_fn() in the same file as the only caller. Drop the export and the declaration in , and make it static. Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-14-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/cxl/core/ras.c | 3 +-- include/cxl/event.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index e307361bb39e46..c91db125a9714d 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -77,7 +77,7 @@ static int match_memdev_by_parent(struct device *dev, const void *uport) return 0; } -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) +static void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) { unsigned int devfn = PCI_DEVFN(data->prot_err.agent_addr.device, data->prot_err.agent_addr.function); @@ -118,7 +118,6 @@ void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) else cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap); } -EXPORT_SYMBOL_GPL(cxl_cper_handle_prot_err); static void cxl_cper_prot_err_work_fn(struct work_struct *work) { diff --git a/include/cxl/event.h b/include/cxl/event.h index 5779e35310c762..a9f5c2381cc9ef 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -331,6 +331,4 @@ cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, } #endif -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *wd); - #endif /* _LINUX_CXL_EVENT_H */ From 01a43c55ae5f00601ef7877dd86b873770b9efe9 Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:31 -0700 Subject: [PATCH 0201/2286] x86/virt/tdx: Simplify PAMT layout calculation For each memory region that the TDX module might use (called TDMR), three separate traditional PAMT allocations are needed. There is one for each supported page size (1GB, 2MB, 4KB). These store information on each page in the TDMR. In Linux, they are allocated out of one physically contiguous block, in order to more efficiently use some internal TDX module bookkeeping resources. So some simple math is needed to break the single large allocation into three smaller allocations for each page size. There are some commonalities in the math needed to calculate the base and size for each smaller allocation, and so an effort was made to share logic across the three. Unfortunately doing this turned out unnaturally tortured, with a loop iterating over the three page sizes, only to call into a function with case statements for each page size. In the future Dynamic PAMT will add more logic that is special to the 4KB page size, making the benefit of the math sharing even more questionable. Three is not a very high number, so get rid of the loop and just duplicate the small calculation three times. In doing so, setup for future Dynamic PAMT changes. Since the loop that iterates over it is gone, further simplify the code by dropping the array of intermediate size and base storage. Just store the values to their final locations. AI was used under supervision to collect/apply feedback, review code and workshop logs. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Chao Gao Reviewed-by: Yan Zhao Reviewed-by: Tony Lindgren Reviewed-by: Dave Hansen Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-2-rick.p.edgecombe@intel.com --- arch/x86/virt/vmx/tdx/tdx.c | 88 ++++++++++++------------------------- 1 file changed, 27 insertions(+), 61 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 1b9ff749dd8e02..51b191bf12258c 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -514,31 +514,21 @@ static __init int fill_out_tdmrs(struct list_head *tmb_list, * Calculate PAMT size given a TDMR and a page size. The returned * PAMT size is always aligned up to 4K page boundary. */ -static __init unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz, - u16 pamt_entry_size) +static __init unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz) { unsigned long pamt_sz, nr_pamt_entries; + const int tdx_pg_size_shift[TDX_PS_NR] = { PAGE_SHIFT, PMD_SHIFT, PUD_SHIFT }; + const u16 pamt_entry_size[TDX_PS_NR] = { + tdx_sysinfo.tdmr.pamt_4k_entry_size, + tdx_sysinfo.tdmr.pamt_2m_entry_size, + tdx_sysinfo.tdmr.pamt_1g_entry_size, + }; - switch (pgsz) { - case TDX_PS_4K: - nr_pamt_entries = tdmr->size >> PAGE_SHIFT; - break; - case TDX_PS_2M: - nr_pamt_entries = tdmr->size >> PMD_SHIFT; - break; - case TDX_PS_1G: - nr_pamt_entries = tdmr->size >> PUD_SHIFT; - break; - default: - WARN_ON_ONCE(1); - return 0; - } + nr_pamt_entries = tdmr->size >> tdx_pg_size_shift[pgsz]; + pamt_sz = nr_pamt_entries * pamt_entry_size[pgsz]; - pamt_sz = nr_pamt_entries * pamt_entry_size; /* TDX requires PAMT size must be 4K aligned */ - pamt_sz = ALIGN(pamt_sz, PAGE_SIZE); - - return pamt_sz; + return PAGE_ALIGN(pamt_sz); } /* @@ -576,15 +566,11 @@ static __init int tdmr_get_nid(struct tdmr_info *tdmr, struct list_head *tmb_lis * within @tdmr, and set up PAMTs for @tdmr. */ static __init int tdmr_set_up_pamt(struct tdmr_info *tdmr, - struct list_head *tmb_list, - u16 pamt_entry_size[]) + struct list_head *tmb_list) { - unsigned long pamt_base[TDX_PS_NR]; - unsigned long pamt_size[TDX_PS_NR]; - unsigned long tdmr_pamt_base; unsigned long tdmr_pamt_size; struct page *pamt; - int pgsz, nid; + int nid; nid = tdmr_get_nid(tdmr, tmb_list); @@ -592,12 +578,10 @@ static __init int tdmr_set_up_pamt(struct tdmr_info *tdmr, * Calculate the PAMT size for each TDX supported page size * and the total PAMT size. */ - tdmr_pamt_size = 0; - for (pgsz = TDX_PS_4K; pgsz < TDX_PS_NR; pgsz++) { - pamt_size[pgsz] = tdmr_get_pamt_sz(tdmr, pgsz, - pamt_entry_size[pgsz]); - tdmr_pamt_size += pamt_size[pgsz]; - } + tdmr->pamt_4k_size = tdmr_get_pamt_sz(tdmr, TDX_PS_4K); + tdmr->pamt_2m_size = tdmr_get_pamt_sz(tdmr, TDX_PS_2M); + tdmr->pamt_1g_size = tdmr_get_pamt_sz(tdmr, TDX_PS_1G); + tdmr_pamt_size = tdmr->pamt_4k_size + tdmr->pamt_2m_size + tdmr->pamt_1g_size; /* * Allocate one chunk of physically contiguous memory for all @@ -606,25 +590,17 @@ static __init int tdmr_set_up_pamt(struct tdmr_info *tdmr, */ pamt = alloc_contig_pages(tdmr_pamt_size >> PAGE_SHIFT, GFP_KERNEL, nid, &node_online_map); - if (!pamt) - return -ENOMEM; /* - * Break the contiguous allocation back up into the - * individual PAMTs for each page size. + * tdmr->pamt_4k_base is still zero so the error + * path of the caller will skip freeing the PAMT. */ - tdmr_pamt_base = page_to_pfn(pamt) << PAGE_SHIFT; - for (pgsz = TDX_PS_4K; pgsz < TDX_PS_NR; pgsz++) { - pamt_base[pgsz] = tdmr_pamt_base; - tdmr_pamt_base += pamt_size[pgsz]; - } + if (!pamt) + return -ENOMEM; - tdmr->pamt_4k_base = pamt_base[TDX_PS_4K]; - tdmr->pamt_4k_size = pamt_size[TDX_PS_4K]; - tdmr->pamt_2m_base = pamt_base[TDX_PS_2M]; - tdmr->pamt_2m_size = pamt_size[TDX_PS_2M]; - tdmr->pamt_1g_base = pamt_base[TDX_PS_1G]; - tdmr->pamt_1g_size = pamt_size[TDX_PS_1G]; + tdmr->pamt_4k_base = page_to_phys(pamt); + tdmr->pamt_2m_base = tdmr->pamt_4k_base + tdmr->pamt_4k_size; + tdmr->pamt_1g_base = tdmr->pamt_2m_base + tdmr->pamt_2m_size; return 0; } @@ -655,10 +631,7 @@ static __init void tdmr_do_pamt_func(struct tdmr_info *tdmr, tdmr_get_pamt(tdmr, &pamt_base, &pamt_size); /* Do nothing if PAMT hasn't been allocated for this TDMR */ - if (!pamt_size) - return; - - if (WARN_ON_ONCE(!pamt_base)) + if (!pamt_base) return; pamt_func(pamt_base, pamt_size); @@ -684,14 +657,12 @@ static __init void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list) /* Allocate and set up PAMTs for all TDMRs */ static __init int tdmrs_set_up_pamt_all(struct tdmr_info_list *tdmr_list, - struct list_head *tmb_list, - u16 pamt_entry_size[]) + struct list_head *tmb_list) { int i, ret = 0; for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) { - ret = tdmr_set_up_pamt(tdmr_entry(tdmr_list, i), tmb_list, - pamt_entry_size); + ret = tdmr_set_up_pamt(tdmr_entry(tdmr_list, i), tmb_list); if (ret) goto err; } @@ -968,18 +939,13 @@ static __init int construct_tdmrs(struct list_head *tmb_list, struct tdmr_info_list *tdmr_list, struct tdx_sys_info_tdmr *sysinfo_tdmr) { - u16 pamt_entry_size[TDX_PS_NR] = { - sysinfo_tdmr->pamt_4k_entry_size, - sysinfo_tdmr->pamt_2m_entry_size, - sysinfo_tdmr->pamt_1g_entry_size, - }; int ret; ret = fill_out_tdmrs(tmb_list, tdmr_list); if (ret) return ret; - ret = tdmrs_set_up_pamt_all(tdmr_list, tmb_list, pamt_entry_size); + ret = tdmrs_set_up_pamt_all(tdmr_list, tmb_list); if (ret) return ret; From eadde434aa9da1f43804c7097ba360dda8ccd9ef Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:32 -0700 Subject: [PATCH 0202/2286] x86/virt/tdx: Allocate page bitmap for Dynamic PAMT The TDX Physical Address Metadata Table (PAMT) holds data about the physical memory used by TDX, and must be allocated by the kernel during TDX module initialization. The exact size of the required PAMT memory is determined by the TDX module and may vary between TDX module versions. Currently it is approximately 0.4% of the system memory. This is a significant commitment, especially if it is not known upfront whether the machine will run any TDX guests. Each memory region that the TDX module might use needs three separate PAMT allocations. One for each supported page size (1GB, 2MB, 4KB). The TDX module supports a new feature designed to reduce PAMT overhead called Dynamic PAMT (DPAMT). Under DPAMT the 4KB level is allocated dynamically during runtime, while the 1GB and 2MB levels remain allocated on TDX module initialization. However, in the details, DPAMT still needs some smaller per 4KB page scoped data (currently it is 1 bit per page). The TDX module exposes the number of bits as a separate piece of metadata than the 4KB static allocation for normal PAMT. Although the size is enumerated differently, it is handed to the TDX module in the same way the 4KB page size PAMT allocation is for normal PAMT. Begin to implement DPAMT in the kernel by reading the bits-per-page needed for DPAMT. Calculate the size needed for the bitmap, and use it instead of the 4KB size determined for normal PAMT, in the case of DPAMT. The existing metadata reading code was generated by a script, but the current plan is to stop generating this code, as the script has continued to need adjustments. So add manually written code and adjust the comment about it being autogenerated to be more generic. Start to adopt a more normal kernel code style without the ternary statements and if conditionals assignments that the auto generated code has. AI was used under supervision to collect/apply feedback, review code and workshop logs. Based on a patch originally by Kiryl Shutsemau. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Tony Lindgren Reviewed-by: Yan Zhao Reviewed-by: Dave Hansen Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-3-rick.p.edgecombe@intel.com --- arch/x86/include/asm/tdx.h | 2 ++ arch/x86/include/asm/tdx_global_metadata.h | 9 ++++--- arch/x86/virt/vmx/tdx/tdx.c | 29 +++++++++++++++++++-- arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 23 +++++++++++++++- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 89e97d5761d89e..d414064436221f 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -118,6 +118,8 @@ static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinf return sysinfo->features.tdx_features0 & TDX_FEATURES0_TD_PRESERVING; } +bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo); + int tdx_guest_keyid_alloc(void); u32 tdx_get_nr_guest_keyids(void); void tdx_guest_keyid_free(unsigned int keyid); diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h index 41150d546589c1..8a3cc1a2a41e2f 100644 --- a/arch/x86/include/asm/tdx_global_metadata.h +++ b/arch/x86/include/asm/tdx_global_metadata.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Automatically generated TDX global metadata structures. */ -#ifndef _X86_VIRT_TDX_AUTO_GENERATED_TDX_GLOBAL_METADATA_H -#define _X86_VIRT_TDX_AUTO_GENERATED_TDX_GLOBAL_METADATA_H +/* TDX global metadata structures. */ +#ifndef _X86_VIRT_TDX_TDX_GLOBAL_METADATA_H +#define _X86_VIRT_TDX_TDX_GLOBAL_METADATA_H #include @@ -21,6 +21,9 @@ struct tdx_sys_info_tdmr { u16 pamt_4k_entry_size; u16 pamt_2m_entry_size; u16 pamt_1g_entry_size; + + /* Optional metadata, if DPAMT is supported */ + u8 pamt_page_bitmap_entry_bits; }; struct tdx_sys_info_td_ctrl { diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 51b191bf12258c..54074ddcce8586 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -510,6 +510,18 @@ static __init int fill_out_tdmrs(struct list_head *tmb_list, return 0; } +static __init unsigned long tdmr_get_pamt_bitmap_sz(struct tdmr_info *tdmr) +{ + unsigned long pamt_sz, nr_pamt_entries; + int bits_per_entry; + + bits_per_entry = tdx_sysinfo.tdmr.pamt_page_bitmap_entry_bits; + nr_pamt_entries = tdmr->size >> PAGE_SHIFT; + pamt_sz = DIV_ROUND_UP(nr_pamt_entries * bits_per_entry, BITS_PER_BYTE); + + return PAGE_ALIGN(pamt_sz); +} + /* * Calculate PAMT size given a TDMR and a page size. The returned * PAMT size is always aligned up to 4K page boundary. @@ -578,9 +590,16 @@ static __init int tdmr_set_up_pamt(struct tdmr_info *tdmr, * Calculate the PAMT size for each TDX supported page size * and the total PAMT size. */ - tdmr->pamt_4k_size = tdmr_get_pamt_sz(tdmr, TDX_PS_4K); - tdmr->pamt_2m_size = tdmr_get_pamt_sz(tdmr, TDX_PS_2M); tdmr->pamt_1g_size = tdmr_get_pamt_sz(tdmr, TDX_PS_1G); + tdmr->pamt_2m_size = tdmr_get_pamt_sz(tdmr, TDX_PS_2M); + + if (tdx_supports_dynamic_pamt(&tdx_sysinfo)) { + /* With DPAMT, PAMT_4K is replaced with a bitmap */ + tdmr->pamt_4k_size = tdmr_get_pamt_bitmap_sz(tdmr); + } else { + tdmr->pamt_4k_size = tdmr_get_pamt_sz(tdmr, TDX_PS_4K); + } + tdmr_pamt_size = tdmr->pamt_4k_size + tdmr->pamt_2m_size + tdmr->pamt_1g_size; /* @@ -1971,6 +1990,12 @@ u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, kvm_pfn_t pfn) } EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid); +bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo) +{ + /* To be enabled when kernel is ready. */ + return false; +} + void tdx_sys_disable(void) { struct tdx_module_args args = {}; diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c index e49c300f23d435..98ebf17aab1ce9 100644 --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Automatically generated functions to read TDX global metadata. + * Functions to read TDX global metadata. * * This file doesn't compile on its own as it lacks of inclusion * of SEAMCALL wrapper primitive which reads global metadata. @@ -33,6 +33,18 @@ static __init int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinf return ret; } +static __init int get_tdx_sys_info_tdmr_dpamt(struct tdx_sys_info_tdmr *sysinfo_tdmr) +{ + int ret; + u64 val; + + ret = read_sys_metadata_field(0x9100000000000013, &val); + if (!ret) + sysinfo_tdmr->pamt_page_bitmap_entry_bits = val; + + return ret; +} + static __init int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr) { int ret = 0; @@ -129,5 +141,14 @@ static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo) ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl); ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf); + /* + * The kernel supports using TDX without DPAMT, so + * avoid reporting failure if it's not supported. Don't + * try to support buggy TDX modules that advertise + * DPAMT but don't expose the metadata. + */ + if (!ret && tdx_supports_dynamic_pamt(sysinfo)) + ret = get_tdx_sys_info_tdmr_dpamt(&sysinfo->tdmr); + return ret; } From ad52ce8389d9f3708bfaa301e194550f582eb2b5 Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:33 -0700 Subject: [PATCH 0203/2286] x86/virt/tdx: Add __tdx_pamt_get/put() helpers Add helpers to use when allocating or preparing pages that are handed to the TDX module for use as control/S-EPT pages, and thus need Dynamic PAMT (DPAMT) adjustments. The TDX module tracks some state for each page of physical memory that it might use. It calls this state the PAMT. It includes separate state for each page size a physical page could be utilized at within the TDX module (1GB, 2MB, 4KB). In DPAMT, only the 4KB page size state is allocated dynamically. KVM will need to hand pages to the TDX module that it will use at 4KB granularity. So these pages will need DPAMT backing added before they are used by the TDX module, and removed afterwards. Create __tdx_pamt_get/put() to handle installing DPAMT 4KB backing for pages that are already allocated (such as KVM's use of S-EPT page tables or guest private memory). Have them take a pfn instead of a struct page, as future changes will want to use these helpers for guest pages which are tracked by PFN. Also add __tdx_alloc_control_page() and __tdx_free_control_page() to handle both page allocation and DPAMT installation. Make them behave like normal alloc/free functions where allocation can fail in the case of no memory, but free (with any necessary DPAMT release) always succeeds. Do this so they can eventually support the existing TDX flows that require teardowns to succeed. Don't CLFLUSH the DPAMT pages handed to the TDX module, as is done for some other SEAMCALLs, as the TDX docs specify that this is only needed on "TD private memory or TD control structure page". Since these allocations will be easily user triggerable, account the memory. Only one pair of DPAMT pages is required for each 2MB-aligned physical region, so multiple callers could trip over each other if they try to manage the shared backing for two separate 4KB pages contained in one. To build the logic up iteratively, don't do anything to handle pages from the same 2MB region yet. Functionality to handle this will be added before DPAMT can be enabled. AI was used under supervision to collect/apply feedback, split patches, review code and workshop logs. Based on a patch originally by Kiryl Shutsemau. Co-developed-by: Sean Christopherson Signed-off-by: Sean Christopherson Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Chao Gao Reviewed-by: Binbin Wu Reviewed-by: Tony Lindgren Reviewed-by: Vishal Annapurve Reviewed-by: Dave Hansen Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-4-rick.p.edgecombe@intel.com --- arch/x86/virt/vmx/tdx/tdx.c | 197 ++++++++++++++++++++++++++++++++++++ arch/x86/virt/vmx/tdx/tdx.h | 2 + 2 files changed, 199 insertions(+) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 54074ddcce8586..a9c5b6f5384ea6 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -46,6 +46,9 @@ #include "seamcall_internal.h" #include "tdx.h" +/* Number of DPAMT pages to be provided to TDX module per 2MB region of PA */ +#define TDX_DPAMT_ENTRY_PAGE_CNT 2 + struct tdx_module_state { bool initialized; bool sysinit_done; @@ -1996,6 +1999,200 @@ bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo) return false; } +static int alloc_pamt_array(struct page **pamt_pages) +{ + int i, j; + + for (i = 0; i < TDX_DPAMT_ENTRY_PAGE_CNT; i++) { + pamt_pages[i] = alloc_page(GFP_KERNEL_ACCOUNT); + if (!pamt_pages[i]) + goto err; + } + + return 0; + +err: + for (j = 0; j < i; j++) + __free_page(pamt_pages[j]); + + return -ENOMEM; +} + +static void free_pamt_array(struct page **pamt_pages) +{ + int i; + + for (i = 0; i < TDX_DPAMT_ENTRY_PAGE_CNT; i++) { + /* + * Reset pages unconditionally to cover cases + * where they were passed to the TDX module. + */ + tdx_quirk_reset_paddr(page_to_phys(pamt_pages[i]), PAGE_SIZE); + + __free_page(pamt_pages[i]); + } +} + +/* Helper for building DPAMT seamcall() arguments. */ +static u64 pamt_2mb_arg(kvm_pfn_t pfn) +{ + /* Find the 2MB-wide DPAMT region for 'pfn': */ + unsigned long hpa_2mb = ALIGN_DOWN(pfn << PAGE_SHIFT, PMD_SIZE); + + /* + * TDX ABI requires specifying the page level the installed DPAMT + * backing will cover, even though today only 2MB is supported. + */ + return hpa_2mb | TDX_PS_2M; +} + +/* Add PAMT backing for the 2MB region surrounding the given pfn. */ +static u64 tdh_phymem_pamt_add(kvm_pfn_t pfn, struct page **pamt_pages) +{ + struct tdx_module_args args = { + .rcx = pamt_2mb_arg(pfn), + .rdx = page_to_phys(pamt_pages[0]), + .r8 = page_to_phys(pamt_pages[1]), + }; + + return seamcall(TDH_PHYMEM_PAMT_ADD, &args); +} + +/* Remove PAMT backing for the 2MB region surrounding the given pfn. */ +static u64 tdh_phymem_pamt_remove(kvm_pfn_t pfn, struct page **pamt_pages) +{ + struct tdx_module_args args = { + .rcx = pamt_2mb_arg(pfn), + }; + u64 ret; + + ret = seamcall_ret(TDH_PHYMEM_PAMT_REMOVE, &args); + if (ret) + return ret; + + /* Copy PAMT pages out of the struct per the TDX ABI */ + pamt_pages[0] = phys_to_page(args.rdx); + pamt_pages[1] = phys_to_page(args.r8); + + return 0; +} + +/* + * Allocate DPAMT memory for the 2MB aligned region surrounding + * the given page. + * + * Only call this when the pfn is known not to already have Dynamic + * PAMT pages in the TDX module for it. + * + * Effectively it is not (yet) like a get, and more like a manual + * manipulation of the DPAMT backing for the 2MB aligned range + * covered by the pfn. + */ +static int __tdx_pamt_get(kvm_pfn_t pfn) +{ + struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT]; + u64 tdx_status; + int ret; + + if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) + return 0; + + ret = alloc_pamt_array(pamt_pages); + if (ret) + return ret; + + tdx_status = tdh_phymem_pamt_add(pfn, pamt_pages); + if (tdx_status != TDX_SUCCESS) { + ret = -EIO; + goto out_free; + } + + return 0; + +out_free: + free_pamt_array(pamt_pages); + + return ret; +} + +/* + * Free DPAMT memory for the 2MB aligned region surrounding the + * given page. Only call this when the pfn is known to already + * have DPAMT pages in the TDX module for it, and no other pfns + * in the aligned 2MB physical region still need it. + * + * Don't make multiple calls concurrently of __tdx_pamt_get/put(), + * as there is no protections from races. + * + * Effectively it is not (yet) like a refcounted put, and more like a + * manual manipulation of the DPAMT backing for the 2MB aligned + * range covered by the pfn. + */ +static void __tdx_pamt_put(kvm_pfn_t pfn) +{ + struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT] = {}; + u64 tdx_status; + + if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) + return; + + tdx_status = tdh_phymem_pamt_remove(pfn, pamt_pages); + + /* + * Don't free pamt_pages as it could hold garbage when + * tdh_phymem_pamt_remove() fails. Don't panic/BUG_ON(), as + * there is no risk of data corruption, but do yell loudly as + * failure indicates a kernel bug, memory is being leaked, and + * the dangling PAMT entry may cause future operations to fail. + */ + if (WARN_ON_ONCE(tdx_status != TDX_SUCCESS)) + return; + + free_pamt_array(pamt_pages); +} + +/* + * Return a page that can be gifted to the TDX module for use as a "control" + * page, i.e. pages that are used for control structures for a given TDX + * guest, and thus obtain TDX protections, including PAMT tracking. + * + * This function is currently only safe to call once. And not safe to call + * if __tdx_pamt_get() is called before or after. + */ +static __maybe_unused struct page *__tdx_alloc_control_page(void) +{ + struct page *page; + + page = alloc_page(GFP_KERNEL_ACCOUNT); + if (!page) + return NULL; + + if (__tdx_pamt_get(page_to_pfn(page))) { + __free_page(page); + return NULL; + } + + return page; +} + +/* + * Free a page that was gifted to the TDX module for use as a control + * page. After this, the page is no longer protected by TDX. + * + * Like __tdx_pamt_put(), this is currently only safe to call this when + * a page is already known to have DPAMT pages in the TDX module for + * it, and no other pages in the aligned 2MB physical region will + * still need the backing. + */ +static __maybe_unused void __tdx_free_control_page(struct page *page) +{ + if (!page) + return; + + __tdx_pamt_put(page_to_pfn(page)); + __free_page(page); +} + void tdx_sys_disable(void) { struct tdx_module_args args = {}; diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index bdfd0e1e337ac0..a886c54decaadc 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -48,6 +48,8 @@ #define TDH_SYS_CONFIG 45 #define TDH_SYS_SHUTDOWN 52 #define TDH_SYS_UPDATE 53 +#define TDH_PHYMEM_PAMT_ADD 58 +#define TDH_PHYMEM_PAMT_REMOVE 59 #define TDH_SYS_DISABLE 69 /* From 5386c5288001f00e5f2019d5f7b50c97d62f3a7b Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:34 -0700 Subject: [PATCH 0204/2286] x86/virt/tdx: Allocate refcounts for Dynamic PAMT memory The PAMT memory holds metadata for all possible TDX protected memory. Each physical address range is covered by PAMT entries at three levels (1GB, 2MB, 4KB). With Dynamic PAMT (DPAMT), the 4KB level of PAMT is allocated on demand. The kernel supplies the TDX module with page pairs to store the 4KB level entries, which cover 2MB of host physical memory. The kernel must provide this page pair before using pages from the range for TDX. If this is not done, SEAMCALLs that give the pages to be protected by the TDX module will fail. Allocate reference counters for every 2MB range to track TDX memory usage. This can be used to handle concurrent get/put callers, in order to accurately determine when the dynamic 4KB level of DPAMT needs to be allocated and when it can be freed. This allocation will currently consume 2MB for every 1TB of address space from 0 to max_pfn. The allocation size will depend on how the RAM is physically laid out. In a worst case scenario where the entire 52 bit address space is covered this would be 8GB. Then the DPAMT refcount allocations could hypothetically cause the savings from DPAMT to go negative on exotic platforms with sparse, small amounts of memory. Future changes could reduce this refcount overhead to be only allocating refcounts for physical ranges that contain memory that TDX can use. However, this is left for future work. AI was used under supervision to collect/apply feedback, review code and workshop logs. Based on a patch originally by Kiryl Shutsemau. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Chao Gao Reviewed-by: Yan Zhao Reviewed-by: Tony Lindgren Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-5-rick.p.edgecombe@intel.com --- arch/x86/virt/vmx/tdx/tdx.c | 53 ++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index a9c5b6f5384ea6..305289bd673be2 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,14 @@ static DEFINE_PER_CPU(bool, tdx_lp_initialized); static struct tdmr_info_list tdx_tdmr_list; +/* + * On a machine with DPAMT, the kernel maintains a reference counter + * for every 2MB range. The counter indicates how many users there are for + * the DPAMT at the 2MB range. The kernel allocates DPAMT refcounts at + * initialization. + */ +static atomic_t *dpamt_refcounts; + /* All TDX-usable memory regions. Protected by mem_hotplug_lock. */ static LIST_HEAD(tdx_memlist); @@ -255,6 +264,42 @@ static struct syscore tdx_syscore = { .ops = &tdx_syscore_ops, }; +/* + * Allocate DPAMT reference counters for all physical memory. + * + * It consumes 2MB for every 1TB of physical memory. + */ +static __init int init_dpamt_refcounts(void) +{ + size_t size = DIV_ROUND_UP(max_pfn, PTRS_PER_PTE) * sizeof(*dpamt_refcounts); + + if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) + return 0; + + dpamt_refcounts = vzalloc(size); + if (!dpamt_refcounts) + return -ENOMEM; + + return 0; +} + +static __init void free_dpamt_refcounts(void) +{ + if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) + return; + + vfree(dpamt_refcounts); + dpamt_refcounts = NULL; +} + +static __maybe_unused atomic_t *tdx_find_dpamt_refcount(unsigned long pfn) +{ + /* Find which PMD a PFN is in. */ + unsigned long index = pfn >> (PMD_SHIFT - PAGE_SHIFT); + + return &dpamt_refcounts[index]; +} + /* * Add a memory region as a TDX memory block. The caller must make sure * all memory regions are added in address ascending order and don't @@ -1155,10 +1200,14 @@ static __init int init_tdx_module(void) */ get_online_mems(); - ret = build_tdx_memlist(&tdx_memlist); + ret = init_dpamt_refcounts(); if (ret) goto out_put_tdxmem; + ret = build_tdx_memlist(&tdx_memlist); + if (ret) + goto err_free_dpamt_refcounts; + /* Allocate enough space for constructing TDMRs */ ret = alloc_tdmr_list(&tdx_tdmr_list, &tdx_sysinfo.tdmr); if (ret) @@ -1208,6 +1257,8 @@ static __init int init_tdx_module(void) free_tdmr_list(&tdx_tdmr_list); err_free_tdxmem: free_tdx_memlist(&tdx_memlist); +err_free_dpamt_refcounts: + free_dpamt_refcounts(); goto out_put_tdxmem; } From eca9d4a3f1f820439453f07ce700d316c398520e Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:35 -0700 Subject: [PATCH 0205/2286] x86/virt/tdx: Handle multiple callers in tdx_pamt_get/put() __tdx_pamt_get()/__tdx_pamt_put() unconditionally add or remove Dynamic PAMT (DPAMT) backing for the 2MB region covering the passed page. However, multiple callers can add or remove 4KB pages that fall within the same 2MB region and in that scenario only a single PAMT entry is required. Make the helpers handle only adding/removing DPAMT backing when required, by refcounting each 2MB range. Gate the actual DPAMT add and remove on refcount transitions (0->1 and 1->0). Serialize the refcount check and SEAMCALL with a global spinlock so the read-decide-act sequence is atomic. This also avoids TDX module BUSY errors, as the DPAMT add and remove SEAMCALLs take internal TDX module locks for the 2MB ranges of the specified PFN and the PAMT page pair PFNs. So simultaneous attempts on the same 2MB ranges of the PFNs would otherwise encounter an error, which would not be handleable in the put case. The lock is global and heavyweight. Use simple conditional logic to keep correctness obvious. This will be optimized in a later change. The dpamt_refcounts[] are atomic_t's. They do not strictly need to be because all access is protected by pamt_lock. The overhead of an atomic_t in this situation is minuscule compared to the global lock. Leave the atomic_t in place to enable future optimization with minimal churn. Since the DPAMT helpers are broadly functional now, drop the "__" to rename them tdx_pamt_get/put() and tdx_alloc/free_control_page(). Export them for use in KVM in subsequent changes. AI was used under supervision to collect/apply feedback, split patches, review code and workshop logs. Based on a patch originally by Kiryl Shutsemau. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Chao Gao Reviewed-by: Yan Zhao Reviewed-by: Tony Lindgren Reviewed-by: Nikolay Borisov Reviewed-by: Dave Hansen Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-6-rick.p.edgecombe@intel.com --- arch/x86/include/asm/tdx.h | 6 +++ arch/x86/virt/vmx/tdx/tdx.c | 102 ++++++++++++++++++++---------------- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index d414064436221f..f7442ad20e46d6 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -120,12 +120,18 @@ static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinf bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo); +int tdx_pamt_get(kvm_pfn_t pfn); +void tdx_pamt_put(kvm_pfn_t pfn); + int tdx_guest_keyid_alloc(void); u32 tdx_get_nr_guest_keyids(void); void tdx_guest_keyid_free(unsigned int keyid); void tdx_quirk_reset_paddr(unsigned long base, unsigned long size); +struct page *tdx_alloc_control_page(void); +void tdx_free_control_page(struct page *page); + struct tdx_td { /* TD root structure: */ struct page *tdr_page; diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 305289bd673be2..c347600a0aabb2 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -292,7 +292,7 @@ static __init void free_dpamt_refcounts(void) dpamt_refcounts = NULL; } -static __maybe_unused atomic_t *tdx_find_dpamt_refcount(unsigned long pfn) +static atomic_t *tdx_find_dpamt_refcount(unsigned long pfn) { /* Find which PMD a PFN is in. */ unsigned long index = pfn >> (PMD_SHIFT - PAGE_SHIFT); @@ -2097,7 +2097,7 @@ static u64 pamt_2mb_arg(kvm_pfn_t pfn) return hpa_2mb | TDX_PS_2M; } -/* Add PAMT backing for the 2MB region surrounding the given pfn. */ +/* Add DPAMT backing for the 2MB region surrounding the given pfn. */ static u64 tdh_phymem_pamt_add(kvm_pfn_t pfn, struct page **pamt_pages) { struct tdx_module_args args = { @@ -2109,7 +2109,7 @@ static u64 tdh_phymem_pamt_add(kvm_pfn_t pfn, struct page **pamt_pages) return seamcall(TDH_PHYMEM_PAMT_ADD, &args); } -/* Remove PAMT backing for the 2MB region surrounding the given pfn. */ +/* Remove DPAMT backing for the 2MB region surrounding the given pfn. */ static u64 tdh_phymem_pamt_remove(kvm_pfn_t pfn, struct page **pamt_pages) { struct tdx_module_args args = { @@ -2128,20 +2128,14 @@ static u64 tdh_phymem_pamt_remove(kvm_pfn_t pfn, struct page **pamt_pages) return 0; } -/* - * Allocate DPAMT memory for the 2MB aligned region surrounding - * the given page. - * - * Only call this when the pfn is known not to already have Dynamic - * PAMT pages in the TDX module for it. - * - * Effectively it is not (yet) like a get, and more like a manual - * manipulation of the DPAMT backing for the 2MB aligned range - * covered by the pfn. - */ -static int __tdx_pamt_get(kvm_pfn_t pfn) +/* Serializes adding/removing DPAMT memory */ +static DEFINE_SPINLOCK(dpamt_lock); + +/* Bump DPAMT refcount for the given pfn and allocate DPAMT backing if needed. */ +int tdx_pamt_get(kvm_pfn_t pfn) { struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT]; + atomic_t *dpamt_refcount; u64 tdx_status; int ret; @@ -2152,41 +2146,59 @@ static int __tdx_pamt_get(kvm_pfn_t pfn) if (ret) return ret; + dpamt_refcount = tdx_find_dpamt_refcount(pfn); + + spin_lock(&dpamt_lock); + + /* + * If the DPAMT entry is already added (i.e. refcount >= 1), + * then just increment the refcount. + */ + if (atomic_inc_not_zero(dpamt_refcount)) + goto out_free; + + /* Try to add the PAMT page and take the refcount 0->1. */ tdx_status = tdh_phymem_pamt_add(pfn, pamt_pages); - if (tdx_status != TDX_SUCCESS) { + if (WARN_ON_ONCE(tdx_status != TDX_SUCCESS)) { ret = -EIO; goto out_free; } + atomic_set(dpamt_refcount, 1); + spin_unlock(&dpamt_lock); return 0; out_free: + spin_unlock(&dpamt_lock); free_pamt_array(pamt_pages); return ret; } +EXPORT_SYMBOL_FOR_KVM(tdx_pamt_get); -/* - * Free DPAMT memory for the 2MB aligned region surrounding the - * given page. Only call this when the pfn is known to already - * have DPAMT pages in the TDX module for it, and no other pfns - * in the aligned 2MB physical region still need it. - * - * Don't make multiple calls concurrently of __tdx_pamt_get/put(), - * as there is no protections from races. - * - * Effectively it is not (yet) like a refcounted put, and more like a - * manual manipulation of the DPAMT backing for the 2MB aligned - * range covered by the pfn. - */ -static void __tdx_pamt_put(kvm_pfn_t pfn) +/* Drop DPAMT refcount for the given pfn and free DPAMT backing if needed. */ +void tdx_pamt_put(kvm_pfn_t pfn) { struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT] = {}; + atomic_t *dpamt_refcount; u64 tdx_status; if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) return; + dpamt_refcount = tdx_find_dpamt_refcount(pfn); + + spin_lock(&dpamt_lock); + /* + * If there is more than 1 reference on the DPAMT entry, don't + * remove it yet. Just decrement the refcount. + */ + if (atomic_read(dpamt_refcount) > 1) { + atomic_dec(dpamt_refcount); + goto out_unlock; + } + + /* Try to remove the pamt page and take the refcount 1->0. */ tdx_status = tdh_phymem_pamt_remove(pfn, pamt_pages); /* @@ -2194,23 +2206,26 @@ static void __tdx_pamt_put(kvm_pfn_t pfn) * tdh_phymem_pamt_remove() fails. Don't panic/BUG_ON(), as * there is no risk of data corruption, but do yell loudly as * failure indicates a kernel bug, memory is being leaked, and - * the dangling PAMT entry may cause future operations to fail. + * the dangling DPAMT entry may cause future operations to fail. */ if (WARN_ON_ONCE(tdx_status != TDX_SUCCESS)) - return; + goto out_unlock; + atomic_set(dpamt_refcount, 0); + spin_unlock(&dpamt_lock); free_pamt_array(pamt_pages); + return; +out_unlock: + spin_unlock(&dpamt_lock); } +EXPORT_SYMBOL_FOR_KVM(tdx_pamt_put); /* * Return a page that can be gifted to the TDX module for use as a "control" * page, i.e. pages that are used for control structures for a given TDX - * guest, and thus obtain TDX protections, including PAMT tracking. - * - * This function is currently only safe to call once. And not safe to call - * if __tdx_pamt_get() is called before or after. + * guest, and thus obtain TDX protections, including DPAMT tracking. */ -static __maybe_unused struct page *__tdx_alloc_control_page(void) +struct page *tdx_alloc_control_page(void) { struct page *page; @@ -2218,31 +2233,28 @@ static __maybe_unused struct page *__tdx_alloc_control_page(void) if (!page) return NULL; - if (__tdx_pamt_get(page_to_pfn(page))) { + if (tdx_pamt_get(page_to_pfn(page))) { __free_page(page); return NULL; } return page; } +EXPORT_SYMBOL_FOR_KVM(tdx_alloc_control_page); /* * Free a page that was gifted to the TDX module for use as a control * page. After this, the page is no longer protected by TDX. - * - * Like __tdx_pamt_put(), this is currently only safe to call this when - * a page is already known to have DPAMT pages in the TDX module for - * it, and no other pages in the aligned 2MB physical region will - * still need the backing. */ -static __maybe_unused void __tdx_free_control_page(struct page *page) +void tdx_free_control_page(struct page *page) { if (!page) return; - __tdx_pamt_put(page_to_pfn(page)); + tdx_pamt_put(page_to_pfn(page)); __free_page(page); } +EXPORT_SYMBOL_FOR_KVM(tdx_free_control_page); void tdx_sys_disable(void) { From 86305da521d1dcf44cb2a61140a3e78f390dd873 Mon Sep 17 00:00:00 2001 From: "Kirill A. Shutemov" Date: Fri, 4 Sep 2026 14:58:36 -0700 Subject: [PATCH 0206/2286] KVM/TDX: Allocate PAMT memory for TD and vCPU control structures Use control page helpers for allocating and freeing TD control structures, such that these operations can work for Dynamic PAMT. The TDX module tracks some state for each page of physical memory that it might use. It calls this state the PAMT. It includes separate state for each page size a physical page could be utilized at within the TDX module (1GB, 2MB, 4KB). In Dynamic PAMT, only the 4KB page size state is allocated dynamically. So the kernel must ensure PAMT backing is installed for any 4KB page being gifted to the TDX module, and must tear down the backing when all associated gifted pages are reclaimed. TD scoped control pages (TDR, TDCS) and vCPU scoped control pages (TDVPR, TDCX) are all handed to the TDX module at 4KB page size and are therefore subject to this requirement. Replace the raw alloc_page()/__free_page() calls for these pages with tdx_alloc/free_control_page(). Switching between special Dynamic PAMT operations or normal page alloc/free operations is handled internally in tdx_alloc/free_control_page(). So don't check for Dynamic PAMT around these calls. Just call them unconditionally. Similarly, drop the NULL checks before freeing, as tdx_free_control_page() handles NULL internally. No functional change intended when DPAMT is not in use. [sean: handle alloc+free+reclaim in one patch] [rick: enhance log, reviewing, rebase, with help from AI tooling] Signed-off-by: Kirill A. Shutemov Signed-off-by: Sean Christopherson Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Chao Gao Reviewed-by: Yan Zhao Reviewed-by: Tony Lindgren Reviewed-by: Nikolay Borisov Reviewed-by: Dave Hansen Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Acked-by: Sean Christopherson Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-7-rick.p.edgecombe@intel.com --- arch/x86/kvm/vmx/tdx.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index b272c20586a744..3592596b5649a9 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -362,7 +362,7 @@ static void tdx_reclaim_control_page(struct page *ctrl_page) if (tdx_reclaim_page(ctrl_page)) return; - __free_page(ctrl_page); + tdx_free_control_page(ctrl_page); } struct tdx_flush_vp_arg { @@ -589,7 +589,7 @@ static void tdx_reclaim_td_control_pages(struct kvm *kvm) tdx_quirk_reset_paddr(page_to_phys(kvm_tdx->td.tdr_page), PAGE_SIZE); - __free_page(kvm_tdx->td.tdr_page); + tdx_free_control_page(kvm_tdx->td.tdr_page); kvm_tdx->td.tdr_page = NULL; } @@ -2456,7 +2456,7 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params, ret = -ENOMEM; - tdr_page = alloc_page(GFP_KERNEL_ACCOUNT); + tdr_page = tdx_alloc_control_page(); if (!tdr_page) goto free_hkid; @@ -2469,7 +2469,7 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params, goto free_tdr; for (i = 0; i < kvm_tdx->td.tdcs_nr_pages; i++) { - tdcs_pages[i] = alloc_page(GFP_KERNEL_ACCOUNT); + tdcs_pages[i] = tdx_alloc_control_page(); if (!tdcs_pages[i]) goto free_tdcs; } @@ -2587,10 +2587,8 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params, teardown: /* Only free pages not yet added, so start at 'i' */ for (; i < kvm_tdx->td.tdcs_nr_pages; i++) { - if (tdcs_pages[i]) { - __free_page(tdcs_pages[i]); - tdcs_pages[i] = NULL; - } + tdx_free_control_page(tdcs_pages[i]); + tdcs_pages[i] = NULL; } if (!kvm_tdx->td.tdcs_pages) kfree(tdcs_pages); @@ -2605,16 +2603,13 @@ static int __tdx_td_init(struct kvm *kvm, struct td_params *td_params, free_cpumask_var(packages); free_tdcs: - for (i = 0; i < kvm_tdx->td.tdcs_nr_pages; i++) { - if (tdcs_pages[i]) - __free_page(tdcs_pages[i]); - } + for (i = 0; i < kvm_tdx->td.tdcs_nr_pages; i++) + tdx_free_control_page(tdcs_pages[i]); kfree(tdcs_pages); kvm_tdx->td.tdcs_pages = NULL; free_tdr: - if (tdr_page) - __free_page(tdr_page); + tdx_free_control_page(tdr_page); kvm_tdx->td.tdr_page = NULL; free_hkid: @@ -2948,7 +2943,7 @@ static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx) int ret, i; u64 err; - page = alloc_page(GFP_KERNEL_ACCOUNT); + page = tdx_alloc_control_page(); if (!page) return -ENOMEM; tdx->vp.tdvpr_page = page; @@ -2968,7 +2963,7 @@ static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx) } for (i = 0; i < kvm_tdx->td.tdcx_nr_pages; i++) { - page = alloc_page(GFP_KERNEL_ACCOUNT); + page = tdx_alloc_control_page(); if (!page) { ret = -ENOMEM; goto free_tdcx; @@ -2990,7 +2985,7 @@ static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx) * method, but the rest are freed here. */ for (; i < kvm_tdx->td.tdcx_nr_pages; i++) { - __free_page(tdx->vp.tdcx_pages[i]); + tdx_free_control_page(tdx->vp.tdcx_pages[i]); tdx->vp.tdcx_pages[i] = NULL; } return -EIO; @@ -3018,16 +3013,14 @@ static int tdx_td_vcpu_init(struct kvm_vcpu *vcpu, u64 vcpu_rcx) free_tdcx: for (i = 0; i < kvm_tdx->td.tdcx_nr_pages; i++) { - if (tdx->vp.tdcx_pages[i]) - __free_page(tdx->vp.tdcx_pages[i]); + tdx_free_control_page(tdx->vp.tdcx_pages[i]); tdx->vp.tdcx_pages[i] = NULL; } kfree(tdx->vp.tdcx_pages); tdx->vp.tdcx_pages = NULL; free_tdvpr: - if (tdx->vp.tdvpr_page) - __free_page(tdx->vp.tdvpr_page); + tdx_free_control_page(tdx->vp.tdvpr_page); tdx->vp.tdvpr_page = NULL; tdx->vp.tdvpr_pa = 0; From 2c8ca2ba9bb6aa277ec2af8e532f337ef12eb008 Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:37 -0700 Subject: [PATCH 0207/2286] x86/virt/tdx: Add APIs to support Dynamic PAMT ops from KVM's fault path When handling an EPT violation, KVM holds a spinlock while manipulating the EPT. Before entering the spinlock it doesn't know how many EPT page tables will need to be installed or whether a huge page will be used. For this reason it allocates a worst case number of page tables that it might need as part of servicing the EPT violation. Under Dynamic PAMT (DPAMT) these pre-allocated pages will potentially need to have DPAMT backing pages installed for them. KVM already has helpers to manage topping up page caches before taking the MMU lock, but they cannot be passed from KVM to arch/x86 code. The problem of how and when to install the DPAMT backing pages for the pages given to the TDX module during the fault path has had a lot of design attempts. - Extracting KVM's MMU caches requires too much inlined code added to headers. - A few varieties of installing DPAMT backing when allocating the S-EPT page tables. (see links) - Using mempool_t to transfer the pages between KVM and arch/x86 doesn't work because the component is designed more around maintaining a pool of pages, rather than topping up a continually drained cache. So don't do these as they all had various problems. Instead just create a small simple data structure to use for handing a pre-allocated list of pages between KVM and arch/x86 code. Model this on KVM's existing MMU memory caches. Add a tdx_pamt_cache arg to tdx_pamt_get() so it can draw pages from a cache when needed. Not all DPAMT page installations will happen under spinlock, for example TD and vCPU scoped control pages. So have tdx_pamt_get() maintain the existing behavior of allocating from the page allocator when NULL is passed for the struct tdx_pamt_cache arg. This prevents excess allocations for cases where it can be avoided. Export the new helpers for KVM. AI was used under supervision to review code and workshop logs. Co-developed-by: Sean Christopherson Signed-off-by: Sean Christopherson Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Binbin Wu Reviewed-by: Chao Gao Reviewed-by: Yan Zhao Reviewed-by: Tony Lindgren Reviewed-by: Nikolay Borisov Reviewed-by: Dave Hansen Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://lore.kernel.org/kvm/aXENNKjAKTM9UJNH@google.com/ Link: https://lore.kernel.org/kvm/20260129011517.3545883-20-seanjc@google.com/ Link: https://lore.kernel.org/kvm/aYW5CbUvZrLogsWF@yzhao56-desk.sh.intel.com/ Link: https://patch.msgid.link/20260904215841.303070-8-rick.p.edgecombe@intel.com --- arch/x86/include/asm/tdx.h | 16 +++++++++- arch/x86/virt/vmx/tdx/tdx.c | 61 ++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index f7442ad20e46d6..8c7839d61296d7 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -120,7 +120,21 @@ static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinf bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo); -int tdx_pamt_get(kvm_pfn_t pfn); +/* Simple structure for pre-allocating DPAMT pages outside of spinlocks. */ +struct tdx_pamt_cache { + struct list_head page_list; + int cnt; +}; + +static inline void tdx_init_pamt_cache(struct tdx_pamt_cache *cache) +{ + INIT_LIST_HEAD(&cache->page_list); + cache->cnt = 0; +} + +void tdx_free_pamt_cache(struct tdx_pamt_cache *cache); +int tdx_topup_pamt_cache(struct tdx_pamt_cache *cache, unsigned long npages); +int tdx_pamt_get(kvm_pfn_t pfn, struct tdx_pamt_cache *cache); void tdx_pamt_put(kvm_pfn_t pfn); int tdx_guest_keyid_alloc(void); diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index c347600a0aabb2..39865a2da5822a 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -2050,12 +2050,33 @@ bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo) return false; } -static int alloc_pamt_array(struct page **pamt_pages) +static struct page *tdx_alloc_page_pamt_cache(struct tdx_pamt_cache *cache) +{ + struct page *page; + + page = list_first_entry_or_null(&cache->page_list, struct page, lru); + if (page) { + list_del(&page->lru); + cache->cnt--; + } + + return page; +} + +static struct page *alloc_dpamt_page(struct tdx_pamt_cache *cache) +{ + if (cache) + return tdx_alloc_page_pamt_cache(cache); + + return alloc_page(GFP_KERNEL_ACCOUNT); +} + +static int alloc_pamt_array(struct page **pamt_pages, struct tdx_pamt_cache *cache) { int i, j; for (i = 0; i < TDX_DPAMT_ENTRY_PAGE_CNT; i++) { - pamt_pages[i] = alloc_page(GFP_KERNEL_ACCOUNT); + pamt_pages[i] = alloc_dpamt_page(cache); if (!pamt_pages[i]) goto err; } @@ -2132,7 +2153,7 @@ static u64 tdh_phymem_pamt_remove(kvm_pfn_t pfn, struct page **pamt_pages) static DEFINE_SPINLOCK(dpamt_lock); /* Bump DPAMT refcount for the given pfn and allocate DPAMT backing if needed. */ -int tdx_pamt_get(kvm_pfn_t pfn) +int tdx_pamt_get(kvm_pfn_t pfn, struct tdx_pamt_cache *cache) { struct page *pamt_pages[TDX_DPAMT_ENTRY_PAGE_CNT]; atomic_t *dpamt_refcount; @@ -2142,7 +2163,7 @@ int tdx_pamt_get(kvm_pfn_t pfn) if (!tdx_supports_dynamic_pamt(&tdx_sysinfo)) return 0; - ret = alloc_pamt_array(pamt_pages); + ret = alloc_pamt_array(pamt_pages, cache); if (ret) return ret; @@ -2220,6 +2241,36 @@ void tdx_pamt_put(kvm_pfn_t pfn) } EXPORT_SYMBOL_FOR_KVM(tdx_pamt_put); +void tdx_free_pamt_cache(struct tdx_pamt_cache *cache) +{ + struct page *page; + + while ((page = tdx_alloc_page_pamt_cache(cache))) + __free_page(page); +} +EXPORT_SYMBOL_FOR_KVM(tdx_free_pamt_cache); + +int tdx_topup_pamt_cache(struct tdx_pamt_cache *cache, unsigned long npages) +{ + if (WARN_ON_ONCE(!tdx_supports_dynamic_pamt(&tdx_sysinfo))) + return 0; + + npages *= TDX_DPAMT_ENTRY_PAGE_CNT; + + while (cache->cnt < npages) { + struct page *page = alloc_page(GFP_KERNEL_ACCOUNT); + + if (!page) + return -ENOMEM; + + list_add(&page->lru, &cache->page_list); + cache->cnt++; + } + + return 0; +} +EXPORT_SYMBOL_FOR_KVM(tdx_topup_pamt_cache); + /* * Return a page that can be gifted to the TDX module for use as a "control" * page, i.e. pages that are used for control structures for a given TDX @@ -2233,7 +2284,7 @@ struct page *tdx_alloc_control_page(void) if (!page) return NULL; - if (tdx_pamt_get(page_to_pfn(page))) { + if (tdx_pamt_get(page_to_pfn(page), NULL)) { __free_page(page); return NULL; } From d7fe610e6a6638c3944b9e553b426efd527ec502 Mon Sep 17 00:00:00 2001 From: "Kirill A. Shutemov" Date: Fri, 4 Sep 2026 14:58:38 -0700 Subject: [PATCH 0208/2286] KVM/TDX: Get/put PAMT pages when (un)mapping private memory Add Dynamic PAMT support to KVM's S-EPT MMU by "getting" a PAMT page when adding guest memory (PAGE.ADD or PAGE.AUG), and "putting" the page when removing guest memory (PAGE.REMOVE). To access the per-vCPU PAMT caches without plumbing @vcpu throughout the TDP MMU, begrudgingly use kvm_get_running_vcpu() to get the vCPU, and bug the VM if KVM attempts to set an S-EPT leaf without an active vCPU. KVM only supports creating _new_ mappings in page (pre)fault paths, all of which require an active vCPU. The PAMT memory holds metadata for TDX protected memory. With Dynamic PAMT, PAMT_4K is allocated on demand. The kernel supplies the TDX module with a few pages that cover 2MB of host physical memory. Releases are balanced via tdx_pamt_put(): every control-page free goes through tdx_free_control_page(), and guest data pages are put directly on the successful tdh_mem_page_remove() path and in the tdx_mem_page_add/aug() error path. Co-developed-by: Sean Christopherson [rick: enhance log, reviewing, rebase, with help from AI tooling] Co-developed-by: Rick Edgecombe Signed-off-by: Kirill A. Shutemov Signed-off-by: Sean Christopherson Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Tony Lindgren Reviewed-by: Dave Hansen Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Acked-by: Sean Christopherson Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-9-rick.p.edgecombe@intel.com --- arch/x86/include/asm/kvm-x86-ops.h | 1 + arch/x86/include/asm/kvm_host.h | 1 + arch/x86/kvm/mmu/mmu.c | 4 ++ arch/x86/kvm/vmx/tdx.c | 64 ++++++++++++++++++++++++++---- arch/x86/kvm/vmx/tdx.h | 2 + arch/x86/virt/vmx/tdx/tdx.c | 1 + 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h index e213c9ae3e301d..5c358c40eae8a1 100644 --- a/arch/x86/include/asm/kvm-x86-ops.h +++ b/arch/x86/include/asm/kvm-x86-ops.h @@ -99,6 +99,7 @@ KVM_X86_OP_OPTIONAL_RET0(tdp_has_smep) KVM_X86_OP(load_mmu_pgd) KVM_X86_OP_OPTIONAL_RET0(set_external_spte) KVM_X86_OP_OPTIONAL(free_external_spt) +KVM_X86_OP_OPTIONAL_RET0(topup_external_cache) KVM_X86_OP(has_wbinvd_exit) KVM_X86_OP(get_l2_tsc_offset) KVM_X86_OP(get_l2_tsc_multiplier) diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 683bb8bf43a945..57d37491c7c09f 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -1644,6 +1644,7 @@ struct kvm_x86_ops { /* Update external page tables for page table about to be freed. */ void (*free_external_spt)(struct kvm *kvm, struct kvm_mmu_page *sp); + int (*topup_external_cache)(struct kvm_vcpu *vcpu, int min_nr_spts); bool (*has_wbinvd_exit)(void); diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c index 064ecc33b92670..2816861ca279cd 100644 --- a/arch/x86/kvm/mmu/mmu.c +++ b/arch/x86/kvm/mmu/mmu.c @@ -617,6 +617,10 @@ static int mmu_topup_memory_caches(struct kvm_vcpu *vcpu, bool maybe_indirect) PT64_ROOT_MAX_LEVEL); if (r) return r; + + r = kvm_x86_call(topup_external_cache)(vcpu, PT64_ROOT_MAX_LEVEL); + if (r) + return r; } r = kvm_mmu_topup_memory_cache(&vcpu->arch.mmu_shadow_page_cache, PT64_ROOT_MAX_LEVEL); diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 3592596b5649a9..7c79f4d4bf99a4 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -681,6 +681,8 @@ int tdx_vcpu_create(struct kvm_vcpu *vcpu) if (!irqchip_split(vcpu->kvm)) return -EINVAL; + tdx_init_pamt_cache(&tdx->pamt_cache); + fpstate_set_confidential(&vcpu->arch.guest_fpu); vcpu->arch.apic->guest_apic_protected = true; INIT_LIST_HEAD(&tdx->vt.pi_wakeup_list); @@ -866,6 +868,8 @@ void tdx_vcpu_free(struct kvm_vcpu *vcpu) struct vcpu_tdx *tdx = to_tdx(vcpu); int i; + tdx_free_pamt_cache(&tdx->pamt_cache); + if (vcpu->cpu != -1) { KVM_BUG_ON(tdx->state == VCPU_TD_STATE_INITIALIZED, vcpu->kvm); tdx_flush_vp_on_cpu(vcpu); @@ -1618,6 +1622,17 @@ void tdx_load_mmu_pgd(struct kvm_vcpu *vcpu, hpa_t root_hpa, int pgd_level) td_vmcs_write64(to_tdx(vcpu), SHARED_EPT_POINTER, root_hpa); } +static int tdx_topup_external_pamt_cache(struct kvm_vcpu *vcpu, int min_nr_spts) +{ + /* + * Minus one page to exclude the root SPT, but plus one page for a + * possible 4KB private mapping. + */ + min_nr_spts += -1 + 1; + + return tdx_topup_pamt_cache(&to_tdx(vcpu)->pamt_cache, min_nr_spts); +} + static int tdx_mem_page_add(struct kvm *kvm, gfn_t gfn, enum pg_level level, kvm_pfn_t pfn) { @@ -1676,16 +1691,28 @@ static struct page *tdx_spte_to_sept_pt(struct kvm *kvm, gfn_t gfn, static int tdx_sept_map_nonleaf_spte(struct kvm *kvm, gfn_t gfn, enum pg_level level, u64 new_spte) { + struct kvm_vcpu *vcpu = kvm_get_running_vcpu(); gpa_t gpa = gfn_to_gpa(gfn); u64 err, entry, level_state; struct page *sept_pt; + int ret; + + if (KVM_BUG_ON(!vcpu, kvm)) + return -EIO; sept_pt = tdx_spte_to_sept_pt(kvm, gfn, new_spte, level); if (!sept_pt) return -EIO; + ret = tdx_pamt_get(page_to_pfn(sept_pt), &to_tdx(vcpu)->pamt_cache); + if (KVM_BUG_ON(ret, kvm)) + return ret; + err = tdh_mem_sept_add(&to_kvm_tdx(kvm)->td, gpa, level, sept_pt, &entry, &level_state); + if (err) + tdx_pamt_put(page_to_pfn(sept_pt)); + if (unlikely(tdx_operand_busy(err))) return -EBUSY; @@ -1698,8 +1725,13 @@ static int tdx_sept_map_nonleaf_spte(struct kvm *kvm, gfn_t gfn, static int tdx_sept_map_leaf_spte(struct kvm *kvm, gfn_t gfn, enum pg_level level, u64 new_spte) { + struct kvm_vcpu *vcpu = kvm_get_running_vcpu(); struct kvm_tdx *kvm_tdx = to_kvm_tdx(kvm); kvm_pfn_t pfn = spte_to_pfn(new_spte); + int ret; + + if (KVM_BUG_ON(!vcpu, kvm)) + return -EIO; /* TODO: handle large pages. */ if (KVM_BUG_ON(level != PG_LEVEL_4K, kvm)) @@ -1707,6 +1739,10 @@ static int tdx_sept_map_leaf_spte(struct kvm *kvm, gfn_t gfn, enum pg_level leve WARN_ON_ONCE((new_spte & VMX_EPT_RWX_MASK) != VMX_EPT_RWX_MASK); + ret = tdx_pamt_get(pfn, &to_tdx(vcpu)->pamt_cache); + if (KVM_BUG_ON(ret, kvm)) + return ret; + /* * Ensure pre_fault_allowed is read by kvm_arch_vcpu_pre_fault_memory() * before kvm_tdx->state. Userspace must not be allowed to pre-fault @@ -1719,10 +1755,15 @@ static int tdx_sept_map_leaf_spte(struct kvm *kvm, gfn_t gfn, enum pg_level leve * If the TD isn't finalized/runnable, then userspace is initializing * the VM image via KVM_TDX_INIT_MEM_REGION; ADD the page to the TD. */ - if (unlikely(kvm_tdx->state != TD_STATE_RUNNABLE)) - return tdx_mem_page_add(kvm, gfn, level, pfn); + if (likely(kvm_tdx->state == TD_STATE_RUNNABLE)) + ret = tdx_mem_page_aug(kvm, gfn, level, pfn); + else + ret = tdx_mem_page_add(kvm, gfn, level, pfn); - return tdx_mem_page_aug(kvm, gfn, level, pfn); + if (ret) + tdx_pamt_put(pfn); + + return ret; } /* @@ -1819,6 +1860,7 @@ static int tdx_sept_remove_leaf_spte(struct kvm *kvm, gfn_t gfn, return -EIO; tdx_quirk_reset_paddr(PFN_PHYS(pfn), PAGE_SIZE); + tdx_pamt_put(pfn); return 0; } @@ -1862,6 +1904,8 @@ static int tdx_sept_set_private_spte(struct kvm *kvm, gfn_t gfn, u64 old_spte, */ static void tdx_sept_free_private_spt(struct kvm *kvm, struct kvm_mmu_page *sp) { + struct page *sept_pt = virt_to_page(sp->external_spt); + /* * KVM doesn't (yet) zap page table pages in mirror page table while * TD is active, though guest pages mapped in mirror page table could be @@ -1875,15 +1919,15 @@ static void tdx_sept_free_private_spt(struct kvm *kvm, struct kvm_mmu_page *sp) * the page to prevent the kernel from accessing the encrypted page. */ if (KVM_BUG_ON(is_hkid_assigned(to_kvm_tdx(kvm)), kvm) || - tdx_reclaim_page(virt_to_page(sp->external_spt))) + tdx_reclaim_page(sept_pt)) goto out; /* - * Immediately free the S-EPT page because RCU-time free is unnecessary - * after TDH.PHYMEM.PAGE.RECLAIM ensures there are no outstanding - * readers. + * Immediately free the S-EPT page as the TDX subsystem doesn't support + * freeing pages from RCU callbacks, and more importantly because + * TDH.PHYMEM.PAGE.RECLAIM ensures there are no outstanding readers. */ - free_page((unsigned long)sp->external_spt); + tdx_free_control_page(sept_pt); out: sp->external_spt = NULL; } @@ -3480,6 +3524,10 @@ int __init tdx_hardware_setup(void) vt_x86_ops.set_external_spte = tdx_sept_set_private_spte; vt_x86_ops.free_external_spt = tdx_sept_free_private_spt; + + if (tdx_supports_dynamic_pamt(tdx_sysinfo)) + vt_x86_ops.topup_external_cache = tdx_topup_external_pamt_cache; + vt_x86_ops.protected_apic_has_interrupt = tdx_protected_apic_has_interrupt; return 0; diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h index ac8323a68b163a..fd368e3ee060b6 100644 --- a/arch/x86/kvm/vmx/tdx.h +++ b/arch/x86/kvm/vmx/tdx.h @@ -72,6 +72,8 @@ struct vcpu_tdx { u64 map_gpa_next; u64 map_gpa_end; + + struct tdx_pamt_cache pamt_cache; }; void tdh_vp_rd_failed(struct vcpu_tdx *tdx, char *uclass, u32 field, u64 err); diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 39865a2da5822a..ff00ee6d5705af 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -2049,6 +2049,7 @@ bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo) /* To be enabled when kernel is ready. */ return false; } +EXPORT_SYMBOL_FOR_KVM(tdx_supports_dynamic_pamt); static struct page *tdx_alloc_page_pamt_cache(struct tdx_pamt_cache *cache) { From 1e3d7913532723b3cb73cc70d04e7bf7f6430fed Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:39 -0700 Subject: [PATCH 0209/2286] x86/virt/tdx: Enable Dynamic PAMT The Physical Address Metadata Table (PAMT) holds TDX metadata for physical memory and must be allocated by the kernel during TDX module initialization. Dynamic PAMT (DPAMT) is a TDX module feature that can reduce this memory use by allocating part of the PAMT dynamically. DPAMT's memory savings are significant enough to make it a good default configuration. So turn it on whenever the TDX module reports support. There is a TDX module bug where on unusual BIOS keyid configurations, the TDX module reports DPAMT as supported even when it can't handle it. Expect this not to be an issue in practice and don't attempt to work around it on the kernel side. The feature increases the kernel size by 2KB (when TDX is configured in the build). Based on a patch originally by Kiryl Shutsemau. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Tony Lindgren Reviewed-by: Vishal Annapurve Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://patch.msgid.link/20260904215841.303070-10-rick.p.edgecombe@intel.com --- arch/x86/include/asm/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx.c | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 8c7839d61296d7..e186dfe5bf8855 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -36,6 +36,7 @@ /* Bit definitions of TDX_FEATURES0 metadata field */ #define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1) #define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18) +#define TDX_FEATURES0_DYNAMIC_PAMT BIT_ULL(36) #ifndef __ASSEMBLER__ diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index ff00ee6d5705af..063574ed862544 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1031,6 +1031,8 @@ static __init int construct_tdmrs(struct list_head *tmb_list, return ret; } +#define TDX_SYS_CONFIG_DYNAMIC_PAMT BIT(16) + static __init int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid) { @@ -1059,6 +1061,12 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list, args.rcx = __pa(tdmr_pa_array); args.rdx = tdmr_list->nr_consumed_tdmrs; args.r8 = global_keyid; + + if (tdx_supports_dynamic_pamt(&tdx_sysinfo)) { + pr_info("Enable Dynamic PAMT\n"); + args.r8 |= TDX_SYS_CONFIG_DYNAMIC_PAMT; + } + ret = seamcall_prerr(TDH_SYS_CONFIG, &args); /* Free the array as it is not required anymore. */ @@ -2046,8 +2054,7 @@ EXPORT_SYMBOL_FOR_KVM(tdh_phymem_page_wbinvd_hkid); bool tdx_supports_dynamic_pamt(const struct tdx_sys_info *sysinfo) { - /* To be enabled when kernel is ready. */ - return false; + return sysinfo->features.tdx_features0 & TDX_FEATURES0_DYNAMIC_PAMT; } EXPORT_SYMBOL_FOR_KVM(tdx_supports_dynamic_pamt); From e61c837817acde1df7ebd977b798683a3d9b0dda Mon Sep 17 00:00:00 2001 From: Rick Edgecombe Date: Fri, 4 Sep 2026 14:58:40 -0700 Subject: [PATCH 0210/2286] Documentation/x86: Add documentation for TDX's Dynamic PAMT Expand TDX documentation to include information on the Dynamic PAMT feature. The new section explains PAMT support in the TDX module and how Dynamic PAMT affects the kernel memory use. Some of the docs verbiage was provided by Dave Hansen (see link). AI was used under supervision to review the docs. Based on a patch originally by Kiryl Shutsemau. Signed-off-by: Rick Edgecombe Signed-off-by: Dave Hansen Reviewed-by: Binbin Wu Reviewed-by: Tony Lindgren Acked-by: Sohil Mehta Tested-by: Hongyu Ning Link: https://lore.kernel.org/all/8e40862e-891a-4cad-8bb8-06ad25ad6061@intel.com/ Link: https://patch.msgid.link/20260904215841.303070-11-rick.p.edgecombe@intel.com --- Documentation/arch/x86/tdx.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/arch/x86/tdx.rst b/Documentation/arch/x86/tdx.rst index 3303499ad4c6f8..a36ea2bd4301d5 100644 --- a/Documentation/arch/x86/tdx.rst +++ b/Documentation/arch/x86/tdx.rst @@ -200,6 +200,27 @@ reflects the TCB of the currently running TDX module and therefore changes after an update. By contrast, TEE_TCB_SVN reflects the TCB at TD launch time and is not affected. +Dynamic PAMT +------------ + +The Physical Address Metadata Table (PAMT) is metadata in which the TDX +module keeps data about each physical page (think struct page). Space +for it is allocated by the VMM, consumes up to about 0.4% of system +memory and needs to be supplied to the TDX module when the TDX module is +first loaded. + +Dynamic PAMT is an add-on feature that allows a VMM to dynamically +allocate the part of the PAMT which tracks 4KB pages. This reduces the +amount of memory that TDX consumes while TDs are not in use. + +When Dynamic PAMT is in use, dmesg shows it like:: + + [..] virt/tdx: Enable Dynamic PAMT + [..] virt/tdx: 10092 KB allocated for PAMT + [..] virt/tdx: TDX-Module initialized + +Dynamic PAMT is enabled automatically if supported. + TDX Interaction to Other Kernel Components ------------------------------------------ From 68b2e64129185d6de3495ec8c73b219938115045 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:50:01 +0530 Subject: [PATCH 0211/2286] kbuild: fix typo "funtions" in comment Correct "funtions" to "functions", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Fixes: 5270316c9fec ("kbuild: Use -fmin-function-alignment when available") Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260904122002.27960-1-hemanth.selam@gmail.com Signed-off-by: Nathan Chancellor --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 570f0c68aadd89..9bd226726358e4 100644 --- a/Makefile +++ b/Makefile @@ -1144,7 +1144,7 @@ export CC_FLAGS_NO_FPU ifneq ($(CONFIG_FUNCTION_ALIGNMENT),0) # Set the minimal function alignment. Use the newer GCC option -# -fmin-function-alignment if it is available, or fall back to -falign-funtions. +# -fmin-function-alignment if it is available, or fall back to -falign-functions. # See also CONFIG_CC_HAS_SANE_FUNCTION_ALIGNMENT. ifdef CONFIG_CC_HAS_MIN_FUNCTION_ALIGNMENT KBUILD_CFLAGS += -fmin-function-alignment=$(CONFIG_FUNCTION_ALIGNMENT) From 19c5d90800a24096697dd6c66237218b7ea417ee Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:34 -0700 Subject: [PATCH 0212/2286] um: Check for missing AVX and AVX-512 xstate bits If the CPU declares AVX or AVX-512 support, verify that all the corresponding bits are set in the XCR0 register. If any are missing, warn and don't set the corresponding X86_FEATURE_* flags. This eliminates the perceived need for UML-supporting AVX and AVX-512 optimized code in the kernel (that is, lib/raid/ currently) to start checking the XCR0 bits in addition to X86_FEATURE_AVX*. This aligns UML with the vast majority of userspace programs, which check the XCR0 bits before considering AVX and AVX-512 to be supported, as per the procedure documented in Intel's CPU manual. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-3-ebiggers@kernel.org --- arch/um/kernel/um_arch.c | 78 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/arch/um/kernel/um_arch.c b/arch/um/kernel/um_arch.c index e4ee693961e4e7..9d53cc6a783d7e 100644 --- a/arch/um/kernel/um_arch.c +++ b/arch/um/kernel/um_arch.c @@ -264,12 +264,88 @@ unsigned long brk_start; #define MIN_VMALLOC (32 * 1024 * 1024) +static u64 __init read_xcr0(void) +{ + u32 a, b, c, d; + + asm volatile("cpuid" + : "=a"(a), "=b"(b), "=c"(c), "=d"(d) + : "a"(0), "c"(0)); + if (a >= 1) { /* max_leaf >= 1 */ + asm volatile("cpuid" + : "=a"(a), "=b"(b), "=c"(c), "=d"(d) + : "a"(1), "c"(0)); + if (c & (1 << 27)) { /* XSAVE enabled by OS */ + asm volatile("xgetbv" : "=d"(d), "=a"(a) : "c"(0)); + return ((u64)d << 32) | a; + } + } + return 0; +} + +static void __init validate_and_set_cpu_cap(int cap, u64 xcr0) +{ + /* + * Check for missing xstate features right away, so that there's no + * perceived need for all optimized code in the kernel to do so. + */ + switch (cap) { + case X86_FEATURE_AVX: + case X86_FEATURE_AVX2: + case X86_FEATURE_AVX_VNNI: + case X86_FEATURE_FMA: + case X86_FEATURE_VAES: + case X86_FEATURE_VPCLMULQDQ: + if ((xcr0 & 0x7) != 0x7) { + static bool warned; + + if (!warned) { + os_warn("Disabling AVX support due to missing xstate features\n"); + warned = true; + } + return; + } + break; + case X86_FEATURE_AVX512F: + case X86_FEATURE_AVX512BW: + case X86_FEATURE_AVX512CD: + case X86_FEATURE_AVX512DQ: + case X86_FEATURE_AVX512ER: + case X86_FEATURE_AVX512IFMA: + case X86_FEATURE_AVX512PF: + case X86_FEATURE_AVX512VBMI: + case X86_FEATURE_AVX512VL: + case X86_FEATURE_AVX512_4FMAPS: + case X86_FEATURE_AVX512_4VNNIW: + case X86_FEATURE_AVX512_BF16: + case X86_FEATURE_AVX512_BITALG: + case X86_FEATURE_AVX512_FP16: + case X86_FEATURE_AVX512_VBMI2: + case X86_FEATURE_AVX512_VNNI: + case X86_FEATURE_AVX512_VP2INTERSECT: + case X86_FEATURE_AVX512_VPOPCNTDQ: + if ((xcr0 & 0xe7) != 0xe7) { + static bool warned; + + if (!warned) { + os_warn("Disabling AVX-512 support due to missing xstate features\n"); + warned = true; + } + return; + } + break; + } + set_cpu_cap(&boot_cpu_data, cap); +} + static void __init parse_host_cpu_flags(char *line) { + u64 xcr0 = read_xcr0(); int i; + for (i = 0; i < 32*NCAPINTS; i++) { if ((x86_cap_flags[i] != NULL) && strstr(line, x86_cap_flags[i])) - set_cpu_cap(&boot_cpu_data, i); + validate_and_set_cpu_cap(i, xcr0); } } From a0e1fdb96578ea562a03c487f70673d228824d0a Mon Sep 17 00:00:00 2001 From: Enlin Mu Date: Tue, 25 Aug 2026 09:15:09 +0800 Subject: [PATCH 0213/2286] softirq: Remove redundant h->action from preempt_count mismatch error When a preempt_count mismatch is detected in handle_softirqs(), the error message currently prints the softirq action handler (h->action). However, as Thomas Gleixner pointed out, the action handler for a given softirq vector is always a known, fixed function (e.g., run_timer_softirq for TIMER_SOFTIRQ). Printing it provides zero diagnostic value. The real issue typically lies in the functions invoked *within* that handler, not the handler entry point itself. Therefore, as suggested by Sebastian Andrzej Siewior, remove h->action from the print statement entirely to clean up the error message. Suggested-by: Thomas Gleixner Suggested-by: Sebastian Andrzej Siewior Signed-off-by: Enlin Mu Signed-off-by: Thomas Gleixner Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Bradley Morgan Link: https://patch.msgid.link/20260825011509.8505-1-enlin.mu@linux.dev --- kernel/softirq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/softirq.c b/kernel/softirq.c index 7980a4a232f9f7..800d81c573024f 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -645,8 +645,8 @@ static void handle_softirqs(bool ksirqd) h->action(); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { - pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n", - vec_nr, softirq_to_name[vec_nr], h->action, + pr_err("huh, entered softirq %u %s with preempt_count %08x, exited with %08x?\n", + vec_nr, softirq_to_name[vec_nr], prev_count, preempt_count()); preempt_count_set(prev_count); } From 2d23d5e9cb468baf6e4fa78d0dd7e8555759385d Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:38:57 +0530 Subject: [PATCH 0214/2286] platform/chrome: Fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://lore.kernel.org/r/20260904110900.20435-2-hemanth.selam@gmail.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_ishtp.c | 2 +- drivers/platform/chrome/cros_usbpd_notify.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c index 3766cef81fe82e..b21a27ad376250 100644 --- a/drivers/platform/chrome/cros_ec_ishtp.c +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -369,7 +369,7 @@ static void ish_event_cb(struct ishtp_cl_device *cl_device) * @cros_ish_cl: ISHTP client instance * @reset: true if called from reset handler * - * This function complete the initializtion of the client. + * This function complete the initialization of the client. * * Return: 0 for success, negative error code for failure. */ diff --git a/drivers/platform/chrome/cros_usbpd_notify.c b/drivers/platform/chrome/cros_usbpd_notify.c index d842c05f4db00b..6f5eea4938cb43 100644 --- a/drivers/platform/chrome/cros_usbpd_notify.c +++ b/drivers/platform/chrome/cros_usbpd_notify.c @@ -64,7 +64,7 @@ static void cros_usbpd_get_event_and_notify(struct device *dev, /* * We still send a 0 event out to older devices which don't - * have the updated device heirarchy. + * have the updated device hierarchy. */ if (!ec_dev) { dev_dbg(dev, @@ -115,7 +115,7 @@ static int cros_usbpd_notify_probe_acpi(struct platform_device *pdev) if (!ec_dev) { /* * We continue even for older devices which don't have the - * correct device heirarchy, namely, GOOG0003 is a child + * correct device hierarchy, namely, GOOG0003 is a child * of GOOG0004. If GOOG0003 is a child of GOOG0004 and we * can't get a pointer to the Chrome EC device, defer the * probe function. From 5859f97c6f404d07e89fe9c0d20a318ebf200bd1 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:38:58 +0530 Subject: [PATCH 0215/2286] platform/chrome: cros_ec_rpmsg: Fix repeated word 'from' in comment Drop the second 'from', reported by checkpatch.pl as a possible repeated word. Only touches a comment, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://lore.kernel.org/r/20260904110900.20435-3-hemanth.selam@gmail.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_rpmsg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c index 09bd9e49464e62..7390c2c91ea5d7 100644 --- a/drivers/platform/chrome/cros_ec_rpmsg.c +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -20,7 +20,7 @@ #define HOST_EVENT_MARK 2 /** - * struct cros_ec_rpmsg_response - rpmsg message format from from EC. + * struct cros_ec_rpmsg_response - rpmsg message format from EC. * * @type: The type of message, should be either HOST_COMMAND_MARK or * HOST_EVENT_MARK, representing that the message is a response to From 5ac0aed54f94f936ef4a8709c90a4b56c7b12c63 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:03:54 +0530 Subject: [PATCH 0216/2286] microblaze: fix typo "multipler" in comment Correct "multipler" to "multiplier", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Michal Simek --- arch/microblaze/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/microblaze/Makefile b/arch/microblaze/Makefile index 02e6be9c5b0dca..56a161147606ca 100644 --- a/arch/microblaze/Makefile +++ b/arch/microblaze/Makefile @@ -16,7 +16,7 @@ export CPU_VER CPU_MAJOR CPU_MINOR CPU_REV # The various CONFIG_XILINX cpu features options are integers 0/1/2... # rather than bools y/n -# Work out HW multipler support. This is tricky. +# Work out HW multiplier support. This is tricky. # 1. Spartan2 has no HW multipliers. # 2. MicroBlaze v3.x always uses them, except in Spartan 2 # 3. All other FPGa/CPU ver combos, we can trust the CONFIG_ settings From 3b79adcf0716a8d534c7719d0eeb7b84c6405d57 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:41:45 +0530 Subject: [PATCH 0217/2286] fsnotify: fix typo "anonynous" in comment Correct "anonynous" to "anonymous", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904121146.22419-1-hemanth.selam@gmail.com Signed-off-by: Jan Kara --- fs/notify/fanotify/fanotify_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index a32c6634d59278..63c9759fc3b06c 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -1837,7 +1837,7 @@ static int fanotify_events_supported(struct fsnotify_group *group, /* * mount and sb marks are not allowed on kernel internal pseudo fs, * like pipe_mnt, because that would subscribe to events on all the - * anonynous pipes in the system. + * anonymous pipes in the system. * * SB_NOUSER covers all of the internal pseudo fs whose objects are not * exposed to user's mount namespace, but there are other SB_KERNMOUNT From 43de050fa4a31f7e7a73cf8974dcf6037e161a39 Mon Sep 17 00:00:00 2001 From: Baolin Liu Date: Mon, 7 Sep 2026 16:26:02 +0800 Subject: [PATCH 0218/2286] isofs: fix Rock Ridge CE extent validation on multisession media Commit a36d990f5913 ("isofs: validate Rock Ridge CE continuation extent against volume size") compares the CE extent directly with s_nzones. The extent is an absolute block number, while s_nzones is the number of blocks relative to the selected ISO session. The comparison is therefore wrong when a multisession disc starts at a non-zero block. isofs_get_last_session() selects the last session on multisession media, and its volume descriptors describe a volume beginning at that session's LBA. For example, a session beginning at LBA 45447 with 64 blocks can contain a valid CE at absolute LBA 45467. The existing check rejects that CE, so the ER continuation record is not read, Rock Ridge is disabled, and the mount falls back to Joliet names. Save the selected session start in filesystem-block units and validate the CE extent against the half-open interval [session_start, session_end). Scale the session length to the same block units and retain a separate block-device limit. The lower bound is intentional: accepting arbitrary blocks before the selected session could make a CE read data from a previous session or another filesystem on the device. Only apply the bounds check when cont_extent is non-zero. A zero extent is the in-memory sentinel indicating that no CE continuation was found, rather than a request to read block zero. For a single-session image, session_start is zero and the effective volume boundary remains unchanged. Build-tested with: make CONFIG_RUST= CONFIG_RUST_DRIVERS= fs/isofs/ Fixes: a36d990f5913 ("isofs: validate Rock Ridge CE continuation extent against volume size") Cc: stable@vger.kernel.org Signed-off-by: Baolin Liu Link: https://patch.msgid.link/20260907082602.3777551-1-liubaolin12138@163.com Signed-off-by: Jan Kara --- fs/isofs/inode.c | 2 ++ fs/isofs/isofs.h | 2 ++ fs/isofs/rock.c | 11 +++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index 337836a0a17046..b766b5c9c593bd 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -821,6 +821,8 @@ static int isofs_fill_super(struct super_block *s, struct fs_context *fc) if (!sb_set_blocksize(s, orig_zonesize)) goto out_freesbi; + sbi->s_session_start = (sector_t)vol_desc_start << + (ISOFS_BLOCK_BITS - s->s_blocksize_bits); sbi->s_nls_iocharset = NULL; #ifdef CONFIG_JOLIET diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h index dacb9cdae4fd1b..79ca0256843aa5 100644 --- a/fs/isofs/isofs.h +++ b/fs/isofs/isofs.h @@ -35,6 +35,8 @@ struct isofs_sb_info { unsigned long s_firstdatazone; unsigned long s_log_zone_size; unsigned long s_max_size; + /* Session start in filesystem block units. */ + sector_t s_session_start; int s_rock_offset; /* offset of SUSP fields within SU area */ s32 s_sbsector; diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c index 2628f31bd3a5da..84e0d764c2100f 100644 --- a/fs/isofs/rock.c +++ b/fs/isofs/rock.c @@ -9,6 +9,7 @@ #include #include +#include #include "isofs.h" #include "rock.h" @@ -84,6 +85,10 @@ static void init_rock_state(struct rock_state *rs, struct inode *inode) */ static int rock_continue(struct rock_state *rs) { + struct super_block *sb = rs->inode->i_sb; + struct isofs_sb_info *sbi = ISOFS_SB(sb); + sector_t session_end = sbi->s_session_start + + ((sector_t)sbi->s_nzones << (ISOFS_BLOCK_BITS - sb->s_blocksize_bits)); int ret = 1; int blocksize = 1 << rs->inode->i_blkbits; const int min_de_size = offsetof(struct rock_ridge, u); @@ -101,11 +106,13 @@ static int rock_continue(struct rock_state *rs) goto out; } - if ((unsigned)rs->cont_extent >= ISOFS_SB(rs->inode->i_sb)->s_nzones) { + if (rs->cont_extent && + (rs->cont_extent < sbi->s_session_start || + rs->cont_extent >= session_end)) { printk(KERN_NOTICE "rock: corrupted directory entry. " "extent=%u out of volume (nzones=%lu)\n", (unsigned)rs->cont_extent, - ISOFS_SB(rs->inode->i_sb)->s_nzones); + sbi->s_nzones); ret = -EIO; goto out; } From 8fccf189738917bcffe91d69d5d79199d38f6f7c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:27:54 +0200 Subject: [PATCH 0219/2286] docs: ioctl-number: Point the 0xEC row at cros_ec_chardev.h The 0xEC entry points at drivers/platform/chrome/cros_ec_dev.h, which commit 5e0115581bbc ("cros_ec: Move cros_ec_dev module to drivers/mfd") moved; the ioctl numbers now live in include/linux/platform_data/cros_ec_chardev.h. Point the row at that file. Fixes: eda2e30c6684 ("mfd / platform: cros_ec: Miscellaneous character device to talk with the EC") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Reviewed-by: Tzung-Bi Shih Signed-off-by: Jonathan Corbet Message-ID: <20260905102754.43160-1-kmehltretter@gmail.com> --- Documentation/userspace-api/ioctl/ioctl-number.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 2fc53093752d13..32c0d08e92e401 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -399,7 +399,7 @@ Code Seq# Include File Comments 0xDD 00-3F ZFCP device driver see drivers/s390/scsi/ 0xE5 00-3F linux/fuse.h -0xEC 00-01 drivers/platform/chrome/cros_ec_dev.h ChromeOS EC driver +0xEC 00-01 linux/platform_data/cros_ec_chardev.h ChromeOS EC driver 0xEE 00-09 uapi/linux/pfrut.h Platform Firmware Runtime Update and Telemetry 0xF3 00-3F drivers/usb/misc/sisusbvga/sisusb.h sisfb (in development) From 19ea70f5f5a86d54629fada5acf0208f0a5d8663 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 12:38:19 +0200 Subject: [PATCH 0220/2286] Documentation: userspace-api: iommufd: Fix the struct iommu_hwpt_alloc name struct iommufd_hwpt_alloc does not exist; the name is struct iommu_hwpt_alloc. Use struct iommu_hwpt_alloc. Fixes: e2d8fe9148b7 ("Documentation: userspace-api: iommufd: Update HWPT_PAGING and HWPT_NESTED") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905103819.43424-1-kmehltretter@gmail.com> --- Documentation/userspace-api/iommufd.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/userspace-api/iommufd.rst b/Documentation/userspace-api/iommufd.rst index f1c4d21e5c5ee2..763b23339480a5 100644 --- a/Documentation/userspace-api/iommufd.rst +++ b/Documentation/userspace-api/iommufd.rst @@ -242,7 +242,7 @@ creating the objects and links:: Either a manual IOMMUFD_OBJ_HWPT_PAGING or an IOMMUFD_OBJ_HWPT_NESTED is created via the same IOMMU_HWPT_ALLOC uAPI. The difference is at the type - of the object passed in via the @pt_id field of struct iommufd_hwpt_alloc. + of the object passed in via the @pt_id field of struct iommu_hwpt_alloc. 5. IOMMUFD_OBJ_VIOMMU can be only manually created via the IOMMU_VIOMMU_ALLOC uAPI, provided a dev_id (for the device's physical IOMMU to back the vIOMMU) From 0c4c9abbf68b862a53ca47acb5a8664200af8e6d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 11:17:43 +0200 Subject: [PATCH 0221/2286] Documentation/features: Mark cmpxchg-local as supported on loongarch arch/loongarch/Kconfig has selected CONFIG_HAVE_CMPXCHG_LOCAL since commit 07d5798aadfa ("LoongArch: Select HAVE_CMPXCHG_LOCAL in Kconfig"), but the feature matrix still lists loongarch as TODO. This is the only cell in Documentation/features that tools/docs/features-refresh.sh changes on today's tree. Mark it ok. Fixes: 07d5798aadfa ("LoongArch: Select HAVE_CMPXCHG_LOCAL in Kconfig") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Jonathan Corbet Message-ID: <20260905091743.41006-1-kmehltretter@gmail.com> --- Documentation/features/locking/cmpxchg-local/arch-support.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/features/locking/cmpxchg-local/arch-support.txt b/Documentation/features/locking/cmpxchg-local/arch-support.txt index 2c3a4b91f16df3..127e8159adaa6b 100644 --- a/Documentation/features/locking/cmpxchg-local/arch-support.txt +++ b/Documentation/features/locking/cmpxchg-local/arch-support.txt @@ -12,7 +12,7 @@ | arm64: | ok | | csky: | TODO | | hexagon: | TODO | - | loongarch: | TODO | + | loongarch: | ok | | m68k: | TODO | | microblaze: | TODO | | mips: | TODO | From b76b794226173a8bbddc932e6f0393f1310d5cda Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:54:38 +0200 Subject: [PATCH 0222/2286] Documentation: livepatch: Point klp_callbacks kernel-doc at livepatch_external.h api.rst asks include/linux/livepatch.h for struct klp_callbacks. Commit dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") moved the structure and its kernel-doc comment to include/linux/livepatch_external.h. Since then the Object Types section renders the other four structures but not klp_callbacks. Add a directive for the new header. Fixes: dd590d4d57eb ("objtool/klp: Introduce klp diff subcommand for diffing object files") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905085438.40349-1-kmehltretter@gmail.com> --- Documentation/livepatch/api.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/livepatch/api.rst b/Documentation/livepatch/api.rst index 78944b63d74b52..2a98c517da9da0 100644 --- a/Documentation/livepatch/api.rst +++ b/Documentation/livepatch/api.rst @@ -27,4 +27,7 @@ Object Types ============ .. kernel-doc:: include/linux/livepatch.h - :identifiers: klp_patch klp_object klp_func klp_callbacks klp_state + :identifiers: klp_patch klp_object klp_func klp_state + +.. kernel-doc:: include/linux/livepatch_external.h + :identifiers: klp_callbacks From 40b2f901d63fff0b7630e0cd83c6d15fd0c56907 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:52:11 +0200 Subject: [PATCH 0223/2286] Documentation: reset: Use fwnode_reset_simple_xlate() in the kernel-doc directive reset.rst asks drivers/reset/core.c for of_reset_simple_xlate(). Commit 9035073d0ef1 ("reset: convert reset core to using firmware nodes") replaced it with fwnode_reset_simple_xlate(). Since then the directive finds nothing for it and the default translation helper is missing from the rendered controller API section. Use the current name. Fixes: 9035073d0ef1 ("reset: convert reset core to using firmware nodes") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905085211.40286-1-kmehltretter@gmail.com> --- Documentation/driver-api/reset.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/driver-api/reset.rst b/Documentation/driver-api/reset.rst index 7a6571849664f0..932699c57d1ed4 100644 --- a/Documentation/driver-api/reset.rst +++ b/Documentation/driver-api/reset.rst @@ -213,7 +213,7 @@ devm_reset_controller_register(). :internal: .. kernel-doc:: drivers/reset/core.c - :functions: of_reset_simple_xlate + :functions: fwnode_reset_simple_xlate reset_controller_register reset_controller_unregister devm_reset_controller_register From 3cdc47b3f2b1d46fb6e58c4959904d77f6e702c3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 10:36:48 +0200 Subject: [PATCH 0224/2286] Documentation: rs485: Pull uart_get_rs485_mode() from serial_core.c serial-rs485.rst asks include/uapi/linux/serial.h for both struct serial_rs485 and uart_get_rs485_mode(). The function and its kernel-doc comment live in drivers/tty/serial/serial_core.c, so the directive has never rendered it and the section shows only the structure. Add a second directive for serial_core.c. Fixes: 0f4648a1a6d3 ("Documentation: rs485: Mention uart_get_rs485_mode()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905083648.39026-1-kmehltretter@gmail.com> --- Documentation/driver-api/serial/serial-rs485.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/driver-api/serial/serial-rs485.rst b/Documentation/driver-api/serial/serial-rs485.rst index f53043d2107159..98d8d83718c6d5 100644 --- a/Documentation/driver-api/serial/serial-rs485.rst +++ b/Documentation/driver-api/serial/serial-rs485.rst @@ -50,7 +50,10 @@ RS485 Serial Communications matching to the current configuration. .. kernel-doc:: include/uapi/linux/serial.h - :identifiers: serial_rs485 uart_get_rs485_mode + :identifiers: serial_rs485 + +.. kernel-doc:: drivers/tty/serial/serial_core.c + :identifiers: uart_get_rs485_mode 4. Usage from user-level ======================== From 8323704f00db41da50504505f8ad514c0a1fb9b4 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Sat, 5 Sep 2026 09:46:40 +0200 Subject: [PATCH 0225/2286] docs: misc-devices/spear-pcie-gadget.txt: Fix bracket and whitespace Add missing ')' and remove needless whitespace. Signed-off-by: Manuel Ebner Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905074641.811164-2-manuelebnerli@mailbox.org> --- Documentation/misc-devices/spear-pcie-gadget.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/misc-devices/spear-pcie-gadget.rst b/Documentation/misc-devices/spear-pcie-gadget.rst index 09b9d6c7ac1545..13333a20944c39 100644 --- a/Documentation/misc-devices/spear-pcie-gadget.rst +++ b/Documentation/misc-devices/spear-pcie-gadget.rst @@ -37,9 +37,9 @@ read behavior of nodes: ----------------------- =============== ============================================================== -link gives ltssm status. -int_type type of supported interrupt -no_of_msi zero if MSI is not enabled by host. A positive value is the +link gives ltssm status. +int_type type of supported interrupt +no_of_msi zero if MSI is not enabled by host. A positive value is the number of MSI vector granted. vendor_id returns programmed vendor id (hex) device_id returns programmed device id(hex) @@ -53,7 +53,7 @@ write behavior of nodes: ------------------------ =============== ================================================================ -link write UP to enable ltsmm DOWN to disable +link write UP to enable ltsmm DOWN to disable int_type write interrupt type to be configured and (int_type could be INTA, MSI or NO_INT). Select MSI only when you have programmed no_of_msi node. @@ -65,7 +65,7 @@ device_id write device id(hex) to be programmed. bar0_size write size of bar0 in hex. default bar0 size is 1000 (hex) bytes. bar0_address write address of bar0 mapped area in hex. (default mapping of - bar0 is SYSRAM1(E0800000). Always program bar size before bar + bar0 is SYSRAM1(E0800000)). Always program bar size before bar address. Kernel might modify bar size and address for alignment, so read back bar size and address after writing to cross check. bar0_rw_offset write offset of bar0 for which bar0_data will write value. From bb2e6adb4739eea65311677316fb40603fd5052c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 04:58:21 +0200 Subject: [PATCH 0226/2286] docs: parse-headers: Fix script name and stale Makefile reference The script is tools/docs/parse-headers.py, not parse_headers.py, and its FILE_RULES argument is optional. Commit 8a298579cdfc ("scripts: sphinx-build-wrapper: get rid of uapi/media Makefile") removed the Makefile the text points to as an example; the cross-references are now generated by the kernel-include directive, so point to a file that uses it. Fixes: d69a03a97a2d ("docs: doc-guide: parse-headers.rst update its documentation") Fixes: 8a298579cdfc ("scripts: sphinx-build-wrapper: get rid of uapi/media Makefile") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905025821.83968-1-kmehltretter@gmail.com> --- Documentation/doc-guide/parse-headers.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Documentation/doc-guide/parse-headers.rst b/Documentation/doc-guide/parse-headers.rst index a7bb01ff04eb0b..eb36c92e9a062c 100644 --- a/Documentation/doc-guide/parse-headers.rst +++ b/Documentation/doc-guide/parse-headers.rst @@ -8,26 +8,27 @@ between the code and the documentation. Adding cross-references for userspace API files has an additional advantage: Sphinx will generate warnings if a symbol is not found at the documentation. That helps to keep the uAPI documentation in sync with the Kernel changes. -The :ref:`parse_headers.py ` provides a way to generate such -cross-references. It has to be called via Makefile, while building the -documentation. Please see ``Documentation/userspace-api/media/Makefile`` for an example -about how to use it inside the Kernel tree. +The :ref:`parse-headers.py ` provides a way to generate such +cross-references. During the documentation build, the same parser is used by +the ``kernel-include`` directive with the ``:generate-cross-refs:`` option. +Please see ``Documentation/userspace-api/media/cec/cec-header.rst`` for an +example about how to use it inside the Kernel tree. .. _parse_headers: -tools/docs/parse_headers.py +tools/docs/parse-headers.py ^^^^^^^^^^^^^^^^^^^^^^^^^^^ NAME **** -parse_headers.py - parse a C file, in order to identify functions, structs, +parse-headers.py - parse a C file, in order to identify functions, structs, enums and defines and create cross-references to a Sphinx book. USAGE ***** -parse-headers.py [-h] [-d] [-t] ``FILE_IN`` ``FILE_OUT`` ``FILE_RULES`` +parse-headers.py [-h] [-d] [-t] ``FILE_IN`` ``FILE_OUT`` [``FILE_RULES``] SYNOPSIS ******** From b31821013b2331fae246fd6e0530174785b7bca3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 04:37:46 +0200 Subject: [PATCH 0227/2286] docs: irqflags-tracing: Fix the lockdep Kconfig option names CONFIG_PROVE_SPIN_LOCKING, CONFIG_PROVE_RW_LOCKING, CONFIG_PROVE_MUTEX_LOCKING and CONFIG_PROVE_RWSEM_LOCKING have never existed in mainline. Lock proving was merged as the single CONFIG_PROVE_LOCKING, which has depended on CONFIG_TRACE_IRQFLAGS_SUPPORT from the start, so there is no reduced set of options without it. Fixes: 55df314fbdb4 ("[PATCH] lockdep: irqtrace subsystem, docs") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Jonathan Corbet Message-ID: <20260905023746.82998-1-kmehltretter@gmail.com> --- Documentation/core-api/irq/irqflags-tracing.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Documentation/core-api/irq/irqflags-tracing.rst b/Documentation/core-api/irq/irqflags-tracing.rst index bdd208259fb30b..a3c77046ae6bf8 100644 --- a/Documentation/core-api/irq/irqflags-tracing.rst +++ b/Documentation/core-api/irq/irqflags-tracing.rst @@ -9,12 +9,8 @@ that it gives interested subsystems an opportunity to be notified of every hardirqs-off/hardirqs-on, softirqs-off/softirqs-on event that happens in the kernel. -CONFIG_TRACE_IRQFLAGS_SUPPORT is needed for CONFIG_PROVE_SPIN_LOCKING -and CONFIG_PROVE_RW_LOCKING to be offered by the generic lock debugging -code. Otherwise only CONFIG_PROVE_MUTEX_LOCKING and -CONFIG_PROVE_RWSEM_LOCKING will be offered on an architecture - these -are locking APIs that are not used in IRQ context. (the one exception -for rwsems is worked around) +CONFIG_TRACE_IRQFLAGS_SUPPORT is needed for CONFIG_PROVE_LOCKING to be +offered by the generic lock debugging code. Architecture support for this is certainly not in the "trivial" category, because lots of lowlevel assembly code deal with irq-flags From 12af11ca76c52152defb49b3da5a6971167ecc26 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 03:55:09 +0200 Subject: [PATCH 0228/2286] docs: maple_tree: Replace removed bulk insertion API Commit e3852a1213ff ("maple_tree: Drop bulk insert support") removed mas_expected_entries() but left the documentation describing it. Document the current preallocation interface instead. Fixes: e3852a1213ff ("maple_tree: Drop bulk insert support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Nick Huang [sef1548@gmail.com](mailto:sef1548@gmail.com) Signed-off-by: Jonathan Corbet Message-ID: <20260905015509.76265-1-kmehltretter@gmail.com> --- Documentation/core-api/maple_tree.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Documentation/core-api/maple_tree.rst b/Documentation/core-api/maple_tree.rst index 12bccfb6aac1f7..836342fb1eec56 100644 --- a/Documentation/core-api/maple_tree.rst +++ b/Documentation/core-api/maple_tree.rst @@ -214,11 +214,10 @@ Advanced Allocating Nodes ------------------------- Allocations are usually handled internally to the tree, however if allocations -need to occur before a write occurs then calling mas_expected_entries() will -allocate the worst-case number of needed nodes to insert the provided number of -ranges. This also causes the tree to enter mass insertion mode. Once -insertions are complete calling mas_destroy() on the maple state will free the -unused allocations. +need to occur before a write occurs then calling mas_preallocate() will +allocate the nodes needed to store the provided entry. The entry is then +stored with mas_store_prealloc(). If the store is abandoned, calling +mas_destroy() on the maple state will free the unused allocations. .. _maple-tree-advanced-locks: From a9b424650f6c279c28e166b852c7f41c54bd6003 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 04:23:10 +0200 Subject: [PATCH 0229/2286] docs: core-api: Fix the ISA DMA example The example never matched the ISA DMA API: the functions are named enable_dma(), disable_dma() and get_dma_residue(), clear_dma_ff() takes the channel number, and claim_dma_lock() returns an unsigned long. Fixes: ddb99f3d35b1 ("[PATCH] ISA DMA API documentation") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Jonathan Corbet Message-ID: <20260905022310.82529-1-kmehltretter@gmail.com> --- Documentation/core-api/dma-isa-lpc.rst | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Documentation/core-api/dma-isa-lpc.rst b/Documentation/core-api/dma-isa-lpc.rst index 17b193603f0ae1..296838ff295a19 100644 --- a/Documentation/core-api/dma-isa-lpc.rst +++ b/Documentation/core-api/dma-isa-lpc.rst @@ -115,17 +115,18 @@ sure that all data has been transferred. Example:: - int flags, residue; + unsigned long flags; + int residue; flags = claim_dma_lock(); - clear_dma_ff(); + clear_dma_ff(channel); set_dma_mode(channel, DMA_MODE_WRITE); set_dma_addr(channel, phys_addr); set_dma_count(channel, num_bytes); - dma_enable(channel); + enable_dma(channel); release_dma_lock(flags); @@ -133,9 +134,9 @@ Example:: flags = claim_dma_lock(); - dma_disable(channel); + disable_dma(channel); - residue = dma_get_residue(channel); + residue = get_dma_residue(channel); if (residue != 0) printk(KERN_ERR "driver: Incomplete DMA transfer!" " %d bytes left!\n", residue); From a237dcf774c53a319fd2ecc3d85a8d88d408f660 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 03:09:52 +0200 Subject: [PATCH 0230/2286] docs: sysctl: Remove obsolete sg-big-buff entry The sg-big-buff sysctl no longer exists. The documented SG_BIG_BUFF macro is only a compatibility alias and changing it does not alter the driver default. Fixes: 50209dec14f8 ("scsi: sg: Remove deprecated sg-big-buff") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Reviewed-by: Bradley Morgan Signed-off-by: Jonathan Corbet Message-ID: <20260905010952.57451-1-kmehltretter@gmail.com> --- Documentation/admin-guide/sysctl/kernel.rst | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index fb38d654846d9c..64b78789459d4f 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1334,19 +1334,6 @@ seccomp See Documentation/userspace-api/seccomp_filter.rst. -sg-big-buff -=========== - -This file shows the size of the generic SCSI (sg) buffer. -You can't tune it just yet, but you could change it on -compile time by editing ``include/scsi/sg.h`` and changing -the value of ``SG_BIG_BUFF``. - -There shouldn't be any reason to change this value. If -you can come up with one, you probably know what you -are doing anyway :) - - shmall ====== From 14ddd53ecf4f8951d86b74970b9a3242d7dba58a Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:02:55 +0530 Subject: [PATCH 0231/2286] Documentation/core-api: fix typo "maintainance" in comment Correct "maintainance" to "maintenance", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260904113257.13594-2-hemanth.selam@gmail.com> --- Documentation/core-api/housekeeping.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/core-api/housekeeping.rst b/Documentation/core-api/housekeeping.rst index ccb0a88b9cb365..71ba5d86f24929 100644 --- a/Documentation/core-api/housekeeping.rst +++ b/Documentation/core-api/housekeeping.rst @@ -9,7 +9,7 @@ extreme workloads can't stand, such as in some DPDK usecases. The kernel work moved away by CPU isolation is commonly described as "housekeeping" because it includes ground work that performs cleanups, -statistics maintainance and actions relying on them, memory release, +statistics maintenance and actions relying on them, memory release, various deferrals etc... Sometimes housekeeping is just some unbound work (unbound workqueues, From 09c12a4e05c6437a52433812518930a9fc0bd94a Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:02:56 +0530 Subject: [PATCH 0232/2286] tools/lib: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260904113257.13594-3-hemanth.selam@gmail.com> --- tools/lib/python/kdoc/c_lex.py | 2 +- tools/lib/python/kdoc/xforms_lists.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/lib/python/kdoc/c_lex.py b/tools/lib/python/kdoc/c_lex.py index cb95f51724483d..efbc23c01abdfe 100644 --- a/tools/lib/python/kdoc/c_lex.py +++ b/tools/lib/python/kdoc/c_lex.py @@ -68,7 +68,7 @@ class CToken(): MISMATCH = 255 #: an error indicator: should never happen in practice. - # Dict to convert from an enum interger into a string. + # Dict to convert from an enum integer into a string. _name_by_val = {v: k for k, v in dict(vars()).items() if isinstance(v, int)} # Dict to convert from string to an enum-like integer value. diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py index e3dda2fe8a5371..423e2bab766703 100644 --- a/tools/lib/python/kdoc/xforms_lists.py +++ b/tools/lib/python/kdoc/xforms_lists.py @@ -53,7 +53,7 @@ class CTransforms: (CMatch("__attribute__"), ""), # - # Macro __struct_group() creates an union with an anonymous + # Macro __struct_group() creates a union with an anonymous # and a non-anonymous struct, depending on the parameters. We only # need one of those at kernel-doc, as we won't be documenting the same # members twice. From 3a875e57d16cc4a80164386eae2a4dad59f37d86 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Thu, 3 Sep 2026 17:49:24 +0200 Subject: [PATCH 0233/2286] Documentation: admin-guide: Fix bracket Add missing ')' and remove ';' to improve readability. Signed-off-by: Manuel Ebner Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260903154924.732852-2-manuelebnerli@mailbox.org> --- Documentation/admin-guide/sysctl/vm.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index 5b318d17aa4b44..5f4a056e4dc222 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -438,7 +438,7 @@ zone[i]'s protection[j] is calculated by following expression:: = (total sums of managed_pages from zone[i+1] to zone[j] on the node) / lowmem_reserve_ratio[i]; (i = j): - (should not be protected. = 0; + (should not be protected. = 0) (i > j): (not necessary, but looks 0) From 42c3cdd2636dab54988ac3e169aa55d090790c8f Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 2 Sep 2026 16:54:28 +0200 Subject: [PATCH 0234/2286] fs: porting.rst: add bracket Add missing '(' to function in prose. Signed-off-by: Manuel Ebner Acked-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260902145428.704582-3-manuelebnerli@mailbox.org> --- Documentation/filesystems/porting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index 60880eb0c49d1a..810657082bfb37 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -673,7 +673,7 @@ watch out, since that shortcut is no longer valid. they used to - they just take it exclusive. However, ->lookup() may be called with parent locked shared. Its instances must not - * use d_instantiate) and d_rehash() separately - use d_add() or + * use d_instantiate() and d_rehash() separately - use d_add() or d_splice_alias() instead. * use d_rehash() alone - call d_add(new_dentry, NULL) instead. * in the unlikely case when (read-only) access to filesystem From 6283ba4b6d088c11096d984b2544a68fc61cdef3 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 2 Sep 2026 16:39:38 +0200 Subject: [PATCH 0235/2286] docs: locking: Fix bracket Remove needless '(' from code block. Signed-off-by: Manuel Ebner Reviewed-by: Jan Kara Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260902143936.703665-4-manuelebnerli@mailbox.org> --- Documentation/filesystems/locking.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 844d65eb47a5fb..1598cf555bfa8b 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -27,7 +27,7 @@ prototypes:: int (*d_init)(struct dentry *); void (*d_release)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); - char *(*d_dname)((struct dentry *dentry, char *buffer, int buflen); + char *(*d_dname)(struct dentry *dentry, char *buffer, int buflen); struct vfsmount *(*d_automount)(struct path *path); int (*d_manage)(const struct path *, bool); struct dentry *(*d_real)(struct dentry *, enum d_real_type type); From ff0335e948cad470f8667b7d2b84cc4122788515 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Wed, 2 Sep 2026 11:24:24 +0200 Subject: [PATCH 0236/2286] doc: Change dynticks-testing repository name Currently the documentation still mention dynticks-testing. Recently the repository name and the tool have been renamed to cpunoise (see Link tag below), so fix the occurrence and the URL. Cc: Valentin Schneider Cc: Waiman Long Cc: Sebastian Andrzej Siewior Cc: "Paul E. McKenney" Link: https://lore.kernel.org/all/20260815083728.15470-1-marco.crivellari@suse.com/ Signed-off-by: Marco Crivellari Reviewed-by: Sebastian Andrzej Siewior Acked-by: Frederic Weisbecker Signed-off-by: Jonathan Corbet Message-ID: <20260902092434.85096-2-marco.crivellari@suse.com> --- Documentation/admin-guide/cpu-isolation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/cpu-isolation.rst b/Documentation/admin-guide/cpu-isolation.rst index 8c65d03fd28c2a..b9f13a92867343 100644 --- a/Documentation/admin-guide/cpu-isolation.rst +++ b/Documentation/admin-guide/cpu-isolation.rst @@ -353,5 +353,5 @@ Some tools may also be useful for higher level analysis: 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 +- cpunoise does something similar to rtla-osnoise but in userspace. It is available + at https://git.kernel.org/pub/scm/linux/kernel/git/frederic/cpunoise.git From 98f768cd1103d05e42b3cf9acd307ad84866203c Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Wed, 2 Sep 2026 11:24:25 +0200 Subject: [PATCH 0237/2286] Documentation/no_hz: Change dynticks-testing repository name Currently the documentation still mention dynticks-testing. Recently the repository name and the tool have been renamed to cpunoise (see Link tag below), so fix the URL. Link: https://lore.kernel.org/all/20260815083728.15470-1-marco.crivellari@suse.com/ Signed-off-by: Marco Crivellari Acked-by: Frederic Weisbecker Signed-off-by: Jonathan Corbet Message-ID: <20260902092434.85096-3-marco.crivellari@suse.com> --- Documentation/timers/no_hz.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/timers/no_hz.rst b/Documentation/timers/no_hz.rst index 7fe8ef9718d8e3..4006cab64079b4 100644 --- a/Documentation/timers/no_hz.rst +++ b/Documentation/timers/no_hz.rst @@ -204,9 +204,9 @@ but do not see any change in your workload's behavior. Is this because your workload isn't affected that much by OS jitter, or is it because something else is in the way? This section helps answer this question by providing a simple OS-jitter test suite, which is available on branch -master of the following git archive: +master of the following git repository: -git://git.kernel.org/pub/scm/linux/kernel/git/frederic/dynticks-testing.git +https://git.kernel.org/pub/scm/linux/kernel/git/frederic/cpunoise.git Clone this archive and follow the instructions in the README file. This test procedure will produce a trace that will allow you to evaluate From c64f9d2a5395ade983b4adb3609752572a6b57de Mon Sep 17 00:00:00 2001 From: Giovanna Macedo Cox Date: Tue, 1 Sep 2026 21:56:01 -0300 Subject: [PATCH 0238/2286] docs: translations: pt_BR: translate coding-assistants.rst Translate coding-assistants.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. Assisted-by: LLM Signed-off-by: Giovanna Macedo Cox Reviewed-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260902005603.18836-1-giovannamccds@gmail.com> --- .../pt_BR/process/coding-assistants.rst | 60 +++++++++++++++++++ .../translations/pt_BR/process/index.rst | 1 + 2 files changed, 61 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/coding-assistants.rst diff --git a/Documentation/translations/pt_BR/process/coding-assistants.rst b/Documentation/translations/pt_BR/process/coding-assistants.rst new file mode 100644 index 00000000000000..2055af34a90819 --- /dev/null +++ b/Documentation/translations/pt_BR/process/coding-assistants.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +IA Assistente de código ++++++++++++++++++++++++ + +Esta documentação fornece um guia para ferramentas de IA e desenvolvedores que +usam IA como assistente para contribuir para o kernel do Linux. + +Ferramentas de IA que ajudam desenvolvedores do Linux kernel devem seguir os +padrões de desenvolvimento do kernel: + +* Documentation/process/development-process.rst +* Documentation/process/coding-style.rst +* Documentation/process/submitting-patches.rst + +Para guias e conteúdo sobre códigos gerados por assistentes de IA veja: + +* Documentation/process/generated-content.rst + +Licenças e Requisitos Legais +============================ + +Todas as contribuições devem estar de acordo com os requisitos de licença do +kernel: + +* Todo código deve ser compatível apenas com GPL-2.0 +* Use identificadores SPDX de licença apropriados +* Veja Documentation/process/license-rules.rst para mais detalhes + +Assinatura e certificado de origem do desenvolvedor +=================================================== + +Agentes de IA NÃO DEVEM adicionar tags Signed-off-by. Apenas pessoas +podem legalmente certificar o Certificado de Origem do Desenvolvedor (DCO). +A pessoa que envia é responsável por: + +* Revisar todo código gerado por IA +* Garantir conformidade com os requisitos de licença +* Acrescentar sua própria tag Signed-off-by para certificar o DCO +* Assumir toda responsabilidade pela contribuição + +Atribuições +=========== + +Quando ferramentas de IA contribuírem para o desenvolvimento do kernel, +a atribuição adequada ajuda a rastrear a função da IA no processo de +desenvolvimento. Contribuições devem incluir a tag de assistência seguindo este +formato:: + + Assisted-by: LLM [FERRAMENTA1] [FERRAMENTA2] + +* ``[FERRAMENTA1] [FERRAMENTA2]`` são ferramentas de análise especializada + opcionais (Exemplo: coccinelle, sparse, smatch, clang-tidy) + +Ferramentas básicas de desenvolvimento (git, gcc, make, editors) não são +listadas. + +Exemplo:: + + Assisted-by: LLM coccinelle sparse \ No newline at end of file diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index eda2a3fc5166f2..6c68ac496245e8 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -59,6 +59,7 @@ Estas são as regras pelas quais tentamos viver na comunidade do kernel Modelos de Maturidade para Contribuição no Kernel Linux Declaração sobre Drivers do Kernel Estilo de gerenciamento do kernel Linux + Assistentes de código Conclave (Continuidade do projeto) Lidando com bugs From c6cca5f384cd8d8c05471fd7a917844c5bc0e3f3 Mon Sep 17 00:00:00 2001 From: Lucas Adryell Ramalho Date: Sun, 30 Aug 2026 17:59:12 -0300 Subject: [PATCH 0239/2286] docs: translations: pt_BR: translate volatile-considered-harmful.rst Translate volatile-considered-harmful.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. Assisted-by: ChatGPT:GPT-5.5 Signed-off-by: Lucas Adryell Ramalho Reviewed-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260830205912.478358-1-lucasadramalho@gmail.com> --- .../translations/pt_BR/process/index.rst | 1 + .../process/volatile-considered-harmful.rst | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/volatile-considered-harmful.rst diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 6c68ac496245e8..094575660fdb2f 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -42,6 +42,7 @@ devem estar familiarizados. Como aplicar patches Backporting e resolução de conflitos Adicionando uma nova chamada de Sistema + Por que a classe de tipo "volatile" não deve ser usada Como não Deixar as ioctls malfeitas Guias de políticas e declarações de desenvolvedores diff --git a/Documentation/translations/pt_BR/process/volatile-considered-harmful.rst b/Documentation/translations/pt_BR/process/volatile-considered-harmful.rst new file mode 100644 index 00000000000000..b8774c0bae77f8 --- /dev/null +++ b/Documentation/translations/pt_BR/process/volatile-considered-harmful.rst @@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Por que a classe de tipo "volatile" não deve ser usada +-------------------------------------------------------- + +Programadores C frequentemente interpretam volatile como uma indicação de +que uma variável pode ser alterada fora da thread de execução atual; como +resultado, às vezes são tentados a usá-la no código do kernel quando +estruturas de dados compartilhadas estão sendo utilizadas. Em outras +palavras, há quem trate tipos volatile como uma espécie de variável +atômica simplificada, mas não são. O uso de volatile no código do +kernel quase nunca é correto; este documento explica o porquê. + +O ponto-chave a entender sobre volatile é que seu propósito é suprimir +otimizações, o que quase nunca é o que realmente se deseja fazer. No kernel, +é necessário proteger estruturas de dados compartilhadas contra acessos +concorrentes indesejados, o que é uma tarefa bastante diferente. O processo +de proteção contra concorrência indesejada também evita, de forma mais +eficiente, quase todos os problemas relacionados a otimizações. + +Assim como volatile, as primitivas do kernel que tornam seguro o acesso +concorrente a dados (spinlocks, mutexes, barreiras de memória etc.) são +projetadas para evitar otimizações indesejadas. Se forem usadas +corretamente, também não haverá necessidade de usar volatile. Se +volatile ainda for necessário, quase certamente há algum bug no código. +Em código de kernel corretamente escrito, volatile só serve para deixar +as coisas mais lentas. + +Considere um bloco típico de código do kernel:: + + spin_lock(&the_lock); + do_something_on(&shared_data); + do_something_else_with(&shared_data); + spin_unlock(&the_lock); + +Se todo o código seguir as regras de bloqueio, o valor de shared_data não +poderá mudar inesperadamente enquanto the_lock estiver mantido. Qualquer +outro código que queira manipular esses dados estará aguardando o bloqueio. +As primitivas de spinlock atuam como barreiras de memória (são escritas +explicitamente para isso), o que significa que os acessos aos dados não +serão otimizados de forma a atravessar essas barreiras. Assim, o compilador +até pode achar que sabe qual será o valor de shared_data, mas a chamada a +spin_lock(), por atuar como barreira de memória, o forçará a esquecer tudo +o que sabia. Não haverá problemas de otimização nos acessos a esses dados. + +Se shared_data fosse declarada volatile, o bloqueio ainda seria +necessário. No entanto, o compilador também seria impedido de otimizar o +acesso a shared_data _dentro_ da seção crítica, justamente quando sabemos +que ninguém mais pode estar manipulando esses dados. Enquanto o bloqueio +estiver mantido, shared_data não é volatile. Ao lidar com dados +compartilhados, um bloqueio adequado torna volatile desnecessário e +potencialmente prejudicial. + +A classe de armazenamento volatile foi originalmente concebida para +registradores de E/S mapeados em memória. No kernel, os acessos a esses +registradores também devem ser protegidos por bloqueios, mas também não se +deseja que o compilador "otimize" esses acessos dentro de uma seção crítica. +Contudo, no kernel, os acessos à memória de E/S são sempre feitos por meio de +funções de acesso; acessar diretamente a memória de E/S via ponteiros é +desencorajado e não funciona em todas as arquiteturas. Essas funções de +acesso são implementadas de modo a impedir otimizações indesejadas e, +portanto, mais uma vez, volatile é desnecessário. + +Outra situação em que se pode ser tentado a usar volatile é quando o +processador fica em espera ocupada pelo valor de uma variável. A forma +correta de realizar essa espera ocupada é:: + + while (my_variable != what_i_want) + cpu_relax(); + +A chamada a cpu_relax() pode reduzir o consumo de energia da CPU ou ceder +recursos a um processador lógico gêmeo hyperthreaded; ela também atua +como barreira para o compilador e, portanto, mais uma vez, volatile é +desnecessário. Naturalmente, a espera ocupada já é, por si só, uma prática +geralmente antissocial. + +Ainda existem algumas situações raras em que volatile faz sentido no +kernel: + + - As funções de acesso mencionadas acima podem usar volatile em + arquiteturas nas quais o acesso direto à memória de E/S funciona. + Essencialmente, cada chamada a uma função de acesso torna-se uma + pequena seção crítica por si só e garante que o acesso ocorra conforme + esperado pelo programador. + - Código assembly inline que modifica memória, mas não tem outros + efeitos colaterais visíveis, corre o risco de ser removido pelo GCC. + Adicionar a palavra-chave volatile às instruções asm impede essa + remoção. + - A variável jiffies é especial, pois pode ter um valor diferente a cada + vez que é referenciada, mas pode ser lida sem qualquer bloqueio + especial. Portanto, jiffies pode ser volatile, mas a adição de + outras variáveis desse tipo é fortemente desencorajada. Nesse + sentido, jiffies é considerada um problema de "legado idiota" (nas + palavras de Linus); corrigi-la daria mais trabalho do que valeria a + pena. + - Ponteiros para estruturas de dados em memória coerente que possam ser + modificadas por dispositivos de E/S podem, às vezes, ser + legitimamente volatile. Um buffer circular usado por um adaptador + de rede, no qual esse adaptador altera ponteiros para indicar quais + descritores já foram processados, é um exemplo desse tipo de + situação. + +Na maior parte do código, nenhuma das justificativas acima para o uso de +volatile se aplica. Como resultado, o uso de volatile provavelmente +será considerado um bug e fará com que o código seja submetido a uma análise +mais rigorosa. Desenvolvedores que se sintam tentados a usar volatile devem +dar um passo atrás e pensar no que realmente estão tentando alcançar. + +Patches para remover variáveis volatile são, em geral, bem-vindos, desde +que venham acompanhados de uma justificativa que demonstre que as questões +de concorrência foram devidamente analisadas. + + +Referências +=========== + +[1] https://lwn.net/Articles/233481/ + +[2] https://lwn.net/Articles/233482/ + +Créditos +======== + +Motivação original e pesquisa por Randy Dunlap + +Escrito por Jonathan Corbet + +Melhorias a partir de comentários de Satyam Sharma, Johannes Stezenbach, +Jesper Juhl, Heikki Orsila, H. Peter Anvin, Philipp Hahn e Stefan +Richter. From 86f316e80bafe554c386ee56cd35846c68257660 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Sat, 29 Aug 2026 13:24:33 -0300 Subject: [PATCH 0240/2286] docs: translations: pt_BR: translate embargoed-hardware-issues.rst Translate Documentation/process/embargoed-hardware-issues.rst into Brazilian Portuguese and add it to the pt_BR process index. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260829162438.13039-2-danielmaraboo@gmail.com> --- .../process/embargoed-hardware-issues.rst | 362 ++++++++++++++++++ .../translations/pt_BR/process/index.rst | 1 + 2 files changed, 363 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/embargoed-hardware-issues.rst diff --git a/Documentation/translations/pt_BR/process/embargoed-hardware-issues.rst b/Documentation/translations/pt_BR/process/embargoed-hardware-issues.rst new file mode 100644 index 00000000000000..ae1fda0880cc1e --- /dev/null +++ b/Documentation/translations/pt_BR/process/embargoed-hardware-issues.rst @@ -0,0 +1,362 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Problemas de hardware sob embargo +================================= + +Escopo +------ + +Problemas de hardware que resultam em problemas de segurança formam uma categoria +de bugs de segurança diferente dos bugs de software puros que afetam apenas o +kernel do Linux. + +Problemas de hardware como Meltdown, Spectre, L1TF, etc., devem ser tratados +de maneira diferente porque geralmente afetam todos os Sistemas Operacionais ("OS") +e, portanto, exigem coordenação entre diferentes fornecedores de SO, distribuições, +fabricantes de silício, integradores de hardware e outras partes. Para alguns +dos problemas, as mitigações de software podem depender de atualizações de +microcódigo ou firmware, o que requer ainda mais coordenação. + +.. _pt_BR_Contact: + +Contato +------- + +A equipe de segurança de hardware do kernel Linux é separada da equipe regular +de segurança do kernel Linux. + +A equipe lida apenas com o desenvolvimento de correções para problemas de +segurança de hardware sob embargo. Relatos de bugs de segurança de software puro +no kernel Linux não são tratados por esta equipe, e o autor do relato será +orientado a contatar a equipe regular de segurança do kernel Linux +(:ref:`Documentation/admin-guide/ `) em vez disso. + +A equipe pode ser contatada por e-mail em . Esta +é uma lista privada de oficiais de segurança que ajudarão você a coordenar uma +correção de acordo com o nosso processo documentado. + +A lista é criptografada e o e-mail para a lista pode ser enviado criptografado +por PGP ou S/MIME, e deve ser assinado com a chave PGP ou certificado S/MIME do +autor do relato. A chave PGP e o certificado S/MIME da equipe estão disponíveis +nas seguintes URLs: + + - PGP: https://www.kernel.org/static/files/hardware-security.asc + - S/MIME: https://www.kernel.org/static/files/hardware-security.crt + +Embora os problemas de segurança de hardware sejam frequentemente tratados pelo +fabricante de silício afetado, nós acolhemos o contato de pesquisadores ou +indivíduos que tenham identificado uma falha potencial de hardware. + +Oficiais de segurança de hardware +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A equipe atual de oficiais de segurança de hardware: + + - Linus Torvalds (Fellow da Linux Foundation) + - Greg Kroah-Hartman (Fellow da Linux Foundation) + - Thomas Gleixner (Fellow da Linux Foundation) + +Operação das listas de e-mail +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As listas de e-mail criptografadas que são usadas em nosso processo são +hospedadas na infraestrutura de TI da Linux Foundation. Ao fornecer este +serviço, os membros da equipe de operações de TI da Linux Foundation têm, +tecnicamente, a capacidade de acessar as informações sob embargo, mas são +obrigados à confidencialidade por seu contrato de trabalho. O pessoal de TI +da Linux Foundation também é responsável por operar e gerenciar o restante da +infraestrutura do kernel.org. + +O atual diretor de infraestrutura de projetos de TI da Linux Foundation é +Konstantin Ryabitsev. + + +Acordos de não divulgação +------------------------- + +A equipe de segurança de hardware do kernel Linux não é um órgão formal e, +portanto, é incapaz de celebrar quaisquer acordos de não divulgação. A +comunidade do kernel está ciente da natureza sensível de tais problemas e +oferece um Memorando de Entendimento em vez disso. + + +Memorando de Entendimento +------------------------- + +A comunidade do kernel Linux compreende profundamente a necessidade de manter +os problemas de segurança de hardware sob embargo para a coordenação entre +diferentes fornecedores de SO, distribuidores, fabricantes de silício e outras +partes. + +A comunidade do kernel Linux lidou com sucesso com problemas de segurança de +hardware no passado e possui os mecanismos necessários para permitir o +desenvolvimento compatível com a comunidade sob restrições de embargo. + +A comunidade do kernel Linux possui uma equipe dedicada de segurança de hardware +para o contato inicial, que supervisiona o processo de tratamento de tais +problemas sob as regras de embargo. + +A equipe de segurança de hardware identifica os desenvolvedores (especialistas no +domínio) que formarão a equipe de resposta inicial para um problema específico. +A equipe de resposta inicial pode trazer outros desenvolvedores (especialistas no +domínio) para resolver o problema da melhor maneira técnica. + +Todos os desenvolvedores envolvidos comprometem-se a aderir às regras de embargo +e a manter as informações recebidas em sigilo. A violação do compromisso levará à +exclusão imediata do problema atual e à remoção de todas as listas de e-mail +relacionadas. Além disso, a equipe de segurança de hardware também excluirá o +infrator de futuros problemas. O impacto dessa consequência é um impedimento +altamente eficaz em nossa comunidade. Caso ocorra uma violação, a equipe de +segurança de hardware informará as partes envolvidas imediatamente. Se você ou +qualquer outra pessoa tomar conhecimento de uma potencial violação, por favor, +relate-a imediatamente aos oficiais de segurança de hardware. + + +Processo +^^^^^^^^ + +Devido à natureza globalmente distribuída do desenvolvimento do kernel Linux, +reuniões presenciais são quase impossíveis para lidar com problemas de +segurança de hardware. Conferências telefônicas são difíceis de coordenar devido +a fusos horários e outros fatores, devendo ser usadas apenas quando estritamente +necessário. O e-mail criptografado tem se mostrado o método de comunicação mais +eficiente e seguro para esses tipos de problema. + +Início da divulgação +""""""""""""""""""""" + +A divulgação começa enviando um e-mail para a equipe de segurança de hardware +do kernel Linux, conforme a seção Contato acima. Este contato inicial deve +conter uma descrição do problema e uma lista de qualquer silício afetado +conhecido. Se a sua organização constrói ou distribui o hardware afetado, +incentivamos você a considerar também quais outros hardwares podem ser +afetados. A parte que faz a divulgação é responsável por contatar os +fabricantes de silício afetados em tempo hábil. + +A equipe de segurança de hardware fornecerá uma lista de e-mail criptografada +específica para o incidente, que será usada para a discussão inicial com o +relator, divulgação posterior e coordenação de correções. + +A equipe de segurança de hardware fornecerá à parte divulgadora uma lista de +desenvolvedores (especialistas no domínio) que devem ser informados inicialmente +sobre o problema após confirmar com os desenvolvedores que eles aderirão a +este Memorando de Entendimento e ao processo documentado. Esses desenvolvedores +formam a equipe de resposta inicial e serão responsáveis por lidar com o +problema após o contato inicial. A equipe de segurança de hardware apoia a +equipe de resposta, mas não está necessariamente envolvida no processo de +desenvolvimento de mitigações. + +Embora desenvolvedores individuais possam estar cobertos por um acordo de não +divulgação por meio de seu empregador, eles não podem celebrar acordos +individuais de não divulgação em seu papel como desenvolvedores do kernel +Linux. No entanto, eles concordarão em aderir a este processo documentado e ao +Memorando de Entendimento. + +A parte divulgadora deve fornecer uma lista de contatos para todas as outras +entidades que já foram, ou devem ser, informadas sobre o problema. Isso serve +a vários propósitos: + + - A lista de entidades informadas permite a comunicação em toda a + indústria, por exemplo, outros fornecedores de SO, fornecedores de HW, etc. + + - As entidades informadas podem ser contatadas para indicar especialistas + que devem participar do desenvolvimento da mitigação. + + - Se um especialista necessário para lidar com um problema for funcionário + de uma entidade listada ou membro de uma entidade listada, as equipes de + resposta podem solicitar a inclusão desse especialista por parte daquela + entidade. Isso garante que o especialista também faça parte da equipe de + resposta da entidade. + +Divulgação +"""""""""" + +A parte divulgadora fornece informações detalhadas à equipe de resposta inicial +por meio da lista de e-mail criptografada específica. + +A partir de nossa experiência, a documentação técnica desses problemas costuma +ser um ponto de partida suficiente, e esclarecimentos técnicos adicionais são +melhor feitos por e-mail. + +Desenvolvimento de mitigações +"""""""""""""""""""""""""""""" + +A equipe de resposta inicial configura uma lista de e-mail criptografada ou +reaproveita uma já existente, se apropriado. + +O uso de uma lista de e-mail é próximo ao processo normal de desenvolvimento +do Linux e tem sido usado com sucesso para desenvolver mitigações para vários +problemas de segurança de hardware no passado. + +A lista de e-mail opera da mesma forma que o desenvolvimento normal do Linux. +Os patches são publicados, discutidos, revisados e, se aprovados, aplicados a +um repositório git não público que é acessível apenas aos desenvolvedores +participantes por meio de uma conexão segura. O repositório contém o ramo +(branch) principal de desenvolvimento contra o kernel mainline e ramos de +retroporte (backport) para versões estáveis do kernel conforme necessário. + +A equipe de resposta inicial identificará outros especialistas da comunidade +de desenvolvedores do kernel Linux conforme necessário. Qualquer parte +envolvida pode sugerir a inclusão de outros especialistas, cada um dos quais +estará sujeito aos mesmos requisitos descritos acima. + +A inclusão de especialistas pode ocorrer a qualquer momento no processo de +desenvolvimento e precisa ser tratada em tempo hábil. + +Se um especialista for funcionário ou membro de uma entidade na lista de +divulgação fornecida pela parte divulgadora, a participação será solicitada +à entidade relevante. + +Caso contrário, a parte divulgadora será informada sobre a participação +dos especialistas. Os especialistas são cobertos pelo Memorando de Entendimento +e a parte divulgadora é solicitada a reconhecer a participação deles. No caso +de a parte divulgadora ter um motivo convincente para se opor, qualquer +objeção deve ser levantada no prazo de cinco dias úteis e resolvida com a +equipe do incidente imediatamente. Se a parte divulgadora não reagir dentro +de cinco dias úteis, isso é considerado como reconhecimento tácito. + +Após a equipe do incidente reconhecer ou resolver uma objeção, o especialista +é informado e integrado ao processo de desenvolvimento. + +Os participantes da lista não podem se comunicar sobre o problema fora da +lista de e-mail privada. Os participantes da lista não podem usar nenhum +recurso compartilhado (por exemplo, fazendas de compilação do empregador, +sistemas de IC, etc.) ao trabalhar em patches. + +Acesso antecipado +""""""""""""""""" + +Os patches discutidos e desenvolvidos na lista não podem ser distribuídos a +nenhum indivíduo que não seja membro da equipe de resposta, nem a nenhuma outra +organização. + +Para permitir que os fornecedores de silício afetados trabalhem com suas equipes +internas e parceiros da indústria em testes, validação e logística, a seguinte +exceção é fornecida: + + Representantes designados dos fornecedores de silício afetados têm permissão + para repassar os patches a qualquer momento para a equipe de resposta do + fornecedor de silício. O representante deve notificar a equipe de resposta + do kernel sobre o repasse. O fornecedor de silício afetado deve possuir e + manter seu próprio processo de segurança documentado para quaisquer patches + compartilhados com sua equipe de resposta que seja consistente com esta + política. + + A equipe de resposta do fornecedor de silício pode distribuir esses patches + aos seus parceiros da indústria e às suas equipes internas sob o processo + de segurança documentado do fornecedor de silício. O feedback dos parceiros + da indústria retorna ao fornecedor de silício e é comunicado por ele à + equipe de resposta do kernel. + + O repasse para a equipe de resposta do fornecedor de silício remove + qualquer responsabilidade civil ou legal da equipe de resposta do kernel + em relação à divulgação prematura que ocorra devido ao envolvimento das + equipes internas ou parceiros da indústria do fornecedor de silício. O + fornecedor de silício garante esta liberação de responsabilidade ao + concordar com este processo. + +Lançamento coordenado +""""""""""""""""""""" + +As partes envolvidas negociarão a data e a hora em que o embargo termina. Nesse +ponto, as mitigações preparadas são publicadas nas árvores de kernel relevantes. +Não há processo de pré-notificação: as mitigações são publicadas publicamente e +disponibilizadas para todos ao mesmo tempo. + +Embora entendamos que problemas de segurança de hardware exijam tempo de embargo +coordenado, o tempo de embargo deve ser restrito ao mínimo necessário para que +todas as partes envolvidas desenvolvam, testem e preparem suas mitigações. +Estender o tempo de embargo artificialmente para cumprir datas de palestras em +conferências ou outros motivos não técnicos cria mais trabalho e ônus para os +desenvolvedores e equipes de resposta envolvidos, pois os patches precisam ser +mantidos atualizados para acompanhar o desenvolvimento contínuo do kernel +upstream, o que pode criar alterações conflitantes. + +Atribuição de CVE +"""""""""""""""""" + +Nem a equipe de segurança de hardware nem a equipe de resposta inicial atribuem +CVEs, nem os CVEs são necessários para o processo de desenvolvimento. Se os CVEs +forem fornecidos pela parte divulgadora, eles poderão ser usados para fins de +documentação. + +Embaixadores do processo +------------------------ + +Para obter assistência com este processo, estabelecemos embaixadores em várias +organizações, que podem responder a perguntas sobre ou fornecer orientações +acerca do processo de relatórios e tratamento posterior. Os embaixadores não +estão envolvidos na divulgação de um problema específico, a menos que seja +solicitado por uma equipe de resposta ou por uma parte divulgada envolvida. +A lista atual de embaixadores: + + ============= ======================================================== + AMD Tom Lendacky + Ampere Darren Hart + ARM Catalin Marinas + IBM Power Madhavan Srinivasan + IBM Z Christian Borntraeger + Intel Tony Luck + Qualcomm Trilok Soni + RISC-V Palmer Dabbelt + Samsung Javier González + + Microsoft James Morris + Xen Andrew Cooper + + Canonical John Johansen + Debian Ben Hutchings + Oracle Konrad Rzeszutek Wilk + Red Hat Josh Poimboeuf + SUSE Jiri Kosina + + Google Kees Cook + + LLVM Nick Desaulniers + ============= ======================================================== + +Se você quiser que sua organização seja adicionada à lista de embaixadores, +entre em contato com a equipe de segurança de hardware. O embaixador indicado +deve compreender e apoiar totalmente o nosso processo e, idealmente, estar bem +conectado na comunidade do kernel Linux. + +Listas de e-mail criptografadas +------------------------------- + +Usamos listas de e-mail criptografadas para comunicação. O princípio de +operação dessas listas é que o e-mail enviado para a lista é criptografado +com a chave PGP da lista ou com o certificado S/MIME da lista. O software +da lista de e-mail descriptografa o e-mail e o recriptografa individualmente +para cada assinante com a chave PGP ou certificado S/MIME do assinante. +Detalhes sobre o software da lista de e-mail e a configuração usada para +garantir a segurança das listas e a proteção dos dados podem ser encontrados +aqui: https://korg.wiki.kernel.org/userdoc/remail. + +Listas de chaves +^^^^^^^^^^^^^^^^ + +Para o contato inicial, consulte a seção :ref:`pt_BR_Contact` acima. Para listas de +e-mail específicas de incidentes, a chave e o certificado S/MIME são transmitidos +aos assinantes por e-mail enviado a partir da lista específica. + +Inscrição em listas específicas de incidentes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A inscrição em listas específicas de incidentes é gerenciada pelas equipes de +resposta. As partes informadas que desejam participar da comunicação enviam +uma lista de potenciais especialistas para a equipe de resposta, para que esta +possa validar as solicitações de inscrição. + +Cada assinante precisa enviar uma solicitação de inscrição para a equipe de +resposta por e-mail. O e-mail deve estar assinado com a chave PGP ou o certificado +S/MIME do assinante. Se uma chave PGP for utilizada, ela deve estar disponível +em um servidor de chaves público e, idealmente, conectada à teia de confiança +(web of trust) PGP do kernel Linux. Veja também: +https://www.kernel.org/signature.html. + +A equipe de resposta verifica se a solicitação do assinante é válida e o +adiciona à lista. Após a inscrição, o assinante receberá e-mails da lista de +e-mail que são assinados com a chave PGP da lista ou com o certificado S/MIME +da lista. O cliente de e-mail do assinante pode extrair a chave PGP ou o +certificado S/MIME da assinatura para que o assinante possa enviar e-mails +criptografados para a lista. \ No newline at end of file diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 094575660fdb2f..7841eca6bf457a 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -74,6 +74,7 @@ gerenciamento de bugs e vulnerabilidades. :maxdepth: 1 Falhas de segurança + Problemas de hardware sob embargo CVEs Informações para mantenedores From e9dfc0f0a671cb0611a89380f0026ec76391f498 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Sat, 29 Aug 2026 13:24:34 -0300 Subject: [PATCH 0241/2286] docs: translations: pt_BR: update process translation files Fix minor typographical, punctuation, and untranslated text errors in the Brazilian Portuguese translations of Documentation/process/2.Process.rst, 3.Early-stage.rst, 4.Coding.rst, and 5.Posting.rst to better match the original text and improve readability. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260829162438.13039-3-danielmaraboo@gmail.com> --- .../translations/pt_BR/process/2.Process.rst | 12 ++++++------ .../translations/pt_BR/process/3.Early-stage.rst | 10 +++++----- .../translations/pt_BR/process/4.Coding.rst | 6 +++--- .../translations/pt_BR/process/5.Posting.rst | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Documentation/translations/pt_BR/process/2.Process.rst b/Documentation/translations/pt_BR/process/2.Process.rst index 5ff35f10aac799..03e288d469502b 100644 --- a/Documentation/translations/pt_BR/process/2.Process.rst +++ b/Documentation/translations/pt_BR/process/2.Process.rst @@ -75,16 +75,16 @@ Como exemplo, veja como ocorreu o ciclo de desenvolvimento da versão 5.4 (todas as datas são de 2019): ============== =============================== - Setembro 15 5.3 Lançamento estável do 5.3 + Setembro 15 Lançamento estável do 5.3 Setembro 30 5.4-rc1, fechamento da janela de integração Outubro 6 5.4-rc2 Outubro 13 5.4-rc3 Outubro 20 5.4-rc4 - October 27 5.4-rc5 + Outubro 27 5.4-rc5 Novembro 3 5.4-rc6 Novembro 10 5.4-rc7 Novembro 17 5.4-rc8 - Novembro 24 5.4 Lançamento estável do 5.4 + Novembro 24 Lançamento estável do 5.4 ============== =============================== Como os desenvolvedores decidem quando encerrar o ciclo de desenvolvimento @@ -330,7 +330,7 @@ A árvore de fontes do kernel contém o diretório drivers/staging/, onde reside muitos subdiretórios para drivers ou sistemas de arquivos que estão a caminho de serem adicionados à árvore do kernel. Eles permanecem em drivers/staging/ enquanto ainda precisam de mais trabalho; uma vez concluídos, podem ser movidos -para o kernel proper Esta é uma maneira de acompanhar drivers que não estão à +para o kernel propriamente dito. Esta é uma maneira de acompanhar drivers que não estão à altura dos padrões de codificação ou de qualidade do kernel Linux, mas que as pessoas podem querer usar e acompanhar o desenvolvimento. @@ -433,7 +433,7 @@ Existem algumas dicas que podem ajudar na sobrevivência na lista linux-kernel: caixa de entrada principal. É preciso ser capaz de ignorar o fluxo de e-mails por períodos prolongados de tempo. -- Não tente acompanhar cada conversa ninguém mais faz isso. É importante +- Não tente acompanhar cada conversa, ninguém mais faz isso. É importante filtrar tanto pelo tópico de interesse (embora note que conversas longas podem se desviar do assunto original sem que a linha de assunto do e-mail seja alterada) quanto pelas pessoas que estão participando. @@ -461,7 +461,7 @@ Existem algumas dicas que podem ajudar na sobrevivência na lista linux-kernel: ponto de encontro geral, mas não é o melhor lugar para encontrar desenvolvedores de todos os subsistemas. -O último ponto, encontrar a lista de discussão correta é um lugar comum +O último ponto, encontrar a lista de discussão correta, é um lugar comum onde os desenvolvedores iniciantes costumam errar. Alguém que faça uma pergunta relacionada a redes na lista linux-kernel quase certamente receberá uma sugestão educada para perguntar na lista netdev em seu lugar, já que essa é a diff --git a/Documentation/translations/pt_BR/process/3.Early-stage.rst b/Documentation/translations/pt_BR/process/3.Early-stage.rst index 74e741766b46bd..86c228353084bf 100644 --- a/Documentation/translations/pt_BR/process/3.Early-stage.rst +++ b/Documentation/translations/pt_BR/process/3.Early-stage.rst @@ -74,7 +74,7 @@ Discussão inicial Ao planejar um projeto de desenvolvimento do kernel, faz todo o sentido realizar discussões com a comunidade antes de iniciar a implementação. A comunicação -inicial pode economizar tempo e problemas de várias maneiras:number of ways: +inicial pode economizar tempo e problemas de várias maneiras: - Pode muito bem ser que o problema já seja tratado pelo kernel de maneiras que você não compreendeu. O kernel Linux é grande e possui uma série de @@ -121,8 +121,8 @@ comunidade do kernel. Alguns exemplos incluem: consideradas inseguras e não confiáveis. Essa preocupação (entre outras) manteve o AppArmor fora do kernel principal (*mainline*) por anos. -In each of these cases, a great deal of pain and extra work could have been -avoided with some early discussion with the kernel developers. +Em cada um desses casos, muito sofrimento e trabalho extra poderiam ter sido +evitados com alguma discussão inicial com os desenvolvedores do kernel. Como encontrar os mantenedores? @@ -200,8 +200,8 @@ prosseguir, mantendo a comunidade informada à medida que avança. Obter a aprovação oficial ------------------------- -Se o seu trabalho estiver sendo realizado em um ambiente corporativo como é o -caso da maior parte do trabalho no kernel do Linux —, você deve, obviamente, ter +Se o seu trabalho estiver sendo realizado em um ambiente corporativo, como é o +caso da maior parte do trabalho no kernel do Linux, você deve, obviamente, ter a permissão de gerentes devidamente autorizados antes de poder publicar os planos ou o código da sua empresa em uma lista de discussão pública. A publicação de código que não tenha sido liberado para lançamento sob uma diff --git a/Documentation/translations/pt_BR/process/4.Coding.rst b/Documentation/translations/pt_BR/process/4.Coding.rst index ca4c74774a9109..7c7567165725da 100644 --- a/Documentation/translations/pt_BR/process/4.Coding.rst +++ b/Documentation/translations/pt_BR/process/4.Coding.rst @@ -197,8 +197,8 @@ a ferramenta certa para o trabalho. Códigos que mostrem falta de atenção à concorrência terão um caminho difícil para entrar no mainline. -Regressions -*********** +Regressões +********** Um perigo final que vale a pena mencionar é este: pode ser tentador fazer uma alteração (que pode trazer grandes melhorias) que faça algo quebrar para os @@ -258,7 +258,7 @@ Note que nem todos os avisos do compilador ficam ativados por padrão. Compile o kernel com "make KCFLAGS=-W" para obter o conjunto completo. O kernel fornece várias opções de configuração que ativam recursos de -depuração; a maioria delas é encontrada no submanu "kernel hacking". Várias +depuração; a maioria delas é encontrada no submenu "kernel hacking". Várias dessas opções devem ser ativadas para qualquer kernel usado para fins de desenvolvimento ou teste. Em particular, você deve ativar: diff --git a/Documentation/translations/pt_BR/process/5.Posting.rst b/Documentation/translations/pt_BR/process/5.Posting.rst index 820a56b661dbb8..2ac7e5557805e4 100644 --- a/Documentation/translations/pt_BR/process/5.Posting.rst +++ b/Documentation/translations/pt_BR/process/5.Posting.rst @@ -37,7 +37,7 @@ Antes de criar patches ---------------------- Há uma série de coisas que devem ser feitas antes de você considerar o envio -de patches para la comunidade de desenvolvimento. Elas incluem: +de patches para a comunidade de desenvolvimento. Elas incluem: - Teste o código tanto quanto puder. Faça uso das ferramentas de depuração do kernel, garanta que o kernel seja compilado com todas as combinações @@ -106,7 +106,7 @@ que podem ajudar consideravelmente: - Como uma forma de reafirmar a diretriz acima: não misture diferentes tipos de alterações no mesmo patch. Se um único patch corrige uma falha crítica de - segurança, reorganiza algumas estruturas e reformatará o código, há uma grande + segurança, reorganiza algumas estruturas e reformata o código, há uma grande chance de que ele seja ignorado e a correção importante seja perdida. - Cada patch deve resultar em um kernel que compile e funcione corretamente; se @@ -199,7 +199,7 @@ a alteração em um sistema de controle de versão. Ele será seguido por: diff associará os nomes das funções às alterações, tornando o patch resultante mais fácil de ser lido por outras pessoas. -As tags já mencionadas brevemente acima são usados para fornecer +Os marcadores (tags) já mencionados brevemente acima são usados para fornecer informações sobre como o patch surgiu. Eles são descritos em detalhes no documento :ref:`Documentation/process/submitting-patches.rst `; o que se segue aqui é um breve resumo. @@ -215,7 +215,7 @@ documento com uma especificação implementada pelo patch:: Link: https://example.com/somewhere.html optional-other-stuff -De acordo com as orientações do Pinguim-Chefe, um marcador Link +De acordo com as orientações do Pinguim-Chefe, um marcador Link: só deve ser adicionado a um commit se ele levar a informações úteis que não são encontradas no próprio commit. @@ -279,7 +279,7 @@ Os marcadores de uso comum são: Tenha cuidado ao adicionar os marcadores mencionados acima aos seus patches, pois todos, exceto Cc:, Reported-by: e Suggested-by:, precisam de permissão explícita -fontes da pessoa nomeada. Para esses três, a permissão implícita é suficiente se +da pessoa nomeada. Para esses três, a permissão implícita é suficiente se a pessoa contribuiu para o kernel Linux usando esse nome e endereço de e-mail de acordo com os arquivos do lore ou o histórico de commits — e, no caso de Reported-by: e Suggested-by:, se fizeram o relato ou a sugestão publicamente. @@ -322,7 +322,7 @@ kernel incentiva as pessoas a pecarem pelo excesso, enviando cópias demais; nã assuma que as pessoas relevantes verão sua publicação nas listas de discussão. Em particular, as cópias devem ir para: -- O(s) mantenedor(es) do(s) subsistema(s) afetado(s). Como descrito antes, o + - O(s) mantenedor(es) do(s) subsistema(s) afetado(s). Como descrito antes, o arquivo MAINTAINERS é o primeiro lugar para procurar por essas pessoas. - Outros desenvolvedores que estiveram trabalhando na mesma área — especialmente From d793bcfca6bb493eb8e93900285f951a0c9dbf7c Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Sat, 29 Aug 2026 13:24:35 -0300 Subject: [PATCH 0242/2286] docs: translations: pt_BR: fix missing text and formatting in process docs Add a missing paragraph about code review in 6.Followthrough.rst and fix a grammatical error. Fix formatting and translate an English snippet in 8.Conclusion.rst. Fix several instances of omitted 'xyzzy' placeholders in code examples, restore truncated sentences, and fix missing links in adding-syscalls.rst. Fix typos in applying-patches.rst. Fix a typo and fix a broken markdown link to reStructuredText format in backporting.rst. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260829162438.13039-4-danielmaraboo@gmail.com> --- .../pt_BR/process/6.Followthrough.rst | 14 ++++- .../pt_BR/process/8.Conclusion.rst | 3 +- .../pt_BR/process/adding-syscalls.rst | 54 ++++++++++--------- .../pt_BR/process/applying-patches.rst | 4 +- .../pt_BR/process/backporting.rst | 4 +- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/Documentation/translations/pt_BR/process/6.Followthrough.rst b/Documentation/translations/pt_BR/process/6.Followthrough.rst index d6bdaa2cb8a4df..dbdf479bb063c6 100644 --- a/Documentation/translations/pt_BR/process/6.Followthrough.rst +++ b/Documentation/translations/pt_BR/process/6.Followthrough.rst @@ -58,7 +58,17 @@ fácil se você mantiver algumas coisas em mente: de codificação e pedidos para refatorar parte do seu código em seções compartilhadas do kernel. Uma das funções dos mantenedores é manter as coisas com a mesma aparência. Às vezes, isso significa que aquele truque inteligente - (*clever hack*) em seu driver para contornar um problema + (*clever hack*) em seu driver para contornar um problema na verdade precisa + se tornar um recurso generalizado do kernel, pronto para a próxima vez. + +O que tudo isso significa é que, quando os revisores lhe enviam comentários, +você precisa prestar atenção às observações técnicas que eles estão fazendo. +Não deixe que a forma como eles se expressam, ou o seu próprio orgulho, impeçam +que isso aconteça. Quando você receber comentários de revisão em um patch, +reserve um tempo para entender o que o revisor está tentando dizer. Se +possível, corrija as coisas que o revisor está pedindo para você corrigir. E +responda ao revisor: agradeça-o e descreva como você responderá às suas +perguntas. Note que você não precisa concordar com todas as mudanças sugeridas pelos revisores. Se você acredita que o revisor entendeu mal o seu código, explique @@ -67,7 +77,7 @@ sugerida, descreva-a e justifique a sua solução para o problema. Se as suas explicações fizerem sentido, o revisor as aceitará. Contudo, caso a sua explicação não seja persuasiva — especialmente se outros começarem a concordar com o revisor —, reserve um tempo para repensar as coisas. Pode ser fácil ficar -ceguificado por sua própria solução para um problema, a ponto de não perceber +cego por sua própria solução para um problema, a ponto de não perceber que algo está fundamentalmente errado ou que, talvez, você não esteja sequer resolvendo o problema certo. diff --git a/Documentation/translations/pt_BR/process/8.Conclusion.rst b/Documentation/translations/pt_BR/process/8.Conclusion.rst index d5af31e7c4d9b0..2696ea16756e2f 100644 --- a/Documentation/translations/pt_BR/process/8.Conclusion.rst +++ b/Documentation/translations/pt_BR/process/8.Conclusion.rst @@ -21,6 +21,7 @@ encontradas através do índice do kernel do LWN em: https://lwn.net/Kernel/Index/ Além disso, um recurso valioso para os desenvolvedores do kernel é: + https://kernelnewbies.org/ E, claro, não se deve esquecer o https://kernel.org/, o local definitivo @@ -29,7 +30,7 @@ para informações sobre os lançamentos do kernel. Há uma série de livros sobre o desenvolvimento do kernel: Linux Device Drivers, 3rd Edition (Jonathan Corbet, Alessandro - Rubini, and Greg Kroah-Hartman). Online at + Rubini, e Greg Kroah-Hartman). Disponível online em https://lwn.net/Kernel/LDD3/. Linux Kernel Development (Robert Love). diff --git a/Documentation/translations/pt_BR/process/adding-syscalls.rst b/Documentation/translations/pt_BR/process/adding-syscalls.rst index cdf8b5033765a1..37b0dd0e2df58b 100644 --- a/Documentation/translations/pt_BR/process/adding-syscalls.rst +++ b/Documentation/translations/pt_BR/process/adding-syscalls.rst @@ -122,10 +122,10 @@ como o handle (identificador) para esse objeto -- não invente um novo tipo de handle de objeto para o espaço do usuário quando o kernel já possui mecanismos e semânticas bem definidas para o uso de descritores de arquivo. -Se a sua nova chamada de sistema (2) de fato retornar un novo descritor de +Se a sua nova chamada de sistema xyzzy(2) de fato retornar um novo descritor de arquivo, então o argumento de flags deve incluir um valor que seja equivalente a definir ``O_CLOEXEC`` no novo FD. Isso torna possível para o espaço do usuário -fechar a janela de tempo entre a chamada ``()`` e a execução de +fechar a janela de tempo entre a chamada ``xyzzy()`` e a execução de ``fcntl(fd, F_SETFD, FD_CLOEXEC)``, onde um ``fork()`` e ``execve()`` inesperados em outra thread poderiam vazar um descritor para o programa executado. (Contudo, resista à tentação de reutilizar o valor real da constante ``O_CLOEXEC``, pois @@ -138,7 +138,7 @@ deve considerar o que significa usar a família de chamadas de sistema pronto para leitura ou escrita é a maneira normal de o kernel indicar ao espaço do usuário que um evento ocorreu no objeto correspondente do kernel. -Se a sua nova chamada de sistema (2) envolver um argumento de nome de arquivo +Se a sua nova chamada de sistema xyzzy(2) envolver um argumento de nome de arquivo (filename):: int sys_xyzzy(const char __user *path, ..., unsigned int flags); @@ -152,18 +152,18 @@ o arquivo em questão; em particular, permite que o espaço do usuário solicite funcionalidade para um descritor de arquivo já aberto usando a flag ``AT_EMPTY_PATH``, fornecendo efetivamente uma operação fxyzzy(3) de graça:: - - xyzzyat(AT_FDCWD, path, ..., 0) é equivalente a (path,...) + - xyzzyat(AT_FDCWD, path, ..., 0) é equivalente a xyzzy(path,...) - xyzzyat(fd, "", ..., AT_EMPTY_PATH) é equivalente a fxyzzy(fd, ...) (Para mais detalhes sobre a justificativa das chamadas \*at(), veja a página de manual :manpage:`openat(2)`; para um exemplo de AT_EMPTY_PATH, veja a página de manual :manpage:`fstatat(2)`.) -Se a sua nova chamada de sistema (2) envolver um parâmetro que descreve um +Se a sua nova chamada de sistema xyzzy(2) envolver um parâmetro que descreve um deslocamento (offset) dentro de um arquivo, mude o seu tipo para ``loff_t`` para que offsets de 64 bits possam ser suportados mesmo em arquiteturas de 32 bits. -Se a sua nova chamada de sistema (2) envolver funcionalidades privilegiadas, +Se a sua nova chamada de sistema xyzzy(2) envolver funcionalidades privilegiadas, ela precisa ser governada pelo bit de capacidade (capability) do Linux apropriado (verificado com uma chamada a ``capable()``), conforme descrito na página de manual :manpage:`capabilities(7)`. Escolha um bit de capacidade existente que governe @@ -172,7 +172,7 @@ apenas uma vaga relação sob o mesmo bit, pois isso vai contra o propósito das capabilities de dividir o poder do root. Em particular, evite adicionar novos usos para a capacidade ``CAP_SYS_ADMIN``, que já é excessivamente generalista. -Se a sua nova chamada de sistema (2) manipular um processo diferente do +Se a sua nova chamada de sistema xyzzy(2) manipular um processo diferente do processo que a chamou, ela deve ser restrita (usando uma chamada a ``ptrace_may_access()``) para que apenas um processo chamador com as mesmas permissões do processo alvo, ou com as capacidades necessárias, possa manipular @@ -211,7 +211,7 @@ kernel, devem sempre ser enviadas com cópia (cc'ed) para linux-api@vger.kernel. Implementação Genérica de Chamadas de Sistema --------------------------------------------- -O ponto de entrada principal para a sua nova chamada de sistema (2) será chamado +O ponto de entrada principal para a sua nova chamada de sistema xyzzy(2) será chamado de ``sys_xyzzy()``, mas você deve adicionar esse ponto de entrada com a macro ``SYSCALL_DEFINEn()`` apropriada, em vez de fazer isso explicitamente. O 'n' indica o número de argumentos da chamada de sistema, e a macro recebe o nome da @@ -242,7 +242,7 @@ O arquivo ``kernel/sys_ni.c`` fornece uma implementação de stub de fallback pa cada chamada de sistema, retornando ``-ENOSYS``. Adicione a sua nova chamada de sistema aqui também:: - COND_SYSCALL(sys_xyzzy); + COND_SYSCALL(xyzzy); A sua nova funcionalidade de kernel, e a chamada de sistema que a controla, deve normalmente ser opcional, portanto adicione uma opção ``CONFIG`` (tipicamente em @@ -259,7 +259,7 @@ normalmente ser opcional, portanto adicione uma opção ``CONFIG`` (tipicamente Para resumir, você precisa de um commit que inclua: - Opção ``CONFIG`` para a nova função, normalmente em ``init/Kconfig`` - - ``SYSCALL_DEFINEn(, ...)`` para o ponto de entrada + - ``SYSCALL_DEFINEn(xyzzy, ...)`` para o ponto de entrada - Protótipo correspondente em ``include/linux/syscalls.h`` - Entrada na tabela genérica em ``include/uapi/asm-generic/unistd.h`` - Stub de fallback em ``kernel/sys_ni.c`` @@ -289,7 +289,7 @@ ajustar ``arch/*/kernel/Makefile.syscalls``. Como o ``scripts/syscall.tbl`` serve como uma tabela de syscall comum para múltiplas arquiteturas, uma nova entrada é necessária nesta tabela:: - 468 common sys_xyzzy + 468 common xyzzy sys_xyzzy Note que adicionar uma entrada ao ``scripts/syscall.tbl`` com a ABI "common" também afeta todas as arquiteturas que compartilham essa tabela. Para alterações @@ -304,7 +304,7 @@ correspondentes também devem ser feitas em ``arch/*/kernel/Makefile.syscalls``: Para resumir, você precisa de um commit que inclua: - Opção ``CONFIG`` para a nova função, normalmente em ``init/Kconfig`` - - ``SYSCALL_DEFINEn(, ...)`` para o ponto de entrada + - ``SYSCALL_DEFINEn(xyzzy, ...)`` para o ponto de entrada - Protótipo correspondente em ``include/linux/syscalls.h`` - Nova entrada em ``scripts/syscall.tbl`` - (Se necessário) Atualizações de Makefile em ``arch/*/kernel/Makefile.syscalls`` @@ -320,11 +320,11 @@ de sistema não seja especial de alguma forma (veja abaixo), isso envolve uma entrada "common" (para x86_64 e x32) em ``arch/x86/entry/syscalls/syscall_64.tbl``:: - 333 common sys_xyzzy + 333 common xyzzy sys_xyzzy e uma entrada "i386" em ``arch/x86/entry/syscalls/syscall_32.tbl``:: - 380 i386 sys_xyzzy + 380 i386 xyzzy sys_xyzzy Novamente, esses números estão sujeitos a alterações caso ocorram conflitos na janela de mesclagem (merge window) relevante. @@ -414,7 +414,7 @@ a versão compat; a entrada em ``include/uapi/asm-generic/unistd.h`` deve usar Para resumir, você precisa de: - - uma macro ``COMPAT_SYSCALL_DEFINEn(, ...)`` para o ponto de entrada compat + - uma macro ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` para o ponto de entrada compat - protótipo correspondente em ``include/linux/compat.h`` - (se necessário) struct de mapeamento de 32 bits em ``include/linux/compat.h`` - instância de ``__SC_COMP``, e não de ``__SYSCALL``, em @@ -433,11 +433,11 @@ Você precisa estender a entrada em ``scripts/syscall.tbl`` com uma coluna extra para indicar que um programa de espaço do usuário de 32 bits rodando em um kernel de 64 bits deve atingir o ponto de entrada compat:: - 468 common sys_xyzzy compat_sys_xyzzy + 468 common xyzzy sys_xyzzy compat_sys_xyzzy Para resumir, você precisa de: - - ``COMPAT_SYSCALL_DEFINEn(, ...)`` para o ponto de entrada compat + - ``COMPAT_SYSCALL_DEFINEn(xyzzy, ...)`` para o ponto de entrada compat - Protótipo correspondente em ``include/linux/compat.h`` - Modificação da entrada em ``scripts/syscall.tbl`` para incluir uma coluna "compat" extra @@ -454,7 +454,7 @@ compatibilidade voltadas para o espaço do usuário de 32 bits (AArch32): ``arch/arm64/tools/syscall_32.tbl``. Você precisa adicionar uma linha adicional a esta tabela especificando o ponto de entrada compat:: - 468 common sys_xyzzy compat_sys_xyzzy + 468 common xyzzy sys_xyzzy compat_sys_xyzzy Chamadas de Sistema de Compatibilidade (x86) @@ -467,7 +467,7 @@ Primeiro, a entrada em ``arch/x86/entry/syscalls/syscall_32.tbl`` ganha uma coluna extra para indicar que um programa de espaço do usuário de 32 bits rodando em um kernel de 64 bits deve atingir o ponto de entrada compat:: - 380 i386 sys_xyzzy __ia32_compat_sys_xyzzy + 380 i386 xyzzy sys_xyzzy __ia32_compat_sys_xyzzy Segundo, você precisa definir o que deve acontecer para a versão da ABI x32 da nova chamada de sistema. Há uma escolha aqui: o layout dos argumentos deve @@ -479,9 +479,9 @@ corresponder à versão de 32 bits, e a entrada em ``arch/x86/entry/syscalls/syscall_64.tbl`` é dividida para que os programas x32 atinjam o wrapper de compatibilidade:: - 333 64 sys_xyzzy + 333 64 xyzzy sys_xyzzy ... - 555 x32 __x32_compat_sys_xyzzy + 555 x32 xyzzy __x32_compat_sys_xyzzy Se não houver ponteiros envolvidos, então é preferível reutilizar a chamada de sistema de 64 bits para a ABI x32 (e, consequentemente, a entrada em @@ -518,14 +518,14 @@ Para x86_64, isso é implementado como um ponto de entrada ``stub_xyzzy`` em ``arch/x86/entry/entry_64.S``, e a entrada correspondente na tabela de syscalls (``arch/x86/entry/syscalls/syscall_64.tbl``) é ajustada para refletir:: - 333 common stub_xyzzy + 333 common xyzzy stub_xyzzy O equivalente para programas de 32 bits executados em um kernel de 64 bits é normalmente chamado de ``stub32_xyzzy`` e implementado em ``arch/x86/entry/entry_64_compat.S``, com o respectivo ajuste na tabela de syscalls em ``arch/x86/entry/syscalls/syscall_32.tbl``:: - 380 i386 sys_xyzzy stub32_xyzzy + 380 i386 xyzzy sys_xyzzy stub32_xyzzy Se a chamada de sistema precisar de uma camada de compatibilidade (como na seção anterior), a versão ``stub32_`` precisará chamar a versão @@ -579,12 +579,16 @@ espaço do usuário, o cabeçalho correspondente precisará ser instalado para compilar o teste. Certifique-se de que o autoteste seja executado com sucesso em todas as -arquiteturas suportadas. Por exemplo, verifique se ele funciona quando compitado +arquiteturas suportadas. Por exemplo, verifique se ele funciona quando compilado como um programa ABI x86_64 (-m64), x86_32 (-m32) e x32 (-mx32). Para testes mais extensos e minuciosos de novas funcionalidades, você também deve considerar a adição de testes ao Linux Test Project ou ao projeto -xfstests para alterações relacionadas +xfstests para alterações relacionadas a sistemas de arquivos. + + - https://linux-test-project.github.io/ + - git://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git + Página de Manual (Man Page) --------------------------- diff --git a/Documentation/translations/pt_BR/process/applying-patches.rst b/Documentation/translations/pt_BR/process/applying-patches.rst index 313401bc23357c..37a8d804d0c720 100644 --- a/Documentation/translations/pt_BR/process/applying-patches.rst +++ b/Documentation/translations/pt_BR/process/applying-patches.rst @@ -12,7 +12,7 @@ Autor Original: manualmente, você quase certamente desejará considerar o uso do Git. Uma pergunta feita com frequência na Linux Kernel Mailing List é como aplicar -an patch ao kernel ou, mais especificamente, a qual kernel base um patch para +um patch ao kernel ou, mais especificamente, a qual kernel base um patch para uma das muitas árvores/branches deve ser aplicado. Esperamos que este documento explique isso a você. @@ -171,7 +171,7 @@ fazer a alteração para fazê-la caber). O arquivo resultante pode ou não estar correto, dependendo do motivo pelo qual o arquivo estava diferente do esperado. -Isso geralmente acontece se você tentar aplicar un patch que foi gerado contra uma +Isso geralmente acontece se você tentar aplicar um patch que foi gerado contra uma versão de kernel diferente daquela que você está tentando modificar. Se você receber uma mensagem como ``Hunk #3 FAILED at 2387.``, significa que o diff --git a/Documentation/translations/pt_BR/process/backporting.rst b/Documentation/translations/pt_BR/process/backporting.rst index ce3f9fb4fc5ba8..afcff708570095 100644 --- a/Documentation/translations/pt_BR/process/backporting.rst +++ b/Documentation/translations/pt_BR/process/backporting.rst @@ -357,7 +357,7 @@ Processo de resolução ~~~~~~~~~~~~~~~~~~~~~ Às vezes, a coisa mais fácil a fazer é apenas remover tudo, exceto a primeira -parteda do conflito, deixando o arquivo essencialmente inalterado, e aplicar +parte do conflito, deixando o arquivo essencialmente inalterado, e aplicar as alterações manualmente. Talvez o patch esteja alterando um argumento de chamada de função de ``0`` para ``1``, enquanto uma alteração conflitante adicionou um parâmetro totalmente novo (e insignificante) ao final da lista de @@ -403,7 +403,7 @@ de volta (``git mv`` e commitando novamente) e, finalmente, esmagar (squash) o resultado usando ``git rebase -i`` (veja o `tutorial de rebase`_) para que ele apareça como um único commit quando você terminar. -.. _tutorial de rebase: [https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec](https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec) +.. _tutorial de rebase: https://medium.com/@slamflipstrom/a-beginners-guide-to-squashing-commits-with-git-rebase-8185cf6e62ec Pegadinhas ---------- From 7752746ab9e369ea2e72ea8f36896ec448ae5070 Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Mon, 7 Sep 2026 07:34:21 +0200 Subject: [PATCH 0243/2286] nilfs2: remove '#if 0' code blocks There are two instances of '#if 0' code blocks that are dead code. Remove these code blocks. Additionally, remove a commented out section containing similar code as the '#if 0' block and unnecessary brackets around a single-statement if per checkpatch rules. Signed-off-by: Joshua Crofts Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/inode.c | 7 ------- fs/nilfs2/super.c | 8 ++------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 34e6096069ad67..778274203f4ca1 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -342,8 +342,6 @@ struct inode *nilfs_new_inode(struct inode *dir, umode_t mode) ii->i_flags = nilfs_mask_flags( mode, NILFS_I(dir)->i_flags & NILFS_FL_INHERITED); - /* ii->i_file_acl = 0; */ - /* ii->i_dir_acl = 0; */ ii->i_dir_start_lookup = 0; nilfs_set_inode_flags(inode); inode->i_generation = get_random_u32(); @@ -422,11 +420,6 @@ int nilfs_read_inode_common(struct inode *inode, inode->i_blocks = le64_to_cpu(raw_inode->i_blocks); ii->i_flags = le32_to_cpu(raw_inode->i_flags); -#if 0 - ii->i_file_acl = le32_to_cpu(raw_inode->i_file_acl); - ii->i_dir_acl = S_ISREG(inode->i_mode) ? - 0 : le32_to_cpu(raw_inode->i_dir_acl); -#endif ii->i_dir_start_lookup = 0; inode->i_generation = le32_to_cpu(raw_inode->i_generation); diff --git a/fs/nilfs2/super.c b/fs/nilfs2/super.c index 893a504cb80c8a..89fa6faa34d254 100644 --- a/fs/nilfs2/super.c +++ b/fs/nilfs2/super.c @@ -824,13 +824,9 @@ static int nilfs_setup_super(struct super_block *sb, int is_mount) max_mnt_count = le16_to_cpu(sbp[0]->s_max_mnt_count); mnt_count = le16_to_cpu(sbp[0]->s_mnt_count); - if (nilfs->ns_mount_state & NILFS_ERROR_FS) { + if (nilfs->ns_mount_state & NILFS_ERROR_FS) nilfs_warn(sb, "mounting fs with errors"); -#if 0 - } else if (max_mnt_count >= 0 && mnt_count >= max_mnt_count) { - nilfs_warn(sb, "maximal mount count reached"); -#endif - } + if (!max_mnt_count) sbp[0]->s_max_mnt_count = cpu_to_le16(NILFS_DFL_MAX_MNT_COUNT); From c59ddba036a4bc3c0f5705dd6b37c7889b191bcc Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 11:54:27 +0530 Subject: [PATCH 0244/2286] nilfs2: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/sufile.c | 2 +- fs/nilfs2/super.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c index eceedca0269725..6918be6fce2eb3 100644 --- a/fs/nilfs2/sufile.c +++ b/fs/nilfs2/sufile.c @@ -1050,7 +1050,7 @@ ssize_t nilfs_sufile_set_suinfo(struct inode *sufile, void *buf, * len: number of Bytes to trim from start * minlen: minimum extent length in Bytes * - * Decription: nilfs_sufile_trim_fs goes through all segments containing bytes + * Description: nilfs_sufile_trim_fs goes through all segments containing bytes * from start to start+len. start is rounded up to the next block boundary * and start+len is rounded down. For each clean segment blkdev_issue_discard * function is invoked. diff --git a/fs/nilfs2/super.c b/fs/nilfs2/super.c index 89fa6faa34d254..2e3b591c117c90 100644 --- a/fs/nilfs2/super.c +++ b/fs/nilfs2/super.c @@ -654,7 +654,7 @@ static int nilfs_statfs(struct dentry *dentry, struct kstatfs *buf) /* * If nilfs_palloc_count_max_entries() returns * -ERANGE error code then we simply treat - * curent inodes count as maximum possible and + * current inodes count as maximum possible and * zero as free inodes value. */ nmaxinodes = atomic64_read(&root->inodes_count); From 02a2db115989749b945fc1dc3e935a4a07eebb8e Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 3 Sep 2026 13:52:19 -0500 Subject: [PATCH 0245/2286] irqchip/pruss-intc: Use match data directly The match data is fetched before the instance data is available, but it is not used until after. Skip the temporary variable and fetch the match data after it has a place to be stored. Signed-off-by: Andrew Davis Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260903185220.2014861-1-afd@ti.com --- drivers/irqchip/irq-pruss-intc.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c index 81078d56f38dc5..cc3a7c21c29047 100644 --- a/drivers/irqchip/irq-pruss-intc.c +++ b/drivers/irqchip/irq-pruss-intc.c @@ -516,24 +516,21 @@ static const char * const irq_names[MAX_NUM_HOST_IRQS] = { static int pruss_intc_probe(struct platform_device *pdev) { - const struct pruss_intc_match_data *data; struct device *dev = &pdev->dev; struct pruss_intc *intc; struct pruss_host_irq_data *host_data; int i, irq, ret; u8 max_system_events, irqs_reserved = 0; - data = of_device_get_match_data(dev); - if (!data) - return -ENODEV; - - max_system_events = data->num_system_events; - intc = devm_kzalloc(dev, sizeof(*intc), GFP_KERNEL); if (!intc) return -ENOMEM; - intc->soc_config = data; + intc->soc_config = of_device_get_match_data(dev); + if (!intc->soc_config) + return -ENODEV; + max_system_events = intc->soc_config->num_system_events; + intc->dev = dev; platform_set_drvdata(pdev, intc); From c23c3a1ec322f8bb9ed73bc1262d1083e76df23b Mon Sep 17 00:00:00 2001 From: Andrew Davis Date: Thu, 3 Sep 2026 13:52:20 -0500 Subject: [PATCH 0246/2286] irqchip/pruss-intc: Use scoped lock guard and devm_mutex_init Scoped locking simplifies the return path in a spot, and removes a couple lines in another couple spots. The devm mutex init will call mutex_destroy() for us on remove, which only really matters when CONFIG_DEBUG_MUTEXES is set, but it is nice to do anyway. Signed-off-by: Andrew Davis Signed-off-by: Thomas Gleixner Reviewed-by: Radu Rendec Link: https://patch.msgid.link/20260903185220.2014861-2-afd@ti.com --- drivers/irqchip/irq-pruss-intc.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/drivers/irqchip/irq-pruss-intc.c b/drivers/irqchip/irq-pruss-intc.c index cc3a7c21c29047..5a3e9e5bccbead 100644 --- a/drivers/irqchip/irq-pruss-intc.c +++ b/drivers/irqchip/irq-pruss-intc.c @@ -181,7 +181,7 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq) u8 ch, host, reg_idx; u32 val; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); intc->event_channel[hwirq].ref_count++; @@ -206,8 +206,6 @@ static void pruss_intc_map(struct pruss_intc *intc, unsigned long hwirq) dev_dbg(dev, "mapped system_event = %lu channel = %d host = %d", hwirq, ch, host); - - mutex_unlock(&intc->lock); } /** @@ -224,7 +222,7 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq) u8 ch, host, reg_idx; u32 val; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); ch = intc->event_channel[hwirq].value; host = intc->channel_host[ch].value; @@ -251,8 +249,6 @@ static void pruss_intc_unmap(struct pruss_intc *intc, unsigned long hwirq) dev_dbg(intc->dev, "unmapped system_event = %lu channel = %d host = %d\n", hwirq, ch, host); - - mutex_unlock(&intc->lock); } static void pruss_intc_init(struct pruss_intc *intc) @@ -376,17 +372,15 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event, int channel, int host) { struct device *dev = intc->dev; - int ret = 0; - mutex_lock(&intc->lock); + guard(mutex)(&intc->lock); /* check if sysevent already assigned */ if (intc->event_channel[event].ref_count > 0 && intc->event_channel[event].value != channel) { dev_err(dev, "event %d (req. ch %d) already assigned to channel %d\n", event, channel, intc->event_channel[event].value); - ret = -EBUSY; - goto unlock; + return -EBUSY; } /* check if channel already assigned */ @@ -394,16 +388,13 @@ static int pruss_intc_validate_mapping(struct pruss_intc *intc, int event, intc->channel_host[channel].value != host) { dev_err(dev, "channel %d (req. host %d) already assigned to host %d\n", channel, host, intc->channel_host[channel].value); - ret = -EBUSY; - goto unlock; + return -EBUSY; } intc->event_channel[event].value = channel; intc->channel_host[channel].value = host; -unlock: - mutex_unlock(&intc->lock); - return ret; + return 0; } static int @@ -550,7 +541,9 @@ static int pruss_intc_probe(struct platform_device *pdev) pruss_intc_init(intc); - mutex_init(&intc->lock); + ret = devm_mutex_init(dev, &intc->lock); + if (ret) + return ret; intc->domain = irq_domain_create_linear(dev_fwnode(dev), max_system_events, &pruss_intc_irq_domain_ops, intc); From 726285316f8fd8965d360ea6546da9b2677e7c7c Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:23:28 +0530 Subject: [PATCH 0247/2286] irqchip: Fix typos and repeated words in comments Fix typos and repeated words in comments as reported by scripts/checkpatch.pl. [ tglx: Fold the two trivial patches ] Signed-off-by: Hemanth Selam Signed-off-by: Thomas Gleixner Assisted-by: Cursor:claude-opus-5 Link: https://patch.msgid.link/20260907065328.32009-1-hemanth.selam@gmail.com Link: https://patch.msgid.link/20260907065659.12034-1-hemanth.selam@gmail.com --- drivers/irqchip/irq-al-fic.c | 2 +- drivers/irqchip/irq-gic-v3.c | 2 +- drivers/irqchip/irq-mtk-cirq.c | 2 +- drivers/irqchip/irq-sifive-plic.c | 2 +- drivers/irqchip/irq-vic.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/irqchip/irq-al-fic.c b/drivers/irqchip/irq-al-fic.c index d10ac9b63c9924..760317db9dba70 100644 --- a/drivers/irqchip/irq-al-fic.c +++ b/drivers/irqchip/irq-al-fic.c @@ -180,7 +180,7 @@ static int al_fic_register(struct device_node *node, * @name: name of the fic * @parent_irq: interrupt of parent * - * This API will configure the fic hardware to to work in wire mode. + * This API will configure the fic hardware to work in wire mode. * In wire mode, fic hardware is generating a wire ("wired") interrupt. * Interrupt can be generated based on positive edge or level - configuration is * to be determined based on connected hardware to this fic. diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index b0fca8f01cd408..19a15c2ea61b11 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -687,7 +687,7 @@ static void gic_eoimode1_eoi_irq(struct irq_data *d) { /* * No need to deactivate an LPI, or an interrupt that - * is is getting forwarded to a vcpu. + * is getting forwarded to a vcpu. */ if (irqd_to_hwirq(d) >= 8192 || irqd_is_forwarded_to_vcpu(d)) return; diff --git a/drivers/irqchip/irq-mtk-cirq.c b/drivers/irqchip/irq-mtk-cirq.c index 914d1d639fe303..d30c34ce0f5685 100644 --- a/drivers/irqchip/irq-mtk-cirq.c +++ b/drivers/irqchip/irq-mtk-cirq.c @@ -247,7 +247,7 @@ static int mtk_cirq_suspend(void *data) writel_relaxed(mask, reg); } - /* set edge_only mode, record edge-triggerd interrupts */ + /* set edge_only mode, record edge-triggered interrupts */ /* enable cirq */ reg = mtk_cirq_reg(cirq_data, CIRQ_CONTROL); value = readl_relaxed(reg); diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c index 5b0dac1048140e..a7cddadddf40ff 100644 --- a/drivers/irqchip/irq-sifive-plic.c +++ b/drivers/irqchip/irq-sifive-plic.c @@ -452,7 +452,7 @@ static irq_hw_number_t cp100_get_hwirq(struct plic_handler *handler, void __iome return 0; /* - * Interrupts delievered to hardware still become pending, but only + * Interrupts delivered to hardware still become pending, but only * interrupts that are both pending and enabled can be claimed. * Clearing the enable bit for all interrupts but the first pending * one avoids a hardware bug that occurs during read from the claim diff --git a/drivers/irqchip/irq-vic.c b/drivers/irqchip/irq-vic.c index e38104c5064e6b..607e3284f70098 100644 --- a/drivers/irqchip/irq-vic.c +++ b/drivers/irqchip/irq-vic.c @@ -478,7 +478,7 @@ static void __init __vic_init(void __iomem *base, int parent_irq, int irq_start, /** * vic_init() - initialise a vectored interrupt controller * @base: iomem base address - * @irq_start: starting interrupt number, must be muliple of 32 + * @irq_start: starting interrupt number, must be multiple of 32 * @vic_sources: bitmask of interrupt sources to allow * @resume_sources: bitmask of interrupt sources to allow for resume */ From 0aa45dce2afe6960865de1f32fabd716efaa6fdb Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:44 +0800 Subject: [PATCH 0248/2286] irqdomain: Set IRQ_DOMAIN_FLAG_DESTROY_GC in __irq_domain_instantiate() When a driver uses irq_domain_instantiate() with dgc_info to create generic irq chips, IRQ_DOMAIN_FLAG_DESTROY_GC is required so that irq_domain_remove() can clean up those generic chips. All existing in-tree callers manually set this flag today, but this pattern is error-prone. A future new caller forgetting to set the flag would leave generic chips allocated by irq_domain_alloc_generic_chips() leaked on domain removal. Set IRQ_DOMAIN_FLAG_DESTROY_GC right after irq_domain_alloc_generic_chips() succeeds inside __irq_domain_instantiate(). This makes automatic cleanup the default for all users that provide dgc_info via irq_domain_instantiate(). This is the correct location for the flag because: - irq_domain_instantiate() is a high-level wrapper which internally allocates the generic chips, so it should also take responsibility for arranging their cleanup. - Setting the flag in irq_domain_alloc_generic_chips() would affect legacy callers like __irq_alloc_domain_generic_chips(), some of which have custom cleanup paths that manually free the generic chips (e.g. gpio-tb10x does kfree(domain->gc) before irq_domain_remove()), leading to use-after-free. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-2-fuqingshuang@kylinos.cn --- kernel/irq/irqdomain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c index 57c819da30c219..4fdcb6df53065e 100644 --- a/kernel/irq/irqdomain.c +++ b/kernel/irq/irqdomain.c @@ -344,6 +344,7 @@ static struct irq_domain *__irq_domain_instantiate(const struct irq_domain_info err = irq_domain_alloc_generic_chips(domain, info->dgc_info); if (err) goto err_domain_free; + domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; } if (info->init) { From 404b7900e62b9d5f4ac25c3e2ce757f32e4cc004 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:45 +0800 Subject: [PATCH 0249/2286] irqchip/lan966x-oic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC Now that __irq_domain_instantiate() automatically sets IRQ_DOMAIN_FLAG_DESTROY_GC when dgc_info is provided, the explicit flag in the irq_domain_info is redundant. Remove it. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-3-fuqingshuang@kylinos.cn --- drivers/irqchip/irq-lan966x-oic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/irqchip/irq-lan966x-oic.c b/drivers/irqchip/irq-lan966x-oic.c index 8af08d0e418230..5122f3f1b353c5 100644 --- a/drivers/irqchip/irq-lan966x-oic.c +++ b/drivers/irqchip/irq-lan966x-oic.c @@ -220,7 +220,6 @@ static int lan966x_oic_probe(struct platform_device *pdev) }; struct irq_domain_info d_info = { .fwnode = of_fwnode_handle(pdev->dev.of_node), - .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, .size = LAN966X_OIC_NR_IRQ, .hwirq_max = LAN966X_OIC_NR_IRQ, .ops = &irq_generic_chip_ops, From c6d93e52d8c8f05898adf29d3b6edf6570da83af Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Mon, 7 Sep 2026 10:40:46 +0800 Subject: [PATCH 0250/2286] soc/fsl/qe: qe_ports_ic: Drop redundant IRQ_DOMAIN_FLAG_DESTROY_GC Now that __irq_domain_instantiate() automatically sets IRQ_DOMAIN_FLAG_DESTROY_GC when dgc_info is provided, the explicit flag in the irq_domain_info is redundant. Remove it. Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Reviewed-by: Herve Codina Link: https://patch.msgid.link/20260907024046.28845-4-fuqingshuang@kylinos.cn --- drivers/soc/fsl/qe/qe_ports_ic.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/soc/fsl/qe/qe_ports_ic.c b/drivers/soc/fsl/qe/qe_ports_ic.c index 7375f92f528b92..fb3b92039547a6 100644 --- a/drivers/soc/fsl/qe/qe_ports_ic.c +++ b/drivers/soc/fsl/qe/qe_ports_ic.c @@ -140,7 +140,6 @@ static int qepic_probe(struct platform_device *pdev) }; struct irq_domain_info d_info = { .fwnode = of_fwnode_handle(pdev->dev.of_node), - .domain_flags = IRQ_DOMAIN_FLAG_DESTROY_GC, .size = 32, .hwirq_max = 32, .ops = &irq_generic_chip_ops, From 135d9e67f7ab4f261c2157ee2de1f93a4de39a41 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 11 Aug 2026 11:55:32 +0200 Subject: [PATCH 0251/2286] irqchip/gic-v3-its: Don't WARN on LPI free allocation failure free_lpi_range() cannot give LPIs back to the allocator without allocating a struct lpi_range to describe the freed range, so it returns -ENOMEM (its only failure mode) when that allocation fails, and its_lpi_free() turns this into a WARN_ON(). syzbot triggers the WARN_ON() by injecting a slab allocation failure on device teardown, which with panic_on_warn becomes a panic: WARNING: drivers/irqchip/irq-gic-v3-its.c:2251 at its_msi_teardown+0x3a4/0x424 its_msi_teardown+0x3a4/0x424 msi_remove_device_irq_domain+0x16c/0x27c msi_device_data_release+0x38/0x9c The failure is transient and the consequence benign: the freed range is simply never returned to the allocator. This does not warrant a WARN_ON() backtrace, so log a rate-limited error instead. Fixes: 880cb3cddd16 ("irqchip/gic-v3-its: Refactor LPI allocator") Reported-by: syzbot+229d761b8a110e6de517@syzkaller.appspotmail.com Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: Claude:claude-fable-5 Link: https://patch.msgid.link/20260811095532.48454-1-kmehltretter@gmail.com Closes: https://syzkaller.appspot.com/bug?extid=229d761b8a110e6de517 --- drivers/irqchip/irq-gic-v3-its.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index e9807af235373a..b0fdc2b42a2259 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -2248,7 +2248,8 @@ static unsigned long *its_lpi_alloc(int nr_irqs, u32 *base, int *nr_ids) static void its_lpi_free(unsigned long *bitmap, u32 base, u32 nr_ids) { - WARN_ON(free_lpi_range(base, nr_ids)); + if (free_lpi_range(base, nr_ids)) + pr_err_ratelimited("ITS: failed to free LPI range %u:%u\n", base, nr_ids); bitmap_free(bitmap); } From 0c65a1331928d0207c385f03fb5eba0cfecce0e6 Mon Sep 17 00:00:00 2001 From: Adrian Ng Ho Yin Date: Mon, 7 Sep 2026 16:22:59 +0800 Subject: [PATCH 0252/2286] irqchip/gic-v3: Add Altera SoCFPGA Agilex5 GIC600 DMA32 erratum workaround Agilex5 integrates GIC600 with an ACE-lite interface limited to a 32-bit address bus, so the ITS can only access the first 4 GB of physical address space. Register intel,socfpga-agilex5 on the existing dma_32bit_impaired_platforms list so ITS allocations use GFP_DMA32. The workaround is guarded by ALTERA_ERRATUM_AGILEX5_2_1_23, which is selected by ARCH_INTEL_SOCFPGA as all Agilex5 devices are affected. This limitation is documented as Agilex 5 ES Device Errata 2.1.23 (825514). Signed-off-by: Adrian Ng Ho Yin Signed-off-by: Thomas Gleixner Reviewed-by: Marc Zyngier Link: https://docs.altera.com/r/docs/825514/current/agilex-5-es-device-errata-and-user-guidelines/hps-gicv3-its-and-lpi-unable-to-access-physical-memory-larger-than-32-bits-causing-msi-x-interrupt-failure Link: https://lore.kernel.org/linux-arm-kernel/372f059069a5551ea1096015f855cc306dbd7cd4.1747368554.git.adrianhoyin.ng@altera.com/ # v1 Link: https://lore.kernel.org/linux-arm-kernel/6a44509ca0edaabc17e59d2e27fef1c782183456.1751618484.git.adrianhoyin.ng@altera.com/ # v2 Link: https://lore.kernel.org/linux-arm-kernel/20260622024945.21354-1-muhammad.nazim.amirul.nazle.asmade@altera.com/ # v3 (untagged) Link: https://lore.kernel.org/all/20260904091114.2259616-1-adrian.ho.yin.ng@altera.com/ #v4 Link: https://patch.msgid.link/fc927e43892b86200e157fa303b0d1dd344caffd.1788751033.git.adrian.ho.yin.ng@altera.com --- Documentation/arch/arm64/silicon-errata.rst | 3 +++ arch/arm64/Kconfig | 9 +++++++++ arch/arm64/Kconfig.platforms | 1 + drivers/irqchip/irq-gic-v3-its.c | 3 +++ 4 files changed, 16 insertions(+) diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index ac3248b9f2f3bb..25afa79266a470 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -53,6 +53,9 @@ stable kernels. | Allwinner | A64/R18 | UNKNOWN1 | SUN50I_ERRATUM_UNKNOWN1 | +----------------+-----------------+-----------------+-----------------------------+ +----------------+-----------------+-----------------+-----------------------------+ +| Altera | SoCFPGA Agilex5 | 2.1.23 |ALTERA_ERRATUM_AGILEX5_2_1_23| ++----------------+-----------------+-----------------+-----------------------------+ ++----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne | AC03_CPU_38 | AMPERE_ERRATUM_AC03_CPU_38 | +----------------+-----------------+-----------------+-----------------------------+ | Ampere | AmpereOne | AC03_CPU_57 | N/A | diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..d25c77a15276d7 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1427,6 +1427,15 @@ config NVIDIA_OLYMPUS_1027_ERRATUM If unsure, say Y. +config ALTERA_ERRATUM_AGILEX5_2_1_23 + bool + help + The Altera SoCFPGA Agilex5 GIC600 SoC integration has ACE-lite + addressing limited to the first 32bit of physical address space, + so the ITS cannot access memory above 4GB. + + Selected by ARCH_INTEL_SOCFPGA, as all Agilex5 devices are affected. + config RENESAS_ERRATUM_GEN4GICITS1 bool "Renesas R-Car Gen4: GIC600 can not access physical addresses above 4 GiB" default y diff --git a/arch/arm64/Kconfig.platforms b/arch/arm64/Kconfig.platforms index d2acfac7300372..07127f7291dbf2 100644 --- a/arch/arm64/Kconfig.platforms +++ b/arch/arm64/Kconfig.platforms @@ -361,6 +361,7 @@ config ARCH_SEATTLE config ARCH_INTEL_SOCFPGA bool "Intel's SoCFPGA ARMv8 Families" + select ALTERA_ERRATUM_AGILEX5_2_1_23 help This enables support for Intel's SoCFPGA ARMv8 families: Stratix 10 (ex. Altera), Stratix10 Software Virtual Platform, diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index b0fdc2b42a2259..b6509b495d6d89 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -4896,6 +4896,9 @@ static bool __maybe_unused its_enable_quirk_hip09_162100801(void *data) } static const char * const dma_32bit_impaired_platforms[] = { +#ifdef CONFIG_ALTERA_ERRATUM_AGILEX5_2_1_23 + "intel,socfpga-agilex5", +#endif #ifdef CONFIG_RENESAS_ERRATUM_GEN4GICITS1 "renesas,r8a779f0", "renesas,r8a779g0", From 0e30b98a545991e440a947fb24f9df4ded9798f0 Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Mon, 7 Sep 2026 15:00:45 +0100 Subject: [PATCH 0253/2286] firmware: coreboot: Use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. Signed-off-by: Pawel Zalewski Link: https://lore.kernel.org/r/20260907-acpi-firmware-v2-1-e0b6d40823d1@thegoodpenguin.co.uk Signed-off-by: Tzung-Bi Shih --- drivers/firmware/coreboot/coreboot_table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/coreboot/coreboot_table.c b/drivers/firmware/coreboot/coreboot_table.c index 25ee8cf534290c..2c8c928fbb33e2 100644 --- a/drivers/firmware/coreboot/coreboot_table.c +++ b/drivers/firmware/coreboot/coreboot_table.c @@ -250,8 +250,8 @@ static void coreboot_table_remove(struct platform_device *pdev) #ifdef CONFIG_ACPI static const struct acpi_device_id cros_coreboot_acpi_match[] = { - { "GOOGCB00", 0 }, - { "BOOT0000", 0 }, + { .id = "GOOGCB00" }, + { .id = "BOOT0000" }, { } }; MODULE_DEVICE_TABLE(acpi, cros_coreboot_acpi_match); From 12255a04ec331a9da0bf049ed48c92af57dfafae Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 11:18:38 -0400 Subject: [PATCH 0254/2286] isofs: convert the zisofs read path to use folios Store folios in the array that zisofs_read_folio() passes to zisofs_fill_pages() and zisofs_uncompress_block(), use the folio APIs on them, and grab the folios with __filemap_get_folio() instead of grab_cache_page_nowait(). This removes six compound_head() calls, from page_offset(), the three SetPageUptodate() calls, unlock_page() and put_page(), and the last struct page usage in isofs. isofs only uses order-0 folios, so folio_size() is PAGE_SIZE and the logic is unchanged. grab_cache_page_nowait() passed FGP_NOFS as well, but FGP_NOWAIT already prevents reclaim entirely, so leave it out, as FGP_NOFS is on its way out [1]. Change poffset to be unsigned int rather than just unsigned while at it. [1] https://lore.kernel.org/linux-mm/20260830041901.2668-9-willy@infradead.org/ Signed-off-by: Tal Zussman Link: https://patch.msgid.link/20260907-isofs-zisofs-folio-v2-1-c2e430abfcbc@columbia.edu Signed-off-by: Jan Kara --- fs/isofs/compress.c | 77 +++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/fs/isofs/compress.c b/fs/isofs/compress.c index f9869d62b85096..2d23abaeb87421 100644 --- a/fs/isofs/compress.c +++ b/fs/isofs/compress.c @@ -39,7 +39,7 @@ static DEFINE_MUTEX(zisofs_zlib_lock); */ static loff_t zisofs_uncompress_block(struct inode *inode, loff_t block_start, loff_t block_end, int pcount, - struct page **pages, unsigned poffset, + struct folio **folios, unsigned int poffset, int *errp) { unsigned int zisofs_block_shift = ISOFS_I(inode)->i_format_parm[1]; @@ -66,11 +66,12 @@ static loff_t zisofs_uncompress_block(struct inode *inode, loff_t block_start, if (block_size == 0) { for ( i = 0 ; i < pcount ; i++ ) { unsigned int off = i ? 0 : poffset; + struct folio *folio = folios[i]; - if (!pages[i]) + if (!folio) continue; - memzero_page(pages[i], off, PAGE_SIZE - off); - SetPageUptodate(pages[i]); + folio_zero_range(folio, off, folio_size(folio) - off); + folio_mark_uptodate(folio); } return (((loff_t)pcount) << PAGE_SHIFT) - poffset; } @@ -119,11 +120,12 @@ static loff_t zisofs_uncompress_block(struct inode *inode, loff_t block_start, while (curpage < pcount && curbh < haveblocks && zerr != Z_STREAM_END) { + struct folio *folio = folios[curpage]; + if (!stream.avail_out) { - if (pages[curpage]) { - stream.next_out = kmap_local_page(pages[curpage]) - + poffset; - stream.avail_out = PAGE_SIZE - poffset; + if (folio) { + stream.next_out = kmap_local_folio(folio, poffset); + stream.avail_out = folio_size(folio) - poffset; poffset = 0; } else { stream.next_out = (void *)&zisofs_sink_page; @@ -173,9 +175,9 @@ static loff_t zisofs_uncompress_block(struct inode *inode, loff_t block_start, if (!stream.avail_out) { /* This page completed */ - if (pages[curpage]) { - flush_dcache_page(pages[curpage]); - SetPageUptodate(pages[curpage]); + if (folio) { + flush_dcache_folio(folio); + folio_mark_uptodate(folio); } if (stream.next_out != (unsigned char *)zisofs_sink_page) { kunmap_local(stream.next_out); @@ -206,7 +208,7 @@ static loff_t zisofs_uncompress_block(struct inode *inode, loff_t block_start, * fills in other pages if we have data for them. */ static int zisofs_fill_pages(struct inode *inode, int full_page, int pcount, - struct page **pages) + struct folio **folios) { loff_t start_off, end_off; loff_t block_start, block_end; @@ -221,14 +223,14 @@ static int zisofs_fill_pages(struct inode *inode, int full_page, int pcount, int err; loff_t ret; - BUG_ON(!pages[full_page]); + BUG_ON(!folios[full_page]); /* * We want to read at least 'full_page' page. Because we have to * uncompress the whole compression block anyway, fill the surrounding * pages with the data we have anyway... */ - start_off = page_offset(pages[full_page]); + start_off = folio_pos(folios[full_page]); end_off = min_t(loff_t, start_off + PAGE_SIZE, inode->i_size); cstart_block = start_off >> zisofs_block_shift; @@ -267,9 +269,9 @@ static int zisofs_fill_pages(struct inode *inode, int full_page, int pcount, } err = 0; ret = zisofs_uncompress_block(inode, block_start, block_end, - pcount, pages, poffset, &err); + pcount, folios, poffset, &err); poffset += ret; - pages += poffset >> PAGE_SHIFT; + folios += poffset >> PAGE_SHIFT; pcount -= poffset >> PAGE_SHIFT; full_page -= poffset >> PAGE_SHIFT; poffset &= ~PAGE_MASK; @@ -289,9 +291,11 @@ static int zisofs_fill_pages(struct inode *inode, int full_page, int pcount, cstart_block++; } - if (poffset && *pages) { - memzero_page(*pages, poffset, PAGE_SIZE - poffset); - SetPageUptodate(*pages); + if (poffset && *folios) { + struct folio *folio = *folios; + + folio_zero_range(folio, poffset, folio_size(folio) - poffset); + folio_mark_uptodate(folio); } brelse(bh); return 0; @@ -312,7 +316,7 @@ static int zisofs_read_folio(struct file *file, struct folio *folio) unsigned int zisofs_pages_per_cblock = PAGE_SHIFT <= zisofs_block_shift ? (1 << (zisofs_block_shift - PAGE_SHIFT)) : 0; - struct page **pages; + struct folio **folios; pgoff_t index = folio->index, end_index; end_index = (inode->i_size + PAGE_SIZE - 1) >> PAGE_SHIFT; @@ -336,33 +340,38 @@ static int zisofs_read_folio(struct file *file, struct folio *folio) full_page = 0; pcount = 1; } - pages = kzalloc_objs(*pages, - max_t(unsigned int, zisofs_pages_per_cblock, 1)); - if (!pages) { + folios = kzalloc_objs(*folios, + max_t(unsigned int, zisofs_pages_per_cblock, 1)); + if (!folios) { folio_unlock(folio); return -ENOMEM; } - pages[full_page] = &folio->page; + folios[full_page] = folio; for (i = 0; i < pcount; i++, index++) { - if (i != full_page) - pages[i] = grab_cache_page_nowait(mapping, index); + if (i == full_page) + continue; + folios[i] = __filemap_get_folio(mapping, index, + FGP_LOCK | FGP_CREAT | FGP_NOWAIT, + mapping_gfp_mask(mapping)); + if (IS_ERR(folios[i])) + folios[i] = NULL; } - err = zisofs_fill_pages(inode, full_page, pcount, pages); + err = zisofs_fill_pages(inode, full_page, pcount, folios); - /* Release any residual pages, do not SetPageUptodate */ + /* Release any residual folios, do not mark them uptodate */ for (i = 0; i < pcount; i++) { - if (pages[i]) { - flush_dcache_page(pages[i]); - unlock_page(pages[i]); + if (folios[i]) { + flush_dcache_folio(folios[i]); + folio_unlock(folios[i]); if (i != full_page) - put_page(pages[i]); + folio_put(folios[i]); } - } + } /* At this point, err contains 0 or -EIO depending on the "critical" page */ - kfree(pages); + kfree(folios); return err; } From 2dfbc0f077fe0c88edf620627691a64d8d9d84ca Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Wed, 2 Sep 2026 23:20:17 +0200 Subject: [PATCH 0255/2286] s390/ebcdic: Add character classes for the ASCII subset of EBCDIC Add _ctype-like array for EBCDIC characters. Mark only those that also exist in ASCII, loosely based on invariant subset of EBCDIC [1], but with cent, broken bar, not sign, and plus-minus sign, left out. [1] https://en.wikipedia.org/wiki/EBCDIC#Code_page_layout [hca@linux.ibm.com: Reformatted tabled] Signed-off-by: Ilya Leoshkevich Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/include/asm/ebcdic.h | 6 +++++ arch/s390/kernel/ebcdic.c | 40 +++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/arch/s390/include/asm/ebcdic.h b/arch/s390/include/asm/ebcdic.h index 7164cb6584355b..78d2e37e985d35 100644 --- a/arch/s390/include/asm/ebcdic.h +++ b/arch/s390/include/asm/ebcdic.h @@ -10,6 +10,7 @@ #ifndef _EBCDIC_H #define _EBCDIC_H +#include #include extern __u8 _ascebc_500[256]; /* ASCII -> EBCDIC 500 conversion table */ @@ -18,6 +19,11 @@ extern __u8 _ascebc[256]; /* ASCII -> EBCDIC conversion table */ extern __u8 _ebcasc[256]; /* EBCDIC -> ASCII conversion table */ extern __u8 _ebc_tolower[256]; /* EBCDIC -> lowercase */ extern __u8 _ebc_toupper[256]; /* EBCDIC -> uppercase */ +extern const __u8 _ebctype_asc[256]; /* EBCDIC -> character classes */ + +#define __ismask_ebc_asc(x) (_ebctype_asc[(int)(unsigned char)(x)]) +#define isprint_ebc_asc(c) ((__ismask_ebc_asc(c)&(_P|_U|_L|_D|_SP)) != 0) +#define isspace_ebc_asc(c) ((__ismask_ebc_asc(c)&(_S)) != 0) static inline void codepage_convert(const __u8 *codepage, volatile char *addr, unsigned long nr) diff --git a/arch/s390/kernel/ebcdic.c b/arch/s390/kernel/ebcdic.c index 0e51fa537262b1..970aace8719d70 100644 --- a/arch/s390/kernel/ebcdic.c +++ b/arch/s390/kernel/ebcdic.c @@ -392,10 +392,48 @@ __u8 _ebc_toupper[256] = 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; +/* + * ASCII subset of EBCDIC -> character classes + */ +const __u8 _ebctype_asc[256] = { + _C, _C, _C, _C, _C, _C|_S, _C, _C, /* 0x00-0x07 */ + _C, _C, _C, _C|_S, _C|_S, _C|_S, _C, _C, /* 0x08-0x0f */ + _C, _C, _C, _C, _C, _C|_S, _C, _C, /* 0x10-0x17 */ + _C, _C, _C, _C, _C, _C, _C, _C, /* 0x18-0x1f */ + _C, _C, _C, _C, _C, _C|_S, _C, _C, /* 0x20-0x27 */ + _C, _C, _C, _C, _C, _C, _C, _C, /* 0x28-0x2f */ + 0, 0, _C, _C, _C, _C, _C, _C, /* 0x30-0x37 */ + _C, _C, _C, _C, _C, _C, 0, _C, /* 0x38-0x3f */ + _S|_SP, 0, 0, 0, 0, 0, 0, 0, /* 0x40-0x47 */ + 0, 0, 0, _P, _P, _P, _P, _P, /* 0x48-0x4f */ + _P, 0, 0, 0, 0, 0, 0, 0, /* 0x50-0x57 */ + 0, 0, _P, _P, _P, _P, _P, 0, /* 0x58-0x5f */ + _P, _P, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */ + 0, 0, 0, _P, _P, _P, _P, _P, /* 0x68-0x6f */ + 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77 */ + 0, _P, _P, _P, _P, _P, _P, _P, /* 0x78-0x7f */ + 0, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L, /* 0x80-0x87 */ + _L, _L, 0, 0, 0, 0, 0, 0, /* 0x88-0x8f */ + 0, _L, _L, _L, _L, _L, _L, _L, /* 0x90-0x97 */ + _L, _L, 0, 0, 0, 0, 0, 0, /* 0x98-0x9f */ + 0, _P, _L, _L, _L, _L, _L, _L, /* 0xa0-0xa7 */ + _L, _L, 0, 0, 0, 0, 0, 0, /* 0xa8-0xaf */ + _P, 0, 0, 0, 0, 0, 0, 0, /* 0xb0-0xb7 */ + 0, 0, _P, _P, 0, 0, 0, 0, /* 0xb8-0xbf */ + _P, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U, /* 0xc0-0xc7 */ + _U, _U, 0, 0, 0, 0, 0, 0, /* 0xc8-0xcf */ + _P, _U, _U, _U, _U, _U, _U, _U, /* 0xd0-0xd7 */ + _U, _U, 0, 0, 0, 0, 0, 0, /* 0xd8-0xdf */ + _P, 0, _U, _U, _U, _U, _U, _U, /* 0xe0-0xe7 */ + _U, _U, 0, 0, 0, 0, 0, 0, /* 0xe8-0xef */ + _D, _D, _D, _D, _D, _D, _D, _D, /* 0xf0-0xf7 */ + _D, _D, 0, 0, 0, 0, 0, _C, /* 0xf8-0xff */ +}; + EXPORT_SYMBOL(_ascebc_500); EXPORT_SYMBOL(_ebcasc_500); EXPORT_SYMBOL(_ascebc); EXPORT_SYMBOL(_ebcasc); EXPORT_SYMBOL(_ebc_tolower); EXPORT_SYMBOL(_ebc_toupper); - +EXPORT_SYMBOL(_ebctype_asc); From 8410516674b52a51ce2c15c7e2ea7dee2a8b6489 Mon Sep 17 00:00:00 2001 From: Ilya Leoshkevich Date: Wed, 2 Sep 2026 23:20:18 +0200 Subject: [PATCH 0256/2286] s390: Warn if kernel command line contains non-printable EBCDIC characters Users may accidentally add multi-byte UTF-8 characters to zipl.conf parmline, for example, by copying snippets containing non-breaking spaces (\xC2\xA0) from web pages. The kernel will then interpret the entire command line as EBCDIC, making it unusable. Distinguish this situation from the legitimate EBCDIC conversion by looking for non-printable characters and issue a warning. Accept the following tradeoffs: * Do not make this a hard failure, there may be a very small number of users who put characters with diacritics on their EBCDIC-encoded command lines. * Do not use heuristics with arbitrary thresholds, these may fail intermittently on, e.g., punctuation-heavy command lines, and having to tune thresholds in subsequent patches is not desirable. * Use the ASCII subset of EBCDIC to determine whether characters are printable: the vast majority of command lines will contain characters only from this subset. Signed-off-by: Ilya Leoshkevich Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens Signed-off-by: Vasily Gorbik --- arch/s390/boot/ipl_parm.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/arch/s390/boot/ipl_parm.c b/arch/s390/boot/ipl_parm.c index c1b43e5e688a06..637cb7b6343f95 100644 --- a/arch/s390/boot/ipl_parm.c +++ b/arch/s390/boot/ipl_parm.c @@ -172,12 +172,23 @@ static inline int has_ebcdic_char(const char *str) return 0; } +static inline bool has_nonprintable_ebcdic_char(const char *str) +{ + for (; *str; str++) + if (!isprint_ebc_asc(*str) && !isspace_ebc_asc(*str)) + return true; + return false; +} + void setup_boot_command_line(void) { parmarea.command_line[COMMAND_LINE_SIZE - 1] = 0; /* convert arch command line to ascii if necessary */ - if (has_ebcdic_char(parmarea.command_line)) + if (has_ebcdic_char(parmarea.command_line)) { + if (has_nonprintable_ebcdic_char(parmarea.command_line)) + boot_warn("Kernel command line was treated as EBCDIC, but contains non-printable characters\n"); EBCASC(parmarea.command_line, COMMAND_LINE_SIZE); + } /* copy arch command line */ strscpy(early_command_line, strim(parmarea.command_line)); From ba71d9b4e9dd83c1614b3b5cec6c82a39d290f7e Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 19 Aug 2026 10:52:59 +0800 Subject: [PATCH 0257/2286] EDAC/i10nm: Fix Granite Rapids ADXL MC mapping ADXL reports memory errors using memory-controller physical indices, while the EDAC driver identifies memory controllers using logical indices. On Granite Rapids systems, BIOS may disable some memory controllers, causing the physical and logical indices to differ. As a result, ADXL memory errors may be associated with the wrong memory controller, preventing EDAC from reporting errors for some DIMMs. Maintain a physical-to-logical MC mapping so that ADXL memory errors are decoded correctly regardless of the BIOS memory-controller configuration. Fixes: ba987eaaabf9 ("EDAC/i10nm: Add Intel Granite Rapids server support") Reported-by: Yanhua Wu Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260819025259.266201-1-qiuxu.zhuo@intel.com --- drivers/edac/i10nm_base.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index fa1853f252b068..f3f8e4e00148aa 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -479,6 +479,7 @@ static struct pci_dev *get_gnr_mdev(struct skx_dev *d, int logical_idx, int *phy if (mdev) { if (logical == logical_idx) { *physical_idx = i; + skx_set_mc_mapping(d, i, logical_idx); return mdev; } From 179b100b45565d4628f434fe73cd71e63dc7282b Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Fri, 28 Aug 2026 08:29:56 -0700 Subject: [PATCH 0258/2286] cacheinfo: Export get_cpu_cacheinfo_id() for loadable modules get_cpu_cacheinfo_id() is a static inline that requires get_cpu_cacheinfo(), which is not exported. Modules that need to identify which cache instance a CPU belongs to therefore cannot use it. Move it out of line and export it. Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-2-tony.luck@intel.com --- drivers/base/cacheinfo.c | 17 +++++++++++++++++ include/linux/cacheinfo.h | 12 +----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/base/cacheinfo.c b/drivers/base/cacheinfo.c index 9f9c72727a0590..c4394baa7e53f6 100644 --- a/drivers/base/cacheinfo.c +++ b/drivers/base/cacheinfo.c @@ -38,6 +38,23 @@ struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu) return ci_cacheinfo(cpu); } +/** + * get_cpu_cacheinfo_id - Return the cache ID for a CPU and cache level + * @cpu: CPU number + * @level: Cache level + * + * The caller must hold the cpuhp lock. + * + * Return: Cache ID on success, or -1 if no matching cache exists. + */ +int get_cpu_cacheinfo_id(int cpu, int level) +{ + struct cacheinfo *ci = get_cpu_cacheinfo_level(cpu, level); + + return ci ? ci->id : -1; +} +EXPORT_SYMBOL_GPL(get_cpu_cacheinfo_id); + static inline bool cache_leaves_are_shared(struct cacheinfo *this_leaf, struct cacheinfo *sib_leaf) { diff --git a/include/linux/cacheinfo.h b/include/linux/cacheinfo.h index fc879ac4cc4f28..56fa646df0d122 100644 --- a/include/linux/cacheinfo.h +++ b/include/linux/cacheinfo.h @@ -82,6 +82,7 @@ struct cpu_cacheinfo { }; struct cpu_cacheinfo *get_cpu_cacheinfo(unsigned int cpu); +int get_cpu_cacheinfo_id(int cpu, int level); int early_cache_level(unsigned int cpu); int init_cache_level(unsigned int cpu); int init_of_cache_level(unsigned int cpu); @@ -137,17 +138,6 @@ static inline struct cacheinfo *get_cpu_cacheinfo_level(int cpu, int level) return NULL; } -/* - * Get the id of the cache associated with @cpu at level @level. - * cpuhp lock must be held. - */ -static inline int get_cpu_cacheinfo_id(int cpu, int level) -{ - struct cacheinfo *ci = get_cpu_cacheinfo_level(cpu, level); - - return ci ? ci->id : -1; -} - #if defined(CONFIG_ARM64) || defined(CONFIG_ARM) #define use_arch_cache_info() (true) #else From 46cff0be63379264a311cd43be09e1fdd38d1abc Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:29:57 -0700 Subject: [PATCH 0259/2286] x86/mce: Add enumeration for Intel bitfix filter reset IA32_CORE_CAPABILITIES enumerates the bitfix filter reset feature. Intel specifies two values for the threshold status field in the IA32_MCi_STATUS MSR: 1 = GREEN (threshold has not been reached) 2 = YELLOW (threshold exceeded, try bitfix filter reset) Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-3-tony.luck@intel.com --- arch/x86/include/asm/mce.h | 5 +++++ arch/x86/include/asm/msr-index.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/arch/x86/include/asm/mce.h b/arch/x86/include/asm/mce.h index e575b702063d0d..9995176d687905 100644 --- a/arch/x86/include/asm/mce.h +++ b/arch/x86/include/asm/mce.h @@ -13,6 +13,7 @@ #define MCG_CTL_P BIT_ULL(8) /* MCG_CTL register available */ #define MCG_EXT_P BIT_ULL(9) /* Extended registers available */ #define MCG_CMCI_P BIT_ULL(10) /* CMCI supported */ +#define MCG_TES_P BIT_ULL(11) /* Threshold-based error status supported */ #define MCG_SEAM_NR BIT_ULL(12) /* MCG_STATUS_SEAM_NR supported */ #define MCG_EXT_CNT_MASK 0xff0000 /* Number of Extended registers */ #define MCG_EXT_CNT_SHIFT 16 @@ -41,6 +42,10 @@ #define MCI_STATUS_PCC BIT_ULL(57) /* processor context corrupt */ #define MCI_STATUS_S BIT_ULL(56) /* Signaled machine check */ #define MCI_STATUS_AR BIT_ULL(55) /* Action required */ +#define MCI_STATUS_TES_MASK GENMASK_ULL(54, 53) +#define MCI_STATUS_TES(s) FIELD_GET(MCI_STATUS_TES_MASK, s) +#define MCI_STATUS_TES_GREEN 1 /* Threshold-based errors below threshold */ +#define MCI_STATUS_TES_YELLOW 2 /* Threshold-based errors above threshold */ #define MCI_STATUS_CEC_SHIFT 38 /* Corrected Error Count */ #define MCI_STATUS_CEC_MASK GENMASK_ULL(52,38) #define MCI_STATUS_CEC(c) (((c) & MCI_STATUS_CEC_MASK) >> MCI_STATUS_CEC_SHIFT) diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h index 3a8e51a0c9e88c..1787fbb1045998 100644 --- a/arch/x86/include/asm/msr-index.h +++ b/arch/x86/include/asm/msr-index.h @@ -114,6 +114,8 @@ #define MSR_IA32_CORE_CAPS_INTEGRITY_CAPS BIT(MSR_IA32_CORE_CAPS_INTEGRITY_CAPS_BIT) #define MSR_IA32_CORE_CAPS_SPLIT_LOCK_DETECT_BIT 5 #define MSR_IA32_CORE_CAPS_SPLIT_LOCK_DETECT BIT(MSR_IA32_CORE_CAPS_SPLIT_LOCK_DETECT_BIT) +#define MSR_IA32_CORE_CAPS_BFF_RESET_BIT 9 +#define MSR_IA32_CORE_CAPS_BFF_RESET BIT(MSR_IA32_CORE_CAPS_BFF_RESET_BIT) #define MSR_PKG_CST_CONFIG_CONTROL 0x000000e2 #define NHM_C3_AUTO_DEMOTE (1UL << 25) From d4ffdf94fe08e0e3be978fbce85633ef2e7c623a Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:29:58 -0700 Subject: [PATCH 0260/2286] EDAC/intel-bff: Add stub Intel bitfix filter driver Check if the platform supports threshold based cache error reporting and the bitfix filter reset feature. Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-4-tony.luck@intel.com --- MAINTAINERS | 6 +++++ drivers/edac/Kconfig | 13 ++++++++++ drivers/edac/Makefile | 2 ++ drivers/edac/intel-bff.c | 54 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 drivers/edac/intel-bff.c diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb0777052..77383c50e240a1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9490,6 +9490,12 @@ L: linux-edac@vger.kernel.org S: Maintained F: drivers/edac/igen6_edac.c +EDAC-INTEL-BFF-RESET +M: Tony Luck +L: linux-edac@vger.kernel.org +S: Maintained +F: drivers/edac/intel-bff.c + EDAC-MPC85XX L: linux-edac@vger.kernel.org S: Orphan diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index a44b85c440cade..f7964cd9545e3f 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -280,6 +280,19 @@ config EDAC_IMH first used on the Diamond Rapids servers but may appear on others in the future. +config EDAC_INTEL_BFF_RESET + tristate "Intel bitfix filter reset driver" + depends on X86_64 && X86_MCE_INTEL + help + Support for Intel systems that implement bitfix filters to + suppress repeated logging and signaling of the same corrected + error. Those filters can become clogged with transient errors + over time. Clearing the filter may make space for additional + hard errors. + + To compile this driver as a module, choose M here: the module + will be called intel_bff. + config EDAC_PND2 tristate "Intel Pondicherry2" depends on PCI && X86_64 && X86_MCE_INTEL diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index a37534300ab921..c2bae03f247b7e 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -66,6 +66,8 @@ obj-$(CONFIG_EDAC_I10NM) += i10nm_edac.o skx_edac_common.o imh_edac-y := imh_base.o obj-$(CONFIG_EDAC_IMH) += imh_edac.o skx_edac_common.o +obj-$(CONFIG_EDAC_INTEL_BFF_RESET) += intel-bff.o + obj-$(CONFIG_EDAC_HIGHBANK_MC) += highbank_mc_edac.o obj-$(CONFIG_EDAC_HIGHBANK_L2) += highbank_l2_edac.o diff --git a/drivers/edac/intel-bff.c b/drivers/edac/intel-bff.c new file mode 100644 index 00000000000000..905cbe58bc5340 --- /dev/null +++ b/drivers/edac/intel-bff.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright(c) 2026 Intel Corporation. */ + +/* + * Intel driver to reset bitfix filters when they overflow. + * + * Each bitfix filter has limited slots to track corrected errors. + * These slots can be filled by transient corrected errors (e.g., + * bit flips from particle strikes) that don't represent permanent + * hardware defects. + * + * When the filter overflows (yellow status), reset it to reclaim + * slots occupied by transient corrected errors. If the overflow + * repeats frequently after reset, it indicates persistent hardware + * defects that need attention: the system should be scheduled for + * servicing. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static int __init bff_init(void) +{ + u64 core_caps, mcg_cap; + + if (!cpu_feature_enabled(X86_FEATURE_MCA)) + return -ENODEV; + rdmsrq(MSR_IA32_MCG_CAP, mcg_cap); + if (!(mcg_cap & MCG_TES_P)) + return -ENODEV; + + if (!cpu_feature_enabled(X86_FEATURE_CORE_CAPABILITIES)) + return -ENODEV; + rdmsrq(MSR_IA32_CORE_CAPS, core_caps); + if (!(core_caps & MSR_IA32_CORE_CAPS_BFF_RESET)) + return -ENODEV; + + return 0; +} + +module_init(bff_init); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tony Luck"); +MODULE_DESCRIPTION("Intel bitfix filter reset driver"); From a2808f919db706b44842b10fb4c69c46a5aabc0b Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:29:59 -0700 Subject: [PATCH 0261/2286] EDAC/intel-bff: Add Diamond Rapids support There are potentially bitfix filters associated with each machine check bank. Only some banks may implement them. Machine check banks have varying scope. E.g. there is a separate L2 cache for each module, each instance has its own bitfix filter. Add information that will be used to map a pair to a unique instance number for a bitfix filter. Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-5-tony.luck@intel.com --- drivers/edac/intel-bff.c | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/edac/intel-bff.c b/drivers/edac/intel-bff.c index 905cbe58bc5340..f70c5e697dae30 100644 --- a/drivers/edac/intel-bff.c +++ b/drivers/edac/intel-bff.c @@ -18,18 +18,55 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include +#include #include +#include #include +#include #include #include #include +/* Machine check bank hardware unit types */ +enum bff_bank_type { + BFF_BANK_NONE = 0, + BFF_BANK_DCU, + BFF_BANK_DTLB, + BFF_BANK_MLC, + BFF_BANK_CCF, + BFF_BANK_HSF, + BFF_BANK_IOCACHE, +}; + +/* + * Mapping from machine check bank numbers on Diamond Rapids CPU, that are + * supported by a bitfix filter, to hardware unit type. + */ +static const enum bff_bank_type dmr_mcbanks[MAX_NR_BANKS] = { + [1] = BFF_BANK_DCU, + [2] = BFF_BANK_DTLB, + [3] = BFF_BANK_MLC, + [6] = BFF_BANK_CCF, + [13] = BFF_BANK_HSF, + [17] = BFF_BANK_IOCACHE +}; + +static const struct x86_cpu_id bff_cpu_ids[] __initconst = { + X86_MATCH_VFM(INTEL_DIAMONDRAPIDS_X, dmr_mcbanks), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, bff_cpu_ids); + +static const enum bff_bank_type *bff_bank_types; + static int __init bff_init(void) { + const struct x86_cpu_id *m; u64 core_caps, mcg_cap; if (!cpu_feature_enabled(X86_FEATURE_MCA)) @@ -44,6 +81,14 @@ static int __init bff_init(void) if (!(core_caps & MSR_IA32_CORE_CAPS_BFF_RESET)) return -ENODEV; + m = x86_match_cpu(bff_cpu_ids); + if (!m) { + pr_info("CPU model not supported by the bitfix filter reset driver\n"); + return -ENODEV; + } + + bff_bank_types = (const enum bff_bank_type *)m->driver_data; + return 0; } From 666192e18fafde5259360a7b8e6b030386d7edcb Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:30:00 -0700 Subject: [PATCH 0262/2286] EDAC/intel-bff: Reset bitfix filter when it overflows Get notifications for all errors logged in machine check banks. Skip any that do not indicate a bitfix filter overflow. Machine check banks are scoped to a hardware unit, so the reset has to be issued from a CPU within that unit. Use the CPU that reported the error. Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-6-tony.luck@intel.com --- drivers/edac/intel-bff.c | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/drivers/edac/intel-bff.c b/drivers/edac/intel-bff.c index f70c5e697dae30..c46d5255f34b27 100644 --- a/drivers/edac/intel-bff.c +++ b/drivers/edac/intel-bff.c @@ -17,11 +17,14 @@ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include #include #include #include #include #include +#include #include #include @@ -32,6 +35,11 @@ #include #include +/* Intel bitfix filter control register defines */ +#define MSR_MC0_BFF_CTL 0x000006c0 +#define MSR_MCx_BFF_CTL(x) (MSR_MC0_BFF_CTL + (x)) +#define MCI_BFF_RESET BIT_ULL(0) + /* Machine check bank hardware unit types */ enum bff_bank_type { BFF_BANK_NONE = 0, @@ -64,6 +72,38 @@ MODULE_DEVICE_TABLE(x86cpu, bff_cpu_ids); static const enum bff_bank_type *bff_bank_types; +static void bff_reset_and_report(struct mce *mce) +{ + /* Reset bitfix filter using the CPU that logged the yellow status */ + if (wrmsrq_on_cpu(mce->extcpu, MSR_MCx_BFF_CTL(mce->bank), MCI_BFF_RESET)) + pr_warn("Failed to reset bitfix filter for CPU %d Bank %d\n", + mce->extcpu, mce->bank); +} + +static int bff_mce_notify(struct notifier_block *nb, unsigned long val, void *data) +{ + struct mce *mce = data; + + /* TES is undefined for uncorrected errors. */ + if (mce->status & MCI_STATUS_UC) + return NOTIFY_DONE; + + if (MCI_STATUS_TES(mce->status) != MCI_STATUS_TES_YELLOW) + return NOTIFY_DONE; + + if (mce->bank >= MAX_NR_BANKS || bff_bank_types[mce->bank] == BFF_BANK_NONE) + return NOTIFY_DONE; + + bff_reset_and_report(mce); + + return NOTIFY_DONE; +} + +static struct notifier_block bff_notifier = { + .notifier_call = bff_mce_notify, + .priority = MCE_PRIO_EDAC, +}; + static int __init bff_init(void) { const struct x86_cpu_id *m; @@ -89,10 +129,18 @@ static int __init bff_init(void) bff_bank_types = (const enum bff_bank_type *)m->driver_data; + mce_register_decode_chain(&bff_notifier); + return 0; } +static void __exit bff_exit(void) +{ + mce_unregister_decode_chain(&bff_notifier); +} + module_init(bff_init); +module_exit(bff_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Tony Luck"); From 4835abed9f652de1bea8957b867b8479505caa74 Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:30:01 -0700 Subject: [PATCH 0263/2286] EDAC/intel-bff: Compute unique ID for overflowed filter Each L2 cache instance has its own bitfix filter, but always reports errors in machine check bank 3 (on Diamond Rapids). Compute a unique bitfix filter instance number based on the CPU that logged the error and the machine check bank number. Special-case the banks associated with the Integrated Memory Hub (IMH). Here the "even" numbered CPU modules are associated with IMH0 and the "odd" modules with IMH1. The unique id will be used to store a time stamp of when the bitfix filter overflowed so that frequent overflows can be logged. Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-7-tony.luck@intel.com --- drivers/edac/intel-bff.c | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/drivers/edac/intel-bff.c b/drivers/edac/intel-bff.c index c46d5255f34b27..06a79745c2fba5 100644 --- a/drivers/edac/intel-bff.c +++ b/drivers/edac/intel-bff.c @@ -19,13 +19,18 @@ #include #include +#include +#include #include +#include #include #include #include +#include #include #include #include +#include #include #include @@ -72,12 +77,87 @@ MODULE_DEVICE_TABLE(x86cpu, bff_cpu_ids); static const enum bff_bank_type *bff_bank_types; +/* Diamond Rapids maps APICID[2] to the IMH instance within a socket. */ +#define APICID_IMH_NUM GENMASK(2, 2) +#define IMH_NUM(apicid) FIELD_GET(APICID_IMH_NUM, apicid) +#define NUM_IMH_PER_SOCKET 2 + +static void bff_set_imh_id(struct mce *mce, unsigned long *id) +{ + int imh_num; + + imh_num = NUM_IMH_PER_SOCKET * topology_physical_package_id(mce->extcpu) + + IMH_NUM(mce->apicid); + + *id |= imh_num; +} + +static bool bff_set_cache_id(int cpu, int level, unsigned long *id) +{ + int cacheid; + + guard(cpus_read_lock)(); + + cacheid = get_cpu_cacheinfo_id(cpu, level); + if (cacheid == -1) { + pr_warn("Could not get L%d cache id for CPU %d\n", level, cpu); + return false; + } + + *id |= cacheid; + + return true; +} + +/* + * Cache IDs are only unique within a cache level. + * Include the MCA bank number so each BFF-capable hardware + * resource has a unique tracking ID. + */ +#define BFF_ID_BANK_FIELD GENMASK(63, 32) + +static unsigned long bff_get_id(struct mce *mce) +{ + unsigned long id = FIELD_PREP(BFF_ID_BANK_FIELD, mce->bank); + + switch (bff_bank_types[mce->bank]) { + case BFF_BANK_DCU: + case BFF_BANK_DTLB: + if (!bff_set_cache_id(mce->extcpu, 1, &id)) + return ULONG_MAX; + break; + + case BFF_BANK_MLC: + if (!bff_set_cache_id(mce->extcpu, 2, &id)) + return ULONG_MAX; + break; + + case BFF_BANK_CCF: + if (!bff_set_cache_id(mce->extcpu, 3, &id)) + return ULONG_MAX; + break; + + case BFF_BANK_HSF: + case BFF_BANK_IOCACHE: + bff_set_imh_id(mce, &id); + break; + + default: + return ULONG_MAX; + } + + return id; +} + static void bff_reset_and_report(struct mce *mce) { /* Reset bitfix filter using the CPU that logged the yellow status */ if (wrmsrq_on_cpu(mce->extcpu, MSR_MCx_BFF_CTL(mce->bank), MCI_BFF_RESET)) pr_warn("Failed to reset bitfix filter for CPU %d Bank %d\n", mce->extcpu, mce->bank); + + /* Placeholder use of bff_get_id() */ + pr_debug("unique_id = 0x%lx\n", bff_get_id(mce)); } static int bff_mce_notify(struct notifier_block *nb, unsigned long val, void *data) From 30713abb1e0f3fd958d08909481ecd101744c78f Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 28 Aug 2026 08:30:02 -0700 Subject: [PATCH 0264/2286] EDAC/intel-bff: Report frequent filter overflows If an instance of a bitfix filter contains some transient errors built up over time, then resetting the filter will free up slots in the filter to store persistent errors. Save a timestamp when "yellow" status is seen and clear the filter. Log at KERN_WARNING level if the overflow occurred quickly after a previous overflow on the same bitfix filter instance. Use KERN_NOTICE for first, or long delayed, overflow. Co-developed-by: Qiuxu Zhuo Signed-off-by: Qiuxu Zhuo Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260828153002.10290-8-tony.luck@intel.com --- drivers/edac/intel-bff.c | 72 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/drivers/edac/intel-bff.c b/drivers/edac/intel-bff.c index 06a79745c2fba5..adf2ec34312901 100644 --- a/drivers/edac/intel-bff.c +++ b/drivers/edac/intel-bff.c @@ -25,13 +25,17 @@ #include #include #include +#include #include +#include #include #include #include #include +#include #include #include +#include #include #include @@ -40,6 +44,16 @@ #include #include +/* + * A 10-minute observation period helps distinguish between: + * + * - A long-term accumulation of transient corrected errors + * (filter stays clear after reset). + * + * - Permanent defects (filter overflows again quickly). + */ +#define BFF_OVERFLOW_INTERVAL secs_to_jiffies(10 * 60) + /* Intel bitfix filter control register defines */ #define MSR_MC0_BFF_CTL 0x000006c0 #define MSR_MCx_BFF_CTL(x) (MSR_MC0_BFF_CTL + (x)) @@ -77,6 +91,8 @@ MODULE_DEVICE_TABLE(x86cpu, bff_cpu_ids); static const enum bff_bank_type *bff_bank_types; +static DEFINE_XARRAY(bff_bank_xa); + /* Diamond Rapids maps APICID[2] to the IMH instance within a socket. */ #define APICID_IMH_NUM GENMASK(2, 2) #define IMH_NUM(apicid) FIELD_GET(APICID_IMH_NUM, apicid) @@ -149,6 +165,41 @@ static unsigned long bff_get_id(struct mce *mce) return id; } +/* + * Save current timestamp for bff_id. Return true if it is within + * BFF_OVERFLOW_INTERVAL of previous timestamp for this bff_id. + */ +static bool bff_overflow_is_frequent(unsigned long bff_id) +{ + unsigned long now = jiffies, interval_end; + unsigned long *ts; + + if (bff_id == ULONG_MAX) + return false; + + ts = xa_load(&bff_bank_xa, bff_id); + if (!ts) { + ts = kzalloc_obj(*ts); + if (!ts) { + pr_warn("Failed to allocate timestamp for bitfix filter 0x%lx\n", bff_id); + return false; + } + if (xa_is_err(xa_store(&bff_bank_xa, bff_id, ts, GFP_KERNEL))) { + kfree(ts); + pr_warn("Failed to record timestamp for bitfix filter 0x%lx\n", bff_id); + return false; + } + *ts = now; + + return false; + } + + interval_end = *ts + BFF_OVERFLOW_INTERVAL; + *ts = now; + + return time_before(now, interval_end); +} + static void bff_reset_and_report(struct mce *mce) { /* Reset bitfix filter using the CPU that logged the yellow status */ @@ -156,8 +207,18 @@ static void bff_reset_and_report(struct mce *mce) pr_warn("Failed to reset bitfix filter for CPU %d Bank %d\n", mce->extcpu, mce->bank); - /* Placeholder use of bff_get_id() */ - pr_debug("unique_id = 0x%lx\n", bff_get_id(mce)); + /* + * Use the unique id for the bitfix filter instance that overflowed and + * check if this is a repeat within the BFF_OVERFLOW_INTERVAL. If it + * is, then report at WARN severity as the user may want to take action. + */ + if (bff_overflow_is_frequent(bff_get_id(mce))) { + pr_warn_ratelimited(HW_ERR "Socket %d CPU %d Bank %d bitfix filter overflowed frequently\n", + mce->socketid, mce->extcpu, mce->bank); + } else { + pr_notice_ratelimited(HW_ERR "Socket %d CPU %d Bank %d bitfix filter overflowed\n", + mce->socketid, mce->extcpu, mce->bank); + } } static int bff_mce_notify(struct notifier_block *nb, unsigned long val, void *data) @@ -216,7 +277,14 @@ static int __init bff_init(void) static void __exit bff_exit(void) { + unsigned long bff_id; + unsigned long *ts; + mce_unregister_decode_chain(&bff_notifier); + + xa_for_each(&bff_bank_xa, bff_id, ts) + kfree(ts); + xa_destroy(&bff_bank_xa); } module_init(bff_init); From ebaf7c9bbb1e16523d7036e7e1225ad2dfcc6482 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Thu, 13 Aug 2026 12:01:28 +0300 Subject: [PATCH 0265/2286] x86/mm/pat: Fix effective RW computation in lookup_address_in_pgd_attr() lookup_address_in_pgd_attr() accumulates the effective NX and RW bits of the walked page table levels so that verify_rwx() can detect mappings that are both writable and executable. The RW bits are folded into a bool with rw &= pXd_flags(*pXd) & _PAGE_RW; but _PAGE_RW is 0x2. So consider the accumulation line: rw &= pXd_flags(*pXd) & _PAGE_RW; where rw=0x1 and the right side evaluates down to 0x2. It'll end up doing: rw = 0x1 & 0x2 and rw always ends up 0. This way rw becomes false at the first level walked, regardless of the actual permissions, and verify_rwx() treats every mapping as non-writable and never reports a W^X violation. Add double negation to the right side to normalize the _PAGE_RW flag to 0 or 1. Assisted-by: Copilot:claude-opus-4.8 Fixes: ceb647b4b529 ("x86/pat: Introduce lookup_address_in_pgd_attr()") Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Reviewed-by: Juergen Gross Reviewed-by: Lorenzo Stoakes (ARM) Tested-by: syzbot@syzkaller.appspotmail.com Tested-by: Atish Patra Tested-by: Nikunj A Dadhania Cc:stable@vger.kernel.org Link: https://patch.msgid.link/20260813-cpa-fixes-v2-5-39b4ff90f91d@kernel.org --- arch/x86/mm/pat/set_memory.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index c38faf39ce152d..b3060000da8d16 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -732,7 +732,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address, *level = PG_LEVEL_512G; *nx |= pgd_flags(*pgd) & _PAGE_NX; - *rw &= pgd_flags(*pgd) & _PAGE_RW; + *rw &= !!(pgd_flags(*pgd) & _PAGE_RW); p4d = p4d_offset(pgd, address); if (p4d_none(*p4d)) @@ -743,7 +743,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address, *level = PG_LEVEL_1G; *nx |= p4d_flags(*p4d) & _PAGE_NX; - *rw &= p4d_flags(*p4d) & _PAGE_RW; + *rw &= !!(p4d_flags(*p4d) & _PAGE_RW); pud = pud_offset(p4d, address); if (pud_none(*pud)) @@ -754,7 +754,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address, *level = PG_LEVEL_2M; *nx |= pud_flags(*pud) & _PAGE_NX; - *rw &= pud_flags(*pud) & _PAGE_RW; + *rw &= !!(pud_flags(*pud) & _PAGE_RW); pmd = pmd_offset(pud, address); if (pmd_none(*pmd)) @@ -765,7 +765,7 @@ pte_t *lookup_address_in_pgd_attr(pgd_t *pgd, unsigned long address, *level = PG_LEVEL_4K; *nx |= pmd_flags(*pmd) & _PAGE_NX; - *rw &= pmd_flags(*pmd) & _PAGE_RW; + *rw &= !!(pmd_flags(*pmd) & _PAGE_RW); return pte_offset_kernel(pmd, address); } From 0bb666d5f5a2339a5692afb312c2161df9620503 Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Thu, 3 Sep 2026 10:16:38 +0000 Subject: [PATCH 0266/2286] once_lite: Simplify condition handling and fix context analysis When WARN_ON_ONCE() wraps a conditional lock acquisition (such as down_write_trylock()) on architectures relying on DO_ONCE_LITE_IF() (e.g. arm), Clang's context analysis (Thread Safety Analysis) failed with a false positive: fs/ext2/xattr.c:825:6: error: rw_semaphore 'EXT2_I().xattr_sem' is not held on every path through here [-Werror,-Wthread-safety-analysis] 825 | if (WARN_ON_ONCE(!down_write_trylock(&EXT2_I(inode)->xattr_sem))) | ^ This happens because DO_ONCE_LITE_IF() branches on __ONCE_LITE_IF()'s return value (__ret_once), creating an intermediate branch merge point where the lock may or may not be held depending on whether the once-flag (__already_done) was already set. Because the merge branch condition is __ret_once rather than the trylock predicate (__ret_do_once), Clang cannot reconcile the lockset at the branch merge points. Fix it by refactoring __ONCE_LITE_IF() into an unconditional __ONCE_LITE() primitive and redefining __ONCE_LITE_IF(condition) as: (unlikely(condition) && __ONCE_LITE()) This simplifies the implementation, short-circuits evaluation so that __ONCE_LITE() is not called when the condition is false, and ensures that DO_ONCE_LITE_IF() only enters __ONCE_LITE() when __ret_do_once is true. Reported-by: Nathan Chancellor Link: https://lore.kernel.org/all/20260903072759.GA1750084@ax162/ Signed-off-by: Marco Elver Tested-by: Nathan Chancellor # build Link: https://patch.msgid.link/20260903101843.3462767-1-elver@google.com Signed-off-by: Nathan Chancellor --- include/linux/once_lite.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/once_lite.h b/include/linux/once_lite.h index 236592c4eeb1a4..5e2b67039aaf01 100644 --- a/include/linux/once_lite.h +++ b/include/linux/once_lite.h @@ -10,19 +10,21 @@ #define DO_ONCE_LITE(func, ...) \ DO_ONCE_LITE_IF(true, func, ##__VA_ARGS__) -#define __ONCE_LITE_IF(condition) \ +#define __ONCE_LITE() \ ({ \ static bool __section(".data..once") __already_done; \ - bool __ret_cond = !!(condition); \ bool __ret_once = false; \ \ - if (unlikely(__ret_cond) && unlikely(!__already_done)) {\ + if (unlikely(!__already_done)) { \ __already_done = true; \ __ret_once = true; \ } \ unlikely(__ret_once); \ }) +#define __ONCE_LITE_IF(condition) \ + (unlikely(condition) && __ONCE_LITE()) + #define DO_ONCE_LITE_IF(condition, func, ...) \ ({ \ bool __ret_do_once = !!(condition); \ From 9256e0496c14d0c267f508400a2450371e265fca Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:35 -0700 Subject: [PATCH 0267/2286] crypto: x86 - Stop using cpu_has_xfeatures() Checking both boot_cpu_has(X86_FEATURE_AVX*) and cpu_has_xfeatures() has never really been needed in practice, and it's never been universally done (e.g., lib/raid/ omits cpu_has_xfeatures()). Nevertheless, both x86 and UML now explicitly clear the AVX and AVX-512 flags if their xfeatures are missing, which should remove any remaining doubts. Thus, remove all the calls to cpu_has_xfeatures(), as well as the related checks of boot_cpu_has(X86_FEATURE_OSXSAVE). In a few cases there was no corresponding boot_cpu_has(X86_FEATURE_AVX*) check, so add the missing ones. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-4-ebiggers@kernel.org --- arch/x86/crypto/aegis128-aesni-glue.c | 3 +-- arch/x86/crypto/aesni-intel_glue.c | 7 ++----- arch/x86/crypto/aria_aesni_avx2_glue.c | 11 +---------- arch/x86/crypto/aria_aesni_avx_glue.c | 11 +---------- arch/x86/crypto/aria_gfni_avx512_glue.c | 11 +---------- arch/x86/crypto/camellia_aesni_avx2_glue.c | 11 +---------- arch/x86/crypto/camellia_aesni_avx_glue.c | 11 +---------- arch/x86/crypto/cast5_avx_glue.c | 7 ++----- arch/x86/crypto/cast6_avx_glue.c | 7 ++----- arch/x86/crypto/serpent_avx2_glue.c | 9 +-------- arch/x86/crypto/serpent_avx_glue.c | 7 ++----- arch/x86/crypto/sm4_aesni_avx2_glue.c | 11 +---------- arch/x86/crypto/sm4_aesni_avx_glue.c | 11 +---------- arch/x86/crypto/twofish_avx_glue.c | 6 ++---- 14 files changed, 19 insertions(+), 104 deletions(-) diff --git a/arch/x86/crypto/aegis128-aesni-glue.c b/arch/x86/crypto/aegis128-aesni-glue.c index f1adfba1a76ea9..09fc0b15b0e998 100644 --- a/arch/x86/crypto/aegis128-aesni-glue.c +++ b/arch/x86/crypto/aegis128-aesni-glue.c @@ -265,8 +265,7 @@ static struct aead_alg crypto_aegis128_aesni_alg = { static int __init crypto_aegis128_aesni_module_init(void) { if (!boot_cpu_has(X86_FEATURE_XMM4_1) || - !boot_cpu_has(X86_FEATURE_AES) || - !cpu_has_xfeatures(XFEATURE_MASK_SSE, NULL)) + !boot_cpu_has(X86_FEATURE_AES)) return -ENODEV; return crypto_register_aead(&crypto_aegis128_aesni_alg); diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index f522fff9231e8e..f6f899db7482df 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -1548,8 +1548,7 @@ static int __init register_avx_algs(void) if (!boot_cpu_has(X86_FEATURE_AVX2) || !boot_cpu_has(X86_FEATURE_VAES) || !boot_cpu_has(X86_FEATURE_VPCLMULQDQ) || - !boot_cpu_has(X86_FEATURE_PCLMULQDQ) || - !cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) + !boot_cpu_has(X86_FEATURE_PCLMULQDQ)) return 0; err = crypto_register_skciphers(skcipher_algs_vaes_avx2, ARRAY_SIZE(skcipher_algs_vaes_avx2)); @@ -1562,9 +1561,7 @@ static int __init register_avx_algs(void) if (!boot_cpu_has(X86_FEATURE_AVX512BW) || !boot_cpu_has(X86_FEATURE_AVX512VL) || - !boot_cpu_has(X86_FEATURE_BMI2) || - !cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM | - XFEATURE_MASK_AVX512, NULL)) + !boot_cpu_has(X86_FEATURE_BMI2)) return 0; if (boot_cpu_has(X86_FEATURE_PREFER_YMM)) { diff --git a/arch/x86/crypto/aria_aesni_avx2_glue.c b/arch/x86/crypto/aria_aesni_avx2_glue.c index 1487a49bfbaccf..371be2fb64695e 100644 --- a/arch/x86/crypto/aria_aesni_avx2_glue.c +++ b/arch/x86/crypto/aria_aesni_avx2_glue.c @@ -195,22 +195,13 @@ static struct skcipher_alg aria_algs[] = { static int __init aria_avx2_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || !boot_cpu_has(X86_FEATURE_AVX2) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX2 or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - if (boot_cpu_has(X86_FEATURE_GFNI)) { aria_ops.aria_encrypt_16way = aria_aesni_avx_gfni_encrypt_16way; aria_ops.aria_decrypt_16way = aria_aesni_avx_gfni_decrypt_16way; diff --git a/arch/x86/crypto/aria_aesni_avx_glue.c b/arch/x86/crypto/aria_aesni_avx_glue.c index e4e3d78915a5fd..d23fc91c0ebdd1 100644 --- a/arch/x86/crypto/aria_aesni_avx_glue.c +++ b/arch/x86/crypto/aria_aesni_avx_glue.c @@ -182,21 +182,12 @@ static struct skcipher_alg aria_algs[] = { static int __init aria_avx_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - if (boot_cpu_has(X86_FEATURE_GFNI)) { aria_ops.aria_encrypt_16way = aria_aesni_avx_gfni_encrypt_16way; aria_ops.aria_decrypt_16way = aria_aesni_avx_gfni_decrypt_16way; diff --git a/arch/x86/crypto/aria_gfni_avx512_glue.c b/arch/x86/crypto/aria_gfni_avx512_glue.c index 363cbf4399cca0..e05bbeb22d4a90 100644 --- a/arch/x86/crypto/aria_gfni_avx512_glue.c +++ b/arch/x86/crypto/aria_gfni_avx512_glue.c @@ -196,24 +196,15 @@ static struct skcipher_alg aria_algs[] = { static int __init aria_avx512_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || !boot_cpu_has(X86_FEATURE_AVX2) || !boot_cpu_has(X86_FEATURE_AVX512F) || !boot_cpu_has(X86_FEATURE_AVX512VL) || - !boot_cpu_has(X86_FEATURE_GFNI) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_GFNI)) { pr_info("AVX512/GFNI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM | - XFEATURE_MASK_AVX512, &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - aria_ops.aria_encrypt_16way = aria_aesni_avx_gfni_encrypt_16way; aria_ops.aria_decrypt_16way = aria_aesni_avx_gfni_decrypt_16way; aria_ops.aria_ctr_crypt_16way = aria_aesni_avx_gfni_ctr_crypt_16way; diff --git a/arch/x86/crypto/camellia_aesni_avx2_glue.c b/arch/x86/crypto/camellia_aesni_avx2_glue.c index 2d2f4e16537c4f..073fa3bb838889 100644 --- a/arch/x86/crypto/camellia_aesni_avx2_glue.c +++ b/arch/x86/crypto/camellia_aesni_avx2_glue.c @@ -97,22 +97,13 @@ static struct skcipher_alg camellia_algs[] = { static int __init camellia_aesni_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || !boot_cpu_has(X86_FEATURE_AVX2) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX2 or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - return crypto_register_skciphers(camellia_algs, ARRAY_SIZE(camellia_algs)); } diff --git a/arch/x86/crypto/camellia_aesni_avx_glue.c b/arch/x86/crypto/camellia_aesni_avx_glue.c index 5c321f255eb7bd..872e5e07220fa8 100644 --- a/arch/x86/crypto/camellia_aesni_avx_glue.c +++ b/arch/x86/crypto/camellia_aesni_avx_glue.c @@ -98,21 +98,12 @@ static struct skcipher_alg camellia_algs[] = { static int __init camellia_aesni_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - return crypto_register_skciphers(camellia_algs, ARRAY_SIZE(camellia_algs)); } diff --git a/arch/x86/crypto/cast5_avx_glue.c b/arch/x86/crypto/cast5_avx_glue.c index 3aca04d43b34a4..5de35e863370cb 100644 --- a/arch/x86/crypto/cast5_avx_glue.c +++ b/arch/x86/crypto/cast5_avx_glue.c @@ -92,11 +92,8 @@ static struct skcipher_alg cast5_algs[] = { static int __init cast5_init(void) { - const char *feature_name; - - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); + if (!boot_cpu_has(X86_FEATURE_AVX)) { + pr_info("AVX instructions are not detected.\n"); return -ENODEV; } diff --git a/arch/x86/crypto/cast6_avx_glue.c b/arch/x86/crypto/cast6_avx_glue.c index c4dd28c3030364..3d7ea48007bc09 100644 --- a/arch/x86/crypto/cast6_avx_glue.c +++ b/arch/x86/crypto/cast6_avx_glue.c @@ -92,11 +92,8 @@ static struct skcipher_alg cast6_algs[] = { static int __init cast6_init(void) { - const char *feature_name; - - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); + if (!boot_cpu_has(X86_FEATURE_AVX)) { + pr_info("AVX instructions are not detected.\n"); return -ENODEV; } diff --git a/arch/x86/crypto/serpent_avx2_glue.c b/arch/x86/crypto/serpent_avx2_glue.c index f5f2121b79567c..72a9e2b306d639 100644 --- a/arch/x86/crypto/serpent_avx2_glue.c +++ b/arch/x86/crypto/serpent_avx2_glue.c @@ -93,17 +93,10 @@ static struct skcipher_alg serpent_algs[] = { static int __init serpent_avx2_init(void) { - const char *feature_name; - - if (!boot_cpu_has(X86_FEATURE_AVX2) || !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + if (!boot_cpu_has(X86_FEATURE_AVX2)) { pr_info("AVX2 instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } return crypto_register_skciphers(serpent_algs, ARRAY_SIZE(serpent_algs)); diff --git a/arch/x86/crypto/serpent_avx_glue.c b/arch/x86/crypto/serpent_avx_glue.c index 9c8b3a335d5ca3..42c4e156967438 100644 --- a/arch/x86/crypto/serpent_avx_glue.c +++ b/arch/x86/crypto/serpent_avx_glue.c @@ -100,11 +100,8 @@ static struct skcipher_alg serpent_algs[] = { static int __init serpent_init(void) { - const char *feature_name; - - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); + if (!boot_cpu_has(X86_FEATURE_AVX)) { + pr_info("AVX instructions are not detected.\n"); return -ENODEV; } diff --git a/arch/x86/crypto/sm4_aesni_avx2_glue.c b/arch/x86/crypto/sm4_aesni_avx2_glue.c index fec0ab7a63dd4b..eef73894e7776a 100644 --- a/arch/x86/crypto/sm4_aesni_avx2_glue.c +++ b/arch/x86/crypto/sm4_aesni_avx2_glue.c @@ -98,22 +98,13 @@ static struct skcipher_alg sm4_aesni_avx2_skciphers[] = { static int __init sm4_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || !boot_cpu_has(X86_FEATURE_AVX2) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX2 or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - return crypto_register_skciphers(sm4_aesni_avx2_skciphers, ARRAY_SIZE(sm4_aesni_avx2_skciphers)); } diff --git a/arch/x86/crypto/sm4_aesni_avx_glue.c b/arch/x86/crypto/sm4_aesni_avx_glue.c index 88caf418a06f16..ed383da5ff4604 100644 --- a/arch/x86/crypto/sm4_aesni_avx_glue.c +++ b/arch/x86/crypto/sm4_aesni_avx_glue.c @@ -314,21 +314,12 @@ static struct skcipher_alg sm4_aesni_avx_skciphers[] = { static int __init sm4_init(void) { - const char *feature_name; - if (!boot_cpu_has(X86_FEATURE_AVX) || - !boot_cpu_has(X86_FEATURE_AES) || - !boot_cpu_has(X86_FEATURE_OSXSAVE)) { + !boot_cpu_has(X86_FEATURE_AES)) { pr_info("AVX or AES-NI instructions are not detected.\n"); return -ENODEV; } - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); - return -ENODEV; - } - return crypto_register_skciphers(sm4_aesni_avx_skciphers, ARRAY_SIZE(sm4_aesni_avx_skciphers)); } diff --git a/arch/x86/crypto/twofish_avx_glue.c b/arch/x86/crypto/twofish_avx_glue.c index 9e20db01375011..985bc54a23404e 100644 --- a/arch/x86/crypto/twofish_avx_glue.c +++ b/arch/x86/crypto/twofish_avx_glue.c @@ -102,10 +102,8 @@ static struct skcipher_alg twofish_algs[] = { static int __init twofish_init(void) { - const char *feature_name; - - if (!cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, &feature_name)) { - pr_info("CPU feature '%s' is not supported.\n", feature_name); + if (!boot_cpu_has(X86_FEATURE_AVX)) { + pr_info("AVX instructions are not detected.\n"); return -ENODEV; } From ffe52429f87081d450d5a669bda9068ec7f36c8b Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:36 -0700 Subject: [PATCH 0268/2286] lib/crypto: x86: Stop using cpu_has_xfeatures() Checking both boot_cpu_has() and cpu_has_xfeatures() has never really been needed in practice, and it's never been universally done (e.g., lib/raid/ omits cpu_has_xfeatures()). Nevertheless, both x86 and UML now explicitly clear the AVX and AVX-512 flags if their xfeatures are missing, which should remove any remaining doubts. Thus, remove all the calls to cpu_has_xfeatures(). Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-5-ebiggers@kernel.org --- lib/crypto/x86/blake2s.h | 4 +--- lib/crypto/x86/chacha.h | 3 +-- lib/crypto/x86/nh.h | 4 +--- lib/crypto/x86/poly1305.h | 7 ++----- lib/crypto/x86/sha1.h | 4 +--- lib/crypto/x86/sha256.h | 4 +--- lib/crypto/x86/sha512.h | 3 +-- lib/crypto/x86/sm3.h | 3 +-- 8 files changed, 9 insertions(+), 23 deletions(-) diff --git a/lib/crypto/x86/blake2s.h b/lib/crypto/x86/blake2s.h index f8eed6cb042e4b..0f7c51f055c8fc 100644 --- a/lib/crypto/x86/blake2s.h +++ b/lib/crypto/x86/blake2s.h @@ -55,8 +55,6 @@ static void blake2s_mod_init_arch(void) if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_AVX2) && boot_cpu_has(X86_FEATURE_AVX512F) && - boot_cpu_has(X86_FEATURE_AVX512VL) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM | - XFEATURE_MASK_AVX512, NULL)) + boot_cpu_has(X86_FEATURE_AVX512VL)) static_branch_enable(&blake2s_use_avx512); } diff --git a/lib/crypto/x86/chacha.h b/lib/crypto/x86/chacha.h index 10cf8f1c569dca..c79562aac56b67 100644 --- a/lib/crypto/x86/chacha.h +++ b/lib/crypto/x86/chacha.h @@ -165,8 +165,7 @@ static void chacha_mod_init_arch(void) static_branch_enable(&chacha_use_simd); if (boot_cpu_has(X86_FEATURE_AVX) && - boot_cpu_has(X86_FEATURE_AVX2) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) { + boot_cpu_has(X86_FEATURE_AVX2)) { static_branch_enable(&chacha_use_avx2); if (boot_cpu_has(X86_FEATURE_AVX512VL) && diff --git a/lib/crypto/x86/nh.h b/lib/crypto/x86/nh.h index 83361c2e97838d..342636dcb750f4 100644 --- a/lib/crypto/x86/nh.h +++ b/lib/crypto/x86/nh.h @@ -37,9 +37,7 @@ static void nh_mod_init_arch(void) { if (boot_cpu_has(X86_FEATURE_XMM2)) { static_branch_enable(&have_sse2); - if (boot_cpu_has(X86_FEATURE_AVX2) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - NULL)) + if (boot_cpu_has(X86_FEATURE_AVX2)) static_branch_enable(&have_avx2); } } diff --git a/lib/crypto/x86/poly1305.h b/lib/crypto/x86/poly1305.h index ee92e3740a7876..b061b9926fa5da 100644 --- a/lib/crypto/x86/poly1305.h +++ b/lib/crypto/x86/poly1305.h @@ -143,15 +143,12 @@ static void poly1305_emit(const struct poly1305_state *ctx, #define poly1305_mod_init_arch poly1305_mod_init_arch static void poly1305_mod_init_arch(void) { - if (boot_cpu_has(X86_FEATURE_AVX) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) + if (boot_cpu_has(X86_FEATURE_AVX)) static_branch_enable(&poly1305_use_avx); - if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_AVX2) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) + if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_AVX2)) static_branch_enable(&poly1305_use_avx2); if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_AVX2) && boot_cpu_has(X86_FEATURE_AVX512F) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM | XFEATURE_MASK_AVX512, NULL) && /* Skylake downclocks unacceptably much when using zmm, but later generations are fast. */ boot_cpu_data.x86_vfm != INTEL_SKYLAKE_X) static_branch_enable(&poly1305_use_avx512); diff --git a/lib/crypto/x86/sha1.h b/lib/crypto/x86/sha1.h index c48a0131fd12c9..6aff433466e7e7 100644 --- a/lib/crypto/x86/sha1.h +++ b/lib/crypto/x86/sha1.h @@ -59,9 +59,7 @@ static void sha1_mod_init_arch(void) { if (boot_cpu_has(X86_FEATURE_SHA_NI)) { static_call_update(sha1_blocks_x86, sha1_blocks_ni); - } else if (cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - NULL) && - boot_cpu_has(X86_FEATURE_AVX)) { + } else if (boot_cpu_has(X86_FEATURE_AVX)) { if (boot_cpu_has(X86_FEATURE_AVX2) && boot_cpu_has(X86_FEATURE_BMI1) && boot_cpu_has(X86_FEATURE_BMI2)) diff --git a/lib/crypto/x86/sha256.h b/lib/crypto/x86/sha256.h index 0ee69d8e39fe85..e98ffdaf4b14f0 100644 --- a/lib/crypto/x86/sha256.h +++ b/lib/crypto/x86/sha256.h @@ -104,9 +104,7 @@ static void sha256_mod_init_arch(void) boot_cpu_has(X86_FEATURE_PHE_EN) && boot_cpu_data.x86 >= 0x07) { static_call_update(sha256_blocks_x86, sha256_blocks_phe); - } else if (cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, - NULL) && - boot_cpu_has(X86_FEATURE_AVX)) { + } else if (boot_cpu_has(X86_FEATURE_AVX)) { if (boot_cpu_has(X86_FEATURE_AVX2) && boot_cpu_has(X86_FEATURE_BMI2)) static_call_update(sha256_blocks_x86, diff --git a/lib/crypto/x86/sha512.h b/lib/crypto/x86/sha512.h index 0213c70cedd01c..4e177b4606bd2f 100644 --- a/lib/crypto/x86/sha512.h +++ b/lib/crypto/x86/sha512.h @@ -37,8 +37,7 @@ static void sha512_blocks(struct sha512_block_state *state, #define sha512_mod_init_arch sha512_mod_init_arch static void sha512_mod_init_arch(void) { - if (cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL) && - boot_cpu_has(X86_FEATURE_AVX)) { + if (boot_cpu_has(X86_FEATURE_AVX)) { if (boot_cpu_has(X86_FEATURE_AVX2) && boot_cpu_has(X86_FEATURE_BMI2)) static_call_update(sha512_blocks_x86, diff --git a/lib/crypto/x86/sm3.h b/lib/crypto/x86/sm3.h index 3834780f2f6a36..e06d4a22e4fa76 100644 --- a/lib/crypto/x86/sm3.h +++ b/lib/crypto/x86/sm3.h @@ -33,7 +33,6 @@ static void sm3_blocks(struct sm3_block_state *state, #define sm3_mod_init_arch sm3_mod_init_arch static void sm3_mod_init_arch(void) { - if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_BMI2) && - cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) + if (boot_cpu_has(X86_FEATURE_AVX) && boot_cpu_has(X86_FEATURE_BMI2)) static_call_update(sm3_blocks_x86, sm3_blocks_avx); } From b31397fe3007a9fc636fcc34fcdc2b1e69d46804 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Tue, 8 Sep 2026 22:27:29 +0000 Subject: [PATCH 0269/2286] userns: Add __counted_by_ptr attribute to struct uid_gid_map The compiler attribute __counted_by_ptr associates a pointer field of a struct with a sibling field within the same struct that specifies the element count of the allocated memory. This enables KASAN and fortified bounds-checking to detect out-of-bounds accesses to the pointer field at runtime. We can add the __counted_by_ptr attribute to the 'forward' and 'reverse' pointer fields of 'struct uid_gid_map', which are counted by 'nr_extents'. Since 'nr_extents' is defined in a sibling anonymous struct inside an anonymous union, the nearest common non-anonymous struct level is 'struct uid_gid_map' itself, which is supported by the compiler. However, doing so has runtime implications. In the original implementation of insert_extent(), elements are written to map->forward[map->nr_extents] before map->nr_extents is incremented: if (map->nr_extents < UID_GID_MAP_MAX_BASE_EXTENTS) dest = &map->extent[map->nr_extents]; else dest = &map->forward[map->nr_extents]; *dest = *extent; map->nr_extents++; At the time of writing to 'map->forward[map->nr_extents]', map->nr_extents is still 5, but we are accessing index 5 (which is the 6th element). Under __counted_by_ptr(nr_extents), the compiler and KASAN expect the accessed index to be strictly less than map->nr_extents. Therefore, accessing index 5 when the count is 5 triggers an out-of-bounds panic/trap at runtime. To resolve this, insert_extent() is refactored to increment map->nr_extents first, and then use map->nr_extents - 1 as the index: map->nr_extents++; if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) dest = &map->extent[map->nr_extents - 1]; else dest = &map->forward[map->nr_extents - 1]; *dest = *extent; Signed-off-by: Bill Wendling Link: https://patch.msgid.link/20260908222734.3048684-1-morbo@google.com Reviewed-by: Gustavo A. R. Silva Reviewed-by: Bradley Morgan Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- include/linux/user_namespace.h | 4 ++-- kernel/user_namespace.c | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index e38d9e60569f00..2962256eddf712 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -29,8 +29,8 @@ struct uid_gid_map { /* 64 bytes -- 1 cache line */ u32 nr_extents; }; struct { - struct uid_gid_extent *forward; - struct uid_gid_extent *reverse; + struct uid_gid_extent *forward __counted_by_ptr(nr_extents); + struct uid_gid_extent *reverse __counted_by_ptr(nr_extents); }; }; }; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index e9e04ce167df72..7c8316c4271f3b 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -806,13 +806,17 @@ static int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent) map->reverse = NULL; } - if (map->nr_extents < UID_GID_MAP_MAX_BASE_EXTENTS) - dest = &map->extent[map->nr_extents]; + /* + * nr_extents must be updated before the extent and forward arrays are + * accessed, otherwise KSAN will assert an out-of-bounds error. + */ + map->nr_extents++; + if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) + dest = &map->extent[map->nr_extents - 1]; else - dest = &map->forward[map->nr_extents]; + dest = &map->forward[map->nr_extents - 1]; *dest = *extent; - map->nr_extents++; return 0; } From f6f1a2496ba5dcc076d7fb7aa329887cd6ae55fa Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Tue, 8 Sep 2026 22:27:30 +0000 Subject: [PATCH 0270/2286] userns: Add KUnit test suite for uid_gid_map Add a KUnit test suite to verify the insertion and sorting of mappings in struct uid_gid_map. This test suite validates both base extent insertion (<= 5 mappings) and extended extent insertion (> 5 mappings, which triggers the allocation of the forward and reverse pointers). This is especially useful for verifying that the __counted_by_ptr attribute added to 'forward' and 'reverse' pointers works correctly without causing any runtime bounds-checking panics or traps. Signed-off-by: Bill Wendling Link: https://patch.msgid.link/20260908222734.3048684-2-morbo@google.com Tested-by: Bradley Morgan Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- include/linux/user_namespace.h | 6 ++ init/Kconfig | 11 ++++ kernel/Makefile | 1 + kernel/tests/.kunitconfig | 4 ++ kernel/tests/user_ns_map_kunit.c | 98 ++++++++++++++++++++++++++++++++ kernel/user_namespace.c | 8 ++- 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 kernel/tests/.kunitconfig create mode 100644 kernel/tests/user_ns_map_kunit.c diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 2962256eddf712..633157781edcbb 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -207,6 +207,12 @@ extern bool in_userns(const struct user_namespace *ancestor, const struct user_namespace *child); extern bool current_in_userns(const struct user_namespace *target_ns); struct ns_common *ns_get_owner(struct ns_common *ns); + +#if IS_ENABLED(CONFIG_USER_NS_MAP_KUNIT_TEST) +extern int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent); +extern int sort_idmaps(struct uid_gid_map *map); +#endif /* CONFIG_USER_NS_MAP_KUNIT_TEST */ + #else static inline struct user_namespace *get_user_ns(struct user_namespace *ns) diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c522e..27c1ffc675bfbb 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1457,6 +1457,17 @@ config USER_NS If unsure, say N. +config USER_NS_MAP_KUNIT_TEST + tristate "KUint test for user namespace map insertion" if !KUNIT_ALL_TESTS + depends on USER_NS && KUNIT + default KUNIT_ALL_TESTS + help + This builds the KUnit test for user namespace uid/gid map insertion. + It validates map insertion, limits, dynamic allocation of the + extended extents array, and mapping sorting functions. + + If unsure, say N. + config PID_NS bool "PID Namespaces" default y diff --git a/kernel/Makefile b/kernel/Makefile index 1e1a31673577d6..2a64282749b8de 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -141,6 +141,7 @@ obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o +obj-$(CONFIG_USER_NS_MAP_KUNIT_TEST) += tests/user_ns_map_kunit.o CFLAGS_kstack_erase.o += $(DISABLE_KSTACK_ERASE) CFLAGS_kstack_erase.o += $(call cc-option,-mgeneral-regs-only) diff --git a/kernel/tests/.kunitconfig b/kernel/tests/.kunitconfig new file mode 100644 index 00000000000000..b3d1206fd81abd --- /dev/null +++ b/kernel/tests/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_USER_NS_MAP_KUNIT_TEST=y diff --git a/kernel/tests/user_ns_map_kunit.c b/kernel/tests/user_ns_map_kunit.c new file mode 100644 index 00000000000000..24c21e43a36c93 --- /dev/null +++ b/kernel/tests/user_ns_map_kunit.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit test for user namespace map insertion and sorting. + */ + +#define pr_fmt(fmt) "user_namespace: " fmt + +#include +#include + +#define NR_EXTENTS (UID_GID_MAP_MAX_BASE_EXTENTS + 5) + +static void user_ns_map_insert(struct kunit *test) +{ + struct uid_gid_map map; + struct uid_gid_extent extent; + int i, ret; + + memset(&map, 0, sizeof(map)); + + /* Insert up to UID_GID_MAP_MAX_BASE_EXTENTS elements */ + for (i = 0; i < UID_GID_MAP_MAX_BASE_EXTENTS; i++) { + extent.first = i * 10; + extent.lower_first = i * 100; + extent.count = 5; + + ret = insert_extent(&map, &extent); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + KUNIT_EXPECT_EQ(test, map.nr_extents, UID_GID_MAP_MAX_BASE_EXTENTS); + + /* Verify the elements ended up in the 'extent' array */ + for (i = 0; i < UID_GID_MAP_MAX_BASE_EXTENTS; i++) { + KUNIT_EXPECT_EQ(test, map.extent[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.extent[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.extent[i].count, 5); + } +} + +static void user_ns_map_insert_extended(struct kunit *test) +{ + struct uid_gid_map map; + struct uid_gid_extent extent; + int i, ret; + + memset(&map, 0, sizeof(map)); + + /* Insert more than UID_GID_MAP_MAX_BASE_EXTENTS elements */ + for (i = 0; i < NR_EXTENTS; i++) { + int value = 9 - i; + + extent.first = value * 10; + extent.lower_first = value * 100; + extent.count = 5; + + ret = insert_extent(&map, &extent); + KUNIT_ASSERT_EQ(test, ret, 0); + } + + KUNIT_EXPECT_EQ(test, map.nr_extents, NR_EXTENTS); + + /* Now sort the map to set up reverse mapping */ + ret = sort_idmaps(&map); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, map.reverse); + + /* Verify the elements are in 'forward' and that sorting is correct */ + for (i = 0; i < map.nr_extents; i++) { + KUNIT_EXPECT_EQ(test, map.forward[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.forward[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.forward[i].count, 5); + + KUNIT_EXPECT_EQ(test, map.reverse[i].first, i * 10); + KUNIT_EXPECT_EQ(test, map.reverse[i].lower_first, i * 100); + KUNIT_EXPECT_EQ(test, map.reverse[i].count, 5); + } + + kfree(map.forward); + kfree(map.reverse); +} + +static struct kunit_case user_ns_map_test_cases[] = { + KUNIT_CASE(user_ns_map_insert), + KUNIT_CASE(user_ns_map_insert_extended), + {} +}; + +static struct kunit_suite user_ns_map_test_suite = { + .name = "user_ns_map", + .test_cases = user_ns_map_test_cases, +}; + +kunit_test_suite(user_ns_map_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("KUnit test for user namespace map insertion"); +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 7c8316c4271f3b..f9cbb4b92b4879 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include #include #include @@ -783,7 +784,8 @@ static bool mappings_overlap(struct uid_gid_map *new_map, * Takes care to allocate a 4K block of memory if the number of mappings exceeds * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent) +VISIBLE_IF_KUNIT int insert_extent(struct uid_gid_map *map, + struct uid_gid_extent *extent) { struct uid_gid_extent *dest; @@ -819,6 +821,7 @@ static int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent) *dest = *extent; return 0; } +EXPORT_SYMBOL_IF_KUNIT(insert_extent); /* cmp function to sort() forward mappings */ static int cmp_extents_forward(const void *a, const void *b) @@ -854,7 +857,7 @@ static int cmp_extents_reverse(const void *a, const void *b) * sort_idmaps - Sorts an array of idmap entries. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static int sort_idmaps(struct uid_gid_map *map) +VISIBLE_IF_KUNIT int sort_idmaps(struct uid_gid_map *map) { if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) return 0; @@ -875,6 +878,7 @@ static int sort_idmaps(struct uid_gid_map *map) return 0; } +EXPORT_SYMBOL_IF_KUNIT(sort_idmaps); /** * verify_root_map() - check the uid 0 mapping From 97adb32f1127cd501d33b3d328cbe7228ae98048 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:08:22 +0530 Subject: [PATCH 0271/2286] iomap: fix typo "metdata" in comment Correct "metdata" to "metadata", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904103825.8752-2-hemanth.selam@gmail.com Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/direct-io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 8b4039d16ce89f..b70f86292049b4 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -505,7 +505,7 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) * We can only do inline completion for pure overwrites that * don't require additional I/O at completion time. * - * This rules out writes that need zeroing or metdata updates to + * This rules out writes that need zeroing or metadata updates to * convert unwritten or shared extents. * * Writes that extend i_size are also not supported, but this is From 9c7483a6e13a22184862921ff476abeedae7495f Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:08:23 +0530 Subject: [PATCH 0272/2286] iomap: fix repeated word 'the' in comment Drop the second 'the', reported by checkpatch.pl as a possible repeated word. Only touches a comment, no code changes. Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260904103825.8752-3-hemanth.selam@gmail.com Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index e11f1327bd986e..678f4c5329e798 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -1708,7 +1708,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero, * @iomap_flags: Flags to set on the associated iomap to track the batch. * * Returns the folio count directly. Also returns the associated control flag if - * the the batch lookup is performed and the expected offset of a subsequent + * the batch lookup is performed and the expected offset of a subsequent * lookup via out params. The caller is responsible to set the flag on the * associated iomap. */ From 21af603b6509e1e2297de5c159317639477dcbf3 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 7 Aug 2026 01:38:17 +0200 Subject: [PATCH 0273/2286] ACPI: battery: Fix bogus cycle count values on systems without _BIX Devices supporting only the legacy _BIF ACPI control method cannot know the cycle count of the associated battery, so cycle_count always remains 0. Claiming support for POWER_SUPPLY_PROP_CYCLE_COUNT on such devices can thus mislead userspace applications and prevents power supply extensions from supplying correct cycle count values. Fix this by not claiming support for POWER_SUPPLY_PROP_CYCLE_COUNT on devices without _BIX. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260806233817.622975-1-W_Armin@gmx.de Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 131 +++++++++++++++++++++++++++++++++++------ 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 670853ec3a4d75..8599949f878675 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -341,7 +341,9 @@ static int acpi_battery_get_property(struct power_supply *psy, return ret; } -static const enum power_supply_property charge_battery_props[] = { +/* For devices supporting the _BIX ACPI control method */ + +static const enum power_supply_property charge_battery_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -359,7 +361,7 @@ static const enum power_supply_property charge_battery_props[] = { POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property charge_battery_full_cap_broken_props[] = { +static const enum power_supply_property charge_battery_full_cap_broken_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -373,7 +375,7 @@ static const enum power_supply_property charge_battery_full_cap_broken_props[] = POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property energy_battery_props[] = { +static const enum power_supply_property energy_battery_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -391,7 +393,7 @@ static const enum power_supply_property energy_battery_props[] = { POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property energy_battery_full_cap_broken_props[] = { +static const enum power_supply_property energy_battery_full_cap_broken_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -405,6 +407,68 @@ static const enum power_supply_property energy_battery_full_cap_broken_props[] = POWER_SUPPLY_PROP_SERIAL_NUMBER, }; +/* For devices supporting only the _BIF ACPI control method */ + +static const enum power_supply_property charge_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property charge_battery_full_cap_broken_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property energy_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property energy_battery_full_cap_broken_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + /* Battery Management */ struct acpi_offsets { size_t offset; /* offset inside struct acpi_sbs_battery */ @@ -904,6 +968,7 @@ static void __exit battery_hook_exit(void) static int sysfs_add_battery(struct acpi_battery *battery) { + bool extended_info_available = test_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags); struct power_supply_config psy_cfg = { .drv_data = battery, .attr_grp = acpi_battery_groups, @@ -922,25 +987,55 @@ static int sysfs_add_battery(struct acpi_battery *battery) if (power_unit == ACPI_BATTERY_POWER_UNIT_MA) { if (full_cap_broken) { - battery->bat_desc.properties = - charge_battery_full_cap_broken_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(charge_battery_full_cap_broken_props); + if (extended_info_available) { + battery->bat_desc.properties = + charge_battery_full_cap_broken_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_full_cap_broken_extended_props); + } else { + battery->bat_desc.properties = + charge_battery_full_cap_broken_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_full_cap_broken_props); + } } else { - battery->bat_desc.properties = charge_battery_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(charge_battery_props); + if (extended_info_available) { + battery->bat_desc.properties = + charge_battery_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_extended_props); + } else { + battery->bat_desc.properties = + charge_battery_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_props); + } } } else { if (full_cap_broken) { - battery->bat_desc.properties = - energy_battery_full_cap_broken_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(energy_battery_full_cap_broken_props); + if (extended_info_available) { + battery->bat_desc.properties = + energy_battery_full_cap_broken_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_full_cap_broken_extended_props); + } else { + battery->bat_desc.properties = + energy_battery_full_cap_broken_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_full_cap_broken_props); + } } else { - battery->bat_desc.properties = energy_battery_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(energy_battery_props); + if (extended_info_available) { + battery->bat_desc.properties = + energy_battery_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_extended_props); + } else { + battery->bat_desc.properties = + energy_battery_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_props); + } } } From 72a35790da31acc12f05d35466e46254439d5dcd Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 21 Aug 2026 22:15:08 +0900 Subject: [PATCH 0274/2286] ACPI: MRRM: Use for_each_populated_zone() in get_node_num() get_node_num() iterates over the populated zones of every online node with a nested loop. for_each_populated_zone() iterates over the same zones in the same order. So use for_each_populated_zone() instead and remove the unused variable. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260821131530.3375820-1-ekffu200098@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_mrrm.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/acpi_mrrm.c b/drivers/acpi/acpi_mrrm.c index e99cbda8345211..83934e2aa0d4ab 100644 --- a/drivers/acpi/acpi_mrrm.c +++ b/drivers/acpi/acpi_mrrm.c @@ -36,17 +36,11 @@ static u32 mrrm_mem_entry_num; static int get_node_num(struct mrrm_mem_range_entry *e) { - unsigned int nid; + struct zone *zone; - for_each_online_node(nid) { - for (int z = 0; z < MAX_NR_ZONES; z++) { - struct zone *zone = NODE_DATA(nid)->node_zones + z; - - if (!populated_zone(zone)) - continue; - if (zone_intersects(zone, PHYS_PFN(e->base), PHYS_PFN(e->length))) - return zone_to_nid(zone); - } + for_each_populated_zone(zone) { + if (zone_intersects(zone, PHYS_PFN(e->base), PHYS_PFN(e->length))) + return zone_to_nid(zone); } return -ENOENT; From d776369a9eded071df83e4738e1572c4c5b912d7 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 27 Aug 2026 07:37:41 +0200 Subject: [PATCH 0275/2286] ACPI: pfr_update: zero-initialize capability query result query_capability() copies four ACPI buffers into fixed-size fields using the firmware-supplied lengths. The existing overflow guard rejects oversized buffers, but shorter buffers are accepted and leave trailing bytes of the stack-allocated cap_hdr uninitialized. PFRU_IOC_QUERY_CAP then copies the entire structure to userspace. Zero-initialize cap_hdr before querying the firmware. This preserves the existing handling of short firmware responses while preventing disclosure of kernel stack data. Oversized buffers remain rejected. A QEMU _DSM returning one-byte buffers reproduced nonzero stack suffix bytes in all four fields. With this change, the ioctl still succeeds and each unwritten suffix is zero-filled, while 64-byte buffers continue to return -EINVAL. Fixes: 0db89fa243e5 ("ACPI: Introduce Platform Firmware Runtime Update device driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/1a001e1fee9.637da6dc3533246.238498880682901704%40a0rg.com Cc: All applicable Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260827053741.79425-1-kmehltretter@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pfr_update.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/pfr_update.c b/drivers/acpi/pfr_update.c index 9afd2c52fdbd88..98ace679601b85 100644 --- a/drivers/acpi/pfr_update.c +++ b/drivers/acpi/pfr_update.c @@ -422,7 +422,7 @@ static int start_update(int action, struct pfru_device *pfru_dev) static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct pfru_update_cap_info cap_hdr; + struct pfru_update_cap_info cap_hdr = {}; struct pfru_device *pfru_dev = to_pfru_dev(file); void __user *p = (void __user *)arg; u32 rev; From f16c512a07b954aa3b0b3c580e7abb5dd4f5e703 Mon Sep 17 00:00:00 2001 From: Ian Gallagher Date: Sun, 30 Aug 2026 13:19:24 +1200 Subject: [PATCH 0276/2286] ACPI: button: Add lid_init_state quirk for Razer Blade Stealth 13 (Early 2020) RZ09-0310 This patch adds a quirk mapping for the "Blade Stealth 13 (Early 2020) - RZ09-0310" laptop, which exhibits the same issue as the following two related products already accounted for in this file. - Razer Blade Stealth 13 Late 2019 - Blade Pro 17 (Early 2020) - RZ09-0329 Summary of the issue this fixes: The firmware's _LID method still returns "closed" after resume when the lid was opened during S3, causing systemd-logind to suspend the system again within several seconds (15-40) after waking. Signed-off-by: Ian Gallagher Link: https://patch.msgid.link/90ab9b58-da8b-4ffc-bd4b-adf7590545a9@app.fastmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/button.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index cdbb1023a8ee4e..34c7173b5a6c97 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -144,6 +144,18 @@ static const struct dmi_system_id dmi_lid_quirks[] = { }, .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, }, + { + /* + * Razer Blade Stealth 13 early 2020, when the lid is opened + * while suspended the open notification is lost and _LID keeps + * returning closed after resume, causing spurious re-suspends. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Razer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Blade Stealth 13 (Early 2020) - RZ09-0310"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, { /* * Samsung galaxybook2 ,initial _LID device notification returns From 412746bb6652ddc6fa435bc11a9b1253d63bf8a6 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Mon, 31 Aug 2026 06:51:04 +0200 Subject: [PATCH 0277/2286] ACPI: OSL: Use vsnprintf() in acpi_os_vprintf() acpi_os_vprintf() formats every ACPICA diagnostic into a 512-byte static buffer with an unbounded vsprintf(): static char buffer[512]; vsprintf(buffer, fmt, args); ACPICA interpolates table-derived namespace paths into its messages, so the length is driven by table content. A malformed ACPI table can therefore produce a diagnostic longer than the buffer and overwrite whatever follows it. From the System.map of an x86_64_defconfig build, acpi_ioremap_lock sits 624 bytes into that buffer - 112 bytes past its end - so bytes 624..631 of an over-long message land on the mutex's owner field, which __mutex_lock() then dereferences. The overwritten bytes are attacker-influenced but not attacker-controlled: they are printable ASCII from a NUL-terminated string, so the resulting pointer is always non-canonical and always faults. This is a reliable denial of service, not a demonstrated pointer-control primitive. Because acpi_tb_load_namespace() parses the DSDT and every SSDT during acpi_init(), a table supplied by firmware triggers this in PID 1 inside do_one_initcall(), panicking the kernel before userspace exists. Bound the write with vsnprintf(). Truncating a diagnostic is strictly better than corrupting adjacent kernel objects. Tested on pristine Linux 7.2.2 - tag: v7.2.2., Stock x86_64_defconfig (no KASAN, no debug options), table supplied by firmware via "qemu -acpitable": [ 0.022259] ACPI: SSDT 0x000000003FFE1C52 000640 (v01 _ASUS_ Notebook 00000005 MSFT 0100000D) [ 0.353684] Oops: general protection fault, probably for non-canonical address 0x41435f2e345f2e64: 0000 [#1] SMP NOPTI [ 0.353727] RIP: 0010:__mutex_lock.constprop.0+0xbd/0xa40 [ 0.353727] RAX: 41435f2e345f2e30 RBX: 00000000fed00000 [ 0.353727] R15: ffffffff8c248530 [ 0.353727] Call Trace: [ 0.353727] acpi_os_map_iomem [ 0.353727] acpi_ex_system_memory_space_handler [ 0.353727] acpi_ev_address_space_dispatch [ 0.353727] acpi_ex_access_region [ 0.353727] acpi_ex_read_data_from_field [ 0.353727] acpi_ex_resolve_node_to_value [ 0.353727] acpi_ds_evaluate_name_path [ 0.353727] acpi_ps_parse_loop [ 0.353727] acpi_ps_execute_method [ 0.354729] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000009 RAX is the corrupted owner; the faulting address is RAX + 0x34. R15 matches acpi_ioremap_lock. On a KASAN build the overflow is reported directly as: BUG: KASAN: global-out-of-bounds in string. Signed-off-by: Vlatko Kosturjak [ rjw: Subject tweak ] Link: https://patch.msgid.link/CAPAw8HEMdsNN6WhizHkv2Orp2pkQ2-g009XCGFnKnhXoRhcKZQ@mail.gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index ed2162a97bd2aa..5b7aefb48eeb01 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -159,7 +159,7 @@ void __printf(1, 0) acpi_os_vprintf(const char *fmt, va_list args) { static char buffer[512]; - vsprintf(buffer, fmt, args); + vsnprintf(buffer, sizeof(buffer), fmt, args); #ifdef ENABLE_DEBUGGER if (acpi_in_debugger) { From 36bca207462d491e5d61cefeb041acf0fce506a7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Aug 2026 22:04:32 +0200 Subject: [PATCH 0278/2286] ACPI: scan: Drop useless and noisy debug statement The debug message printing statement in handle_to_device() is super-noisy because it triggers whenever the presence of an attached struct acpi_device is checked for the given object in the ACPI namespace and it turns out that there is none. That is a perfectly valid situation and the lack of an attached struct acpi_device is not particularly interesting, so drop that message. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2861358.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/scan.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 163a3cccf197b0..ec3a802e92380b 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -637,10 +637,9 @@ static struct acpi_device *handle_to_device(acpi_handle handle, status = acpi_get_data_full(handle, acpi_scan_drop_device, (void **)&adev, callback); - if (ACPI_FAILURE(status) || !adev) { - acpi_handle_debug(handle, "No context!\n"); + if (ACPI_FAILURE(status) || !adev) return NULL; - } + return adev; } From 13e5c292e0f41856ba41bab065a826da31bc1ec4 Mon Sep 17 00:00:00 2001 From: Prabhakar Pujeri Date: Wed, 2 Sep 2026 11:49:12 +0000 Subject: [PATCH 0279/2286] Documentation: ACPI: EINJ: Update stale driver source reference The EINJ implementation was split when CXL protocol error injection support landed: einj-core.c holds the core logic, einj-cxl.c the CXL-specific part, and apei-internal.h the shared plumbing. The old drivers/acpi/apei/einj.c does not exist anymore, so the documentation points readers at a nonexistent file. Point them at the actual files instead. Signed-off-by: Prabhakar Pujeri Link: https://patch.msgid.link/20260902114912.6690-1-prabhakar.pujeri@dell.com Signed-off-by: Rafael J. Wysocki --- Documentation/firmware-guide/acpi/apei/einj.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/firmware-guide/acpi/apei/einj.rst b/Documentation/firmware-guide/acpi/apei/einj.rst index 7d8435d35a18bf..70fa59bd97d8b1 100644 --- a/Documentation/firmware-guide/acpi/apei/einj.rst +++ b/Documentation/firmware-guide/acpi/apei/einj.rst @@ -180,8 +180,10 @@ function are specified using param1:: | segment | bus | device | function | reserved | +-------------------------------------------------+ -Anyway, you get the idea, if there's doubt just take a look at the code -in drivers/acpi/apei/einj.c. +Anyway, you get the idea, if there's doubt just take a look at the code: +drivers/acpi/apei/einj-core.c holds the core logic, while CXL-specific +handling lives in drivers/acpi/apei/einj-cxl.c and the shared plumbing +in drivers/acpi/apei/apei-internal.h. An ACPI 5.0 BIOS may also allow vendor-specific errors to be injected. In this case a file named vendor will contain identifying information From 7c0940e431c33ca13e9fde57ea0ad56da6529c56 Mon Sep 17 00:00:00 2001 From: Itay Shem-tov Date: Thu, 3 Sep 2026 13:42:44 +0300 Subject: [PATCH 0280/2286] ACPI: SBS: report relative state of charge as CAPACITY acpi_sbs reads SBS command 0x0e (AbsoluteStateOfCharge) into battery->state_of_charge, which is exported unmodified as POWER_SUPPLY_PROP_CAPACITY. Per the Smart Battery Data Specification 1.1, 0x0e is expressed as a percentage of DesignCapacity and is explicitly permitted to exceed 100%. Documentation/ABI/testing/sysfs-class-power specifies the capacity attribute as "Valid values: 0 - 100 (percent)", so any pack whose FullChargeCapacity exceeds its DesignCapacity - the normal state of a new or recently replaced battery - makes the driver report out of range. The correct source is 0x0d (RelativeStateOfCharge), a percentage of FullChargeCapacity, which the specification bounds to 0..100. This is the same defect that was fixed in the i2c SBS driver by commit b1f092f6480e ("sbs-battery.c: Capacity attr = remaining relative capacity"), whose reasoning applies verbatim here; drivers/acpi/sbs.c was not updated at the time. drivers/power/supply/sbs-battery.c has used 0x0d since, so the two SBS drivers currently disagree about what CAPACITY means. Observed on a MacBookPro11,1 with an SMP/bq20z451 pack (FullChargeCapacity 6775 mAh, DesignCapacity 6400 mAh). Both registers read back-to-back from the pack at a full charge: 0x0d RelativeStateOfCharge = 100 % 0x0e AbsoluteStateOfCharge = 106 % /sys/class/power_supply/BAT0/capacity reported 106 while upower, which computes charge_now/charge_full itself rather than trusting the driver, reported 100. battery->state_of_charge has no other consumer, so no other property changes behaviour. Signed-off-by: Itay Shem-tov Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260903104244.25556-1-itayst@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 86b7c797585263..862cb94a8b43ba 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -318,7 +318,7 @@ static struct acpi_battery_reader state_readers[] = { {0x0a, SMBUS_READ_WORD, offsetof(struct acpi_battery, rate_now)}, {0x0b, SMBUS_READ_WORD, offsetof(struct acpi_battery, rate_avg)}, {0x0f, SMBUS_READ_WORD, offsetof(struct acpi_battery, capacity_now)}, - {0x0e, SMBUS_READ_WORD, offsetof(struct acpi_battery, state_of_charge)}, + {0x0d, SMBUS_READ_WORD, offsetof(struct acpi_battery, state_of_charge)}, {0x16, SMBUS_READ_WORD, offsetof(struct acpi_battery, state)}, }; From 5636eebb7bb1c6f06f0058d48353686304d4bb47 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 18:58:34 +0200 Subject: [PATCH 0281/2286] ACPI: PM: Drop parent state update from acpi_device_get_power() The parent state update in acpi_device_get_power(), that occurs when the child device turns out to be in D0 and the parent's power state is still unknown, is highly questionable because it may cause the reference counters of the power resources used by the parent in D0 (if any) to underflow when the parent goes into a low-power state later. Moreover, there is no reason to do it on reads from the real_power_state sysfs attribute of the child. That check had been added by commit 8f7412a792bc ("ACPI / PM: Infer parent power state from child if unknown, v2") before starting to handle the "missing _PSC and no power resources" case directly in acpi_bus_init_power(), as of commit b3785492268f ("ACPI / PM: Do not power manage devices in unknown initial states"). It is not necessary any more and commit b3785492268f should have removed it. Drop it now. Fixes: b3785492268f ("ACPI / PM: Do not power manage devices in unknown initial states") Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/5147279.31r3eYUQgx@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/device_pm.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index aa55ecfc2923cf..4269735aadde59 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -75,15 +75,14 @@ static int acpi_dev_pm_explicit_get(struct acpi_device *device, int *state) int acpi_device_get_power(struct acpi_device *device, int *state) { int result = ACPI_STATE_UNKNOWN; - struct acpi_device *parent; int error; if (!device || !state) return -EINVAL; - parent = acpi_dev_parent(device); - if (!device->flags.power_manageable) { + struct acpi_device *parent = acpi_dev_parent(device); + /* TBD: Non-recursive algorithm for walking up hierarchy. */ *state = parent ? parent->power.state : ACPI_STATE_D0; goto out; @@ -119,16 +118,6 @@ int acpi_device_get_power(struct acpi_device *device, int *state) result = psc > ACPI_STATE_D2 ? ACPI_STATE_D3_HOT : psc; } - /* - * If we were unsure about the device parent's power state up to this - * point, the fact that the device is in D0 implies that the parent has - * to be in D0 too, except if ignore_parent is set. - */ - if (!device->power.flags.ignore_parent && parent && - parent->power.state == ACPI_STATE_UNKNOWN && - result == ACPI_STATE_D0) - parent->power.state = ACPI_STATE_D0; - *state = result; out: From 2f4ddf2bd1baf0ee3c8542ec6f382a3585cae607 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:05:37 +0200 Subject: [PATCH 0282/2286] ACPI: scan: Stop calling acpi_bus_init_power() early There is a problem, introduced by commit 9d9bcae47fd5 ("ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device") inadvertently, that devices with missing dependencies may be put into power state D0 prematurely [1]. Namely, acpi_bus_init_power() called by acpi_bus_get_power_flags() during the early initialization of ACPI device objects, may discover that all of the power resources needed by the given device to be in power state D0 are initially on, so it will reference count those power resources and transition the device into D0. Later, if acpi_bus_attach() running for that device notices that it has missing dependencies, the enumeration of it will be deferred and its power_manageable flag will be cleared, even though it is still in D0 at that point. After the dependencies in question have been met, acpi_bus_attach() runs again for the device and now it calls acpi_bus_init_power() that takes additional references to the power resources used by the device in D0. These additional references prevent the power resources from being turned off when the device goes into D3hot/D3cold. Another problem, related to the previous one, is that ACPI power state initialization may be carried out for devices whose parents are not ready for enumeration which may lead to initialization ordering issues. To address both, stop calling acpi_bus_init_power() from acpi_bus_get_power_flags(), but also take the initialization of PCI devices into account, which needs to be done because they are initialized and bound to their ACPI companions before acpi_bus_attach() is called for the latter. To that end, notice that acpi_power_up_if_adr_present() is used for powering-up PCI devices in D3cold before walking the bus in order to discover them and the initial ACPI power state of those devices needs to be known for this purpose, so add an acpi_bus_init_power() invocation to that function. [The debug statement printed by it duplicates the debug statements printed during the acpi_bus_init_power() execution, so drop it.] Moreover, since the ACPI companions of PCI devices are associated with the corresponding PCI devices found on the bus before acpi_bus_attach() is called for them, it is not necessary or even useful to skip them in acpi_bus_attach() due to an ACPI status mismatch, so avoid doing that and complain if the ACPI status does not match the observed situation. Also use the ACPI power state tracking to decide whether or not the device's power state needs to be initialized in acpi_bus_attach() instead of using the "initialized" flag of the ACPI device object for this purpose, which is fragile and inconvenient, and clear the power_manageable flag on failure in acpi_bus_init_power() (additionally, poison the device ACPI power state as "invalid" if the initialization of it fails). That allows the clearing of the power_manageable flag for devices with unmet dependencies to be dropped. While at it, add a debug message pringing statement to acpi_bus_init_power() to facilitate diagnostics. Fixes: 9d9bcae47fd5 ("ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device") Link: https://lore.kernel.org/linux-acpi/20260820-acpi-power-resource-ref-fix-v2-1-29818173ea13@linux.spacemit.com/ [1] Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/2295263.irdbgypaU6@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/device_pm.c | 62 ++++++++++++++++++++++++++++++++-------- drivers/acpi/scan.c | 40 +++++++++++++++++--------- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 4269735aadde59..76104f715efa11 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -23,6 +23,8 @@ #include "fan.h" #include "internal.h" +#define ACPI_D_STATE_INVALID ACPI_D_STATE_COUNT + /** * acpi_power_state_string - String representation of ACPI device power state. * @state: ACPI device power state to return the string representation of. @@ -293,24 +295,28 @@ int acpi_bus_set_power(acpi_handle handle, int state) } EXPORT_SYMBOL(acpi_bus_set_power); -int acpi_bus_init_power(struct acpi_device *device) +static int acpi_device_init_power(struct acpi_device *device) { int state; int result; - if (!device) - return -EINVAL; - - device->power.state = ACPI_STATE_UNKNOWN; - if (!acpi_device_is_present(device)) { - device->flags.initialized = false; - return -ENXIO; - } - result = acpi_device_get_power(device, &state); if (result) return result; + /* + * If the current power state of the device is D0 and it has a parent + * whose power state is not ignored, and the parent's power state + * initialization has failed, the parent's power state can be updated to + * D0 for consistency. + */ + if (!device->power.flags.ignore_parent && state == ACPI_STATE_D0) { + struct acpi_device *parent = acpi_dev_parent(device); + + if (parent && parent->power.state == ACPI_D_STATE_INVALID) + parent->power.state = ACPI_STATE_D0; + } + if (state < ACPI_STATE_D3_COLD && device->power.flags.power_resources) { /* Reference count the power resources. */ result = acpi_power_on_resources(device, state); @@ -340,9 +346,36 @@ int acpi_bus_init_power(struct acpi_device *device) state = ACPI_STATE_D0; } device->power.state = state; + + acpi_handle_debug(device->handle, "Initial power state: %s\n", + acpi_power_state_string(state)); + return 0; } +int acpi_bus_init_power(struct acpi_device *device) +{ + int result; + + if (device->power.state != ACPI_STATE_UNKNOWN) + return 0; + + /* + * The ACPI device power state can be only initialized once. If this + * fails, ACPI power management will not be used for the device going + * forward. + */ + result = acpi_device_init_power(device); + if (result) { + device->flags.power_manageable = 0; + device->power.state = ACPI_D_STATE_INVALID; + acpi_handle_info(device->handle, + "Initial power state undetermined, ACPI PM disabled\n"); + } + + return result; +} + /** * acpi_device_fix_up_power - Force device with missing _PSC into D0. * @device: Device object whose power state is to be fixed up. @@ -464,8 +497,13 @@ static int acpi_power_up_if_adr_present(struct acpi_device *adev, void *not_used if (!(adev->flags.power_manageable && adev->pnp.type.bus_address)) return 0; - acpi_handle_debug(adev->handle, "Power state: %s\n", - acpi_power_state_string(adev->power.state)); + /* + * This is done during the PCI root initialization which occurs before + * acpi_bus_attach() is called for the device, so the ACPI power state + * of the device needs to be initialized here. + */ + if (acpi_bus_init_power(adev)) + return 0; if (adev->power.state == ACPI_STATE_D3_COLD) return acpi_device_set_power(adev, ACPI_STATE_D0); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index ec3a802e92380b..13d1e77c99737b 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1142,8 +1143,7 @@ static void acpi_bus_get_power_flags(struct acpi_device *device) device->power.states[ACPI_STATE_D3_COLD].flags.valid = 1; } - if (acpi_bus_init_power(device)) - device->flags.power_manageable = 0; + device->power.state = ACPI_STATE_UNKNOWN; } static void acpi_bus_get_flags(struct acpi_device *device) @@ -2337,38 +2337,50 @@ static int acpi_scan_attach_handler(struct acpi_device *device) static int acpi_bus_attach(struct acpi_device *device, void *first_pass) { bool skip = !first_pass && device->flags.visited; + struct pci_dev *pci; acpi_handle ejd; int ret; if (skip) goto ok; + device->flags.initialized = true; + if (ACPI_SUCCESS(acpi_bus_get_ejd(device->handle, &ejd))) register_dock_dependent_device(device, ejd); acpi_bus_get_status(device); - /* Skip devices that are not ready for enumeration (e.g. not present) */ - if (!acpi_dev_ready_for_enumeration(device)) { - device->flags.initialized = false; + /* + * If the given ACPI device object has been already associated with a + * PCI device found on the bus, its status is effectively "present and + * enabled", and dependencies are not tracked for PCI devices, so it is + * not necessary or even useful to check the device's readiness in that + * case. + */ + pci = acpi_dev_get_pci_dev(device); + if (pci) { + acpi_handle_debug(device->handle, "PCI companion %s found\n", + pci_name(pci)); + + if (!acpi_device_is_present(device)) + pci_info(pci, FW_BUG "ACPI status differs from reality\n"); + + pci_dev_put(pci); + } else if (!acpi_dev_ready_for_enumeration(device)) { + /* The device is not ready (e.g. not present), so skip it. */ acpi_device_clear_enumerated(device); - device->flags.power_manageable = 0; return 0; } + if (device->handler) goto ok; acpi_ec_register_opregions(device); - if (!device->flags.initialized) { - device->flags.power_manageable = - device->power.states[ACPI_STATE_D0].flags.valid; - if (acpi_bus_init_power(device)) - device->flags.power_manageable = 0; + acpi_bus_init_power(device); - device->flags.initialized = true; - } else if (device->flags.visited) { + if (device->flags.visited) goto ok; - } ret = acpi_scan_attach_handler(device); if (ret < 0) From 811268084ef2f9ead128c3d31b5b646510b7c180 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:06:32 +0200 Subject: [PATCH 0283/2286] ACPI: PM: Move acpi_bus_init_power() declaration to internal header file Since acpi_bus_init_power() is only used internally in the core ACPI device enumeration and power management code, it need not be visible outside, so move its declaration to an internal header file. No functional impact. Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/3079955.e9J7NaK4W3@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/internal.h | 1 + include/acpi/acpi_bus.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 40f875b265a942..011da4ed6a80ee 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -157,6 +157,7 @@ void acpi_turn_off_unused_power_resources(void); Device Power Management -------------------------------------------------------------------------- */ int acpi_device_get_power(struct acpi_device *device, int *state); +int acpi_bus_init_power(struct acpi_device *device); int acpi_wakeup_device_init(void); /* -------------------------------------------------------------------------- diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b239..9da74b3d4774d1 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -613,7 +613,6 @@ int acpi_bus_get_status(struct acpi_device *device); int acpi_bus_set_power(acpi_handle handle, int state); const char *acpi_power_state_string(int state); int acpi_device_set_power(struct acpi_device *device, int state); -int acpi_bus_init_power(struct acpi_device *device); int acpi_device_fix_up_power(struct acpi_device *device); void acpi_device_fix_up_power_extended(struct acpi_device *adev); void acpi_device_fix_up_power_children(struct acpi_device *adev); From dc2447880b728993a40881bf4ad482dbb93c14f2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:09:22 +0200 Subject: [PATCH 0284/2286] ACPI: scan: Combine two conditionals in acpi_bus_attach() There are two conditionals in acpi_bus_attach() that can be combined, which slightly reduces the overhead and makes the code a bit easier to follow, so do that. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/8746939.T7Z3S40VBb@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/scan.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 13d1e77c99737b..30bfef67cbaa5d 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2386,13 +2386,8 @@ static int acpi_bus_attach(struct acpi_device *device, void *first_pass) if (ret < 0) return 0; - if (ret > 0 && !device->flags.enumeration_by_parent) { - acpi_device_set_enumerated(device); - goto ok; - } - - if (device->pnp.type.platform_id || device->pnp.type.backlight || - device->flags.enumeration_by_parent) + if (device->flags.enumeration_by_parent || + (!ret && (device->pnp.type.platform_id || device->pnp.type.backlight))) acpi_default_enumeration(device); else acpi_device_set_enumerated(device); From 5bc1e72f80da4c20ce8b57851be4fad67e855ebd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:51:23 +0200 Subject: [PATCH 0285/2286] ACPI: glue: Reduce debug noise from acpi_device_notify() When debug is enabled in the ACPI glue code that handles binding devices to ACPI companions, acpi_device_notify() produces a lot of message noise related to devices that have no ACPI companions. Reduce that noise by checking the most obvious case, ACPI device objects, directly and returning from acpi_device_notify() in that case without printing any debug messages. Also avoid printing a debug message when there is no matching ACPI bus type definition for the given device, which is the case for the vast majority of devices. Additionally, make the debug messages that get printed more informative. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/6325978.lOV4Wx5bFT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index b1776809279ddb..a2a394c226b3f3 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -354,22 +354,27 @@ void acpi_device_notify(struct device *dev) struct acpi_device *adev; int ret; + /* ACPI devices have no ACPI companions. */ + if (dev->bus == &acpi_bus_type) + return; + ret = acpi_bind_one(dev, NULL); if (ret) { struct acpi_bus_type *type = acpi_get_bus_type(dev); if (!type) - goto err; + return; adev = type->find_companion(dev); if (!adev) { dev_dbg(dev, "ACPI companion not found\n"); - goto err; + return; } ret = acpi_bind_one(dev, adev); - if (ret) - goto err; - + if (ret) { + dev_dbg(dev, "Binding to ACPI companion failed\n"); + return; + } if (type->setup) { type->setup(dev); goto done; @@ -391,11 +396,6 @@ void acpi_device_notify(struct device *dev) done: acpi_handle_debug(ACPI_HANDLE(dev), "Bound to device %s\n", dev_name(dev)); - - return; - -err: - dev_dbg(dev, "No ACPI support\n"); } void acpi_device_notify_remove(struct device *dev) From aef7792cddcac7ab400912c2947a595401d4a7e8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:54:10 +0200 Subject: [PATCH 0286/2286] ACPI: glue: Rework the success message in acpi_device_notify() Using an ACPI handle in the acpi_device_notify() success message is somewhat misleading because the "physical" device is not bound to an ACPI namespace object in that function, but to an ACPI device object attached to it. Moreover, the message is outright confusing for the "physical" devices bound to ACPI devices without ACPI namespace objects (for example, fixed event buttons). Address this by turning that message into a dev_dbg() one printed for the "physical" device and mentioning the name of the ACPI companion. Fixes: ab06eb920401 ("ACPI: scan: Register platform devices for fixed event buttons") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2448150.ElGaqSPkdT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index a2a394c226b3f3..a47cccc4efd3f8 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -394,8 +394,7 @@ void acpi_device_notify(struct device *dev) adev->handler->bind(dev); done: - acpi_handle_debug(ACPI_HANDLE(dev), "Bound to device %s\n", - dev_name(dev)); + dev_dbg(dev, "Bound to ACPI device %s\n", acpi_dev_name(adev)); } void acpi_device_notify_remove(struct device *dev) From 78ddbfaa72d68acc98cd5ba12eca071d452702c4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:56:56 +0200 Subject: [PATCH 0287/2286] ACPI: utils: Adjust message printing macros for ACPI objects The majority of acpi_handle_() macros use neither pr_fmt() nor dev_fmt() and acpi_handle_debug() uses pr_fmt() like dev_fmt(), but only if dynamic debug is compiled in. For more consistency, redefine all of the macros in question to use dev_fmt() because they are more similar to the analogous dev_*() family of macros than to the pr_*() ones and make all of them use it in the same way. While at it, rephrase the comment documenting those macros for more clarity. Fixes: 45fef5b88d1f ("ACPI: add dynamic_debug support") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2300525.irdbgypaU6@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac81209473..f7e3efd328f795 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1305,34 +1305,34 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c #endif /* - * acpi_handle_: Print message with ACPI prefix and object path + * acpi_handle_ - Print a message with ACPI prefix and object path * - * These interfaces acquire the global namespace mutex to obtain an object - * path. In interrupt context, it shows the object path as . + * In thread context, the global namespace mutex is acquired to obtain the + * object path. In interrupt context, the object path is shown as . */ #define acpi_handle_emerg(handle, fmt, ...) \ - acpi_handle_printk(KERN_EMERG, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_EMERG, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_alert(handle, fmt, ...) \ - acpi_handle_printk(KERN_ALERT, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_ALERT, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_crit(handle, fmt, ...) \ - acpi_handle_printk(KERN_CRIT, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_CRIT, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_err(handle, fmt, ...) \ - acpi_handle_printk(KERN_ERR, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_ERR, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_warn(handle, fmt, ...) \ - acpi_handle_printk(KERN_WARNING, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_WARNING, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_notice(handle, fmt, ...) \ - acpi_handle_printk(KERN_NOTICE, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_NOTICE, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_info(handle, fmt, ...) \ - acpi_handle_printk(KERN_INFO, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_INFO, handle, dev_fmt(fmt), ##__VA_ARGS__) #if defined(DEBUG) #define acpi_handle_debug(handle, fmt, ...) \ - acpi_handle_printk(KERN_DEBUG, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_DEBUG, handle, dev_fmt(fmt), ##__VA_ARGS__) #else #if defined(CONFIG_DYNAMIC_DEBUG) #define acpi_handle_debug(handle, fmt, ...) \ _dynamic_func_call(fmt, __acpi_handle_debug, \ - handle, pr_fmt(fmt), ##__VA_ARGS__) + handle, dev_fmt(fmt), ##__VA_ARGS__) #else #define acpi_handle_debug(handle, fmt, ...) \ ({ \ From a23fca4ae137ec8e7620cc4f00b468b89cf5a7bd Mon Sep 17 00:00:00 2001 From: K Prateek Nayak Date: Wed, 9 Sep 2026 10:26:50 +0000 Subject: [PATCH 0288/2286] cpufreq/amd-pstate-ut: Fix amd_pstate_ut_check_freq failure with 'Requested CPU Min frequency' BIOS option Since commit 608a76b65288 ("cpufreq/amd-pstate: Add support for the "Requested CPU Min frequency" BIOS option"), amd-pstate driver sets policy->min to frequency corresponding to bios_min_perf if a valid BIOS programmed min frequency value is detected. amd_pstate_ut_check_freq expects policy->min to always match lowest_nonlinear_freq which does not hold true on platforms with user configured BIOS min freq. Update the test case to compare policy->min to bios_min_freq on platforms that set it. Final comparison is adjusted to account for insane values by clamping the result within the supported frequency range. While at it, move freq_to_perf() and perf_to_freq() helpers to internal header to allow their use from amd-pstate-ut. Reviewed-by: Mario Limonciello Fixes: 608a76b65288 ("cpufreq/amd-pstate: Add support for the "Requested CPU Min frequency" BIOS option") Signed-off-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260909102650.4582-1-kprateek.nayak@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate-ut.c | 23 ++++++++++++++++++++++- drivers/cpufreq/amd-pstate.c | 13 ------------- drivers/cpufreq/amd-pstate.h | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index e23773680e050d..c2c1a166b3a9e7 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -226,11 +226,14 @@ static int amd_pstate_ut_check_freq(u32 index) for_each_online_cpu(cpu) { struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL; struct amd_cpudata *cpudata; + union perf_cached perf; policy = cpufreq_cpu_get(cpu); if (!policy) continue; + cpudata = policy->driver_data; + perf = READ_ONCE(cpudata->perf); if (!((policy->cpuinfo.max_freq >= cpudata->nominal_freq) && (cpudata->nominal_freq > cpudata->lowest_nonlinear_freq) && @@ -242,7 +245,25 @@ static int amd_pstate_ut_check_freq(u32 index) return -EINVAL; } - if (cpudata->lowest_nonlinear_freq != policy->min) { + if (perf.bios_min_perf) { + u32 bios_min_freq = perf_to_freq(perf, cpudata->nominal_freq, + perf.bios_min_perf); + + /* + * User set bios_min_freq cannot be trusted to + * be within the driver limits. Clamp it similar + * to cpufreq_verify_within_cpu_limits(). + */ + bios_min_freq = clamp_t(u32, bios_min_freq, + policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + if (bios_min_freq != policy->min) { + pr_err("%s cpu%d bios_min_freq=%d policy_min=%d, they should be equal!\n", + __func__, cpu, bios_min_freq, policy->min); + return -EINVAL; + } + } else if (cpudata->lowest_nonlinear_freq != policy->min) { pr_err("%s cpu%d cpudata_lowest_nonlinear_freq=%d policy_min=%d, they should be equal!\n", __func__, cpu, cpudata->lowest_nonlinear_freq, policy->min); return -EINVAL; diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 8bfd46d60843dd..ae1673b36ac45b 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -145,19 +145,6 @@ static struct quirk_entry quirk_amd_7k62 = { .lowest_freq = 550, }; -static inline u8 freq_to_perf(union perf_cached perf, u32 nominal_freq, unsigned int freq_val) -{ - u32 perf_val = DIV_ROUND_UP_ULL((u64)freq_val * perf.nominal_perf, nominal_freq); - - return (u8)clamp(perf_val, perf.lowest_perf, perf.highest_perf); -} - -static inline u32 perf_to_freq(union perf_cached perf, u32 nominal_freq, u8 perf_val) -{ - return DIV_ROUND_UP_ULL((u64)nominal_freq * perf_val, - perf.nominal_perf); -} - static int __init dmi_matched_7k62_bios_bug(const struct dmi_system_id *dmi) { /** diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index 9f5a81976eae06..578465187b7ae9 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -159,6 +159,20 @@ enum amd_pstate_mode { AMD_PSTATE_GUIDED, AMD_PSTATE_MAX, }; + +static inline u8 freq_to_perf(union perf_cached perf, u32 nominal_freq, unsigned int freq_val) +{ + u32 perf_val = DIV_ROUND_UP_ULL((u64)freq_val * perf.nominal_perf, nominal_freq); + + return (u8)clamp(perf_val, perf.lowest_perf, perf.highest_perf); +} + +static inline u32 perf_to_freq(union perf_cached perf, u32 nominal_freq, u8 perf_val) +{ + return DIV_ROUND_UP_ULL((u64)nominal_freq * perf_val, + perf.nominal_perf); +} + const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode); int amd_pstate_get_status(void); int amd_pstate_update_status(const char *buf, size_t size); From b93f05df9c660a3a11d1db608a859241d65ccb96 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:37 -0700 Subject: [PATCH 0289/2286] lib/crc: x86: Stop using cpu_has_xfeatures() Checking both boot_cpu_has() and cpu_has_xfeatures() has never really been needed in practice, and it's never been universally done (e.g., lib/raid/ omits cpu_has_xfeatures()). Nevertheless, both x86 and UML now explicitly clear the AVX and AVX-512 flags if their xfeatures are missing, which should remove any remaining doubts. Thus, remove all the calls to cpu_has_xfeatures(). Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-6-ebiggers@kernel.org --- lib/crc/x86/crc-pclmul-template.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/crc/x86/crc-pclmul-template.h b/lib/crc/x86/crc-pclmul-template.h index 02744831c6fac0..893119bb7c0771 100644 --- a/lib/crc/x86/crc-pclmul-template.h +++ b/lib/crc/x86/crc-pclmul-template.h @@ -27,16 +27,14 @@ DEFINE_STATIC_CALL(prefix##_pclmul, prefix##_pclmul_sse) static inline bool have_vpclmul(void) { return boot_cpu_has(X86_FEATURE_VPCLMULQDQ) && - boot_cpu_has(X86_FEATURE_AVX2) && - cpu_has_xfeatures(XFEATURE_MASK_YMM, NULL); + boot_cpu_has(X86_FEATURE_AVX2); } static inline bool have_avx512(void) { return boot_cpu_has(X86_FEATURE_AVX512BW) && boot_cpu_has(X86_FEATURE_AVX512VL) && - !boot_cpu_has(X86_FEATURE_PREFER_YMM) && - cpu_has_xfeatures(XFEATURE_MASK_AVX512, NULL); + !boot_cpu_has(X86_FEATURE_PREFER_YMM); } /* From 01f10e8c6a46d93c1c4e7de228d9b39390337ffa Mon Sep 17 00:00:00 2001 From: Mohammed EL Kadiri Date: Thu, 11 Jun 2026 08:00:54 +0100 Subject: [PATCH 0290/2286] cred: prevent slab cache merging for cred_jar Add SLAB_NO_MERGE to cred_jar to ensure struct cred objects get dedicated slab pages, preventing the allocator from merging this cache with other similarly-sized caches. This is a hardening measure to provide type isolation for credential objects. Reviewed-by: Kees Cook Signed-off-by: Mohammed EL Kadiri [PM: line length fix] Signed-off-by: Paul Moore --- kernel/cred.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/cred.c b/kernel/cred.c index 0bd6a58bc12dc1..d01df40a9a3861 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -537,7 +537,8 @@ void __init cred_init(void) { /* allocate a slab in which we can store credentials */ cred_jar = KMEM_CACHE(cred, - SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); + SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT | + SLAB_NO_MERGE); } /** From 1aeacc2cbf86eb522c3add7626477a4439048f8c Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 6 Aug 2026 12:58:24 -0400 Subject: [PATCH 0291/2286] buffer_head: Remove b_page All users except bh_offset() have been converted to use b_folio instead. Convert bh_offset() and remove b_page. Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Chao Shi Acked-by: Weidong Zhu Reviewed-by: Jan Kara Link: https://patch.msgid.link/e9d168901578902bffe78ac8b5dfaf1210ee7fb3.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- include/linux/buffer_head.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index fd2c7115c05427..699970b4bbf28d 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -59,10 +59,7 @@ struct address_space; struct buffer_head { unsigned long b_state; /* buffer state bitmap (see above) */ struct buffer_head *b_this_page;/* circular list of page's buffers */ - union { - struct page *b_page; /* the page this bh is mapped to */ - struct folio *b_folio; /* the folio this bh is mapped to */ - }; + struct folio *b_folio; /* the folio this bh is mapped to */ sector_t b_blocknr; /* start block number */ size_t b_size; /* size of mapping */ @@ -172,7 +169,7 @@ static __always_inline int buffer_uptodate(const struct buffer_head *bh) static inline unsigned long bh_offset(const struct buffer_head *bh) { - return (unsigned long)(bh)->b_data & (page_size(bh->b_page) - 1); + return (unsigned long)(bh)->b_data & (folio_size(bh->b_folio) - 1); } /* If we *know* page->private refers to buffer_heads */ From 781bf9c7e307910a843d019251f174d22796a005 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:25 -0400 Subject: [PATCH 0292/2286] buffer: allow a buffer_head to point at memory outside the page cache jbd2 builds a temporary buffer_head to write out the frozen copy of a metadata block, and that copy lives in slab memory. Today jbd2 points the temporary buffer at the slab folio backing it. A slab folio's ->mapping is not an address_space, so anything that follows bh->b_folio->mapping there gets garbage rather than NULL; mark_buffer_write_io_error() does exactly that, and we are about to start calling it on this buffer. Rather than teach every such helper about slab folios, allow bh->b_folio to be NULL and let b_data point straight at the memory. Code that needs the folio has to check. There are two places in this file: - __bh_submit() adds the data by virtual address using bio_add_virt_nofail(), and skips the cgroup accounting: a buffer that is not in the page cache has no owning folio to attribute writeback to. - buffer_set_crypto_ctx() returns early. fscrypt has no interest in a buffer that is not part of a file mapping, which is why it already returns when the folio has no mapping. Nothing sets b_folio to NULL yet, so this patch is a no-op on its own. A buffer_head without a folio is a narrow thing, not a new general capability. Most of the buffer_head API assumes a folio and will fault or corrupt state without one - touch_buffer(), bh_offset(), the async read completion path, and plenty more - so it is up to whoever builds such a buffer to keep it away from all of that. What NULL buys us is that getting it wrong fails loudly instead of quietly following a slab folio's overloaded ->mapping. It is also only valid over memory that is always mapped: buffers over highmem have no permanent kernel virtual address, which is why folio_set_bh() records a folio and an offset instead. Suggested-by: Matthew Wilcox (Oracle) Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/bb6fab2111a48d7ba61887fc1372362576fce6e6.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index ed966fa73b1ba2..fa55a507835aa4 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1070,12 +1070,16 @@ EXPORT_SYMBOL(__bforget); static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh, gfp_t gfp_mask) { - const struct address_space *mapping = folio_mapping(bh->b_folio); + const struct address_space *mapping; /* * The ext4 journal (jbd2) can submit a buffer_head it directly created - * for a non-pagecache page. fscrypt doesn't care about these. + * for memory that is not in the page cache at all. fscrypt doesn't + * care about these. */ + if (!bh->b_folio) + return; + mapping = folio_mapping(bh->b_folio); if (!mapping) return; fscrypt_set_bio_crypt_ctx(bio, mapping->host, @@ -1107,7 +1111,8 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf, bio = bio_alloc(bh->b_bdev, 1, opf, GFP_NOIO); - if (folio_test_dropbehind(bh->b_folio) && op_is_write(opf)) + if (bh->b_folio && folio_test_dropbehind(bh->b_folio) && + op_is_write(opf)) bio_set_flag(bio, BIO_COMPLETE_IN_TASK); if (IS_ENABLED(CONFIG_FS_ENCRYPTION)) @@ -1116,7 +1121,11 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf, bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9); bio->bi_write_hint = write_hint; - bio_add_folio_nofail(bio, bh->b_folio, bh->b_size, bh_offset(bh)); + if (bh->b_folio) + bio_add_folio_nofail(bio, bh->b_folio, bh->b_size, + bh_offset(bh)); + else + bio_add_virt_nofail(bio, bh->b_data, bh->b_size); bio->bi_end_io = end_bio; bio->bi_private = bh; @@ -1126,7 +1135,8 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf, if (wbc) { wbc_init_bio(wbc, bio); - wbc_account_cgroup_owner(wbc, bh->b_folio, bh->b_size); + if (bh->b_folio) + wbc_account_cgroup_owner(wbc, bh->b_folio, bh->b_size); } blk_crypto_submit_bio(bio); From 3c9ab45852f6368a3c9d5c18451de7d1f893cc74 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:26 -0400 Subject: [PATCH 0293/2286] jbd2: point the shadow buffer at the frozen data directly When a metadata buffer has to be copied out before it can be journalled, jbd2_journal_write_metadata_buffer() writes jh->b_frozen_data rather than the page cache copy. b_frozen_data is kmalloc()ed, so folio_set_bh() makes the shadow buffer point at a slab folio. That is not something the buffer_head layer can reason about. A slab folio overloads ->mapping, so a shadow buffer looks like it belongs to an address_space when it does not. buffer_set_crypto_ctx() already has to work around this, and it is the reason mark_buffer_write_io_error() cannot be called on a shadow buffer today. Point the shadow buffer at the frozen data itself instead: leave b_folio NULL, which it already is out of alloc_buffer_head(), and set b_data. The previous patch taught fs/buffer.c to submit such a buffer. folio_set_bh() is now needed on only one path - the one that journals the page cache copy directly - so it moves there, and new_folio, new_offset and the flag that used to pick between them all go away. The two commit-path checksum helpers reach the shadow buffer's contents through a new kmap_local_bh()/kunmap_local_bh() pair, which handle a buffer with or without a folio. Memory outside the page cache is always mapped, so for those there is nothing to map or unmap. Mapping it anyway would be worse than pointless: with CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP, kmap_local_page() hands back a one page mapping even for such memory, which is not enough for a buffer bigger than a page. Tested with ext4 mounted data=journal,journal_checksum on a metadata_csum filesystem, writing files whose every block begins with the JBD2 magic so that escaping forces the copy-out, then crashing with sysrq-b without unmounting and replaying the journal on the next mount. Recovery completed, the file contents matched, e2fsck -fn was clean, and an instrumented build confirmed the b_folio == NULL path was taken. Suggested-by: Matthew Wilcox (Oracle) Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/6140cd23beb88e99f40eaeff4044a16213f6caab.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/commit.c | 8 ++++---- fs/jbd2/journal.c | 29 +++++++++++++++++------------ include/linux/buffer_head.h | 29 +++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index 3029cb6f6d640e..0c85af91f9b21f 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -330,9 +330,9 @@ static __u32 jbd2_checksum_data(__u32 crc32_sum, struct buffer_head *bh) char *addr; __u32 checksum; - addr = kmap_local_folio(bh->b_folio, bh_offset(bh)); + addr = kmap_local_bh(bh); checksum = crc32_be(crc32_sum, addr, bh->b_size); - kunmap_local(addr); + kunmap_local_bh(bh, addr); return checksum; } @@ -357,10 +357,10 @@ static void jbd2_block_tag_csum_set(journal_t *j, journal_block_tag_t *tag, return; seq = cpu_to_be32(sequence); - addr = kmap_local_folio(bh->b_folio, bh_offset(bh)); + addr = kmap_local_bh(bh); csum32 = jbd2_chksum(j->j_csum_seed, (__u8 *)&seq, sizeof(seq)); csum32 = jbd2_chksum(csum32, addr, bh->b_size); - kunmap_local(addr); + kunmap_local_bh(bh, addr); if (jbd2_has_feature_csum3(j)) tag3->t_checksum = cpu_to_be32(csum32); diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index 00f5a98f3d4fe6..a4f63d73833723 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -328,8 +328,6 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, { int do_escape = 0; struct buffer_head *new_bh; - struct folio *new_folio; - unsigned int new_offset; struct buffer_head *bh_in = jh2bh(jh_in); journal_t *journal = transaction->t_journal; @@ -349,24 +347,31 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, /* keep subsequent assertions sane */ atomic_set(&new_bh->b_count, 1); + /* + * b_frozen_data is slab memory, not page cache, so when we use it the + * shadow buffer gets no folio at all: b_folio stays NULL from the + * allocation and b_data points straight at the copy. Pointing it at + * the slab folio instead would hand its overloaded ->mapping to + * anything that goes looking for an address_space. + */ + spin_lock(&jh_in->b_state_lock); /* * If a new transaction has already done a buffer copy-out, then * we use that version of the data for the commit. */ if (jh_in->b_frozen_data) { - new_folio = virt_to_folio(jh_in->b_frozen_data); - new_offset = offset_in_folio(new_folio, jh_in->b_frozen_data); do_escape = jbd2_data_needs_escaping(jh_in->b_frozen_data); if (do_escape) jbd2_data_do_escape(jh_in->b_frozen_data); + new_bh->b_data = jh_in->b_frozen_data; } else { + struct folio *folio = bh_in->b_folio; + unsigned int offset = offset_in_folio(folio, bh_in->b_data); char *tmp; char *mapped_data; - new_folio = bh_in->b_folio; - new_offset = offset_in_folio(new_folio, bh_in->b_data); - mapped_data = kmap_local_folio(new_folio, new_offset); + mapped_data = kmap_local_folio(folio, offset); /* * Fire data frozen trigger if data already wasn't frozen. Do * this before checking for escaping, as the trigger may modify @@ -380,8 +385,10 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, /* * Do we need to do a data copy? */ - if (!do_escape) + if (!do_escape) { + folio_set_bh(new_bh, folio, offset); goto escape_done; + } spin_unlock(&jh_in->b_state_lock); tmp = kmalloc(bh_in->b_size, GFP_NOFS | __GFP_NOFAIL); @@ -392,7 +399,7 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, } jh_in->b_frozen_data = tmp; - memcpy_from_folio(tmp, new_folio, new_offset, bh_in->b_size); + memcpy_from_folio(tmp, folio, offset, bh_in->b_size); /* * This isn't strictly necessary, as we're using frozen * data for the escaping, but it keeps consistency with @@ -401,13 +408,11 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction, jh_in->b_frozen_triggers = jh_in->b_triggers; copy_done: - new_folio = virt_to_folio(jh_in->b_frozen_data); - new_offset = offset_in_folio(new_folio, jh_in->b_frozen_data); jbd2_data_do_escape(jh_in->b_frozen_data); + new_bh->b_data = jh_in->b_frozen_data; } escape_done: - folio_set_bh(new_bh, new_folio, new_offset); new_bh->b_size = bh_in->b_size; new_bh->b_bdev = journal->j_dev; new_bh->b_blocknr = blocknr; diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 699970b4bbf28d..20b8fca1abfaab 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -172,6 +172,35 @@ static inline unsigned long bh_offset(const struct buffer_head *bh) return (unsigned long)(bh)->b_data & (folio_size(bh->b_folio) - 1); } +/** + * kmap_local_bh - Map the data of a buffer. + * @bh: The buffer. + * + * Buffers usually live in the page cache, but a few are built over memory + * which is not. Those carry no folio and b_data is already a kernel address + * which is always mapped, so there is nothing to do for them. Pair with + * kunmap_local_bh(). + * + * Return: A pointer to the buffer's data. + */ +static inline void *kmap_local_bh(const struct buffer_head *bh) +{ + if (!bh->b_folio) + return bh->b_data; + return kmap_local_folio(bh->b_folio, bh_offset(bh)); +} + +/** + * kunmap_local_bh - Unmap the data of a buffer. + * @bh: The buffer. + * @addr: The address returned by kmap_local_bh(). + */ +static inline void kunmap_local_bh(const struct buffer_head *bh, void *addr) +{ + if (bh->b_folio) + kunmap_local(addr); +} + /* If we *know* page->private refers to buffer_heads */ #define page_buffers(page) \ ({ \ From 8775d14a90e29fafa8b556e07d8142042fd58689 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:27 -0400 Subject: [PATCH 0294/2286] buffer: read the folio's mapping directly in buffer_set_crypto_ctx() folio_mapping() was doing two jobs here. One was to turn a slab folio into NULL, which is what made this safe for jbd2's shadow buffers; the previous patch removed the need for that by giving those buffers no folio at all. The other is a hazard. folio_mapping() maps a folio in the swap cache to its swap_address_space, so if a buffer_head were ever attached to such a folio this would hand fscrypt a swap mapping and dereference ->host on it. There is no reason to want that here: this path wants the file's mapping or nothing. Read ->mapping directly. Buffers with no folio are already handled above. Suggested-by: Matthew Wilcox (Oracle) Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/3fe72ec37bf8491a69031db5f3ba1319da935b97.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/buffer.c b/fs/buffer.c index fa55a507835aa4..887151bbc5ae15 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1079,7 +1079,7 @@ static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh, */ if (!bh->b_folio) return; - mapping = folio_mapping(bh->b_folio); + mapping = bh->b_folio->mapping; if (!mapping) return; fscrypt_set_bio_crypt_ctx(bio, mapping->host, From d9a8f9ffce29dc8409c885996dc210a8f394e478 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:28 -0400 Subject: [PATCH 0295/2286] buffer: clear BH_Write_EIO when a buffer is forgotten BH_Write_EIO records that the last write of this buffer failed. It is cleared when the buffer is written again, but a filesystem freeing a metadata block never writes it again. It calls bforget() and hands the block back to the allocator, so the flag outlives the block it refers to. That does not matter much today, because the write error is also recorded by clearing BH_Uptodate and the buffer is discarded soon after. It starts to matter in the rest of this series, which stops clearing BH_Uptodate on write error and makes BH_Write_EIO the way a failed metadata write is reported. bforget() is where a filesystem says it no longer cares about this buffer's contents, so clear the error there alongside the dirty flag. Suggested-by: Jan Kara Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/ebe0b4f179ccdac7a9400fe2611623ce218c8d87.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/buffer.c b/fs/buffer.c index 887151bbc5ae15..f3bf87ccb536f7 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1062,6 +1062,7 @@ EXPORT_SYMBOL(__brelse); void __bforget(struct buffer_head *bh) { clear_buffer_dirty(bh); + clear_buffer_write_io_error(bh); remove_assoc_queue(bh); __brelse(bh); } From 7d439c46c9230b9ef9da8d164dcd8795b528e631 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:29 -0400 Subject: [PATCH 0296/2286] buffer: discard BH_Write_EIO along with the rest of the buffer state discard_buffer() strips the state that describes where a buffer lives and what has happened to it, because after an invalidate none of it applies any more. BH_Write_EIO belongs in that set for the same reason: it describes a write of the data that is being thrown away. Leaving it set means a buffer_head reused for a different block starts life carrying somebody else's write error. Like the bforget() change, this is mostly theoretical today and becomes load bearing once the rest of the series makes BH_Write_EIO the report of a failed metadata write. Suggested-by: Jan Kara Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/c6e9db48d8d0feb83d4ca29306f4bc1e58f1ee0f.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/buffer.c b/fs/buffer.c index f3bf87ccb536f7..9ff946a2487abf 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1493,7 +1493,7 @@ EXPORT_SYMBOL(folio_set_bh); /* Bits that are cleared during an invalidate */ #define BUFFER_FLAGS_DISCARD \ (1 << BH_Mapped | 1 << BH_New | 1 << BH_Req | \ - 1 << BH_Delay | 1 << BH_Unwritten) + 1 << BH_Delay | 1 << BH_Unwritten | 1 << BH_Write_EIO) static void discard_buffer(struct buffer_head * bh) { From bc411b171a89abc9b3a267599919a4dbc826e626 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:30 -0400 Subject: [PATCH 0297/2286] buffer: detect metadata write errors with buffer_write_io_error() Both places in this file that report a metadata write error to a caller do it by testing !buffer_uptodate() after waiting for the write. That works only because the write completion handlers clear BH_Uptodate when the write fails, which is what this series is removing: a buffer whose write failed still holds the correct data, and saying otherwise makes callers rewrite, re-read or WARN over a buffer that was never wrong. BH_Write_EIO is the flag that actually means "the last write of this buffer failed", and both handlers already set it via mark_buffer_write_io_error(). Test that instead. No behaviour change: today a failed write through bh_end_write() or bh_end_async_write() sets BH_Write_EIO and clears BH_Uptodate together, so the two tests agree. They stop agreeing at the end of the series, and this one stays right. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/2b309196b883cc8979800911a47668e225401b2c.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 9ff946a2487abf..6e8aa77e0f3800 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -589,7 +589,7 @@ int mmb_sync(struct mapping_metadata_bhs *mmb) } spin_unlock(&mmb->lock); wait_on_buffer(bh); - if (!buffer_uptodate(bh)) + if (buffer_write_io_error(bh)) err = -EIO; brelse(bh); spin_lock(&mmb->lock); @@ -2721,7 +2721,7 @@ int __sync_dirty_buffer(struct buffer_head *bh, blk_opf_t op_flags) bh_submit(bh, REQ_OP_WRITE | op_flags, bh_end_write); wait_on_buffer(bh); - if (!buffer_uptodate(bh)) + if (buffer_write_io_error(bh)) return -EIO; } else { unlock_buffer(bh); From 0b1576d602083aa4697df93c1d0ae126684ba2f6 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:31 -0400 Subject: [PATCH 0298/2286] adfs: check for a directory write error with buffer_write_io_error() adfs_dir_sync() spots a failed write by testing BH_Req together with !BH_Uptodate. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. BH_Write_EIO says exactly what this code wants to know, and it implies BH_Req, so the pair collapses into one test. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/05cf8ad911ad7a6eb68f42e38f1eea39c38e246b.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/adfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c index 11afa9e157aa70..b8cc6a697a05dd 100644 --- a/fs/adfs/dir.c +++ b/fs/adfs/dir.c @@ -191,7 +191,7 @@ static int adfs_dir_sync(struct adfs_dir *dir) for (i = dir->nr_buffers - 1; i >= 0; i--) { struct buffer_head *bh = dir->bhs[i]; sync_dirty_buffer(bh); - if (buffer_req(bh) && !buffer_uptodate(bh)) + if (buffer_write_io_error(bh)) err = -EIO; } From 5c49ba63bead84b0584a57095a703147cca6c811 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:32 -0400 Subject: [PATCH 0299/2286] ext2: check for an xattr block write error with buffer_write_io_error() ext2_xattr_set2() spots a failed synchronous write by testing BH_Req together with !BH_Uptodate. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. BH_Write_EIO says exactly what this code wants to know, and it implies BH_Req, so the pair collapses into one test. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/fcc530fffd0f1e18e8cd1a3974bc0ccc5b4c4b66.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/ext2/xattr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/ext2/xattr.c b/fs/ext2/xattr.c index 9b68c490ab26de..8f608930a48c64 100644 --- a/fs/ext2/xattr.c +++ b/fs/ext2/xattr.c @@ -769,7 +769,7 @@ ext2_xattr_set2(struct inode *inode, struct buffer_head *old_bh, if (IS_SYNC(inode)) { sync_dirty_buffer(new_bh); error = -EIO; - if (buffer_req(new_bh) && !buffer_uptodate(new_bh)) + if (buffer_write_io_error(new_bh)) goto cleanup; } } From 5089db0f2dd38a64921eb44e55eb78dfd0b242fc Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:33 -0400 Subject: [PATCH 0300/2286] omfs: check for an inode write error with buffer_write_io_error() __omfs_write_inode() spots a failed synchronous write, on both the primary block and each mirror, by testing BH_Req together with !BH_Uptodate. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. BH_Write_EIO says exactly what this code wants to know, and it implies BH_Req, so each pair collapses into one test. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/b33ccb12c29ae743ccfa48cdf1e9140fbf2dabcc.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/omfs/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/omfs/inode.c b/fs/omfs/inode.c index 1d915ef72119fb..bc37029a4afb1f 100644 --- a/fs/omfs/inode.c +++ b/fs/omfs/inode.c @@ -145,7 +145,7 @@ static int __omfs_write_inode(struct inode *inode, int wait) mark_buffer_dirty(bh); if (wait) { sync_dirty_buffer(bh); - if (buffer_req(bh) && !buffer_uptodate(bh)) + if (buffer_write_io_error(bh)) sync_failed = 1; } @@ -159,7 +159,7 @@ static int __omfs_write_inode(struct inode *inode, int wait) mark_buffer_dirty(bh2); if (wait) { sync_dirty_buffer(bh2); - if (buffer_req(bh2) && !buffer_uptodate(bh2)) + if (buffer_write_io_error(bh2)) sync_failed = 1; } brelse(bh2); From 0015b215eb6270d3e6bb5437b5198f5c38f4d2cd Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:34 -0400 Subject: [PATCH 0301/2286] exfat: check for a directory write error with buffer_write_io_error() exfat_update_bhs() waits for the writes it issued and then tests !buffer_uptodate() to find the ones that failed. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. Test BH_Write_EIO, which is what the completion handler sets and what this code actually wants to know. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/0784ef63a525434e7c0aff730eca7b43e043095d.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/exfat/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exfat/misc.c b/fs/exfat/misc.c index 6f11a96a4ffa84..dfd0bbf31c940a 100644 --- a/fs/exfat/misc.c +++ b/fs/exfat/misc.c @@ -187,7 +187,7 @@ int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) for (i = 0; i < nr_bhs && sync; i++) { wait_on_buffer(bhs[i]); - if (!err && !buffer_uptodate(bhs[i])) + if (!err && buffer_write_io_error(bhs[i])) err = -EIO; } return err; From 8417ab622ffc140960818420cf72d42ecd3a12fe Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:35 -0400 Subject: [PATCH 0302/2286] fat: check for a metadata write error with buffer_write_io_error() fat_sync_bhs() waits for the writes it issued and then tests !buffer_uptodate() to find the ones that failed. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. Test BH_Write_EIO, which is what the completion handler sets and what this code actually wants to know. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/4b6019b5a48b83c8235918b084248983a57692e1.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/fat/misc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/fat/misc.c b/fs/fat/misc.c index e79762cf19754d..0d04228f916e1e 100644 --- a/fs/fat/misc.c +++ b/fs/fat/misc.c @@ -360,7 +360,7 @@ int fat_sync_bhs(struct buffer_head **bhs, int nr_bhs) for (i = 0; i < nr_bhs; i++) { wait_on_buffer(bhs[i]); - if (!err && !buffer_uptodate(bhs[i])) + if (!err && buffer_write_io_error(bhs[i])) err = -EIO; } return err; From 4ed32fcd59ceb3dd9eeecf608ee612107030b5e3 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:36 -0400 Subject: [PATCH 0303/2286] ext4: check for a metadata write error with buffer_write_io_error() Two places detect a failed metadata write by testing !buffer_uptodate() after waiting for it. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. ext4 already does this correctly for the superblock - see ext4_commit_super(), which tests buffer_write_io_error() - so this brings the other two into line. In __ext4_handle_dirty_metadata() the old test also required BH_Req. BH_Write_EIO implies it, so the pair collapses into one test. The new test is also strictly stronger than consuming sync_dirty_buffer()'s return value, because it still fires when the buffer was written by background writeback and that write hit an error, which sync_dirty_buffer() does not report. Note that the failing write does not clear BH_Write_EIO, so an unrepaired itable block now reports on every subsequent sync of that inode rather than only on the write that failed. That is the intended behaviour, and matches what ocfs2 has always done with this flag. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/568e57d184da17041872fd4b498f31dd6259a4c1.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/ext4/ext4_jbd2.c | 2 +- fs/ext4/mmp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/ext4/ext4_jbd2.c b/fs/ext4/ext4_jbd2.c index 53ddedb52a6f73..c241f50b97bc0a 100644 --- a/fs/ext4/ext4_jbd2.c +++ b/fs/ext4/ext4_jbd2.c @@ -421,7 +421,7 @@ int __ext4_handle_dirty_metadata(const char *where, unsigned int line, } if (inode && inode_needs_sync(inode)) { sync_dirty_buffer(bh); - if (buffer_req(bh) && !buffer_uptodate(bh)) { + if (buffer_write_io_error(bh)) { ext4_error_inode_err(inode, where, line, bh->b_blocknr, EIO, "IO error syncing itable block"); diff --git a/fs/ext4/mmp.c b/fs/ext4/mmp.c index 7ce361484b3821..4b18ddef468d71 100644 --- a/fs/ext4/mmp.c +++ b/fs/ext4/mmp.c @@ -49,7 +49,7 @@ static int write_mmp_block_thawed(struct super_block *sb, bh_submit(bh, REQ_OP_WRITE | REQ_SYNC | REQ_META | REQ_PRIO, bh_end_write); wait_on_buffer(bh); - if (unlikely(!buffer_uptodate(bh))) + if (unlikely(buffer_write_io_error(bh))) return -EIO; return 0; } From a186ae5716fab861104e746930c34812530ac8bd Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:37 -0400 Subject: [PATCH 0304/2286] ocfs2: check for a metadata write error with buffer_write_io_error() ocfs2_write_block() and ocfs2_write_super_or_backup() detect a failed write by looking at BH_Uptodate afterwards. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. Test BH_Write_EIO instead. Note that ocfs2_write_block()'s test is the positive one, so the sense has to be inverted rather than the flag simply swapped. The comment in ocfs2_write_block()'s error arm needs updating for the same reason. It said the clustered uptodate information did not have to be removed because the buffer was not marked locally uptodate; after this series it is, so the reason no longer holds. Not advertising the block to the cluster is still the right thing to do - the data is in memory but not on disk - so only the justification changes, not the behaviour. No behaviour change today - a failed write sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/05cd3c34f2dc69b542db7baaef058cd75005d4cb.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/ocfs2/buffer_head_io.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/fs/ocfs2/buffer_head_io.c b/fs/ocfs2/buffer_head_io.c index 7bfe377af2dfcf..733ceda79ca1f1 100644 --- a/fs/ocfs2/buffer_head_io.c +++ b/fs/ocfs2/buffer_head_io.c @@ -66,12 +66,14 @@ int ocfs2_write_block(struct ocfs2_super *osb, struct buffer_head *bh, wait_on_buffer(bh); - if (buffer_uptodate(bh)) { + if (!buffer_write_io_error(bh)) { ocfs2_set_buffer_uptodate(ci, bh); } else { - /* We don't need to remove the clustered uptodate - * information for this bh as it's not marked locally - * uptodate. */ + /* + * The buffer still holds what we tried to write, but it did + * not reach the disk, so don't advertise it to the cluster + * as up to date. + */ ret = -EIO; mlog_errno(ret); } @@ -446,7 +448,7 @@ int ocfs2_write_super_or_backup(struct ocfs2_super *osb, wait_on_buffer(bh); - if (!buffer_uptodate(bh)) { + if (buffer_write_io_error(bh)) { ret = -EIO; mlog_errno(ret); } From 8bf5c5a94a8707f879706fd42a8bf5604955d12c Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:38 -0400 Subject: [PATCH 0305/2286] ocfs2: check for a stale write error before reusing a metadata buffer __ocfs2_journal_access() refuses to journal a buffer whose previous write failed, and turns the filesystem read-only rather than risk metadata inconsistency. That check sits inside an if (!buffer_uptodate(bh)) block, because until now a failed write also cleared BH_Uptodate. This series stops clearing BH_Uptodate on write error, so that outer test would never fire again and ocfs2 would silently start reusing buffers whose last write failed. Hoist the check out of the debug block, where it does not depend on BH_Uptodate any more, and drop the now dead second half of its condition. The mlog() pair keeps its own !buffer_uptodate() guard: it is a separate "we can safely remove this assertion after testing" debug aid about being handed a buffer with no valid contents, which is a different question from whether the last write of that buffer failed. The unlocked test followed by a locked retest is deliberate. BH_Write_EIO is cleared under the buffer lock, so taking the lock and looking a second time avoids turning the filesystem read-only over an error that a concurrent rewrite has already cleared, while keeping the common case lock-free. The code in this patch is Jan's, from the review discussion linked in the cover letter. Suggested-by: Jan Kara Acked-by: Weidong Zhu Signed-off-by: Chao Shi Reviewed-by: Jan Kara Link: https://patch.msgid.link/ad75c40927060cafe5a8ac45ae71c02ae4dfba04.1785951556.git.coshi036@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/ocfs2/journal.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/fs/ocfs2/journal.c b/fs/ocfs2/journal.c index d8afbc1a76bb8a..ea6802d894c2b2 100644 --- a/fs/ocfs2/journal.c +++ b/fs/ocfs2/journal.c @@ -676,19 +676,20 @@ static int __ocfs2_journal_access(handle_t *handle, mlog(ML_ERROR, "giving me a buffer that's not uptodate!\n"); mlog(ML_ERROR, "b_blocknr=%llu, b_state=0x%lx\n", (unsigned long long)bh->b_blocknr, bh->b_state); - + } + /* + * A previous transaction with a couple of buffer heads fail + * to checkpoint, so all the bhs are marked as BH_Write_EIO. + * For current transaction, the bh is just among those error + * bhs which previous transaction handle. We can't just clear + * its BH_Write_EIO and reuse directly, since other bhs are + * not written to disk yet and that will cause metadata + * inconsistency. So we should set fs read-only to avoid + * further damage. + */ + if (buffer_write_io_error(bh)) { lock_buffer(bh); - /* - * A previous transaction with a couple of buffer heads fail - * to checkpoint, so all the bhs are marked as BH_Write_EIO. - * For current transaction, the bh is just among those error - * bhs which previous transaction handle. We can't just clear - * its BH_Write_EIO and reuse directly, since other bhs are - * not written to disk yet and that will cause metadata - * inconsistency. So we should set fs read-only to avoid - * further damage. - */ - if (buffer_write_io_error(bh) && !buffer_uptodate(bh)) { + if (buffer_write_io_error(bh)) { unlock_buffer(bh); return ocfs2_error(osb->sb, "A previous attempt to " "write this buffer head failed\n"); From 9ba9d0441569749391bea9938c91870cd1e68601 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:39 -0400 Subject: [PATCH 0306/2286] gfs2: check for a metadata write error with buffer_write_io_error() gfs2_ail1_start_one() and gfs2_ail1_empty_one() decide whether a buffer on the ail reached the disk by looking at BH_Uptodate once it is no longer busy. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers re-read it. Test BH_Write_EIO instead. In gfs2_ail1_start_one() the test is the positive one, so the sense has to be inverted rather than the flag simply swapped. This is not a pure conversion for gfs2, because gfs2 already has a private write completion handler that behaves the way this series is heading: gfs2_end_log_write_bh() calls mark_buffer_write_io_error() and leaves BH_Uptodate alone. Buffers completed through it are therefore invisible to both tests today, and start being caught once they look at BH_Write_EIO. That is a real behaviour change, and it is the one gfs2 wanted: a failed log write now withdraws the filesystem instead of passing silently. gfs2_pin() is a different case and gets a different treatment. Its !buffer_uptodate() test is not only a proxy for a failed write - a buffer with no valid contents at all is equally a reason to withdraw before pinning it into a transaction - so the write error test is added to it rather than replacing it. Left alone deliberately: the BUG_ON(!buffer_uptodate(bh)) in gfs2_unpin() and the two WARN_ON()s in fs/gfs2/rgrp.c. After this series they simply stop firing for write errors, which is correct; turning them into BUG_ON(buffer_write_io_error(bh)) would newly panic on an I/O error. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/a09b6d08d31d24f69b833e4d855ed88875e87780.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/gfs2/log.c | 4 ++-- fs/gfs2/lops.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 78bba8cc10b8fd..e3e0dcb1f56733 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -107,7 +107,7 @@ __acquires(&sdp->sd_ail_lock) gfs2_assert(sdp, bd->bd_tr == tr); if (!buffer_busy(bh)) { - if (buffer_uptodate(bh)) { + if (!buffer_write_io_error(bh)) { list_move(&bd->bd_ail_st_list, &tr->tr_ail2_list); continue; @@ -321,7 +321,7 @@ static int gfs2_ail1_empty_one(struct gfs2_sbd *sdp, struct gfs2_trans *tr, active_count++; continue; } - if (!buffer_uptodate(bh) && + if (buffer_write_io_error(bh) && !cmpxchg(&sdp->sd_log_error, 0, -EIO)) gfs2_io_error_bh(sdp, bh); /* diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index 6dabe73ad790d9..3df6e4b7e8b9eb 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -48,7 +48,7 @@ void gfs2_pin(struct gfs2_sbd *sdp, struct buffer_head *bh) clear_buffer_dirty(bh); if (test_set_buffer_pinned(bh)) gfs2_assert_withdraw(sdp, 0); - if (!buffer_uptodate(bh)) + if (!buffer_uptodate(bh) || buffer_write_io_error(bh)) gfs2_io_error_bh(sdp, bh); bd = bh->b_private; /* If this buffer is in the AIL and it has already been written From a05d5c40af1bbf32a3fe78a1e25defcf0503f7bf Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:40 -0400 Subject: [PATCH 0307/2286] jbd2: report journal write errors with BH_Write_EIO The journal's own write completion handler, journal_end_buffer_io_sync(), reports a failed write by clearing BH_Uptodate, and the three places that wait for journal writes look for that. This series is removing that convention: a buffer whose write failed still holds the data that was supposed to reach the disk, and saying it is not up to date makes callers rewrite, re-read or WARN over data that was never wrong. Set BH_Write_EIO instead, with mark_buffer_write_io_error(), and test it in journal_wait_on_commit_record() and in the two commit-phase waits. The handler stops touching BH_Uptodate in either direction. Setting it on success was never needed: every caller marks the buffer up to date before submitting the write, because a buffer with no valid data is not something you can write out. The local flag is renamed to match what it now means. The two changes have to go together, because commit phase 4 waits on a mixed list: descriptor blocks are submitted with journal_end_buffer_io_sync(), while revoke blocks go through write_dirty_buffer() and land in bh_end_write(). bh_end_write() already sets BH_Write_EIO, so converting the consumer alone would keep working for revoke blocks and silently stop detecting failed descriptor writes. With the handler converted, both halves of the list report the same way. mark_buffer_write_io_error() is safe on all of these buffers. The shadow buffers from jbd2_journal_write_metadata_buffer() have no folio and no associated mapping, so it does nothing beyond setting the flag. Descriptor and commit blocks are ordinary buffers on the journal device, and marking the journal's mapping with the error is what write_dirty_buffer() already does for revoke blocks on the same device. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/c700983eea955429d927791fe5fb9ffe6cf11056.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/commit.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index 0c85af91f9b21f..cd7ef783bd36a2 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -32,14 +32,12 @@ static void journal_end_buffer_io_sync(struct bio *bio) { struct buffer_head *bh; - bool uptodate = bio_endio_bh(bio, &bh); + bool success = bio_endio_bh(bio, &bh); struct buffer_head *orig_bh = bh->b_private; BUFFER_TRACE(bh, ""); - if (uptodate) - set_buffer_uptodate(bh); - else - clear_buffer_uptodate(bh); + if (!success) + mark_buffer_write_io_error(bh); if (orig_bh) { clear_and_wake_up_bit(BH_Shadow, &orig_bh->b_state); } @@ -169,7 +167,7 @@ static int journal_wait_on_commit_record(journal_t *journal, clear_buffer_dirty(bh); wait_on_buffer(bh); - if (unlikely(!buffer_uptodate(bh))) + if (unlikely(buffer_write_io_error(bh))) ret = -EIO; put_bh(bh); /* One for getblk() */ @@ -834,7 +832,7 @@ void jbd2_journal_commit_transaction(journal_t *journal) wait_on_buffer(bh); cond_resched(); - if (unlikely(!buffer_uptodate(bh))) + if (unlikely(buffer_write_io_error(bh))) err = -EIO; jbd2_unfile_log_bh(bh); stats.run.rs_blocks_logged++; @@ -877,7 +875,7 @@ void jbd2_journal_commit_transaction(journal_t *journal) wait_on_buffer(bh); cond_resched(); - if (unlikely(!buffer_uptodate(bh))) + if (unlikely(buffer_write_io_error(bh))) err = -EIO; BUFFER_TRACE(bh, "ph5: control buffer writeout done: unfile"); From b1c88ae0b554322fa29b4b490ce0b09c2c84ec52 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:41 -0400 Subject: [PATCH 0308/2286] jbd2: say what jbd2_freeze_jh_data()'s assertion is actually checking The assertion that the buffer about to be copied out is up to date is correct and stays, but its message - "Possible IO failure" - describes what a buffer that is not up to date used to mean rather than what is being checked. Once this series stops clearing BH_Uptodate on write error, that reading is wrong twice over. A failed write no longer makes a buffer not up to date, and a buffer that does carry BH_Write_EIO is fine here: it still holds valid data and the journal will write it again. What the assertion is really guarding is that there is something valid to copy at all. Say that instead. Suggested-by: Jan Kara Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/713ffb9c0cdfe55499b3cdd2c06a59a2c152c46c.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/transaction.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/jbd2/transaction.c b/fs/jbd2/transaction.c index 5cc7d097b2ac8e..85d84d909f785c 100644 --- a/fs/jbd2/transaction.c +++ b/fs/jbd2/transaction.c @@ -920,7 +920,7 @@ static void jbd2_freeze_jh_data(struct journal_head *jh) char *source; struct buffer_head *bh = jh2bh(jh); - J_EXPECT_JH(jh, buffer_uptodate(bh), "Possible IO failure.\n"); + J_EXPECT_JH(jh, buffer_uptodate(bh), "Buffer not uptodate!\n"); source = kmap_local_folio(bh->b_folio, bh_offset(bh)); /* Fire data frozen trigger just before we copy the data */ jbd2_buffer_frozen_trigger(jh, source, jh->b_triggers); From 8ed37e18b915a36bf382873833656c181d1fb942 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:42 -0400 Subject: [PATCH 0309/2286] jbd2: check for a fast commit write error with buffer_write_io_error() jbd2_fc_wait_bufs() decides whether ext4's fast commit blocks reached the disk by looking at BH_Uptodate once the write has completed. That relies on the write completion handler clearing BH_Uptodate on error, which this series removes: a buffer whose write failed still holds the data the filesystem asked to be written, so declaring it not up to date is wrong and makes callers rewrite or re-read it. Test BH_Write_EIO instead. Nothing is needed on the producer side. These buffers used to be submitted by ext4_fc_submit_bh() with a private completion handler that cleared BH_Uptodate, but commit 7f0485dd3017 ("ext4: remove ext4_end_buffer_io_sync()") dropped it in favour of bh_end_write(), which already reports a failed write with mark_buffer_write_io_error(). The wait in jbd2 is the only half left to convert. A stale flag from an earlier write cannot fool the new test. jbd2_fc_get_buf() looks the buffer up with __getblk() at a fixed block on the journal device, so the same buffer comes back commit after commit, but __bh_submit() clears BH_Write_EIO when it resubmits a buffer for writing. The flag jbd2_fc_wait_bufs() sees always belongs to the write it just waited for. No behaviour change today - bh_end_write() sets BH_Write_EIO and clears BH_Uptodate together. It stops being a no-op at the end of the series, where the new test is the one that still works. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/970d8b9603d4620ab73038f49bc36831e37e00a0.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/jbd2/journal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/jbd2/journal.c b/fs/jbd2/journal.c index a4f63d73833723..cda1ff8851dcc6 100644 --- a/fs/jbd2/journal.c +++ b/fs/jbd2/journal.c @@ -887,7 +887,7 @@ int jbd2_fc_wait_bufs(journal_t *journal, int num_blks) * Update j_fc_off so jbd2_fc_release_bufs can release remain * buffer head. */ - if (unlikely(!buffer_uptodate(bh))) { + if (unlikely(buffer_write_io_error(bh))) { journal->j_fc_off = i + 1; return -EIO; } From 9f08e58149dca8453bf99e0af35cbb227ded17b1 Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:43 -0400 Subject: [PATCH 0310/2286] buffer: stop touching BH_Uptodate on write completion A buffer whose write failed still holds exactly the data the filesystem asked to be written. It is the disk that is out of date, not the buffer. Clearing BH_Uptodate says the opposite, and callers act on it: - mark_buffer_dirty() has a WARN_ON_ONCE(!buffer_uptodate(bh)). A filesystem that dirties the buffer again after a failed write - which is the normal way to retry - trips it. That is the warning this series started from. - a buffer that is not up to date gets re-read from disk, which replaces the data the filesystem was trying to write with the stale on-disk copy, silently. - the window between the write completing and the buffer being marked not up to date is visible to anyone holding the folio lock, so the state is not even self consistent while it lasts. BH_Write_EIO already records the failure, and by now every place in the tree that needs to know about it tests that flag instead: the two core helpers in this file, adfs, exfat, ext2, ext4, fat, gfs2, jbd2, ocfs2 and omfs, converted one filesystem at a time in the preceding patches. The private completion handler in jbd2 was converted along with its waiters, and ext4 fast commit needed only its waiter, because commit 7f0485dd3017 ("ext4: remove ext4_end_buffer_io_sync()") had already dropped its handler in favour of bh_end_write(). Nothing is left that reads BH_Uptodate to find out whether a write failed. Setting BH_Uptodate on success goes too. A buffer has to be up to date before it can be written - you cannot write out data you do not have - so the only thing that assignment could do is paper over a caller that got that wrong. Write completion now leaves BH_Uptodate alone in both directions. What this changes for readers. A buffer whose write failed stays up to date, so the read paths stop replacing it with the on-disk copy: __bread_gfp() no longer sends it to __bread_slow(), and bh_uptodate_or_lock() reports it as usable. That is the intent. ocfs2 changes the most, because ocfs2_read_blocks() decides whether to go to disk on its own cluster uptodate cache and only tests BH_Uptodate after the wait, so a block whose write failed makes that read return -EIO today and from here it succeeds and hands back the in-memory data. A caller that needs to know the write failed asks BH_Write_EIO. Found by FuzzNvme. Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/61d7d5737f5773f53ee543f375fcde81aa8d28c2.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 6e8aa77e0f3800..db17841d62ba35 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -202,12 +202,9 @@ void bh_end_write(struct bio *bio) struct buffer_head *bh; bool success = bio_endio_bh(bio, &bh); - if (success) { - set_buffer_uptodate(bh); - } else { + if (!success) { buffer_io_error(bh, ", lost sync page write"); mark_buffer_write_io_error(bh); - clear_buffer_uptodate(bh); } unlock_buffer(bh); } @@ -407,12 +404,9 @@ void bh_end_async_write(struct bio *bio) BUG_ON(!buffer_async_write(bh)); folio = bh->b_folio; - if (success) { - set_buffer_uptodate(bh); - } else { + if (!success) { buffer_io_error(bh, ", lost async page write"); mark_buffer_write_io_error(bh); - clear_buffer_uptodate(bh); } first = folio_buffers(folio); From c7ea7fb6095b758608fd2aaf63f24b62175dc93b Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:21 +0100 Subject: [PATCH 0311/2286] buffer: Remove reference to unmap_buffer() unmap_buffer() was removed 25 years ago in v2.4.10.0.1. We don't need to mention it in a comment any more. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-2-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index a43f3c9ecaa0cd..b5d40693371202 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1615,9 +1615,7 @@ EXPORT_SYMBOL(create_empty_buffers); * moment when something will explicitly mark the buffer dirty (hopefully that * will not happen until we will free that block ;-) We don't even need to mark * it not-uptodate - nobody can expect anything from a newly allocated buffer - * anyway. We used to use unmap_buffer() for such invalidation, but that was - * wrong. We definitely don't want to mark the alias unmapped, for example - it - * would confuse anyone who might pick it with bread() afterwards... + * anyway. * * Also.. Note that bforget() doesn't lock the buffer. So there can be * writeout I/O going on against recently-freed buffers. We don't wait on that From 336e794c59173407d22cfc1f254c517c264ef9ed Mon Sep 17 00:00:00 2001 From: Chao Shi Date: Thu, 6 Aug 2026 12:58:44 -0400 Subject: [PATCH 0312/2286] buffer: clear BH_Write_EIO when a write succeeds, not when one starts BH_Write_EIO is cleared in __bh_submit(), when a buffer that has been written before is submitted for write again. That is early: it says the error is gone at the moment we start trying to fix it, rather than when we have. It also loses errors. A task whose write fails sets the flag and then goes to look at it; if another task redirties the buffer and resubmits it in between, the submission clears the flag and the first task sees no error at all. Neither of them is doing anything wrong. Clear it on successful write completion instead, in the end io handlers - the same three the rest of this series has been converting, plus gfs2's, which already marked errors this way. Then the flag means what it says: the last write of this buffer that finished, failed. A resubmission no longer hides an error that has not been fixed yet, and one that has been fixed clears the flag when the data reaches the disk. __bh_submit() keeps setting BH_Req, which is what the rest of the tree reads it for. Suggested-by: Jan Kara Acked-by: Weidong Zhu Signed-off-by: Chao Shi Link: https://patch.msgid.link/1c976fd191aa6e99dbe65d6a1ec63f8706cc0dfa.1785951556.git.coshi036@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 15 +++++++-------- fs/gfs2/lops.c | 2 ++ fs/jbd2/commit.c | 4 +++- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index db17841d62ba35..a43f3c9ecaa0cd 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -202,7 +202,9 @@ void bh_end_write(struct bio *bio) struct buffer_head *bh; bool success = bio_endio_bh(bio, &bh); - if (!success) { + if (success) { + clear_buffer_write_io_error(bh); + } else { buffer_io_error(bh, ", lost sync page write"); mark_buffer_write_io_error(bh); } @@ -404,7 +406,9 @@ void bh_end_async_write(struct bio *bio) BUG_ON(!buffer_async_write(bh)); folio = bh->b_folio; - if (!success) { + if (success) { + clear_buffer_write_io_error(bh); + } else { buffer_io_error(bh, ", lost async page write"); mark_buffer_write_io_error(bh); } @@ -1085,7 +1089,6 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf, enum rw_hint write_hint, struct writeback_control *wbc, bio_end_io_t end_bio) { - const enum req_op op = opf & REQ_OP_MASK; struct bio *bio; BUG_ON(!buffer_locked(bh)); @@ -1093,11 +1096,7 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf, BUG_ON(buffer_delay(bh)); BUG_ON(buffer_unwritten(bh)); - /* - * Only clear out a write error when rewriting - */ - if (test_set_buffer_req(bh) && (op == REQ_OP_WRITE)) - clear_buffer_write_io_error(bh); + set_buffer_req(bh); if (buffer_meta(bh)) opf |= REQ_META; diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index 3df6e4b7e8b9eb..7440e5b72f8adb 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -179,6 +179,8 @@ static void gfs2_end_log_write_bh(struct gfs2_sbd *sdp, struct folio *folio, do { if (error) mark_buffer_write_io_error(bh); + else + clear_buffer_write_io_error(bh); unlock_buffer(bh); next = bh->b_this_page; size -= bh->b_size; diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index cd7ef783bd36a2..ebf6ba58ff4d0c 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -36,7 +36,9 @@ static void journal_end_buffer_io_sync(struct bio *bio) struct buffer_head *orig_bh = bh->b_private; BUFFER_TRACE(bh, ""); - if (!success) + if (success) + clear_buffer_write_io_error(bh); + else mark_buffer_write_io_error(bh); if (orig_bh) { clear_and_wake_up_bit(BH_Shadow, &orig_bh->b_state); From 3732e5a9875f04aba06fd735eb5dcffea4de7947 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:22 +0100 Subject: [PATCH 0313/2286] fs: Remove references to mark_buffer_dirty_inode() Complete the removal of mark_buffer_dirty_inode() by changing all references to it in the documentation to refer to mmb_mark_buffer_dirty() instead. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-3-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/porting.rst | 2 +- fs/buffer.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index 60880eb0c49d1a..de96b6745600b4 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -348,7 +348,7 @@ simply of return 1. Note that all actual eviction work is done by caller after As before, clear_inode() must be called exactly once on each call of ->evict_inode() (as it used to be for each call of ->delete_inode()). Unlike before, if you are using inode-associated metadata buffers (i.e. -mark_buffer_dirty_inode()), it's your responsibility to call +mmb_mark_buffer_dirty()), it's your responsibility to call invalidate_inode_buffers() before clear_inode(). NOTE: checking i_nlink in the beginning of ->write_inode() and bailing out diff --git a/fs/buffer.c b/fs/buffer.c index b5d40693371202..8dfef0330c4dda 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -518,8 +518,8 @@ EXPORT_SYMBOL_GPL(mmb_has_buffers); * * Do this in two main stages: first we copy dirty buffers to a * temporary inode list, queueing the writes as we go. Then we clean - * up, waiting for those writes to complete. mark_buffer_dirty_inode() - * doesn't touch b_assoc_buffers list if b_mmb is not NULL so we are sure the + * up, waiting for those writes to complete. mmb_mark_buffer_dirty() + * doesn't touch b_assoc_buffers list if b_mmb is set so we are sure the * buffer stays on our list until IO completes (at which point it can be * reaped). */ @@ -540,7 +540,7 @@ int mmb_sync(struct mapping_metadata_bhs *mmb) bh = BH_ENTRY(mmb->list.next); WARN_ON_ONCE(bh->b_mmb != mmb); __remove_assoc_queue(mmb, bh); - /* Avoid race with mark_buffer_dirty_inode() which does + /* Avoid race with mmb_mark_buffer_dirty() which does * a lockless check and we rely on seeing the dirty bit */ smp_mb(); if (buffer_dirty(bh) || buffer_locked(bh)) { @@ -578,7 +578,7 @@ int mmb_sync(struct mapping_metadata_bhs *mmb) bh = BH_ENTRY(tmp.prev); get_bh(bh); __remove_assoc_queue(mmb, bh); - /* Avoid race with mark_buffer_dirty_inode() which does + /* Avoid race with mmb_mark_buffer_dirty() which does * a lockless check and we rely on seeing the dirty bit */ smp_mb(); if (buffer_dirty(bh)) { From f0a2efd6b1abcd365605e676cc8e98077dbea894 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:23 +0100 Subject: [PATCH 0314/2286] buffer: Document sb_bread(), sb_bread_unmovable() and sb_breadahead() Add kernel-doc for these three functions. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-4-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- include/linux/buffer_head.h | 50 ++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 20b8fca1abfaab..e7a701ce029d1c 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -364,20 +364,58 @@ static inline void bforget(struct buffer_head *bh) __bforget(bh); } -static inline struct buffer_head * -sb_bread(struct super_block *sb, sector_t block) +/** + * sb_bread - Read a block. + * @sb: The superblock to read from. + * @block: Block number in units of block size. + * + * Read a specified block, and return the buffer head that refers + * to it. The memory is allocated from the movable area so that it can + * be migrated. The returned buffer head has its refcount increased. + * The caller should call brelse() when it has finished with the buffer. + * + * Context: May sleep waiting for I/O. + * Return: NULL if the block was unreadable. + */ +static inline +struct buffer_head *sb_bread(struct super_block *sb, sector_t block) { return __bread_gfp(sb->s_bdev, block, sb->s_blocksize, __GFP_MOVABLE); } -static inline struct buffer_head * -sb_bread_unmovable(struct super_block *sb, sector_t block) +/** + * sb_bread_unmovable - Read a block. + * @sb: The superblock to read from. + * @block: Block number in units of block size. + * + * Read a specified block, and return the buffer head that refers to it. + * The memory is allocated from the unmovable area so that pointers into + * it remain valid after compaction runs. The returned buffer head has + * its refcount increased. The caller should call brelse() when it has + * finished with the buffer. + * + * Context: May sleep waiting for I/O. + * Return: NULL if the block was unreadable. + */ +static inline +struct buffer_head *sb_bread_unmovable(struct super_block *sb, sector_t block) { return __bread_gfp(sb->s_bdev, block, sb->s_blocksize, 0); } -static inline void -sb_breadahead(struct super_block *sb, sector_t block) +/** + * sb_breadahead - Start readahead. + * @sb: Superblock identifying the block device. + * @block: The block to read. + * + * Read this block. The I/O will be flagged as being readahead rather + * than immediate read, but (unlike the page cache), surrounding blocks + * will not be read. + * + * Context: May sleep in order to allocate memory. + */ +static inline +void sb_breadahead(struct super_block *sb, sector_t block) { __breadahead(sb->s_bdev, block, sb->s_blocksize); } From 6c8b7651d06f635fe3d8789eda493eda3b4d13ef Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:24 +0100 Subject: [PATCH 0315/2286] buffer: Document mmb_mark_buffer_dirty() Add kernel-doc for mmb_mark_buffer_dirty(). Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-5-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/buffer.c b/fs/buffer.c index 8dfef0330c4dda..625e36cafd304c 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -616,6 +616,14 @@ void write_boundary_block(struct block_device *bdev, } } +/** + * mmb_mark_buffer_dirty - Mark a metadata buffer dirty. + * @bh: The buffer to mark dirty. + * @mmb: The list of buffers to add the buffer to. + * + * Mark the buffer dirty and add it to the list if it is not already on + * a list. + */ void mmb_mark_buffer_dirty(struct buffer_head *bh, struct mapping_metadata_bhs *mmb) { From c4c8cbb92ee02f1105ce0a1ec0cba0bdc75e757e Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:25 +0100 Subject: [PATCH 0316/2286] buffer: Refer to folios in comments Update some comments which mention pages to refer to folios instead. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-6-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 67 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 625e36cafd304c..f1c0ae0c7d19d6 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -264,7 +264,7 @@ __find_get_block_slow(struct block_device *bdev, sector_t block, bool atomic) bh = bh->b_this_page; } while (bh != head); - /* we might be here because some of the buffers on this page are + /* we might be here because some of the buffers on this folio are * not mapped. This is due to various races between * file io on the block device and getblk. It gets dealt with * elsewhere, don't buffer_error if we had some unmapped buffers @@ -310,7 +310,7 @@ static void end_buffer_async_read(struct buffer_head *bh, int uptodate) /* * Be _very_ careful from here on. Bad things can happen if * two buffer heads end IO at almost the same time and both - * decide that the page is now completely done. + * decide that the folio is now completely done. */ first = folio_buffers(folio); spin_lock_irqsave(&first->b_uptodate_lock, flags); @@ -692,7 +692,7 @@ bool block_dirty_folio(struct address_space *mapping, struct folio *folio) } while (bh != head); } /* - * Lock out page's memcg migration to keep PageDirty + * Lock out folio's memcg migration to keep folio dirty flag * synchronized with per-memcg dirty page counters. */ newly_dirty = !folio_test_set_dirty(folio); @@ -958,23 +958,23 @@ __getblk_slow(struct block_device *bdev, sector_t block, } /* - * The relationship between dirty buffers and dirty pages: + * The relationship between dirty buffers and dirty folios: * - * Whenever a page has any dirty buffers, the page's dirty bit is set, and - * the page is tagged dirty in the page cache. + * Whenever a folio has any dirty buffers, the folio's dirty flag is set, and + * the folio is tagged dirty in the page cache. * * At all times, the dirtiness of the buffers represents the dirtiness of - * subsections of the page. If the page has buffers, the page dirty bit is + * subsections of the folio. If the folio has buffers, the folio dirty flag is * merely a hint about the true dirty state. * - * When a page is set dirty in its entirety, all its buffers are marked dirty - * (if the page has buffers). + * When a folio is set dirty in its entirety, all its buffers are marked dirty + * (if the folio has buffers). * - * When a buffer is marked dirty, its page is dirtied, but the page's other + * When a buffer is marked dirty, its folio is dirtied, but the folio's other * buffers are not. * * Also. When blockdev buffers are explicitly read with bread(), they - * individually become uptodate. But their backing page remains not + * individually become uptodate. But their backing folio remains not * uptodate - even if all of its buffers are uptodate. A subsequent * block_read_full_folio() against that folio will discover all the uptodate * buffers, will set the folio uptodate and will perform no I/O. @@ -985,7 +985,7 @@ __getblk_slow(struct block_device *bdev, sector_t block, * @bh: the buffer_head to mark dirty * * mark_buffer_dirty() will set the dirty bit against the buffer, then set - * its backing page dirty, then tag the page as dirty in the page cache + * its backing folio dirty, then tag the folio as dirty in the page cache * and then attach the address_space's inode to its superblock's dirty * inode list. * @@ -1228,7 +1228,7 @@ static void bh_lru_install(struct buffer_head *bh) /* * the refcount of buffer_head in bh_lru prevents dropping the - * attached page(i.e., try_to_free_buffers) so it could cause + * attached folio (i.e., try_to_free_buffers) so it could cause * failing page migration. * Skip putting upcoming bh into bh_lru until migration is done. */ @@ -1292,7 +1292,7 @@ lookup_bh_lru(struct block_device *bdev, sector_t block, unsigned size) * Perform a pagecache lookup for the matching buffer. If it's there, refresh * it in the LRU and mark it as accessed. If it is not present then return * NULL. Atomic context callers may also return NULL if the buffer is being - * migrated; similarly the page is not marked accessed either. + * migrated; similarly the folio is not marked accessed either. */ static struct buffer_head * find_get_block_common(struct block_device *bdev, sector_t block, @@ -1301,7 +1301,7 @@ find_get_block_common(struct block_device *bdev, sector_t block, struct buffer_head *bh = lookup_bh_lru(bdev, block, size); if (bh == NULL) { - /* __find_get_block_slow will mark the page accessed */ + /* __find_get_block_slow will mark the folio accessed */ bh = __find_get_block_slow(bdev, block, atomic); if (bh) bh_lru_install(bh); @@ -1487,15 +1487,14 @@ void folio_set_bh(struct buffer_head *bh, struct folio *folio, } EXPORT_SYMBOL(folio_set_bh); -/* - * Called when truncating a buffer on a page completely. - */ - /* Bits that are cleared during an invalidate */ #define BUFFER_FLAGS_DISCARD \ (1 << BH_Mapped | 1 << BH_New | 1 << BH_Req | \ 1 << BH_Delay | 1 << BH_Unwritten | 1 << BH_Write_EIO) +/* + * Called when truncating a buffer on a folio completely. + */ static void discard_buffer(struct buffer_head * bh) { unsigned long b_state; @@ -1720,7 +1719,7 @@ static struct buffer_head *folio_create_buffers(struct folio *folio, * * If block_write_full_folio() is called for regular writeback * (wbc->sync_mode == WB_SYNC_NONE) then it will redirty a folio which - * has a locked buffer. This only can happen if someone has written + * has a locked buffer. This can only happen if someone has written * the buffer directly, with bh_submit(). At the address_space level * the folio writeback flag prevents this contention from occurring. * @@ -2223,9 +2222,9 @@ int generic_write_end(const struct kiocb *iocb, struct address_space *mapping, if (old_size < pos) pagecache_isize_extended(inode, old_size, pos); /* - * Don't mark the inode dirty under page lock. First, it unnecessarily - * makes the holding time of page lock longer. Second, it forces lock - * ordering of page lock and transaction start for journaling + * Don't mark the inode dirty under folio lock. First, it unnecessarily + * makes the holding time of folio lock longer. Second, it forces lock + * ordering of folio lock and transaction start for journaling * filesystems. */ if (i_size_changed) @@ -2351,7 +2350,7 @@ int block_read_full_folio(struct folio *folio, get_block_t *get_block) * BH_Async_Read tells end_buffer_async_read() that this * buffer is not under async I/O. * - * The folio comes unlocked when it has no locked + * The folio is unlocked when it has no locked * buffer_async buffers left. * * The folio lock prevents anyone starting new async @@ -2461,7 +2460,7 @@ static int cont_expand_zero(const struct kiocb *iocb, } } - /* page covers the boundary, find the boundary offset */ + /* folio crosses the boundary, find the boundary offset */ if (index == curidx) { zerofrom = curpos & ~PAGE_MASK; /* if we will expand the thing last block will be filled */ @@ -2519,18 +2518,18 @@ EXPORT_SYMBOL(cont_write_begin); /* * block_page_mkwrite() is not allowed to change the file size as it gets - * called from a page fault handler when a page is first dirtied. Hence we must - * be careful to check for EOF conditions here. We set the page up correctly - * for a written page which means we get ENOSPC checking when writing into + * called from a page fault handler when a folio is first dirtied. Hence we must + * be careful to check for EOF conditions here. We set the folio up correctly + * for a written folio which means we get ENOSPC checking when writing into * holes and correct delalloc and unwritten extent mapping on filesystems that * support these features. * * We are not allowed to take the i_rwsem here so we have to play games to - * protect against truncate races as the page could now be beyond EOF. Because - * truncate writes the inode size before removing pages, once we have the - * page lock we can determine safely if the page is beyond EOF. If it is not - * beyond EOF, then the page is guaranteed safe against truncation until we - * unlock the page. + * protect against truncate races as the folio could now be beyond EOF. Because + * truncate writes the inode size before removing folios, once we have the + * folio lock we can determine safely if the folio is beyond EOF. If it is not + * beyond EOF, then the folio is guaranteed safe against truncation until we + * unlock the folio. * * Direct callers of this function should protect against filesystem freezing * using sb_start_pagefault() - sb_end_pagefault() functions. @@ -2548,7 +2547,7 @@ int block_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf, size = i_size_read(inode); if ((folio->mapping != inode->i_mapping) || (folio_pos(folio) >= size)) { - /* We overload EFAULT to mean page got truncated */ + /* We overload EFAULT to mean folio got truncated */ ret = -EFAULT; goto out_unlock; } From a21153de5aa936900aa98fd81643895c7fd2a267 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Fri, 28 Aug 2026 21:54:26 +0100 Subject: [PATCH 0317/2286] buffer: Rename label We now unlock the folio, not the page. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260828205429.3678204-7-willy@infradead.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index f1c0ae0c7d19d6..1dc933ba6925a2 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -1658,7 +1658,7 @@ void clean_bdev_aliases(struct block_device *bdev, sector_t block, sector_t len) /* Recheck when the folio is locked which pins bhs */ head = folio_buffers(folio); if (!head) - goto unlock_page; + goto unlock_folio; bh = head; do { if (!buffer_mapped(bh) || (bh->b_blocknr < block)) @@ -1671,7 +1671,7 @@ void clean_bdev_aliases(struct block_device *bdev, sector_t block, sector_t len) next: bh = bh->b_this_page; } while (bh != head); -unlock_page: +unlock_folio: folio_unlock(folio); } folio_batch_release(&fbatch); From b4db97b9ef46909d6a8323d8cd04d81ef042e126 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Thu, 27 Aug 2026 04:17:29 +0000 Subject: [PATCH 0318/2286] vfs: Annotate struct fdtable's fd field with __counted_by_ptr The 'struct fdtable' holds the file descriptor table information, including the current file descriptor array 'fd' and its size 'max_fds'. To harden the kernel against out-of-bounds accesses, we can annotate the 'fd' pointer field with the '__counted_by_ptr' attribute, referencing 'max_fds'. The compiler uses the '__counted_by_ptr' attribute to track the size of the memory allocated for the pointer field, enabling runtime bounds checks under KASAN and fortified functions. There are three places where a 'struct fdtable' is initialized, and in all of them, 'max_fds' is set before the 'fd' pointer is accessed or assigned in all allocation and initialization places. No accesses to 'fd' occur before 'max_fds' is set, preventing any potential runtime false-positives or panics due to uninitialized count fields. Signed-off-by: Bill Wendling Link: https://patch.msgid.link/20260827041732.188707-1-morbo@google.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- include/linux/fdtable.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index c45306a9f00723..3a5c8829112534 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -25,7 +25,7 @@ struct fdtable { unsigned int max_fds; - struct file __rcu **fd; /* current fd array */ + struct file __rcu **fd __counted_by_ptr(max_fds); /* current fd array */ unsigned long *close_on_exec; unsigned long *open_fds; unsigned long *full_fds_bits; From 501cbbd276b77d34af20ea3dd4f897c93630bac8 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Thu, 27 Aug 2026 04:17:30 +0000 Subject: [PATCH 0319/2286] vfs: Add KUnit tests for fdtable This adds a KUnit test suite for fdtable to verify correct allocation, max_fds initialization, and dynamic object size of the fd array under __counted_by_ptr when CONFIG_CC_HAS_COUNTED_BY_PTR is enabled. Signed-off-by: Bill Wendling Link: https://patch.msgid.link/20260827041732.188707-2-morbo@google.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/Kconfig | 8 +++++ fs/file.c | 4 +++ fs/tests/.kunitconfig | 2 ++ fs/tests/fdtable_kunit.c | 72 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 fs/tests/.kunitconfig create mode 100644 fs/tests/fdtable_kunit.c diff --git a/fs/Kconfig b/fs/Kconfig index cf6ae64776e62c..f4b9235ab8836b 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -423,4 +423,12 @@ source "fs/unicode/Kconfig" config IO_WQ bool +config FDTABLE_KUNIT_TEST + bool "KUnit test for fdtable" if !KUNIT_ALL_TESTS + depends on KUNIT=y + default KUNIT_ALL_TESTS + help + This builds the fdtable KUnit tests, which tests various aspects + of the fdtable structure and allocation. + endmenu diff --git a/fs/file.c b/fs/file.c index 628ca07dc4b179..9c7001b901cfaa 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1529,3 +1529,7 @@ int iterate_fd(struct files_struct *files, unsigned n, return res; } EXPORT_SYMBOL(iterate_fd); + +#ifdef CONFIG_FDTABLE_KUNIT_TEST +#include "tests/fdtable_kunit.c" +#endif diff --git a/fs/tests/.kunitconfig b/fs/tests/.kunitconfig new file mode 100644 index 00000000000000..de67125a9421a0 --- /dev/null +++ b/fs/tests/.kunitconfig @@ -0,0 +1,2 @@ +CONFIG_KUNIT=y +CONFIG_FDTABLE_KUNIT_TEST=y diff --git a/fs/tests/fdtable_kunit.c b/fs/tests/fdtable_kunit.c new file mode 100644 index 00000000000000..6abd2a8d8f5de3 --- /dev/null +++ b/fs/tests/fdtable_kunit.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include + +static void test_alloc_fdtable(struct kunit *test) +{ + struct fdtable *fdt; + unsigned int slots = 64; + + fdt = alloc_fdtable(slots); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fdt); + + /* Check that max_fds is set correctly and is >= slots */ + KUNIT_EXPECT_GE(test, fdt->max_fds, slots); + + /* Check that fd is allocated */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fdt->fd); + + /* + * Check dynamic object size of fdt->fd if compiler supports + * __counted_by_ptr. + */ +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR + KUNIT_EXPECT_EQ(test, __builtin_dynamic_object_size(fdt->fd, 0), + fdt->max_fds * sizeof(struct file *)); +#endif + + __free_fdtable(fdt); +} + +static void test_dup_fd(struct kunit *test) +{ + struct files_struct *newf; + struct fdtable *fdt; + + newf = dup_fd(&init_files, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, newf); + + fdt = rcu_dereference_raw(newf->fdt); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fdt); + + /* Check that max_fds is set correctly and is >= NR_OPEN_DEFAULT */ + KUNIT_EXPECT_GE(test, fdt->max_fds, NR_OPEN_DEFAULT); + + /* Check that fd is allocated */ + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fdt->fd); + + /* + * Check dynamic object size of fdt->fd if compiler supports + * __counted_by_ptr. + */ +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR + KUNIT_EXPECT_EQ(test, __builtin_dynamic_object_size(fdt->fd, 0), + fdt->max_fds * sizeof(struct file *)); +#endif + + put_files_struct(newf); +} + +static struct kunit_case fdtable_test_cases[] = { + KUNIT_CASE(test_alloc_fdtable), + KUNIT_CASE(test_dup_fd), + {} +}; + +static struct kunit_suite fdtable_test_suite = { + .name = "fdtable", + .test_cases = fdtable_test_cases, +}; + +kunit_test_suite(fdtable_test_suite); From 60da22a38b01eae6f343f3aad8e78924a4b97aeb Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 5 Sep 2026 12:16:10 -0700 Subject: [PATCH 0320/2286] selftests: cover kernfs file handles and same-parent rename The kernfs tests only reach kernfs through lookup and readdir. Two paths are not covered: file handles, which find a node without a lookup through its parent, and rename of a node that keeps its parent. Add three tests: - decode a file handle, live and after the node is gone; - decode while the node is being removed; - look up a name while an interface is renamed, which renames its /sys/class/net entry with the parent unchanged. Two small fixes while here. ns_tag_isolates_class_net now counts only symlinks, because bonding adds a bonding_masters attribute that is not a device. A failing mkdtemp() now skips instead of aborting. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260905191613.3143937-2-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/filesystems/config | 1 + .../selftests/filesystems/kernfs_test.c | 296 +++++++++++++++++- 2 files changed, 295 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/config b/tools/testing/selftests/filesystems/config index 7231710d5ce77c..9f45bc493a30a8 100644 --- a/tools/testing/selftests/filesystems/config +++ b/tools/testing/selftests/filesystems/config @@ -1,5 +1,6 @@ CONFIG_CGROUPS=y CONFIG_CGROUP_PIDS=y +CONFIG_FHANDLE=y CONFIG_NAMESPACES=y CONFIG_NET=y CONFIG_NET_NS=y diff --git a/tools/testing/selftests/filesystems/kernfs_test.c b/tools/testing/selftests/filesystems/kernfs_test.c index 2178428b9665e2..6e74da91ebca5f 100644 --- a/tools/testing/selftests/filesystems/kernfs_test.c +++ b/tools/testing/selftests/filesystems/kernfs_test.c @@ -610,6 +610,189 @@ TEST_F(kernfs_cgroup, lookup_vs_create_remove_stress) } } +struct kernfs_handle { + struct file_handle h; + unsigned char buf[MAX_HANDLE_SZ]; +}; + +static int kernfs_encode(const char *path, struct kernfs_handle *fh) +{ + int mount_id; + + memset(fh, 0, sizeof(*fh)); + fh->h.handle_bytes = sizeof(fh->buf); + return name_to_handle_at(AT_FDCWD, path, &fh->h, &mount_id, 0); +} + +/* + * Skip only where file handles do not work at all. ENOENT must still + * fail: mkdir leaves a negative dentry cached, so the name resolves only + * after ->d_revalidate() drops it. The encode tests revalidation too. + */ +static bool fh_unsupported(int err) +{ + return err == EOPNOTSUPP || err == EPERM || err == ENOSYS; +} + +/* + * Decoding a file needs CAP_DAC_READ_SEARCH in the initial user + * namespace. Probe once so the tests skip instead of fail. + */ +static bool fh_can_decode(int mfd, struct kernfs_handle *fh) +{ + int fd = open_by_handle_at(mfd, &fh->h, O_PATH); + + if (fd < 0) + return errno != EPERM; + close(fd); + return true; +} + +/* + * A file handle reaches a node without a lookup through its parent. A + * live node must decode. A removed one must not, because + * kernfs_find_and_get_node_by_id() refuses inactive nodes. + * + * Use O_PATH: opening a removed node fails with ENODEV, which would hide + * what is being tested. + */ +TEST_F(kernfs_cgroup, exportfs_decode_and_stale) +{ + char victim[PATH_MAX], procs[PATH_MAX]; + struct kernfs_handle fh; + struct stat st; + int mfd, fd; + + snprintf(victim, sizeof(victim), "%s/fh", self->scratch); + snprintf(procs, sizeof(procs), "%s/cgroup.procs", victim); + ASSERT_EQ(mkdir(victim, 0755), 0); + + /* Any fd on the filesystem identifies it to open_by_handle_at(). */ + mfd = open(self->scratch, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(mfd, 0); + + if (kernfs_encode(procs, &fh)) { + int err = errno; + + close(mfd); + rmdir(victim); + ASSERT_TRUE(fh_unsupported(err)) + TH_LOG("name_to_handle_at: %s", strerror(err)); + SKIP(return, "name_to_handle_at: %s", strerror(err)); + } + + if (!fh_can_decode(mfd, &fh)) { + close(mfd); + rmdir(victim); + SKIP(return, "open_by_handle_at: no CAP_DAC_READ_SEARCH"); + } + + fd = open_by_handle_at(mfd, &fh.h, O_PATH); + ASSERT_GE(fd, 0); + EXPECT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_nlink, 1); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(rmdir(victim), 0); + + fd = open_by_handle_at(mfd, &fh.h, O_PATH); + EXPECT_LT(fd, 0); + if (fd >= 0) + close(fd); + else + EXPECT_EQ(errno, ESTALE); + + EXPECT_EQ(close(mfd), 0); +} + +#define FH_STRESS_SECS 2 +#define FH_DECODE_CAP 10000 + +/* + * Decode file handles while the node is being removed. A decode must + * answer with a usable handle or ESTALE, never garbage and never a hang. + * + * The link count is checked too. An inode that reaches the inode hash + * after the removal cleared link counts keeps the 1 it was born with, so + * it never gets an IN_DELETE_SELF. This has not been seen to fire: it + * needs the decode to stall between the lookup by id and the hash insert, + * and nothing there blocks. It is kept because it is cheap and only + * looks once the directory is gone, so it cannot fail falsely. + */ +TEST_F(kernfs_cgroup, exportfs_decode_vs_rmdir_stress) +{ + int mfd, bad = 0, rounds = 0; + struct timespec end; + + mfd = open(self->scratch, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(mfd, 0); + + clock_gettime(CLOCK_MONOTONIC, &end); + end.tv_sec += FH_STRESS_SECS; + + while (!stress_deadline(&end)) { + char victim[PATH_MAX], procs[PATH_MAX]; + int last = -1, fd, i; + struct kernfs_handle fh; + struct stat st; + pid_t pid; + + snprintf(victim, sizeof(victim), "%s/fh%d", self->scratch, + rounds++); + snprintf(procs, sizeof(procs), "%s/cgroup.procs", victim); + if (mkdir(victim, 0755)) + break; + if (kernfs_encode(procs, &fh)) { + int err = errno; + + rmdir(victim); + ASSERT_TRUE(fh_unsupported(err)) + TH_LOG("name_to_handle_at: %s", strerror(err)); + SKIP(goto out, "name_to_handle_at: %s", strerror(err)); + } + if (rounds == 1 && !fh_can_decode(mfd, &fh)) { + rmdir(victim); + SKIP(goto out, + "open_by_handle_at: no CAP_DAC_READ_SEARCH"); + } + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + rmdir_retry(victim); + _exit(0); + } + + /* + * Decode until the removal deactivates the node. Keep the + * last one that worked: it ran closest to the removal. + */ + for (i = 0; i < FH_DECODE_CAP; i++) { + fd = open_by_handle_at(mfd, &fh.h, O_PATH); + if (fd < 0) + break; + if (last >= 0) + close(last); + last = fd; + } + ASSERT_EQ(waitpid(pid, NULL, 0), pid); + + if (last >= 0) { + if (access(victim, F_OK) && errno == ENOENT && + !fstat(last, &st) && st.st_nlink != 0) + bad++; + close(last); + } + rmdir(victim); + } + + EXPECT_EQ(bad, 0) + TH_LOG("%d of %d rounds decoded a removed node whose inode kept its link count", + bad, rounds); +out: + close(mfd); +} + /* * sysfs is namespace tagged (KERNFS_NS) and supports rename; cgroup2 does * neither. Run in a private netns with its own sysfs so the host is @@ -635,7 +818,8 @@ FIXTURE_SETUP(kernfs_netns) ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); strcpy(self->mnt, "/tmp/kernfs_selftest_sysfs.XXXXXX"); - ASSERT_NE(mkdtemp(self->mnt), NULL); + if (!mkdtemp(self->mnt)) + SKIP(return, "mkdtemp: %s", strerror(errno)); if (mount("none", self->mnt, "sysfs", 0, NULL)) { rmdir(self->mnt); @@ -662,6 +846,9 @@ FIXTURE_TEARDOWN(kernfs_netns) * depends on the modules the host has. Check the set instead -- * if_nametoindex() resolves in the current netns, so every name sysfs shows * must resolve there, and the counts must agree. + * + * Count only symlinks. Not every entry is a device: bonding adds a + * bonding_masters attribute to /sys/class/net in every namespace. */ TEST_F(kernfs_netns, ns_tag_isolates_class_net) { @@ -674,7 +861,7 @@ TEST_F(kernfs_netns, ns_tag_isolates_class_net) d = opendir(self->net); ASSERT_NE(d, NULL); while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + if (de->d_type != DT_LNK) continue; EXPECT_NE(if_nametoindex(de->d_name), 0u) TH_LOG("%s is not in this netns", de->d_name); @@ -728,4 +915,109 @@ TEST_F(kernfs_netns, rename_is_revalidated) EXPECT_EQ(stat(new_path, &st), 0); } +static int netdev_rename(const char *from, const char *to) +{ + struct ifreq ifr = {}; + int sk, ret; + + sk = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sk < 0) + return -1; + strncpy(ifr.ifr_name, from, IFNAMSIZ - 1); + strncpy(ifr.ifr_newname, to, IFNAMSIZ - 1); + ret = ioctl(sk, SIOCSIFNAME, &ifr); + close(sk); + return ret; +} + +/* + * Bounded by a count, not by time: every rename is logged and not rate + * limited, so a timed loop would flood the kernel log. + */ +#define RENAME_FLIPS 200 +#define RENAME_READERS 4 + +/* + * Rename an interface while other tasks look up the names it moves + * between. This renames its /sys/class/net entry through + * kernfs_rename_ns() with the parent unchanged. + * + * The renamer checks what is certain: SIOCSIFNAME returns once the rename + * is done and nothing else renames here, so the new name must resolve and + * the old must not. The readers cannot check that, because the name can + * move between their two lstat() calls. They only check that a lookup + * returns success or ENOENT, and keep the lock busy while renames run. + * + * lstat() not stat(): /sys/class/net/ is a symlink and is renamed + * before the directory it points at, so the two are not atomic. + */ +TEST_F(kernfs_netns, rename_vs_lookup_stress) +{ + char old_path[PATH_MAX], new_path[PATH_MAX]; + pid_t pids[RENAME_READERS]; + int i, status, n = 0, bad = 0; + struct stat st; + int done[2]; + + snprintf(old_path, sizeof(old_path), "%s/lo", self->net); + snprintf(new_path, sizeof(new_path), "%s/%s", self->net, TEST_IFNAME); + + if (netdev_rename("lo", TEST_IFNAME)) + SKIP(return, "SIOCSIFNAME: %s", strerror(errno)); + if (netdev_rename(TEST_IFNAME, "lo")) + SKIP(return, "SIOCSIFNAME back: %s", strerror(errno)); + + /* Readers run until the renamer closes the write end. */ + ASSERT_EQ(pipe2(done, O_NONBLOCK | O_CLOEXEC), 0); + + for (i = 0; i < RENAME_READERS; i++) { + pid_t pid = fork(); + + ASSERT_GE(pid, 0); + if (pid == 0) { + struct stat rst; + char c; + + close(done[1]); + while (read(done[0], &c, 1) < 0 && errno == EAGAIN) { + if (lstat(old_path, &rst) && errno != ENOENT) + _exit(20); + if (lstat(new_path, &rst) && errno != ENOENT) + _exit(21); + } + _exit(0); + } + pids[n++] = pid; + } + close(done[0]); + + for (i = 0; i < RENAME_FLIPS; i++) { + if (netdev_rename("lo", TEST_IFNAME)) + break; + if (lstat(new_path, &st) || !lstat(old_path, &st)) { + bad++; + break; + } + if (netdev_rename(TEST_IFNAME, "lo")) + break; + if (lstat(old_path, &st) || !lstat(new_path, &st)) { + bad++; + break; + } + } + close(done[1]); + + for (i = 0; i < n; i++) { + ASSERT_EQ(waitpid(pids[i], &status, 0), pids[i]); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + } + + EXPECT_EQ(bad, 0) + TH_LOG("a completed rename left the wrong name resolving"); + + /* Leave the interface as the fixture found it. */ + netdev_rename(TEST_IFNAME, "lo"); +} + TEST_HARNESS_MAIN From 6034974e3aec7bd47e4ad4f5a01e7a289e8ec9cf Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 5 Sep 2026 12:16:11 -0700 Subject: [PATCH 0321/2286] kernfs: take kernfs_rename_lock for same-parent renames too kernfs_rename_ns() takes kernfs_rename_lock only when the rename moves the node to a new parent. A rename that keeps the parent, like renaming a network interface, changes kernfs_node::name under kernfs_rwsem alone. So the lock covers ->__parent but not ->name, and a reader that wants a stable name has to take kernfs_rwsem, the lock every lookup needs. It is also a real bug. kernfs_path_from_node() holds the lock for reading and reads each ancestor's name once. One rename only moves the answer from the old path to the new one, but two renames inside one walk build a path that never existed: CPU0 CPU1 kernfs_path_from_node() on /a/b/c reads the name of a, gets "a" renames a to a2 renames b to b2 reads the name of b, gets "b2" returns "/a/b2/c" Only sysfs can hit this: sysfs_warn_dup() is the one caller on a root without KERNFS_ROOT_INVARIANT_PARENT. The rest are cgroup, which sets the flag, so it skips the lock and reads names under RCU alone. That case needs something else and is left alone here. So take the lock for both kinds of rename, and let kernfs_rcu_name() accept it, like kernfs_parent() already does for ->__parent. Renames are rare, the lock is per filesystem, and the locked section is at most three stores. It also gives a future rename counter one place to sit. Fixes: 741c10b096bc ("kernfs: Use RCU to access kernfs_node::name.") Acked-by: Tejun Heo Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260905191613.3143937-3-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 28 +++++++++++++++------------- fs/kernfs/kernfs-internal.h | 9 ++++++++- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index cd7a8ff8b6b212..214c97130a8a38 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1808,6 +1808,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, struct kernfs_node *old_parent; struct kernfs_root *root; const char *old_name; + bool reparent; int error; /* can't move or rename root */ @@ -1857,25 +1858,26 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, */ kernfs_unlink_sibling(kn); - /* rename_lock protects ->parent accessors */ - if (old_parent != new_parent) { + reparent = old_parent != new_parent; + if (reparent) kernfs_get(new_parent); - write_lock_irq(&root->kernfs_rename_lock); + /* + * kernfs_rename_lock protects ->__parent, ->ns and ->name, so take it + * even when the parent does not change. + */ + write_lock_irq(&root->kernfs_rename_lock); + + if (reparent) rcu_assign_pointer(kn->__parent, new_parent); + WRITE_ONCE(kn->ns, new_ns); + if (new_name) + rcu_assign_pointer(kn->name, new_name); - WRITE_ONCE(kn->ns, new_ns); - if (new_name) - rcu_assign_pointer(kn->name, new_name); + write_unlock_irq(&root->kernfs_rename_lock); - write_unlock_irq(&root->kernfs_rename_lock); + if (reparent) kernfs_put(old_parent); - } else { - /* name assignment is RCU protected, parent is the same */ - WRITE_ONCE(kn->ns, new_ns); - if (new_name) - rcu_assign_pointer(kn->name, new_name); - } kn->hash = kernfs_name_hash(new_name ?: old_name, kn->ns); kernfs_link_sibling(kn); diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 20a0cf42ba8d40..1609c1519698ee 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -117,7 +117,14 @@ static inline bool kernfs_rename_is_locked(const struct kernfs_node *kn) static inline const char *kernfs_rcu_name(const struct kernfs_node *kn) { - return rcu_dereference_check(kn->name, kernfs_root_is_locked(kn)); + /* + * Like kernfs_node::__parent below, the name is only replaced under + * both kernfs_root::kernfs_rwsem and kernfs_root::kernfs_rename_lock, + * so either one keeps it, and the string it points at, stable. + */ + return rcu_dereference_check(kn->name, + kernfs_root_is_locked(kn) || + kernfs_rename_is_locked(kn)); } static inline struct kernfs_node *kernfs_parent(const struct kernfs_node *kn) From dc1a0cc95737344b1a6aa42075e573e9625c6b9a Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 5 Sep 2026 12:16:12 -0700 Subject: [PATCH 0322/2286] kernfs: don't lose IN_DELETE_SELF when decoding a file handle __kernfs_remove() clears i_nlink on a node's inodes and finds them with ilookup(). A decode that has pinned the node but not yet hashed its inode is invisible to that pass: CPU0 CPU1 open_by_handle_at() kernfs_find_and_get_node_by_id() pins the node, still active rmdir() marks the subtree removing ilookup() finds no inode kernfs_get_inode() hashes an inode with i_nlink 1 Nothing fixes it later: kernfs_refresh_inode() never touches i_nlink for a file and skips it for a directory being removed. The inode keeps the 1 it was born with, and dentry_unlink_inode() sends IN_DELETE_SELF only at 0, so a watcher never learns the node went away. The other callers of kernfs_get_inode() are safe: those in fs/kernfs hold kernfs_rwsem, and cgroup_may_write() is covered by cgroup_mutex, which cgroup_destroy_locked() holds across kernfs_remove(). __kernfs_fh_to_dentry() has held nothing since exportfs support was added. Take kernfs_rwsem for reading, as ->get_parent already does, and cover the lookup as well as kernfs_get_inode(). __kernfs_remove() deactivates the whole subtree under the write lock, so under the read lock either the lookup refuses the node, or the inode is hashed before the ilookup() pass runs. The same holds for ->fh_to_parent, since a node cannot be active while an ancestor is being removed. Reproduced with a 300ms delay between the lookup and kernfs_get_inode(), decoding a handle for a file in a cgroup directory while another task rmdir()s it: st_nlink is 1 without this patch and 0 with it. ->get_parent still has a window of its own. It takes the same lock but has no active check, so reconnect_path() can build an inode for an ancestor that is already gone. That needs the active test rather than a lock, and changes what ->get_parent returns, so it is left to the series that reworks these paths. Fixes: eea5d2bb34ba ("kernfs: Send IN_DELETE_SELF and IN_IGNORED") Cc: stable@vger.kernel.org Acked-by: Tejun Heo Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260905191613.3143937-4-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/mount.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c index f183a96778b9a2..c15ba63571622d 100644 --- a/fs/kernfs/mount.c +++ b/fs/kernfs/mount.c @@ -124,22 +124,32 @@ static struct dentry *__kernfs_fh_to_dentry(struct super_block *sb, return NULL; } - kn = kernfs_find_and_get_node_by_id(info->root, id); - if (!kn) - return ERR_PTR(-ESTALE); + /* + * Hold kernfs_rwsem across the lookup as well as kernfs_get_inode(). + * __kernfs_remove() deactivates the subtree and clears i_nlink on its + * inodes under the write lock, so under the read lock either + * kernfs_find_and_get_node_by_id() refuses the node, or the inode is + * in the inode hash before the ilookup() pass goes looking for it. + */ + scoped_guard(rwsem_read, &info->root->kernfs_rwsem) { + kn = kernfs_find_and_get_node_by_id(info->root, id); + if (!kn) + return ERR_PTR(-ESTALE); - if (get_parent) { - struct kernfs_node *parent; + if (get_parent) { + struct kernfs_node *parent; - parent = kernfs_get_parent(kn); + parent = kernfs_get_parent(kn); + kernfs_put(kn); + kn = parent; + if (!kn) + return ERR_PTR(-ESTALE); + } + + inode = kernfs_get_inode(sb, kn); kernfs_put(kn); - kn = parent; - if (!kn) - return ERR_PTR(-ESTALE); } - inode = kernfs_get_inode(sb, kn); - kernfs_put(kn); return d_obtain_alias(inode); } From 81bdc85b4c086502f4446a58eb5d25f29bb5e9f7 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 5 Sep 2026 12:16:13 -0700 Subject: [PATCH 0323/2286] kernfs: fix up the unlocked attribute reads on the creation paths Two creation paths read a live node's attributes without holding kernfs_iattr_rwsem, which kernfs_iop_setattr() takes for writing. They need different fixes. kernfs_create_link() copies the target's ia_uid and then its ia_gid into the new link. A chown of the target between the two reads leaves the link with the old uid and the new gid, an owner the target never had. Read both under the rwsem. kernfs_new_node() reads the parent's mode and ia_gid for S_ISGID inheritance. Either value is fine there: the node does not exist yet, so nothing orders a racing chmod or chown against the creation. Taking the rwsem would only pick between two answers that are both right. Mark the reads with READ_ONCE() instead. The pointer that leads to them is already fine: __kernfs_iattrs() publishes kernfs_node::iattr with try_cmpxchg(), and both sides read it with READ_ONCE(), like the rest of fs/kernfs. The Fixes tag is for the symlink half. kernfs_create_link() has read the pair unlocked since it started copying the target's owner; only the name of the lock its writer takes has changed. The READ_ONCE() markings are not a fix. Fixes: 488dee96bb62 ("kernfs: allow creating kernfs objects with arbitrary uid/gid") Acked-by: Tejun Heo Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260905191613.3143937-5-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 12 +++++++++--- fs/kernfs/symlink.c | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 214c97130a8a38..07abf59f0264a6 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -736,13 +736,19 @@ struct kernfs_node *kernfs_new_node(struct kernfs_node *parent, { struct kernfs_node *kn; - if (parent->mode & S_ISGID) { + /* + * The mode and the gid below are read unlocked on purpose: they feed + * a node that does not exist yet, so nothing orders a racing chmod or + * chown against this creation. + */ + if (READ_ONCE(parent->mode) & S_ISGID) { /* this code block imitates inode_init_owner() for * kernfs */ + struct kernfs_iattrs *attrs = READ_ONCE(parent->iattr); - if (parent->iattr) - gid = parent->iattr->ia_gid; + if (attrs) + gid = READ_ONCE(attrs->ia_gid); if (flags & KERNFS_DIR) mode |= S_ISGID; diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 90e2b3221b83d8..3e53105d3abf14 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -31,9 +31,20 @@ struct kernfs_node *kernfs_create_link(struct kernfs_node *parent, kuid_t uid = GLOBAL_ROOT_UID; kgid_t gid = GLOBAL_ROOT_GID; - if (target->iattr) { - uid = target->iattr->ia_uid; - gid = target->iattr->ia_gid; + /* + * A symlink takes its owner from its target, so both fields have to + * come from the same moment: read them under kernfs_iattr_rwsem, or + * a chown of the target racing this could leave the link with the + * old uid and the new gid. The section ends before kernfs_add_one() + * takes kernfs_rwsem. + */ + scoped_guard(rwsem_read, &kernfs_root(target)->kernfs_iattr_rwsem) { + struct kernfs_iattrs *attrs = READ_ONCE(target->iattr); + + if (attrs) { + uid = attrs->ia_uid; + gid = attrs->ia_gid; + } } kn = kernfs_new_node(parent, name, S_IFLNK|0777, uid, gid, KERNFS_LINK); From cc939930c450ba3e6b68676e7c048aa47ad5afbe Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Fri, 4 Sep 2026 23:27:48 +0800 Subject: [PATCH 0324/2286] fs/dax: Remove unused dax_layout_busy_page() dax_layout_busy_page() used to let filesystems scan an entire DAX mapping for pinned pages. All callers now use dax_break_layout(), which also handles waiting for busy pages and deleting DAX mapping entries once the range becomes idle. The remaining function is only a wrapper around dax_layout_busy_page_range() and has no in-tree users. Remove it together with its declaration, !CONFIG_FS_DAX stub, and exported symbol. Signed-off-by: Kaitao Cheng Link: https://patch.msgid.link/20260904152749.84493-2-kaitao.cheng@linux.dev Acked-by: Muchun Song Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/dax.c | 6 ------ include/linux/dax.h | 6 ------ 2 files changed, 12 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index dad4efea796404..1e19e4a354ceee 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -841,12 +841,6 @@ struct page *dax_layout_busy_page_range(struct address_space *mapping, } EXPORT_SYMBOL_GPL(dax_layout_busy_page_range); -struct page *dax_layout_busy_page(struct address_space *mapping) -{ - return dax_layout_busy_page_range(mapping, 0, LLONG_MAX); -} -EXPORT_SYMBOL_GPL(dax_layout_busy_page); - static int __dax_invalidate_entry(struct address_space *mapping, pgoff_t index, bool trunc) { diff --git a/include/linux/dax.h b/include/linux/dax.h index fe6c3ded1b50f7..05e59e45a1c2fc 100644 --- a/include/linux/dax.h +++ b/include/linux/dax.h @@ -155,7 +155,6 @@ int dax_writeback_mapping_range(struct address_space *mapping, struct dax_device *dax_dev, struct writeback_control *wbc); int dax_folio_reset_order(struct folio *folio); -struct page *dax_layout_busy_page(struct address_space *mapping); struct page *dax_layout_busy_page_range(struct address_space *mapping, loff_t start, loff_t end); dax_entry_t dax_lock_folio(struct folio *folio); void dax_unlock_folio(struct folio *folio, dax_entry_t cookie); @@ -173,11 +172,6 @@ static inline int fs_dax_get(struct dax_device *dax_dev, void *holder, { return -EOPNOTSUPP; } -static inline struct page *dax_layout_busy_page(struct address_space *mapping) -{ - return NULL; -} - static inline struct page *dax_layout_busy_page_range(struct address_space *mapping, pgoff_t start, pgoff_t nr_pages) { return NULL; From 24bf180c869bdfe43f9ec621a54ed131d65b0e2d Mon Sep 17 00:00:00 2001 From: Kaitao Cheng Date: Fri, 4 Sep 2026 23:27:49 +0800 Subject: [PATCH 0325/2286] fs/dax: Make dax_layout_busy_page_range() static dax_layout_busy_page_range() no longer has callers outside fs/dax.c. Keeping the range helper exported unnecessarily exposes a low-level implementation detail. Make the helper static and remove its export and header definitions. Also fix the stale dax_layout_busy_page_range() comment to match the implementation. Signed-off-by: Kaitao Cheng Link: https://patch.msgid.link/20260904152749.84493-3-kaitao.cheng@linux.dev Acked-by: Muchun Song Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/dax.c | 18 ++++++++---------- include/linux/dax.h | 6 ------ 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/fs/dax.c b/fs/dax.c index 1e19e4a354ceee..c6ad9533aa544a 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -770,24 +770,23 @@ static void *grab_mapping_entry(struct xa_state *xas, /** * dax_layout_busy_page_range - find first pinned page in @mapping - * @mapping: address space to scan for a page with ref count > 1 + * @mapping: address space to scan for a pinned page * @start: Starting offset. Page containing 'start' is included. * @end: End offset. Page containing 'end' is included. If 'end' is LLONG_MAX, * pages from 'start' till the end of file are included. * - * DAX requires ZONE_DEVICE mapped pages. These pages are never - * 'onlined' to the page allocator so they are considered idle when - * page->count == 1. A filesystem uses this interface to determine if - * any page in the mapping is busy, i.e. for DMA, or other - * get_user_pages() usages. + * DAX requires ZONE_DEVICE mapped pages. A page is considered busy when + * folio_ref_count(folio) exceeds folio_mapcount(folio). This helper is + * used to determine if any page in the mapping is busy, i.e. for DMA, + * or other get_user_pages() usages. * * It is expected that the filesystem is holding locks to block the * establishment of new mappings in this address_space. I.e. it expects - * to be able to run unmap_mapping_range() and subsequently not race + * to be able to run unmap_mapping_pages() and subsequently not race * mapping_mapped() becoming true. */ -struct page *dax_layout_busy_page_range(struct address_space *mapping, - loff_t start, loff_t end) +static struct page *dax_layout_busy_page_range(struct address_space *mapping, + loff_t start, loff_t end) { void *entry; unsigned int scanned = 0; @@ -839,7 +838,6 @@ struct page *dax_layout_busy_page_range(struct address_space *mapping, xas_unlock_irq(&xas); return page; } -EXPORT_SYMBOL_GPL(dax_layout_busy_page_range); static int __dax_invalidate_entry(struct address_space *mapping, pgoff_t index, bool trunc) diff --git a/include/linux/dax.h b/include/linux/dax.h index 05e59e45a1c2fc..f2d47975d905fe 100644 --- a/include/linux/dax.h +++ b/include/linux/dax.h @@ -155,7 +155,6 @@ int dax_writeback_mapping_range(struct address_space *mapping, struct dax_device *dax_dev, struct writeback_control *wbc); int dax_folio_reset_order(struct folio *folio); -struct page *dax_layout_busy_page_range(struct address_space *mapping, loff_t start, loff_t end); dax_entry_t dax_lock_folio(struct folio *folio); void dax_unlock_folio(struct folio *folio, dax_entry_t cookie); dax_entry_t dax_lock_mapping_entry(struct address_space *mapping, @@ -172,11 +171,6 @@ static inline int fs_dax_get(struct dax_device *dax_dev, void *holder, { return -EOPNOTSUPP; } -static inline struct page *dax_layout_busy_page_range(struct address_space *mapping, pgoff_t start, pgoff_t nr_pages) -{ - return NULL; -} - static inline int dax_writeback_mapping_range(struct address_space *mapping, struct dax_device *dax_dev, struct writeback_control *wbc) { From 21a62663d473475db9f05c108e84f035b33e8484 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:02:52 +0530 Subject: [PATCH 0326/2286] fs: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Link: https://patch.msgid.link/20260907063252.21433-1-hemanth.selam@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 2 +- fs/fs-writeback.c | 4 ++-- fs/namespace.c | 2 +- include/linux/fs.h | 2 +- include/uapi/linux/fs.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index ac3cd74808c640..2e8f247efd6549 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1047,7 +1047,7 @@ static bool coredump_write(struct core_name *cn, cn->core_dumped = binfmt->core_dump(cprm); /* * Ensures that file size is big enough to contain the current - * file postion. This prevents gdb from complaining about + * file position. This prevents gdb from complaining about * a truncated file if the last "write" to the file was * dump_skip. */ diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index 48ee2d55075e51..f303936446daff 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -248,7 +248,7 @@ void wb_wait_for_completion(struct wb_completion *done) * to 16 slots. To avoid tiny writes from swinging the decision too much, * writes smaller than 1/8 of avg size are ignored. */ -#define WB_FRN_TIME_SHIFT 13 /* 1s = 2^13, upto 8 secs w/ 16bit */ +#define WB_FRN_TIME_SHIFT 13 /* 1s = 2^13, up to 8 secs w/ 16bit */ #define WB_FRN_TIME_AVG_SHIFT 3 /* avg = avg * 7/8 + new * 1/8 */ #define WB_FRN_TIME_CUT_DIV 8 /* ignore rounds < avg / 8 */ #define WB_FRN_TIME_PERIOD (2 * (1 << WB_FRN_TIME_SHIFT)) /* 2s */ @@ -259,7 +259,7 @@ void wb_wait_for_completion(struct wb_completion *done) #define WB_FRN_HIST_THR_SLOTS (WB_FRN_HIST_SLOTS / 2) /* if foreign slots >= 8, switch */ #define WB_FRN_HIST_MAX_SLOTS (WB_FRN_HIST_THR_SLOTS / 2 + 1) - /* one round can affect upto 5 slots */ + /* one round can affect up to 5 slots */ #define WB_FRN_MAX_IN_FLIGHT 1024 /* don't queue too many concurrently */ /* diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b335..f45233f425f7f3 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4115,7 +4115,7 @@ int path_mount(const char *dev_name, const struct path *path, if (flags & SB_MANDLOCK) warn_mandlock(); - /* Default to relatime unless overriden */ + /* Default to relatime unless overridden */ if (!(flags & MS_NOATIME)) mnt_flags |= MNT_RELATIME; diff --git a/include/linux/fs.h b/include/linux/fs.h index f9d1e05e8ae68e..05d05bb9c951a2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1436,7 +1436,7 @@ static inline void i_gid_write(struct inode *inode, gid_t gid) * @idmap: idmap of the mount the inode was found from * @inode: inode to map * - * Return: whe inode's i_uid mapped down according to @idmap. + * Return: the inode's i_uid mapped down according to @idmap. * If the inode's i_uid has no mapping INVALID_VFSUID is returned. */ static inline vfsuid_t i_uid_into_vfsuid(struct mnt_idmap *idmap, diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 34c6f219462a59..a46c33692aa21a 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -88,7 +88,7 @@ struct fstrim_range { * We include a length field because some filesystems (vfat) have an identifier * that we do want to expose as a UUID, but doesn't have the standard length. * - * We use a fixed size buffer beacuse this interface will, by fiat, never + * We use a fixed size buffer because this interface will, by fiat, never * support "UUIDs" longer than 16 bytes; we don't want to force all downstream * users to have to deal with that. */ From bfe240aab4a1be5270bb8560d6895fd9550b8719 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Tue, 8 Sep 2026 21:01:03 +0800 Subject: [PATCH 0327/2286] squashfs: fix inode number description in documentation The documentation incorrectly described the inode identifier as a 48-bit number. In reality it is a 64-bit integer where the upper 48 bits hold the location of the compressed metadata block and the lower 16 bits hold the byte offset into the uncompressed block. Signed-off-by: Ray Lee Link: https://patch.msgid.link/20260908130103.272230-1-hburaylee@gmail.com Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/squashfs.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/squashfs.rst b/Documentation/filesystems/squashfs.rst index 45653b3228f972..d6397961d16ba2 100644 --- a/Documentation/filesystems/squashfs.rst +++ b/Documentation/filesystems/squashfs.rst @@ -177,9 +177,9 @@ or if the compressed block was larger than the uncompressed block. Inodes are packed into the metadata blocks, and are not aligned to block boundaries, therefore inodes overlap compressed blocks. Inodes are identified -by a 48-bit number which encodes the location of the compressed metadata block -containing the inode, and the byte offset into that block where the inode is -placed (). +by a 64-bit number: the upper 48 bits encode the location of the compressed +metadata block containing the inode, and the lower 16 bits give the byte offset +into that block where the inode is placed (). To maximise compression there are different inodes for each file type (regular file, directory, device, etc.), the inode contents and length From e81ee06308379a5f2ededf997bcf17551bce5db7 Mon Sep 17 00:00:00 2001 From: Eric Kim Date: Tue, 18 Aug 2026 12:48:29 +0900 Subject: [PATCH 0328/2286] sched/fair: Reset NUMA fault locality after scan period update When updating the task scan period for NUMA locality checks, update_task_scan_period() checks whether there were no faults or failed migration on the last scan window, at which it prolongs the scan period. However, p->numa_faults_locality, which is used to check for migration failure and number of faults at the previous scan window is not cleared after changing the scan period, which unintentionally increases numa_scan_period up to numa_scan_period_max even when there were no migration failures or no faults at previous scan. Fix this by jumping to the out label on early exit at this case to ensure p->numa_faults_locality is always cleared before returning. Closes: https://lore.kernel.org/all/20250404095354.311156-1-qlsdnjs236@chungbuk.ac.kr/ Reported-by: Binwon Song Signed-off-by: Eric Kim Signed-off-by: Peter Zijlstra (Intel) Cc: stable@vger.kernel.org Link: https://patch.msgid.link/Message-ID: --- kernel/sched/fair.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index b8bd308c2d5b12..a42360ca94c3d0 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -3514,7 +3514,7 @@ static void update_task_scan_period(struct task_struct *p, p->mm->numa_next_scan = jiffies + msecs_to_jiffies(p->numa_scan_period); - return; + goto out; } /* @@ -3558,7 +3558,10 @@ static void update_task_scan_period(struct task_struct *p, p->numa_scan_period = clamp(p->numa_scan_period + diff, task_scan_min(p), task_scan_max(p)); - memset(p->numa_faults_locality, 0, sizeof(p->numa_faults_locality)); + +out: + memset(p->numa_faults_locality, 0, + sizeof(p->numa_faults_locality)); } /* From 2405d15e5ae469bd9ae88ab0381f03e07787742f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:39 -0700 Subject: [PATCH 0329/2286] xor: Remove redundant X86_FEATURE_OSXSAVE check X86_FEATURE_AVX implies X86_FEATURE_OSXSAVE already. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Christoph Hellwig Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-8-ebiggers@kernel.org --- lib/raid/xor/x86/xor_arch.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/raid/xor/x86/xor_arch.h b/lib/raid/xor/x86/xor_arch.h index 99fe85a213c669..991abe3f4bbdac 100644 --- a/lib/raid/xor/x86/xor_arch.h +++ b/lib/raid/xor/x86/xor_arch.h @@ -18,8 +18,7 @@ extern struct xor_block_template xor_block_avx; */ static __always_inline void __init arch_xor_init(void) { - if (boot_cpu_has(X86_FEATURE_AVX) && - boot_cpu_has(X86_FEATURE_OSXSAVE)) { + if (boot_cpu_has(X86_FEATURE_AVX)) { xor_force(&xor_block_avx); } else if (IS_ENABLED(CONFIG_X86_64) || boot_cpu_has(X86_FEATURE_XMM)) { xor_register(&xor_block_sse); From c0a44d1745d3f209e4840b78ab75274fb74f01a0 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 1 Sep 2026 07:57:40 -0700 Subject: [PATCH 0330/2286] xor: Add AVX-512 optimized xor_gen() Add an implementation of xor_gen() using AVX-512. It uses 512-bit vectors, i.e. ZMM registers. It also uses the vpternlogq instruction to do three-input XORs when applicable. It's enabled on x86_64 CPUs that have AVX512F && !PREFER_YMM. In practice that means: - AMD Zen 4 and later (client and server) - Intel Sapphire Rapids and later (server) - Intel Rocket Lake (client) - Intel Nova Lake and later (client) The !PREFER_YMM condition excludes the older AVX-512 implementations in Intel Skylake Server and Intel Ice Lake. They could run this code, but they're known to have overly-eager downclocking when ZMM registers are used. This is the same policy that the crypto and CRC code uses. Results from xor_benchmark on AMD Ryzen 9 9950X (Zen 5): Before: # xor_benchmark: 4096 bytes 16384 bytes # xor_benchmark: 4 disks: 256 GB/s 185 GB/s # xor_benchmark: 5 disks: 233 GB/s 165 GB/s # xor_benchmark: 6 disks: 243 GB/s 174 GB/s # xor_benchmark: 7 disks: 244 GB/s 176 GB/s # xor_benchmark: 8 disks: 265 GB/s 179 GB/s # xor_benchmark: 10 disks: 249 GB/s 175 GB/s # xor_benchmark: 12 disks: 219 GB/s 173 GB/s # xor_benchmark: 15 disks: 206 GB/s 177 GB/s # xor_benchmark: 16 disks: 212 GB/s 180 GB/s # xor_benchmark: 32 disks: 193 GB/s 177 GB/s After: # xor_benchmark: 4096 bytes 16384 bytes # xor_benchmark: 4 disks: 492 GB/s 202 GB/s # xor_benchmark: 5 disks: 436 GB/s 191 GB/s # xor_benchmark: 6 disks: 466 GB/s 193 GB/s # xor_benchmark: 7 disks: 491 GB/s 194 GB/s # xor_benchmark: 8 disks: 513 GB/s 205 GB/s # xor_benchmark: 10 disks: 454 GB/s 199 GB/s # xor_benchmark: 12 disks: 330 GB/s 204 GB/s # xor_benchmark: 15 disks: 266 GB/s 198 GB/s # xor_benchmark: 16 disks: 243 GB/s 177 GB/s # xor_benchmark: 32 disks: 187 GB/s 165 GB/s Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Reviewed-by: David Laight Reviewed-by: Christoph Hellwig Acked-by: Christoph Hellwig Link: https://patch.msgid.link/20260901145740.42337-9-ebiggers@kernel.org --- lib/raid/xor/Makefile | 2 +- lib/raid/xor/x86/xor-avx512.c | 122 ++++++++++++++++++++++++++++++++++ lib/raid/xor/x86/xor_arch.h | 30 ++++++--- 3 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 lib/raid/xor/x86/xor-avx512.c diff --git a/lib/raid/xor/Makefile b/lib/raid/xor/Makefile index 9b0fad459cdb60..e1e3455c219dee 100644 --- a/lib/raid/xor/Makefile +++ b/lib/raid/xor/Makefile @@ -31,7 +31,7 @@ xor-$(CONFIG_SPARC32) += sparc/xor-sparc32.o xor-$(CONFIG_SPARC64) += sparc/xor-sparc64.o sparc/xor-sparc64-glue.o xor-$(CONFIG_S390) += s390/xor.o xor-$(CONFIG_X86_32) += x86/xor-avx.o x86/xor-sse.o x86/xor-mmx.o -xor-$(CONFIG_X86_64) += x86/xor-avx.o x86/xor-sse.o +xor-$(CONFIG_X86_64) += x86/xor-avx.o x86/xor-sse.o x86/xor-avx512.o obj-y += tests/ CFLAGS_xor-neon.o += $(CC_FLAGS_FPU) -I$(src)/$(SRCARCH) diff --git a/lib/raid/xor/x86/xor-avx512.c b/lib/raid/xor/x86/xor-avx512.c new file mode 100644 index 00000000000000..c11d83441875c1 --- /dev/null +++ b/lib/raid/xor/x86/xor-avx512.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AVX-512 optimized implementation of xor_gen() + * + * Copyright 2026 Google LLC + */ + +#include +#include +#include "xor_impl.h" +#include "xor_arch.h" + +/* + * Implementation notes: + * + * Unrolling by the number of buffers (2-5) is very important. + * + * Unrolling by length is less important, especially when using register-indexed + * addressing with negative indices from the end of the buffers. That approach + * results in just two loop control instructions being needed per iteration, + * regardless of the number of buffers. + * + * In fact, benchmarks showed that the 2 and 3 buffer cases require only 2x + * unrolling by length, while the 4 and 5 buffer cases don't require any + * unrolling by length. Benchmarks also showed that the register-indexed + * addressing isn't a bottleneck either; i.e., we can't do any better by + * incrementing the pointers as we go along, even with more unrolling. + */ + +static void xor_avx512_2(long bytes, u8 *p1, const u8 *p2) +{ + long i = -bytes; + + asm volatile("1: vmovdqa64 (%1,%0), %%zmm0\n" + "vmovdqa64 64(%1,%0), %%zmm1\n" + "vpxorq (%2,%0), %%zmm0, %%zmm0\n" + "vpxorq 64(%2,%0), %%zmm1, %%zmm1\n" + "vmovdqa64 %%zmm0, (%1,%0)\n" + "vmovdqa64 %%zmm1, 64(%1,%0)\n" + "add $128, %0\n" + "jnz 1b\n" + : "+&r"(i) + : "r"(p1 + bytes), "r"(p2 + bytes) + : "memory", "cc"); +} + +static void xor_avx512_3(long bytes, u8 *p1, const u8 *p2, const u8 *p3) +{ + long i = -bytes; + + asm volatile("1: vmovdqa64 (%1,%0), %%zmm0\n" + "vmovdqa64 64(%1,%0), %%zmm1\n" + "vmovdqa64 (%2,%0), %%zmm2\n" + "vmovdqa64 64(%2,%0), %%zmm3\n" + "vpternlogq $0x96, (%3,%0), %%zmm2, %%zmm0\n" + "vpternlogq $0x96, 64(%3,%0), %%zmm3, %%zmm1\n" + "vmovdqa64 %%zmm0, (%1,%0)\n" + "vmovdqa64 %%zmm1, 64(%1,%0)\n" + "add $128, %0\n" + "jnz 1b\n" + : "+&r"(i) + : "r"(p1 + bytes), "r"(p2 + bytes), "r"(p3 + bytes) + : "memory", "cc"); +} + +static void xor_avx512_4(long bytes, u8 *p1, const u8 *p2, const u8 *p3, + const u8 *p4) +{ + long i = -bytes; + + asm volatile("1: vmovdqa64 (%1,%0), %%zmm0\n" + "vmovdqa64 (%2,%0), %%zmm1\n" + "vpxorq (%3,%0), %%zmm0, %%zmm0\n" + "vpternlogq $0x96, (%4,%0), %%zmm1, %%zmm0\n" + "vmovdqa64 %%zmm0, (%1,%0)\n" + "add $64, %0\n" + "jnz 1b\n" + : "+&r"(i) + : "r"(p1 + bytes), "r"(p2 + bytes), "r"(p3 + bytes), + "r"(p4 + bytes) + : "memory", "cc"); +} + +static void xor_avx512_5(long bytes, u8 *p1, const u8 *p2, const u8 *p3, + const u8 *p4, const u8 *p5) +{ + long i = -bytes; + + asm volatile("1: vmovdqa64 (%1,%0), %%zmm0\n" + "vmovdqa64 (%2,%0), %%zmm1\n" + "vpternlogq $0x96, (%3,%0), %%zmm1, %%zmm0\n" + "vmovdqa64 (%4,%0), %%zmm1\n" + "vpternlogq $0x96, (%5,%0), %%zmm1, %%zmm0\n" + "vmovdqa64 %%zmm0, (%1,%0)\n" + "add $64, %0\n" + "jnz 1b\n" + : "+&r"(i) + : "r"(p1 + bytes), "r"(p2 + bytes), "r"(p3 + bytes), + "r"(p4 + bytes), "r"(p5 + bytes) + : "memory", "cc"); +} + +DO_XOR_BLOCKS(avx512_inner, xor_avx512_2, xor_avx512_3, xor_avx512_4, + xor_avx512_5); + +/* + * Preconditions: bytes is a nonzero multiple of 512, and all buffers are + * 64-byte aligned. + */ +static void xor_gen_avx512(void *dest, void **srcs, unsigned int src_cnt, + unsigned int bytes) +{ + kernel_fpu_begin(); + xor_gen_avx512_inner(dest, srcs, src_cnt, bytes); + asm volatile("vzeroupper"); + kernel_fpu_end(); +} + +struct xor_block_template xor_block_avx512 = { + .name = "avx512", + .xor_gen = xor_gen_avx512, +}; diff --git a/lib/raid/xor/x86/xor_arch.h b/lib/raid/xor/x86/xor_arch.h index 991abe3f4bbdac..ed5921d2e2aadd 100644 --- a/lib/raid/xor/x86/xor_arch.h +++ b/lib/raid/xor/x86/xor_arch.h @@ -6,21 +6,31 @@ extern struct xor_block_template xor_block_p5_mmx; extern struct xor_block_template xor_block_sse; extern struct xor_block_template xor_block_sse_pf64; extern struct xor_block_template xor_block_avx; +extern struct xor_block_template xor_block_avx512; -/* - * When SSE is available, use it as it can write around L2. We may also be able - * to load into the L1 only depending on how the cpu deals with a load to a line - * that is being prefetched. - * - * When AVX2 is available, force using it as it is better by all measures. - * - * 32-bit without MMX can fall back to the generic routines. - */ static __always_inline void __init arch_xor_init(void) { - if (boot_cpu_has(X86_FEATURE_AVX)) { + if (IS_ENABLED(CONFIG_X86_64) && boot_cpu_has(X86_FEATURE_AVX512F) && + !boot_cpu_has(X86_FEATURE_PREFER_YMM)) { + /* + * Use the AVX-512 code on CPUs that support AVX-512 without + * overly-eager downclocking. On such CPUs the AVX-512 code + * should always work at least as well as the AVX code, so + * runtime selection is unnecessary. + * + * The AVX-512 code can work on X86_32. However, due to lack of + * use case for that, for now it's built only for X86_64. + */ + xor_force(&xor_block_avx512); + } else if (boot_cpu_has(X86_FEATURE_AVX)) { + /* AVX will be the best; no need to try others. */ xor_force(&xor_block_avx); } else if (IS_ENABLED(CONFIG_X86_64) || boot_cpu_has(X86_FEATURE_XMM)) { + /* + * When SSE is available, use it as it can write around L2. We + * may also be able to load into the L1 only depending on how + * the cpu deals with a load to a line that is being prefetched. + */ xor_register(&xor_block_sse); xor_register(&xor_block_sse_pf64); } else if (boot_cpu_has(X86_FEATURE_MMX)) { From 3107a9a3241d6b585cbc08c7035b03fa44e9fb94 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 8 Sep 2026 14:34:58 -0500 Subject: [PATCH 0331/2286] ACPI: utils: Ignore leading root scope prefix in string _UID match Firmware may express an ACPI namespace path used as a _UID with or without the leading root scope character ('\'). For example, an AMD IVRS IVHD ACPI HID device entry may carry a character UID of "\_SB.MHSP" while the corresponding device's _UID evaluates to "_SB.MHSP" (or vice versa). This semantic difference is due to how Windows PnP enumerates and uses devices. acpi_str_uid_match() compared the two strings verbatim, so such entries failed to match on the UID even though they refer to the same object. In the AMD IOMMU case (get_acpihid_device_id()) this caused the exact HID+UID match to be missed and the code to fall through to the HID-only path, spuriously raising a FW_BUG. Skip a single leading '\' on either string before comparing so that paths that differ only by the root scope prefix are treated as a match. The integer _UID path is unaffected. [ rjw: It is sad that making this change needed to be considered at all. That being said, it is also rather unlikely that someone will use a leading backslash as the only difference to distinguish one UID string from antother, so this change is unlikely to break anything. ] Signed-off-by: Mario Limonciello [ rjw: Added separator line to a kerneldoc comment ] [ rjw: Droppend Link: tag with a limited access target ] Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b239..22be27e02682c6 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -830,7 +830,15 @@ static inline bool acpi_str_uid_match(struct acpi_device *adev, const char *uid2 { const char *uid1 = acpi_device_uid(adev); - return uid1 && uid2 && !strcmp(uid1, uid2); + if (!uid1 || !uid2) + return false; + + if (*uid1 == '\\' && uid1[1]) + uid1++; + if (*uid2 == '\\' && uid2[1]) + uid2++; + + return !strcmp(uid1, uid2); } static inline bool acpi_int_uid_match(struct acpi_device *adev, u64 uid2) @@ -857,6 +865,10 @@ static inline bool acpi_int_uid_match(struct acpi_device *adev, u64 uid2) * * Matches UID in @adev with given @uid2. * + * If both the UID in @adev and @uid2 are strings, they are compared + * after optionally skipping a leading backslash ('\') if the given + * string contains additional characters. + * * Returns: %true if matches, %false otherwise. */ #define acpi_dev_uid_match(adev, uid2) \ From d51e53fbb4c25d865852a01c5f15f3c4dad618d9 Mon Sep 17 00:00:00 2001 From: Qiuxu Zhuo Date: Wed, 9 Sep 2026 15:16:15 +0000 Subject: [PATCH 0332/2286] MAINTAINERS: Update Qiuxu Zhuo's email address Following a job transition, update my email address to my current work address. I'm willing to continue reviewing EDAC patches. Signed-off-by: Qiuxu Zhuo Signed-off-by: Borislav Petkov (AMD) Acked-by: Tony Luck Link: https://patch.msgid.link/20260909151531.69074-1-qzhuo@nvidia.com --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb0777052..f2a3dc643cf627 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9485,7 +9485,7 @@ F: drivers/edac/ie31200_edac.c EDAC-IGEN6 M: Tony Luck -R: Qiuxu Zhuo +R: Qiuxu Zhuo L: linux-edac@vger.kernel.org S: Maintained F: drivers/edac/igen6_edac.c @@ -9524,7 +9524,7 @@ F: drivers/edac/qcom_edac.c EDAC-SBRIDGE M: Tony Luck -R: Qiuxu Zhuo +R: Qiuxu Zhuo L: linux-edac@vger.kernel.org S: Maintained F: drivers/edac/sb_edac.c From f8113f311cf8be566d809598e967bf5ceb35b567 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Sep 2026 18:39:18 +0200 Subject: [PATCH 0333/2286] ACPI: bus: Reduce runtime memory footprint of struct acpi_device The ACPI handle is heavily used by the ACPI code. Making it as the first member in the struct acpi_device reduces code size. For instance, the drivers/acpi/scan.o (with `make allyesconfig`) gives this statistics: add/remove: 0/0 grow/shrink: 3/8 up/down: 50/-169 (-119) Function old new delta acpi_device_add 2294 2319 +25 acpi_add_single_object 6523 6544 +21 acpi_dev_get_next_consumer_dev 602 606 +4 acpi_tie_acpi_dev 169 165 -4 acpi_scan_drop_device 319 315 -4 acpi_dev_clear_dependencies 1151 1147 -4 acpi_bus_attach 2898 2894 -4 acpi_dma_get_range 1420 1415 -5 acpi_init_device_object 5497 5485 -12 acpi_bus_check_add 1940 1923 -17 acpi_device_hotplug 3407 3288 -119 Total: Before=77509, After=77390, chg -0.15% Signed-off-by: Andy Shevchenko [ rjw: Changelog tweaks ] Link: https://patch.msgid.link/20260909163918.368017-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b239..64ae66ca456efe 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -435,9 +435,9 @@ struct acpi_device_software_nodes { /* Device */ struct acpi_device { + acpi_handle handle; /* no handle for fixed hardware */ u32 pld_crc; int device_type; - acpi_handle handle; /* no handle for fixed hardware */ struct fwnode_handle fwnode; struct list_head wakeup_list; struct list_head del_list; From d98d8b50a87368880a58d04b48e5f66ba9d7cdc9 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Mon, 31 Aug 2026 14:42:35 -0500 Subject: [PATCH 0334/2286] thermal: of: Match trip property helper types The thermal-zone binding defines "temperature" as a signed int32 value and "hysteresis" as an unsigned int32 value. Using helpers with matching types avoids dt_property_check mismatches and preserves the signed interpretation needed for trips below zero. Read "temperature" with the signed helper and keep "hysteresis" on the unsigned helper using separate typed temporaries before storing them in the trip structure. Signed-off-by: Rob Herring (Arm) Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260831194235.1182138-1-robh@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_of.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae33..deaae7a995c503 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -63,22 +63,23 @@ static int thermal_of_get_trip_type(struct device_node *np, static int thermal_of_populate_trip(struct device_node *np, struct thermal_trip *trip) { - int prop; + u32 hysteresis; + s32 temperature; int ret; - ret = of_property_read_u32(np, "temperature", &prop); + ret = of_property_read_s32(np, "temperature", &temperature); if (ret < 0) { pr_err("missing temperature property\n"); return ret; } - trip->temperature = prop; + trip->temperature = temperature; - ret = of_property_read_u32(np, "hysteresis", &prop); + ret = of_property_read_u32(np, "hysteresis", &hysteresis); if (ret < 0) { pr_err("missing hysteresis property\n"); return ret; } - trip->hysteresis = prop; + trip->hysteresis = hysteresis; ret = thermal_of_get_trip_type(np, &trip->type); if (ret < 0) { From cd1f696de880c41ef0983d2c54999384a5cbf393 Mon Sep 17 00:00:00 2001 From: Benno Lossin Date: Fri, 28 Aug 2026 02:44:30 +0000 Subject: [PATCH 0335/2286] rust: lsm: replace `core::mem::zeroed` with `pin_init::zeroed` All types in `bindings` implement `Zeroable` if they can, so use `pin_init::zeroed` instead of relying on `unsafe` code. If this ends up not compiling in the future, something in bindgen or on the C side changed and is most likely incorrect. Link: https://lore.kernel.org/r/20250814093046.2071971-8-lossin@kernel.org Link: https://github.com/Rust-for-Linux/linux/issues/1189 Signed-off-by: Benno Lossin Reviewed-by: Alexandre Courbot Signed-off-by: Nils Lehnen Reviewed-by: Alice Ryhl Reviewed-by: Miguel Ojeda [PM: subject tweak] Signed-off-by: Paul Moore --- rust/kernel/security.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/kernel/security.rs b/rust/kernel/security.rs index 9d271695265fb4..4dc3eba6ce84d8 100644 --- a/rust/kernel/security.rs +++ b/rust/kernel/security.rs @@ -62,8 +62,7 @@ impl SecurityCtx { /// Get the security context given its id. #[inline] pub fn from_secid(secid: u32) -> Result { - // SAFETY: `struct lsm_context` can be initialized to all zeros. - let mut ctx: bindings::lsm_context = unsafe { core::mem::zeroed() }; + let mut ctx: bindings::lsm_context = pin_init::zeroed(); // SAFETY: Just a C FFI call. The pointer is valid for writes. to_result(unsafe { bindings::security_secid_to_secctx(secid, &mut ctx) })?; From db40a7877cf14fc34fa27ee479eb6561d10648cf Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Tue, 18 Aug 2026 23:11:51 -0500 Subject: [PATCH 0336/2286] cpufreq/amd-pstate: Add per SoC and per core type EPP tuning values On newer SoCs the behavior of the platform has changed, and using the same EPP values for everything will yield worse results than expected. Add a x86_cpu_id match table that allows setting policies on a per-SoC/per-core type basis. If a SoC is missing, fallback to legacy values that are used. Changes also: - Change epp_values array and amd_pstate_cpu_epp_values() from unsigned int to u8 - Add static_assert to ensure epp_values has rows for all CPU types - Fix show_energy_performance_preference() loop to exclude uninitialized EPP_INDEX_CUSTOM and EPP_INDEX_DYNAMIC slots - Add debug print statements - Remove amd_pstate_get_epp_from_platform_profile and inline the logic - Fix cleanup path in amd_pstate_set_dynamic_epp - Change show_energy_performance_preference to use for loop with preference tracking - Add debug prints in amd_pstate_init_epp_values Behavioral change: show_energy_performance_preference() now returns a numeric EPP value via sysfs when the cached EPP does not match any named preference, instead of returning -EINVAL as before. This provides visibility into custom EPP values set by hardware or BIOS. No new platforms are added in this commit. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 183 ++++++++++++++++++++++++++++------- drivers/cpufreq/amd-pstate.h | 2 + 2 files changed, 148 insertions(+), 37 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index ae1673b36ac45b..8047f168b6d758 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -55,10 +55,10 @@ #define AMD_PSTATE_TRANSITION_DELAY 1000 #define AMD_PSTATE_FAST_CPPC_TRANSITION_DELAY 600 -#define AMD_CPPC_EPP_PERFORMANCE 0x00 -#define AMD_CPPC_EPP_BALANCE_PERFORMANCE 0x80 -#define AMD_CPPC_EPP_BALANCE_POWERSAVE 0xBF -#define AMD_CPPC_EPP_POWERSAVE 0xFF +#define AMD_CPPC_EPP_LEGACY_PERFORMANCE 0x00 +#define AMD_CPPC_EPP_LEGACY_BALANCE_PERFORMANCE 0x80 +#define AMD_CPPC_EPP_LEGACY_BALANCE_POWERSAVE 0xBF +#define AMD_CPPC_EPP_LEGACY_POWERSAVE 0xFF static const char * const amd_pstate_mode_string[] = { [AMD_PSTATE_UNDEFINED] = "undefined", @@ -129,14 +129,86 @@ static const char * const energy_perf_strings[] = { }; static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); -static unsigned int epp_values[] = { - [EPP_INDEX_DEFAULT] = 0, - [EPP_INDEX_PERFORMANCE] = AMD_CPPC_EPP_PERFORMANCE, - [EPP_INDEX_BALANCE_PERFORMANCE] = AMD_CPPC_EPP_BALANCE_PERFORMANCE, - [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_BALANCE_POWERSAVE, - [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_POWERSAVE, +/* + * The numeric EPP value programmed for each named preference. First dimension + * is CPU type (TOPO_CPU_TYPE_ANY for non-hybrid, TOPO_CPU_TYPE_PERFORMANCE/ + * EFFICIENCY/LOW_POWER for hybrid). The initializer holds the legacy values + * used as the fallback on any platform not listed in amd_pstate_epp_soc_ids[]; + * amd_pstate_init_epp_values() overwrites slots at boot when the running SoC + * has a per-SoC (and potentially per-CPU-type) override. + */ +static u8 epp_values[][EPP_INDEX_MAX] = { + /* + * Initialize all CPU types to legacy defaults. + * amd_pstate_init_epp_values() will fix these up + * based on the platform during boot. + */ + [TOPO_CPU_TYPE_ANY ... TOPO_CPU_TYPE_LOW_POWER] = { + [EPP_INDEX_DEFAULT] = 0, + [EPP_INDEX_PERFORMANCE] = AMD_CPPC_EPP_LEGACY_PERFORMANCE, + [EPP_INDEX_BALANCE_PERFORMANCE] = AMD_CPPC_EPP_LEGACY_BALANCE_PERFORMANCE, + [EPP_INDEX_BALANCE_POWERSAVE] = AMD_CPPC_EPP_LEGACY_BALANCE_POWERSAVE, + [EPP_INDEX_POWERSAVE] = AMD_CPPC_EPP_LEGACY_POWERSAVE, + }, +}; +static_assert(ARRAY_SIZE(epp_values) == TOPO_CPU_TYPE_LOW_POWER + 1, + "epp_values must have entries for all CPU types up to TOPO_CPU_TYPE_LOW_POWER"); + +/* + * Get the EPP value row for a given CPU, accounting for hybrid CPU types. + * Non-hybrid systems use TOPO_CPU_TYPE_ANY; hybrid systems use the CPU's + * actual type (PERFORMANCE/EFFICIENCY/LOW_POWER). + */ +static inline u8 *amd_pstate_cpu_epp_values(enum x86_topology_cpu_type cpu_type) +{ + switch (cpu_type) { + case TOPO_CPU_TYPE_PERFORMANCE: + case TOPO_CPU_TYPE_EFFICIENCY: + case TOPO_CPU_TYPE_LOW_POWER: + return epp_values[cpu_type]; + default: + return epp_values[TOPO_CPU_TYPE_ANY]; + } +} + +/** + * struct amd_pstate_epp_values - EPP values for the four named preferences + * @performance: value for the "performance" preference + * @balance_performance: value for the "balance_performance" preference + * @balance_power: value for the "balance_power" preference + * @power: value for the "power" preference + */ +struct amd_pstate_epp_values { + u8 performance; + u8 balance_performance; + u8 balance_power; + u8 power; +}; + +/** + * struct amd_pstate_epp_soc - per-CPU-type EPP overrides for hybrid systems + * @performance_core: values for TOPO_CPU_TYPE_PERFORMANCE cores + * @efficiency_core: values for TOPO_CPU_TYPE_EFFICIENCY cores + * @low_power_core: values for TOPO_CPU_TYPE_LOW_POWER cores + * + * Referenced from amd_pstate_epp_soc_ids[] to give a hybrid platform its own + * numeric EPP values for the four named preferences, with distinct values per + * CPU type. Non-hybrid systems are not listed in the table and always use the + * legacy defaults. + */ +struct amd_pstate_epp_soc { + struct amd_pstate_epp_values performance_core; + struct amd_pstate_epp_values efficiency_core; + struct amd_pstate_epp_values low_power_core; +}; + +/* + * Per-CPU-type EPP overrides for hybrid systems. Only hybrid SoCs should be + * listed here; non-hybrid systems always use the legacy defaults. + */ +static const struct x86_cpu_id amd_pstate_epp_soc_ids[] __initconst = { + {} }; -static_assert(ARRAY_SIZE(epp_values) == EPP_INDEX_MAX - 2); typedef int (*cppc_mode_transition_fn)(int); @@ -1055,6 +1127,7 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) return -ENOMEM; cpudata->cpu = policy->cpu; + cpudata->cpu_type = cpu_data(policy->cpu).topo.cpu_type; ret = amd_pstate_init_perf(cpudata); if (ret) @@ -1182,13 +1255,16 @@ static int amd_pstate_power_supply_notifier(struct notifier_block *nb, static int amd_pstate_get_epp_from_platform_profile(struct cpufreq_policy *policy, enum platform_profile_option profile) { + struct amd_cpudata *cpudata = policy->driver_data; + u8 *values = amd_pstate_cpu_epp_values(cpudata->cpu_type); + switch (profile) { case PLATFORM_PROFILE_PERFORMANCE: - return AMD_CPPC_EPP_PERFORMANCE; + return values[EPP_INDEX_PERFORMANCE]; case PLATFORM_PROFILE_BALANCED: return amd_pstate_get_balanced_epp(policy); case PLATFORM_PROFILE_LOW_POWER: - return AMD_CPPC_EPP_POWERSAVE; + return values[EPP_INDEX_POWERSAVE]; default: break; } @@ -1398,6 +1474,7 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, const char *buf, size_t count) { struct amd_cpudata *cpudata = policy->driver_data; + u8 *values = amd_pstate_cpu_epp_values(cpudata->cpu_type); ssize_t ret; bool raw_epp = false; u8 epp; @@ -1432,12 +1509,13 @@ ssize_t store_energy_performance_preference(struct cpufreq_policy *policy, } if (ret) - epp = epp_values[ret]; + epp = values[ret]; else epp = cpudata->epp_default_dc; } - if (epp > 0 && cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) { + if (epp > 0 && epp != values[EPP_INDEX_PERFORMANCE] && + cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) { pr_debug("EPP cannot be set under performance policy\n"); return -EBUSY; } @@ -1462,34 +1540,31 @@ EXPORT_SYMBOL_FOR_PSTATE_UT(store_energy_performance_preference); ssize_t show_energy_performance_preference(struct cpufreq_policy *policy, char *buf) { struct amd_cpudata *cpudata = policy->driver_data; - u8 preference, epp; + u8 *values = amd_pstate_cpu_epp_values(cpudata->cpu_type); + u8 epp; + int i; epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); if (!cpudata->dynamic_epp && cpudata->raw_epp) return sysfs_emit(buf, "%u\n", epp); - switch (epp) { - case AMD_CPPC_EPP_PERFORMANCE: - preference = EPP_INDEX_PERFORMANCE; - break; - case AMD_CPPC_EPP_BALANCE_PERFORMANCE: - preference = EPP_INDEX_BALANCE_PERFORMANCE; - break; - case AMD_CPPC_EPP_BALANCE_POWERSAVE: - preference = EPP_INDEX_BALANCE_POWERSAVE; - break; - case AMD_CPPC_EPP_POWERSAVE: - preference = EPP_INDEX_POWERSAVE; - break; - default: - return -EINVAL; - } + /* + * Map the cached EPP value back to a named preference. Skip the + * "default" slot (index 0) so an EPP of 0 reports as "performance". + * Stop at POWERSAVE; CUSTOM and DYNAMIC are not initialized in epp_values. + */ + for (i = EPP_INDEX_PERFORMANCE; i <= EPP_INDEX_POWERSAVE; i++) { + const char *name = energy_perf_strings[i]; - if (cpudata->dynamic_epp) - return sysfs_emit(buf, "dynamic(profile:%s)\n", energy_perf_strings[preference]); + if (epp == values[i]) { + if (cpudata->dynamic_epp) + return sysfs_emit(buf, "dynamic(profile:%s)\n", name); + return sysfs_emit(buf, "%s\n", name); + } + } - return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]); + return sysfs_emit(buf, "%u\n", epp); } EXPORT_SYMBOL_FOR_PSTATE_UT(show_energy_performance_preference); @@ -1881,6 +1956,7 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) return -ENOMEM; cpudata->cpu = policy->cpu; + cpudata->cpu_type = cpu_data(policy->cpu).topo.cpu_type; ret = amd_pstate_init_perf(cpudata); if (ret) @@ -1931,9 +2007,11 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) cpudata->epp_default_ac = cpudata->epp_default_dc = default_epp; cpudata->current_profile = PLATFORM_PROFILE_PERFORMANCE; } else { + u8 *values = amd_pstate_cpu_epp_values(cpudata->cpu_type); + policy->policy = CPUFREQ_POLICY_POWERSAVE; - cpudata->epp_default_ac = AMD_CPPC_EPP_PERFORMANCE; - cpudata->epp_default_dc = AMD_CPPC_EPP_BALANCE_PERFORMANCE; + cpudata->epp_default_ac = values[EPP_INDEX_PERFORMANCE]; + cpudata->epp_default_dc = values[EPP_INDEX_BALANCE_PERFORMANCE]; cpudata->current_profile = PLATFORM_PROFILE_BALANCED; } @@ -2232,6 +2310,34 @@ static bool amd_cppc_supported(void) return true; } +/* + * Resolve the numeric EPP values for hybrid systems. Only hybrid SoCs are listed + * in amd_pstate_epp_soc_ids[]; non-hybrid systems always use the legacy defaults. + */ +static inline void __init amd_pstate_set_epp_values(enum x86_topology_cpu_type type, + const struct amd_pstate_epp_values *core) +{ + epp_values[type][EPP_INDEX_PERFORMANCE] = core->performance; + epp_values[type][EPP_INDEX_BALANCE_PERFORMANCE] = core->balance_performance; + epp_values[type][EPP_INDEX_BALANCE_POWERSAVE] = core->balance_power; + epp_values[type][EPP_INDEX_POWERSAVE] = core->power; +} + +static void __init amd_pstate_init_epp_values(void) +{ + const struct x86_cpu_id *id = x86_match_cpu(amd_pstate_epp_soc_ids); + const struct amd_pstate_epp_soc *soc; + + if (!id || !id->driver_data) + return; + + soc = (const struct amd_pstate_epp_soc *)id->driver_data; + + amd_pstate_set_epp_values(TOPO_CPU_TYPE_PERFORMANCE, &soc->performance_core); + amd_pstate_set_epp_values(TOPO_CPU_TYPE_EFFICIENCY, &soc->efficiency_core); + amd_pstate_set_epp_values(TOPO_CPU_TYPE_LOW_POWER, &soc->low_power_core); +} + static int __init amd_pstate_init(void) { struct device *dev_root; @@ -2259,6 +2365,9 @@ static int __init amd_pstate_init(void) /* check if this machine need CPPC quirks */ dmi_check_system(amd_pstate_quirks_table); + /* resolve per-SoC EPP values for the named preferences */ + amd_pstate_init_epp_values(); + /* * determine the driver mode from the command line or kernel config. * If no command line input is provided, cppc_state will be AMD_PSTATE_UNDEFINED. diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index 578465187b7ae9..c7189d177b81a5 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -146,6 +146,8 @@ struct amd_cpudata { enum platform_profile_option current_profile; struct device *ppdev; char *profile_name; + + enum x86_topology_cpu_type cpu_type; }; /* From 114a157e8d30601d024b53564a0fe54202f6567f Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Tue, 18 Aug 2026 23:12:16 -0500 Subject: [PATCH 0337/2286] cpufreq/amd-pstate: Add EPP tunings for Zen6 client platforms Zen6 client platforms perform better with individual tunings for different core types. Add initial EPP tuning values for those platforms. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Mario Limonciello (AMD) --- drivers/cpufreq/amd-pstate.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 8047f168b6d758..4344cc93904b5c 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -206,7 +206,33 @@ struct amd_pstate_epp_soc { * Per-CPU-type EPP overrides for hybrid systems. Only hybrid SoCs should be * listed here; non-hybrid systems always use the legacy defaults. */ +static const struct amd_pstate_epp_soc epp_soc_zen6_client __initconst = { + .performance_core = { + .performance = 25, + .balance_performance = 51, + .balance_power = 64, + .power = 64, + }, + .efficiency_core = { + .performance = 25, + .balance_performance = 51, + .balance_power = 64, + .power = 115, + }, + .low_power_core = { + .performance = 25, + .balance_performance = 51, + .balance_power = 64, + .power = 115, + }, +}; + static const struct x86_cpu_id amd_pstate_epp_soc_ids[] __initconst = { + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x80, &epp_soc_zen6_client), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x81, &epp_soc_zen6_client), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x84, &epp_soc_zen6_client), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0x85, &epp_soc_zen6_client), + X86_MATCH_VENDOR_FAM_MODEL(AMD, 0x1A, 0xe0, &epp_soc_zen6_client), {} }; From 350de8c394d4e61df6ebe31d8ddd928a69766fd3 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 31 Aug 2026 16:39:47 -0500 Subject: [PATCH 0338/2286] cpufreq/amd-pstate: Show a warning if missing EPP tunings EPP tunings are going to be more important going forward. Show a warning if on a heterogenous platform without them. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Suggested-by: Christian Ludloff Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 4344cc93904b5c..83960a6336b9a6 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -2354,8 +2354,12 @@ static void __init amd_pstate_init_epp_values(void) const struct x86_cpu_id *id = x86_match_cpu(amd_pstate_epp_soc_ids); const struct amd_pstate_epp_soc *soc; - if (!id || !id->driver_data) + if (!id || !id->driver_data) { + if (cpu_feature_enabled(X86_FEATURE_ZEN6) && + cpu_feature_enabled(X86_FEATURE_AMD_HTR_CORES)) + pr_warn_once("No EPP tunings found for platform\n"); return; + } soc = (const struct amd_pstate_epp_soc *)id->driver_data; From c8663e0457e6e6c72006478f4b6da99060030ec1 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 03:07:06 +0200 Subject: [PATCH 0339/2286] cpufreq/amd-pstate: Remove obsolete amd_dynamic_epp documentation The amd_dynamic_epp command-line parameter was removed. Dynamic EPP is now selected through the per-policy energy_performance_preference attribute, which is already documented. Fixes: 32692fcf61ed ("cpufreq/amd-pstate: Remove "amd_dynamic_epp" cmdline and "dynamic_epp" sysfs") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: K Prateek Nayak Reviewed-by: Randy Dunlap Link: https://lore.kernel.org/r/20260905010706.57380-1-kmehltretter@gmail.com Signed-off-by: Mario Limonciello --- Documentation/admin-guide/kernel-parameters.txt | 7 ------- Documentation/admin-guide/pm/amd-pstate.rst | 6 ------ 2 files changed, 13 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..378ba224948c01 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -501,13 +501,6 @@ Kernel parameters disable Disable amd-pstate preferred core. - amd_dynamic_epp= - [X86] - disable - Disable amd-pstate dynamic EPP. - enable - Enable amd-pstate dynamic EPP. - amijoy.map= [HW,JOY] Amiga joystick support Map of devices attached to JOY0DAT and JOY1DAT Format: , diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst index 6fd72ba52df20d..4c5d3d8acf645b 100644 --- a/Documentation/admin-guide/pm/amd-pstate.rst +++ b/Documentation/admin-guide/pm/amd-pstate.rst @@ -481,12 +481,6 @@ For systems that support ``amd-pstate`` preferred core, the core rankings will always be advertised by the platform. But OS can choose to ignore that via the kernel parameter ``amd_prefcore=disable``. -``amd_dynamic_epp`` - -When AMD pstate is in auto mode, dynamic EPP will control whether the kernel -autonomously changes the EPP mode. The default is disabled. It can be enabled -with the kernel parameter ``amd_dynamic_epp=enable``. - User Space Interface in ``sysfs`` - General =========================================== From 74ea55a6ecb3fd69b47b2b8a6f6979a6c79c7ce7 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Tue, 8 Sep 2026 20:58:26 +0200 Subject: [PATCH 0340/2286] block: store GPT attributes as a raw value struct _gpt_entry_attributes currently models the GPT partition entry attributes field with bitfields. This is broken on machines using the __BIG_ENDIAN_BITFIELD ABI because GPT always stores the attributes on disk as a 64-bit little-endian. No current code consumes individual fields from that structure. So just remove struct _gpt_entry_attributes entirely and replace it by an __le64 value in struct _gpt_entry instead. This lets users apply endian-aware masks explicitly when inspecting attribute bits. Signed-off-by: Vincent Mailhol Acked-by: Davidlohr Bueso Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260908-fix_gpt_entry_attributes-v1-1-a4dcb417e8fc@kernel.org Signed-off-by: Jens Axboe --- block/partitions/efi.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/block/partitions/efi.h b/block/partitions/efi.h index 84b9f36b9e4797..1f56f93b28048f 100644 --- a/block/partitions/efi.h +++ b/block/partitions/efi.h @@ -75,18 +75,12 @@ typedef struct _gpt_header { */ } __packed gpt_header; -typedef struct _gpt_entry_attributes { - u64 required_to_function:1; - u64 reserved:47; - u64 type_guid_specific:16; -} __packed gpt_entry_attributes; - typedef struct _gpt_entry { efi_guid_t partition_type_guid; efi_guid_t unique_partition_guid; __le64 starting_lba; __le64 ending_lba; - gpt_entry_attributes attributes; + __le64 attributes; __le16 partition_name[72/sizeof(__le16)]; } __packed gpt_entry; From 83bd31c2840b3640ba08b7afac2908bbb19c4b2c Mon Sep 17 00:00:00 2001 From: Magnus Lindholm Date: Wed, 2 Sep 2026 20:38:56 +0200 Subject: [PATCH 0341/2286] alpha: enable GENERIC_ENTRY Wire Alpha into the generic entry code for syscall entry/exit and return-to-user handling, while keeping the low-level PALcode return paths Alpha-specific. Move most of the syscall entry/exit logic out of entSys and into C helpers built around the generic entry API. Syscall entry now uses syscall_enter_from_user_mode(), records Alpha-local syscall metadata in thread_info, handles the ptrace/seccomp skip decision, and selects the syscall table target in C. The final target call remains in entry.S so Alpha can preserve its existing syscall ABI and assembly syscall-table wrappers. On return from syscalls, finish Alpha's r0/r19 result encoding and skipped-syscall restart handling in C before calling syscall_exit_to_user_mode(). Non-syscall returns to user mode use a separate alpha_exit_to_user_mode() helper, which disables interrupts, runs irqentry_exit_to_user_mode_prepare(), and then enters the common exit_to_user_mode() path. Keep the remaining PALcode restore handling in assembly. In particular, kernel-mode returns still need Alpha-specific lockdep IRQ-state annotation based on the saved processor status, while user-mode returns are handed to the generic exit-to-user code. Add the generic-entry support bits needed by common code, including thread_info.syscall_work, syscall trace support, ptrace sysemu request numbers, and arch_syscall_is_vdso_sigreturn(). This has been tested by booting Alpha with GENERIC_ENTRY enabled, checking lockdep IRQ-state accounting, running fork/clone-heavy package builds, and running the seccomp as well as strace test suites. Reviewed-by: Matt Turner Signed-off-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260902184058.198679-2-linmag7@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 2 + arch/alpha/include/asm/entry-common.h | 14 ++ arch/alpha/include/asm/ptrace.h | 14 +- arch/alpha/include/asm/stacktrace.h | 20 ++ arch/alpha/include/asm/syscall.h | 11 +- arch/alpha/include/asm/thread_info.h | 30 +-- arch/alpha/kernel/asm-offsets.c | 5 + arch/alpha/kernel/entry.S | 322 +++++++------------------- arch/alpha/kernel/irq_alpha.c | 14 +- arch/alpha/kernel/proto.h | 9 +- arch/alpha/kernel/ptrace.c | 130 +++++------ arch/alpha/kernel/signal.c | 164 ++++++++++--- 12 files changed, 370 insertions(+), 365 deletions(-) create mode 100644 arch/alpha/include/asm/entry-common.h create mode 100644 arch/alpha/include/asm/stacktrace.h diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index e53ef2d8846360..51a7391869dd65 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -2,6 +2,8 @@ config ALPHA bool default y + select GENERIC_ENTRY + select HAVE_SYSCALL_TRACEPOINTS select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI diff --git a/arch/alpha/include/asm/entry-common.h b/arch/alpha/include/asm/entry-common.h new file mode 100644 index 00000000000000..a811c73454d28d --- /dev/null +++ b/arch/alpha/include/asm/entry-common.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ARCH_ALPHA_ENTRY_COMMON_H +#define ARCH_ALPHA_ENTRY_COMMON_H + +#include /* For on_thread_stack() */ +#include + +#define arch_exit_to_user_mode_work arch_exit_to_user_mode_work + +static __always_inline void arch_exit_to_user_mode_work(struct pt_regs *regs, + unsigned long ti_work) +{ +} +#endif diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h index 8e0a589e2d15b4..430e8dc27ff771 100644 --- a/arch/alpha/include/asm/ptrace.h +++ b/arch/alpha/include/asm/ptrace.h @@ -3,7 +3,7 @@ #define _ASMAXP_PTRACE_H #include - +#include #define arch_has_single_step() (1) #define user_mode(regs) (((regs)->ps & 8) != 0) @@ -17,7 +17,9 @@ #define current_pt_regs() \ ((struct pt_regs *) ((char *)current_thread_info() + 2*PAGE_SIZE) - 1) -#define force_successful_syscall_return() (current_pt_regs()->r0 = 0) +#define force_successful_syscall_return() \ + (current_thread_info()->syscall_meta \ + |= ALPHA_SYSCALL_META_FORCE_SUCCESS) static inline unsigned long regs_return_value(struct pt_regs *regs) { @@ -31,4 +33,12 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs) return regs->usp; } +static __always_inline bool regs_irqs_disabled(struct pt_regs *regs) +{ + return arch_irqs_disabled_flags(regs->ps); +} + +/* Syscall emulation defines */ +#define PTRACE_SYSEMU 0x1d +#define PTRACE_SYSEMU_SINGLESTEP 0x1e #endif diff --git a/arch/alpha/include/asm/stacktrace.h b/arch/alpha/include/asm/stacktrace.h new file mode 100644 index 00000000000000..f006d6f00fd09c --- /dev/null +++ b/arch/alpha/include/asm/stacktrace.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_ALPHA_STACKTRACE_H +#define _ASM_ALPHA_STACKTRACE_H + +#include +#include + +#include +#include +#include + +static __always_inline bool on_thread_stack(void) +{ + unsigned long base = (unsigned long)current->stack; + unsigned long sp = (unsigned long)current_stack_pointer; + + return !((base ^ sp) & ~(THREAD_SIZE - 1)); +} + +#endif /* _ASM_ALPHA_STACKTRACE_H */ diff --git a/arch/alpha/include/asm/syscall.h b/arch/alpha/include/asm/syscall.h index 1e78cbd46fafcd..c1394910f584f9 100644 --- a/arch/alpha/include/asm/syscall.h +++ b/arch/alpha/include/asm/syscall.h @@ -8,6 +8,8 @@ #include #include +extern void *sys_call_table[]; + static inline int syscall_get_arch(struct task_struct *task) { return AUDIT_ARCH_ALPHA; @@ -104,10 +106,17 @@ static inline void syscall_set_return_value(struct task_struct *task, } /* Restore the original syscall nr after seccomp/ptrace modified regs->r1. */ + static inline void syscall_rollback(struct task_struct *task, struct pt_regs *regs) { - regs->r1 = regs->r2; + unsigned long nr = task_thread_info(task)->syscall_saved_nr; + + regs->r1 = nr; } +static inline bool arch_syscall_is_vdso_sigreturn(struct pt_regs *regs) +{ + return false; +} #endif /* _ASM_ALPHA_SYSCALL_H */ diff --git a/arch/alpha/include/asm/thread_info.h b/arch/alpha/include/asm/thread_info.h index 1552ecca852073..d781ac79106dd1 100644 --- a/arch/alpha/include/asm/thread_info.h +++ b/arch/alpha/include/asm/thread_info.h @@ -12,16 +12,21 @@ #endif #ifndef __ASSEMBLER__ + struct thread_info { struct pcb_struct pcb; /* palcode state */ struct task_struct *task; /* main task structure */ - unsigned int flags; /* low level flags */ + unsigned long flags; /* low level flags */ + unsigned long syscall_work; /* SYSCALL_WORK_* flags */ unsigned int ieee_state; /* see fpu.h */ unsigned cpu; /* current CPU */ - int preempt_count; /* 0 => preemptable, <0 => BUG */ + int preempt_count; /* 0 => preemptable, <0 => BUG */ unsigned int status; /* thread-synchronous flags */ + unsigned long syscall_saved_r19; + unsigned long syscall_meta; + unsigned long syscall_saved_nr; int bpt_nsaved; unsigned long bpt_addr[2]; /* breakpoint handling */ @@ -50,6 +55,9 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define THREAD_SIZE_ORDER 1 #define THREAD_SIZE (2*PAGE_SIZE) +#define ALPHA_SYSCALL_META_SKIP 0x2 +#define ALPHA_SYSCALL_META_FORCE_SUCCESS 0x4 + /* * Thread information flags: * - these are process state flags and used from assembly @@ -68,6 +76,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define TIF_SECCOMP 6 /* seccomp syscall filtering active */ #define TIF_SYSCALL_TRACEPOINT 7 /* syscall tracepoint instrumentation */ #define TIF_DIE_IF_KERNEL 9 /* dik recursion lock */ +#define TIF_UPROBE 10 /* uprobe breakpoint or singlestep */ #define TIF_MEMDIE 13 /* is terminating due to OOM killer */ #define TIF_POLLING_NRFLAG 14 /* idle is polling for TIF_NEED_RESCHED */ @@ -80,22 +89,7 @@ register unsigned long *current_stack_pointer __asm__ ("$30"); #define _TIF_SECCOMP (1< restart allowed - * $26 = 1 => restart NOT allowed - * $18 = preserved syscall nr (regs->r2) if restart allowed, else 0 - */ -.macro SYSCALL_SKIP_RETURN_RESTART_GATE - /* Fix up invalid "-1 success" return state. */ - ldq $19, 72($sp) /* a3 */ - bne $19, 1f /* already error => skip fixup */ - - ldq $20, 0($sp) /* r0 */ - lda $21, -1($31) - cmpeq $20, $21, $22 - beq $22, 1f /* r0 != -1 => skip fixup */ - - - lda $20, ENOSYS($31) - stq $20, 0($sp) /* r0 = ENOSYS */ - lda $19, 1($31) - stq $19, 72($sp) /* a3 = 1 */ -1: - /* Restart gating: success is never restartable here. */ - ldq $19, 72($sp) /* a3 */ - beq $19, 3f /* success => not restartable */ - - ldq $20, 0($sp) /* r0 (positive errno if a3==1) */ - lda $21, ERESTARTSYS($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTARTNOINTR($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTARTNOHAND($31) - cmpeq $20, $21, $22 - bne $22, 2f - lda $21, ERESTART_RESTARTBLOCK($31) - cmpeq $20, $21, $22 - bne $22, 2f - -3: /* Not a restart code (or success) => restart NOT allowed. */ - addq $31, 1, $26 /* $26=1 => restart NOT allowed */ - mov 0, $18 - br 4f - -2: /* Restart allowed. */ - ldq $18, 16($sp) /* preserved syscall nr (regs->r2) */ - mov $31, $26 /* $26=0 => restart allowed */ - br 4f -4: -.endm - -.macro LOCKDEP_HARDIRQS_ON_RESTORE +.macro LOCKDEP_HARDIRQS_ON_RESTORE psreg #ifdef CONFIG_PROVE_LOCKING /* a0 = saved PS */ - ldq $16, SP_OFF($sp) + ldq $16, \psreg /* a1 = callsite IP for lockdep */ lda $17, 1f @@ -248,7 +191,7 @@ CFI_START_OSF_FRAME entInt SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $19 jsr $31, do_entInt @@ -257,7 +200,7 @@ CFI_END_OSF_FRAME entInt CFI_START_OSF_FRAME entArith SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $18 jsr $31, do_entArith @@ -305,13 +248,13 @@ CFI_START_OSF_FRAME entMM .cfi_restore $15 .cfi_adjust_cfa_offset -64 /* finish up the syscall as normal. */ - br ret_from_sys_call + br ret_from_exception CFI_END_OSF_FRAME entMM CFI_START_OSF_FRAME entIF SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $17 jsr $31, do_entIF @@ -440,7 +383,7 @@ CFI_START_OSF_FRAME entUna .cfi_restore $28 .cfi_restore $29 .cfi_adjust_cfa_offset -256 - LOCKDEP_HARDIRQS_ON_RESTORE + LOCKDEP_HARDIRQS_ON_RESTORE SP_OFF($sp) call_pal PAL_rti .align 4 @@ -487,18 +430,19 @@ entUnaUser: .cfi_restore $14 .cfi_restore $15 .cfi_adjust_cfa_offset -64 - br ret_from_sys_call + br ret_from_exception CFI_END_OSF_FRAME entUna CFI_START_OSF_FRAME entDbg SAVE_ALL lda $8, 0x3fff - lda $26, ret_from_sys_call + lda $26, ret_from_exception bic $sp, $8, $8 mov $sp, $16 jsr $31, do_entDbg CFI_END_OSF_FRAME entDbg + /* * The system call entry point is special. Most importantly, it looks * like a function call to userspace as far as clobbered registers. We @@ -516,9 +460,7 @@ CFI_END_OSF_FRAME entDbg * For seccomp/ptrace/generic syscall helpers we track the syscall * number separately: * - regs->r1: current (mutable) syscall number (may be changed or set to -1) - * - regs->r2: original syscall number for restart/rollback * - * On entry PAL provides the syscall number in r0; copy it into r1/r2. */ .align 4 @@ -531,203 +473,117 @@ CFI_END_OSF_FRAME entDbg .cfi_rel_offset $gp, 16 entSys: SAVE_ALL - ldq $1, 0($sp) /* syscall nr from saved r0 */ - stq $1, 8($sp) /* regs->r1 = shadow syscall nr */ - stq $1, 16($sp) /* regs->r2 = restart syscall nr */ - /* Syscalls always enter from user mode: snapshot USP into pt_regs->usp */ + ldq $1, 0($sp) /* syscall nr from saved r0 */ + stq $1, 8($sp) /* regs->r1 = shadow syscall nr */ + mov $0, $8 call_pal PAL_rdusp - stq $0, PT_REGS_USP($sp) + stq $0, PT_REGS_USP($sp) mov $8, $0 - lda $8, 0x3fff bic $sp, $8, $8 - lda $4, NR_syscalls($31) + stq $16, SP_OFF+24($sp) - lda $5, sys_call_table - lda $27, sys_ni_syscall - cmpult $0, $4, $4 - ldl $3, TI_FLAGS($8) stq $17, SP_OFF+32($sp) - s8addq $0, $5, $5 stq $18, SP_OFF+40($sp) - .cfi_rel_offset $16, SP_OFF+24 - .cfi_rel_offset $17, SP_OFF+32 - .cfi_rel_offset $18, SP_OFF+40 - lda $6, _TIF_SYSCALL_WORK - and $3, $6, $3 - bne $3, strace - beq $4, 1f - ldq $27, 0($5) -1: ldq $0, 8($sp) /* syscall nr shadow (regs->r1) */ + mov $0, $1 + lda $16, 7 + call_pal PAL_swpipl + mov $1, $0 + mov $sp, $16 + mov $0, $17 /* pv = selected syscall function */ + + DO_SWITCH_STACK + jsr $26, alpha_syscall_enter_select + ldgp $gp, 0($26) + UNDO_SWITCH_STACK + + /* + * C returned syscall function pointer in $0. + * It also stored SKIP in TI_SYSCALL_META if dispatch is skipped. + */ + lda $8, 0x3fff + bic $sp, $8, $8 + ldq $3, TI_SYSCALL_META($8) + lda $6, ALPHA_SYSCALL_META_SKIP + and $3, $6, $6 + bne $6, skip_dispatch + + + mov $0, $27 + ldq $16, SP_OFF+24($sp) + ldq $17, SP_OFF+32($sp) + ldq $18, SP_OFF+40($sp) + ldq $19, 72($sp) + ldq $20, 80($sp) + ldq $21, 88($sp) jsr $26, ($27), sys_ni_syscall ldgp $gp, 0($26) - blt $0, $syscall_error /* the call failed */ -$ret_success: - stq $0, 0($sp) - stq $31, 72($sp) /* a3=0 => no error */ - .align 4 - .globl ret_from_sys_call -ret_from_sys_call: - cmovne $26, 0, $18 /* $18 = 0 => non-restartable */ - ldq $0, SP_OFF($sp) - and $0, 8, $0 - beq $0, ret_to_kernel -ret_to_user: - /* Make sure need_resched and sigpending don't change between - sampling and the rti. */ - lda $16, 7 - call_pal PAL_swpipl - ldl $17, TI_FLAGS($8) - and $17, _TIF_WORK_MASK, $2 - bne $2, work_pending +skip_dispatch: + mov $0, $17 /* raw ret; ignored if SKIP is set */ + mov $sp, $16 /* regs */ + + DO_SWITCH_STACK + jsr $26, alpha_finish_syscall_to_user_mode + ldgp $gp, 0($26) + UNDO_SWITCH_STACK + restore_all: + lda $8, 0x3fff + bic $sp, $8, $8 ldl $2, TI_STATUS($8) and $2, TS_SAVED_FP | TS_RESTORE_FP, $3 bne $3, restore_fpu restore_other: .cfi_remember_state - LOCKDEP_HARDIRQS_ON_RESTORE RESTORE_ALL call_pal PAL_rti -ret_to_kernel: - .cfi_restore_state - lda $16, 7 - call_pal PAL_swpipl - br restore_other - - .align 3 -$syscall_error: - /* Restart syscall nr comes from saved r2 (preserved even if r0 overwritten). */ - ldq $18, 16($sp) /* old syscall nr for restart */ - - ldq $19, 72($sp) /* .. and this a3 */ - subq $31, $0, $0 /* with error in v0 */ - addq $31, 1, $1 /* set a3 for errno return */ - stq $0, 0($sp) - mov $31, $26 /* tell "ret_from_sys_call" we can restart */ - stq $1, 72($sp) /* a3 for return */ - br ret_from_sys_call - -/* - * Do all cleanup when returning from all interrupts and system calls. - * - * Arguments: - * $8: current. - * $17: TI_FLAGS. - * $18: The old syscall number, or zero if this is not a return - * from a syscall that errored and is possibly restartable. - * $19: The old a3 value - */ - - .align 4 - .type work_pending, @function -work_pending: - and $17, _TIF_NOTIFY_RESUME | _TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL, $2 - bne $2, $work_notifysig - -$work_resched: - /* - * We can get here only if we returned from syscall without SIGPENDING - * or got through work_notifysig already. Either case means no syscall - * restarts for us, so let $18 and $19 burn. - */ - jsr $26, alpha_schedule_user_work - mov 0, $18 - br ret_to_user +ret_to_user_from_syscall: + lda $8, 0x3fff + bic $sp, $8, $8 -$work_notifysig: mov $sp, $16 DO_SWITCH_STACK - jsr $26, do_work_pending + jsr $26, alpha_syscall_exit_to_user_mode + ldgp $gp, 0($26) UNDO_SWITCH_STACK br restore_all -/* - * PTRACE syscall handler - */ - .align 4 - .type strace, @function -strace: - /* set up signal stack, call syscall_trace */ - // NB: if anyone adds preemption, this block will need to be protected - ldl $1, TI_STATUS($8) - and $1, TS_SAVED_FP, $3 - or $1, TS_SAVED_FP, $2 - bne $3, 1f - stl $2, TI_STATUS($8) - bsr $26, __save_fpu -1: - DO_SWITCH_STACK - jsr $26, syscall_trace_enter /* returns the syscall number */ - UNDO_SWITCH_STACK - - stq $0, 8($sp) /* regs->r1 = shadow syscall nr */ - - /* get the arguments back.. */ - ldq $16, SP_OFF+24($sp) - ldq $17, SP_OFF+32($sp) - ldq $18, SP_OFF+40($sp) - ldq $19, 72($sp) - ldq $20, 80($sp) - ldq $21, 88($sp) + .globl ret_from_sys_call +ret_from_sys_call: + ldq $0, SP_OFF($sp) + and $0, 8, $0 + beq $0, ret_to_kernel + br ret_to_user_from_syscall - /* nr == -1: internal skip-dispatch or userspace syscall(-1)? */ - lda $6, -1($31) - cmpeq $0, $6, $6 - bne $6, $strace_skip_call /* nr == -1 => dispatch */ - - /* get the system call pointer.. */ - lda $1, NR_syscalls($31) - lda $2, sys_call_table - lda $27, sys_ni_syscall - cmpult $0, $1, $1 - s8addq $0, $2, $2 - beq $1, 1f - ldq $27, 0($2) -1: jsr $26, ($27), sys_gettimeofday -ret_from_straced: - ldgp $gp, 0($26) +ret_from_exception: + ldq $0, SP_OFF($sp) + and $0, 8, $0 + beq $0, ret_to_kernel + br ret_to_user_from_exception - /* check return.. */ - blt $0, $strace_error /* the call failed */ -$strace_success: - stq $31, 72($sp) /* a3=0 => no error */ - stq $0, 0($sp) /* save return value */ +ret_to_user_from_exception: -$strace_skip_call: - SYSCALL_SKIP_RETURN_RESTART_GATE + mov $sp, $16 DO_SWITCH_STACK - jsr $26, syscall_trace_leave + jsr $26, alpha_exit_to_user_mode + ldgp $gp, 0($26) UNDO_SWITCH_STACK - br $31, ret_from_sys_call - - .align 3 -$strace_error: - ldq $18, 16($sp) /* restart syscall nr */ - ldq $19, 72($sp) /* .. and this a3 */ - - subq $31, $0, $0 /* with error in v0 */ - addq $31, 1, $1 /* set a3 for errno return */ - stq $0, 0($sp) - stq $1, 72($sp) /* a3 for return */ + br restore_all - DO_SWITCH_STACK - mov $18, $9 /* save old syscall number */ - mov $19, $10 /* save old a3 */ - jsr $26, syscall_trace_leave - mov $9, $18 - mov $10, $19 - UNDO_SWITCH_STACK +ret_to_kernel: + .cfi_restore_state + lda $16, 7 + call_pal PAL_swpipl + LOCKDEP_HARDIRQS_ON_RESTORE SP_OFF($sp) + br restore_other - mov $31, $26 /* tell "ret_from_sys_call" we can restart */ - br ret_from_sys_call CFI_END_OSF_FRAME entSys /* @@ -815,7 +671,6 @@ restore_fpu: br restore_other #undef V - /* * The meat of the context switch code. */ @@ -851,7 +706,7 @@ alpha_switch_to: .align 4 .ent ret_from_fork ret_from_fork: - lda $26, ret_to_user + lda $26, ret_to_user_from_exception mov $17, $16 jmp $31, schedule_tail .end ret_from_fork @@ -868,7 +723,7 @@ ret_from_kernel_thread: mov $9, $27 mov $10, $16 jsr $26, ($9) - br $31, ret_to_user + br $31, ret_to_user_from_exception .end ret_from_kernel_thread @@ -910,12 +765,9 @@ fork_like clone3 .ent sys_\name sys_\name: .prologue 0 - lda $9, ret_from_straced - cmpult $26, $9, $9 + mov $sp, $10 lda $sp, -SWITCH_STACK_SIZE($sp) jsr $26, do_\name - bne $9, 1f - jsr $26, syscall_trace_leave 1: br $1, undo_switch_stack br ret_from_sys_call .end sys_\name diff --git a/arch/alpha/kernel/irq_alpha.c b/arch/alpha/kernel/irq_alpha.c index 1b799aafb7dcc4..f90ff898a71a69 100644 --- a/arch/alpha/kernel/irq_alpha.c +++ b/arch/alpha/kernel/irq_alpha.c @@ -116,20 +116,24 @@ void notrace lockdep_on_restore(unsigned long ps, unsigned long ip) { #ifdef CONFIG_PROVE_LOCKING - /* Restoring IPL==7 means interrupts remain disabled. */ + /* + * If PAL_rti will restore IPL == 7, IRQs remain disabled. + * There is no hardirqs-on transition to annotate. + */ if ((ps & 7) == 7) return; /* - * If hardware IRQs are already enabled here, then emitting a - * hardirqs-on transition is redundant. + * This helper is meant to run before PAL_rti, after entry.S has + * forced IPL to 7. If IRQs are already enabled, do not emit a + * fake transition. */ if (!irqs_disabled()) return; /* - * Only emit the transition if lockdep currently believes - * hardirqs are off. + * Only emit an ON transition if lockdep currently tracks hardirqs + * as off. */ if (lockdep_hardirqs_enabled()) return; diff --git a/arch/alpha/kernel/proto.h b/arch/alpha/kernel/proto.h index 9b262ef09a3a4f..f138bd494628a3 100644 --- a/arch/alpha/kernel/proto.h +++ b/arch/alpha/kernel/proto.h @@ -2,6 +2,7 @@ #include #include #include +#include /* Prototypes of functions used across modules here in this directory. */ @@ -164,16 +165,18 @@ extern void pcibios_claim_one_bus(struct pci_bus *); /* ptrace.c */ extern int ptrace_set_bpt (struct task_struct *child); extern int ptrace_cancel_bpt (struct task_struct *child); -extern void syscall_trace_leave(void); -extern unsigned long syscall_trace_enter(void); /* signal.c */ struct sigcontext; extern void do_sigreturn(struct sigcontext __user *); struct rt_sigframe; extern void do_rt_sigreturn(struct rt_sigframe __user *); -extern void do_work_pending(struct pt_regs *, unsigned long, unsigned long, unsigned long); extern void alpha_schedule_user_work(void); +extern void do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19); +extern void alpha_syscall_exit_to_user_mode(struct pt_regs *regs); +extern void alpha_exit_to_user_mode(struct pt_regs *regs); +extern void alpha_finish_syscall_to_user_mode(struct pt_regs *regs, long ret); +extern unsigned long alpha_syscall_enter_select(struct pt_regs *regs, long syscall); /* traps.c */ extern void dik_show_regs(struct pt_regs *regs, unsigned long *r9_15); diff --git a/arch/alpha/kernel/ptrace.c b/arch/alpha/kernel/ptrace.c index fc8f6cedbb286b..e1d8c2f6968818 100644 --- a/arch/alpha/kernel/ptrace.c +++ b/arch/alpha/kernel/ptrace.c @@ -134,18 +134,44 @@ get_reg_addr(struct task_struct * task, unsigned long regno) /* * Get contents of register REGNO in task TASK. */ -static unsigned long -get_reg(struct task_struct * task, unsigned long regno) + +static bool +valid_regno(unsigned long regno) +{ + return regno <= 65; +} + +static long +get_reg(struct task_struct *task, unsigned long regno) { - /* Special hack for fpcr -- combine hardware and software bits. */ + unsigned long *addr; + + if (!valid_regno(regno)) + return -EIO; + + /* + * Special hack for fpcr -- combine hardware and software bits. + */ if (regno == 63) { - unsigned long fpcr = *get_reg_addr(task, regno); - unsigned long swcr - = task_thread_info(task)->ieee_state & IEEE_SW_MASK; + unsigned long fpcr; + unsigned long swcr; + + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + fpcr = *addr; + swcr = task_thread_info(task)->ieee_state & IEEE_SW_MASK; swcr = swcr_update_status(swcr, fpcr); + return fpcr | swcr; } - return *get_reg_addr(task, regno); + + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + return *addr; } static void alpha_elf_fpregs_get(struct task_struct *target, @@ -271,14 +297,17 @@ static void alpha_elf_gregs_set(struct task_struct *child, pt->r19 = 1; } - -/* - * Write contents of register REGNO in task TASK. - */ static int put_reg(struct task_struct *task, unsigned long regno, unsigned long data) { struct pt_regs *regs = task_pt_regs(task); + unsigned long *addr; + unsigned long old_r0 = regs->r0; + + if (regno == 31) + return 0; + if (!valid_regno(regno)) + return -EIO; if (regno == 63) { task_thread_info(task)->ieee_state @@ -287,24 +316,30 @@ put_reg(struct task_struct *task, unsigned long regno, unsigned long data) data = (data & FPCR_DYN_MASK) | ieee_swcr_to_fpcr(data); } - *get_reg_addr(task, regno) = data; + addr = get_reg_addr(task, regno); + if (!addr) + return -EIO; + + *addr = data; /* * Alpha historically exposes r0/v0 as the syscall number at a * syscall-entry stop. The generic-entry conversion keeps the - * mutable syscall number in regs->r1, so old ptrace users such - * as strace that skip a syscall by poking r0 to -1 must also - * update the internal shadow syscall number. - * - * Do not mirror other r0 writes. strace later pokes r0 to the - * injected return value, e.g. 42, while r1 must remain -1. + * mutable syscall number in regs->r1. */ - if (regno == 0 && data == (unsigned long)-1) { + if (regno == 0 && regs->r1 == old_r0 && + (data == (unsigned long)-1 || + (regs->r19 == 0 && data < NR_syscalls))) { regs->r1 = data; - regs->r19 = 0; - } + /* + * Keep the skip path looking like a clean entry-side syscall + * rewrite. Do not touch r19 for ordinary syscall substitution. + */ + if (data == (unsigned long)-1) + regs->r19 = 0; + } return 0; } @@ -435,25 +470,24 @@ long arch_ptrace(struct task_struct *child, long request, switch (request) { /* When I and D space are separate, these will need to be fixed. */ - case PTRACE_PEEKTEXT: /* read word at location addr. */ + case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: copied = ptrace_access_vm(child, addr, &tmp, sizeof(tmp), FOLL_FORCE); ret = -EIO; if (copied != sizeof(tmp)) break; - force_successful_syscall_return(); ret = tmp; break; - /* Read register number ADDR. */ case PTRACE_PEEKUSR: - force_successful_syscall_return(); ret = get_reg(child, addr); - DBG(DBG_MEM, ("peek $%lu->%#lx\n", addr, ret)); - break; + if (ret == -EIO) + break; + force_successful_syscall_return(); + break; /* When I and D space are separate, this will have to be fixed. */ case PTRACE_POKETEXT: /* write the word at location addr. */ case PTRACE_POKEDATA: @@ -471,47 +505,6 @@ long arch_ptrace(struct task_struct *child, long request, return ret; } -asmlinkage unsigned long syscall_trace_enter(void) -{ - struct pt_regs *regs = current_pt_regs(); - - if (test_thread_flag(TIF_SYSCALL_TRACE) && - !ptrace_report_syscall_permit_entry(regs)) { - syscall_set_nr(current, regs, -1); - if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) - syscall_set_return_value(current, regs, -ENOSYS, 0); - return -1UL; - } - - /* - * Do the secure computing after ptrace; failures should be fast. - * If this fails, seccomp may already have set up the return value - * (e.g. SECCOMP_RET_ERRNO / TRACE). - */ - if (!seccomp_permit_syscall()) { - if (regs->r19 == 0 && regs->r0 == (unsigned long)-1) - syscall_set_return_value(current, regs, -ENOSYS, 0); - syscall_set_nr(current, regs, -1); - return -1UL; - } - -#ifdef CONFIG_AUDITSYSCALL - audit_syscall_entry(syscall_get_nr(current, regs), - regs->r16, regs->r17, regs->r18, regs->r19); -#endif - return syscall_get_nr(current, regs); -} - - - -asmlinkage void -syscall_trace_leave(void) -{ - audit_syscall_exit(current_pt_regs()); - if (test_thread_flag(TIF_SYSCALL_TRACE)) - ptrace_report_syscall_exit(current_pt_regs(), 0); -} - /* * Minimal regset support for Alpha. * @@ -522,7 +515,6 @@ syscall_trace_leave(void) * regset_get should return 0 on success. So call dump_elf_thread() * directly and return membuf_write()'s result. */ - static int alpha_regset_set(struct task_struct *target, const struct user_regset *regset, unsigned int pos, unsigned int count, diff --git a/arch/alpha/kernel/signal.c b/arch/alpha/kernel/signal.c index ce40a49b8496df..0c55a7554d63ee 100644 --- a/arch/alpha/kernel/signal.c +++ b/arch/alpha/kernel/signal.c @@ -27,10 +27,9 @@ #include #include #include - +#include #include "proto.h" - #define DEBUG_SIG 0 #define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) @@ -41,14 +40,6 @@ asmlinkage void ret_from_sys_call(void); * The OSF/1 sigprocmask calling sequence is different from the * C sigprocmask() sequence.. */ - -asmlinkage void alpha_schedule_user_work(void) -{ - local_irq_enable(); - schedule(); - local_irq_disable(); -} - SYSCALL_DEFINE2(osf_sigprocmask, int, how, unsigned long, newmask) { sigset_t oldmask; @@ -465,6 +456,7 @@ syscall_restart(unsigned long r0, unsigned long r19, fallthrough; case ERESTARTNOINTR: regs->r0 = r0; /* reset v0 and a3 and replay syscall */ + regs->r1 = r0; regs->r19 = r19; regs->pc -= 4; break; @@ -488,7 +480,7 @@ syscall_restart(unsigned long r0, unsigned long r19, * restart. "r0" is also used as an indicator whether we can restart at * all (if we get here from anything but a syscall return, it will be 0) */ -static void +void do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) { unsigned long single_stepping = ptrace_cancel_bpt(current); @@ -511,12 +503,14 @@ do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) case ERESTARTNOINTR: /* Reset v0 and a3 and replay syscall. */ regs->r0 = r0; + regs->r1 = r0; regs->r19 = r19; regs->pc -= 4; break; case ERESTART_RESTARTBLOCK: /* Set v0 to the restart_syscall and replay */ regs->r0 = __NR_restart_syscall; + regs->r1 = __NR_restart_syscall; regs->pc -= 4; break; } @@ -527,27 +521,133 @@ do_signal(struct pt_regs *regs, unsigned long r0, unsigned long r19) ptrace_set_bpt(current); /* re-set breakpoint */ } -void -do_work_pending(struct pt_regs *regs, unsigned long thread_flags, - unsigned long r0, unsigned long r19) +asmlinkage void alpha_exit_to_user_mode(struct pt_regs *regs) { - do { - if (thread_flags & _TIF_NEED_RESCHED) { - local_irq_enable(); - schedule(); - } else { - local_irq_enable(); - if (thread_flags & (_TIF_SIGPENDING|_TIF_NOTIFY_SIGNAL)) { - preempt_disable(); - save_fpu(); - preempt_enable(); - do_signal(regs, r0, r19); - r0 = 0; - } else { - resume_user_mode_work(regs); - } + local_irq_disable(); + irqentry_exit_to_user_mode_prepare(regs); + exit_to_user_mode(); +} + +/* + * Syscall return reaches here after Alpha-specific r0/a3 result encoding. + * Delegate syscall-exit work and final exit-to-user handling to generic + * entry code; low-level PAL restore remains in assembly. + */ +asmlinkage void alpha_syscall_exit_to_user_mode(struct pt_regs *regs) +{ + syscall_exit_to_user_mode(regs); +} + +void arch_do_signal_or_restart(struct pt_regs *regs) +{ + struct thread_info *ti = current_thread_info(); + + preempt_disable(); + save_fpu(); + preempt_enable(); + do_signal(regs, ti->syscall_saved_nr, ti->syscall_saved_r19); +} + +asmlinkage unsigned long +alpha_syscall_enter_select(struct pt_regs *regs, long syscall) +{ + struct thread_info *ti = current_thread_info(); + unsigned long saved_r0 = regs->r0; + unsigned long saved_r19 = regs->r19; + unsigned long fn = (unsigned long)sys_ni_syscall; + long nr = syscall; + bool permit; + + ti->syscall_meta = 0; + ti->syscall_saved_nr = syscall; + + if (!(ti->status & TS_SAVED_FP)) { + ti->status |= TS_SAVED_FP; + __save_fpu(); + } + + permit = syscall_enter_from_user_mode_randomize_stack(regs, &nr); + + /* + * Alpha cannot preset the full -ENOSYS return state before generic + * entry handling, because r19 is still syscall argument 4 on the + * normal dispatch path. So provide the default -ENOSYS result only + * here, after we know no real syscall will be invoked, and only if + * entry work did not already change the return state. + */ + if (unlikely(!permit || (unsigned long)nr >= NR_syscalls)) { + if (regs->r0 == saved_r0 && regs->r19 == saved_r19) + syscall_set_return_value(current, regs, -ENOSYS, 0); + + ti->syscall_meta = ALPHA_SYSCALL_META_SKIP; + return fn; /* ignored by asm when SKIP is set */ + } + + instrumentation_begin(); + { + unsigned long index; + + index = array_index_nospec((unsigned long)nr, NR_syscalls); + fn = (unsigned long)sys_call_table[index]; + } + instrumentation_end(); + + return fn; +} + +asmlinkage noinstr void +alpha_finish_syscall_to_user_mode(struct pt_regs *regs, long ret) +{ + struct thread_info *ti = current_thread_info(); + unsigned long meta = ti->syscall_meta; + + ti->syscall_meta = 0; + ti->syscall_saved_r19 = regs->r19; + + instrumentation_begin(); + + if (meta & ALPHA_SYSCALL_META_SKIP) { + /* + * Skip-dispatch path: ptrace/seccomp may already have installed + * the return state in r0/r19. Preserve it unless the syscall was + * skipped with no explicit return value. + * + * Generic PTRACE_SET_SYSCALL_INFO changes only the syscall-number + * shadow, so r1 == -1 while r0 still contains the original Alpha + * syscall number. Legacy PTRACE_POKEUSR based skipping can leave + * r0 == -1 with a3/r19 still indicating success. Both represent + * an unhandled skipped syscall and should become ENOSYS/a3=1. + */ + if (regs->r1 == (unsigned long)-1 && + (regs->r0 == ti->syscall_saved_nr || + regs->r0 == (unsigned long)-1)) { + regs->r0 = ENOSYS; + regs->r19 = 1; } - local_irq_disable(); - thread_flags = read_thread_flags(); - } while (thread_flags & _TIF_WORK_MASK); + + instrumentation_end(); + + syscall_exit_to_user_mode(regs); + return; + } + + /* + * Some successful syscalls, notably legacy ptrace PEEK requests, + * return arbitrary data in r0. That data may have the bit pattern + * of a negative errno, so do not infer failure from ret < 0 when + * arch code explicitly requested a successful Alpha return. + */ + if (meta & ALPHA_SYSCALL_META_FORCE_SUCCESS) { + regs->r0 = ret; + regs->r19 = 0; + } else if (ret < 0) { + regs->r0 = -ret; + regs->r19 = 1; + } else { + regs->r0 = ret; + regs->r19 = 0; + } + + instrumentation_end(); + syscall_exit_to_user_mode(regs); } From 07d35cec28c6a33d7714da6eebcf5c69644f530e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 11 Sep 2026 14:47:49 +0200 Subject: [PATCH 0342/2286] vfs: use __struct_size() in the fdtable KUnit test sparse doesn't know __builtin_dynamic_object_size() and so the two size checks in the fdtable test fall apart on every config where the compiler has __counted_by_ptr: fs/tests/fdtable_kunit.c:25:9: sparse: undefined identifier '__builtin_dynamic_object_size' fs/tests/fdtable_kunit.c:25:9: sparse: incompatible types for operation (==): Use __struct_size() instead. Every compiler that has __counted_by_ptr also has __builtin_dynamic_object_size() so it expands to the same builtin and the test checks the same thing. sparse lacks the builtin and gets the __builtin_object_size() fallback which it does understand. Fixes: 501cbbd276b7 ("vfs: Add KUnit tests for fdtable") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609111337.fBSfTIpR-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202609111828.SjerhPyO-lkp@intel.com/ Signed-off-by: Christian Brauner (Amutable) --- fs/tests/fdtable_kunit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/tests/fdtable_kunit.c b/fs/tests/fdtable_kunit.c index 6abd2a8d8f5de3..c5b028264557d7 100644 --- a/fs/tests/fdtable_kunit.c +++ b/fs/tests/fdtable_kunit.c @@ -22,7 +22,7 @@ static void test_alloc_fdtable(struct kunit *test) * __counted_by_ptr. */ #ifdef CONFIG_CC_HAS_COUNTED_BY_PTR - KUNIT_EXPECT_EQ(test, __builtin_dynamic_object_size(fdt->fd, 0), + KUNIT_EXPECT_EQ(test, __struct_size(fdt->fd), fdt->max_fds * sizeof(struct file *)); #endif @@ -51,7 +51,7 @@ static void test_dup_fd(struct kunit *test) * __counted_by_ptr. */ #ifdef CONFIG_CC_HAS_COUNTED_BY_PTR - KUNIT_EXPECT_EQ(test, __builtin_dynamic_object_size(fdt->fd, 0), + KUNIT_EXPECT_EQ(test, __struct_size(fdt->fd), fdt->max_fds * sizeof(struct file *)); #endif From 86cfbad9d7b3d2f6546dd3452aaade8022ac63ec Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 11 Sep 2026 14:58:39 +0200 Subject: [PATCH 0343/2286] userns: rename insert_extent() and sort_idmaps() The KUnit test suite turned insert_extent() and sort_idmaps() into global symbols and declared them in include/linux/user_namespace.h. That header is pulled in nearly everywhere through linux/cgroup.h and the btrfs self-tests carry a static insert_extent() of their own, so any config with both test suites enabled fails to build: fs/btrfs/tests/inode-tests.c:16:13: error: conflicting types for 'insert_extent' include/linux/user_namespace.h:212:12: note: previous declaration of 'insert_extent' A global symbol needs a prefix. Rename them to uid_gid_map_insert_extent() and uid_gid_map_sort(). It's the better name anyway. No functional changes. Fixes: f6f1a2496ba5 ("userns: Add KUnit test suite for uid_gid_map") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609110843.okbPe7GM-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202609110937.zHKPPXJk-lkp@intel.com/ Link: https://patch.msgid.link/20260911-suggerieren-suchaktion-fliehen-87b9cdf1d169@brauner Signed-off-by: Christian Brauner (Amutable) --- include/linux/user_namespace.h | 5 +++-- kernel/tests/user_ns_map_kunit.c | 6 +++--- kernel/user_namespace.c | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 633157781edcbb..637cd7e91a457e 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -209,8 +209,9 @@ extern bool current_in_userns(const struct user_namespace *target_ns); struct ns_common *ns_get_owner(struct ns_common *ns); #if IS_ENABLED(CONFIG_USER_NS_MAP_KUNIT_TEST) -extern int insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent); -extern int sort_idmaps(struct uid_gid_map *map); +extern int uid_gid_map_insert_extent(struct uid_gid_map *map, + struct uid_gid_extent *extent); +extern int uid_gid_map_sort(struct uid_gid_map *map); #endif /* CONFIG_USER_NS_MAP_KUNIT_TEST */ #else diff --git a/kernel/tests/user_ns_map_kunit.c b/kernel/tests/user_ns_map_kunit.c index 24c21e43a36c93..033dccc6a535bb 100644 --- a/kernel/tests/user_ns_map_kunit.c +++ b/kernel/tests/user_ns_map_kunit.c @@ -24,7 +24,7 @@ static void user_ns_map_insert(struct kunit *test) extent.lower_first = i * 100; extent.count = 5; - ret = insert_extent(&map, &extent); + ret = uid_gid_map_insert_extent(&map, &extent); KUNIT_ASSERT_EQ(test, ret, 0); } @@ -54,14 +54,14 @@ static void user_ns_map_insert_extended(struct kunit *test) extent.lower_first = value * 100; extent.count = 5; - ret = insert_extent(&map, &extent); + ret = uid_gid_map_insert_extent(&map, &extent); KUNIT_ASSERT_EQ(test, ret, 0); } KUNIT_EXPECT_EQ(test, map.nr_extents, NR_EXTENTS); /* Now sort the map to set up reverse mapping */ - ret = sort_idmaps(&map); + ret = uid_gid_map_sort(&map); KUNIT_ASSERT_EQ(test, ret, 0); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, map.reverse); diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index f9cbb4b92b4879..98b0279b6cc6e7 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -780,12 +780,13 @@ static bool mappings_overlap(struct uid_gid_map *new_map, } /* - * insert_extent - Safely insert a new idmap extent into struct uid_gid_map. + * uid_gid_map_insert_extent - Safely insert a new idmap extent into + * struct uid_gid_map. * Takes care to allocate a 4K block of memory if the number of mappings exceeds * UID_GID_MAP_MAX_BASE_EXTENTS. */ -VISIBLE_IF_KUNIT int insert_extent(struct uid_gid_map *map, - struct uid_gid_extent *extent) +VISIBLE_IF_KUNIT int uid_gid_map_insert_extent(struct uid_gid_map *map, + struct uid_gid_extent *extent) { struct uid_gid_extent *dest; @@ -821,7 +822,7 @@ VISIBLE_IF_KUNIT int insert_extent(struct uid_gid_map *map, *dest = *extent; return 0; } -EXPORT_SYMBOL_IF_KUNIT(insert_extent); +EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_insert_extent); /* cmp function to sort() forward mappings */ static int cmp_extents_forward(const void *a, const void *b) @@ -854,10 +855,10 @@ static int cmp_extents_reverse(const void *a, const void *b) } /* - * sort_idmaps - Sorts an array of idmap entries. + * uid_gid_map_sort - Sorts an array of idmap entries. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -VISIBLE_IF_KUNIT int sort_idmaps(struct uid_gid_map *map) +VISIBLE_IF_KUNIT int uid_gid_map_sort(struct uid_gid_map *map) { if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) return 0; @@ -878,7 +879,7 @@ VISIBLE_IF_KUNIT int sort_idmaps(struct uid_gid_map *map) return 0; } -EXPORT_SYMBOL_IF_KUNIT(sort_idmaps); +EXPORT_SYMBOL_IF_KUNIT(uid_gid_map_sort); /** * verify_root_map() - check the uid 0 mapping @@ -1047,7 +1048,7 @@ static ssize_t map_write(struct file *file, const char __user *buf, (next_line != NULL)) goto out; - ret = insert_extent(&new_map, &extent); + ret = uid_gid_map_insert_extent(&new_map, &extent); if (ret < 0) goto out; ret = -EINVAL; @@ -1091,7 +1092,7 @@ static ssize_t map_write(struct file *file, const char __user *buf, * If we want to use binary search for lookup, this clones the extent * array and sorts both copies. */ - ret = sort_idmaps(&new_map); + ret = uid_gid_map_sort(&new_map); if (ret < 0) goto out; From 4879a907335976d27f15c0aa3f1d358db3e9bd9f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 11 Sep 2026 14:58:40 +0200 Subject: [PATCH 0344/2286] userns: declare the KUnit-visible helpers whenever KUnit is enabled VISIBLE_IF_KUNIT drops the static qualifier whenever CONFIG_KUNIT is enabled but the prototypes for the two helpers it exposes are only declared when CONFIG_USER_NS_MAP_KUNIT_TEST is set. kernel/user_namespace.c:787:22: warning: no previous prototype for 'insert_extent' [-Wmissing-prototypes] kernel/user_namespace.c:860:22: warning: no previous prototype for 'sort_idmaps' [-Wmissing-prototypes] Guard the declarations on CONFIG_KUNIT so they match the definitions. Fixes: f6f1a2496ba5 ("userns: Add KUnit test suite for uid_gid_map") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202609111048.yhHILE11-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202609111305.ta3EOA2x-lkp@intel.com/ Link: https://patch.msgid.link/20260911-zyklisch-stuhl-zweit-d61b18a17838@brauner Signed-off-by: Christian Brauner (Amutable) --- include/linux/user_namespace.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 637cd7e91a457e..91232053775d8d 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -208,11 +208,11 @@ extern bool in_userns(const struct user_namespace *ancestor, extern bool current_in_userns(const struct user_namespace *target_ns); struct ns_common *ns_get_owner(struct ns_common *ns); -#if IS_ENABLED(CONFIG_USER_NS_MAP_KUNIT_TEST) +#if IS_ENABLED(CONFIG_KUNIT) extern int uid_gid_map_insert_extent(struct uid_gid_map *map, struct uid_gid_extent *extent); extern int uid_gid_map_sort(struct uid_gid_map *map); -#endif /* CONFIG_USER_NS_MAP_KUNIT_TEST */ +#endif /* CONFIG_KUNIT */ #else From e0600e95bd5b5a31d83fe29143f44069dacf821d Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 13:40:45 +0200 Subject: [PATCH 0345/2286] gfs2: Avoid ip->i_gl dereferences in gfs2_inode_lookup and gfs2_create_inode Store the inode glock in a separate variable instead of repeatedly retrieving it as ip->i_gl. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/inode.c | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index f361876c558335..438519bc06d496 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -130,6 +130,7 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, { struct inode *inode; struct gfs2_inode *ip; + struct gfs2_glock *gl = NULL; struct gfs2_holder i_gh; int error; @@ -147,9 +148,10 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, gfs2_setup_inode(inode); error = gfs2_glock_get(sdp, no_addr, &gfs2_inode_glops, CREATE, - &ip->i_gl); + &gl); if (unlikely(error)) goto fail; + ip->i_gl = gl; error = gfs2_glock_get(sdp, no_addr, &gfs2_iopen_glops, CREATE, &io_gl); @@ -178,14 +180,14 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, * block. We read the inode when instantiating it * after possibly checking the block type. */ - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, GL_SKIP, &i_gh); if (error) goto fail; error = -ESTALE; if (no_formal_ino && - gfs2_inode_already_deleted(ip->i_gl, no_formal_ino)) + gfs2_inode_already_deleted(gl, no_formal_ino)) goto fail; if (blktype != GFS2_BLKST_FREE) { @@ -196,20 +198,20 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, } } - set_bit(GLF_INSTANTIATE_NEEDED, &ip->i_gl->gl_flags); + set_bit(GLF_INSTANTIATE_NEEDED, &gl->gl_flags); /* Lowest possible timestamp; will be overwritten in gfs2_dinode_in. */ inode_set_atime(inode, 1LL << (8 * sizeof(inode_get_atime_sec(inode)) - 1), 0); - glock_set_object(ip->i_gl, ip); + glock_set_object(gl, ip); if (type == DT_UNKNOWN) { /* Inode glock must be locked already */ error = gfs2_instantiate(&i_gh); if (error) { - glock_clear_object(ip->i_gl, ip); + glock_clear_object(gl, ip); goto fail; } } else { @@ -240,8 +242,8 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, gfs2_glock_dq_uninit(&ip->i_iopen_gh); if (gfs2_holder_initialized(&i_gh)) gfs2_glock_dq_uninit(&i_gh); - if (ip->i_gl) { - gfs2_glock_put(ip->i_gl); + if (gl) { + gfs2_glock_put(gl); ip->i_gl = NULL; } iget_failed(inode); @@ -708,7 +710,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, struct inode *inode = NULL; struct gfs2_inode *dip = GFS2_I(dir), *ip; struct gfs2_sbd *sdp = GFS2_SB(&dip->i_inode); - struct gfs2_glock *io_gl; + struct gfs2_glock *gl = NULL, *io_gl; int error, dealloc_error; u32 aflags = 0; unsigned blocks = 1; @@ -832,9 +834,10 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, gfs2_set_inode_blocks(inode, blocks); - error = gfs2_glock_get(sdp, ip->i_no_addr, &gfs2_inode_glops, CREATE, &ip->i_gl); + error = gfs2_glock_get(sdp, ip->i_no_addr, &gfs2_inode_glops, CREATE, &gl); if (error) goto fail_dealloc_inode; + ip->i_gl = gl; error = gfs2_glock_get(sdp, ip->i_no_addr, &gfs2_iopen_glops, CREATE, &io_gl); if (error) @@ -854,10 +857,10 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, if (error) goto fail_gunlock2; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_SKIP, &gh); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, GL_SKIP, &gh); if (error) goto fail_gunlock3; - clear_bit(GLF_INSTANTIATE_NEEDED, &ip->i_gl->gl_flags); + clear_bit(GLF_INSTANTIATE_NEEDED, &gl->gl_flags); error = gfs2_trans_begin(sdp, blocks, 0); if (error) @@ -870,7 +873,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, init_dinode(dip, ip, symname); gfs2_trans_end(sdp); - glock_set_object(ip->i_gl, ip); + glock_set_object(gl, ip); glock_set_object(io_gl, ip); gfs2_set_iop(inode); @@ -914,7 +917,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, return error; fail_gunlock4: - glock_clear_object(ip->i_gl, ip); + glock_clear_object(gl, ip); glock_clear_object(io_gl, ip); fail_gunlock3: gfs2_glock_dq_uninit(&ip->i_iopen_gh); @@ -932,8 +935,8 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, fs_warn(sdp, "%s: %d\n", __func__, dealloc_error); ip->i_no_addr = 0; fail_free_inode: - if (ip->i_gl) { - gfs2_glock_put(ip->i_gl); + if (gl) { + gfs2_glock_put(gl); ip->i_gl = NULL; } gfs2_rs_deltree(&ip->i_res); From 9fa5390e3e70a4d9e2cca4565ae44c5385cf63a2 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 13:45:28 +0200 Subject: [PATCH 0346/2286] gfs2: Get to super block from inode in tracepoints When we have a gfs2 inode, use that to get to the super block instead of going via the glock. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/trace_gfs2.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/gfs2/trace_gfs2.h b/fs/gfs2/trace_gfs2.h index bc40320ef239b1..0c60d755ba7627 100644 --- a/fs/gfs2/trace_gfs2.h +++ b/fs/gfs2/trace_gfs2.h @@ -457,7 +457,7 @@ TRACE_EVENT(gfs2_bmap, ), TP_fast_assign( - __entry->dev = glock_sbd(ip->i_gl)->sd_vfs->s_dev; + __entry->dev = ip->i_inode.i_sb->s_dev; __entry->lblock = lblock; __entry->pblock = buffer_mapped(bh) ? bh->b_blocknr : 0; __entry->inum = ip->i_no_addr; @@ -493,7 +493,7 @@ TRACE_EVENT(gfs2_iomap_start, ), TP_fast_assign( - __entry->dev = glock_sbd(ip->i_gl)->sd_vfs->s_dev; + __entry->dev = ip->i_inode.i_sb->s_dev; __entry->inum = ip->i_no_addr; __entry->pos = pos; __entry->length = length; @@ -525,7 +525,7 @@ TRACE_EVENT(gfs2_iomap_end, ), TP_fast_assign( - __entry->dev = glock_sbd(ip->i_gl)->sd_vfs->s_dev; + __entry->dev = ip->i_inode.i_sb->s_dev; __entry->inum = ip->i_no_addr; __entry->offset = iomap->offset; __entry->length = iomap->length; From 96e08c4dfed57790af22e0e2c1401fc8d12a9e0b Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 14:47:05 +0200 Subject: [PATCH 0347/2286] gfs2: Add inode variables Add inode variables in a few places to get rid of '&ip->i_inode'. This cleans up the code a little and prepares for the next step of adding an accessor function for ip->i_gl. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 37 ++++++++++++++++++++----------------- fs/gfs2/dir.c | 4 +++- fs/gfs2/file.c | 5 +++-- fs/gfs2/quota.c | 25 ++++++++++++++----------- fs/gfs2/recovery.c | 5 +++-- 5 files changed, 43 insertions(+), 33 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 73c62697116390..636139f463b062 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -89,6 +89,7 @@ static int gfs2_unstuffer_folio(struct gfs2_inode *ip, struct buffer_head *dibh, static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) { + struct inode *inode = &ip->i_inode; struct buffer_head *bh, *dibh; struct gfs2_dinode *di; u64 block = 0; @@ -99,7 +100,7 @@ static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) if (error) return error; - if (i_size_read(&ip->i_inode)) { + if (i_size_read(inode)) { /* Get a free block, fill it with the stuffed data, and write it out to disk */ @@ -108,7 +109,7 @@ static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) if (error) goto out_brelse; if (isdir) { - gfs2_trans_remove_revoke(GFS2_SB(&ip->i_inode), block, 1); + gfs2_trans_remove_revoke(GFS2_SB(inode), block, 1); error = gfs2_dir_get_new_buffer(ip, block, &bh); if (error) goto out_brelse; @@ -128,10 +129,10 @@ static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) di = (struct gfs2_dinode *)dibh->b_data; gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode)); - if (i_size_read(&ip->i_inode)) { + if (i_size_read(inode)) { *(__be64 *)(di + 1) = cpu_to_be64(block); - gfs2_add_inode_blocks(&ip->i_inode, 1); - di->di_blocks = cpu_to_be64(gfs2_get_inode_blocks(&ip->i_inode)); + gfs2_add_inode_blocks(inode, 1); + di->di_blocks = cpu_to_be64(gfs2_get_inode_blocks(inode)); } ip->i_height = 1; @@ -1488,7 +1489,8 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, struct buffer_head *bh, __be64 *start, __be64 *end, bool meta, u32 *btotal) { - struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); + struct inode *inode = &ip->i_inode; + struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_rgrpd *rgd; struct gfs2_trans *tr; __be64 *p; @@ -1546,7 +1548,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, jblocks_rqsted = rgd->rd_length + RES_DINODE + RES_INDIRECT; - isize_blks = gfs2_get_inode_blocks(&ip->i_inode); + isize_blks = gfs2_get_inode_blocks(inode); if (isize_blks > atomic_read(&sdp->sd_log_thresh2)) jblocks_rqsted += atomic_read(&sdp->sd_log_thresh2); @@ -1597,7 +1599,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, if (bstart) { __gfs2_free_blocks(ip, rgd, bstart, (u32)blen, meta); (*btotal) += blen; - gfs2_add_inode_blocks(&ip->i_inode, -blen); + gfs2_add_inode_blocks(inode, -blen); } bstart = bn; blen = 1; @@ -1605,7 +1607,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, if (bstart) { __gfs2_free_blocks(ip, rgd, bstart, (u32)blen, meta); (*btotal) += blen; - gfs2_add_inode_blocks(&ip->i_inode, -blen); + gfs2_add_inode_blocks(inode, -blen); } out_unlock: if (!ret && blks_outside_rgrp) { /* If buffer still has non-zero blocks @@ -1620,7 +1622,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, /* Every transaction boundary, we rewrite the dinode to keep its di_blocks current in case of failure. */ - inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode)); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); gfs2_trans_add_meta(ip->i_gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); @@ -1746,7 +1748,8 @@ static inline bool walk_done(struct gfs2_sbd *sdp, */ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) { - struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); + struct inode *inode = &ip->i_inode; + struct gfs2_sbd *sdp = GFS2_SB(inode); u64 maxsize = sdp->sd_heightsize[ip->i_height]; struct metapath mp = {}; struct buffer_head *dibh, *bh; @@ -1985,9 +1988,8 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) down_write(&ip->i_rw_mutex); } gfs2_statfs_change(sdp, 0, +btotal, 0); - gfs2_quota_change(ip, -(s64)btotal, ip->i_inode.i_uid, - ip->i_inode.i_gid); - inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode)); + gfs2_quota_change(ip, -(s64)btotal, inode->i_uid, inode->i_gid); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); gfs2_trans_add_meta(ip->i_gl, dibh); gfs2_dinode_out(ip, dibh->b_data); up_write(&ip->i_rw_mutex); @@ -2010,7 +2012,8 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) static int trunc_end(struct gfs2_inode *ip) { - struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); + struct inode *inode = &ip->i_inode; + struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *dibh; int error; @@ -2024,13 +2027,13 @@ static int trunc_end(struct gfs2_inode *ip) if (error) goto out; - if (!i_size_read(&ip->i_inode)) { + if (!i_size_read(inode)) { ip->i_height = 0; ip->i_goal = ip->i_no_addr; gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode)); gfs2_ordered_del_inode(ip); } - inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode)); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); ip->i_diskflags &= ~GFS2_DIF_TRUNC_IN_PROG; gfs2_trans_add_meta(ip->i_gl, dibh); diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c index 0237b36b9eb165..f6111276ebb072 100644 --- a/fs/gfs2/dir.c +++ b/fs/gfs2/dir.c @@ -759,10 +759,12 @@ static struct gfs2_dirent *gfs2_dirent_split_alloc(struct inode *inode, static int get_leaf(struct gfs2_inode *dip, u64 leaf_no, struct buffer_head **bhp) { + struct inode *inode = &dip->i_inode; + struct gfs2_sbd *sdp = GFS2_SB(inode); int error; error = gfs2_meta_read(dip->i_gl, leaf_no, DIO_WAIT, 0, bhp); - if (!error && gfs2_metatype_check(GFS2_SB(&dip->i_inode), *bhp, GFS2_METATYPE_LF)) { + if (!error && gfs2_metatype_check(sdp, *bhp, GFS2_METATYPE_LF)) { /* pr_info("block num=%llu\n", leaf_no); */ error = -EIO; } diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index b8c10de113ba7a..164e160e9064a9 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -812,7 +812,8 @@ static ssize_t gfs2_file_direct_read(struct kiocb *iocb, struct iov_iter *to, struct gfs2_holder *gh) { struct file *file = iocb->ki_filp; - struct gfs2_inode *ip = GFS2_I(file->f_mapping->host); + struct inode *inode = file->f_mapping->host; + struct gfs2_inode *ip = GFS2_I(inode); size_t prev_count = 0, window_size = 0; size_t read = 0; ssize_t ret; @@ -906,7 +907,7 @@ static ssize_t gfs2_file_direct_write(struct kiocb *iocb, struct iov_iter *from, if (ret) goto out_uninit; /* Silently fall back to buffered I/O when writing beyond EOF */ - if (iocb->ki_pos + iov_iter_count(from) > i_size_read(&ip->i_inode)) + if (iocb->ki_pos + iov_iter_count(from) > i_size_read(inode)) goto out_unlock; from->nofault = true; diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index 001c8b39ca5573..0cb2bb0aec7b23 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -740,8 +740,8 @@ static void do_qc(struct gfs2_quota_data *qd, s64 change) static int gfs2_write_buf_to_page(struct gfs2_sbd *sdp, unsigned long index, unsigned off, void *buf, unsigned bytes) { - struct gfs2_inode *ip = GFS2_I(sdp->sd_quota_inode); - struct inode *inode = &ip->i_inode; + struct inode *inode = sdp->sd_quota_inode; + struct gfs2_inode *ip = GFS2_I(inode); struct address_space *mapping = inode->i_mapping; struct folio *folio; struct buffer_head *bh; @@ -908,7 +908,8 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, u64 sync_gen) { struct gfs2_sbd *sdp = (*qda)->qd_sbd; - struct gfs2_inode *ip = GFS2_I(sdp->sd_quota_inode); + struct inode *inode = sdp->sd_quota_inode; + struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_alloc_parms ap = {}; unsigned int data_blocks, ind_blocks; struct gfs2_holder *ghs, i_gh; @@ -927,7 +928,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, return -ENOMEM; sort(qda, num_qd, sizeof(struct gfs2_quota_data *), sort_qd, NULL); - inode_lock(&ip->i_inode); + inode_lock(inode); for (qx = 0; qx < num_qd; qx++) { error = gfs2_glock_nq_init(qda[qx]->qd_gl, LM_ST_EXCLUSIVE, GL_NOCACHE, &ghs[qx]); @@ -991,9 +992,9 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, out_dq: while (qx--) gfs2_glock_dq_uninit(&ghs[qx]); - inode_unlock(&ip->i_inode); + inode_unlock(inode); kfree(ghs); - gfs2_log_flush(glock_sbd(ip->i_gl), ip->i_gl, + gfs2_log_flush(sdp, ip->i_gl, GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_DO_SYNC); if (!error) { for (x = 0; x < num_qd; x++) { @@ -1402,7 +1403,8 @@ int gfs2_quota_refresh(struct gfs2_sbd *sdp, struct kqid qid) int gfs2_quota_init(struct gfs2_sbd *sdp) { - struct gfs2_inode *ip = GFS2_I(sdp->sd_qc_inode); + struct inode *inode = sdp->sd_qc_inode; + struct gfs2_inode *ip = GFS2_I(inode); u64 size = i_size_read(sdp->sd_qc_inode); unsigned int blocks = size >> sdp->sd_sb.sb_bsize_shift; unsigned int x, slot = 0; @@ -1434,7 +1436,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp) if (!extlen) { extlen = 32; - error = gfs2_get_extent(&ip->i_inode, x, &dblock, &extlen); + error = gfs2_get_extent(inode, x, &dblock, &extlen); if (error) goto fail; } @@ -1714,7 +1716,8 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid, struct qc_dqblk *fdq) { struct gfs2_sbd *sdp = sb->s_fs_info; - struct gfs2_inode *ip = GFS2_I(sdp->sd_quota_inode); + struct inode *inode = sdp->sd_quota_inode; + struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_quota_data *qd; struct gfs2_holder q_gh, i_gh; unsigned int data_blocks, ind_blocks; @@ -1741,7 +1744,7 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid, if (error) goto out_put; - inode_lock(&ip->i_inode); + inode_lock(inode); error = gfs2_glock_nq_init(qd->qd_gl, LM_ST_EXCLUSIVE, 0, &q_gh); if (error) goto out_unlockput; @@ -1807,7 +1810,7 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid, gfs2_glock_dq_uninit(&q_gh); out_unlockput: gfs2_qa_put(ip); - inode_unlock(&ip->i_inode); + inode_unlock(inode); out_put: qd_put(qd); return error; diff --git a/fs/gfs2/recovery.c b/fs/gfs2/recovery.c index 616c46aa3434ae..b45aa3032ca2a9 100644 --- a/fs/gfs2/recovery.c +++ b/fs/gfs2/recovery.c @@ -32,14 +32,15 @@ struct workqueue_struct *gfs2_recovery_wq; int gfs2_replay_read_block(struct gfs2_jdesc *jd, unsigned int blk, struct buffer_head **bh) { - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); + struct inode *inode = jd->jd_inode; + struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_glock *gl = ip->i_gl; u64 dblock; u32 extlen; int error; extlen = 32; - error = gfs2_get_extent(&ip->i_inode, blk, &dblock, &extlen); + error = gfs2_get_extent(inode, blk, &dblock, &extlen); if (error) return error; if (!dblock) { From b2ac87810d6027fa0823d9158c7477feeaed3e1a Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 12:55:19 +0200 Subject: [PATCH 0348/2286] gfs2: Introduce gfs2_inode_glock Introduce gfs2_inode_glock(inode) for getting from an inode to its inode glock. This obsoletes the local variable 'ip' in several places. The remaining direct accesses to ip->i_gl are for inode creation and distruction, and will be cleaned up in the next patch. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/acl.c | 11 +++--- fs/gfs2/aops.c | 17 ++++----- fs/gfs2/bmap.c | 48 +++++++++++++++---------- fs/gfs2/dentry.c | 6 ++-- fs/gfs2/dir.c | 57 ++++++++++++++++++------------ fs/gfs2/export.c | 7 ++-- fs/gfs2/file.c | 69 ++++++++++++++++++++---------------- fs/gfs2/glock.c | 4 ++- fs/gfs2/glops.c | 3 +- fs/gfs2/incore.h | 5 ++- fs/gfs2/inode.c | 83 ++++++++++++++++++++++++-------------------- fs/gfs2/lops.c | 18 +++++----- fs/gfs2/meta_io.c | 7 ++-- fs/gfs2/ops_fstype.c | 25 +++++++------ fs/gfs2/quota.c | 29 ++++++++-------- fs/gfs2/recovery.c | 12 +++---- fs/gfs2/rgrp.c | 4 +-- fs/gfs2/super.c | 70 +++++++++++++++++++------------------ fs/gfs2/util.c | 8 ++--- fs/gfs2/xattr.c | 69 +++++++++++++++++++++--------------- 20 files changed, 307 insertions(+), 245 deletions(-) diff --git a/fs/gfs2/acl.c b/fs/gfs2/acl.c index a5b60778b91c90..49e489fe27ef59 100644 --- a/fs/gfs2/acl.c +++ b/fs/gfs2/acl.c @@ -59,7 +59,7 @@ static struct posix_acl *__gfs2_get_acl(struct inode *inode, int type) struct posix_acl *gfs2_get_acl(struct inode *inode, int type, bool rcu) { - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; bool need_unlock = false; struct posix_acl *acl; @@ -67,8 +67,8 @@ struct posix_acl *gfs2_get_acl(struct inode *inode, int type, bool rcu) if (rcu) return ERR_PTR(-ECHILD); - if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { - int ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, + if (!gfs2_glock_is_locked_by_me(gl)) { + int ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); if (ret) return ERR_PTR(ret); @@ -106,6 +106,7 @@ int gfs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct inode *inode = d_inode(dentry); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; bool need_unlock = false; @@ -119,8 +120,8 @@ int gfs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, if (ret) return ret; - if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + if (!gfs2_glock_is_locked_by_me(gl)) { + ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &gh); if (ret) goto out; need_unlock = true; diff --git a/fs/gfs2/aops.c b/fs/gfs2/aops.c index 0a7b8076af3a5e..ee11d494e47c52 100644 --- a/fs/gfs2/aops.c +++ b/fs/gfs2/aops.c @@ -102,7 +102,7 @@ static int __gfs2_jdata_write_folio(struct folio *folio, struct writeback_control *wbc) { struct inode *inode = folio->mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); if (folio_test_checked(folio)) { folio_clear_checked(folio); @@ -111,7 +111,7 @@ static int __gfs2_jdata_write_folio(struct folio *folio, inode->i_sb->s_blocksize, BIT(BH_Dirty)|BIT(BH_Uptodate)); } - gfs2_trans_add_databufs(ip->i_gl, folio, 0, folio_size(folio)); + gfs2_trans_add_databufs(gl, folio, 0, folio_size(folio)); } return gfs2_write_jdata_folio(folio, wbc); } @@ -126,13 +126,13 @@ static int __gfs2_jdata_write_folio(struct folio *folio, int gfs2_jdata_writeback(struct address_space *mapping, struct writeback_control *wbc) { struct inode *inode = mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(mapping->host); struct folio *folio = NULL; int error; BUG_ON(current->journal_info); - if (gfs2_assert_withdraw(sdp, ip->i_gl->gl_state == LM_ST_EXCLUSIVE)) + if (gfs2_assert_withdraw(sdp, gl->gl_state == LM_ST_EXCLUSIVE)) return 0; while ((folio = writeback_iter(mapping, wbc, folio, &error))) { @@ -362,14 +362,14 @@ static int gfs2_write_cache_jdata(struct address_space *mapping, static int gfs2_jdata_writepages(struct address_space *mapping, struct writeback_control *wbc) { - struct gfs2_inode *ip = GFS2_I(mapping->host); + struct gfs2_glock *gl = gfs2_inode_glock(mapping->host); struct gfs2_sbd *sdp = GFS2_SB(mapping->host); int ret; ret = gfs2_write_cache_jdata(mapping, wbc); if (ret == 0 && wbc->sync_mode == WB_SYNC_ALL) { - gfs2_log_flush(sdp, ip->i_gl, GFS2_LOG_HEAD_FLUSH_NORMAL | - GFS2_LFC_JDATA_WPAGES); + gfs2_log_flush(sdp, gl, GFS2_LOG_HEAD_FLUSH_NORMAL | + GFS2_LFC_JDATA_WPAGES); ret = gfs2_write_cache_jdata(mapping, wbc); } return ret; @@ -561,12 +561,13 @@ static bool gfs2_jdata_dirty_folio(struct address_space *mapping, static sector_t gfs2_bmap(struct address_space *mapping, sector_t lblock) { + struct gfs2_glock *gl = gfs2_inode_glock(mapping->host); struct gfs2_inode *ip = GFS2_I(mapping->host); struct gfs2_holder i_gh; sector_t dblock = 0; int error; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); if (error) return 0; diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 636139f463b062..b5ddd936f48925 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -55,6 +55,7 @@ static int gfs2_unstuffer_folio(struct gfs2_inode *ip, struct buffer_head *dibh, u64 block, struct folio *folio) { struct inode *inode = &ip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); if (!folio_test_uptodate(folio)) { void *kaddr = kmap_local_folio(folio, 0); @@ -78,7 +79,7 @@ static int gfs2_unstuffer_folio(struct gfs2_inode *ip, struct buffer_head *dibh, map_bh(bh, inode->i_sb, block); set_buffer_uptodate(bh); - gfs2_trans_add_data(ip->i_gl, bh); + gfs2_trans_add_data(gl, bh); } else { folio_mark_dirty(folio); gfs2_ordered_add_inode(ip); @@ -90,6 +91,7 @@ static int gfs2_unstuffer_folio(struct gfs2_inode *ip, struct buffer_head *dibh, static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) { struct inode *inode = &ip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct buffer_head *bh, *dibh; struct gfs2_dinode *di; u64 block = 0; @@ -125,7 +127,7 @@ static int __gfs2_unstuff_inode(struct gfs2_inode *ip, struct folio *folio) /* Set up the pointer to the new block */ - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); di = (struct gfs2_dinode *)dibh->b_data; gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode)); @@ -663,6 +665,7 @@ enum alloc_state { static int __gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, struct metapath *mp) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *dibh = metapath_dibh(mp); @@ -679,7 +682,7 @@ static int __gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, BUG_ON(dibh == NULL); BUG_ON(dblks < 1); - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); down_write(&ip->i_rw_mutex); @@ -723,7 +726,7 @@ static int __gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, } for (; i - 1 < mp->mp_fheight - ip->i_height && n > 0; i++, n--) - gfs2_indirect_init(mp, ip->i_gl, i, 0, bn++); + gfs2_indirect_init(mp, gl, i, 0, bn++); if (i - 1 == mp->mp_fheight - ip->i_height) { i--; gfs2_buffer_copy_tail(mp->mp_bh[i], @@ -749,9 +752,9 @@ static int __gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, fallthrough; /* To branching from existing tree */ case ALLOC_GROW_DEPTH: if (i > 1 && i < mp->mp_fheight) - gfs2_trans_add_meta(ip->i_gl, mp->mp_bh[i-1]); + gfs2_trans_add_meta(gl, mp->mp_bh[i-1]); for (; i < mp->mp_fheight && n > 0; i++, n--) - gfs2_indirect_init(mp, ip->i_gl, i, + gfs2_indirect_init(mp, gl, i, mp->mp_list[i-1], bn++); if (i == mp->mp_fheight) state = ALLOC_DATA; @@ -761,7 +764,7 @@ static int __gfs2_iomap_alloc(struct inode *inode, struct iomap *iomap, case ALLOC_DATA: BUG_ON(n > dblks); BUG_ON(mp->mp_bh[end_of_metadata] == NULL); - gfs2_trans_add_meta(ip->i_gl, mp->mp_bh[end_of_metadata]); + gfs2_trans_add_meta(gl, mp->mp_bh[end_of_metadata]); dblks = n; ptr = metapointer(end_of_metadata, mp); iomap->addr = bn << inode->i_blkbits; @@ -990,12 +993,12 @@ static void gfs2_iomap_put_folio(struct inode *inode, loff_t pos, unsigned copied, struct folio *folio) { struct gfs2_trans *tr = current->journal_info; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); if (gfs2_is_jdata(ip) && !gfs2_is_stuffed(ip)) - gfs2_trans_add_databufs(ip->i_gl, folio, - offset_in_folio(folio, pos), + gfs2_trans_add_databufs(gl, folio, offset_in_folio(folio, pos), copied); folio_unlock(folio); @@ -1151,6 +1154,7 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, ssize_t written, unsigned flags, struct iomap *iomap) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); @@ -1197,7 +1201,7 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, if (iomap->flags & IOMAP_F_SIZE_CHANGED) mark_inode_dirty(inode); - set_bit(GLF_DIRTY, &ip->i_gl->gl_flags); + set_bit(GLF_DIRTY, &gl->gl_flags); return 0; } @@ -1388,6 +1392,7 @@ static int gfs2_journaled_truncate(struct inode *inode, u64 oldsize, u64 newsize static int trunc_start(struct inode *inode, u64 newsize) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *dibh = NULL; @@ -1416,7 +1421,7 @@ static int trunc_start(struct inode *inode, u64 newsize) if (error) goto out; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); if (gfs2_is_stuffed(ip)) gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode) + newsize); @@ -1490,6 +1495,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, bool meta, u32 *btotal) { struct inode *inode = &ip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_rgrpd *rgd; struct gfs2_trans *tr; @@ -1589,7 +1595,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, goto out_unlock; } - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); buf_in_tr = true; *p = 0; if (bstart + blen == bn) { @@ -1623,7 +1629,7 @@ static int sweep_bh_for_rgrps(struct gfs2_inode *ip, struct gfs2_holder *rd_gh, /* Every transaction boundary, we rewrite the dinode to keep its di_blocks current in case of failure. */ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); up_write(&ip->i_rw_mutex); @@ -1749,6 +1755,7 @@ static inline bool walk_done(struct gfs2_sbd *sdp, static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) { struct inode *inode = &ip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); u64 maxsize = sdp->sd_heightsize[ip->i_height]; struct metapath mp = {}; @@ -1836,7 +1843,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) for (mp_h = 0; mp_h < mp.mp_aheight - 1; mp_h++) { metapointer_range(&mp, mp_h, start_list, start_aligned, end_list, end_aligned, &start, &end); - gfs2_metapath_ra(ip->i_gl, start, end); + gfs2_metapath_ra(gl, start, end); } if (mp.mp_aheight == ip->i_height) @@ -1956,7 +1963,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) start_list, start_aligned, end_list, end_aligned, &start, &end); - gfs2_metapath_ra(ip->i_gl, start, end); + gfs2_metapath_ra(gl, start, end); } } @@ -1990,7 +1997,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) gfs2_statfs_change(sdp, 0, +btotal, 0); gfs2_quota_change(ip, -(s64)btotal, inode->i_uid, inode->i_gid); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); up_write(&ip->i_rw_mutex); gfs2_trans_end(sdp); @@ -2013,6 +2020,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) static int trunc_end(struct gfs2_inode *ip) { struct inode *inode = &ip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *dibh; int error; @@ -2036,7 +2044,7 @@ static int trunc_end(struct gfs2_inode *ip) inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); ip->i_diskflags &= ~GFS2_DIF_TRUNC_IN_PROG; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); @@ -2097,6 +2105,7 @@ static int do_shrink(struct inode *inode, u64 newsize) static int do_grow(struct inode *inode, u64 size) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_alloc_parms ap = { .target = 1, }; @@ -2141,7 +2150,7 @@ static int do_grow(struct inode *inode, u64 size) truncate_setsize(inode, size); inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode)); - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); @@ -2380,6 +2389,7 @@ int gfs2_write_alloc_required(struct gfs2_inode *ip, u64 offset, static int stuffed_zero_range(struct inode *inode, loff_t offset, loff_t length) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct buffer_head *dibh; int error; @@ -2392,7 +2402,7 @@ static int stuffed_zero_range(struct inode *inode, loff_t offset, loff_t length) error = gfs2_meta_inode_buffer(ip, &dibh); if (error) return error; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); memset(dibh->b_data + sizeof(struct gfs2_dinode) + offset, 0, length); brelse(dibh); diff --git a/fs/gfs2/dentry.c b/fs/gfs2/dentry.c index 95050e719233db..7b344461658ec3 100644 --- a/fs/gfs2/dentry.c +++ b/fs/gfs2/dentry.c @@ -35,8 +35,8 @@ static int gfs2_drevalidate(struct inode *dir, const struct qstr *name, struct dentry *dentry, unsigned int flags) { + struct gfs2_glock *gl = gfs2_inode_glock(dir); struct gfs2_sbd *sdp = GFS2_SB(dir); - struct gfs2_inode *dip = GFS2_I(dir); struct inode *inode; struct gfs2_holder d_gh; struct gfs2_inode *ip = NULL; @@ -57,9 +57,9 @@ static int gfs2_drevalidate(struct inode *dir, const struct qstr *name, if (sdp->sd_lockstruct.ls_ops->lm_mount == NULL) return 1; - had_lock = (gfs2_glock_is_locked_by_me(dip->i_gl) != NULL); + had_lock = (gfs2_glock_is_locked_by_me(gl) != NULL); if (!had_lock) { - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &d_gh); if (error) return 0; } diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c index f6111276ebb072..6cfe335fd5909f 100644 --- a/fs/gfs2/dir.c +++ b/fs/gfs2/dir.c @@ -90,10 +90,11 @@ typedef int (*gfs2_dscan_t)(const struct gfs2_dirent *dent, int gfs2_dir_get_new_buffer(struct gfs2_inode *ip, u64 block, struct buffer_head **bhp) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct buffer_head *bh; - bh = gfs2_meta_new(ip->i_gl, block); - gfs2_trans_add_meta(ip->i_gl, bh); + bh = gfs2_meta_new(gl, block); + gfs2_trans_add_meta(gl, bh); gfs2_metatype_set(bh, GFS2_METATYPE_JD, GFS2_FORMAT_JD); gfs2_buffer_clear_tail(bh, sizeof(struct gfs2_meta_header)); *bhp = bh; @@ -103,10 +104,11 @@ int gfs2_dir_get_new_buffer(struct gfs2_inode *ip, u64 block, static int gfs2_dir_get_existing_buffer(struct gfs2_inode *ip, u64 block, struct buffer_head **bhp) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct buffer_head *bh; int error; - error = gfs2_meta_read(ip->i_gl, block, DIO_WAIT, 0, &bh); + error = gfs2_meta_read(gl, block, DIO_WAIT, 0, &bh); if (error) return error; if (gfs2_metatype_check(GFS2_SB(&ip->i_inode), bh, GFS2_METATYPE_JD)) { @@ -120,6 +122,7 @@ static int gfs2_dir_get_existing_buffer(struct gfs2_inode *ip, u64 block, static int gfs2_dir_write_stuffed(struct gfs2_inode *ip, const char *buf, unsigned int offset, unsigned int size) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct buffer_head *dibh; int error; @@ -127,7 +130,7 @@ static int gfs2_dir_write_stuffed(struct gfs2_inode *ip, const char *buf, if (error) return error; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); memcpy(dibh->b_data + offset + sizeof(struct gfs2_dinode), buf, size); if (ip->i_inode.i_size < offset + size) i_size_write(&ip->i_inode, offset + size); @@ -153,6 +156,7 @@ static int gfs2_dir_write_stuffed(struct gfs2_inode *ip, const char *buf, static int gfs2_dir_write_data(struct gfs2_inode *ip, const char *buf, u64 offset, unsigned int size) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct buffer_head *dibh; u64 lblock, dblock; @@ -208,7 +212,7 @@ static int gfs2_dir_write_data(struct gfs2_inode *ip, const char *buf, if (error) goto fail; - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); memcpy(bh->b_data + o, buf, amount); brelse(bh); @@ -230,7 +234,7 @@ static int gfs2_dir_write_data(struct gfs2_inode *ip, const char *buf, i_size_write(&ip->i_inode, offset + copied); inode_set_mtime_to_ts(&ip->i_inode, inode_set_ctime_current(&ip->i_inode)); - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); @@ -268,6 +272,7 @@ static int gfs2_dir_read_stuffed(struct gfs2_inode *ip, __be64 *buf, static int gfs2_dir_read_data(struct gfs2_inode *ip, __be64 *buf, unsigned int size) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); u64 lblock, dblock; u32 extlen = 0; @@ -299,9 +304,9 @@ static int gfs2_dir_read_data(struct gfs2_inode *ip, __be64 *buf, if (error || !dblock) goto fail; BUG_ON(extlen < 1); - bh = gfs2_meta_ra(ip->i_gl, dblock, extlen); + bh = gfs2_meta_ra(gl, dblock, extlen); } else { - error = gfs2_meta_read(ip->i_gl, dblock, DIO_WAIT, 0, &bh); + error = gfs2_meta_read(gl, dblock, DIO_WAIT, 0, &bh); if (error) goto fail; } @@ -672,6 +677,7 @@ static int dirent_next(struct gfs2_inode *dip, struct buffer_head *bh, static void dirent_del(struct gfs2_inode *dip, struct buffer_head *bh, struct gfs2_dirent *prev, struct gfs2_dirent *cur) { + struct gfs2_glock *gl = gfs2_inode_glock(&dip->i_inode); u16 cur_rec_len, prev_rec_len; if (gfs2_dirent_sentinel(cur)) { @@ -679,7 +685,7 @@ static void dirent_del(struct gfs2_inode *dip, struct buffer_head *bh, return; } - gfs2_trans_add_meta(dip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); /* If there is no prev entry, this is the first entry in the block. The de_rec_len is already as big as it needs to be. Just zero @@ -712,13 +718,13 @@ static struct gfs2_dirent *do_init_dirent(struct inode *inode, struct buffer_head *bh, unsigned offset) { - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_dirent *ndent; unsigned totlen; totlen = be16_to_cpu(dent->de_rec_len); BUG_ON(offset + name->len > totlen); - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); ndent = (struct gfs2_dirent *)((char *)dent + offset); dent->de_rec_len = cpu_to_be16(offset); gfs2_qstr2dirent(name, totlen - offset, ndent); @@ -760,10 +766,11 @@ static int get_leaf(struct gfs2_inode *dip, u64 leaf_no, struct buffer_head **bhp) { struct inode *inode = &dip->i_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); int error; - error = gfs2_meta_read(dip->i_gl, leaf_no, DIO_WAIT, 0, bhp); + error = gfs2_meta_read(gl, leaf_no, DIO_WAIT, 0, bhp); if (!error && gfs2_metatype_check(sdp, *bhp, GFS2_METATYPE_LF)) { /* pr_info("block num=%llu\n", leaf_no); */ error = -EIO; @@ -865,6 +872,7 @@ static struct gfs2_dirent *gfs2_dirent_search(struct inode *inode, static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh, u16 depth) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); unsigned int n = 1; u64 bn; @@ -877,12 +885,12 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh, error = gfs2_alloc_blocks(ip, &bn, &n, 0); if (error) return NULL; - bh = gfs2_meta_new(ip->i_gl, bn); + bh = gfs2_meta_new(gl, bn); if (!bh) return NULL; gfs2_trans_remove_revoke(GFS2_SB(inode), bn, 1); - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); gfs2_metatype_set(bh, GFS2_METATYPE_LF, GFS2_FORMAT_LF); leaf = (struct gfs2_leaf *)bh->b_data; leaf->lf_depth = cpu_to_be16(depth); @@ -909,6 +917,7 @@ static struct gfs2_leaf *new_leaf(struct inode *inode, struct buffer_head **pbh, static int dir_make_exhash(struct inode *inode) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *dip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_dirent *dent; @@ -970,7 +979,7 @@ static int dir_make_exhash(struct inode *inode) /* We're done with the new leaf block, now setup the new hash table. */ - gfs2_trans_add_meta(dip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_buffer_clear_tail(dibh, sizeof(struct gfs2_dinode)); lp = (__be64 *)(dibh->b_data + sizeof(struct gfs2_dinode)); @@ -1000,6 +1009,7 @@ static int dir_make_exhash(struct inode *inode) static int dir_split_leaf(struct inode *inode, const struct qstr *name) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *dip = GFS2_I(inode); struct buffer_head *nbh, *obh, *dibh; struct gfs2_leaf *nleaf, *oleaf; @@ -1027,7 +1037,7 @@ static int dir_split_leaf(struct inode *inode, const struct qstr *name) return 1; /* can't split */ } - gfs2_trans_add_meta(dip->i_gl, obh); + gfs2_trans_add_meta(gl, obh); nleaf = new_leaf(inode, &nbh, be16_to_cpu(oleaf->lf_depth) + 1); if (!nleaf) { @@ -1120,7 +1130,7 @@ static int dir_split_leaf(struct inode *inode, const struct qstr *name) error = gfs2_meta_inode_buffer(dip, &dibh); if (!gfs2_assert_withdraw(GFS2_SB(&dip->i_inode), !error)) { - gfs2_trans_add_meta(dip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_add_inode_blocks(&dip->i_inode, 1); gfs2_dinode_out(dip, dibh->b_data); brelse(dibh); @@ -1482,8 +1492,8 @@ static int gfs2_dir_read_leaf(struct inode *inode, struct dir_context *ctx, static void gfs2_dir_readahead(struct inode *inode, unsigned hsize, u32 index, struct file_ra_state *f_ra) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_glock *gl = ip->i_gl; struct buffer_head *bh; u64 blocknr = 0, last; unsigned count; @@ -1724,6 +1734,7 @@ int gfs2_dir_check(struct inode *dir, const struct qstr *name, static int dir_new_leaf(struct inode *inode, const struct qstr *name) { struct buffer_head *bh, *obh; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_leaf *leaf, *oleaf; u32 dist = 1; @@ -1747,7 +1758,7 @@ static int dir_new_leaf(struct inode *inode, const struct qstr *name) return error; } while(1); - gfs2_trans_add_meta(ip->i_gl, obh); + gfs2_trans_add_meta(gl, obh); leaf = new_leaf(inode, &bh, be16_to_cpu(oleaf->lf_depth)); if (!leaf) { @@ -1762,7 +1773,7 @@ static int dir_new_leaf(struct inode *inode, const struct qstr *name) error = gfs2_meta_inode_buffer(ip, &bh); if (error) return error; - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); gfs2_add_inode_blocks(&ip->i_inode, 1); gfs2_dinode_out(ip, bh->b_data); brelse(bh); @@ -1937,6 +1948,7 @@ int gfs2_dir_del(struct gfs2_inode *dip, const struct dentry *dentry) int gfs2_dir_mvino(struct gfs2_inode *dip, const struct qstr *filename, const struct gfs2_inode *nip, unsigned int new_type) { + struct gfs2_glock *gl = gfs2_inode_glock(&dip->i_inode); struct buffer_head *bh; struct gfs2_dirent *dent; @@ -1948,7 +1960,7 @@ int gfs2_dir_mvino(struct gfs2_inode *dip, const struct qstr *filename, if (IS_ERR(dent)) return PTR_ERR(dent); - gfs2_trans_add_meta(dip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); gfs2_inum_out(nip, dent); dent->de_type = cpu_to_be16(new_type); brelse(bh); @@ -1974,6 +1986,7 @@ static int leaf_dealloc(struct gfs2_inode *dip, u32 index, u32 len, u64 leaf_no, struct buffer_head *leaf_bh, int last_dealloc) { + struct gfs2_glock *gl = gfs2_inode_glock(&dip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&dip->i_inode); struct gfs2_leaf *tmp_leaf; struct gfs2_rgrp_list rlist; @@ -2068,7 +2081,7 @@ static int leaf_dealloc(struct gfs2_inode *dip, u32 index, u32 len, if (error) goto out_end_trans; - gfs2_trans_add_meta(dip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); /* On the last dealloc, make this a regular file in case we crash. (We don't want to free these blocks a second time.) */ if (last_dealloc) diff --git a/fs/gfs2/export.c b/fs/gfs2/export.c index 3334c394ce9cbe..970bf2d723884b 100644 --- a/fs/gfs2/export.c +++ b/fs/gfs2/export.c @@ -87,7 +87,8 @@ static int gfs2_get_name(struct dentry *parent, char *name, { struct inode *dir = d_inode(parent); struct inode *inode = d_inode(child); - struct gfs2_inode *dip, *ip; + struct gfs2_glock *gl; + struct gfs2_inode *ip; struct get_name_filldir gnfd = { .ctx.actor = get_name_filldir, .name = name @@ -102,14 +103,14 @@ static int gfs2_get_name(struct dentry *parent, char *name, if (!S_ISDIR(dir->i_mode) || !inode) return -EINVAL; - dip = GFS2_I(dir); + gl = gfs2_inode_glock(dir); ip = GFS2_I(inode); *name = 0; gnfd.inum.no_addr = ip->i_no_addr; gnfd.inum.no_formal_ino = ip->i_no_formal_ino; - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &gh); if (error) return error; diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index 164e160e9064a9..6fb2adeef2741b 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -57,13 +57,13 @@ static loff_t gfs2_llseek(struct file *file, loff_t offset, int whence) { - struct gfs2_inode *ip = GFS2_I(file->f_mapping->host); + struct gfs2_glock *gl = gfs2_inode_glock(file->f_mapping->host); struct gfs2_holder i_gh; loff_t error; switch (whence) { case SEEK_END: - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); if (!error) { error = generic_file_llseek(file, offset, whence); @@ -105,11 +105,11 @@ static loff_t gfs2_llseek(struct file *file, loff_t offset, int whence) static int gfs2_readdir(struct file *file, struct dir_context *ctx) { struct inode *dir = file->f_mapping->host; - struct gfs2_inode *dip = GFS2_I(dir); + struct gfs2_glock *gl = gfs2_inode_glock(dir); struct gfs2_holder d_gh; int error; - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &d_gh); if (error) return error; @@ -158,6 +158,7 @@ static inline u32 gfs2_gfsflags_to_fsflags(struct inode *inode, u32 gfsflags) int gfs2_fileattr_get(struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; int error; @@ -166,7 +167,7 @@ int gfs2_fileattr_get(struct dentry *dentry, struct file_kattr *fa) if (d_is_special(dentry)) return -ENOTTY; - gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + gfs2_holder_init(gl, LM_ST_SHARED, 0, &gh); error = gfs2_glock_nq(&gh); if (error) goto out_uninit; @@ -218,6 +219,7 @@ void gfs2_set_inode_flags(struct inode *inode) */ static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *bh; @@ -225,7 +227,7 @@ static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask) int error; u32 new_flags, flags; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &gh); if (error) return error; @@ -242,7 +244,7 @@ static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask) } if ((flags ^ new_flags) & GFS2_DIF_JDATA) { if (new_flags & GFS2_DIF_JDATA) - gfs2_log_flush(sdp, ip->i_gl, + gfs2_log_flush(sdp, gl, GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_SET_FLAGS); error = filemap_fdatawrite(inode->i_mapping); @@ -262,7 +264,7 @@ static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask) if (error) goto out_trans_end; inode_set_ctime_current(inode); - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); ip->i_diskflags = new_flags; gfs2_dinode_out(ip, bh->b_data); brelse(bh); @@ -417,6 +419,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf) { struct folio *folio = page_folio(vmf->page); struct inode *inode = file_inode(vmf->vma->vm_file); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_alloc_parms ap = {}; @@ -430,7 +433,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf) sb_start_pagefault(inode->i_sb); - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + gfs2_holder_init(gl, LM_ST_EXCLUSIVE, 0, &gh); err = gfs2_glock_nq(&gh); if (err) { ret = vmf_fs_error(err); @@ -455,7 +458,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf) gfs2_size_hint(vmf->vma->vm_file, pos, length); - set_bit(GLF_DIRTY, &ip->i_gl->gl_flags); + set_bit(GLF_DIRTY, &gl->gl_flags); set_bit(GIF_SW_PAGED, &ip->i_flags); /* @@ -552,12 +555,12 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf) static vm_fault_t gfs2_fault(struct vm_fault *vmf) { struct inode *inode = file_inode(vmf->vma->vm_file); - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; vm_fault_t ret; int err; - gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + gfs2_holder_init(gl, LM_ST_SHARED, 0, &gh); err = gfs2_glock_nq(&gh); if (err) { ret = vmf_fs_error(err); @@ -590,6 +593,7 @@ static const struct vm_operations_struct gfs2_vm_ops = { static int gfs2_mmap(struct file *file, struct vm_area_struct *vma) { + struct gfs2_glock *gl = gfs2_inode_glock(file->f_mapping->host); struct gfs2_inode *ip = GFS2_I(file->f_mapping->host); if (!(file->f_flags & O_NOATIME) && @@ -597,7 +601,7 @@ static int gfs2_mmap(struct file *file, struct vm_area_struct *vma) struct gfs2_holder i_gh; int error; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); if (error) return error; @@ -674,13 +678,14 @@ int gfs2_open_common(struct inode *inode, struct file *file) static int gfs2_open(struct inode *inode, struct file *file) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder i_gh; int error; bool need_unlock = false; if (S_ISREG(ip->i_inode.i_mode)) { - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); if (error) return error; @@ -745,6 +750,7 @@ static int gfs2_fsync(struct file *file, loff_t start, loff_t end, struct address_space *mapping = file->f_mapping; struct inode *inode = mapping->host; int sync_state = inode_state_read_once(inode) & I_DIRTY; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); int ret = 0, ret1 = 0; @@ -767,7 +773,7 @@ static int gfs2_fsync(struct file *file, loff_t start, loff_t end, ret = file_write_and_wait(file); if (ret) return ret; - gfs2_ail_flush(ip->i_gl, 1); + gfs2_ail_flush(gl, 1); } if (mapping->nrpages) @@ -813,7 +819,7 @@ static ssize_t gfs2_file_direct_read(struct kiocb *iocb, struct iov_iter *to, { struct file *file = iocb->ki_filp; struct inode *inode = file->f_mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); size_t prev_count = 0, window_size = 0; size_t read = 0; ssize_t ret; @@ -838,7 +844,7 @@ static ssize_t gfs2_file_direct_read(struct kiocb *iocb, struct iov_iter *to, if (!iov_iter_count(to)) return 0; /* skip atime */ - gfs2_holder_init(ip->i_gl, LM_ST_DEFERRED, 0, gh); + gfs2_holder_init(gl, LM_ST_DEFERRED, 0, gh); retry: ret = gfs2_glock_nq(gh); if (ret) @@ -877,7 +883,7 @@ static ssize_t gfs2_file_direct_write(struct kiocb *iocb, struct iov_iter *from, { struct file *file = iocb->ki_filp; struct inode *inode = file->f_mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); size_t prev_count = 0, window_size = 0; size_t written = 0; bool enough_retries; @@ -901,7 +907,7 @@ static ssize_t gfs2_file_direct_write(struct kiocb *iocb, struct iov_iter *from, * unfortunately, have the option of only flushing a range like the * VFS does. */ - gfs2_holder_init(ip->i_gl, LM_ST_DEFERRED, 0, gh); + gfs2_holder_init(gl, LM_ST_DEFERRED, 0, gh); retry: ret = gfs2_glock_nq(gh); if (ret) @@ -949,7 +955,7 @@ static ssize_t gfs2_file_direct_write(struct kiocb *iocb, struct iov_iter *from, static ssize_t gfs2_file_read_iter(struct kiocb *iocb, struct iov_iter *to) { - struct gfs2_inode *ip; + struct gfs2_glock *gl; struct gfs2_holder gh; size_t prev_count = 0, window_size = 0; size_t read = 0; @@ -980,8 +986,8 @@ static ssize_t gfs2_file_read_iter(struct kiocb *iocb, struct iov_iter *to) if (iocb->ki_flags & IOCB_NOWAIT) return ret; } - ip = GFS2_I(iocb->ki_filp->f_mapping->host); - gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + gl = gfs2_inode_glock(iocb->ki_filp->f_mapping->host); + gfs2_holder_init(gl, LM_ST_SHARED, 0, &gh); retry: ret = gfs2_glock_nq(&gh); if (ret) @@ -1014,7 +1020,7 @@ static ssize_t gfs2_file_buffered_write(struct kiocb *iocb, { struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_holder *statfs_gh = NULL; size_t prev_count = 0, window_size = 0; @@ -1035,7 +1041,7 @@ static ssize_t gfs2_file_buffered_write(struct kiocb *iocb, return -ENOMEM; } - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, gh); + gfs2_holder_init(gl, LM_ST_EXCLUSIVE, 0, gh); if (should_fault_in_pages(from, iocb, &prev_count, &window_size)) { retry: window_size -= fault_in_iov_iter_readable(from, window_size); @@ -1050,9 +1056,9 @@ static ssize_t gfs2_file_buffered_write(struct kiocb *iocb, goto out_uninit; if (inode == sdp->sd_rindex) { - struct gfs2_inode *m_ip = GFS2_I(sdp->sd_statfs_inode); + struct gfs2_glock *m_gl = gfs2_inode_glock(sdp->sd_statfs_inode); - ret = gfs2_glock_nq_init(m_ip->i_gl, LM_ST_EXCLUSIVE, + ret = gfs2_glock_nq_init(m_gl, LM_ST_EXCLUSIVE, GL_NOCACHE, statfs_gh); if (ret) goto out_unlock; @@ -1106,14 +1112,15 @@ static ssize_t gfs2_file_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); - struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; ssize_t ret; gfs2_size_hint(file, iocb->ki_pos, iov_iter_count(from)); if (iocb->ki_flags & IOCB_APPEND) { - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + struct gfs2_glock *gl = gfs2_inode_glock(inode); + + ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &gh); if (ret) return ret; gfs2_glock_dq_uninit(&gh); @@ -1181,6 +1188,7 @@ static ssize_t gfs2_file_write_iter(struct kiocb *iocb, struct iov_iter *from) static int fallocate_chunk(struct inode *inode, loff_t offset, loff_t len) { struct super_block *sb = inode->i_sb; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); loff_t end = offset + len; struct buffer_head *dibh; @@ -1190,7 +1198,7 @@ static int fallocate_chunk(struct inode *inode, loff_t offset, loff_t len) if (unlikely(error)) return error; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); if (gfs2_is_stuffed(ip)) { error = gfs2_unstuff_dinode(ip); @@ -1379,6 +1387,7 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le { struct inode *inode = file_inode(file); struct gfs2_sbd *sdp = GFS2_SB(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; int ret; @@ -1391,7 +1400,7 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le inode_lock(inode); - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + gfs2_holder_init(gl, LM_ST_EXCLUSIVE, 0, &gh); ret = gfs2_glock_nq(&gh); if (ret) goto out_uninit; diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index d59ea71a84db06..d0612014408e43 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -891,7 +891,9 @@ static void gfs2_try_to_evict(struct gfs2_glock *gl) /* If the inode was evicted, gl->gl_object will now be NULL. */ ip = gfs2_grab_existing_inode(gl); if (ip) { - gfs2_glock_poke(ip->i_gl); + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); + + gfs2_glock_poke(gl); iput(&ip->i_inode); } } diff --git a/fs/gfs2/glops.c b/fs/gfs2/glops.c index 28f32424ee64bf..662d033fd2cafa 100644 --- a/fs/gfs2/glops.c +++ b/fs/gfs2/glops.c @@ -602,8 +602,7 @@ static void freeze_go_callback(struct gfs2_glock *gl, bool remote) static int freeze_go_xmote_bh(struct gfs2_glock *gl) { struct gfs2_sbd *sdp = glock_sbd(gl); - struct gfs2_inode *ip = GFS2_I(sdp->sd_jdesc->jd_inode); - struct gfs2_glock *j_gl = ip->i_gl; + struct gfs2_glock *j_gl = gfs2_inode_glock(sdp->sd_jdesc->jd_inode); struct gfs2_log_header_host head; int error; diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index dadb4d3c9d3d61..3ae8e2be486c90 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -879,5 +879,8 @@ static inline unsigned gfs2_max_stuffed_size(const struct gfs2_inode *ip) return GFS2_SB(&ip->i_inode)->sd_sb.sb_bsize - sizeof(struct gfs2_dinode); } +static inline struct gfs2_glock *gfs2_inode_glock(struct inode *inode) +{ + return GFS2_I(inode)->i_gl; +} #endif /* __INCORE_DOT_H__ */ - diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 438519bc06d496..5aaed0018fb338 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -324,8 +324,8 @@ struct inode *gfs2_lookup_meta(struct inode *dip, const char *name) struct inode *gfs2_lookupi(struct inode *dir, const struct qstr *name, int is_root) { + struct gfs2_glock *gl = gfs2_inode_glock(dir); struct super_block *sb = dir->i_sb; - struct gfs2_inode *dip = GFS2_I(dir); struct gfs2_holder d_gh; int error = 0; struct inode *inode = NULL; @@ -341,8 +341,8 @@ struct inode *gfs2_lookupi(struct inode *dir, const struct qstr *name, return dir; } - if (gfs2_glock_is_locked_by_me(dip->i_gl) == NULL) { - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh); + if (gfs2_glock_is_locked_by_me(gl) == NULL) { + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &d_gh); if (error) return ERR_PTR(error); } @@ -458,7 +458,7 @@ static int alloc_dinode(struct gfs2_inode *ip, u32 flags, unsigned *dblocks) static void gfs2_final_release_pages(struct gfs2_inode *ip) { struct inode *inode = &ip->i_inode; - struct gfs2_glock *gl = ip->i_gl; + struct gfs2_glock *gl = gfs2_inode_glock(inode); /* This can only happen during incomplete inode creation. */ if (unlikely(!gl)) @@ -548,12 +548,13 @@ static void gfs2_init_dir(struct buffer_head *dibh, static void gfs2_init_xattr(struct gfs2_inode *ip) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct buffer_head *bh; struct gfs2_ea_header *ea; - bh = gfs2_meta_new(ip->i_gl, ip->i_eattr); - gfs2_trans_add_meta(ip->i_gl, bh); + bh = gfs2_meta_new(gl, ip->i_eattr); + gfs2_trans_add_meta(gl, bh); gfs2_metatype_set(bh, GFS2_METATYPE_EA, GFS2_FORMAT_EA); gfs2_buffer_clear_tail(bh, sizeof(struct gfs2_meta_header)); @@ -576,11 +577,12 @@ static void gfs2_init_xattr(struct gfs2_inode *ip) static void init_dinode(struct gfs2_inode *dip, struct gfs2_inode *ip, const char *symname) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_dinode *di; struct buffer_head *dibh; - dibh = gfs2_meta_new(ip->i_gl, ip->i_no_addr); - gfs2_trans_add_meta(ip->i_gl, dibh); + dibh = gfs2_meta_new(gl, ip->i_no_addr); + gfs2_trans_add_meta(gl, dibh); di = (struct gfs2_dinode *)dibh->b_data; gfs2_dinode_out(ip, di); @@ -728,7 +730,8 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, if (error) goto fail; - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_EXCLUSIVE, 0, &d_gh); + error = gfs2_glock_nq_init(gfs2_inode_glock(dir), LM_ST_EXCLUSIVE, 0, + &d_gh); if (error) goto fail; gfs2_holder_mark_uninitialized(&gh); @@ -999,7 +1002,7 @@ static struct dentry *__gfs2_lookup(struct inode *dir, struct dentry *dentry, if (inode == NULL || IS_ERR(inode)) return d_splice_alias(inode, dentry); - gl = GFS2_I(inode)->i_gl; + gl = gfs2_inode_glock(inode); error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); if (error) { iput(inode); @@ -1059,8 +1062,8 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir, if (error) return error; - gfs2_holder_init(dip->i_gl, LM_ST_EXCLUSIVE, 0, &d_gh); - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + gfs2_holder_init(gfs2_inode_glock(dir), LM_ST_EXCLUSIVE, 0, &d_gh); + gfs2_holder_init(gfs2_inode_glock(inode), LM_ST_EXCLUSIVE, 0, &gh); error = gfs2_glock_nq(&d_gh); if (error) @@ -1133,7 +1136,7 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir, if (error) goto out_brelse; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gfs2_inode_glock(inode), dibh); inc_nlink(&ip->i_inode); inode_set_ctime_current(&ip->i_inode); ihold(inode); @@ -1259,8 +1262,8 @@ static int gfs2_unlink(struct inode *dir, struct dentry *dentry) error = -EROFS; - gfs2_holder_init(dip->i_gl, LM_ST_EXCLUSIVE, 0, &d_gh); - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + gfs2_holder_init(gfs2_inode_glock(dir), LM_ST_EXCLUSIVE, 0, &d_gh); + gfs2_holder_init(gfs2_inode_glock(inode), LM_ST_EXCLUSIVE, 0, &gh); rgd = gfs2_blk2rgrpd(sdp, ip->i_no_addr, 1); if (!rgd) @@ -1534,18 +1537,19 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, } num_gh = 1; - gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs); + gfs2_holder_init(gfs2_inode_glock(odir), LM_ST_EXCLUSIVE, GL_ASYNC, ghs); if (odip != ndip) { - gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE,GL_ASYNC, + gfs2_holder_init(gfs2_inode_glock(ndir), LM_ST_EXCLUSIVE,GL_ASYNC, ghs + num_gh); num_gh++; } - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); + gfs2_holder_init(gfs2_inode_glock(&ip->i_inode), LM_ST_EXCLUSIVE, + GL_ASYNC, ghs + num_gh); num_gh++; if (nip) { - gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, - ghs + num_gh); + gfs2_holder_init(gfs2_inode_glock(&nip->i_inode), + LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); num_gh++; } @@ -1780,16 +1784,19 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, } num_gh = 1; - gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs); + gfs2_holder_init(gfs2_inode_glock(odir), LM_ST_EXCLUSIVE, GL_ASYNC, + ghs); if (odip != ndip) { - gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, - ghs + num_gh); + gfs2_holder_init(gfs2_inode_glock(ndir), LM_ST_EXCLUSIVE, + GL_ASYNC, ghs + num_gh); num_gh++; } - gfs2_holder_init(oip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); + gfs2_holder_init(gfs2_inode_glock(&oip->i_inode), LM_ST_EXCLUSIVE, + GL_ASYNC, ghs + num_gh); num_gh++; - gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); + gfs2_holder_init(gfs2_inode_glock(&nip->i_inode), LM_ST_EXCLUSIVE, + GL_ASYNC, ghs + num_gh); num_gh++; again: @@ -1910,6 +1917,7 @@ static const char *gfs2_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder i_gh; struct buffer_head *dibh; @@ -1920,7 +1928,7 @@ static const char *gfs2_get_link(struct dentry *dentry, if (!dentry) return ERR_PTR(-ECHILD); - gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh); + gfs2_holder_init(gl, LM_ST_SHARED, 0, &i_gh); error = gfs2_glock_nq(&i_gh); if (error) { gfs2_holder_uninit(&i_gh); @@ -2102,6 +2110,7 @@ static int gfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder i_gh; int error; @@ -2110,7 +2119,7 @@ static int gfs2_setattr(struct mnt_idmap *idmap, if (error) return error; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &i_gh); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &i_gh); if (error) goto out; @@ -2164,14 +2173,15 @@ static int gfs2_getattr(struct mnt_idmap *idmap, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; u32 gfsflags; int error; gfs2_holder_mark_uninitialized(&gh); - if (gfs2_glock_is_locked_by_me(ip->i_gl) == NULL) { - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); + if (gfs2_glock_is_locked_by_me(gl) == NULL) { + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); if (error) return error; } @@ -2207,14 +2217,14 @@ static bool fault_in_fiemap(struct fiemap_extent_info *fi) static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len) { - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; int ret; inode_lock_shared(inode); retry: - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &gh); if (ret) goto out; @@ -2237,12 +2247,12 @@ static int gfs2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, loff_t gfs2_seek_data(struct file *file, loff_t offset) { struct inode *inode = file->f_mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; loff_t ret; inode_lock_shared(inode); - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &gh); if (!ret) ret = iomap_seek_data(inode, offset, &gfs2_iomap_ops); gfs2_glock_dq_uninit(&gh); @@ -2256,12 +2266,12 @@ loff_t gfs2_seek_data(struct file *file, loff_t offset) loff_t gfs2_seek_hole(struct file *file, loff_t offset) { struct inode *inode = file->f_mapping->host; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; loff_t ret; inode_lock_shared(inode); - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh); + ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &gh); if (!ret) ret = iomap_seek_hole(inode, offset, &gfs2_iomap_ops); gfs2_glock_dq_uninit(&gh); @@ -2275,8 +2285,7 @@ loff_t gfs2_seek_hole(struct file *file, loff_t offset) static int gfs2_update_time(struct inode *inode, enum fs_update_time type, unsigned int flags) { - struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_glock *gl = ip->i_gl; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder *gh; int error; diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index 6dabe73ad790d9..77ef22eab36844 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -775,9 +775,8 @@ static int buf_lo_scan_elements(struct gfs2_jdesc *jd, u32 start, struct gfs2_log_descriptor *ld, __be64 *ptr, int pass) { - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode); - struct gfs2_glock *gl = ip->i_gl; unsigned int blks = be32_to_cpu(ld->ld_data1); struct buffer_head *bh_log, *bh_ip; u64 blkno; @@ -828,17 +827,17 @@ static int buf_lo_scan_elements(struct gfs2_jdesc *jd, u32 start, static void buf_lo_after_scan(struct gfs2_jdesc *jd, int error, int pass) { - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode); if (error) { - gfs2_inode_metasync(ip->i_gl); + gfs2_inode_metasync(gl); return; } if (pass != 1) return; - gfs2_inode_metasync(ip->i_gl); + gfs2_inode_metasync(gl); fs_info(sdp, "jid=%u: Replayed %u of %u blocks\n", jd->jd_jid, jd->jd_replayed_blocks, jd->jd_found_blocks); @@ -1000,8 +999,7 @@ static int databuf_lo_scan_elements(struct gfs2_jdesc *jd, u32 start, struct gfs2_log_descriptor *ld, __be64 *ptr, int pass) { - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); - struct gfs2_glock *gl = ip->i_gl; + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); unsigned int blks = be32_to_cpu(ld->ld_data1); struct buffer_head *bh_log, *bh_ip; u64 blkno; @@ -1048,18 +1046,18 @@ static int databuf_lo_scan_elements(struct gfs2_jdesc *jd, u32 start, static void databuf_lo_after_scan(struct gfs2_jdesc *jd, int error, int pass) { - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode); if (error) { - gfs2_inode_metasync(ip->i_gl); + gfs2_inode_metasync(gl); return; } if (pass != 1) return; /* data sync? */ - gfs2_inode_metasync(ip->i_gl); + gfs2_inode_metasync(gl); fs_info(sdp, "jid=%u: Replayed %u of %u data blocks\n", jd->jd_jid, jd->jd_replayed_blocks, jd->jd_found_blocks); diff --git a/fs/gfs2/meta_io.c b/fs/gfs2/meta_io.c index a87cfbf0df3870..63591a4be10a01 100644 --- a/fs/gfs2/meta_io.c +++ b/fs/gfs2/meta_io.c @@ -402,18 +402,19 @@ static struct buffer_head *gfs2_getjdatabuf(struct gfs2_inode *ip, u64 blkno) void gfs2_journal_wipe(struct gfs2_inode *ip, u64 bstart, u32 blen) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct buffer_head *bh; int ty; /* This can only happen during incomplete inode creation. */ - if (!ip->i_gl) + if (!gl) return; gfs2_ail1_wipe(sdp, bstart, blen); while (blen) { ty = REMOVE_META; - bh = gfs2_getbuf(ip->i_gl, bstart, NO_CREATE); + bh = gfs2_getbuf(gl, bstart, NO_CREATE); if (!bh && gfs2_is_jdata(ip)) { bh = gfs2_getjdatabuf(ip, bstart); ty = REMOVE_JDATA; @@ -447,8 +448,8 @@ void gfs2_journal_wipe(struct gfs2_inode *ip, u64 bstart, u32 blen) int gfs2_meta_buffer(struct gfs2_inode *ip, u32 mtype, u64 num, struct buffer_head **bhp) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); - struct gfs2_glock *gl = ip->i_gl; struct buffer_head *bh; int ret = 0; int rahead = 0; diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index 718e0da7dfce02..188b3e67f2d1eb 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -534,7 +534,7 @@ static void gfs2_others_may_mount(struct gfs2_sbd *sdp) static int gfs2_jindex_hold(struct gfs2_sbd *sdp, struct gfs2_holder *ji_gh) { - struct gfs2_inode *dip = GFS2_I(sdp->sd_jindex); + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_jindex); struct qstr name; char buf[20]; struct gfs2_jdesc *jd; @@ -545,7 +545,7 @@ static int gfs2_jindex_hold(struct gfs2_sbd *sdp, struct gfs2_holder *ji_gh) mutex_lock(&sdp->sd_jindex_mutex); for (;;) { - error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, ji_gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, ji_gh); if (error) break; @@ -611,7 +611,6 @@ static int init_statfs(struct gfs2_sbd *sdp) struct inode *pn = NULL; char buf[30]; struct gfs2_jdesc *jd; - struct gfs2_inode *ip; sdp->sd_statfs_inode = gfs2_lookup_meta(master, "statfs"); if (IS_ERR(sdp->sd_statfs_inode)) { @@ -656,15 +655,15 @@ static int init_statfs(struct gfs2_sbd *sdp) iput(pn); pn = NULL; - ip = GFS2_I(sdp->sd_sc_inode); - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_NOPID, - &sdp->sd_sc_gh); + error = gfs2_glock_nq_init(gfs2_inode_glock(sdp->sd_sc_inode), + LM_ST_EXCLUSIVE, GL_NOPID, &sdp->sd_sc_gh); if (error) { fs_err(sdp, "can't lock local \"sc\" file: %d\n", error); goto free_local; } /* read in the local statfs buffer - other nodes don't change it. */ - error = gfs2_meta_inode_buffer(ip, &sdp->sd_sc_bh); + error = gfs2_meta_inode_buffer(GFS2_I(sdp->sd_sc_inode), + &sdp->sd_sc_bh); if (error) { fs_err(sdp, "Cannot read in local statfs: %d\n", error); goto unlock_sd_gh; @@ -697,7 +696,7 @@ static int init_journal(struct gfs2_sbd *sdp, int undo) { struct inode *master = d_inode(sdp->sd_master_dir); struct gfs2_holder ji_gh; - struct gfs2_inode *ip; + struct gfs2_glock *gl; int error = 0; gfs2_holder_mark_uninitialized(&ji_gh); @@ -751,8 +750,8 @@ static int init_journal(struct gfs2_sbd *sdp, int undo) goto fail_jindex; } - ip = GFS2_I(sdp->sd_jdesc->jd_inode); - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, + gl = gfs2_inode_glock(sdp->sd_jdesc->jd_inode); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_RECOVER | GL_EXACT | GL_NOCACHE | GL_NOPID, &sdp->sd_jinode_gh); @@ -893,7 +892,7 @@ static int init_per_node(struct gfs2_sbd *sdp, int undo) struct inode *pn = NULL; char buf[30]; int error = 0; - struct gfs2_inode *ip; + struct gfs2_glock *gl; struct inode *master = d_inode(sdp->sd_master_dir); if (sdp->sd_args.ar_spectator) @@ -920,8 +919,8 @@ static int init_per_node(struct gfs2_sbd *sdp, int undo) iput(pn); pn = NULL; - ip = GFS2_I(sdp->sd_qc_inode); - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_NOPID, + gl = gfs2_inode_glock(sdp->sd_qc_inode); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, GL_NOPID, &sdp->sd_qc_gh); if (error) { fs_err(sdp, "can't lock local \"qc\" file: %d\n", error); diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index 0cb2bb0aec7b23..b62431724eea88 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -408,7 +408,7 @@ static int bh_get(struct gfs2_quota_data *qd) { struct gfs2_sbd *sdp = qd->qd_sbd; struct inode *inode = sdp->sd_qc_inode; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); unsigned int block, offset; struct buffer_head *bh = NULL; struct iomap iomap = { }; @@ -434,7 +434,7 @@ static int bh_get(struct gfs2_quota_data *qd) if (iomap.type != IOMAP_MAPPED) return error; - error = gfs2_meta_read(ip->i_gl, iomap.addr >> inode->i_blkbits, + error = gfs2_meta_read(gl, iomap.addr >> inode->i_blkbits, DIO_WAIT, 0, &bh); if (error) return error; @@ -687,12 +687,12 @@ static int sort_qd(const void *a, const void *b) static void do_qc(struct gfs2_quota_data *qd, s64 change) { struct gfs2_sbd *sdp = qd->qd_sbd; - struct gfs2_inode *ip = GFS2_I(sdp->sd_qc_inode); + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_qc_inode); struct gfs2_quota_change *qc = qd->qd_bh_qc; bool needs_put = false; s64 x; - gfs2_trans_add_meta(ip->i_gl, qd->qd_bh); + gfs2_trans_add_meta(gl, qd->qd_bh); /* * The QDF_CHANGE flag indicates that the slot in the quota change file @@ -741,7 +741,7 @@ static int gfs2_write_buf_to_page(struct gfs2_sbd *sdp, unsigned long index, unsigned off, void *buf, unsigned bytes) { struct inode *inode = sdp->sd_quota_inode; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct address_space *mapping = inode->i_mapping; struct folio *folio; struct buffer_head *bh; @@ -780,7 +780,7 @@ static int gfs2_write_buf_to_page(struct gfs2_sbd *sdp, unsigned long index, set_buffer_uptodate(bh); if (bh_read(bh, REQ_META | REQ_PRIO) < 0) goto unlock_out; - gfs2_trans_add_data(ip->i_gl, bh); + gfs2_trans_add_data(gl, bh); /* If we need to write to the next block as well */ if (to_write > (bsize - boff)) { @@ -909,6 +909,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, { struct gfs2_sbd *sdp = (*qda)->qd_sbd; struct inode *inode = sdp->sd_quota_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_alloc_parms ap = {}; unsigned int data_blocks, ind_blocks; @@ -936,7 +937,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, goto out_dq; } - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &i_gh); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &i_gh); if (error) goto out_dq; @@ -994,8 +995,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda, gfs2_glock_dq_uninit(&ghs[qx]); inode_unlock(inode); kfree(ghs); - gfs2_log_flush(sdp, ip->i_gl, - GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_DO_SYNC); + gfs2_log_flush(sdp, gl, GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_DO_SYNC); if (!error) { for (x = 0; x < num_qd; x++) { qd = qda[x]; @@ -1039,7 +1039,7 @@ static int do_glock(struct gfs2_quota_data *qd, int force_refresh, struct gfs2_holder *q_gh) { struct gfs2_sbd *sdp = qd->qd_sbd; - struct gfs2_inode *ip = GFS2_I(sdp->sd_quota_inode); + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_quota_inode); struct gfs2_holder i_gh; int error; @@ -1063,7 +1063,7 @@ static int do_glock(struct gfs2_quota_data *qd, int force_refresh, if (error) return error; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &i_gh); if (error) goto fail; @@ -1404,7 +1404,7 @@ int gfs2_quota_refresh(struct gfs2_sbd *sdp, struct kqid qid) int gfs2_quota_init(struct gfs2_sbd *sdp) { struct inode *inode = sdp->sd_qc_inode; - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); u64 size = i_size_read(sdp->sd_qc_inode); unsigned int blocks = size >> sdp->sd_sb.sb_bsize_shift; unsigned int x, slot = 0; @@ -1441,7 +1441,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp) goto fail; } error = -EIO; - bh = gfs2_meta_ra(ip->i_gl, dblock, extlen); + bh = gfs2_meta_ra(gl, dblock, extlen); if (!bh) goto fail; if (gfs2_metatype_check(sdp, bh, GFS2_METATYPE_QC)) @@ -1717,6 +1717,7 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid, { struct gfs2_sbd *sdp = sb->s_fs_info; struct inode *inode = sdp->sd_quota_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_quota_data *qd; struct gfs2_holder q_gh, i_gh; @@ -1748,7 +1749,7 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid, error = gfs2_glock_nq_init(qd->qd_gl, LM_ST_EXCLUSIVE, 0, &q_gh); if (error) goto out_unlockput; - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &i_gh); + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &i_gh); if (error) goto out_q; diff --git a/fs/gfs2/recovery.c b/fs/gfs2/recovery.c index b45aa3032ca2a9..5b1f494dc3c054 100644 --- a/fs/gfs2/recovery.c +++ b/fs/gfs2/recovery.c @@ -33,8 +33,8 @@ int gfs2_replay_read_block(struct gfs2_jdesc *jd, unsigned int blk, struct buffer_head **bh) { struct inode *inode = jd->jd_inode; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_glock *gl = ip->i_gl; u64 dblock; u32 extlen; int error; @@ -307,15 +307,13 @@ static int update_statfs_inode(struct gfs2_jdesc *jd, struct inode *inode) { struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode); - struct gfs2_inode *ip; struct buffer_head *bh; struct gfs2_statfs_change_host sc; int error = 0; BUG_ON(!inode); - ip = GFS2_I(inode); - error = gfs2_meta_inode_buffer(ip, &bh); + error = gfs2_meta_inode_buffer(GFS2_I(inode), &bh); if (error) goto out; @@ -346,7 +344,7 @@ static int update_statfs_inode(struct gfs2_jdesc *jd, mark_buffer_dirty(bh); brelse(bh); - gfs2_inode_metasync(ip->i_gl); + gfs2_inode_metasync(gfs2_inode_glock(inode)); out: return error; @@ -399,7 +397,7 @@ static void recover_local_statfs(struct gfs2_jdesc *jd, void gfs2_recover_func(struct work_struct *work) { struct gfs2_jdesc *jd = container_of(work, struct gfs2_jdesc, jd_work); - struct gfs2_inode *ip = GFS2_I(jd->jd_inode); + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); struct gfs2_sbd *sdp = GFS2_SB(jd->jd_inode); struct gfs2_log_header_host head; struct gfs2_holder j_gh, ji_gh; @@ -441,7 +439,7 @@ void gfs2_recover_func(struct work_struct *work) goto fail; } - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_RECOVER | GL_NOCACHE, &ji_gh); if (error) diff --git a/fs/gfs2/rgrp.c b/fs/gfs2/rgrp.c index 5988a165a8301d..f6048a73e5c33a 100644 --- a/fs/gfs2/rgrp.c +++ b/fs/gfs2/rgrp.c @@ -1033,8 +1033,8 @@ static int gfs2_ri_update(struct gfs2_inode *ip) int gfs2_rindex_update(struct gfs2_sbd *sdp) { + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_rindex); struct gfs2_inode *ip = GFS2_I(sdp->sd_rindex); - struct gfs2_glock *gl = ip->i_gl; struct gfs2_holder ri_gh; int error = 0; int unlock_required = 0; @@ -2453,7 +2453,7 @@ int gfs2_alloc_blocks(struct gfs2_inode *ip, u64 *bn, unsigned int *nblocks, if (error == 0) { struct gfs2_dinode *di = (struct gfs2_dinode *)dibh->b_data; - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gfs2_inode_glock(&ip->i_inode), dibh); di->di_goal_meta = di->di_goal_data = cpu_to_be64(ip->i_goal); brelse(dibh); diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 06302c29340f2b..af8c715768336a 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -132,8 +132,7 @@ int gfs2_jdesc_check(struct gfs2_jdesc *jd) int gfs2_make_fs_rw(struct gfs2_sbd *sdp) { - struct gfs2_inode *ip = GFS2_I(sdp->sd_jdesc->jd_inode); - struct gfs2_glock *j_gl = ip->i_gl; + struct gfs2_glock *j_gl = gfs2_inode_glock(sdp->sd_jdesc->jd_inode); int error; j_gl->gl_ops->go_inval(j_gl, DIO_METADATA); @@ -176,6 +175,7 @@ void gfs2_statfs_change_out(const struct gfs2_statfs_change_host *sc, void *buf) int gfs2_statfs_init(struct gfs2_sbd *sdp) { + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_statfs_inode); struct gfs2_inode *m_ip = GFS2_I(sdp->sd_statfs_inode); struct gfs2_statfs_change_host *m_sc = &sdp->sd_statfs_master; struct gfs2_statfs_change_host *l_sc = &sdp->sd_statfs_local; @@ -183,7 +183,7 @@ int gfs2_statfs_init(struct gfs2_sbd *sdp) struct gfs2_holder gh; int error; - error = gfs2_glock_nq_init(m_ip->i_gl, LM_ST_EXCLUSIVE, GL_NOCACHE, + error = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, GL_NOCACHE, &gh); if (error) return error; @@ -216,13 +216,13 @@ int gfs2_statfs_init(struct gfs2_sbd *sdp) void gfs2_statfs_change(struct gfs2_sbd *sdp, s64 total, s64 free, s64 dinodes) { - struct gfs2_inode *l_ip = GFS2_I(sdp->sd_sc_inode); + struct gfs2_glock *gl = gfs2_inode_glock(sdp->sd_sc_inode); struct gfs2_statfs_change_host *l_sc = &sdp->sd_statfs_local; struct gfs2_statfs_change_host *m_sc = &sdp->sd_statfs_master; s64 x, y; int need_sync = 0; - gfs2_trans_add_meta(l_ip->i_gl, sdp->sd_sc_bh); + gfs2_trans_add_meta(gl, sdp->sd_sc_bh); spin_lock(&sdp->sd_statfs_spin); l_sc->sc_total += total; @@ -244,13 +244,13 @@ void gfs2_statfs_change(struct gfs2_sbd *sdp, s64 total, s64 free, void update_statfs(struct gfs2_sbd *sdp, struct buffer_head *m_bh) { - struct gfs2_inode *m_ip = GFS2_I(sdp->sd_statfs_inode); - struct gfs2_inode *l_ip = GFS2_I(sdp->sd_sc_inode); + struct gfs2_glock *m_gl = gfs2_inode_glock(sdp->sd_statfs_inode); + struct gfs2_glock *l_gl = gfs2_inode_glock(sdp->sd_sc_inode); struct gfs2_statfs_change_host *m_sc = &sdp->sd_statfs_master; struct gfs2_statfs_change_host *l_sc = &sdp->sd_statfs_local; - gfs2_trans_add_meta(l_ip->i_gl, sdp->sd_sc_bh); - gfs2_trans_add_meta(m_ip->i_gl, m_bh); + gfs2_trans_add_meta(l_gl, sdp->sd_sc_bh); + gfs2_trans_add_meta(m_gl, m_bh); spin_lock(&sdp->sd_statfs_spin); m_sc->sc_total += l_sc->sc_total; @@ -273,8 +273,8 @@ int gfs2_statfs_sync(struct super_block *sb, int type) struct buffer_head *m_bh; int error; - error = gfs2_glock_nq_init(m_ip->i_gl, LM_ST_EXCLUSIVE, GL_NOCACHE, - &gh); + error = gfs2_glock_nq_init(gfs2_inode_glock(&m_ip->i_inode), + LM_ST_EXCLUSIVE, GL_NOCACHE, &gh); if (error) goto out; @@ -323,7 +323,6 @@ struct lfcc { static int gfs2_lock_fs_check_clean(struct gfs2_sbd *sdp) { - struct gfs2_inode *ip; struct gfs2_jdesc *jd; struct lfcc *lfcc; LIST_HEAD(list); @@ -336,13 +335,14 @@ static int gfs2_lock_fs_check_clean(struct gfs2_sbd *sdp) */ list_for_each_entry(jd, &sdp->sd_jindex_list, jd_list) { + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); + lfcc = kmalloc_obj(struct lfcc); if (!lfcc) { error = -ENOMEM; goto out; } - ip = GFS2_I(jd->jd_inode); - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &lfcc->gh); + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, 0, &lfcc->gh); if (error) { kfree(lfcc); goto out; @@ -438,15 +438,16 @@ void gfs2_dinode_out(const struct gfs2_inode *ip, void *buf) static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); - struct address_space *metamapping = gfs2_glock2aspace(ip->i_gl); + struct address_space *metamapping = gfs2_glock2aspace(gl); struct backing_dev_info *bdi = inode_to_bdi(metamapping->host); int ret = 0; bool flush_all = (wbc->sync_mode == WB_SYNC_ALL || gfs2_is_jdata(ip)); if (flush_all) - gfs2_log_flush(GFS2_SB(inode), ip->i_gl, + gfs2_log_flush(GFS2_SB(inode), gl, GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_WRITE_INODE); if (bdi_wb_dirty_exceeded(bdi)) @@ -481,6 +482,7 @@ static int gfs2_write_inode(struct inode *inode, struct writeback_control *wbc) static void gfs2_dirty_inode(struct inode *inode, int flags) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct buffer_head *bh; @@ -490,20 +492,20 @@ static void gfs2_dirty_inode(struct inode *inode, int flags) int ret; /* This can only happen during incomplete inode creation. */ - if (unlikely(!ip->i_gl)) + if (unlikely(!gl)) return; if (gfs2_withdrawn(sdp)) return; - if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + if (!gfs2_glock_is_locked_by_me(gl)) { + ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &gh); if (ret) { fs_err(sdp, "dirty_inode: glock %d\n", ret); - gfs2_dump_glock(NULL, ip->i_gl, true); + gfs2_dump_glock(NULL, gl, true); return; } need_unlock = 1; - } else if (WARN_ON_ONCE(ip->i_gl->gl_state != LM_ST_EXCLUSIVE)) + } else if (WARN_ON_ONCE(gl->gl_state != LM_ST_EXCLUSIVE)) return; if (current->journal_info == NULL) { @@ -517,7 +519,7 @@ static void gfs2_dirty_inode(struct inode *inode, int flags) ret = gfs2_meta_inode_buffer(ip, &bh); if (ret == 0) { - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); gfs2_dinode_out(ip, bh->b_data); brelse(bh); } @@ -1176,6 +1178,7 @@ static void gfs2_glock_put_eventually(struct gfs2_glock *gl) static enum evict_behavior gfs2_upgrade_iopen_glock(struct inode *inode) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_holder *gh = &ip->i_iopen_gh; @@ -1211,11 +1214,11 @@ static enum evict_behavior gfs2_upgrade_iopen_glock(struct inode *inode) wait_event_interruptible_timeout(sdp->sd_async_glock_wait, !test_bit(HIF_WAIT, &gh->gh_iflags) || - glock_needs_demote(ip->i_gl), + glock_needs_demote(gl), 5 * HZ); if (!test_bit(HIF_HOLDER, &gh->gh_iflags)) { gfs2_glock_dq(gh); - if (glock_needs_demote(ip->i_gl)) + if (glock_needs_demote(gl)) return EVICT_SHOULD_SKIP_DELETE; return EVICT_SHOULD_DEFER_DELETE; } @@ -1238,6 +1241,7 @@ static enum evict_behavior gfs2_upgrade_iopen_glock(struct inode *inode) static enum evict_behavior evict_should_delete(struct inode *inode, struct gfs2_holder *gh) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct super_block *sb = inode->i_sb; struct gfs2_sbd *sdp = sb->s_fs_info; @@ -1255,11 +1259,11 @@ static enum evict_behavior evict_should_delete(struct inode *inode, return EVICT_SHOULD_DEFER_DELETE; /* Must not read inode block until block type has been verified */ - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_SKIP, gh); + ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, GL_SKIP, gh); if (unlikely(ret)) return EVICT_SHOULD_SKIP_DELETE; - if (gfs2_inode_already_deleted(ip->i_gl, ip->i_no_formal_ino)) + if (gfs2_inode_already_deleted(gl, ip->i_no_formal_ino)) return EVICT_SHOULD_SKIP_DELETE; ret = gfs2_check_blk_type(sdp, ip->i_no_addr, GFS2_BLKST_UNLINKED); if (ret) @@ -1288,8 +1292,8 @@ static enum evict_behavior evict_should_delete(struct inode *inode, */ static int evict_unlinked_inode(struct inode *inode, struct gfs2_holder *gh) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_glock *gl = ip->i_gl; int ret; /* The inode glock must be held exclusively and be instantiated. */ @@ -1390,8 +1394,7 @@ static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh) { struct super_block *sb = inode->i_sb; struct gfs2_sbd *sdp = sb->s_fs_info; - struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_glock *gl = ip->i_gl; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct address_space *metamapping = gfs2_glock2aspace(gl); int ret; @@ -1446,13 +1449,14 @@ static void gfs2_evict_inode(struct inode *inode) { struct super_block *sb = inode->i_sb; struct gfs2_sbd *sdp = sb->s_fs_info; + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; enum evict_behavior behavior; int ret; gfs2_holder_mark_uninitialized(&gh); - if (sb_rdonly(sb) || !ip->i_no_addr || !ip->i_gl) + if (sb_rdonly(sb) || !ip->i_no_addr || !gl) goto out; /* @@ -1505,10 +1509,10 @@ static void gfs2_evict_inode(struct inode *inode) gfs2_glock_dq_uninit(&ip->i_iopen_gh); gfs2_glock_put_eventually(gl); } - if (ip->i_gl) { - glock_clear_object(ip->i_gl, ip); + if (gl) { + glock_clear_object(gl, ip); wait_on_bit_io(&ip->i_flags, GIF_GLOP_PENDING, TASK_UNINTERRUPTIBLE); - gfs2_glock_put_eventually(ip->i_gl); + gfs2_glock_put_eventually(gl); rcu_assign_pointer(ip->i_gl, NULL); } } diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c index 83b8bb6446e57b..61b0668ecabc08 100644 --- a/fs/gfs2/util.c +++ b/fs/gfs2/util.c @@ -55,10 +55,9 @@ int check_journal_clean(struct gfs2_sbd *sdp, struct gfs2_jdesc *jd, int error; struct gfs2_holder j_gh; struct gfs2_log_header_host head; - struct gfs2_inode *ip; + struct gfs2_glock *gl = gfs2_inode_glock(jd->jd_inode); - ip = GFS2_I(jd->jd_inode); - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_RECOVER | + error = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_RECOVER | GL_EXACT | GL_NOCACHE, &j_gh); if (error) { if (verbose) @@ -333,6 +332,7 @@ void gfs2_consist_i(struct gfs2_sbd *sdp, const char *function, void gfs2_consist_inode_i(struct gfs2_inode *ip, const char *function, char *file, unsigned int line) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); gfs2_lm(sdp, @@ -342,7 +342,7 @@ void gfs2_consist_inode_i(struct gfs2_inode *ip, (unsigned long long)ip->i_no_formal_ino, (unsigned long long)ip->i_no_addr, function, file, line); - gfs2_dump_glock(NULL, ip->i_gl, 1); + gfs2_dump_glock(NULL, gl, 1); gfs2_withdraw(sdp); } diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c index b9f48d6f10a97a..db38d972debd04 100644 --- a/fs/gfs2/xattr.c +++ b/fs/gfs2/xattr.c @@ -128,11 +128,12 @@ static int ea_foreach_i(struct gfs2_inode *ip, struct buffer_head *bh, static int ea_foreach(struct gfs2_inode *ip, ea_call_t ea_call, void *data) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct buffer_head *bh, *eabh; __be64 *eablk, *end; int error; - error = gfs2_meta_read(ip->i_gl, ip->i_eattr, DIO_WAIT, 0, &bh); + error = gfs2_meta_read(gl, ip->i_eattr, DIO_WAIT, 0, &bh); if (error) return error; @@ -156,7 +157,7 @@ static int ea_foreach(struct gfs2_inode *ip, ea_call_t ea_call, void *data) break; bn = be64_to_cpu(*eablk); - error = gfs2_meta_read(ip->i_gl, bn, DIO_WAIT, 0, &eabh); + error = gfs2_meta_read(gl, bn, DIO_WAIT, 0, &eabh); if (error) break; error = ea_foreach_i(ip, eabh, ea_call, data); @@ -279,7 +280,7 @@ static int ea_dealloc_unstuffed(struct gfs2_inode *ip, struct buffer_head *bh, if (error) goto out_gunlock; - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gfs2_inode_glock(&ip->i_inode), bh); dataptrs = GFS2_EA2DATAPTRS(ea); for (x = 0; x < ea->ea_num_ptrs; x++, dataptrs++) { @@ -426,7 +427,8 @@ ssize_t gfs2_listxattr(struct dentry *dentry, char *buffer, size_t size) er.er_data_len = size; } - error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh); + error = gfs2_glock_nq_init(gfs2_inode_glock(&ip->i_inode), LM_ST_SHARED, + LM_FLAG_ANY, &i_gh); if (error) return error; @@ -457,6 +459,7 @@ ssize_t gfs2_listxattr(struct dentry *dentry, char *buffer, size_t size) static int gfs2_iter_unstuffed(struct gfs2_inode *ip, struct gfs2_ea_header *ea, const char *din, char *dout) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct buffer_head **bh; unsigned int amount = GFS2_EA_DATA_LEN(ea); @@ -472,7 +475,7 @@ static int gfs2_iter_unstuffed(struct gfs2_inode *ip, struct gfs2_ea_header *ea, return -ENOMEM; for (x = 0; x < nptrs; x++) { - error = gfs2_meta_read(ip->i_gl, be64_to_cpu(*dataptrs), 0, 0, + error = gfs2_meta_read(gl, be64_to_cpu(*dataptrs), 0, 0, bh + x); if (error) { while (x--) @@ -505,7 +508,7 @@ static int gfs2_iter_unstuffed(struct gfs2_inode *ip, struct gfs2_ea_header *ea, } if (din) { - gfs2_trans_add_meta(ip->i_gl, bh[x]); + gfs2_trans_add_meta(gl, bh[x]); memcpy(pos, din, cp_size); din += sdp->sd_jbsize; } @@ -608,14 +611,14 @@ static int gfs2_xattr_get(const struct xattr_handler *handler, struct dentry *unused, struct inode *inode, const char *name, void *buffer, size_t size) { - struct gfs2_inode *ip = GFS2_I(inode); + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_holder gh; int ret; /* During lookup, SELinux calls this function with the glock locked. */ - if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); + if (!gfs2_glock_is_locked_by_me(gl)) { + ret = gfs2_glock_nq_init(gl, LM_ST_SHARED, LM_FLAG_ANY, &gh); if (ret) return ret; } else { @@ -637,6 +640,7 @@ static int gfs2_xattr_get(const struct xattr_handler *handler, static int ea_alloc_blk(struct gfs2_inode *ip, struct buffer_head **bhp) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct gfs2_ea_header *ea; unsigned int n = 1; @@ -647,8 +651,8 @@ static int ea_alloc_blk(struct gfs2_inode *ip, struct buffer_head **bhp) if (error) return error; gfs2_trans_remove_revoke(sdp, block, 1); - *bhp = gfs2_meta_new(ip->i_gl, block); - gfs2_trans_add_meta(ip->i_gl, *bhp); + *bhp = gfs2_meta_new(gl, block); + gfs2_trans_add_meta(gl, *bhp); gfs2_metatype_set(*bhp, GFS2_METATYPE_EA, GFS2_FORMAT_EA); gfs2_buffer_clear_tail(*bhp, sizeof(struct gfs2_meta_header)); @@ -678,6 +682,7 @@ static int ea_alloc_blk(struct gfs2_inode *ip, struct buffer_head **bhp) static int ea_write(struct gfs2_inode *ip, struct gfs2_ea_header *ea, struct gfs2_ea_request *er) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); int error; @@ -709,8 +714,8 @@ static int ea_write(struct gfs2_inode *ip, struct gfs2_ea_header *ea, if (error) return error; gfs2_trans_remove_revoke(sdp, block, 1); - bh = gfs2_meta_new(ip->i_gl, block); - gfs2_trans_add_meta(ip->i_gl, bh); + bh = gfs2_meta_new(gl, block); + gfs2_trans_add_meta(gl, bh); gfs2_metatype_set(bh, GFS2_METATYPE_ED, GFS2_FORMAT_ED); gfs2_add_inode_blocks(&ip->i_inode, 1); @@ -841,11 +846,12 @@ static struct gfs2_ea_header *ea_split_ea(struct gfs2_ea_header *ea) static void ea_set_remove_stuffed(struct gfs2_inode *ip, struct gfs2_ea_location *el) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_ea_header *ea = el->el_ea; struct gfs2_ea_header *prev = el->el_prev; u32 len; - gfs2_trans_add_meta(ip->i_gl, el->el_bh); + gfs2_trans_add_meta(gl, el->el_bh); if (!prev || !GFS2_EA_IS_STUFFED(ea)) { ea->ea_type = GFS2_EATYPE_UNUSED; @@ -875,6 +881,7 @@ struct ea_set { static int ea_set_simple_noalloc(struct gfs2_inode *ip, struct buffer_head *bh, struct gfs2_ea_header *ea, struct ea_set *es) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_ea_request *er = es->es_er; int error; @@ -882,7 +889,7 @@ static int ea_set_simple_noalloc(struct gfs2_inode *ip, struct buffer_head *bh, if (error) return error; - gfs2_trans_add_meta(ip->i_gl, bh); + gfs2_trans_add_meta(gl, bh); if (es->ea_split) ea = ea_split_ea(ea); @@ -902,11 +909,12 @@ static int ea_set_simple_noalloc(struct gfs2_inode *ip, struct buffer_head *bh, static int ea_set_simple_alloc(struct gfs2_inode *ip, struct gfs2_ea_request *er, void *private) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct ea_set *es = private; struct gfs2_ea_header *ea = es->es_ea; int error; - gfs2_trans_add_meta(ip->i_gl, es->es_bh); + gfs2_trans_add_meta(gl, es->es_bh); if (es->ea_split) ea = ea_split_ea(ea); @@ -971,6 +979,7 @@ static int ea_set_simple(struct gfs2_inode *ip, struct buffer_head *bh, static int ea_set_block(struct gfs2_inode *ip, struct gfs2_ea_request *er, void *private) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct buffer_head *indbh, *newbh; __be64 *eablk; @@ -980,7 +989,7 @@ static int ea_set_block(struct gfs2_inode *ip, struct gfs2_ea_request *er, if (ip->i_diskflags & GFS2_DIF_EA_INDIRECT) { __be64 *end; - error = gfs2_meta_read(ip->i_gl, ip->i_eattr, DIO_WAIT, 0, + error = gfs2_meta_read(gl, ip->i_eattr, DIO_WAIT, 0, &indbh); if (error) return error; @@ -1002,7 +1011,7 @@ static int ea_set_block(struct gfs2_inode *ip, struct gfs2_ea_request *er, goto out; } - gfs2_trans_add_meta(ip->i_gl, indbh); + gfs2_trans_add_meta(gl, indbh); } else { u64 blk; unsigned int n = 1; @@ -1010,8 +1019,8 @@ static int ea_set_block(struct gfs2_inode *ip, struct gfs2_ea_request *er, if (error) return error; gfs2_trans_remove_revoke(sdp, blk, 1); - indbh = gfs2_meta_new(ip->i_gl, blk); - gfs2_trans_add_meta(ip->i_gl, indbh); + indbh = gfs2_meta_new(gl, blk); + gfs2_trans_add_meta(gl, indbh); gfs2_metatype_set(indbh, GFS2_METATYPE_IN, GFS2_FORMAT_IN); gfs2_buffer_clear_tail(indbh, mh_size); @@ -1088,6 +1097,7 @@ static int ea_set_remove_unstuffed(struct gfs2_inode *ip, static int ea_remove_stuffed(struct gfs2_inode *ip, struct gfs2_ea_location *el) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_ea_header *ea = el->el_ea; struct gfs2_ea_header *prev = el->el_prev; int error; @@ -1096,7 +1106,7 @@ static int ea_remove_stuffed(struct gfs2_inode *ip, struct gfs2_ea_location *el) if (error) return error; - gfs2_trans_add_meta(ip->i_gl, el->el_bh); + gfs2_trans_add_meta(gl, el->el_bh); if (prev) { u32 len; @@ -1234,6 +1244,7 @@ static int gfs2_xattr_set(const struct xattr_handler *handler, const char *name, const void *value, size_t size, int flags) { + struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_holder gh; int ret; @@ -1244,12 +1255,12 @@ static int gfs2_xattr_set(const struct xattr_handler *handler, /* May be called from gfs_setattr with the glock locked. */ - if (!gfs2_glock_is_locked_by_me(ip->i_gl)) { - ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh); + if (!gfs2_glock_is_locked_by_me(gl)) { + ret = gfs2_glock_nq_init(gl, LM_ST_EXCLUSIVE, 0, &gh); if (ret) goto out; } else { - if (WARN_ON_ONCE(ip->i_gl->gl_state != LM_ST_EXCLUSIVE)) { + if (WARN_ON_ONCE(gl->gl_state != LM_ST_EXCLUSIVE)) { ret = -EIO; goto out; } @@ -1265,6 +1276,7 @@ static int gfs2_xattr_set(const struct xattr_handler *handler, static int ea_dealloc_indirect(struct gfs2_inode *ip) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct gfs2_rgrp_list rlist; struct gfs2_rgrpd *rgd; @@ -1283,7 +1295,7 @@ static int ea_dealloc_indirect(struct gfs2_inode *ip) memset(&rlist, 0, sizeof(struct gfs2_rgrp_list)); - error = gfs2_meta_read(ip->i_gl, ip->i_eattr, DIO_WAIT, 0, &indbh); + error = gfs2_meta_read(gl, ip->i_eattr, DIO_WAIT, 0, &indbh); if (error) return error; @@ -1333,7 +1345,7 @@ static int ea_dealloc_indirect(struct gfs2_inode *ip) if (error) goto out_gunlock; - gfs2_trans_add_meta(ip->i_gl, indbh); + gfs2_trans_add_meta(gl, indbh); eablk = (__be64 *)(indbh->b_data + sizeof(struct gfs2_meta_header)); bstart = 0; @@ -1367,7 +1379,7 @@ static int ea_dealloc_indirect(struct gfs2_inode *ip) error = gfs2_meta_inode_buffer(ip, &dibh); if (!error) { - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); } @@ -1385,6 +1397,7 @@ static int ea_dealloc_indirect(struct gfs2_inode *ip) static int ea_dealloc_block(struct gfs2_inode *ip, bool initialized) { + struct gfs2_glock *gl = gfs2_inode_glock(&ip->i_inode); struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode); struct gfs2_rgrpd *rgd; struct buffer_head *dibh; @@ -1419,7 +1432,7 @@ static int ea_dealloc_block(struct gfs2_inode *ip, bool initialized) if (initialized) { error = gfs2_meta_inode_buffer(ip, &dibh); if (!error) { - gfs2_trans_add_meta(ip->i_gl, dibh); + gfs2_trans_add_meta(gl, dibh); gfs2_dinode_out(ip, dibh->b_data); brelse(dibh); } From 51b812f668e252d19bc80b1956bdd796aef56121 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 16:00:56 +0200 Subject: [PATCH 0349/2286] gfs2: annotate i_gl with __rcu ip->i_gl is always set over the lifetime of a gfs2 inode; it is initialized at inode create time and torn down during evict. However, gfs2_permission() can be called on an inode in non-blocking mode, when the inode may already be undergoing evict. So far, to make that work, we have been using rcu_dereference_check() in gfs2_permission() and rcu_assign_pointer() in gfs2_evict_inode(). However, ip->i_gl wasn't marked as __rcu so far. rcu_dereference_check() and rcu_assign_pointer() expect __rcu pointer arguments, and sparse complains when regular pointers are passed to those functions: fs/gfs2/super.c:1516:17: error: incompatible types in comparison expression (different address spaces): fs/gfs2/super.c:1516:17: struct gfs2_glock [noderef] __rcu * fs/gfs2/super.c:1516:17: struct gfs2_glock * fs/gfs2/inode.c:1988:14: error: incompatible types in comparison expression (different address spaces): fs/gfs2/inode.c:1988:14: struct gfs2_glock [noderef] __rcu * fs/gfs2/inode.c:1988:14: struct gfs2_glock * To fix those errors, turn ip->i_gl into a __rcu variable. Use rcu_assign_pointer() to assign to it, and rcu_dereference_protected() to access it when the pointer is known to be valid. Make sure not to use gfs2_inode_glock() in gfs2_permission(). Based on a patch from Adrian Garcia Casado . Signed-off-by: Andreas Gruenbacher --- fs/gfs2/incore.h | 4 ++-- fs/gfs2/inode.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index 3ae8e2be486c90..0a48f8e8ed6c5b 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -391,7 +391,7 @@ struct gfs2_inode { u64 i_generation; u64 i_eattr; unsigned long i_flags; /* GIF_... */ - struct gfs2_glock *i_gl; + struct gfs2_glock __rcu *i_gl; struct gfs2_holder i_iopen_gh; struct gfs2_qadata *i_qadata; /* quota allocation data */ struct gfs2_holder i_rgd_gh; @@ -881,6 +881,6 @@ static inline unsigned gfs2_max_stuffed_size(const struct gfs2_inode *ip) static inline struct gfs2_glock *gfs2_inode_glock(struct inode *inode) { - return GFS2_I(inode)->i_gl; + return rcu_dereference_protected(GFS2_I(inode)->i_gl, 1); } #endif /* __INCORE_DOT_H__ */ diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 5aaed0018fb338..d4e1e443391e0f 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -151,7 +151,7 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, &gl); if (unlikely(error)) goto fail; - ip->i_gl = gl; + rcu_assign_pointer(ip->i_gl, gl); error = gfs2_glock_get(sdp, no_addr, &gfs2_iopen_glops, CREATE, &io_gl); @@ -244,7 +244,7 @@ struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type, gfs2_glock_dq_uninit(&i_gh); if (gl) { gfs2_glock_put(gl); - ip->i_gl = NULL; + rcu_assign_pointer(ip->i_gl, NULL); } iget_failed(inode); return ERR_PTR(error); @@ -840,7 +840,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, error = gfs2_glock_get(sdp, ip->i_no_addr, &gfs2_inode_glops, CREATE, &gl); if (error) goto fail_dealloc_inode; - ip->i_gl = gl; + rcu_assign_pointer(ip->i_gl, gl); error = gfs2_glock_get(sdp, ip->i_no_addr, &gfs2_iopen_glops, CREATE, &io_gl); if (error) @@ -940,7 +940,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, fail_free_inode: if (gl) { gfs2_glock_put(gl); - ip->i_gl = NULL; + rcu_assign_pointer(ip->i_gl, NULL); } gfs2_rs_deltree(&ip->i_res); gfs2_qa_put(ip); From c4f01c85c8fe964a44f6967473a10f477c5f3cd0 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 15:15:35 +0200 Subject: [PATCH 0350/2286] gfs2: Silence sparse signedness warning Silence the following sparse warning: fs/gfs2/bmap.c:1718:42: warning: unsigned value that used to be signed checked against zero? fs/gfs2/bmap.c:2017:16: signed value source It's unclear to me what sparse is trying to tell me there, but making the height variables unsigned shuts it up. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index b5ddd936f48925..3714364385c7cf 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1699,7 +1699,7 @@ enum dealloc_states { }; static inline void -metapointer_range(struct metapath *mp, int height, +metapointer_range(struct metapath *mp, unsigned int height, __u16 *start_list, unsigned int start_aligned, __u16 *end_list, unsigned int end_aligned, __be64 **start, __be64 **end) @@ -1770,7 +1770,7 @@ static int punch_hole(struct gfs2_inode *ip, u64 offset, u64 length) unsigned int strip_h = ip->i_height - 1; u32 btotal = 0; int ret, state; - int mp_h; /* metapath buffers are read in to this height */ + unsigned int mp_h; /* metapath buffers are read in to this height */ u64 prev_bnr = 0; __be64 *start, *end; From 26cb3a444d8ec1d15a3c951c30db1dc121e5e1a4 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 20 Aug 2026 23:39:40 +0200 Subject: [PATCH 0351/2286] gfs2: Improve resource group validation and error handling Validate the resource group boundaries, check against the device size, simplify the initialization logic, and check for resource group overlaps. Reported-by: syzbot+9d20c3ad7d29227de28d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=9d20c3ad7d29227de28d Tested-by: syzbot+9d20c3ad7d29227de28d@syzkaller.appspotmail.com Signed-off-by: Andreas Gruenbacher --- fs/gfs2/rgrp.c | 123 ++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/fs/gfs2/rgrp.c b/fs/gfs2/rgrp.c index f6048a73e5c33a..3ac751eea05505 100644 --- a/fs/gfs2/rgrp.c +++ b/fs/gfs2/rgrp.c @@ -745,6 +745,7 @@ void gfs2_clear_rgrpd(struct gfs2_sbd *sdp) /** * compute_bitstructs - Compute the bitmap sizes + * @sb: The superblock * @rgd: The resource group descriptor * * Calculates bitmap descriptors, one for each block that contains bitmap data @@ -752,84 +753,74 @@ void gfs2_clear_rgrpd(struct gfs2_sbd *sdp) * Returns: errno */ -static int compute_bitstructs(struct gfs2_rgrpd *rgd) +static int compute_bitstructs(struct super_block *sb, struct gfs2_rgrpd *rgd) { struct gfs2_sbd *sdp = rgd->rd_sbd; struct gfs2_bitmap *bi; - u32 length = rgd->rd_length; /* # blocks in hdr & bitmap */ + u32 expected_length; u32 bytes_left, bytes; + u64 data_end; int x; - if (!length) - return -EINVAL; + /* + * The first resource group block has a gfs2_rgrp header; the remaining + * blocks have a gfs2_meta_header header. The rest of each block is + * filled with bitmap data. + */ + + if (rgd->rd_addr <= (GFS2_SB_ADDR >> sdp->sd_fsb2bb_shift)) { + gfs2_consist_rgrpd(rgd); + return -EIO; + } + if (check_add_overflow(rgd->rd_data0, rgd->rd_data, &data_end) || + rgd->rd_data == 0 || data_end > sb_bdev_nr_blocks(sb)) { + gfs2_consist_rgrpd(rgd); + return -EIO; + } + if (rgd->rd_bitbytes != DIV_ROUND_UP(rgd->rd_data, GFS2_NBBY)) { + gfs2_consist_rgrpd(rgd); + return -EIO; + } + expected_length = DIV_ROUND_UP(rgd->rd_bitbytes + + sizeof(struct gfs2_rgrp) - sizeof(struct gfs2_meta_header), + sdp->sd_sb.sb_bsize - sizeof(struct gfs2_meta_header)); + if (rgd->rd_length != expected_length) { + gfs2_consist_rgrpd(rgd); + return -EIO; + } + if (rgd->rd_data0 < rgd->rd_addr + rgd->rd_length) { + gfs2_consist_rgrpd(rgd); + return -EIO; + } - rgd->rd_bits = kzalloc_objs(struct gfs2_bitmap, length, GFP_NOFS); + rgd->rd_bits = kzalloc_objs(struct gfs2_bitmap, rgd->rd_length, GFP_NOFS); if (!rgd->rd_bits) return -ENOMEM; bytes_left = rgd->rd_bitbytes; - for (x = 0; x < length; x++) { + for (x = 0; x < rgd->rd_length; x++) { bi = rgd->rd_bits + x; bi->bi_flags = 0; - /* small rgrp; bitmap stored completely in header block */ - if (length == 1) { - bytes = bytes_left; - bi->bi_offset = sizeof(struct gfs2_rgrp); + if (x == 0) { + /* header block */ bi->bi_start = 0; - bi->bi_bytes = bytes; - bi->bi_blocks = bytes * GFS2_NBBY; - /* header block */ - } else if (x == 0) { - bytes = sdp->sd_sb.sb_bsize - sizeof(struct gfs2_rgrp); bi->bi_offset = sizeof(struct gfs2_rgrp); - bi->bi_start = 0; - bi->bi_bytes = bytes; - bi->bi_blocks = bytes * GFS2_NBBY; - /* last block */ - } else if (x + 1 == length) { - bytes = bytes_left; - bi->bi_offset = sizeof(struct gfs2_meta_header); - bi->bi_start = rgd->rd_bitbytes - bytes_left; - bi->bi_bytes = bytes; - bi->bi_blocks = bytes * GFS2_NBBY; - /* other blocks */ } else { - bytes = sdp->sd_sb.sb_bsize - - sizeof(struct gfs2_meta_header); + /* bitmap-only block */ + struct gfs2_bitmap *prev = bi - 1; + + bi->bi_start = prev->bi_start + prev->bi_bytes; bi->bi_offset = sizeof(struct gfs2_meta_header); - bi->bi_start = rgd->rd_bitbytes - bytes_left; - bi->bi_bytes = bytes; - bi->bi_blocks = bytes * GFS2_NBBY; } - + bytes = sdp->sd_sb.sb_bsize - bi->bi_offset; + if (bytes > bytes_left) + bytes = bytes_left; + bi->bi_bytes = bytes; + bi->bi_blocks = bytes * GFS2_NBBY; bytes_left -= bytes; } - - if (bytes_left) { - gfs2_consist_rgrpd(rgd); - return -EIO; - } - bi = rgd->rd_bits + (length - 1); - if ((bi->bi_start + bi->bi_bytes) * GFS2_NBBY != rgd->rd_data) { - gfs2_lm(sdp, - "ri_addr=%llu " - "ri_length=%u " - "ri_data0=%llu " - "ri_data=%u " - "ri_bitbytes=%u " - "start=%u len=%u offset=%u\n", - (unsigned long long)rgd->rd_addr, - rgd->rd_length, - (unsigned long long)rgd->rd_data0, - rgd->rd_data, - rgd->rd_bitbytes, - bi->bi_start, bi->bi_bytes, bi->bi_offset); - gfs2_consist_rgrpd(rgd); - return -EIO; - } - return 0; } @@ -864,6 +855,7 @@ static int rgd_insert(struct gfs2_rgrpd *rgd) { struct gfs2_sbd *sdp = rgd->rd_sbd; struct rb_node **newn = &sdp->sd_rindex_tree.rb_node, *parent = NULL; + struct rb_node *prevn; /* Figure out where to put new node */ while (*newn) { @@ -882,6 +874,19 @@ static int rgd_insert(struct gfs2_rgrpd *rgd) rb_link_node(&rgd->rd_node, parent, newn); rb_insert_color(&rgd->rd_node, &sdp->sd_rindex_tree); sdp->sd_rgrps++; + + prevn = rb_prev(&rgd->rd_node); + if (prevn) { + struct gfs2_rgrpd *prev = + rb_entry(prevn, struct gfs2_rgrpd, rd_node); + + if (prev->rd_data0 + prev->rd_data > rgd->rd_addr) { + fs_err(sdp, "overlapping resource groups.\n"); + rb_erase(&rgd->rd_node, &sdp->sd_rindex_tree); + return -ENOENT; + } + } + return 0; } @@ -928,7 +933,7 @@ static int read_rindex_entry(struct gfs2_inode *ip) if (error) goto fail; - error = compute_bitstructs(rgd); + error = compute_bitstructs(sdp->sd_vfs, rgd); if (error) goto fail_glock; @@ -944,7 +949,9 @@ static int read_rindex_entry(struct gfs2_inode *ip) return 0; } - error = 0; /* someone else read in the rgrp; free it and ignore it */ + /* If someone else read in the rgrp, free it and ignore it. */ + if (error == -EEXIST) + error = 0; fail_glock: gfs2_glock_put(rgd->rd_gl); From 59932d7f7e48e2bd2017e8d4d9e1a3f2574436f1 Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Tue, 25 Aug 2026 09:55:01 +0800 Subject: [PATCH 0352/2286] gfs2: move brelse() after buffer head accesses The brelse() call happens before all buffer head accesses are complete. While the reference counting prevents a real use-after-free in practice, this pattern is error-prone for future maintenance. Move the brelse() call to the end of the function to make the intent clearer and eliminate potential static analysis warnings. Suggested-by: Andreas Gruenbacher Suggested-by: Andrew Price Signed-off-by: Hongling Zeng Signed-off-by: Andreas Gruenbacher --- fs/gfs2/log.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c index 78bba8cc10b8fd..a92c84146de918 100644 --- a/fs/gfs2/log.c +++ b/fs/gfs2/log.c @@ -1038,7 +1038,6 @@ void gfs2_remove_from_journal(struct buffer_head *bh, int meta) set_bit(TR_TOUCHED, &tr->tr_flags); } was_pinned = 1; - brelse(bh); } if (bd) { if (bd->bd_tr) { @@ -1056,6 +1055,8 @@ void gfs2_remove_from_journal(struct buffer_head *bh, int meta) } clear_buffer_dirty(bh); clear_buffer_uptodate(bh); + if (was_pinned) + brelse(bh); } /** From 0008baad6377c8195c63151f66cc01039919eab6 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 26 Aug 2026 12:41:28 +0200 Subject: [PATCH 0353/2286] gfs2: No quotas for meta inodes Inodes on the meta filesystem (mount -o meta) can never have quotas, so skip them entirely in the quota code. This avoids allocating ip->i_qadata unnecessarily. Without this fix, unlinking meta inodes like "statfs" on the meta filesystem will result in a NULL pointer dereference at unmount time: gfs2_evict_inode() -> gfs2_dinode_dealloc() -> gfs2_quota_hold() -> qdsb_get() -> slot_get() -> find_first_zero_bit(sd_quota_bitmap) Unlinking inodes on the meta filesystem isn't a useful operation, but it still shouldn't cause the kernel to misbehave. Reported-by: syzbot+cb79de2cc8b76fbf474f@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=cb79de2cc8b76fbf474f Signed-off-by: Andreas Gruenbacher --- fs/gfs2/quota.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index b62431724eea88..dbfc21693dd576 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -562,6 +562,8 @@ int gfs2_qa_get(struct gfs2_inode *ip) if (sdp->sd_args.ar_quota == GFS2_QUOTA_OFF) return 0; + if (ip->i_diskflags & GFS2_DIF_SYSTEM) + return 0; spin_lock(&inode->i_lock); if (ip->i_qadata == NULL) { @@ -605,7 +607,7 @@ int gfs2_quota_hold(struct gfs2_inode *ip, kuid_t uid, kgid_t gid) return 0; error = gfs2_qa_get(ip); - if (error) + if (error || !ip->i_qadata) return error; qd = ip->i_qadata->qa_qd; From 33ed0a0567e0abe66ea65e89dad2e35082890fbe Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Mon, 31 Aug 2026 18:54:25 +0800 Subject: [PATCH 0354/2286] gfs2: reject invalid inode sizes A corrupted GFS2 image can store a dinode size that is larger than what VFS i_size can represent. gfs2_dinode_in() reads the on-disk di_size as a u64 and writes it directly into inode->i_size. If the value is larger than S64_MAX, the incore i_size becomes negative. That negative value can bypass the existing stuffed inode size check: inode->i_size > gfs2_max_stuffed_size(ip) Later, gfs2_quotad may try to sync the quota file and unstuff the quota inode. gfs2_unstuffer_folio() reads the negative i_size into an unsigned length and passes it to memcpy(), turning it into a huge copy size and triggering a out-of-bound issue. Reject dinodes whose size exceeds sb->s_maxbytes before storing the value in inode->i_size. Also make the stuffed inode check use the raw on-disk size while it is still unsigned. Fixes: 70376c7ff312 ("gfs2: Always check inode size of inline inodes") Closes: https://lore.kernel.org/lkml/CANypQFaF6bvORKKbRALvEL0k_epFaneFiOQqco4gjdmKVbdURg@mail.gmail.com/ Assisted-by: Codex:gpt-5.5-xhigh Cc: stable@vger.kernel.org Signed-off-by: Jiaming Zhang Signed-off-by: Andreas Gruenbacher --- fs/gfs2/glops.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/gfs2/glops.c b/fs/gfs2/glops.c index 662d033fd2cafa..2597e1cf2315c7 100644 --- a/fs/gfs2/glops.c +++ b/fs/gfs2/glops.c @@ -393,6 +393,7 @@ static int gfs2_dinode_in(struct gfs2_inode *ip, const void *buf) umode_t mode = be32_to_cpu(str->di_mode); struct inode *inode = &ip->i_inode; bool is_new = inode_state_read_once(inode) & I_NEW; + u64 size; if (unlikely(ip->i_no_addr != be64_to_cpu(str->di_num.no_addr))) { gfs2_consist_inode(ip); @@ -418,7 +419,12 @@ static int gfs2_dinode_in(struct gfs2_inode *ip, const void *buf) i_uid_write(inode, be32_to_cpu(str->di_uid)); i_gid_write(inode, be32_to_cpu(str->di_gid)); set_nlink(inode, be32_to_cpu(str->di_nlink)); - i_size_write(inode, be64_to_cpu(str->di_size)); + size = be64_to_cpu(str->di_size); + if (unlikely(size > inode->i_sb->s_maxbytes)) { + gfs2_consist_inode(ip); + return -EIO; + } + i_size_write(inode, size); gfs2_set_inode_blocks(inode, be64_to_cpu(str->di_blocks)); atime.tv_sec = be64_to_cpu(str->di_atime); atime.tv_nsec = be32_to_cpu(str->di_atime_nsec); @@ -462,7 +468,7 @@ static int gfs2_dinode_in(struct gfs2_inode *ip, const void *buf) return -EIO; } - if (gfs2_is_stuffed(ip) && inode->i_size > gfs2_max_stuffed_size(ip)) { + if (gfs2_is_stuffed(ip) && size > gfs2_max_stuffed_size(ip)) { gfs2_consist_inode(ip); return -EIO; } From a098f26d3b80e99740ea155a7012008d12a46010 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Sun, 30 Aug 2026 16:36:37 +0200 Subject: [PATCH 0355/2286] gfs2: Get rid of sd_async_glock_wait Get rid of the per-superblock wait queue for asynchronous locking requests. Instead, wait for the specific events we are interested in. Use multiple wait queue entries when waiting for multiple events at once. Without this patch, the per-superblock wait queue for asynchronous locking requests can become a bottleneck when many inodes are deleted remotely: in that case, we schedule delayed work with gfs2_queue_verify_delete(). When that work later runs, we end up in delete_work_func() -> iput() -> gfs2_evict_inode() -> gfs2_upgrade_iopen_glock(), which uses asynchronous locking. Each completing locking asynchronous request will wake up sdp->sd_async_glock_wait, and all the waiters will compete with each other and waste resources. Avoid that by eliminating the per-superblock wait queue. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/glock.c | 56 ++++++++++++++++++++++++++++++-------------- fs/gfs2/incore.h | 1 - fs/gfs2/ops_fstype.c | 1 - fs/gfs2/super.c | 30 ++++++++++++++++++++---- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index d0612014408e43..d22a088c66cdab 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -329,11 +329,6 @@ static void gfs2_holder_wake(struct gfs2_holder *gh) clear_bit(HIF_WAIT, &gh->gh_iflags); smp_mb__after_atomic(); wake_up_bit(&gh->gh_iflags, HIF_WAIT); - if (gh->gh_flags & GL_ASYNC) { - struct gfs2_sbd *sdp = glock_sbd(gh->gh_gl); - - wake_up(&sdp->sd_async_glock_wait); - } } /** @@ -512,11 +507,9 @@ static void state_change(struct gfs2_glock *gl, unsigned int new_state) static void gfs2_set_demote(int nr, struct gfs2_glock *gl) { - struct gfs2_sbd *sdp = glock_sbd(gl); - set_bit(nr, &gl->gl_flags); - smp_mb(); - wake_up(&sdp->sd_async_glock_wait); + smp_mb__after_atomic(); + wake_up_bit(&gl->gl_flags, GLF_DEMOTE); } static void gfs2_demote_wake(struct gfs2_glock *gl) @@ -1272,11 +1265,14 @@ static int glocks_pending(unsigned int num_gh, struct gfs2_holder *ghs) int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs, unsigned int retries) { - struct gfs2_sbd *sdp = glock_sbd(ghs[0].gh_gl); unsigned long start_time = jiffies; - int i, ret = 0; - long timeout; + struct wait_queue_head *waitq[4]; + struct wait_queue_entry wait[4]; + long ret, timeout; + int i; + BUILD_BUG_ON(ARRAY_SIZE(waitq) != ARRAY_SIZE(wait)); + BUG_ON(num_gh > ARRAY_SIZE(wait)); might_sleep(); timeout = GL_GLOCK_MIN_HOLD; @@ -1294,14 +1290,38 @@ int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs, timeout += (incr / 3) + get_random_long() % (incr / 3); } - if (!wait_event_interruptible_timeout(sdp->sd_async_glock_wait, - !glocks_pending(num_gh, ghs), timeout)) { - ret = -ESTALE; /* request timed out. */ - goto out; + ret = timeout; + for (i = 0; i < num_gh; i++) { + waitq[i] = bit_waitqueue(&ghs[i].gh_iflags, HIF_WAIT); + init_wait(wait + i); + } + for (;;) { + for (i = 0; i < num_gh; i++) + prepare_to_wait(waitq[i], wait + i, TASK_INTERRUPTIBLE); + if (!glocks_pending(num_gh, ghs)) + break; + if (signal_pending(current)) { + ret = -EINTR; + break; + } + ret = schedule_timeout(ret); + if (!glocks_pending(num_gh, ghs)) + break; + if (!ret) { + ret = -ESTALE; /* request timed out. */ + break; + } + if (signal_pending(current)) { + ret = -EINTR; + break; + } } - if (signal_pending(current)) - goto interrupted; + for (i = 0; i < num_gh; i++) + finish_wait(waitq[i], wait + i); + if (ret < 0) + goto out; + ret = 0; for (i = 0; i < num_gh; i++) { struct gfs2_holder *gh = &ghs[i]; int ret2; diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index 0a48f8e8ed6c5b..49cc232942a293 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -716,7 +716,6 @@ struct gfs2_sbd { struct work_struct sd_freeze_work; struct work_struct sd_withdraw_work; wait_queue_head_t sd_kill_wait; - wait_queue_head_t sd_async_glock_wait; atomic_t sd_glock_disposal; struct completion sd_locking_init; struct completion sd_withdraw_helper; diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index 188b3e67f2d1eb..6ec47376aecf25 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -90,7 +90,6 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb) gfs2_tune_init(&sdp->sd_tune); init_waitqueue_head(&sdp->sd_kill_wait); - init_waitqueue_head(&sdp->sd_async_glock_wait); atomic_set(&sdp->sd_glock_disposal, 0); init_completion(&sdp->sd_locking_init); init_completion(&sdp->sd_withdraw_helper); diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index af8c715768336a..04bb4cf787d4db 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -1180,8 +1180,10 @@ static enum evict_behavior gfs2_upgrade_iopen_glock(struct inode *inode) { struct gfs2_glock *gl = gfs2_inode_glock(inode); struct gfs2_inode *ip = GFS2_I(inode); - struct gfs2_sbd *sdp = GFS2_SB(inode); struct gfs2_holder *gh = &ip->i_iopen_gh; + struct wait_queue_head *holder_waitq, *glock_waitq; + struct wait_queue_entry holder_wait, glock_wait; + long ret = 5 * HZ; int error; gh->gh_flags |= GL_NOCACHE; @@ -1212,10 +1214,28 @@ static enum evict_behavior gfs2_upgrade_iopen_glock(struct inode *inode) if (error) return EVICT_SHOULD_SKIP_DELETE; - wait_event_interruptible_timeout(sdp->sd_async_glock_wait, - !test_bit(HIF_WAIT, &gh->gh_iflags) || - glock_needs_demote(gl), - 5 * HZ); + holder_waitq = bit_waitqueue(&gh->gh_iflags, HIF_WAIT); + glock_waitq = bit_waitqueue(&gl->gl_flags, GLF_DEMOTE); + init_wait(&holder_wait); + init_wait(&glock_wait); + for (;;) { + prepare_to_wait(holder_waitq, &holder_wait, TASK_INTERRUPTIBLE); + prepare_to_wait(glock_waitq, &glock_wait, TASK_INTERRUPTIBLE); + if (gfs2_glock_poll(gh) || glock_needs_demote(gl)) + break; + if (signal_pending(current)) + break; + ret = schedule_timeout(ret); + if (gfs2_glock_poll(gh) || glock_needs_demote(gl)) + break; + if (!ret) + break; + if (signal_pending(current)) + break; + } + finish_wait(holder_waitq, &holder_wait); + finish_wait(glock_waitq, &glock_wait); + if (!test_bit(HIF_HOLDER, &gh->gh_iflags)) { gfs2_glock_dq(gh); if (glock_needs_demote(gl)) From c6b4fcac9a4811032891904597590776fc4c81aa Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 1 Sep 2026 10:00:30 +0200 Subject: [PATCH 0356/2286] gfs2: missing whitespace in error message Add a missing whitespace in this error message in gfs2_quota_init(). Signed-off-by: Andreas Gruenbacher --- fs/gfs2/quota.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index dbfc21693dd576..52457f271f8b53 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -1477,7 +1477,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp) spin_lock_bucket(hash); old_qd = gfs2_qd_search_bucket_noref(hash, sdp, qc_id); if (old_qd) { - fs_err(sdp, "Corruption found in quota_change%u" + fs_err(sdp, "Corruption found in quota_change%u " "file: duplicate identifier in " "slot %u\n", sdp->sd_jdesc->jd_jid, slot); From a52652efd8f985397e87669f505af83f06e335cf Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 2 Sep 2026 16:36:57 +0200 Subject: [PATCH 0357/2286] gfs2: Fix journaled truncate range end offset The third argument to truncate_pagecache_range() is the inclusive end offset, but gfs2_journaled_truncate_range() passes the length of each chunk. A hole punch at an offset larger than its length therefore creates an inverted range. Commit 86066914edff ("gfs2: Don't support fallocate on jdata files") disables fallocate for jdata files. Otherwise, the following bug could occur: The MM truncation path then underflows the unsigned partial-folio length and passes an oversized range to folio_zero_range(), triggering a BUG. Pass the last byte of the chunk so page-cache invalidation covers the intended range. Fixes: 4e56a6411fbc ("gfs2: Implement fallocate(FALLOC_FL_PUNCH_HOLE)") Reported-by: VEGA Assisted-by: LLM Signed-off-by: Jan Kara Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 3714364385c7cf..f0c4d054e5f04b 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -2429,7 +2429,7 @@ static int gfs2_journaled_truncate_range(struct inode *inode, loff_t offset, if (offs && chunk > PAGE_SIZE) chunk = offs + ((chunk - offs) & PAGE_MASK); - truncate_pagecache_range(inode, offset, chunk); + truncate_pagecache_range(inode, offset, offset + chunk - 1); offset += chunk; length -= chunk; From 277536c882467e348173238e4ff701a4e4d2fb9e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:54:27 +0530 Subject: [PATCH 0358/2286] gfs2: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 2 +- fs/gfs2/lock_dlm.c | 4 ++-- fs/gfs2/recovery.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index f0c4d054e5f04b..264170f91c5dd8 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1217,7 +1217,7 @@ const struct iomap_ops gfs2_iomap_ops = { * @inode: The inode * @lblock: The logical block number * @bh_map: The bh to be mapped - * @create: True if its ok to alloc blocks to satify the request + * @create: True if its ok to alloc blocks to satisfy the request * * The size of the requested mapping is defined in bh_map->b_size. * diff --git a/fs/gfs2/lock_dlm.c b/fs/gfs2/lock_dlm.c index cef4bdee92a827..47eec0c13025d5 100644 --- a/fs/gfs2/lock_dlm.c +++ b/fs/gfs2/lock_dlm.c @@ -431,7 +431,7 @@ static void gdlm_cancel(struct gfs2_glock *gl) * 10. gfs2_control sets control_lock lvb = new gen + bits for failed jids * 12. gfs2_recover does journal recoveries for failed jids identified above * 14. gfs2_control clears control_lock lvb bits for recovered jids - * 15. gfs2_control checks if recover_block == recover_start (step 3 occured + * 15. gfs2_control checks if recover_block == recover_start (step 3 occurred * again) then do nothing, otherwise if recover_start > recover_block * then clear BLOCK_LOCKS. * @@ -809,7 +809,7 @@ static void gfs2_control_func(struct work_struct *work) /* * No more jid bits set in lvb, all recovery is done, unblock locks - * (unless a new recover_prep callback has occured blocking locks + * (unless a new recover_prep callback has occurred blocking locks * again while working above) */ diff --git a/fs/gfs2/recovery.c b/fs/gfs2/recovery.c index 5b1f494dc3c054..84db5bb7dc5c36 100644 --- a/fs/gfs2/recovery.c +++ b/fs/gfs2/recovery.c @@ -156,7 +156,7 @@ int __get_log_header(struct gfs2_sbd *sdp, const struct gfs2_log_header *lh, * @blk: the block to look at * @head: the log header to return * - * Read the log header for a given segement in a given journal. Do a few + * Read the log header for a given segment in a given journal. Do a few * sanity checks on it. * * Returns: 0 on success, From 914141c17e08ecf4080ae0bd2fdedee0d8dedd18 Mon Sep 17 00:00:00 2001 From: ThangNN99 Date: Sun, 6 Sep 2026 15:08:21 +0700 Subject: [PATCH 0359/2286] gfs2: fix NULL deref in gfs2_quota_lock() for meta inodes gfs2_quota_hold() now treats a meta/system inode's NULL i_qadata as success, but gfs2_quota_lock() still dereferences it unconditionally afterwards. Bail out early when there is nothing to lock. Reported-by: syzbot+d84e35a320d14f8c270a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d84e35a320d14f8c270a Fixes: 0025112796287599dc79c822ed37124582a0d68a ("gfs2: No quotas for meta inodes") Signed-off-by: ThangNN99 Signed-off-by: Andreas Gruenbacher --- fs/gfs2/quota.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index 52457f271f8b53..b2984fb7b35c3b 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -1101,6 +1101,9 @@ int gfs2_quota_lock(struct gfs2_inode *ip, kuid_t uid, kgid_t gid) error = gfs2_quota_hold(ip, uid, gid); if (error) return error; + /* Meta inodes never get quota data (see gfs2_qa_get()); nothing to lock. */ + if (!ip->i_qadata) + return 0; sort(ip->i_qadata->qa_qd, ip->i_qadata->qa_qd_num, sizeof(struct gfs2_quota_data *), sort_qd, NULL); From d952f6acb7acc9b94c8c25014b1f468a680c6c00 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:40:52 +0300 Subject: [PATCH 0360/2286] block: avoid integer overflows in max_integrity_io_size Sashiko reports that the calculation in max_integrity_io_size could overflow a u32 when using 16kiB or larger LBA sizes. Fix this by evaluating the maximum size as a 64-bit integer. Fixes: ec7f31b2a2d3 ("block: make bio auto-integrity deadlock safe") Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Link: https://patch.msgid.link/20260907074111.721054-2-hch@lst.de Signed-off-by: Jens Axboe --- include/linux/blkdev.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 4f7905c3412b8b..098a65f3e48b1e 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -1816,9 +1816,11 @@ static inline int bio_split_rw_at(struct bio *bio, */ static inline unsigned int max_integrity_io_size(struct queue_limits *lim) { - return min_t(unsigned int, lim->max_segment_size, - (BLK_INTEGRITY_MAX_SIZE / lim->integrity.metadata_size) << - lim->integrity.interval_exp); + u64 max_intervals; + + max_intervals = BLK_INTEGRITY_MAX_SIZE / lim->integrity.metadata_size; + return min_t(u64, lim->max_segment_size, + max_intervals << lim->integrity.interval_exp); } #define DEFINE_IO_COMP_BATCH(name) struct io_comp_batch name = { } From 1418b5633ca973723fd20db5c394e5b031f2bd85 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:40:53 +0300 Subject: [PATCH 0361/2286] block: cap atomic write size by PI buffer size constraints As Sashiko points out, limiting the I/O size by the size of the available PI buffer can cause inconsistencies for atomic writes. This also limit atomic_write_max_sectors in blk_validate_integrity_limits. Fixes: ec7f31b2a2d3 ("block: make bio auto-integrity deadlock safe") Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Link: https://patch.msgid.link/20260907074111.721054-3-hch@lst.de Signed-off-by: Jens Axboe --- block/blk-settings.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/blk-settings.c b/block/blk-settings.c index 8274631290dbf4..e469baa1f08b75 100644 --- a/block/blk-settings.c +++ b/block/blk-settings.c @@ -206,6 +206,12 @@ static int blk_validate_integrity_limits(struct queue_limits *lim) lim->max_sectors = min(lim->max_sectors, max_integrity_io_size(lim) >> SECTOR_SHIFT); + if (lim->features & BLK_FEAT_ATOMIC_WRITES) { + lim->atomic_write_max_sectors = + min(lim->atomic_write_max_sectors, + max_integrity_io_size(lim) >> SECTOR_SHIFT); + } + return 0; } From 9dd2351aef70512373f79ace94097a32e5ed0f70 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 7 Sep 2026 10:40:54 +0300 Subject: [PATCH 0362/2286] block: improve aligning down bios in bio_iov_iter_bounce_write Sashiko complained about bio_iov_iter_align_down potentially dropping bvecs, and thus losing bounce folios. While losing bio_vecs is real, bio_iov_iter_bounce_write allocates all segments and thus the entire bio in multiples of the minsize, thus making the rounding down redundant. Replace it with a safety rounding down of this_len in case something odd happens to the greedy folio allocation helper. Fixes: e7b8b3c5b2a6 ("block: align down bounces bios") Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Link: https://patch.msgid.link/20260907074111.721054-4-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/block/bio.c b/block/bio.c index f95b63c0604afd..57ee335899f3ad 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1389,6 +1389,14 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, folio = folio_alloc_greedy(GFP_KERNEL, &this_len, minsize); if (!folio) break; + + /* + * Align down the size to the minimum alignment. In practice + * this should not happen as minsize is expected to be a power + * of two, as is the allocation size, but it offers us a cheap + * extra safety belt. + */ + this_len &= ~(minsize - 1); bio_add_folio_nofail(bio, folio, this_len, 0); if (iter->nofault) @@ -1416,8 +1424,7 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, if (!bio->bi_iter.bi_size) return -ENOMEM; - return bio_iov_iter_align_down(bio, iter, - &bio->bi_io_vec[bio->bi_vcnt - 1], minsize - 1); + return 0; } static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter, From 44c209f294b88bfa55fc5fcb9753ed925ceeab6b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:50 +0300 Subject: [PATCH 0363/2286] block: split bio_iov_iter_bounce_write Factor out a bio_alloc_bounce_folios helper that we'll use for a different take on read-side bounce buffering soon. For that make it and also bio_free_folios available to callers outside of bio.c. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-2-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 58 ++++++++++++++++++++++++++------------------- include/linux/bio.h | 2 ++ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/block/bio.c b/block/bio.c index 57ee335899f3ad..67846e765f4a61 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1350,7 +1350,7 @@ static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size, return folio_alloc(gfp, get_order(*size)); } -static void bio_free_folios(struct bio *bio) +void bio_free_folios(struct bio *bio) { struct bio_vec *bv; int i; @@ -1363,11 +1363,8 @@ static void bio_free_folios(struct bio *bio) } } -static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, - size_t maxlen, size_t minsize) +int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize) { - size_t total_len = min(maxlen, iov_iter_count(iter)); - if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED))) return -EINVAL; if (WARN_ON_ONCE(bio->bi_iter.bi_size)) @@ -1377,7 +1374,6 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, do { size_t this_len = min(total_len, SZ_1M); - size_t copied; struct folio *folio; if (this_len > minsize * 2) @@ -1398,32 +1394,44 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, */ this_len &= ~(minsize - 1); bio_add_folio_nofail(bio, folio, this_len, 0); + total_len -= this_len; + } while (total_len && bio->bi_vcnt < bio->bi_max_vecs); + + if (!bio->bi_iter.bi_size) + return -ENOMEM; + return 0; +} + +static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, + size_t maxlen, size_t minsize) +{ + size_t total_len = min(maxlen, iov_iter_count(iter)); + size_t total_copied = 0; + struct bio_vec *bv; + int i, error; + + error = bio_alloc_bounce_folios(bio, total_len, minsize); + if (error) + return error; + + bio_for_each_bvec_all(bv, bio, i) { + struct folio *folio = page_folio(bv->bv_page); + size_t copied; if (iter->nofault) - copied = copy_folio_from_iter_atomic(folio, 0, this_len, - iter); + copied = copy_folio_from_iter_atomic(folio, 0, + bv->bv_len, iter); else - copied = copy_folio_from_iter(folio, 0, this_len, iter); - if (copied < this_len) { - /* - * Need to revert the iov iter for all bytes we have - * copied. - * - * However the bio size differs from the real copied - * bytes as @this_len is queued but only advanced - * less than that. - * Need to compensate that for the revert. - */ - iov_iter_revert(iter, bio->bi_iter.bi_size - this_len + - copied); + copied = copy_folio_from_iter(folio, 0, bv->bv_len, + iter); + total_copied += copied; + if (copied < bv->bv_len) { + iov_iter_revert(iter, total_copied); bio_free_folios(bio); return -EFAULT; } - total_len -= this_len; - } while (total_len && bio->bi_vcnt < bio->bi_max_vecs); + } - if (!bio->bi_iter.bi_size) - return -ENOMEM; return 0; } diff --git a/include/linux/bio.h b/include/linux/bio.h index bb3235497e673a..19e9add3614012 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -526,6 +526,8 @@ extern void bio_check_pages_dirty(struct bio *bio); int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen, size_t minsize); void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty); +int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize); +void bio_free_folios(struct bio *bio); extern void bio_copy_data(struct bio *dst, struct bio *src); extern void bio_free_pages(struct bio *bio); From 7848da3b1ec374620318bba49328093c99e846d4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:51 +0300 Subject: [PATCH 0364/2286] block: export fs_bio_integrity_{alloc,free} We'll move integrity generation and verification into the iomap submission helpers, which means they will be needed in modular file system code. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-3-hch@lst.de Signed-off-by: Jens Axboe --- block/bio-integrity-fs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/block/bio-integrity-fs.c b/block/bio-integrity-fs.c index 692403dfa047bb..4f67b34bf28905 100644 --- a/block/bio-integrity-fs.c +++ b/block/bio-integrity-fs.c @@ -31,6 +31,7 @@ unsigned int fs_bio_integrity_alloc(struct bio *bio) bio_integrity_setup_default(bio); return action; } +EXPORT_SYMBOL_GPL(fs_bio_integrity_alloc); void fs_bio_integrity_free(struct bio *bio) { @@ -43,6 +44,7 @@ void fs_bio_integrity_free(struct bio *bio) bio->bi_integrity = NULL; bio->bi_opf &= ~REQ_INTEGRITY; } +EXPORT_SYMBOL_GPL(fs_bio_integrity_free); void fs_bio_integrity_generate(struct bio *bio) { From 584e6e36b6d0b95af00e0ee38bab43607152c981 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:52 +0300 Subject: [PATCH 0365/2286] block: add a bio_prepare_reissue helper Add a helper to a clear a bio for reissue without a lot of the pointless clearing and reinitializing done by bio_reset and bio_reuse, and keeping the page pinning flag intact. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-4-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 20 ++++++++++++++++++++ include/linux/bio.h | 1 + 2 files changed, 21 insertions(+) diff --git a/block/bio.c b/block/bio.c index 67846e765f4a61..9654acfab113d0 100644 --- a/block/bio.c +++ b/block/bio.c @@ -320,6 +320,26 @@ void bio_reuse(struct bio *bio, blk_opf_t opf) } EXPORT_SYMBOL_GPL(bio_reuse); +/** + * bio_prepare_reissue - prepare a bio for reuissing the original I/O + * @bio: bio to reuse + * @bdev: block device to use the bio for + * + * Prepare @bio to be resubmitted to retry the original operation. + * The caller must reset bio->bi_iter to the original state. + */ +void bio_prepare_reissue(struct bio *bio, struct block_device *bdev) +{ + bio->bi_bdev = bdev; + bio_associate_blkg(bio); + bio->bi_flags &= + (BIO_PAGE_PINNED | BIO_CLONED | BIO_QUIET | BIO_REFFED); + bio->bi_status = BLK_STS_OK; + bio->bi_bvec_gap_bit = 0; + atomic_set(&bio->__bi_remaining, 1); +} +EXPORT_SYMBOL_GPL(bio_prepare_reissue); + static struct bio *__bio_chain_endio(struct bio *bio) { struct bio *parent = bio->bi_private; diff --git a/include/linux/bio.h b/include/linux/bio.h index 19e9add3614012..592636ead3310f 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -479,6 +479,7 @@ static inline void bio_init_inline(struct bio *bio, struct block_device *bdev, extern void bio_uninit(struct bio *); void bio_reset(struct bio *bio, struct block_device *bdev, blk_opf_t opf); void bio_reuse(struct bio *bio, blk_opf_t opf); +void bio_prepare_reissue(struct bio *bio, struct block_device *bdev); void bio_chain(struct bio *, struct bio *); void bio_await(struct bio *bio, void *priv, void (*submit)(struct bio *bio, void *priv)); From c94619c8a5b5d3b980433d247ac87a2d1c827cb6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:04 +0200 Subject: [PATCH 0366/2286] ACPI: glue: Carry out companion lookup under bus_type_sem When acpi_device_notify() looks up an ACPI companion for the given device, it invokes callbacks from struct acpi_bus_type() without synchronization which may lead to a use-after-free if the driver module containing those callbacks is unloaded at the same time. Address this by holding bus_type_sem throughout the entire ACPI companion lookup and the execution of the .setup() callback in struct acpi_bus_type (if present) instead of dropping the semaphore (prematurely) after finding a matching struct acpi_bus_type. For this purpose, rename acpi_get_bus_type() to acpi_companion_lookup(), make it return a struct acpi_device pointer, and move the relevant code from acpi_device_notify() to it. Also notice that the only case in which the .bind() callback from an ACPI scan handler may need to be invoked is when the given device is a platform one, so adjust acpi_device_notify() accordingly and drop the "done" label that is not used any more from it. Fixes: 2ef5236660b6 ("ACPI: glue: Look for ACPI bus type only if ACPI companion is not known") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3089807.e9J7NaK4W3@rafael.j.wysocki --- drivers/acpi/glue.c | 61 +++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index a47cccc4efd3f8..40e6513a9942bc 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -59,19 +59,34 @@ int unregister_acpi_bus_type(struct acpi_bus_type *type) } EXPORT_SYMBOL_GPL(unregister_acpi_bus_type); -static struct acpi_bus_type *acpi_get_bus_type(struct device *dev) +static struct acpi_device *acpi_companion_lookup(struct device *dev) { - struct acpi_bus_type *tmp, *ret = NULL; + struct acpi_bus_type *type; - down_read(&bus_type_sem); - list_for_each_entry(tmp, &bus_type_list, list) { - if (tmp->match(dev)) { - ret = tmp; - break; + guard(rwsem_read)(&bus_type_sem); + + list_for_each_entry(type, &bus_type_list, list) { + struct acpi_device *adev; + + if (!type->match(dev)) + continue; + + adev = type->find_companion(dev); + if (!adev) { + dev_dbg(dev, "ACPI companion not found\n"); + return NULL; } + if (acpi_bind_one(dev, adev)) { + dev_dbg(dev, "Binding to ACPI companion failed\n"); + return NULL; + } + if (type->setup) + type->setup(dev); + + return adev; } - up_read(&bus_type_sem); - return ret; + + return NULL; } #define FIND_CHILD_MIN_SCORE 1 @@ -360,40 +375,22 @@ void acpi_device_notify(struct device *dev) ret = acpi_bind_one(dev, NULL); if (ret) { - struct acpi_bus_type *type = acpi_get_bus_type(dev); - - if (!type) + adev = acpi_companion_lookup(dev); + if (!adev) return; - - adev = type->find_companion(dev); - if (!adev) { - dev_dbg(dev, "ACPI companion not found\n"); - return; - } - ret = acpi_bind_one(dev, adev); - if (ret) { - dev_dbg(dev, "Binding to ACPI companion failed\n"); - return; - } - if (type->setup) { - type->setup(dev); - goto done; - } } else { adev = ACPI_COMPANION(dev); if (dev_is_pci(dev)) { pci_acpi_setup(dev, adev); - goto done; } else if (dev_is_platform(dev)) { acpi_configure_pmsi_domain(dev); + + if (adev->handler && adev->handler->bind) + adev->handler->bind(dev); } } - if (adev->handler && adev->handler->bind) - adev->handler->bind(dev); - -done: dev_dbg(dev, "Bound to ACPI device %s\n", acpi_dev_name(adev)); } From fcfb2b562115632e0a60a0928dca495f98eb335c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:10 +0200 Subject: [PATCH 0367/2286] ACPI: glue: Rearrange acpi_bind_one() to avoid breakage Rearange the code in acpi_bind_one() to avoid situations in which the existing ACPI companion of the given device would be replaced with NULL due to a memory allocation error or because somebody tries to bind a physical device with an ACPI companion to a different ACPI device erroneously. Fixes: 7b1998116bbb ("ACPI / driver core: Store an ACPI device pointer in struct acpi_dev_node") Signed-off-by: Rafael J. Wysocki [ rjw: Merged two conditionals to avoid redundant check ] Link: https://patch.msgid.link/3465482.44csPzL39Z@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 58 +++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 40e6513a9942bc..1e9e7150b95a79 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -243,31 +243,25 @@ static void acpi_physnode_link_name(char *buf, unsigned int node_id) int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) { struct acpi_device_physical_node *physical_node, *pn; + struct acpi_device *comp_dev = ACPI_COMPANION(dev); char physical_node_name[PHYSICAL_NODE_NAME_SIZE]; struct list_head *physnode_list; unsigned int node_id; int retval = -EINVAL; - if (has_acpi_companion(dev)) { - if (acpi_dev) { - dev_warn(dev, "ACPI companion already set\n"); + if (!acpi_dev) { + if (!comp_dev) return -EINVAL; - } else { - acpi_dev = ACPI_COMPANION(dev); - } - } - if (!acpi_dev) - return -EINVAL; - acpi_dev_get(acpi_dev); - get_device(dev); - physical_node = kzalloc_obj(*physical_node); - if (!physical_node) { - retval = -ENOMEM; - goto err; + /* If the companion has been set upfront, pick it up. */ + acpi_dev = comp_dev; + } else if (comp_dev && acpi_dev != comp_dev) { + dev_warn(dev, "ACPI companion already set to %s which is not %s\n", + acpi_dev_name(comp_dev), acpi_dev_name(acpi_dev)); + return -EEXIST; } - mutex_lock(&acpi_dev->physical_node_lock); + guard(mutex)(&acpi_dev->physical_node_lock); /* * Keep the list sorted by node_id so that the IDs of removed nodes can @@ -278,15 +272,12 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) list_for_each_entry(pn, &acpi_dev->physical_node_list, node) { /* Sanity check. */ if (pn->dev == dev) { - mutex_unlock(&acpi_dev->physical_node_lock); - - dev_warn(dev, "Already associated with ACPI node\n"); - kfree(physical_node); - if (ACPI_COMPANION(dev) != acpi_dev) - goto err; - - put_device(dev); - acpi_dev_put(acpi_dev); + if (!comp_dev) { + /* Really unexpected. */ + ACPI_COMPANION_SET(dev, acpi_dev); + dev_warn(&acpi_dev->dev, + "Physical device list corruption fixed up\n"); + } return 0; } if (pn->node_id == node_id) { @@ -295,12 +286,19 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) } } + physical_node = kzalloc_obj(*physical_node); + if (!physical_node) + return -ENOMEM; + + acpi_dev_get(acpi_dev); + get_device(dev); + physical_node->node_id = node_id; physical_node->dev = dev; list_add(&physical_node->node, physnode_list); acpi_dev->physical_node_count++; - if (!has_acpi_companion(dev)) + if (!comp_dev) ACPI_COMPANION_SET(dev, acpi_dev); acpi_physnode_link_name(physical_node_name, node_id); @@ -316,18 +314,10 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) dev_err(dev, "Failed to create link firmware_node (%d)\n", retval); - mutex_unlock(&acpi_dev->physical_node_lock); - if (acpi_dev->wakeup.flags.valid) device_set_wakeup_capable(dev, true); return 0; - - err: - ACPI_COMPANION_SET(dev, NULL); - put_device(dev); - acpi_dev_put(acpi_dev); - return retval; } EXPORT_SYMBOL_GPL(acpi_bind_one); From 693546852656075e0247ac637b05625ead134eff Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:16 +0200 Subject: [PATCH 0368/2286] ACPI: glue: Fix up and adjust acpi_unbind_one() Since none of the acpi_unbind_one() callers check its return value and it always returns 0 anyway, make it void. Also notice that unlocking physical_node_lock for the given ACPI device should be carried out before dropping the reference to it in case that reference is the last one (highly unlikely), so rearrange the code to make that happen. Fixes: 3e3327837c18 ("ACPI: Use list_for_each_entry() in acpi_unbind_one()") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/10940500.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/glue.c | 10 ++++++---- include/acpi/acpi_bus.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 1e9e7150b95a79..e55daa4221da86 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -321,13 +321,13 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) } EXPORT_SYMBOL_GPL(acpi_bind_one); -int acpi_unbind_one(struct device *dev) +void acpi_unbind_one(struct device *dev) { struct acpi_device *acpi_dev = ACPI_COMPANION(dev); struct acpi_device_physical_node *entry; if (!acpi_dev) - return 0; + return; mutex_lock(&acpi_dev->physical_node_lock); @@ -342,15 +342,17 @@ int acpi_unbind_one(struct device *dev) sysfs_remove_link(&acpi_dev->dev.kobj, physnode_name); sysfs_remove_link(&dev->kobj, "firmware_node"); ACPI_COMPANION_SET(dev, NULL); + + mutex_unlock(&acpi_dev->physical_node_lock); + /* Drop references taken by acpi_bind_one(). */ put_device(dev); acpi_dev_put(acpi_dev); kfree(entry); - break; + return; } mutex_unlock(&acpi_dev->physical_node_lock); - return 0; } EXPORT_SYMBOL_GPL(acpi_unbind_one); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b239..93b00635ba3dcd 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -665,7 +665,7 @@ struct acpi_bus_type { int register_acpi_bus_type(struct acpi_bus_type *); int unregister_acpi_bus_type(struct acpi_bus_type *); int acpi_bind_one(struct device *dev, struct acpi_device *adev); -int acpi_unbind_one(struct device *dev); +void acpi_unbind_one(struct device *dev); enum acpi_bridge_type { ACPI_BRIDGE_TYPE_PCIE = 1, From ea9102cae4a634a752749dda4914cb235910134e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:22 +0200 Subject: [PATCH 0369/2286] ACPI: glue: Skip devices with no type in acpi_device_notify() Since all of the users of struct acpi_bus_type use the device type for matching, devices without a type will not be matched by any of them, so they can be skipped early in acpi_device_notify(). Update the code accordingly. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2078562.PYKUYFuaPT@rafael.j.wysocki --- drivers/acpi/glue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index e55daa4221da86..0959f58cb77c65 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -63,6 +63,9 @@ static struct acpi_device *acpi_companion_lookup(struct device *dev) { struct acpi_bus_type *type; + if (!dev->type) + return NULL; + guard(rwsem_read)(&bus_type_sem); list_for_each_entry(type, &bus_type_list, list) { From d3515092f1bff9cfb4d99ea82dcd4ac66f32f50d Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sat, 22 Aug 2026 17:12:36 +0530 Subject: [PATCH 0370/2286] thermal: gov_power_allocator: Fix NULL pointer dereference in update_tz() power_allocator_update_tz() unconditionally derives the trip descriptor of params->trip_max as below, const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max); and then walks td->thermal_instances. However, params->trip_max is allowed to be NULL and in that case the list walk dereferences a bogus pointer derived from NULL which oops the kernel. get_governor_trips() picks trip_switch_on and trip_max out of the trip table of the zone. When the zone has neither a passive nor an active trip point, last_active is NULL and params->trip_max is left NULL. This is an explicitly supported configuration, as documented in the function get_governor_trips() as below, If there are no passive or active trip points, then the governor won't do anything. In fact, its throttle function won't be called at all. Return early from power_allocator_update_tz() when params->trip_max is NULL and only compute the trip descriptor after that check. There is nothing to update in that case anyway, with no trip_max and there are no thermal instances for the governor to account for, num_actors stays zero and total_weight is irrelevant. Fixes: 912e97c67cc3 ("thermal: gov_power_allocator: Move memory allocation out of throttle()") Signed-off-by: Sumeet Pawnikar Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260822114236.15998-1-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/gov_power_allocator.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c index 37f2e22a999e64..b5c254187628bd 100644 --- a/drivers/thermal/gov_power_allocator.c +++ b/drivers/thermal/gov_power_allocator.c @@ -660,10 +660,15 @@ static void power_allocator_update_tz(struct thermal_zone_device *tz, enum thermal_notify_event reason) { struct power_allocator_params *params = tz->governor_data; - const struct thermal_trip_desc *td = trip_to_trip_desc(params->trip_max); + const struct thermal_trip_desc *td; struct thermal_instance *instance; int num_actors = 0; + if (!params->trip_max) + return; + + td = trip_to_trip_desc(params->trip_max); + switch (reason) { case THERMAL_TZ_BIND_CDEV: case THERMAL_TZ_UNBIND_CDEV: From 04cb4f75f314e137f0a0c38f9b4fd5c2b4b5667d Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 13 Aug 2026 07:43:42 +0000 Subject: [PATCH 0371/2286] clocksource/drivers/timer-ti-dm: Unregister CPU PM notifier outside of the timer lock omap_dm_timer_remove() calls cpu_pm_unregister_notifier() with dm_timer_lock held and interrupts disabled. Nothing sleeps in there today, but it pins the helper into a context where it can never be allowed to sleep, which is in the way of restoring the RCU grace period on the cpu_pm notifier chain. Do the list lookup under the lock and move the unregister after the unlock. Nothing can race it at that point: remove() owns the device, and once the timer is off the list nobody can reach it anymore. Signed-off-by: Bradley Morgan Link: https://patch.msgid.link/20260813074343.26460-2-include@grrlz.net Signed-off-by: Rafael J. Wysocki --- drivers/clocksource/timer-ti-dm.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c index 6787acac9a4387..922a8f14562c95 100644 --- a/drivers/clocksource/timer-ti-dm.c +++ b/drivers/clocksource/timer-ti-dm.c @@ -1528,7 +1528,7 @@ static int omap_dm_timer_probe(struct platform_device *pdev) */ static void omap_dm_timer_remove(struct platform_device *pdev) { - struct dmtimer *timer; + struct dmtimer *timer, *found = NULL; unsigned long flags; int ret = -EINVAL; @@ -1536,14 +1536,17 @@ static void omap_dm_timer_remove(struct platform_device *pdev) list_for_each_entry(timer, &omap_timer_list, node) if (!strcmp(dev_name(&timer->pdev->dev), dev_name(&pdev->dev))) { - if (!(timer->capability & OMAP_TIMER_ALWON)) - cpu_pm_unregister_notifier(&timer->nb); list_del(&timer->node); + found = timer; ret = 0; break; } spin_unlock_irqrestore(&dm_timer_lock, flags); + /* Unregister outside the lock: cpu_pm_unregister_notifier() may sleep. */ + if (found && !(found->capability & OMAP_TIMER_ALWON)) + cpu_pm_unregister_notifier(&found->nb); + pm_runtime_disable(&pdev->dev); if (ret) From 61f7eee7d4664079fbce821576891c3ee5ccf597 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 13 Aug 2026 07:43:43 +0000 Subject: [PATCH 0372/2286] PM: CPU: Restore synchronize_rcu() to cpu_pm_unregister_notifier() cpu_pm_notify() walks the notifier chain lockless under only rcu_read_lock(). That only works if a removed notifier block is not freed until every concurrent walker is done with it. The chain used to be an atomic_notifier, whose unregister ends in synchronize_rcu(). Commit b2f6662ac08d ("PM: cpu: Make notifier chain use a raw_spinlock_t") switched it over to a raw_notifier, and in doing so replaced that with raw_notifier_chain_unregister(), which does not synchronize. The grace period quietly went away, so a driver that frees the memory holding its notifier block right after cpu_pm_unregister_notifier() returns can race with a concurrent walker: cpu1 (idle exit) cpu2 (driver remove) ---------------- -------------------- cpu_pm_notify(CPU_PM_EXIT) rcu_read_lock() nb = rcu_dereference_raw(*nl) cpu_pm_unregister_notifier(&od->nb) unlink, no grace period remove() frees od (devm) nb->notifier_call(nb, ...) /* use after free */ The rcu_read_lock() on cpu1 does not stop cpu2 from freeing the block, and nothing else does. The read side is the idle exit path, so this can hit on any system where a driver with a cpu_pm notifier gets unbound. Add the grace period back. It is only needed when a notifier was actually removed, so wait on success and return -ENOENT without waiting otherwise. The kerneldoc gets back the "may sleep" note that the same commit dropped. Fixes: b2f6662ac08d ("PM: cpu: Make notifier chain use a raw_spinlock_t") Cc: All applicable Signed-off-by: Bradley Morgan [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260813074343.26460-3-include@grrlz.net Signed-off-by: Rafael J. Wysocki --- kernel/cpu_pm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kernel/cpu_pm.c b/kernel/cpu_pm.c index 7481fbb947d32e..a2a598ad6e6d50 100644 --- a/kernel/cpu_pm.c +++ b/kernel/cpu_pm.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -76,7 +77,8 @@ EXPORT_SYMBOL_GPL(cpu_pm_register_notifier); * * Remove a driver from the CPU PM notifier list. * - * This function has the same return conditions as raw_notifier_chain_unregister. + * This function may sleep, and has the same return conditions as + * raw_notifier_chain_unregister. */ int cpu_pm_unregister_notifier(struct notifier_block *nb) { @@ -86,6 +88,11 @@ int cpu_pm_unregister_notifier(struct notifier_block *nb) raw_spin_lock_irqsave(&cpu_pm_notifier.lock, flags); ret = raw_notifier_chain_unregister(&cpu_pm_notifier.chain, nb); raw_spin_unlock_irqrestore(&cpu_pm_notifier.lock, flags); + + /* Wait for the rcu_read_lock() walkers in cpu_pm_notify(). */ + if (!ret) + synchronize_rcu(); + return ret; } EXPORT_SYMBOL_GPL(cpu_pm_unregister_notifier); From d594187cafd5ad65afc5f00242f8fa20e1d8c0f1 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Thu, 27 Aug 2026 17:48:19 +0800 Subject: [PATCH 0373/2286] cpuidle: menu: Do not return a disabled idle state When idle state 0 is disabled and the PM QoS latency constraint is zero (latency_req == 0), the first branch of the || in the early-return condition of menu_select() is taken immediately, so state 0 is returned regardless of its disable flag. Address this by making the !disable check a prerequisite for the entire early-return condition, so that when state 0 is disabled the normal state selection loop is used instead, which already skips disabled states. Fixes: 53812cdc9100 ("cpuidle: menu: Move the latency_req == 0 special case check") Signed-off-by: Xueqin Luo Link: https://patch.msgid.link/20260827094820.3900062-1-luoxueqin@kylinos.cn Signed-off-by: Rafael J. Wysocki --- drivers/cpuidle/governors/menu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/cpuidle/governors/menu.c b/drivers/cpuidle/governors/menu.c index 544a5d59300778..eb529d73d4b752 100644 --- a/drivers/cpuidle/governors/menu.c +++ b/drivers/cpuidle/governors/menu.c @@ -284,10 +284,10 @@ static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, data->bucket = BUCKETS - 1; } - if (latency_req == 0 || - ((data->next_timer_ns < drv->states[1].target_residency_ns || - latency_req < drv->states[1].exit_latency_ns) && - !dev->states_usage[0].disable)) { + if (!dev->states_usage[0].disable && + (latency_req == 0 || + data->next_timer_ns < drv->states[1].target_residency_ns || + latency_req < drv->states[1].exit_latency_ns)) { /* * In this case state[0] will be used no matter what, so return * it right away and keep the tick running if state[0] is a From 60d5fca628f94f8f23398ad62d80aa284dae6571 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Fri, 28 Aug 2026 11:24:57 +0800 Subject: [PATCH 0374/2286] cpuidle: teo: Do not return a disabled idle state If idle state 0 is disabled and none of the enabled idle states satisfies the PM QoS latency constraint, constraint_idx in teo_select() stays at its initial value of 0, because the loop that updates it starts at state 1, and so the candidate state index is capped at state 0, which is disabled, and that state is returned to the caller. The cpuidle core does not validate the returned state index against the per-state disable flags, so the CPU ends up entering the disabled state 0. To address this, ensure that constraint_idx is at least equal to the index of the first enabled idle state (idx0), so that the candidate state is never capped to a disabled one. Fixes: c410a9a142f1 ("cpuidle: teo: Change the main idle state selection logic") Signed-off-by: Xueqin Luo Reviewed-by: Christian Loehle Link: https://patch.msgid.link/20260828032457.2317326-1-luoxueqin@kylinos.cn Signed-off-by: Rafael J. Wysocki --- drivers/cpuidle/governors/teo.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/cpuidle/governors/teo.c b/drivers/cpuidle/governors/teo.c index ac43b9b013b3fc..906f4f89e3659d 100644 --- a/drivers/cpuidle/governors/teo.c +++ b/drivers/cpuidle/governors/teo.c @@ -426,6 +426,17 @@ static int teo_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, } } + /* + * If the latency constraint does not allow any of the enabled idle + * states to be used, the candidate state index will be capped to 0 + * by the check below, but state 0 may be disabled. To prevent the + * selection of a disabled state in that case, ensure that + * constraint_idx is at least equal to the index of the first + * enabled idle state. + */ + if (constraint_idx < idx0) + constraint_idx = idx0; + /* * If there is a latency constraint, it may be necessary to select an * idle state shallower than the current candidate one. From 797a8e3cb5539a82cc0364c41a850aba940d436a Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:22 -0400 Subject: [PATCH 0375/2286] alpha: enter hardirq context before looking up the irq descriptor handle_irq() called irq_to_desc() before irq_enter(), so that lookup ran with the preempt count still saying task context and with RCU not yet watching. Generic code called from an interrupt handler should see hardirq context, and irq_to_desc() is more than an array index once SPARSE_IRQ is in use. Move irq_enter() to the top of the function and add the matching irq_exit() to the invalid interrupt path. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-1-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/kernel/irq.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/alpha/kernel/irq.c b/arch/alpha/kernel/irq.c index 4a6a8b1d5a8b1b..5867a165504511 100644 --- a/arch/alpha/kernel/irq.c +++ b/arch/alpha/kernel/irq.c @@ -107,18 +107,21 @@ handle_irq(int irq) * handled by some other CPU. (or is disabled) */ static unsigned int illegal_count=0; - struct irq_desc *desc = irq_to_desc(irq); - + struct irq_desc *desc; + + irq_enter(); + + desc = irq_to_desc(irq); if (!desc || ((unsigned) irq > ACTUAL_NR_IRQS && illegal_count < MAX_ILLEGAL_IRQS)) { irq_err_count++; illegal_count++; printk(KERN_CRIT "device_interrupt: invalid interrupt %d\n", irq); + irq_exit(); return; } - irq_enter(); generic_handle_irq_desc(desc); irq_exit(); } From 63d97e41ec92d611644c6ad134f10a98d9534cb5 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:23 -0400 Subject: [PATCH 0376/2286] alpha: select ARCH_HAS_UBSAN GCC's undefined behavior sanitizer has no architecture-specific runtime requirements. Enable it for Alpha to allow UBSAN instrumentation of the kernel. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-2-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 51a7391869dd65..713a0c1cc93050 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO select ARCH_MODULE_NEEDS_WEAK_PER_CPU if SMP From 278de0f04816c97f701456b72af4f59d36440249 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:24 -0400 Subject: [PATCH 0377/2286] alpha: select ARCH_HAS_GCOV_PROFILE_ALL GCC's gcov instrumentation (-fprofile-arcs -ftest-coverage) has no architecture-specific runtime requirements. Enable GCOV_PROFILE_ALL support for Alpha to allow whole-kernel code coverage analysis. Also mark Alpha as "ok" in the gcov-profile-all feature matrix. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-3-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/debug/gcov-profile-all/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/gcov-profile-all/arch-support.txt b/Documentation/features/debug/gcov-profile-all/arch-support.txt index 63494bddc2638e..4701209b869d68 100644 --- a/Documentation/features/debug/gcov-profile-all/arch-support.txt +++ b/Documentation/features/debug/gcov-profile-all/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | TODO | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 713a0c1cc93050..c4da16971652be 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_GCOV_PROFILE_ALL select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT select ARCH_MIGHT_HAVE_PC_SERIO From ca33550bf0f4feedfe71e75d0dd12546770d11f4 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:17:24 +0530 Subject: [PATCH 0378/2286] cpuidle: tegra: fix repeated word 'that' in comment Drop the second 'that', reported by checkpatch.pl as a possible repeated word. Only touches a comment, no code changes. Signed-off-by: Hemanth Selam [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260907064725.10686-5-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/cpuidle/cpuidle-tegra.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpuidle/cpuidle-tegra.c b/drivers/cpuidle/cpuidle-tegra.c index aca907a62bb5de..d2fc216c38fc56 100644 --- a/drivers/cpuidle/cpuidle-tegra.c +++ b/drivers/cpuidle/cpuidle-tegra.c @@ -281,7 +281,7 @@ static int tegra114_enter_s2idle(struct cpuidle_device *dev, * LP2 | C7 (CPU core power gating) * LP2 | CC6 (CPU cluster power gating) * - * Note that that the older CPUIDLE driver versions didn't explicitly + * Note that the older CPUIDLE driver versions didn't explicitly * differentiate the LP2 states because these states either used the same * code path or because CC6 wasn't supported. */ From 2b41fbab67ef7ccdb48c7a5a69c38dfadba610e8 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:25 -0400 Subject: [PATCH 0379/2286] alpha: select HAVE_DEBUG_KMEMLEAK Kmemleak needs no architecture support beyond the standard linker script symbols for the data and bss sections, which Alpha already provides, and a scan of the kernel stacks, which is done generically. Also mark Alpha as "ok" in the kmemleak feature matrix. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-4-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- Documentation/features/debug/kmemleak/arch-support.txt | 2 +- arch/alpha/Kconfig | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/features/debug/kmemleak/arch-support.txt b/Documentation/features/debug/kmemleak/arch-support.txt index f45149cfa313d4..b65b3f8c0e89f6 100644 --- a/Documentation/features/debug/kmemleak/arch-support.txt +++ b/Documentation/features/debug/kmemleak/arch-support.txt @@ -6,7 +6,7 @@ ----------------------- | arch |status| ----------------------- - | alpha: | TODO | + | alpha: | ok | | arc: | ok | | arm: | ok | | arm64: | ok | diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index c4da16971652be..f6d1d246967736 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -19,6 +19,7 @@ config ALPHA select PCI_DOMAINS if PCI select PCI_SYSCALL if PCI select HAVE_ASM_MODVERSIONS + select HAVE_DEBUG_KMEMLEAK select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS From 533993a3ac5db0542b5b781cad47115eae182486 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:26 -0400 Subject: [PATCH 0380/2286] alpha: select EDAC_SUPPORT The EDAC core builds and works on Alpha; it only needs the architecture to declare support. There is no Alpha memory controller driver yet, but the core is what a future driver, or the existing machine check error decoding in err_ev6.c and err_ev7.c, would report through. Assisted-by: Claude:claude-opus-5 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-5-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index f6d1d246967736..032c23f979c498 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -32,6 +32,7 @@ config ALPHA select ARCH_WANT_IPC_PARSE_VERSION select ARCH_HAVE_NMI_SAFE_CMPXCHG select AUDIT_ARCH + select EDAC_SUPPORT select GENERIC_CPU_VULNERABILITIES select GENERIC_SMP_IDLE_THREAD select HAS_IOPORT From b345db9e12089e0221e4ac9e36124affdf879443 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:27 -0400 Subject: [PATCH 0381/2286] alpha: select ARCH_SUPPORTS_ATOMIC_RMW Alpha has native LL/SC instructions (LDL_L/STL_C, LDQ_L/STQ_C) that provide atomic read-modify-write capability, and already selects ARCH_HAVE_NMI_SAFE_CMPXCHG. Selecting ARCH_SUPPORTS_ATOMIC_RMW enables MUTEX_SPIN_ON_OWNER and RWSEM_SPIN_ON_OWNER on SMP, allowing optimistic spinning on contended locks rather than immediately sleeping. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-6-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 032c23f979c498..d039b7135d18c7 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -14,6 +14,7 @@ config ALPHA select ARCH_MODULE_NEEDS_WEAK_PER_CPU if SMP select ARCH_NO_PREEMPT select ARCH_NO_SG_CHAIN + select ARCH_SUPPORTS_ATOMIC_RMW select ARCH_USE_CMPXCHG_LOCKREF select FORCE_PCI select PCI_DOMAINS if PCI From d8df465bc46d1d6169a05dd55f82ec0dfa639a31 Mon Sep 17 00:00:00 2001 From: Matt Turner Date: Sat, 5 Sep 2026 15:29:28 -0400 Subject: [PATCH 0382/2286] alpha: select ARCH_HAS_FAST_MULTIPLIER All Alpha CPUs have hardware integer multiply (MULL/MULQ). EV4 and EV5 implement it in microcode; EV56 and later have fast single-cycle multiply. Since no EV4 system is likely to run a modern kernel, select this unconditionally to enable multiply-based fast paths in hweight and string routines. Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Matt Turner Tested-by: Magnus Lindholm Reviewed-by: Magnus Lindholm Link: https://lore.kernel.org/r/20260905-arch-kconfig-options-v2-v2-7-83e8245761a2@gmail.com Signed-off-by: Magnus Lindholm --- arch/alpha/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index d039b7135d18c7..2672971b83f311 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -7,6 +7,7 @@ config ALPHA select ARCH_32BIT_USTAT_F_TINODE select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DMA_OPS if PCI + select ARCH_HAS_FAST_MULTIPLIER select ARCH_HAS_GCOV_PROFILE_ALL select ARCH_HAS_UBSAN select ARCH_MIGHT_HAVE_PC_PARPORT From 3b8650f2e8810f0baa48608c8dfe4f44120de123 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Thu, 27 Aug 2026 20:32:56 +0800 Subject: [PATCH 0383/2286] PM: hibernate: docs: update swsusp.txt references to swsusp.rst The swsusp.txt documentation file has been renamed to swsusp.rst. Update the references in the Documentation/power/userland-swsusp.rst comments to use the correct filename. Signed-off-by: Li Jun [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260827123256.2548946-1-lijun01@kylinos.cn Signed-off-by: Rafael J. Wysocki --- Documentation/power/userland-swsusp.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/power/userland-swsusp.rst b/Documentation/power/userland-swsusp.rst index 1cf62d80a9ca10..b00b850148b2c8 100644 --- a/Documentation/power/userland-swsusp.rst +++ b/Documentation/power/userland-swsusp.rst @@ -4,9 +4,9 @@ Documentation for userland software suspend interface (C) 2006 Rafael J. Wysocki -First, the warnings at the beginning of swsusp.txt still apply. +First, the warnings at the beginning of swsusp.rst still apply. -Second, you should read the FAQ in swsusp.txt _now_ if you have not +Second, you should read the FAQ in swsusp.rst _now_ if you have not done it already. Now, to use the userland interface for software suspend you need special From be5755f023e7e02ec90bbd1d4ca63a0abe594733 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Mon, 31 Aug 2026 18:23:22 -0700 Subject: [PATCH 0384/2286] PM: runtime: Only queue an idle check for RPM-linked suppliers When a device RPM-suspends, it queues up an idle check for all of its suppliers, in case that device was the last consumer, and those suppliers are now able to suspend. Today, we do this for all suppliers, and not only for those suppliers that are marked for use by runtime PM. This isn't directly harmful, but it is a bit wasteful, and also may induce unexpected suspend attempts (e.g., if a device was purposely last touched with pm_runtime_put_noidle()). To avoid excess idle checks, only call pm_request_idle() on linked suppliers that opted into runtime PM. Noticed by inspection of trace logs. This is not expected to have a functional impact on systems that are using device links properly, and should only be considered an optimization. Fixes: 5244f5e2d801 ("PM: runtime: Defer suspending suppliers") Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260901013341.1417232-1-briannorris@chromium.org Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index fab38bc981139c..4d84d97f09c4e5 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -361,7 +361,8 @@ static void rpm_suspend_suppliers(struct device *dev) list_for_each_entry_rcu(link, &dev->links.suppliers, c_node, device_links_read_lock_held()) - pm_request_idle(link->supplier); + if (device_link_test(link, DL_FLAG_PM_RUNTIME)) + pm_request_idle(link->supplier); device_links_read_unlock(idx); } From e7572e381d049daabc236f5a12c67adcaa117791 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Mon, 31 Aug 2026 18:23:23 -0700 Subject: [PATCH 0385/2286] PM: runtime: Add kunit test for supplier idle/suspend Add a unit test that covers the bugfix/optimization in the previous patch, "PM: runtime: Only queue an idle check for RPM-linked suppliers". This test ensures: 1) device links with DL_FLAG_PM_RUNTIME can RPM-resume/suspend properly; and 2) device links without DL_FLAG_PM_RUNTIME are not accidentally resumed/suspended along with the consumer. Notably, without patch 1 ("PM: runtime: Only queue an idle check for RPM-linked suppliers"), the no-RPM supplier will fail 2 of its last 3 test expectations: $ tools/testing/kunit/kunit.py run --kconfig_add CONFIG_PM=y 'pm_runtime*' ... ## Fails because rpm_suspend_suppliers() suspended the wrong links [17:01:44] # pm_runtime_supplier_suspend_test: EXPECTATION FAILED at drivers/base/power/runtime-test.c:279 [17:01:44] Expected pm_runtime_active(norpm_supplier) to be true, but is false ## Fails because the no-RPM supplier is already suspended unexpectedly [17:01:44] # pm_runtime_supplier_suspend_test: EXPECTATION FAILED at drivers/base/power/runtime-test.c:282 [17:01:44] Expected 0 == pm_runtime_suspend(norpm_supplier), but [17:01:44] pm_runtime_suspend(norpm_supplier) == 1 (0x1) ... Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260901013341.1417232-2-briannorris@chromium.org Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime-test.c | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c index 1535ad2b02649f..24865ce844fc6a 100644 --- a/drivers/base/power/runtime-test.c +++ b/drivers/base/power/runtime-test.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -229,6 +230,61 @@ static void pm_runtime_probe_active_test(struct kunit *test) KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev)); } +static void pm_runtime_supplier_suspend_test(struct kunit *test) +{ + struct device *dev = kunit_device_register(test, DEVICE_NAME); + struct device *supplier = kunit_device_register(test, DEVICE_NAME "_supplier"); + struct device *norpm_supplier = kunit_device_register(test, DEVICE_NAME "_norpm_supplier"); + + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, supplier); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, norpm_supplier); + + KUNIT_ASSERT_NOT_NULL(test, device_link_add(dev, supplier, DL_FLAG_PM_RUNTIME)); + KUNIT_ASSERT_NOT_NULL(test, device_link_add(dev, norpm_supplier, 0)); + + pm_runtime_enable(dev); + pm_runtime_enable(supplier); + pm_runtime_enable(norpm_supplier); + + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(norpm_supplier)); + + /* + * Resume the device and RPM-linked supplier; non-RPM-linked supplier + * stays suspended. + */ + KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev)); + KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev)); + KUNIT_EXPECT_TRUE(test, pm_runtime_active(supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(norpm_supplier)); + + /* Also resume the non-RPM-linked supplier. */ + KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(norpm_supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_active(norpm_supplier)); + + pm_runtime_put_noidle(norpm_supplier); + KUNIT_EXPECT_TRUE(test, pm_runtime_active(norpm_supplier)); + + /* + * Suspend device. This should only suspend the device and its + * RPM-linked supplier, not the non-RPM-linked supplier. + */ + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev)); + /* Note: supplier suspend is async, so we have to flush the queue. */ + flush_workqueue(pm_wq); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_active(norpm_supplier)); + + /* Now suspend non-RPM-linked supplier. */ + KUNIT_EXPECT_EQ(test, 0, pm_runtime_suspend(norpm_supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(supplier)); + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(norpm_supplier)); +} + static struct kunit_case pm_runtime_test_cases[] = { KUNIT_CASE(pm_runtime_depth_test), KUNIT_CASE(pm_runtime_already_suspended_test), @@ -236,6 +292,7 @@ static struct kunit_case pm_runtime_test_cases[] = { KUNIT_CASE(pm_runtime_disabled_test), KUNIT_CASE(pm_runtime_error_test), KUNIT_CASE(pm_runtime_probe_active_test), + KUNIT_CASE(pm_runtime_supplier_suspend_test), {} }; From 16959c469f232f5c76cfb628297cafbf6dbdba79 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 4 Sep 2026 16:48:55 +0200 Subject: [PATCH 0386/2286] lsm: expose mount idmaps to inode hooks OverlayFS performs upper-layer operations through inode-based security hooks. Those hooks receive the upper inode and dentry, but not the mount idmap used by the VFS operation. The security layer cannot distinguish an identity-mapped upper from an idmapped one or make the same ownership decision as the VFS. The VFS layer already passes the idmap down into all relevant inode operations so this just brings the security hooks to parity. So pass the mount idmap through the create, link, symlink, mkdir, mknod, and permission hooks. Update the in-tree security implementations and non-VFS callers accordingly. Signed-off-by: Daan De Meyer Reviewed-by: Christian Brauner (Amutable) [PM: updated the commit description as discussed on-list] Signed-off-by: Paul Moore --- fs/cachefiles/security.c | 4 +-- fs/namei.c | 18 +++++------ include/linux/lsm_hook_defs.h | 23 +++++++------- include/linux/security.h | 58 ++++++++++++++++++++--------------- security/security.c | 40 ++++++++++++++---------- security/selinux/hooks.c | 19 ++++++++---- security/smack/smack_lsm.c | 9 ++++-- 7 files changed, 100 insertions(+), 71 deletions(-) diff --git a/fs/cachefiles/security.c b/fs/cachefiles/security.c index fc6611886b3b5e..eefe5453b904e6 100644 --- a/fs/cachefiles/security.c +++ b/fs/cachefiles/security.c @@ -51,14 +51,14 @@ static int cachefiles_check_cache_dir(struct cachefiles_cache *cache, { int ret; - ret = security_inode_mkdir(d_backing_inode(root), root, 0); + ret = security_inode_mkdir(&nop_mnt_idmap, d_backing_inode(root), root, 0); if (ret < 0) { pr_err("Security denies permission to make dirs: error %d", ret); return ret; } - ret = security_inode_create(d_backing_inode(root), root, 0); + ret = security_inode_create(&nop_mnt_idmap, d_backing_inode(root), root, 0); if (ret < 0) pr_err("Security denies permission to create files: error %d", ret); diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3efff..99f894f3f7e132 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -658,7 +658,7 @@ int inode_permission(struct mnt_idmap *idmap, if (unlikely(retval)) return retval; - return security_inode_permission(inode, mask); + return security_inode_permission(idmap, inode, mask); } EXPORT_SYMBOL(inode_permission); @@ -695,7 +695,7 @@ static __always_inline int lookup_inode_permission_may_exec(struct mnt_idmap *id if (unlikely(((inode->i_mode & 0111) != 0111) || !no_acl_inode(inode))) return inode_permission(idmap, inode, mask); - return security_inode_permission(inode, mask); + return security_inode_permission(idmap, inode, mask); } /** @@ -4188,7 +4188,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, return -EACCES; /* shouldn't it be ENOSYS? */ mode = vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); - error = security_inode_create(dir, dentry, mode); + error = security_inode_create(idmap, dir, dentry, mode); if (error) return error; error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); @@ -4212,7 +4212,7 @@ int vfs_mkobj(struct dentry *dentry, umode_t mode, mode &= S_IALLUGO; mode |= S_IFREG; - error = security_inode_create(dir, dentry, mode); + error = security_inode_create(&nop_mnt_idmap, dir, dentry, mode); if (error) return error; error = f(dentry, mode, arg); @@ -4328,7 +4328,7 @@ static int may_o_create(struct mnt_idmap *idmap, if (error) return error; - return security_inode_create(dir->dentry->d_inode, dentry, mode); + return security_inode_create(idmap, dir->dentry->d_inode, dentry, mode); } /** @@ -5258,7 +5258,7 @@ int vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, if (error) return error; - error = security_inode_mknod(dir, dentry, mode, dev); + error = security_inode_mknod(idmap, dir, dentry, mode, dev); if (error) return error; @@ -5395,7 +5395,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, goto err; mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR); - error = security_inode_mkdir(dir, dentry, mode); + error = security_inode_mkdir(idmap, dir, dentry, mode); if (error) goto err; @@ -5783,7 +5783,7 @@ int vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, if (!dir->i_op->symlink) return -EPERM; - error = security_inode_symlink(dir, dentry, oldname); + error = security_inode_symlink(idmap, dir, dentry, oldname); if (error) return error; @@ -5907,7 +5907,7 @@ int vfs_link(struct dentry *old_dentry, struct mnt_idmap *idmap, if (S_ISDIR(inode->i_mode)) return -EPERM; - error = security_inode_link(old_dentry, dir, new_dentry); + error = security_inode_link(idmap, old_dentry, dir, new_dentry); if (error) return error; diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 0da6c7e8f65957..af9624c30f99d3 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -120,26 +120,27 @@ LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode, int *xattr_count) LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode, const struct qstr *name, const struct inode *context_inode) -LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry, - umode_t mode) +LSM_HOOK(int, 0, inode_create, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) LSM_HOOK(void, LSM_RET_VOID, inode_post_create_tmpfile, struct mnt_idmap *idmap, struct inode *inode) -LSM_HOOK(int, 0, inode_link, struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +LSM_HOOK(int, 0, inode_link, struct mnt_idmap *idmap, + struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) LSM_HOOK(int, 0, inode_unlink, struct inode *dir, struct dentry *dentry) -LSM_HOOK(int, 0, inode_symlink, struct inode *dir, struct dentry *dentry, - const char *old_name) -LSM_HOOK(int, 0, inode_mkdir, struct inode *dir, struct dentry *dentry, - umode_t mode) +LSM_HOOK(int, 0, inode_symlink, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name) +LSM_HOOK(int, 0, inode_mkdir, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) LSM_HOOK(int, 0, inode_rmdir, struct inode *dir, struct dentry *dentry) -LSM_HOOK(int, 0, inode_mknod, struct inode *dir, struct dentry *dentry, - umode_t mode, dev_t dev) +LSM_HOOK(int, 0, inode_mknod, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) LSM_HOOK(int, 0, inode_rename, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) LSM_HOOK(int, 0, inode_readlink, struct dentry *dentry) LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode, bool rcu) -LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask) +LSM_HOOK(int, 0, inode_permission, struct mnt_idmap *idmap, + struct inode *inode, int mask) LSM_HOOK(int, 0, inode_setattr, struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, struct mnt_idmap *idmap, diff --git a/include/linux/security.h b/include/linux/security.h index bf002ed14ac83c..7bb073f734f600 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -406,24 +406,28 @@ int security_inode_init_security(struct inode *inode, struct inode *dir, int security_inode_init_security_anon(struct inode *inode, const struct qstr *name, const struct inode *context_inode); -int security_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode); +int security_inode_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode); void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, struct inode *inode); -int security_inode_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry); +int security_inode_link(struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry); int security_inode_unlink(struct inode *dir, struct dentry *dentry); -int security_inode_symlink(struct inode *dir, struct dentry *dentry, - const char *old_name); -int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode); +int security_inode_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name); +int security_inode_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode); int security_inode_rmdir(struct inode *dir, struct dentry *dentry); -int security_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev); +int security_inode_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev); int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); int security_inode_readlink(struct dentry *dentry); int security_inode_follow_link(struct dentry *dentry, struct inode *inode, bool rcu); -int security_inode_permission(struct inode *inode, int mask); +int security_inode_permission(struct mnt_idmap *idmap, struct inode *inode, + int mask); int security_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, @@ -907,9 +911,10 @@ static inline int security_inode_init_security_anon(struct inode *inode, return 0; } -static inline int security_inode_create(struct inode *dir, - struct dentry *dentry, - umode_t mode) +static inline int security_inode_create(struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + umode_t mode) { return 0; } @@ -918,9 +923,10 @@ static inline void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, struct inode *inode) { } -static inline int security_inode_link(struct dentry *old_dentry, - struct inode *dir, - struct dentry *new_dentry) +static inline int security_inode_link(struct mnt_idmap *idmap, + struct dentry *old_dentry, + struct inode *dir, + struct dentry *new_dentry) { return 0; } @@ -931,16 +937,18 @@ static inline int security_inode_unlink(struct inode *dir, return 0; } -static inline int security_inode_symlink(struct inode *dir, - struct dentry *dentry, - const char *old_name) +static inline int security_inode_symlink(struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + const char *old_name) { return 0; } -static inline int security_inode_mkdir(struct inode *dir, - struct dentry *dentry, - int mode) +static inline int security_inode_mkdir(struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + int mode) { return 0; } @@ -951,9 +959,10 @@ static inline int security_inode_rmdir(struct inode *dir, return 0; } -static inline int security_inode_mknod(struct inode *dir, - struct dentry *dentry, - int mode, dev_t dev) +static inline int security_inode_mknod(struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + int mode, dev_t dev) { return 0; } @@ -979,7 +988,8 @@ static inline int security_inode_follow_link(struct dentry *dentry, return 0; } -static inline int security_inode_permission(struct inode *inode, int mask) +static inline int security_inode_permission(struct mnt_idmap *idmap, + struct inode *inode, int mask) { return 0; } diff --git a/security/security.c b/security/security.c index ecfb875f0c69dd..7fa52d39a255a2 100644 --- a/security/security.c +++ b/security/security.c @@ -1628,6 +1628,7 @@ int security_path_chroot(const struct path *path) /** * security_inode_create() - Check if creating a file is allowed + * @idmap: idmap of the mount * @dir: the parent directory * @dentry: the file being created * @mode: requested file mode @@ -1636,12 +1637,12 @@ int security_path_chroot(const struct path *path) * * Return: Returns 0 if permission is granted. */ -int security_inode_create(struct inode *dir, struct dentry *dentry, - umode_t mode) +int security_inode_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_create, dir, dentry, mode); + return call_int_hook(inode_create, idmap, dir, dentry, mode); } EXPORT_SYMBOL_GPL(security_inode_create); @@ -1662,6 +1663,7 @@ void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, /** * security_inode_link() - Check if creating a hard link is allowed + * @idmap: idmap of the mount * @old_dentry: existing file * @dir: new parent directory * @new_dentry: new link @@ -1670,12 +1672,12 @@ void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +int security_inode_link(struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { if (unlikely(IS_PRIVATE(d_backing_inode(old_dentry)))) return 0; - return call_int_hook(inode_link, old_dentry, dir, new_dentry); + return call_int_hook(inode_link, idmap, old_dentry, dir, new_dentry); } /** @@ -1696,6 +1698,7 @@ int security_inode_unlink(struct inode *dir, struct dentry *dentry) /** * security_inode_symlink() - Check if creating a symbolic link is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: symbolic link * @old_name: existing filename @@ -1704,16 +1707,17 @@ int security_inode_unlink(struct inode *dir, struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_symlink(struct inode *dir, struct dentry *dentry, - const char *old_name) +int security_inode_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_symlink, dir, dentry, old_name); + return call_int_hook(inode_symlink, idmap, dir, dentry, old_name); } /** * security_inode_mkdir() - Check if creating a new directory is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: new directory * @mode: new directory mode @@ -1723,11 +1727,12 @@ int security_inode_symlink(struct inode *dir, struct dentry *dentry, * * Return: Returns 0 if permission is granted. */ -int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +int security_inode_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_mkdir, dir, dentry, mode); + return call_int_hook(inode_mkdir, idmap, dir, dentry, mode); } EXPORT_SYMBOL_GPL(security_inode_mkdir); @@ -1749,6 +1754,7 @@ int security_inode_rmdir(struct inode *dir, struct dentry *dentry) /** * security_inode_mknod() - Check if creating a special file is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: new file * @mode: new file mode @@ -1761,12 +1767,12 @@ int security_inode_rmdir(struct inode *dir, struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_mknod(struct inode *dir, struct dentry *dentry, - umode_t mode, dev_t dev) +int security_inode_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_mknod, dir, dentry, mode, dev); + return call_int_hook(inode_mknod, idmap, dir, dentry, mode, dev); } /** @@ -1837,6 +1843,7 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode, /** * security_inode_permission() - Check if accessing an inode is allowed + * @idmap: idmap of the mount * @inode: inode * @mask: access mask * @@ -1849,11 +1856,12 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode, * * Return: Returns 0 if permission is granted. */ -int security_inode_permission(struct inode *inode, int mask) +int security_inode_permission(struct mnt_idmap *idmap, struct inode *inode, + int mask) { if (unlikely(IS_PRIVATE(inode))) return 0; - return call_int_hook(inode_permission, inode, mask); + return call_int_hook(inode_permission, idmap, inode, mask); } /** diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1da5..967984d0af300d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3074,12 +3074,14 @@ static int selinux_inode_init_security_anon(struct inode *inode, &ad); } -static int selinux_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) +static int selinux_inode_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { return may_create(dir, dentry, SECCLASS_FILE); } -static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +static int selinux_inode_link(struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { return may_link(dir, old_dentry, MAY_LINK); } @@ -3089,12 +3091,14 @@ static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) return may_link(dir, dentry, MAY_UNLINK); } -static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) +static int selinux_inode_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *name) { return may_create(dir, dentry, SECCLASS_LNK_FILE); } -static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mask) +static int selinux_inode_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mask) { return may_create(dir, dentry, SECCLASS_DIR); } @@ -3104,7 +3108,8 @@ static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) return may_link(dir, dentry, MAY_RMDIR); } -static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) +static int selinux_inode_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { return may_create(dir, dentry, inode_mode_to_security_class(mode)); } @@ -3236,13 +3241,15 @@ static inline void task_avdcache_update(struct task_security_struct *tsec, /** * selinux_inode_permission - Check if the current task can access an inode + * @idmap: idmap of the mount * @inode: the inode that is being accessed * @requested: the accesses being requested * * Check if the current task is allowed to access @inode according to * @requested. Returns 0 if allowed, negative values otherwise. */ -static int selinux_inode_permission(struct inode *inode, int requested) +static int selinux_inode_permission(struct mnt_idmap *idmap, + struct inode *inode, int requested) { int mask; u32 perms; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8e88ac65fd7fcf..a45819d13c0d69 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1089,14 +1089,15 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir, /** * smack_inode_link - Smack check on link + * @idmap: idmap of the mount * @old_dentry: the existing object * @dir: unused * @new_dentry: the new object * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +static int smack_inode_link(struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { struct smack_known *isp; struct smk_audit_info ad; @@ -1226,6 +1227,7 @@ static int smack_inode_rename(struct inode *old_inode, /** * smack_inode_permission - Smack version of permission() + * @idmap: idmap of the mount * @inode: the inode in question * @mask: the access requested * @@ -1233,7 +1235,8 @@ static int smack_inode_rename(struct inode *old_inode, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_permission(struct inode *inode, int mask) +static int smack_inode_permission(struct mnt_idmap *idmap, struct inode *inode, + int mask) { struct superblock_smack *sbsp = smack_superblock(inode->i_sb); struct smk_audit_info ad; From ed578004b26b49325abc5ad84bcc33a82e76f602 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 4 Sep 2026 16:48:56 +0200 Subject: [PATCH 0387/2286] selftests/bpf: verify mount idmaps reach inode hooks Extend the BPF LSM selftest to exercise create, link, symlink, mkdir, mknod, and permission through both the VFS identity idmap and a real idmapped tmpfs mount. Record the idmap observed by each hook and verify that every updated hook receives the mapping used by the VFS operation. This provides regression coverage for passing mount idmaps through inode security hooks. Signed-off-by: Daan De Meyer Reviewed-by: Matt Bobrowski Signed-off-by: Paul Moore --- .../selftests/bpf/prog_tests/test_lsm.c | 231 ++++++++++++++++++ tools/testing/selftests/bpf/progs/lsm.c | 79 ++++++ 2 files changed, 310 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c index d7495efd4a5621..c0ae813698ae28 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_lsm.c +++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c @@ -1,18 +1,30 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE /* * Copyright (C) 2020 Google LLC. */ #include +#include +#include +#include +#include +#include #include #include +#include + #include "lsm.skel.h" #include "lsm_tailcall.skel.h" char *CMD_ARGS[] = {"true", NULL}; +enum { + INODE_IDMAP_ALL = (1U << 6) - 1, +}; + int exec_cmd(int *monitored_pid) { int child_pid, child_status; @@ -30,6 +42,219 @@ int exec_cmd(int *monitored_pid) return -EINVAL; } +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + do { + ret = write(fd, buf, count); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int write_file(const char *path, const char *value) +{ + size_t len = strlen(value); + int fd, saved_errno = 0; + ssize_t ret; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, value, len); + if (ret < 0) + saved_errno = errno; + else if ((size_t)ret != len) + saved_errno = EIO; + close(fd); + if (saved_errno) { + errno = saved_errno; + return -1; + } + return 0; +} + +static int write_userns_file(pid_t pid, const char *name, const char *value) +{ + char path[64]; + int len; + + len = snprintf(path, sizeof(path), "/proc/%d/%s", pid, name); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + return -1; + } + + return write_file(path, value); +} + +static int create_userns_fd(void) +{ + char path[64]; + pid_t pid, waited; + int fd = -1, len, saved_errno, status; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + if (unshare(CLONE_NEWUSER)) + _exit(1); + raise(SIGSTOP); + _exit(0); + } + + do { + waited = waitpid(pid, &status, WUNTRACED); + } while (waited < 0 && errno == EINTR); + if (waited != pid) + goto out; + if (!WIFSTOPPED(status)) { + pid = -1; + goto out; + } + + /* A one-entry map is identity for root but remains distinct from nop_mnt_idmap. */ + if (write_userns_file(pid, "setgroups", "deny") && errno != ENOENT) + goto out; + if (write_userns_file(pid, "uid_map", "0 0 1") || + write_userns_file(pid, "gid_map", "0 0 1")) + goto out; + + len = snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + goto out; + } + fd = open(path, O_RDONLY | O_CLOEXEC); + +out: + saved_errno = errno; + if (pid > 0) { + kill(pid, SIGKILL); + do { + waited = waitpid(pid, NULL, 0); + } while (waited < 0 && errno == EINTR); + } + errno = saved_errno; + return fd; +} + +static int create_idmapped_tmpfs(void) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fsfd = -1, mntfd = -1, saved_errno, userns_fd = -1; + + userns_fd = create_userns_fd(); + if (userns_fd < 0) + goto out; + + /* A detached tmpfs avoids relying on the host test directory supporting idmaps. */ + fsfd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC); + if (fsfd < 0) + goto out; + if (syscall(__NR_fsconfig, fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto out; + + mntfd = syscall(__NR_fsmount, fsfd, FSMOUNT_CLOEXEC, 0); + if (mntfd < 0) + goto out; + + attr.userns_fd = userns_fd; + if (syscall(__NR_mount_setattr, mntfd, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(mntfd); + mntfd = -1; + } + +out: + saved_errno = errno; + if (fsfd >= 0) + close(fsfd); + if (userns_fd >= 0) + close(userns_fd); + errno = saved_errno; + return mntfd; +} + +static int exercise_inode_idmap_hooks(int dirfd) +{ + int fd = -1, ret = -1; + + fd = openat(dirfd, "file", O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, + 0600); + if (!ASSERT_GE(fd, 0, "create")) + goto out; + close(fd); + fd = -1; + + if (!ASSERT_OK(mkdirat(dirfd, "dir", 0700), "mkdir")) + goto out; + if (!ASSERT_OK(symlinkat("target", dirfd, "symlink"), "symlink")) + goto out; + if (!ASSERT_OK(linkat(dirfd, "file", dirfd, "link", 0), "link")) + goto out; + if (!ASSERT_OK(mkfifoat(dirfd, "fifo", 0600), "mknod")) + goto out; + + ret = 0; +out: + if (fd >= 0) + close(fd); + unlinkat(dirfd, "link", 0); + unlinkat(dirfd, "fifo", 0); + unlinkat(dirfd, "symlink", 0); + unlinkat(dirfd, "file", 0); + unlinkat(dirfd, "dir", AT_REMOVEDIR); + return ret; +} + +static int test_lsm_inode_idmap(struct lsm *skel) +{ + char tmpdir[] = "/var/tmp/test_lsm_idmap.XXXXXX"; + __u32 expected = INODE_IDMAP_ALL; + int dirfd = -1, idmapped_dirfd = -1; + int ret = -1; + + if (!ASSERT_OK_PTR(mkdtemp(tmpdir), "mkdtemp")) + return -1; + + dirfd = open(tmpdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (!ASSERT_GE(dirfd, 0, "open_tmpdir")) + goto out; + + idmapped_dirfd = create_idmapped_tmpfs(); + if (!ASSERT_GE(idmapped_dirfd, 0, "create_idmapped_tmpfs")) + goto out; + + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; + + if (!ASSERT_OK(exercise_inode_idmap_hooks(dirfd), "identity_idmap")) + goto out; + if (!ASSERT_OK(exercise_inode_idmap_hooks(idmapped_dirfd), + "idmapped_mount")) + goto out; + + if (!ASSERT_EQ(skel->bss->inode_identity_idmap_seen, expected, + "inode_identity_idmap_seen")) + goto out; + ret = ASSERT_EQ(skel->bss->inode_idmapped_mount_seen, expected, + "inode_idmapped_mount_seen") ? 0 : -1; + +out: + if (idmapped_dirfd >= 0) + close(idmapped_dirfd); + if (dirfd >= 0) + close(dirfd); + rmdir(tmpdir); + return ret; +} + static int test_lsm(struct lsm *skel) { struct bpf_link *link; @@ -53,6 +278,10 @@ static int test_lsm(struct lsm *skel) skel->bss->monitored_pid = getpid(); + err = test_lsm_inode_idmap(skel); + if (!ASSERT_OK(err, "test_lsm_inode_idmap")) + return err; + err = stack_mprotect(); if (!ASSERT_EQ(err, -1, "stack_mprotect") || !ASSERT_EQ(errno, EPERM, "stack_mprotect")) @@ -71,6 +300,8 @@ static int test_lsm(struct lsm *skel) skel->bss->copy_test = 0; skel->bss->bprm_count = 0; skel->bss->mprotect_count = 0; + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; return 0; } diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c index 7de173daf27b6d..7e32fbddbfadf0 100644 --- a/tools/testing/selftests/bpf/progs/lsm.c +++ b/tools/testing/selftests/bpf/progs/lsm.c @@ -84,6 +84,85 @@ char _license[] SEC("license") = "GPL"; int monitored_pid = 0; int mprotect_count = 0; int bprm_count = 0; +__u32 inode_identity_idmap_seen = 0; +__u32 inode_idmapped_mount_seen = 0; + +enum { + INODE_IDMAP_CREATE = 1U << 0, + INODE_IDMAP_LINK = 1U << 1, + INODE_IDMAP_SYMLINK = 1U << 2, + INODE_IDMAP_MKDIR = 1U << 3, + INODE_IDMAP_MKNOD = 1U << 4, + INODE_IDMAP_PERMISSION = 1U << 5, +}; + +static __always_inline bool is_monitored_idmap(struct mnt_idmap *idmap) +{ + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + return monitored_pid == pid && idmap; +} + +static __always_inline bool is_identity_idmap(struct mnt_idmap *idmap) +{ + return idmap->uid_map.nr_extents == 0 && + idmap->gid_map.nr_extents == 0; +} + +static __always_inline int record_inode_idmap(struct mnt_idmap *idmap, + __u32 hook, int ret) +{ + if (ret || !is_monitored_idmap(idmap)) + return ret; + if (is_identity_idmap(idmap)) + inode_identity_idmap_seen |= hook; + else + inode_idmapped_mount_seen |= hook; + return 0; +} + +SEC("lsm/inode_create") +int BPF_PROG(test_inode_create, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_CREATE, ret); +} + +SEC("lsm/inode_link") +int BPF_PROG(test_inode_link, struct mnt_idmap *idmap, + struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_LINK, ret); +} + +SEC("lsm/inode_symlink") +int BPF_PROG(test_inode_symlink, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_SYMLINK, ret); +} + +SEC("lsm/inode_mkdir") +int BPF_PROG(test_inode_mkdir, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKDIR, ret); +} + +SEC("lsm/inode_mknod") +int BPF_PROG(test_inode_mknod, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKNOD, ret); +} + +SEC("lsm/inode_permission") +int BPF_PROG(test_inode_permission, struct mnt_idmap *idmap, + struct inode *inode, int mask, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_PERMISSION, ret); +} SEC("lsm/file_mprotect") int BPF_PROG(test_int_hook, struct vm_area_struct *vma, From 5a06263597a8b7b528d92999cf654667f61d92a7 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 13 Jul 2026 22:38:31 -0300 Subject: [PATCH 0388/2286] doc: describe CONFIG_LSM and lsm= as the selection mechanism The LSM usage document states that security modules are selectable at build time via CONFIG_DEFAULT_SECURITY and can be overridden at boot time via the "security=..." kernel command line argument. CONFIG_DEFAULT_SECURITY no longer exists: LSMs are enabled via CONFIG_LSM, an ordered list of the LSMs to initialize, which can be overridden at boot time with the "lsm=" parameter. The "security=" parameter remains as a deprecated way to choose a legacy "major" security module, and is ignored when "lsm=" is specified; see commit 89a9684ea158 ("LSM: Ignore "security=" when "lsm=" is specified"). Update the paragraph to describe CONFIG_LSM and "lsm=" as the current selection mechanism, keeping "security=" documented as the deprecated legacy option, matching the wording in kernel-parameters.txt. Signed-off-by: Lincoln Wallace [PM: subject tweak, dropped refs to earlier rejected revisions] Signed-off-by: Paul Moore --- Documentation/admin-guide/LSM/index.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index b44ef68f6e4da6..c24310c709dcde 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -6,9 +6,11 @@ The Linux Security Module (LSM) framework provides a mechanism for various security checks to be hooked by new kernel extensions. The name "module" is a bit of a misnomer since these extensions are not actually loadable kernel modules. Instead, they are selectable at build-time via -CONFIG_DEFAULT_SECURITY and can be overridden at boot-time via the -``"security=..."`` kernel command line argument, in the case where multiple -LSMs were built into a given kernel. +CONFIG_LSM, an ordered list of the LSMs to enable, and can be +overridden at boot-time via the ``"lsm=..."`` kernel command line +argument. The ``"security=..."`` kernel command line argument remains +available to choose a legacy "major" security module, but has been +deprecated by the ``"lsm=..."`` parameter. The primary users of the LSM interface are Mandatory Access Control (MAC) extensions which provide a comprehensive security policy. Examples From 7dfacee94c9cac9e62149791f91ce7777d6d870e Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Mon, 13 Jul 2026 22:38:32 -0300 Subject: [PATCH 0389/2286] doc: fix LSM ordering description for /sys/kernel/security/lsm The LSM usage document states that the capability module will always be first in /sys/kernel/security/lsm, followed by any "minor" modules and then the one "major" module. This does not match the current LSM infrastructure: - When CONFIG_SECURITY_LOCKDOWN_LSM_EARLY is enabled, lockdown is initialized as an early LSM, before all other modules including capability, and appears first in the list. - The integrity modules (e.g. IMA and EVM) register with LSM_ORDER_LAST and are always placed at the end of the list, regardless of the position of the major module. - The relative order of the remaining modules is not fixed by the framework; it follows CONFIG_LSM or the "lsm=" kernel command line parameter. Rewrite the paragraph to describe the actual ordering: lockdown first when early lockdown is enabled, capability otherwise, integrity modules at the end, and the remaining modules in the configured order. Signed-off-by: Lincoln Wallace [PM: subject tweak] Signed-off-by: Paul Moore --- Documentation/admin-guide/LSM/index.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index c24310c709dcde..9518495edfbc32 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -27,9 +27,15 @@ man-pages project. A list of the active security modules can be found by reading ``/sys/kernel/security/lsm``. This is a comma separated list, and will always include the capability module. The list reflects the -order in which checks are made. The capability module will always -be first, followed by any "minor" modules (e.g. Yama) and then -the one "major" module (e.g. SELinux) if there is one configured. +order in which checks are made. The capability module will be +first, unless CONFIG_SECURITY_LOCKDOWN_LSM_EARLY is enabled, in +which case the lockdown module will precede it. The integrity +modules (e.g. IMA and EVM), if enabled in the kernel +configuration, are always placed at the end of the list. Any +other "minor" modules (e.g. Yama) and the one "major" module +(e.g. SELinux), if there is one configured, appear in between, +in the order given by CONFIG_LSM or the ``"lsm=..."`` kernel +command line parameter. Process attributes associated with "major" security modules should be accessed and maintained using the special files in ``/proc/.../attr``. From 50b265abd9eb00ddb29b77e996e0c4cac38212bf Mon Sep 17 00:00:00 2001 From: Pawel Zalewski Date: Fri, 4 Sep 2026 15:41:43 +0100 Subject: [PATCH 0390/2286] EDAC/{bluefield,loongson}: Use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. While at it - unify the list terminator to have a single space between the brackets and no trailing comma. Signed-off-by: Pawel Zalewski Signed-off-by: Borislav Petkov (AMD) Reviewed-by: David Thompson Acked-by: Qunqin Zhao Link: https://patch.msgid.link/20260904-acpi-edac-v1-1-ef93d60bbda4@thegoodpenguin.co.uk --- drivers/edac/bluefield_edac.c | 4 ++-- drivers/edac/loongson_edac.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/edac/bluefield_edac.c b/drivers/edac/bluefield_edac.c index ae3bb7afa103eb..472fc876277880 100644 --- a/drivers/edac/bluefield_edac.c +++ b/drivers/edac/bluefield_edac.c @@ -472,8 +472,8 @@ static void bluefield_edac_mc_remove(struct platform_device *pdev) } static const struct acpi_device_id bluefield_mc_acpi_ids[] = { - {"MLNXBF08", 0}, - {} + { .id = "MLNXBF08" }, + { } }; MODULE_DEVICE_TABLE(acpi, bluefield_mc_acpi_ids); diff --git a/drivers/edac/loongson_edac.c b/drivers/edac/loongson_edac.c index 38745800ed01dd..3ed4d34a33680c 100644 --- a/drivers/edac/loongson_edac.c +++ b/drivers/edac/loongson_edac.c @@ -137,8 +137,8 @@ static void edac_remove(struct platform_device *pdev) } static const struct acpi_device_id loongson_edac_acpi_match[] = { - {"LOON0010", 0}, - {} + { .id = "LOON0010" }, + { } }; MODULE_DEVICE_TABLE(acpi, loongson_edac_acpi_match); From 700a0a6892f7c0c541d5d23837cfab98cc744d17 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 08:26:14 +0200 Subject: [PATCH 0391/2286] gfs2: Fix the return value in gfs2_get_link() kernel-doc gfs2_get_link() returns the link target or an error pointer, but its kernel-doc still describes an integer status. The mismatch dates back to when gfs2_readlink() was replaced by generic_readlink() and persisted across the removal of nd_set_link(). Describe the returned pointer and error value. Fixes: c177c2ac8c5a ("Switch gfs2 to nd_set_link()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Andreas Gruenbacher --- fs/gfs2/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index d4e1e443391e0f..2ec848b993d030 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1910,7 +1910,7 @@ static int gfs2_rename2(struct mnt_idmap *idmap, struct inode *odir, * * This can handle symlinks of any size. * - * Returns: 0 on success or error code + * Returns: the link target on success, an ERR_PTR() on failure */ static const char *gfs2_get_link(struct dentry *dentry, From 2cf65d00347005eccf9a24801783377d7f920021 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:00:51 +0200 Subject: [PATCH 0392/2286] ACPI: fan: Fix memory leak due to leftover devm_kcalloc() argument An ACPI device object's dev field in passed as the first argument to devm_kcalloc() in acpi_fan_get_fps() which is incorrect and leads to a memory leak on driver probe errors and removal because the driver is not bound to that ACPI device. Address this by replacing that pointer with a pointer to the device the driver is actually bound to. While at it, drop a redundant error message after a memory allocation failure (that also gets printed relative to the ACPI device). Fixes: d91a1d129b63 ("ACPI: fan: Use platform device for devres-related actions") Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/14089663.uLZWGnKmhe@rafael.j.wysocki --- drivers/acpi/fan_core.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 624d0736b581cd..a31d7beca5d0a0 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -284,7 +284,7 @@ static int acpi_fan_speed_cmp(const void *a, const void *b) return fps1->speed - fps2->speed; } -static int acpi_fan_get_fps(struct acpi_device *device) +static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) { struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -304,11 +304,8 @@ static int acpi_fan_get_fps(struct acpi_device *device) } fan->fps_count = obj->package.count - 1; /* minus revision field */ - fan->fps = devm_kcalloc(&device->dev, - fan->fps_count, sizeof(struct acpi_fan_fps), - GFP_KERNEL); + fan->fps = devm_kcalloc(dev, fan->fps_count, sizeof(*fan->fps), GFP_KERNEL); if (!fan->fps) { - dev_err(&device->dev, "Not enough memory\n"); status = -ENOMEM; goto err; } @@ -522,7 +519,7 @@ static int acpi_fan_probe(struct platform_device *pdev) if (result) return result; - result = acpi_fan_get_fps(device); + result = acpi_fan_get_fps(&pdev->dev, device); if (result) return result; } From 46577d36611953d1b99e86493289cfc824116ad2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:01:30 +0200 Subject: [PATCH 0393/2286] ACPI: video: Fix backlight unregistration ordering In acpi_video_dev_unregister_backlight(), the sysfs interface of the cooling class device may access the brightness object under the backlight device's ACPI companion, so that object cannot be freed before unregistering the cooling class device. Adjust the code to take that into account. Fixes: 67b662e189f4 ("ACPI / video: seperate backlight control and event interface") Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Fixed the Fixes: tag ] Link: https://patch.msgid.link/3429468.aeNJFYEL58@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_video.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 4d6fd9f6e9ada7..6cfe390411c1d2 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1825,6 +1825,12 @@ static int acpi_video_bus_register_backlight(struct acpi_video_bus *video) static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device) { + if (device->cooling_dev) { + sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); + sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); + thermal_cooling_device_unregister(device->cooling_dev); + device->cooling_dev = NULL; + } if (device->backlight) { backlight_device_unregister(device->backlight); device->backlight = NULL; @@ -1834,12 +1840,6 @@ static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device kfree(device->brightness); device->brightness = NULL; } - if (device->cooling_dev) { - sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); - thermal_cooling_device_unregister(device->cooling_dev); - device->cooling_dev = NULL; - } } static int acpi_video_bus_unregister_backlight(struct acpi_video_bus *video) From 7e2c70f8a104e777df799130b4a39c35f51ba19c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:02:09 +0200 Subject: [PATCH 0394/2286] thermal: core: Introduce thermal_cooling_device_create() Currently, thermal cooling devices have no parents, but it would be generally useful to be able to create them under specific parents in the device hierarchy (for instance, it may help to identify the device representing the actual cooling hardware). To make that possible, add thermal_cooling_device_create() that will work like thermal_cooling_device_register() except that it will take an additional parent argument (which may be NULL). Redefine thermal_cooling_device_register() as a static inline wrapper around thermal_cooling_device_create(). Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/8729484.T7Z3S40VBb@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 28 ++++++++++++++++------------ drivers/thermal/thermal_core.h | 3 ++- drivers/thermal/thermal_of.c | 2 +- include/linux/thermal.h | 19 ++++++++++++++----- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 82e2f0d8a26dee..ac928c19982959 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1005,7 +1005,8 @@ thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_devi return ERR_PTR(ret); } -int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, + struct device *parent, void *devdata) { unsigned long current_state; int ret; @@ -1013,6 +1014,7 @@ int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdat mutex_init(&cdev->lock); INIT_LIST_HEAD(&cdev->thermal_instances); cdev->updated = false; + cdev->device.parent = parent; cdev->device.class = &thermal_class; cdev->device.release = thermal_cdev_release; device_initialize(&cdev->device); @@ -1062,21 +1064,23 @@ int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdat } /** - * thermal_cooling_device_register() - register a new thermal cooling device + * thermal_cooling_device_create() - register a new thermal cooling device + * @parent: parent device (optional). * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. * - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. + * Allocate and register a new thermal cooling device under the given parent (if + * not NULL) and with the given type, device data, and operations. During the + * registration, it will be matched against all of the registered thermal zones + * and it will be bound to the matching ones. * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + * Return: A pointer to the created struct thermal_cooling_device or an ERR_PTR. + * Callers must use IS_ERR*() helpers to check the return value. */ -struct thermal_cooling_device * -thermal_cooling_device_register(const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -1085,13 +1089,13 @@ thermal_cooling_device_register(const char *type, void *devdata, if (IS_ERR(cdev)) return cdev; - ret = thermal_cooling_device_add(cdev, devdata); + ret = thermal_cooling_device_add(cdev, parent, devdata); if (ret) return ERR_PTR(ret); return cdev; } -EXPORT_SYMBOL_GPL(thermal_cooling_device_register); +EXPORT_SYMBOL_GPL(thermal_cooling_device_create); static void thermal_cooling_device_release(void *data) { diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index e98b0aa5aacc4f..7ef7c6cab4374f 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -270,7 +270,8 @@ void thermal_governor_update_tz(struct thermal_zone_device *tz, struct thermal_cooling_device * thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops); -int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata); +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, + struct device *parent, void *devdata); /* Helpers */ #define for_each_trip_desc(__tz, __td) \ diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae33..14dfac9359b87f 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -561,7 +561,7 @@ thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, cdev->np = np; cdev->cdev_id = cdev_id; - ret = thermal_cooling_device_add(cdev, devdata); + ret = thermal_cooling_device_add(cdev, NULL, devdata); if (ret) return ERR_PTR(ret); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 083b4f5339331d..306ad17aed89c1 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -293,8 +293,9 @@ struct device *thermal_zone_device(struct thermal_zone_device *tzd); void thermal_zone_device_update(struct thermal_zone_device *, enum thermal_notify_event); -struct thermal_cooling_device *thermal_cooling_device_register(const char *, - void *, const struct thermal_cooling_device_ops *); +struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, @@ -340,9 +341,9 @@ static inline void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { } -static inline struct thermal_cooling_device * -thermal_cooling_device_register(const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +static inline struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } static inline struct thermal_cooling_device * @@ -391,4 +392,12 @@ static inline void thermal_pm_prepare(void) {} static inline void thermal_pm_complete(void) {} #endif /* CONFIG_THERMAL */ +static inline struct thermal_cooling_device *thermal_cooling_device_register( + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return thermal_cooling_device_create(NULL, type, devdata, ops); +} + + #endif /* __THERMAL_H__ */ From bb03cfceb1f11047a0155fa7871b729a345c8de2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:02:53 +0200 Subject: [PATCH 0395/2286] ACPI: processor: thermal: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a cooling device in the ACPI processor driver, make it use thermal_cooling_device_create() and pass a pointer to the processor device representing the given CPU to that function as the cooling device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating the "device" symbolic link from the sysfs directory of the cooling device in question to the sysfs directory of the parent's companion ACPI device. That ACPI device is reachable through the "firmware_node" symbolic link in the parent's sysfs directory regardless. Moreover, since the cooling device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent's ACPI companion to it. Accordingly, also remove the code creating that symbolic link. While at it, check for error pointer values in addition to checking for NULL in acpi_processor_thermal_exit() to avoid dereferencing them mistakenly. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Corrected typo in the changelog ] Link: https://patch.msgid.link/2362345.iZASKD2KPV@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_driver.c | 4 +-- drivers/acpi/processor_thermal.c | 47 +++++--------------------------- include/acpi/processor.h | 3 +- 3 files changed, 10 insertions(+), 44 deletions(-) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index cdc2ae1632b21a..90c14480393de1 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -179,7 +179,7 @@ static int __acpi_processor_start(struct acpi_device *device) return 0; err_thermal_exit: - acpi_processor_thermal_exit(pr, device); + acpi_processor_thermal_exit(pr); err_power_exit: acpi_processor_power_exit(pr); return result; @@ -203,7 +203,7 @@ static int acpi_processor_stop(struct device *dev) acpi_cppc_processor_exit(pr); - acpi_processor_thermal_exit(pr, device); + acpi_processor_thermal_exit(pr); return 0; } diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c index c7b1dc5687eccc..6cd47551844ae1 100644 --- a/drivers/acpi/processor_thermal.c +++ b/drivers/acpi/processor_thermal.c @@ -311,52 +311,19 @@ const struct thermal_cooling_device_ops processor_cooling_ops = { int acpi_processor_thermal_init(struct acpi_processor *pr, struct acpi_device *device) { - int result = 0; - - pr->cdev = thermal_cooling_device_register("Processor", device, - &processor_cooling_ops); - if (IS_ERR(pr->cdev)) { - result = PTR_ERR(pr->cdev); - return result; - } - - dev_dbg(&device->dev, "registered as cooling_device%d\n", - pr->cdev->id); - - result = sysfs_create_link(&device->dev.kobj, - &pr->cdev->device.kobj, - "thermal_cooling"); - if (result) { - dev_err(&device->dev, - "Failed to create sysfs link 'thermal_cooling'\n"); - goto err_thermal_unregister; - } + pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", device, + &processor_cooling_ops); + if (IS_ERR(pr->cdev)) + return PTR_ERR(pr->cdev); - result = sysfs_create_link(&pr->cdev->device.kobj, - &device->dev.kobj, - "device"); - if (result) { - dev_err(&pr->cdev->device, - "Failed to create sysfs link 'device'\n"); - goto err_remove_sysfs_thermal; - } + dev_dbg(pr->dev, "registered as cooling_device%d\n", pr->cdev->id); return 0; - -err_remove_sysfs_thermal: - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); -err_thermal_unregister: - thermal_cooling_device_unregister(pr->cdev); - - return result; } -void acpi_processor_thermal_exit(struct acpi_processor *pr, - struct acpi_device *device) +void acpi_processor_thermal_exit(struct acpi_processor *pr) { - if (pr->cdev) { - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&pr->cdev->device.kobj, "device"); + if (!IS_ERR_OR_NULL(pr->cdev)) { thermal_cooling_device_unregister(pr->cdev); pr->cdev = NULL; } diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 554be224ce7666..656aaf74fb18e8 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -429,8 +429,7 @@ int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi); /* in processor_thermal.c */ int acpi_processor_thermal_init(struct acpi_processor *pr, struct acpi_device *device); -void acpi_processor_thermal_exit(struct acpi_processor *pr, - struct acpi_device *device); +void acpi_processor_thermal_exit(struct acpi_processor *pr); extern const struct thermal_cooling_device_ops processor_cooling_ops; #ifdef CONFIG_CPU_FREQ void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy); From d7ab07938c46f877448fa61d38ec87ba8643dff1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:03:34 +0200 Subject: [PATCH 0396/2286] ACPI: video: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a backlight cooling device in the ACPI video bus driver, make it use thermal_cooling_device_create() and pass a pointer to the LCD device to that function as the cooling class device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating the "device" symbolic link from the sysfs directory of the cooling device in question to the sysfs directory of the parent's ACPI companion device. That companion device is reachable through the "firmware_node" symbolic link in the parent's sysfs directory regardless. Moreover, since the cooling class device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent's ACPI companion to it. Accordingly, also remove the code creating that symbolic link. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/23237491.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 6cfe390411c1d2..2a2822516665a5 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1700,9 +1700,9 @@ static int acpi_video_resume(struct notifier_block *nb, static void acpi_video_dev_register_backlight(struct acpi_video_device *device) { + struct device *phys_dev, *parent = NULL; struct backlight_properties props; struct pci_dev *pdev; - struct device *parent = NULL; int result; static int count; char *name; @@ -1729,11 +1729,10 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) device, &acpi_backlight_ops, &props); - put_device(parent); kfree(name); if (IS_ERR(device->backlight)) { device->backlight = NULL; - return; + goto put_parent; } /* @@ -1743,8 +1742,12 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) device->backlight->props.brightness = acpi_video_get_brightness(device->backlight); - device->cooling_dev = thermal_cooling_device_register("LCD", device, - &video_cooling_ops); + phys_dev = acpi_bus_get_primary_device(device->dev); + if (!phys_dev) + phys_dev = get_device(parent); + + device->cooling_dev = thermal_cooling_device_create(phys_dev, "LCD", device, + &video_cooling_ops); if (IS_ERR(device->cooling_dev)) { /* * Set cooling_dev to NULL so we don't crash trying to free it. @@ -1753,21 +1756,17 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) * -- dtor */ device->cooling_dev = NULL; - return; + goto put_phys_dev; } - dev_info(&device->dev->dev, "registered as cooling_device%d\n", - device->cooling_dev->id); - result = sysfs_create_link(&device->dev->dev.kobj, - &device->cooling_dev->device.kobj, - "thermal_cooling"); - if (result) - pr_info("sysfs link creation failed\n"); + dev_info(&device->cooling_dev->device, "Using ACPI device %s\n", + acpi_dev_name(device->dev)); - result = sysfs_create_link(&device->cooling_dev->device.kobj, - &device->dev->dev.kobj, "device"); - if (result) - pr_info("Reverse sysfs link creation failed\n"); +put_phys_dev: + put_device(phys_dev); + +put_parent: + put_device(parent); } static void acpi_video_run_bcl_for_osi(struct acpi_video_bus *video) @@ -1826,8 +1825,6 @@ static int acpi_video_bus_register_backlight(struct acpi_video_bus *video) static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device) { if (device->cooling_dev) { - sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); thermal_cooling_device_unregister(device->cooling_dev); device->cooling_dev = NULL; } From 8a2622ae68984913ba0a778e64722f192721109b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:04:11 +0200 Subject: [PATCH 0397/2286] ACPI: fan: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a cooling device in the ACPI fan driver, make it use thermal_cooling_device_create() and pass a pointer to the platform device representing the given fan to that function as the cooling device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating that symbolic link manually. Moreover, since the cooling device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent to it. Accordingly, also remove the code creating that symbolic link. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3969045.kQq0lBPeGt@rafael.j.wysocki --- drivers/acpi/fan_core.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index a31d7beca5d0a0..29ca97503deb5f 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -564,8 +564,7 @@ static int acpi_fan_probe(struct platform_device *pdev) else name = acpi_device_bid(device); - cdev = thermal_cooling_device_register(name, device, - &fan_cooling_ops); + cdev = thermal_cooling_device_create(&pdev->dev, name, device, &fan_cooling_ops); if (IS_ERR(cdev)) { result = PTR_ERR(cdev); goto err_end; @@ -574,28 +573,9 @@ static int acpi_fan_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); fan->cdev = cdev; - result = sysfs_create_link(&pdev->dev.kobj, - &cdev->device.kobj, - "thermal_cooling"); - if (result) { - dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling'\n"); - goto err_unregister; - } - - result = sysfs_create_link(&cdev->device.kobj, - &pdev->dev.kobj, - "device"); - if (result) { - dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n"); - goto err_remove_link; - } return 0; -err_remove_link: - sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); -err_unregister: - thermal_cooling_device_unregister(cdev); err_end: if (fan->has_fst) acpi_fan_delete_attributes(device); @@ -612,8 +592,6 @@ static void acpi_fan_remove(struct platform_device *pdev) acpi_fan_delete_attributes(device); } - sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&fan->cdev->device.kobj, "device"); thermal_cooling_device_unregister(fan->cdev); } From cdb9bf0faa50592d976eb956fcfc68382dae567b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:05:07 +0200 Subject: [PATCH 0398/2286] ACPI: thermal: Use cooling device parent for thermal zone binding The ACPI thermal zone .should_bind() callback function, acpi_thermal_should_bind_cdev(), expects the given cooling device's devdata to point to an ACPI device object whose ACPI handle should be compared with ACPI handles in a list associated with the given trip point. That is not particularly straightforward and it effectively requires the drivers of ACPI cooling devices to populate the devdata with addresses of the ACPI companions of the devices they bind to. Consequently, the devdata cannot be used by the driver for its own needs which is its intended purpose. That can be overcome with the help of the observation that the ACPI device objects to be matched against the lists of ACPI handles associated with trip points are in fact the ACPI companions of the parents of cooling devices. Thus instead of using the given cooling device's devdata, it is sufficient to obtain the ACPI handle of its parent and compare that ACPI handle with the ones in the list associated with the given trip point. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Added parent_handle check against NULL ] Link: https://patch.msgid.link/9672996.CDJkKcVGEf@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/thermal.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index dd7666c176a057..08c5a4ba16a660 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -564,17 +564,20 @@ static bool acpi_thermal_should_bind_cdev(struct thermal_zone_device *thermal, struct cooling_spec *c) { struct acpi_thermal_trip *acpi_trip = trip->priv; - struct acpi_device *cdev_adev = cdev->devdata; + struct device *parent = cdev->device.parent; + acpi_handle parent_handle; int i; - /* Skip critical and hot trips. */ - if (!acpi_trip) + /* Skip critical and hot trips and parentless cooling devices. */ + if (!acpi_trip || !parent) return false; - for (i = 0; i < acpi_trip->devices.count; i++) { - acpi_handle handle = acpi_trip->devices.handles[i]; + parent_handle = ACPI_HANDLE(parent); + if (!parent_handle) + return false; - if (acpi_fetch_acpi_dev(handle) == cdev_adev) + for (i = 0; i < acpi_trip->devices.count; i++) { + if (acpi_trip->devices.handles[i] == parent_handle) return true; } From d1b25eb2d87738b5ad07070e0859017f6561760c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:05:50 +0200 Subject: [PATCH 0399/2286] ACPI: processor: thermal: Use more suitable cooling device data Instead of passing an ACPI device object pointer as devdata to thermal_cooling_device_create(), make acpi_processor_thermal_init() pass a pointer to the struct acpi_processor representing the given CPU to it, which allows the callback functions in processor_cooling_ops to be simplified and the second argument of acpi_processor_thermal_init() and acpi_processor_thermal_exit() to be dropped. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/6192780.MhkbZ0Pkbq@rafael.j.wysocki --- drivers/acpi/processor_driver.c | 2 +- drivers/acpi/processor_thermal.c | 35 +++++--------------------------- include/acpi/processor.h | 3 +-- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index 90c14480393de1..bdd1529d79f035 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -164,7 +164,7 @@ static int __acpi_processor_start(struct acpi_device *device) acpi_pss_perf_init(pr); - result = acpi_processor_thermal_init(pr, device); + result = acpi_processor_thermal_init(pr); if (result) goto err_power_exit; diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c index 6cd47551844ae1..11036f0d2b9422 100644 --- a/drivers/acpi/processor_thermal.c +++ b/drivers/acpi/processor_thermal.c @@ -235,15 +235,7 @@ static int processor_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; - - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; + struct acpi_processor *pr = cdev->devdata; *state = acpi_processor_max_state(pr); return 0; @@ -253,15 +245,7 @@ static int processor_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; - - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; + struct acpi_processor *pr = cdev->devdata; *cur_state = cpufreq_get_cur_state(pr->id); if (pr->flags.throttling) @@ -273,18 +257,10 @@ static int processor_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; + struct acpi_processor *pr = cdev->devdata; int result = 0; int max_pstate; - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; - max_pstate = cpufreq_get_max_state(pr->id); if (state > acpi_processor_max_state(pr)) @@ -308,10 +284,9 @@ const struct thermal_cooling_device_ops processor_cooling_ops = { .set_cur_state = processor_set_cur_state, }; -int acpi_processor_thermal_init(struct acpi_processor *pr, - struct acpi_device *device) +int acpi_processor_thermal_init(struct acpi_processor *pr) { - pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", device, + pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", pr, &processor_cooling_ops); if (IS_ERR(pr->cdev)) return PTR_ERR(pr->cdev); diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 656aaf74fb18e8..b5447af4d40b48 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -427,8 +427,7 @@ int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi); #endif /* CONFIG_ACPI_PROCESSOR_IDLE */ /* in processor_thermal.c */ -int acpi_processor_thermal_init(struct acpi_processor *pr, - struct acpi_device *device); +int acpi_processor_thermal_init(struct acpi_processor *pr); void acpi_processor_thermal_exit(struct acpi_processor *pr); extern const struct thermal_cooling_device_ops processor_cooling_ops; #ifdef CONFIG_CPU_FREQ From 520aac33ff5617e925de31cf3bf069a403763532 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:06:27 +0200 Subject: [PATCH 0400/2286] ACPI: fan: Store ACPI device pointer in struct acpi_fan In preparation for subsequent changes, instead of storing a fan ACPI handle in struct acpi_fan, store a pointer to the corresponding struct acpi_device in it. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/8772267.NyiUUSuA9g@rafael.j.wysocki --- drivers/acpi/fan.h | 2 +- drivers/acpi/fan_core.c | 11 ++++++----- drivers/acpi/fan_hwmon.c | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index e20d6ad9df8050..3faa247af5148d 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -52,7 +52,7 @@ struct acpi_fan_fst { }; struct acpi_fan { - acpi_handle handle; + struct acpi_device *adev; bool acpi4; bool has_fst; struct acpi_fan_fif fif; diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 29ca97503deb5f..8b20b8a55c13a1 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -340,17 +340,18 @@ static int acpi_fan_dsm_init(struct device *dev) }, }; struct acpi_fan *fan = dev_get_drvdata(dev); + acpi_handle fan_handle = fan->adev->handle; union acpi_object *obj; int ret = 0; - if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, + if (!acpi_check_dsm(fan_handle, &acpi_fan_microsoft_guid, 0, BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) | BIT(ACPI_FAN_DSM_SET_TRIP_POINTS))) return 0; dev_info(dev, "Using Microsoft fan extensions\n"); - obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0, + obj = acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy, ACPI_TYPE_INTEGER); if (!obj) @@ -392,8 +393,8 @@ static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower }; union acpi_object *obj; - obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, - ACPI_FAN_DSM_SET_TRIP_POINTS, &in); + obj = acpi_evaluate_dsm(fan->adev->handle, &acpi_fan_microsoft_guid, + 0, ACPI_FAN_DSM_SET_TRIP_POINTS, &in); kfree(obj); return 0; @@ -503,7 +504,7 @@ static int acpi_fan_probe(struct platform_device *pdev) return -ENOMEM; } - fan->handle = device->handle; + fan->adev = device; device->driver_data = fan; platform_set_drvdata(pdev, fan); diff --git a/drivers/acpi/fan_hwmon.c b/drivers/acpi/fan_hwmon.c index d3374f8f524b72..c5d8419f41c559 100644 --- a/drivers/acpi/fan_hwmon.c +++ b/drivers/acpi/fan_hwmon.c @@ -94,7 +94,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, struct acpi_fan_fst fst; int ret; - ret = acpi_fan_get_fst(fan->handle, &fst); + ret = acpi_fan_get_fst(fan->adev->handle, &fst); if (ret < 0) return ret; From cc695a67458758bfbc0a6d40630d8ecc9ec2533d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:07:07 +0200 Subject: [PATCH 0401/2286] ACPI: fan: Use more suitable cooling device data Instead of passing an ACPI device object pointer as devdata to thermal_cooling_device_create(), make acpi_fan_probe() pass a pointer to struct acpi_fan to it, which allows the callback functions in fan_cooling_ops to be simplified. Also avoid using acpi_driver_data() in two functions invoked by the cooling device callbacks by passing struct acpi_fan pointers instead of struct acpi_device pointers to them. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3631084.QJadu78ljV@rafael.j.wysocki --- drivers/acpi/fan_core.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 8b20b8a55c13a1..5ad65975fbb41f 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -54,8 +54,7 @@ MODULE_DEVICE_TABLE(acpi, fan_device_ids); static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) { if (fan->fif.fine_grain_ctrl) @@ -105,9 +104,9 @@ int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst) return ret; } -static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) +static int fan_get_state_acpi4(struct acpi_fan *fan, unsigned long *state) { - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_device *device = fan->adev; struct acpi_fan_fst fst; int status, i; @@ -159,13 +158,12 @@ static int fan_get_state(struct acpi_device *device, unsigned long *state) static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) - return fan_get_state_acpi4(device, state); + return fan_get_state_acpi4(fan, state); else - return fan_get_state(device, state); + return fan_get_state(fan->adev, state); } static int fan_set_state(struct acpi_device *device, unsigned long state) @@ -177,9 +175,9 @@ static int fan_set_state(struct acpi_device *device, unsigned long state) state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); } -static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) +static int fan_set_state_acpi4(struct acpi_fan *fan, unsigned long state) { - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_device *device = fan->adev; acpi_status status; u64 value = state; int max_state; @@ -213,13 +211,12 @@ static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) static int fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) - return fan_set_state_acpi4(device, state); + return fan_set_state_acpi4(fan, state); else - return fan_set_state(device, state); + return fan_set_state(fan->adev, state); } static const struct thermal_cooling_device_ops fan_cooling_ops = { @@ -565,7 +562,7 @@ static int acpi_fan_probe(struct platform_device *pdev) else name = acpi_device_bid(device); - cdev = thermal_cooling_device_create(&pdev->dev, name, device, &fan_cooling_ops); + cdev = thermal_cooling_device_create(&pdev->dev, name, fan, &fan_cooling_ops); if (IS_ERR(cdev)) { result = PTR_ERR(cdev); goto err_end; From f19ae28f1f050b7d6551412a04281955cc4103e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:05:01 +0200 Subject: [PATCH 0402/2286] tools/nolibc: validate directory with O_DIRECTORY in opendir() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch to O_DIRECTORY to validate that the opened file is a directory. ALso remove the call to fdopendir() in opendir() to avoid a double check of 'fd'. This will also make it easier to add a similar check for fdopendir(). Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-3-8cf0e79c4e6f@weissschuh.net --- tools/include/nolibc/dirent.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index 4e02ef25e72d84..c7372c935e0db9 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -42,10 +42,11 @@ DIR *opendir(const char *name) { int fd; - fd = open(name, O_RDONLY); + fd = open(name, O_RDONLY | O_DIRECTORY); if (fd == -1) return NULL; - return fdopendir(fd); + + return (DIR *)(intptr_t)~fd; } static __attribute__((unused)) From f90242f39f32c6d708c33af504bdc80a683f0da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:04:59 +0200 Subject: [PATCH 0403/2286] tools/nolibc: verify that a directory is opened MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a non-directory is opened, ENODIR should be returned from opendir()/fdopendir() right away and not only during readdir_r(). Validate the type of opened file during open. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-1-8cf0e79c4e6f@weissschuh.net --- tools/include/nolibc/dirent.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index c7372c935e0db9..2dbf4052b85a0b 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -30,10 +30,23 @@ typedef struct { static __attribute__((unused)) DIR *fdopendir(int fd) { + struct stat buf; + int ret; + if (fd < 0) { SET_ERRNO(EBADF); return NULL; } + + ret = fstat(fd, &buf); + if (ret < 0) + return NULL; + + if (!S_ISDIR(buf.st_mode)) { + SET_ERRNO(ENOTDIR); + return NULL; + } + return (DIR *)(intptr_t)~fd; } From 319b153f400a510e15029ce92c9c9b8981aa8bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 31 Aug 2026 18:05:00 +0200 Subject: [PATCH 0404/2286] selftests/nolibc: validate ENOTDIR return values from opendir()/fdopendir() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure that ENOTDIR is detected already during directory opening. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260831-nolibc-fdopendir-enotdir-v1-2-8cf0e79c4e6f@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 0a8fe5100b7f58..f62183a46205c1 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1711,6 +1711,7 @@ int run_syscall(int min, int max) CASE_TEST(execve_root); EXPECT_SYSER(1, execve("/", (char*[]){ [0] = (char []){"/"}, [1] = NULL }, NULL), -1, EACCES); break; CASE_TEST(fchdir_stdin); EXPECT_SYSER(1, fchdir(STDIN_FILENO), -1, ENOTDIR); break; CASE_TEST(fchdir_badfd); EXPECT_SYSER(1, fchdir(-1), -1, EBADF); break; + CASE_TEST(fdopendir_notdir); EXPECT_SYSER(1, (uintptr_t)fdopendir(STDIN_FILENO), (uintptr_t)NULL, ENOTDIR); break; CASE_TEST(file_stream); EXPECT_SYSZR(1, test_file_stream()); break; CASE_TEST(file_stream_wsr); EXPECT_SYSZR(1, test_file_stream_wsr()); break; CASE_TEST(fork); EXPECT_SYSZR(1, test_fork(FORK_STANDARD)); break; @@ -1739,6 +1740,7 @@ int run_syscall(int min, int max) CASE_TEST(open_blah); EXPECT_SYSER(1, tmp = open("/proc/self/blah", O_RDONLY), -1, ENOENT); if (tmp != -1) close(tmp); break; CASE_TEST(openat_dir); EXPECT_SYSZR(1, test_openat()); break; CASE_TEST(open_mode); EXPECT_SYSZR(1, test_open_mode()); break; + CASE_TEST(opendir_notdir); EXPECT_SYSER(1, (uintptr_t)opendir("/dev/null"), (uintptr_t)NULL, ENOTDIR); break; CASE_TEST(pipe); EXPECT_SYSZR(1, test_pipe()); break; CASE_TEST(poll_null); EXPECT_SYSZR(1, poll(NULL, 0, 0)); break; CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; From c2bc9bd605add0b9f1f3b7e93b649bd814de226a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 4 Sep 2026 15:12:29 +0100 Subject: [PATCH 0405/2286] rust: pin-init: internal: extract utility code to new module Create a new `util.rs` to host utility code that are generic and can be shared by multiple macros. Link: https://patch.msgid.link/20260904-tuple-struct-v1-1-72c50bd037fd@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/lib.rs | 1 + rust/pin-init/internal/src/pin_data.rs | 17 +++++----------- rust/pin-init/internal/src/util.rs | 27 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 rust/pin-init/internal/src/util.rs diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs index 60d5093f3128e7..4d8ff86484b663 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -18,6 +18,7 @@ mod diagnostics; mod init; mod pin_data; mod pinned_drop; +mod util; mod zeroable; #[proc_macro_attribute] diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index ff194d27565e82..074bc6b3091af6 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -10,7 +10,10 @@ use syn::{ Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + util::*, +}; pub(crate) mod kw { syn::custom_keyword!(PinnedDrop); @@ -81,21 +84,11 @@ pub(crate) fn pin_data( // // We need to perform this after parsing so we can reliably detect field cfgs. for (field_idx, field) in struct_.fields.iter_mut().enumerate() { - let cfg: Vec<_> = field - .attrs - .iter() - .filter(|a| a.path().is_ident("cfg")) - .map(|a| { - a.parse_args::() - .expect("parse as token stream cannot fail") - }) - .collect(); - + let cfg = field.attrs.extract_cfg_attrs(); if cfg.is_empty() { continue; } - field.attrs.retain(|a| !a.path().is_ident("cfg")); let cfg_true_struct = quote!(#struct_); let punctuated = match &mut struct_.fields { diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs new file mode 100644 index 00000000000000..ed18ab7d45e6a5 --- /dev/null +++ b/rust/pin-init/internal/src/util.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use proc_macro2::TokenStream; +use syn::Attribute; + +pub(crate) trait AttrListExt { + fn extract_cfg_attrs(&mut self) -> Vec; +} + +impl AttrListExt for Vec { + fn extract_cfg_attrs(&mut self) -> Vec { + let cfg: Vec<_> = self + .iter() + .filter(|a| a.path().is_ident("cfg")) + .map(|a| { + a.parse_args::() + .expect("parse as token stream cannot fail") + }) + .collect(); + + if !cfg.is_empty() { + self.retain(|a| !a.path().is_ident("cfg")); + } + + cfg + } +} From 1ee699c4516607c0b594813c9c81da4285ed7d56 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 4 Sep 2026 15:12:30 +0100 Subject: [PATCH 0406/2286] rust: pin-init: internal: pin_data: support tuple struct projections `#[pin_data]` rejects tuple structs because it assumes every field has a name, which it uses for the projection field, the `__Unpin` field and the pin-data accessor. Identify fields by `syn::Member` instead, so that tuple fields are referred to by their index in generated field accesses. The names that generated items still need are derived from the index as `_0`, `_1`, etc. The projection of a tuple struct is a tuple struct itself, so projected fields are accessed with the same `.0`, `.1` syntax as on the input type rather than through synthesised names. Signed-off-by: Mohamad Alsadhan [ Moved utility code to util.rs as extension trait - Gary ] Link: https://patch.msgid.link/20260904-tuple-struct-v1-2-72c50bd037fd@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 112 +++++++++++++++++-------- rust/pin-init/internal/src/util.rs | 31 ++++++- rust/pin-init/src/lib.rs | 23 +++++ 3 files changed, 130 insertions(+), 36 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 074bc6b3091af6..8cd9bf139567ce 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -7,7 +7,8 @@ use syn::{ parse_quote, parse_quote_spanned, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Item, PathSegment, Type, TypePath, Visibility, WhereClause, + Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, + WhereClause, }; use crate::{ @@ -49,6 +50,7 @@ impl ToTokens for Args { struct FieldInfo<'a> { field: &'a Field, + member: Member, pinned: bool, } @@ -129,10 +131,12 @@ pub(crate) fn pin_data( replacer.visit_generics_mut(&mut struct_.generics); replacer.visit_fields_mut(&mut struct_.fields); + let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); let fields: Vec> = struct_ .fields .iter_mut() - .map(|field| { + .enumerate() + .map(|(index, field)| { let len = field.attrs.len(); field.attrs.retain(|a| !a.path().is_ident("pin")); let pinned_count = len - field.attrs.len(); @@ -144,23 +148,30 @@ pub(crate) fn pin_data( !field.attrs.iter().any(|a| a.path().is_ident("cfg")), "cfgs should be all resolved at this point" ); + let member = match &field.ident { + Some(ident) => Member::Named(ident.clone()), + None => Member::Unnamed(Index { + index: index as u32, + span: field.span(), + }), + }; FieldInfo { field: &*field, + member, pinned: pinned_count != 0, } }) .collect(); for field in &fields { - let ident = field.field.ident.as_ref().unwrap(); - if !field.pinned && is_phantom_pinned(&field.field.ty) { dcx.warn( field.field, format!( - "The field `{ident}` of type `PhantomPinned` only has an effect \ + "The field {} of type `PhantomPinned` only has an effect \ if it has the `#[pin]` attribute", + field.member.display_name(), ), ); } @@ -168,8 +179,13 @@ pub(crate) fn pin_data( let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); - let projections = - generate_projections(&struct_.vis, &struct_.ident, &struct_.generics, &fields); + let projections = generate_projections( + &struct_.vis, + &struct_.ident, + &struct_.generics, + is_tuple_struct, + &fields, + ); let the_pin_data = generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); @@ -231,7 +247,7 @@ fn generate_unpin_impl( unreachable!() }; let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { - let ident = f.field.ident.as_ref().unwrap(); + let ident = f.member.as_ident(); let ty = &f.field.ty; quote!( #ident: #ty @@ -313,6 +329,7 @@ fn generate_projections( vis: &Visibility, ident: &Ident, generics: &Generics, + is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { let (impl_generics, ty_generics, _) = generics.split_for_impl(); @@ -325,28 +342,32 @@ fn generate_projections( let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields .iter() .map(|field| { - let Field { vis, ident, ty, .. } = &field.field; + let Field { vis, ty, .. } = &field.field; + let member = &field.member; + // The projection of a tuple struct is a tuple struct itself, so its fields are + // positional and must not be named. + let name = (!is_tuple_struct).then(|| { + let ident = field.member.as_ident(); + quote!(#ident:) + }); - let ident = ident - .as_ref() - .expect("only structs with named fields are supported"); if field.pinned { ( quote!( - #vis #ident: ::core::pin::Pin<&'__pin mut #ty>, + #vis #name ::core::pin::Pin<&'__pin mut #ty>, ), quote!( // SAFETY: this field is structurally pinned. - #ident: unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#ident) }, + #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, ), ) } else { ( quote!( - #vis #ident: &'__pin mut #ty, + #vis #name &'__pin mut #ty, ), quote!( - #ident: &mut #this.#ident, + #name &mut #this.#member, ), ) } @@ -355,24 +376,52 @@ fn generate_projections( let structurally_pinned_fields_docs = fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.member.display_name())); let not_structurally_pinned_fields_docs = fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - `{}`", f.field.ident.as_ref().unwrap())); + .map(|f| format!(" - {}", f.member.display_name())); let docs = format!(" Pin-projections of [`{ident}`]"); + let (projection_def, projection_init) = if is_tuple_struct { + ( + quote! { + #vis struct #projection #generics_with_pin_lt ( + #(#fields_decl)* + ::core::marker::PhantomData<&'__pin mut ()>, + ) #whr; + }, + quote! { + #projection( + #(#fields_proj)* + ::core::marker::PhantomData, + ) + }, + ) + } else { + ( + quote! { + #vis struct #projection #generics_with_pin_lt + #whr + { + #(#fields_decl)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + } + }, + quote! { + #projection { + #(#fields_proj)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on // the struct definition. #[allow(dead_code, non_snake_case)] #[doc(hidden)] - #vis struct #projection #generics_with_pin_lt - #whr - { - #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, - } + #projection_def impl #impl_generics #ident #ty_generics #whr @@ -390,10 +439,7 @@ fn generate_projections( ) -> #projection #ty_generics_with_pin_lt { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; - #projection { - #(#fields_proj)* - ___pin_phantom_data: ::core::marker::PhantomData, - } + #projection_init } } } @@ -414,11 +460,9 @@ fn generate_the_pin_data( let field_accessors = fields .iter() .map(|f| { - let Field { vis, ident, ty, .. } = f.field; - - let field_name = ident - .as_ref() - .expect("only structs with named fields are supported"); + let Field { vis, ty, .. } = f.field; + let field_name = f.member.as_ident(); + let member = &f.member; let pin_marker = if f.pinned { quote!(Pinned) } else { @@ -443,7 +487,7 @@ fn generate_the_pin_data( // - If `#pin_marker` is `Pinned`, the corresponding field is structurally // pinned. // - Other safety requirements follows the safety requirement. - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#field_name) } + unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } } } }) diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index ed18ab7d45e6a5..ed2c78f0658f30 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use proc_macro2::TokenStream; -use syn::Attribute; +use proc_macro2::{Ident, TokenStream}; +use quote::format_ident; +use syn::{Attribute, Index, Member}; pub(crate) trait AttrListExt { fn extract_cfg_attrs(&mut self) -> Vec; @@ -25,3 +26,29 @@ impl AttrListExt for Vec { cfg } } + +pub(crate) trait MemberExt { + /// Returns an identifier for the member. + /// + /// Tuple fields have no name of their own, so they are named `_0`, `_1`, ... instead. + fn as_ident(&self) -> Ident; + + /// Obtain a display name for the member in diagnostics. + fn display_name(&self) -> String; +} + +impl MemberExt for Member { + fn as_ident(&self) -> Ident { + match self { + Member::Named(ident) => ident.clone(), + Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + } + } + + fn display_name(&self) -> String { + match self { + Member::Named(ident) => format!("`{ident}`"), + Member::Unnamed(Index { index, .. }) => format!("index `{index}`"), + } + } +} diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index f1463be9479d67..6bf42c9e90979a 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -304,6 +304,9 @@ pub use alloc::InPlaceInit; /// This macro enables the use of the [`pin_init!`] macro. When pin-initializing a `struct`, /// then `#[pin]` directs the type of initializer that is required. /// +/// Tuple structs are supported as well. Their fields have no names, so the generated projection +/// is a tuple struct too and its fields are accessed by index. +/// /// If your `struct` implements `Drop`, then you need to add `PinnedDrop` as arguments to this /// macro, and change your `Drop` implementation to `PinnedDrop` annotated with /// `#[`[`macro@pinned_drop`]`]`, since dropping pinned values requires extra care. @@ -327,6 +330,26 @@ pub use alloc::InPlaceInit; /// } /// ``` /// +/// The same as a tuple struct, projected by index: +/// +/// ``` +/// # #![feature(allocator_api)] +/// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; +/// use core::pin::Pin; +/// use pin_init::pin_data; +/// +/// enum Command { +/// /* ... */ +/// } +/// +/// #[pin_data] +/// struct DriverData(#[pin] CMutex>, Box<[u8; 1024 * 1024]>); +/// +/// fn queue(data: Pin<&mut DriverData>) -> Pin<&mut CMutex>> { +/// data.project().0 +/// } +/// ``` +/// /// ``` /// # #![feature(allocator_api)] /// # #[path = "../examples/mutex.rs"] mod mutex; use mutex::*; From d48aa955cb91745ef2e5089f9277a7a95060cce3 Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 4 Sep 2026 15:12:31 +0100 Subject: [PATCH 0407/2286] rust: pin-init: internal: init: support tuple structs in `[pin_]init!` Extend the initializer syntax so that a field can be named by an index, addressing tuple struct fields the same way a struct expression does: pin_init!(Foo { 0: value, 1 <- initializer }) Tuple fields are not exposed by a `let` binding to the fields after them, since they have no name to bind; `_0` would shadow a user variable. Signed-off-by: Mohamad Alsadhan [ Fixed incorrect index calculation and cleaned up the code - Gary ] Link: https://patch.msgid.link/20260904-tuple-struct-v1-3-72c50bd037fd@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 115 +++++++++++++++++------------ rust/pin-init/src/lib.rs | 32 +++++++- 2 files changed, 95 insertions(+), 52 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index fd0b5ea4a0a30c..5920bb281a5004 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -8,10 +8,13 @@ use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, LitInt, Member, Path, Token, Type, }; -use crate::diagnostics::{DiagCtxt, ErrorGuaranteed}; +use crate::{ + diagnostics::{DiagCtxt, ErrorGuaranteed}, + util::*, +}; pub(crate) struct Initializer { attrs: Vec, @@ -36,11 +39,11 @@ struct InitializerField { enum InitializerKind { Value { - ident: Ident, + member: Member, value: Option<(Token![:], Expr)>, }, Init { - ident: Ident, + member: Member, _left_arrow_token: Token![<-], value: Expr, }, @@ -52,9 +55,9 @@ enum InitializerKind { } impl InitializerKind { - fn ident(&self) -> Option<&Ident> { + fn member(&self) -> Option<&Member> { match self { - Self::Value { ident, .. } | Self::Init { ident, .. } => Some(ident), + Self::Value { member, .. } | Self::Init { member, .. } => Some(member), Self::Code { .. } => None, } } @@ -229,9 +232,9 @@ fn init_fields( cfgs }; - let ident = match kind { - InitializerKind::Value { ident, .. } => ident, - InitializerKind::Init { ident, .. } => ident, + let member = match kind { + InitializerKind::Value { member, .. } => member, + InitializerKind::Init { member, .. } => member, InitializerKind::Code { block, .. } => { let stmt = &block.stmts; res.extend(quote! { @@ -243,27 +246,28 @@ fn init_fields( continue; } }; + let ident = member.as_ident(); let slot = if pinned { quote! { // SAFETY: // - `slot` is valid and properly aligned. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` is exclusively accessed and has not been initialized. (unsafe { #data.#ident(#slot) }) } } else { quote! { // For `init!()` macro, everything is unpinned. // SAFETY: - // - `&raw mut (*slot).#ident` is valid. - // - `make_field_check` checks that `&raw mut (*slot).#ident` is properly aligned. - // - `make_field_check` prevents `#ident` from being used twice, therefore - // `(*slot).#ident` is exclusively accessed and has not been initialized. + // - `&raw mut (*slot).#member` is valid. + // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. + // - `make_field_check` prevents `#member` from being used twice, therefore + // `(*slot).#member` is exclusively accessed and has not been initialized. (unsafe { ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new( - &raw mut (*#slot).#ident + &raw mut (*#slot).#member ) }) } @@ -273,11 +277,11 @@ fn init_fields( let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); let init = match kind { - InitializerKind::Value { ident, value } => { + InitializerKind::Value { value, .. } => { let value = value .as_ref() .map(|(_, value)| quote!(#value)) - .unwrap_or_else(|| quote!(#ident)); + .unwrap_or_else(|| quote!(#member)); quote! { #(#attrs)* @@ -294,14 +298,23 @@ fn init_fields( InitializerKind::Code { .. } => unreachable!(), }; + // A tuple field has no name that could be bound here (the `_0` identifiers are considered + // implementation detail and not user-facing). + let binding = match member { + Member::Named(ident) => quote! { + #(#cfgs)* + // Allow `non_snake_case` since the same warning is going to be reported for the + // struct field. + #[allow(unused_variables, non_snake_case)] + let #ident = #guard.let_binding(); + }, + Member::Unnamed(_) => quote!(), + }; + res.extend(quote! { #init - #(#cfgs)* - // Allow `non_snake_case` since the same warning is going to be reported for the struct - // field. - #[allow(unused_variables, non_snake_case)] - let #ident = #guard.let_binding(); + #binding }); guards.push(guard); @@ -326,9 +339,9 @@ fn make_field_check( ) -> TokenStream { let field_attrs: Vec<_> = fields .iter() - .filter_map(|f| f.kind.ident().map(|_| &f.attrs)) + .filter_map(|f| f.kind.member().map(|_| &f.attrs)) .collect(); - let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.ident()).collect(); + let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect(); let zeroing_trailer = match init_kind { InitKind::Normal => None, InitKind::Zeroing => Some(quote! { @@ -376,7 +389,8 @@ impl Parse for Initializer { let lh = content.lookahead1(); if lh.peek(End) || lh.peek(Token![..]) { break; - } else if lh.peek(Ident) || lh.peek(Token![_]) || lh.peek(Token![#]) { + } else if lh.peek(Ident) || lh.peek(LitInt) || lh.peek(Token![_]) || lh.peek(Token![#]) + { fields.push_value(content.parse()?); let lh = content.lookahead1(); if lh.peek(End) { @@ -450,31 +464,36 @@ impl Parse for InitializerField { impl Parse for InitializerKind { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let lh = input.lookahead1(); - if lh.peek(Token![_]) { - Ok(Self::Code { + let member = if lh.peek(Token![_]) { + return Ok(Self::Code { _underscore_token: input.parse()?, _colon_token: input.parse()?, block: input.parse()?, + }); + } else if lh.peek(Ident) || lh.peek(LitInt) { + input.parse::()? + } else { + return Err(lh.error()); + }; + + let lh = input.lookahead1(); + if lh.peek(Token![<-]) { + Ok(Self::Init { + member, + _left_arrow_token: input.parse()?, + value: input.parse()?, + }) + } else if lh.peek(Token![:]) { + Ok(Self::Value { + member, + value: Some((input.parse()?, input.parse()?)), + }) + } else if matches!(member, Member::Named(_)) && (lh.peek(Token![,]) || lh.peek(End)) { + // Short-hand syntax, available for named fields only. + Ok(Self::Value { + member, + value: None, }) - } else if lh.peek(Ident) { - let ident = input.parse()?; - let lh = input.lookahead1(); - if lh.peek(Token![<-]) { - Ok(Self::Init { - ident, - _left_arrow_token: input.parse()?, - value: input.parse()?, - }) - } else if lh.peek(Token![:]) { - Ok(Self::Value { - ident, - value: Some((input.parse()?, input.parse()?)), - }) - } else if lh.peek(Token![,]) || lh.peek(End) { - Ok(Self::Value { ident, value: None }) - } else { - Err(lh.error()) - } } else { Err(lh.error()) } diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 6bf42c9e90979a..fc34cfd7372d84 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -592,7 +592,7 @@ macro_rules! stack_try_pin_init { }; } -/// Construct an in-place, fallible pinned initializer for `struct`s. +/// Construct an in-place, fallible pinned initializer for structs, including tuple structs. /// /// The error type defaults to [`Infallible`]; if you need a different one, write `? Error` at the /// end, after the struct initializer. @@ -626,6 +626,28 @@ macro_rules! stack_try_pin_init { /// # Box::pin_init(demo()).unwrap(); /// ``` /// +/// The fields of a tuple struct are addressed by their index: +/// +/// ```rust +/// # use pin_init::*; +/// # use core::pin::Pin; +/// #[pin_data] +/// struct Pair(usize, Bar); +/// +/// #[pin_data] +/// struct Bar { +/// x: u32, +/// } +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair { +/// 0: 42, +/// 1 <- Bar { x: 64 }, +/// }); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// /// Arbitrary Rust expressions can be used to set the value of a variable. /// /// The fields are initialized in the order that they appear in the initializer. So it is possible @@ -744,9 +766,11 @@ macro_rules! stack_try_pin_init { /// /// # Syntax /// -/// As already mentioned in the examples above, inside of `pin_init!` a `struct` initializer with -/// the following modifications is expected: +/// As already mentioned in the examples above, inside of `pin_init!` a struct initializer with the +/// following modifications is expected: /// - Fields that you want to initialize in-place have to use `<-` instead of `:`. +/// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They +/// are not exposed by a `let` binding, since they have no name to bind. /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in /// order to run arbitrary code. /// - In front of the initializer you can write `&this in` to have access to a [`NonNull`] @@ -785,7 +809,7 @@ macro_rules! stack_try_pin_init { /// [`NonNull`]: core::ptr::NonNull pub use pin_init_internal::pin_init; -/// Construct an in-place, fallible initializer for `struct`s. +/// Construct an in-place, fallible initializer for structs, including tuple structs. /// /// This macro defaults the error to [`Infallible`]; if you need a different one, write `? Error` /// at the end, after the struct initializer. From 3b3ff9034a497fb3b8429f6fe8ba5697bc7dcebd Mon Sep 17 00:00:00 2001 From: Mohamad Alsadhan Date: Fri, 4 Sep 2026 15:12:32 +0100 Subject: [PATCH 0408/2286] rust: pin-init: internal: init: support tuple struct constructor syntax A tuple struct whose fields are all set to a value reads better written like a call to its constructor than with the indices spelled out: pin_init!(Foo(value, value)) Parse the two forms into separate types and rewrite the constructor arguments into the indexed fields they are shorthand for, so that only the parser has to know about the second form. The arguments have no names, so they cannot use `<-`. Parse it anyway and reject it afterwards, which reports the position of every offending `<-` rather than stopping at the first one. `cfg` needs different treatment for tuple constructor syntax. As non-derive proc macros are invoked before cfg is resolved, the macro cannot know whether a field survives, and dropping a tuple field renumbers every field after it. That cannot be expressed by attaching a `cfg` attribute to the initializer of a single field. Thus, resolve tuple field cfgs up front instead, by generating two cfg-gated invocations of the macro with one field resolved in each. This is the approach of commit 5bbf2b2deb94 ("rust: pin-init: internal: rework how `#[pin_data]` handles cfg"), and it is linear time because only one of the two branches is ever expanded. Struct expression syntax do not renumber, so using tuple structs with struct syntax can keep using the existing attribute-based handling. Suggested-by: Gary Guo Link: https://github.com/Rust-for-Linux/pin-init/pull/165 Signed-off-by: Mohamad Alsadhan [ Pre-expand cfgs for tuple init syntax. Use generics instead of separate types for normalization - Gary ] Co-developed-by: Gary Guo Link: https://patch.msgid.link/20260904-tuple-struct-v1-4-72c50bd037fd@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 381 +++++++++++++++++++++++++++-- rust/pin-init/internal/src/lib.rs | 11 +- rust/pin-init/src/lib.rs | 16 ++ 3 files changed, 383 insertions(+), 25 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 5920bb281a5004..dc5dfb2ca11427 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -1,14 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use syn::{ - braced, + braced, parenthesized, parse::{End, Parse}, parse_quote, - punctuated::Punctuated, + punctuated::{Pair, Punctuated}, spanned::Spanned, - token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, LitInt, Member, Path, Token, Type, + token, Attribute, Block, Expr, ExprCall, ExprPath, Ident, Index, LitInt, Member, Path, Token, + Type, }; use crate::{ @@ -16,14 +17,87 @@ use crate::{ util::*, }; -pub(crate) struct Initializer { +pub(crate) struct Initializer { attrs: Vec, this: Option, + kind: Kind, + error: Option<(Token![?], Type)>, +} + +pub(crate) struct InitExprStruct { path: Path, brace_token: token::Brace, fields: Punctuated, rest: Option<(Token![..], Expr)>, - error: Option<(Token![?], Type)>, +} + +pub(crate) struct InitExprTuple { + path: Path, + paren_token: token::Paren, + fields: Punctuated, +} + +pub(crate) enum InitExprKind { + Struct(InitExprStruct), + Tuple(InitExprTuple), +} + +struct InitTupleField { + attrs: Vec, + /// `<-` is not valid in constructor syntax; it is parsed anyway so that it can be rejected + /// with a proper diagnostic instead of a parse error. + left_arrow_token: Option, + value: Expr, +} + +impl InitExprTuple { + fn normalize(self) -> InitExprStruct { + let InitExprTuple { + path, + paren_token, + fields, + } = self; + InitExprStruct { + path, + brace_token: token::Brace { + span: paren_token.span, + }, + fields: fields + .into_pairs() + .enumerate() + .map(|(index, pair)| { + let (field, comma) = pair.into_tuple(); + let span = field.value.span(); + let field = InitializerField { + attrs: field.attrs, + kind: InitializerKind::Value { + member: Member::Unnamed(Index { + index: index.try_into().unwrap(), + span, + }), + value: Some((Token![:](span), field.value)), + }, + }; + Pair::new(field, comma) + }) + .collect(), + rest: None, + } + } + + fn validate(&self, dcx: &mut DiagCtxt) -> Result<(), ErrorGuaranteed> { + let mut result = Ok(()); + for field in &self.fields { + if let Some(left_arrow_token) = &field.left_arrow_token { + result = Err(dcx.error( + left_arrow_token, + "`<-` is not supported in tuple constructor syntax; name the fields by index \ + instead, e.g. `Type { 0 <- initializer, 1: value }`", + )); + } + } + result + } } struct This { @@ -71,16 +145,103 @@ struct DefaultErrorAttribute { ty: Box, } -pub(crate) fn expand( +pub(crate) fn expand_with_cfg( + initializer: Initializer, + default_error: Option<&'static str>, + pinned: bool, + dcx: &mut DiagCtxt, +) -> Result { + let initializer = match initializer.kind { + InitExprKind::Tuple(expr) => { + expr.validate(dcx)?; + + let mut initializer = Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: expr, + error: initializer.error, + }; + + // Removing a tuple field renumbers every field after it, which cannot be expressed with + // a `cfg` attribute on the initializer of a single field. Therefore, resolve tuple + // field cfgs before continuing. Struct expression syntax uses explicit numbers, so + // there is no need to pre-expand them and we only need to emit their cfgs on generated + // code. + for (field_idx, field) in initializer.kind.fields.iter_mut().enumerate() { + let cfg = field.attrs.extract_cfg_attrs(); + + if cfg.is_empty() { + continue; + } + + let true_initializer = initializer.to_token_stream(); + initializer.kind.fields = initializer + .kind + .fields + .into_pairs() + .enumerate() + .filter(|&(index, _)| index != field_idx) + .map(|(_, pair)| pair) + .collect(); + + let false_initializer = &initializer; + + let macro_name = if pinned { + quote!(::pin_init::pin_init) + } else { + quote!(::pin_init::init) + }; + + // Resolve one field at a time until we've got no more tuple field cfgs. + // + // This is linear time because macro invocations with false cfg will not be + // expanded. + return Ok(quote! { + { + // Use `{}` delimiter here so semicolon is not required, otherwise the + // expression becomes unit type. + #[cfg(all(#(#cfg,)*))] + #macro_name! { #true_initializer } + + #[cfg(not(all(#(#cfg,)*)))] + #macro_name! { #false_initializer } + } + }); + } + + // No cfgs left, we can normalize the initializer to the struct kind. + Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: initializer.kind.normalize(), + error: initializer.error, + } + } + + InitExprKind::Struct(expr) => Initializer { + attrs: initializer.attrs, + this: initializer.this, + kind: expr, + error: initializer.error, + }, + }; + + expand(initializer, default_error, pinned, dcx) +} + +fn expand( Initializer { attrs, this, - path, - brace_token, - fields, - rest, + kind: + InitExprStruct { + path, + brace_token, + fields, + rest, + }, error, - }: Initializer, + }: Initializer, default_error: Option<&'static str>, pinned: bool, dcx: &mut DiagCtxt, @@ -99,7 +260,10 @@ pub(crate) fn expand( } else if let Some(default_error) = default_error { syn::parse_str(default_error).unwrap() } else { - dcx.error(brace_token.span.close(), "expected `? ` after `}`"); + dcx.error( + brace_token.span.close(), + "expected `? ` after initializer", + ); parse_quote!(::core::convert::Infallible) } }, @@ -377,11 +541,8 @@ fn make_field_check( } } -impl Parse for Initializer { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let attrs = input.call(Attribute::parse_outer)?; - let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; - let path = input.parse()?; +impl InitExprStruct { + fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result { let content; let brace_token = braced!(content in input); let mut fields = Punctuated::new(); @@ -408,6 +569,51 @@ impl Parse for Initializer { .peek(Token![..]) .then(|| Ok::<_, syn::Error>((content.parse()?, content.parse()?))) .transpose()?; + Ok(Self { + path, + brace_token, + fields, + rest, + }) + } +} + +impl InitExprTuple { + fn parse_with_path(path: Path, input: syn::parse::ParseStream<'_>) -> syn::Result { + let content; + let paren_token = parenthesized!(content in input); + let mut fields = Punctuated::new(); + while !content.is_empty() { + fields.push_value(InitTupleField { + attrs: content.call(Attribute::parse_outer)?, + left_arrow_token: content.parse()?, + value: content.parse()?, + }); + if content.is_empty() { + break; + } + fields.push_punct(content.parse()?); + } + Ok(InitExprTuple { + path, + paren_token, + fields, + }) + } +} + +impl Parse for Initializer { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let attrs = input.call(Attribute::parse_outer)?; + let this = input.peek(Token![&]).then(|| input.parse()).transpose()?; + let path = input.parse()?; + let kind = if input.peek(token::Brace) { + InitExprKind::Struct(InitExprStruct::parse_with_path(path, input)?) + } else if input.peek(token::Paren) { + InitExprKind::Tuple(InitExprTuple::parse_with_path(path, input)?) + } else { + return Err(input.error("expected curly braces or parentheses")); + }; let error = input .peek(Token![?]) .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) @@ -426,10 +632,7 @@ impl Parse for Initializer { Ok(Self { attrs, this, - path, - brace_token, - fields, - rest, + kind, error, }) } @@ -499,3 +702,137 @@ impl Parse for InitializerKind { } } } + +impl ToTokens for Initializer { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + this, + kind, + error, + } = self; + tokens.append_all(attrs); + this.to_tokens(tokens); + kind.to_tokens(tokens); + if let Some((question, ty)) = error { + question.to_tokens(tokens); + ty.to_tokens(tokens); + } + } +} + +impl ToTokens for InitExprKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Struct(init) => init.to_tokens(tokens), + Self::Tuple(init) => init.to_tokens(tokens), + } + } +} + +impl ToTokens for InitExprStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + brace_token, + fields, + rest, + } = self; + path.to_tokens(tokens); + brace_token.surround(tokens, |tokens| { + fields.to_tokens(tokens); + if let Some((dotdot, expr)) = rest { + dotdot.to_tokens(tokens); + expr.to_tokens(tokens); + } + }); + } +} + +impl ToTokens for InitExprTuple { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + path, + paren_token, + fields, + } = self; + path.to_tokens(tokens); + paren_token.surround(tokens, |tokens| fields.to_tokens(tokens)); + } +} + +impl ToTokens for InitTupleField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + attrs, + left_arrow_token, + value, + } = self; + tokens.append_all(attrs); + left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } +} + +impl ToTokens for InitializerAttribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::DefaultError(DefaultErrorAttribute { ty }) => { + quote!(#[default_error(#ty)]).to_tokens(tokens); + } + } + } +} + +impl ToTokens for This { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + _and_token, + ident, + _in_token, + } = self; + _and_token.to_tokens(tokens); + ident.to_tokens(tokens); + _in_token.to_tokens(tokens); + } +} + +impl ToTokens for InitializerField { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { attrs, kind } = self; + tokens.append_all(attrs); + kind.to_tokens(tokens); + } +} + +impl ToTokens for InitializerKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Value { member, value } => { + member.to_tokens(tokens); + if let Some((colon, expr)) = value { + colon.to_tokens(tokens); + expr.to_tokens(tokens); + } + } + Self::Init { + member, + _left_arrow_token, + value, + } => { + member.to_tokens(tokens); + _left_arrow_token.to_tokens(tokens); + value.to_tokens(tokens); + } + Self::Code { + _underscore_token, + _colon_token, + block, + } => { + _underscore_token.to_tokens(tokens); + _colon_token.to_tokens(tokens); + block.to_tokens(tokens); + } + } + } +} diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs index 4d8ff86484b663..c488019d6250ef 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -49,12 +49,17 @@ pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { #[proc_macro] pub fn init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), false, dcx)) - .into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx) + }) + .into() } #[proc_macro] pub fn pin_init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| init::expand(input, Some("::core::convert::Infallible"), true, dcx)).into() + DiagCtxt::with(|dcx| { + init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx) + }) + .into() } diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index fc34cfd7372d84..4110590348c6ac 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -648,6 +648,20 @@ macro_rules! stack_try_pin_init { /// # Box::pin_init(demo()).unwrap(); /// ``` /// +/// A tuple struct whose fields are all set to a value can also be written like a call to its +/// constructor: +/// +/// ```rust +/// # use pin_init::*; +/// #[pin_data] +/// struct Pair(usize, usize); +/// +/// # fn demo() -> impl PinInit { +/// let initializer = pin_init!(Pair(42, 64)); +/// # initializer } +/// # Box::pin_init(demo()).unwrap(); +/// ``` +/// /// Arbitrary Rust expressions can be used to set the value of a variable. /// /// The fields are initialized in the order that they appear in the initializer. So it is possible @@ -771,6 +785,8 @@ macro_rules! stack_try_pin_init { /// - Fields that you want to initialize in-place have to use `<-` instead of `:`. /// - Tuple struct fields are named by their index, as in `0: value` or `0 <- initializer`. They /// are not exposed by a `let` binding, since they have no name to bind. +/// - A tuple struct can also be initialized with constructor syntax, as in `Type(value, value)`. +/// Since its arguments are not named, they cannot use `<-`; write them out by index instead. /// - You can use `_: { /* run any user-code here */ },` anywhere where you can place fields in /// order to run arbitrary code. /// - In front of the initializer you can write `&this in` to have access to a [`NonNull`] From 54c35131e628e1a18f02365495a24a73a1fb515e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 01:50:08 +0200 Subject: [PATCH 0409/2286] microblaze: uaccess: Zero out destination on failed get_user() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On failure get_user() is supposed to zero out the destination variable. This is documented in the kdoc of the microblaze get_user() implementation and validated in lib/tests/usercopy_kunit.c. Currently that zeroing is missing. Add it. Fixes: 0d6de9532663 ("microblaze_mmu_v2: uaccess MMU update") Signed-off-by: Thomas Weißschuh Reviewed-by: David Gow Link: https://patch.msgid.link/20260914-kunit-microblaze-v2-1-7d5756e858f8@weissschuh.net Signed-off-by: Michal Simek --- arch/microblaze/include/asm/uaccess.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/microblaze/include/asm/uaccess.h b/arch/microblaze/include/asm/uaccess.h index afa0dd8d013fbc..77203af255e5f9 100644 --- a/arch/microblaze/include/asm/uaccess.h +++ b/arch/microblaze/include/asm/uaccess.h @@ -95,7 +95,8 @@ extern long __user_bad(void); #define get_user(x, ptr) ({ \ const typeof(*(ptr)) __user *__gu_ptr = (ptr); \ access_ok(__gu_ptr, sizeof(*__gu_ptr)) ? \ - __get_user(x, __gu_ptr) : -EFAULT; \ + __get_user(x, __gu_ptr) : \ + ((x) = 0, -EFAULT); \ }) #define __get_user(x, ptr) \ From b9eababe8c0b9f71ce7f35f903879a7fcb6daebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 01:50:09 +0200 Subject: [PATCH 0410/2286] microblaze: Pass -m{big,little}-endian through KBUILD_CPPFLAGS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These parameters are also necessary for preprocessed files which do not contain code. One example is arch/microblaze/kernel/vmlinux.lds.S which inspects __MICROBLAZEEL__ for the definition of 'jiffies_64'. As currently the endianness is not passed from the build system for this file, the preprocessor will instead use the default endianness of the toolchain. When a default big-endian toolchain is used to build a little-endian kernel, that kernel will hang during delay calibration as 'jiffies' never changes. Make sure all preprocessor invocations know about the correct endianness by passing it through KBUILD_CPPFLAGS. This will also be inherited by CFLAGS and AFLAGS. Fixes: 02b08045a030 ("microblaze: Add support for little-endian Microblaze") Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260914-kunit-microblaze-v2-2-7d5756e858f8@weissschuh.net Signed-off-by: Michal Simek --- arch/microblaze/Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/microblaze/Makefile b/arch/microblaze/Makefile index 56a161147606ca..01085dd5e6f871 100644 --- a/arch/microblaze/Makefile +++ b/arch/microblaze/Makefile @@ -34,12 +34,10 @@ CPUFLAGS-$(CONFIG_XILINX_MICROBLAZE0_USE_BARREL) += -mxl-barrel-shift CPUFLAGS-$(CONFIG_XILINX_MICROBLAZE0_USE_PCMP_INSTR) += -mxl-pattern-compare ifdef CONFIG_CPU_BIG_ENDIAN -KBUILD_CFLAGS += -mbig-endian -KBUILD_AFLAGS += -mbig-endian +KBUILD_CPPFLAGS += -mbig-endian KBUILD_LDFLAGS += -EB else -KBUILD_CFLAGS += -mlittle-endian -KBUILD_AFLAGS += -mlittle-endian +KBUILD_CPPFLAGS += -mlittle-endian KBUILD_LDFLAGS += -EL endif From 4ebcbacb0b0adf827cd9475f36f866deda39cbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 01:50:10 +0200 Subject: [PATCH 0411/2286] microblaze: reset: Call POWER_OFF handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System power off might be implemented through sys_off handlers. Currently these are not respected on microblaze. On power_off call into the generic power off function which will execute all regustered handlers. Signed-off-by: Thomas Weißschuh Acked-by: David Gow Link: https://patch.msgid.link/20260914-kunit-microblaze-v2-3-7d5756e858f8@weissschuh.net Signed-off-by: Michal Simek --- arch/microblaze/kernel/reset.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/microblaze/kernel/reset.c b/arch/microblaze/kernel/reset.c index 2f66c7963084dd..774bc1f79119fb 100644 --- a/arch/microblaze/kernel/reset.c +++ b/arch/microblaze/kernel/reset.c @@ -28,6 +28,7 @@ void machine_halt(void) void machine_power_off(void) { pr_notice("Machine power off...\n"); + do_kernel_power_off(); while (1) ; } From 7d376ea6a7f9518929b0621aa06c4bd348374098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 01:50:11 +0200 Subject: [PATCH 0412/2286] microblaze: reset: Provide a power off handler through an unaligned PC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit microblaze is missing a generic architecture-wide power off mechanism. To enable KUnit for microblaze it is necessary for KUnit to shut down the machine in a way that QEMU will recognize. The machines emulated by QEMU do not provide machine-specific power off functionality which could be used. However at least the petalogix-s3adsp1800 machine will abort if an unaligned instruction is executed. An ugly message will be printed but that is not an issue for KUnit. Make use of this to provide a power off handler. Signed-off-by: Thomas Weißschuh Tested-by: David Gow Link: https://patch.msgid.link/20260914-kunit-microblaze-v2-4-7d5756e858f8@weissschuh.net Signed-off-by: Michal Simek --- arch/microblaze/Kconfig | 9 +++++++++ arch/microblaze/kernel/reset.c | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/arch/microblaze/Kconfig b/arch/microblaze/Kconfig index 484ebb3baedf15..0ca8999dd7709d 100644 --- a/arch/microblaze/Kconfig +++ b/arch/microblaze/Kconfig @@ -216,3 +216,12 @@ config MB_MANAGER Say N here unless you know what you are doing. endmenu + +config MB_POWER_OFF_THROUGH_UNALIGNED_PC + bool "Power off through unaligned PC" + help + This options adds a power off handler which executes an unaligned PC + so the machine resets in a generic way. This works for the + petalogix-s3adsp1800 QEMU machine. + + Say N here unless you know what you are doing. diff --git a/arch/microblaze/kernel/reset.c b/arch/microblaze/kernel/reset.c index 774bc1f79119fb..42031c535d48d2 100644 --- a/arch/microblaze/kernel/reset.c +++ b/arch/microblaze/kernel/reset.c @@ -41,3 +41,24 @@ void machine_restart(char *cmd) pr_emerg("Reboot failed -- System halted\n"); while (1); } + +#ifdef CONFIG_MB_POWER_OFF_THROUGH_UNALIGNED_PC +static int unaligned_pc_sys_off(struct sys_off_data *data) +{ + __asm__( + "bri 1\n" + ); + + return NOTIFY_DONE; +} + +static int __init register_unaligned_pc_sys_off(void) +{ + struct sys_off_handler *sys_off; + + sys_off = register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_LOW, + unaligned_pc_sys_off, NULL); + return PTR_ERR_OR_ZERO(sys_off); +} +device_initcall(register_unaligned_pc_sys_off); +#endif /* CONFIG_MB_POWER_OFF_THROUGH_UNALIGNED_PC */ From 98f468b04d5cc3be5382a923f19ab289e0c40fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 01:50:12 +0200 Subject: [PATCH 0413/2286] kunit: qemu_configs: Add microblaze configurations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add basic configurations to run kunit tests on big and little endian microblaze. Suggested-by: Michal Simek Signed-off-by: Thomas Weißschuh Reviewed-by: David Gow Link: https://patch.msgid.link/20260914-kunit-microblaze-v2-5-7d5756e858f8@weissschuh.net Signed-off-by: Michal Simek --- .../kunit/qemu_configs/microblazebe.py | 24 +++++++++++++++++ .../kunit/qemu_configs/microblazeel.py | 27 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tools/testing/kunit/qemu_configs/microblazebe.py create mode 100644 tools/testing/kunit/qemu_configs/microblazeel.py diff --git a/tools/testing/kunit/qemu_configs/microblazebe.py b/tools/testing/kunit/qemu_configs/microblazebe.py new file mode 100644 index 00000000000000..f37b7af20142df --- /dev/null +++ b/tools/testing/kunit/qemu_configs/microblazebe.py @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +from ..qemu_config import QemuArchParams + +QEMU_ARCH = QemuArchParams(linux_arch='microblaze', + kconfig=''' +CONFIG_KERNEL_BASE_ADDR=0x90000000 +CONFIG_XILINX_MICROBLAZE0_USE_MSR_INSTR=1 +CONFIG_XILINX_MICROBLAZE0_USE_PCMP_INSTR=1 +CONFIG_XILINX_MICROBLAZE0_USE_BARREL=1 +CONFIG_XILINX_MICROBLAZE0_USE_DIV=0 +CONFIG_XILINX_MICROBLAZE0_USE_HW_MUL=1 +CONFIG_XILINX_MICROBLAZE0_USE_FPU=0 +CONFIG_CPU_BIG_ENDIAN=y +CONFIG_SERIAL_UARTLITE=y +CONFIG_SERIAL_UARTLITE_CONSOLE=y +CONFIG_MB_POWER_OFF_THROUGH_UNALIGNED_PC=y +''', + qemu_arch='microblaze', + kernel_path='arch/microblaze/boot/linux.bin', + kernel_command_line='kunit_shutdown=poweroff', + extra_qemu_params=[ + '-M', 'petalogix-s3adsp1800', + ], +) diff --git a/tools/testing/kunit/qemu_configs/microblazeel.py b/tools/testing/kunit/qemu_configs/microblazeel.py new file mode 100644 index 00000000000000..a8bc572d892ad3 --- /dev/null +++ b/tools/testing/kunit/qemu_configs/microblazeel.py @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only +from ..qemu_config import QemuArchParams + +QEMU_ARCH = QemuArchParams(linux_arch='microblaze', + kconfig=''' +CONFIG_KERNEL_BASE_ADDR=0x50000000 +CONFIG_XILINX_MICROBLAZE0_FAMILY="virtex5" +CONFIG_XILINX_MICROBLAZE0_USE_MSR_INSTR=1 +CONFIG_XILINX_MICROBLAZE0_USE_PCMP_INSTR=1 +CONFIG_XILINX_MICROBLAZE0_USE_BARREL=1 +CONFIG_XILINX_MICROBLAZE0_USE_DIV=1 +CONFIG_XILINX_MICROBLAZE0_USE_HW_MUL=2 +CONFIG_XILINX_MICROBLAZE0_USE_FPU=1 +CONFIG_CPU_LITTLE_ENDIAN=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_16550A_VARIANTS=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_MB_POWER_OFF_THROUGH_UNALIGNED_PC=y +''', + qemu_arch='microblaze', + kernel_path='arch/microblaze/boot/linux.bin', + kernel_command_line='kunit_shutdown=poweroff', + extra_qemu_params=[ + '-M', 'petalogix-ml605', + ], +) From 168eb0ff8d8de861a6ae9a3001861fe2e61f20cf Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Mon, 25 May 2026 19:16:44 -0700 Subject: [PATCH 0414/2286] affs: don't instantiate dentry if affs_insert_hash failed affs_add_entry() calls d_instantiate() unconditionally after affs_insert_hash() returns, even when the hash insert failed. When the parent directory's block cannot be read (e.g. -EIO from affs_bread on a crafted image), affs_insert_hash() returns the error, but the dentry has already been bound to the freshly-created inode by d_instantiate(). The callers (affs_create / affs_mkdir / affs_symlink) then run iput() on the inode in their error path, dropping its refcount to zero and evicting it through affs_evict_inode(). The dentry still holds a pointer to that now-evicted inode. On umount, shrink_dcache_for_umount() walks the dentry and __dentry_kill() calls iput() on an inode already in I_FREEING|I_CLEAR, hitting the VFS_BUG_ON_INODE() at fs/inode.c:1980: VFS_BUG_ON_INODE(inode_state_read_once(inode) & (I_FREEING | I_CLEAR)) inode:... fs:affs mode:120777 ... state:0x300 count:0 kernel BUG at fs/inode.c:1980! Call Trace: iput+0xe29/0xe80 fs/inode.c:1980 __dentry_kill+0x20e/0x710 fs/dcache.c:718 shrink_kill+0xa9/0x2c0 fs/dcache.c:1195 shrink_dentry_list+0x2e5/0x5f0 fs/dcache.c:1222 shrink_dcache_for_umount+0xa5/0x170 fs/dcache.c:1738 generic_shutdown_super+0x74/0x2d0 fs/super.c:624 kill_block_super+0x49/0xa0 fs/super.c:1725 affs_kill_sb+0x4c/0x160 fs/affs/super.c:590 Reproduced by mounting a crafted AFFS image whose root-directory header block is unreadable, calling symlinkat() to create an entry under it (which reaches affs_add_entry() with retval != 0 from affs_insert_hash()), and letting the mount tear down. Trigger requires the ability to mount a crafted image (CAP_SYS_ADMIN or equivalent). Only call d_instantiate() when affs_insert_hash() succeeded. On the error path the dentry remains negative, so the caller's iput() correctly disposes of the unbound inode and no stale dentry-to-evicted-inode binding survives into umount. Reported-by: Farhad Alemi Signed-off-by: Farhad Alemi Signed-off-by: David Sterba --- fs/affs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/affs/inode.c b/fs/affs/inode.c index d4a3f381c4bcff..d4ecb59f33ae52 100644 --- a/fs/affs/inode.c +++ b/fs/affs/inode.c @@ -404,7 +404,8 @@ affs_add_entry(struct inode *dir, struct inode *inode, struct dentry *dentry, s3 affs_unlock_dir(dir); affs_unlock_link(inode); - d_instantiate(dentry, inode); + if (!retval) + d_instantiate(dentry, inode); done: affs_brelse(inode_bh); affs_brelse(bh); From 194164a843397a4584c5fa682f9f3e5b2740132c Mon Sep 17 00:00:00 2001 From: Farhad Alemi Date: Tue, 26 May 2026 16:08:23 -0400 Subject: [PATCH 0415/2286] affs: split hash-lock subclass and reject self-referencing dir entry affs_remove_header() acquires i_hash_lock on the parent directory at the top of the function and again on the child inode inside the ST_USERDIR arm, both via affs_lock_dir() which uses SINGLE_DEPTH_NESTING. Lockdep sees two acquisitions of the same lock class in the same task at the same subclass and warns: WARNING: possible recursive locking detected syz.2.17/3573 is trying to acquire lock: ffff888123db0778 (&ei->i_ext_lock/1){+.+.}-{4:4}, at: affs_lock_dir affs_remove_header+0x72f fs/affs/amigaffs.c:296 but task is already holding lock: ffff888123db0100 (&ei->i_ext_lock/1){+.+.}-{4:4}, at: affs_lock_dir affs_remove_header+0x261 fs/affs/amigaffs.c:289 Call Trace: affs_lock_dir fs/affs/affs.h:311 [inline] affs_remove_header+0x72f/0x1ab0 fs/affs/amigaffs.c:296 vfs_rmdir+0x20b/0x6a0 fs/namei.c:4446 do_rmdir+0x2ed/0x3c0 fs/namei.c:4524 __x64_sys_unlinkat+0xf4/0x140 fs/namei.c:4692 With panic_on_warn this terminates the kernel. The two lock instances in the report are distinct inodes (db0778 vs db0100), so what lockdep is reporting is two acquisitions of the same lock class at the same subclass -- a missing subclass distinction in the annotation. Trigger requires the ability to mount a crafted image (CAP_SYS_ADMIN or equivalent) and is reproduced by rmdir() of a subdirectory under a crafted AFFS image. The same locking pattern is also vulnerable to a strict self-deadlock on a crafted image whose on-disk hash table contains an entry with i_ino equal to its containing directory's own block number: affs_iget() would return the same in-memory inode for both d_inode(dentry) and d_inode(dentry->d_parent), and the two affs_lock_dir() calls would take the same mutex twice. No lockdep distinction would prevent this since the same lock instance is acquired twice. Address both by: 1. Adding affs_lock_subdir() that uses SINGLE_DEPTH_NESTING + 1, so lockdep can prove the nested parent->child acquisition is safe. 2. Rejecting the case where the parsed child inode coincides with the parent before taking the second lock, returning -EIO under the existing done_unlock path so all previously-acquired locks are released cleanly. Reported-by: Farhad Alemi Closes: https://lore.kernel.org/lkml/CA+0ovCiA7huMwMxvWgC8km2P+gJwd-jax+ACo=EbGrJ6FVp55A@mail.gmail.com/ Signed-off-by: Farhad Alemi Signed-off-by: David Sterba --- fs/affs/affs.h | 5 +++++ fs/affs/amigaffs.c | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/fs/affs/affs.h b/fs/affs/affs.h index d1c506c1f3105f..952c5b2059b14c 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -306,6 +306,11 @@ affs_lock_dir(struct inode *inode) mutex_lock_nested(&AFFS_I(inode)->i_hash_lock, SINGLE_DEPTH_NESTING); } static inline void +affs_lock_subdir(struct inode *inode) +{ + mutex_lock_nested(&AFFS_I(inode)->i_hash_lock, SINGLE_DEPTH_NESTING + 1); +} +static inline void affs_unlock_dir(struct inode *inode) { mutex_unlock(&AFFS_I(inode)->i_hash_lock); diff --git a/fs/affs/amigaffs.c b/fs/affs/amigaffs.c index 6cc0fc9a4cbf92..affa20d2b73d09 100644 --- a/fs/affs/amigaffs.c +++ b/fs/affs/amigaffs.c @@ -293,7 +293,11 @@ affs_remove_header(struct dentry *dentry) * i_hash_lock of the inode must only be * taken after some checks */ - affs_lock_dir(inode); + if (inode == dir) { + retval = -EIO; + goto done_unlock; + } + affs_lock_subdir(inode); retval = affs_empty_dir(inode); affs_unlock_dir(inode); if (retval) From 78ea2058abf3ee053c0bd9c1b9a77304cb4ae58b Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Thu, 11 Jun 2026 14:21:10 -0700 Subject: [PATCH 0416/2286] affs: validate blocks before freeing them affs_free_block() accepts block numbers from on-disk file block tables when truncating or unlinking files. The function currently rejects only block numbers greater than s_partition_size before subtracting s_reserved and using the result to index the in-memory bitmap descriptor array. That check still accepts reserved blocks and the one-past-end block at s_partition_size. A crafted image can therefore make the subtraction underflow or make the computed bitmap index equal to s_bmap_count, leading to an out-of-bounds access on s_bitmap before the bitmap block is read and updated. Use the existing AFFS block validity helper so freeing uses the same range as block reads and writes: s_reserved <= block < s_partition_size. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") CC: stable@vger.kernel.org Assisted-by: Codex:gpt-5.5 Signed-off-by: Kyle Zeng Signed-off-by: David Sterba --- fs/affs/bitmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/affs/bitmap.c b/fs/affs/bitmap.c index 40bc4ce6af4ac9..f013a2a94d4d40 100644 --- a/fs/affs/bitmap.c +++ b/fs/affs/bitmap.c @@ -46,7 +46,7 @@ affs_free_block(struct super_block *sb, u32 block) pr_debug("%s(%u)\n", __func__, block); - if (block > sbi->s_partition_size) + if (!affs_validblock(sb, block)) goto err_range; blk = block - sbi->s_reserved; From fd932734f43741b2ba78ca9c44f3b8d826f3d6b7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Sat, 15 Aug 2026 11:47:56 +0200 Subject: [PATCH 0417/2286] block: pass a maxlen argument to bio_iov_iter_get_pages Writes that require additional per-I/O allocations such as when using file system level protection information can be limited to a maximum size. Allow passing that to bio_iov_iter_get_pages. Signed-off-by: Christoph Hellwig Signed-off-by: Jens Axboe --- block/bio.c | 8 ++++++-- block/blk-map.c | 2 +- block/fops.c | 3 ++- fs/iomap/direct-io.c | 10 +++++----- include/linux/bio.h | 3 ++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/block/bio.c b/block/bio.c index 9654acfab113d0..3c0c423a31f7d6 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1282,6 +1282,7 @@ static inline bool bio_iov_bvec_aligned(const struct bio *bio, * bio_iov_iter_get_pages - add user or kernel pages to a bio * @bio: bio to add pages to * @iter: iov iterator describing the region to be added + * @maxlen: maximum size to consume from @iter * @mem_align_mask: the mask the source address and length must be aligned to, * 0 for no requirement * @len_align_mask: the mask to align the total size to, 0 for any length @@ -1302,7 +1303,8 @@ static inline bool bio_iov_bvec_aligned(const struct bio *bio, * is returned only if 0 pages could be pinned. */ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, - unsigned mem_align_mask, unsigned len_align_mask) + unsigned maxlen, unsigned mem_align_mask, + unsigned len_align_mask) { iov_iter_extraction_t flags = 0; @@ -1314,6 +1316,8 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, !bio_iov_bvec_aligned(bio, mem_align_mask)) return -EINVAL; + /* Truncate to the maximum size that the caller can handle */ + bio->bi_iter.bi_size = min(bio->bi_iter.bi_size, maxlen); iov_iter_advance(iter, bio->bi_iter.bi_size); return 0; } @@ -1327,7 +1331,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, ssize_t ret; ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec, - BIO_MAX_SIZE - bio->bi_iter.bi_size, + maxlen - bio->bi_iter.bi_size, &bio->bi_vcnt, bio->bi_max_vecs, mem_align_mask, flags); if (ret <= 0) { diff --git a/block/blk-map.c b/block/blk-map.c index 9cb9605d1f62da..81cba3af4e9c1d 100644 --- a/block/blk-map.c +++ b/block/blk-map.c @@ -274,7 +274,7 @@ static int bio_map_user_iov(struct request *rq, struct iov_iter *iter, * No alignment requirements on our part to support arbitrary * passthrough commands. */ - ret = bio_iov_iter_get_pages(bio, iter, 0, 0); + ret = bio_iov_iter_get_pages(bio, iter, BIO_MAX_SIZE, 0, 0); if (ret) goto out_put; ret = blk_rq_append_bio(rq, bio); diff --git a/block/fops.c b/block/fops.c index 2ce7c6c4714e42..a83df69b175aac 100644 --- a/block/fops.c +++ b/block/fops.c @@ -46,7 +46,8 @@ static bool blkdev_dio_invalid(struct block_device *bdev, struct kiocb *iocb, static inline int blkdev_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, struct block_device *bdev) { - return bio_iov_iter_get_pages(bio, iter, bdev_dma_alignment(bdev), + return bio_iov_iter_get_pages(bio, iter, BIO_MAX_SIZE, + bdev_dma_alignment(bdev), bdev_logical_block_size(bdev) - 1); } diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 8b4039d16ce89f..bc726d47b7dc19 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -359,8 +359,8 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, iomap_max_bio_size(&iter->iomap), alignment); else ret = bio_iov_iter_get_pages(bio, dio->submit.iter, - bdev_dma_alignment(bio->bi_bdev), - alignment - 1); + BIO_MAX_SIZE, bdev_dma_alignment(bio->bi_bdev), + alignment - 1); if (unlikely(ret)) goto out_put_bio; ret = bio->bi_iter.bi_size; @@ -1034,9 +1034,9 @@ ssize_t __iomap_dio_read_simple(struct kiocb *iocb, struct iov_iter *iter, bio->bi_iter.bi_sector = iomap_sector(&iomi->iomap, iomi->pos); bio->bi_ioprio = iocb->ki_ioprio; - ret = bio_iov_iter_get_pages(bio, iter, - bdev_dma_alignment(bio->bi_bdev), - alignment - 1); + ret = bio_iov_iter_get_pages(bio, iter, BIO_MAX_SIZE, + bdev_dma_alignment(bio->bi_bdev), + alignment - 1); if (unlikely(ret)) goto out_bio_put; diff --git a/include/linux/bio.h b/include/linux/bio.h index 592636ead3310f..bfa3c0e97b6f5e 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -517,7 +517,8 @@ int bdev_rw_virt(struct block_device *bdev, sector_t sector, void *data, size_t len, enum req_op op); int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, - unsigned mem_align_mask, unsigned len_align_mask); + unsigned maxlen, unsigned mem_align_mask, + unsigned len_align_mask); bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter); void __bio_release_pages(struct bio *bio, bool mark_dirty); From 3e756cc861e5a6c9ed428bb9918b4c5daaf42ee3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Sat, 15 Aug 2026 11:55:46 +0200 Subject: [PATCH 0418/2286] block: warn on too larger integrity allocations Catch cases where upper layer bugs create larger I/Os than the mempool would return even without dipping into the mempool. Signed-off-by: Christoph Hellwig Signed-off-by: Jens Axboe --- block/bio-integrity.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block/bio-integrity.c b/block/bio-integrity.c index b23e2434d80c2c..d3df726e0f0881 100644 --- a/block/bio-integrity.c +++ b/block/bio-integrity.c @@ -72,6 +72,7 @@ void bio_integrity_alloc_buf(struct bio *bio, gfp_t gfp, bool zero_buffer) unsigned int len = bio_integrity_bytes(bi, bio_sectors(bio)); void *buf; + WARN_ON_ONCE(len > BLK_INTEGRITY_MAX_SIZE); buf = kmalloc(len, gfp | __GFP_NOWARN | (zero_buffer ? __GFP_ZERO : 0)); if (unlikely(!buf)) { struct page *page; From c7715a942e9daffe566bb87274714c170d06e256 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:53 +0300 Subject: [PATCH 0419/2286] iomap: respect maximum I/O size in iomap_dio_bio_iter_one Respect the maximum I/O size set for PI-enabled I/O in iomap_dio_bio_iter_one, otherwise bio_integrity_alloc_buf could under-allocate the integrity buffer when the initial kmalloc fails. Currently this should not be triggered as file systems that limit the size for PI always use bounce buffering, but this will change soon. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-5-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/direct-io.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index bc726d47b7dc19..91f5b67718a55f 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -336,6 +336,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, struct iomap_dio *dio, loff_t pos, unsigned int alignment, blk_opf_t op) { + unsigned int maxsize = iomap_max_bio_size(&iter->iomap); unsigned int nr_vecs; struct bio *bio; ssize_t ret; @@ -353,14 +354,12 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, bio->bi_private = dio; bio->bi_end_io = iomap_dio_bio_end_io; - if (dio->flags & IOMAP_DIO_BOUNCE) - ret = bio_iov_iter_bounce(bio, dio->submit.iter, - iomap_max_bio_size(&iter->iomap), alignment); + ret = bio_iov_iter_bounce(bio, dio->submit.iter, maxsize, + alignment); else - ret = bio_iov_iter_get_pages(bio, dio->submit.iter, - BIO_MAX_SIZE, bdev_dma_alignment(bio->bi_bdev), - alignment - 1); + ret = bio_iov_iter_get_pages(bio, dio->submit.iter, maxsize, + bdev_dma_alignment(bio->bi_bdev), alignment - 1); if (unlikely(ret)) goto out_put_bio; ret = bio->bi_iter.bi_size; From a552b1c7388929f5d7fc16b249c25366550197ea Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:54 +0300 Subject: [PATCH 0420/2286] iomap: add a iomap_ioend_flags helper Add a helper to initialize the ioend flags to the values that can be directly derived from the iomap. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-6-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/ioend.c | 10 +++------- include/linux/iomap.h | 12 ++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 7bbbb417f9152c..2d77c11e3c184f 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -215,7 +215,7 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, { struct iomap_ioend *ioend = wpc->wb_ctx; size_t poff = offset_in_folio(folio, pos); - unsigned int ioend_flags = 0; + unsigned int ioend_flags = iomap_ioend_flags(&wpc->iomap); unsigned int map_len = min_t(u64, dirty_len, wpc->iomap.offset + wpc->iomap.length - pos); int error; @@ -225,20 +225,16 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, WARN_ON_ONCE(!folio->private && map_len < dirty_len); switch (wpc->iomap.type) { + case IOMAP_HOLE: + return map_len; case IOMAP_UNWRITTEN: - ioend_flags |= IOMAP_IOEND_UNWRITTEN; - break; case IOMAP_MAPPED: break; - case IOMAP_HOLE: - return map_len; default: WARN_ON_ONCE(1); return -EIO; } - if (wpc->iomap.flags & IOMAP_F_SHARED) - ioend_flags |= IOMAP_IOEND_SHARED; if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY)) ioend_flags |= IOMAP_IOEND_BOUNDARY; diff --git a/include/linux/iomap.h b/include/linux/iomap.h index bc7ae6327dbf87..8e09b2d4eda014 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -491,6 +491,18 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno, #define IOMAP_IOEND_NOMERGE_FLAGS \ (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT) +/* ioend flags directly implied by iomap flags */ +static inline u16 iomap_ioend_flags(const struct iomap *iomap) +{ + unsigned int flags = 0; + + if (iomap->type == IOMAP_UNWRITTEN) + flags |= IOMAP_IOEND_UNWRITTEN; + if (iomap->flags & IOMAP_F_SHARED) + flags |= IOMAP_IOEND_SHARED; + return flags; +} + /* * Structure for writeback I/O completions. * From d457743d00559e63dead7bbe8066802c5361471f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:55 +0300 Subject: [PATCH 0421/2286] iomap: add a IOMAP_IOEND_INTEGRITY flag This will make it easier to share code for the various submit callback provided by the file system to iomap. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-7-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/ioend.c | 2 +- fs/xfs/xfs_aops.c | 5 +++-- fs/xfs/xfs_file.c | 5 +++-- include/linux/iomap.h | 12 +++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 2d77c11e3c184f..573fa89c163216 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -149,7 +149,7 @@ int iomap_ioend_writeback_submit(struct iomap_writepage_ctx *wpc, int error) return error; } - if (wpc->iomap.flags & IOMAP_F_INTEGRITY) + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) fs_bio_integrity_generate(&ioend->io_bio); submit_bio(&ioend->io_bio); return 0; diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 8b6119776fb3ec..4e862406eee905 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -498,7 +498,7 @@ xfs_zoned_writeback_submit( bio_endio(&ioend->io_bio); return error; } - if (wpc->iomap.flags & IOMAP_F_INTEGRITY) + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) fs_bio_integrity_generate(&ioend->io_bio); xfs_zone_alloc_and_submit(ioend, &XFS_ZWPC(wpc)->open_zone); return 0; @@ -588,7 +588,8 @@ xfs_bio_submit_read( struct bio *bio = ctx->read_ctx; /* defer read completions to the ioend workqueue */ - iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, 0); + iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, + iomap_ioend_flags(&iter->iomap)); iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio); } diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 426a67b813a7fc..4d7a284fcff96f 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -241,7 +241,8 @@ xfs_dio_read_bounce_submit_io( struct bio *bio, loff_t file_offset) { - iomap_init_ioend(iter->inode, bio, file_offset, IOMAP_IOEND_DIRECT); + iomap_init_ioend(iter->inode, bio, file_offset, + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); bio->bi_end_io = xfs_end_bio; submit_bio(bio); } @@ -732,7 +733,7 @@ xfs_dio_zoned_submit_io( bio->bi_end_io = xfs_end_bio; ioend = iomap_init_ioend(iter->inode, bio, file_offset, - IOMAP_IOEND_DIRECT); + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); xfs_zone_alloc_and_submit(ioend, &ac->open_zone); } diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 8e09b2d4eda014..1cc9a35fd5ccbf 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -483,13 +483,20 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno, #define IOMAP_IOEND_BOUNDARY (1U << 2) /* is direct I/O */ #define IOMAP_IOEND_DIRECT (1U << 3) +/* generate integrity (PI) information */ +#ifdef CONFIG_BLK_DEV_INTEGRITY +#define IOMAP_IOEND_INTEGRITY (1U << 4) +#else +#define IOMAP_IOEND_INTEGRITY 0 +#endif /* CONFIG_BLK_DEV_INTEGRITY */ /* * Flags that if set on either ioend prevent the merge of two ioends. * (IOMAP_IOEND_BOUNDARY also prevents merges, but only one-way) */ #define IOMAP_IOEND_NOMERGE_FLAGS \ - (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT) + (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT | \ + IOMAP_IOEND_INTEGRITY) /* ioend flags directly implied by iomap flags */ static inline u16 iomap_ioend_flags(const struct iomap *iomap) @@ -500,6 +507,9 @@ static inline u16 iomap_ioend_flags(const struct iomap *iomap) flags |= IOMAP_IOEND_UNWRITTEN; if (iomap->flags & IOMAP_F_SHARED) flags |= IOMAP_IOEND_SHARED; + if (iomap->flags & IOMAP_F_INTEGRITY) + flags |= IOMAP_IOEND_INTEGRITY; + return flags; } From d3f30d94f1d9a401e6df45c612f73a9a0daebca7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:56 +0300 Subject: [PATCH 0422/2286] iomap,xfs: move T10 PI handling for direct I/O into ->submit_io Currently the buffered I/O code defers PI handling into the submit handlers, while direct I/O does it in common code. Move the direct I/O side into the file system callbacks or their generic implementations to be consistent, and to allow file systems to shared helpers for submission of buffered and direct bios. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-8-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/direct-io.c | 22 ++++++++++++---------- fs/xfs/xfs_file.c | 13 ++++++++++--- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 91f5b67718a55f..4154717a09de90 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -76,10 +76,19 @@ static void iomap_dio_submit_bio(const struct iomap_iter *iter, if (dio->dops && dio->dops->submit_io) { dio->dops->submit_io(iter, bio, pos); - } else { - WARN_ON_ONCE(iter->iomap.flags & IOMAP_F_ANON_WRITE); - blk_crypto_submit_bio(bio); + return; + } + + WARN_ON_ONCE(iter->iomap.flags & IOMAP_F_ANON_WRITE); + + if (iter->iomap.flags & IOMAP_F_INTEGRITY) { + if (dio->flags & IOMAP_DIO_WRITE) + fs_bio_integrity_generate(bio); + else + fs_bio_integrity_alloc(bio); } + + blk_crypto_submit_bio(bio); } static inline enum fserror_type iomap_dio_err_type(const struct iomap_dio *dio) @@ -373,13 +382,6 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, goto out_bio_release_pages; } - if (iter->iomap.flags & IOMAP_F_INTEGRITY) { - if (dio->flags & IOMAP_DIO_WRITE) - fs_bio_integrity_generate(bio); - else - fs_bio_integrity_alloc(bio); - } - if (dio->flags & IOMAP_DIO_WRITE) task_io_account_write(ret); else if ((dio->flags & IOMAP_DIO_USER_BACKED) && diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 4d7a284fcff96f..acbfd55f56a3f9 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -37,6 +37,7 @@ #include #include #include +#include static const struct vm_operations_struct xfs_file_vm_ops; @@ -241,8 +242,12 @@ xfs_dio_read_bounce_submit_io( struct bio *bio, loff_t file_offset) { - iomap_init_ioend(iter->inode, bio, file_offset, - iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); + struct iomap_ioend *ioend; + + ioend = iomap_init_ioend(iter->inode, bio, file_offset, + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) + fs_bio_integrity_alloc(bio); bio->bi_end_io = xfs_end_bio; submit_bio(bio); } @@ -733,7 +738,9 @@ xfs_dio_zoned_submit_io( bio->bi_end_io = xfs_end_bio; ioend = iomap_init_ioend(iter->inode, bio, file_offset, - iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); + iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) + fs_bio_integrity_generate(bio); xfs_zone_alloc_and_submit(ioend, &ac->open_zone); } From 793875070ab18ce9aafd524160d8cd77ccf385d6 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:57 +0300 Subject: [PATCH 0423/2286] xfs: move PI generation into xfs_submit_zoned_bio Keep the code in one place. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-9-hch@lst.de Signed-off-by: Jens Axboe --- fs/xfs/xfs_aops.c | 4 +--- fs/xfs/xfs_file.c | 2 -- fs/xfs/xfs_zone_alloc.c | 4 ++++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index 4e862406eee905..d2cdb7cffa6b5c 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -23,7 +23,6 @@ #include "xfs_ioend.h" #include "xfs_zone_alloc.h" #include "xfs_rtgroup.h" -#include struct xfs_writepage_ctx { struct iomap_writepage_ctx ctx; @@ -498,8 +497,7 @@ xfs_zoned_writeback_submit( bio_endio(&ioend->io_bio); return error; } - if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) - fs_bio_integrity_generate(&ioend->io_bio); + xfs_zone_alloc_and_submit(ioend, &XFS_ZWPC(wpc)->open_zone); return 0; } diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index acbfd55f56a3f9..0e029db2e85b45 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -739,8 +739,6 @@ xfs_dio_zoned_submit_io( bio->bi_end_io = xfs_end_bio; ioend = iomap_init_ioend(iter->inode, bio, file_offset, iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); - if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) - fs_bio_integrity_generate(bio); xfs_zone_alloc_and_submit(ioend, &ac->open_zone); } diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index bdbb60cc5d5b26..bede798596716a 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -26,6 +26,7 @@ #include "xfs_zones.h" #include "xfs_trace.h" #include "xfs_mru_cache.h" +#include static void xfs_open_zone_free_rcu( @@ -909,6 +910,9 @@ xfs_zone_alloc_and_submit( if (xfs_is_shutdown(mp)) goto out_error; + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) + fs_bio_integrity_generate(&ioend->io_bio); + /* * If we don't have a locally cached zone in this write context, see if * the inode is still associated with a zone and use that if so. From 7de79a5ffe338b959be8cd862292596ac9f7848b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:58 +0300 Subject: [PATCH 0424/2286] block,iomap: fix protection information verification with initial bvec offset When reconstructing a bvec_iter from an ioend for protection information verification, iomap currently ignores the offset into the initial bio_vec. This can't happen for buffered I/O or direct I/O to user addresses, but is exercised by splice on O_DIRECT file descriptors or when using the loop driver in direct I/O mode. Fortunately the only file system PI user (XFS) currently always bounce buffers, so this can't actually be triggered yet. But we'll want to make the bounce buffering conditional soon, for which this needs to be fixed. Store the initial offset in struct iomap_ioend, and pass a pre-constructed bvec_iter to fs_bio_integrity_verify. For the synchronous read case the fix is even simpler as this path can simply stash away the original bvec_iter. Fixes: 0bde8a12b554 ("block: add fs_bio_integrity helpers") Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-10-hch@lst.de Signed-off-by: Jens Axboe --- block/bio-integrity-fs.c | 13 +++++-------- fs/iomap/bio.c | 4 +++- fs/iomap/ioend.c | 14 ++++++++++---- include/linux/bio-integrity.h | 3 +-- include/linux/iomap.h | 8 ++++++++ 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/block/bio-integrity-fs.c b/block/bio-integrity-fs.c index 4f67b34bf28905..c8e91ada8ca61c 100644 --- a/block/bio-integrity-fs.c +++ b/block/bio-integrity-fs.c @@ -54,14 +54,10 @@ void fs_bio_integrity_generate(struct bio *bio) } EXPORT_SYMBOL_GPL(fs_bio_integrity_generate); -int fs_bio_integrity_verify(struct bio *bio, sector_t sector, unsigned int size) +int fs_bio_integrity_verify(struct bio *bio, struct bvec_iter *data_iter) { struct blk_integrity *bi = blk_get_integrity(bio->bi_bdev->bd_disk); struct bio_integrity_payload *bip = bio_integrity(bio); - struct bvec_iter data_iter = { - .bi_sector = sector, - .bi_size = size, - }; if (!bip || !(bip->bip_flags & BIP_CHECK_FLAGS)) return 0; @@ -73,9 +69,10 @@ int fs_bio_integrity_verify(struct bio *bio, sector_t sector, unsigned int size) * bio. Requires the submitter to remember the sector and the size. */ memset(&bip->bip_iter, 0, sizeof(bip->bip_iter)); - bip->bip_iter.bi_sector = sector; - bip->bip_iter.bi_size = bio_integrity_bytes(bi, size >> SECTOR_SHIFT); - return blk_status_to_errno(bio_integrity_verify(bio, &data_iter)); + bip->bip_iter.bi_sector = data_iter->bi_sector; + bip->bip_iter.bi_size = + bio_integrity_bytes(bi, data_iter->bi_size >> SECTOR_SHIFT); + return blk_status_to_errno(bio_integrity_verify(bio, data_iter)); } static int __init fs_bio_integrity_init(void) diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c index 48100c614431c0..d46c2f8ea18ce0 100644 --- a/fs/iomap/bio.c +++ b/fs/iomap/bio.c @@ -169,6 +169,7 @@ int iomap_bio_read_folio_range_sync(const struct iomap_iter *iter, { const struct iomap *srcmap = iomap_iter_srcmap(iter); sector_t sector = iomap_sector(srcmap, pos); + struct bvec_iter saved_iter; struct bio_vec bvec; struct bio bio; int error; @@ -178,10 +179,11 @@ int iomap_bio_read_folio_range_sync(const struct iomap_iter *iter, bio_add_folio_nofail(&bio, folio, len, offset_in_folio(folio, pos)); if (srcmap->flags & IOMAP_F_INTEGRITY) fs_bio_integrity_alloc(&bio); + saved_iter = bio.bi_iter; error = submit_bio_wait(&bio); if (bio_integrity(&bio)) { if (!error) - error = fs_bio_integrity_verify(&bio, sector, len); + error = fs_bio_integrity_verify(&bio, &saved_iter); fs_bio_integrity_free(&bio); } bio_uninit(&bio); diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 573fa89c163216..332dbfb2230fb7 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -25,6 +25,7 @@ struct iomap_ioend *iomap_init_ioend(struct inode *inode, ioend->io_parent = NULL; INIT_LIST_HEAD(&ioend->io_list); ioend->io_flags = ioend_flags; + ioend->io_bvec_offset = bio->bi_iter.bi_offset; ioend->io_inode = inode; ioend->io_offset = file_offset; ioend->io_size = bio->bi_iter.bi_size; @@ -308,6 +309,13 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, } EXPORT_SYMBOL_GPL(iomap_add_to_ioend); +static int iomap_ioend_integrity_verify(struct iomap_ioend *ioend) +{ + struct bvec_iter data_iter = BVEC_ITER_IOEND(ioend); + + return fs_bio_integrity_verify(&ioend->io_bio, &data_iter); +} + static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error) { if (ioend->io_parent) { @@ -325,10 +333,8 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error) if (!ioend->io_error && bio_integrity(&ioend->io_bio) && - bio_op(&ioend->io_bio) == REQ_OP_READ) { - ioend->io_error = fs_bio_integrity_verify(&ioend->io_bio, - ioend->io_sector, ioend->io_size); - } + bio_op(&ioend->io_bio) == REQ_OP_READ) + ioend->io_error = iomap_ioend_integrity_verify(ioend); if (ioend->io_flags & IOMAP_IOEND_DIRECT) return iomap_finish_ioend_direct(ioend); diff --git a/include/linux/bio-integrity.h b/include/linux/bio-integrity.h index 0ea2a8bf7efb73..a954c97be0b336 100644 --- a/include/linux/bio-integrity.h +++ b/include/linux/bio-integrity.h @@ -151,7 +151,6 @@ void bio_integrity_setup_default(struct bio *bio); unsigned int fs_bio_integrity_alloc(struct bio *bio); void fs_bio_integrity_free(struct bio *bio); void fs_bio_integrity_generate(struct bio *bio); -int fs_bio_integrity_verify(struct bio *bio, sector_t sector, - unsigned int size); +int fs_bio_integrity_verify(struct bio *bio, struct bvec_iter *data_iter); #endif /* _LINUX_BIO_INTEGRITY_H */ diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 1cc9a35fd5ccbf..bffdc217ad850b 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -522,6 +522,7 @@ static inline u16 iomap_ioend_flags(const struct iomap *iomap) struct iomap_ioend { struct list_head io_list; /* next ioend in chain */ u16 io_flags; /* IOMAP_IOEND_* */ + u32 io_bvec_offset; /* offset into first bvec */ struct inode *io_inode; /* file being written to */ size_t io_size; /* size of the extent */ atomic_t io_remaining; /* completetion defer count */ @@ -539,6 +540,13 @@ static inline struct iomap_ioend *iomap_ioend_from_bio(struct bio *bio) return container_of(bio, struct iomap_ioend, io_bio); } +#define BVEC_ITER_IOEND(_ioend) \ +{ \ + .bi_sector = (_ioend)->io_sector, \ + .bi_size = (_ioend)->io_size, \ + .bi_offset = (_ioend)->io_bvec_offset, \ +} + struct iomap_writeback_ops { /* * Performs writeback on the passed in range From df7ed21685d5e258ca5c9e62a9bfad143317b7d0 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:08:59 +0300 Subject: [PATCH 0425/2286] iomap: better read bounce buffering support Add helpers to bounce buffer an upper bio into one or more lower bios using bounce buffers, and to copy the data back on completion. Compared to the existing IOMAP_DIO_BOUNCE support for read bios, this has two advantages: by removing the special bounce bio_vec it allows to the full and "round" size of a single bio, i.e., 1MiB when using 4k pages. This is important for good performance on HDD. Additionally it allows to bounce buffer a bio from completion conext, and thus implement a "lazy" bounce buffering scheme, where the data is only read into a bounce buffer after an initial checksum validation failure, thus avoiding the bounce buffering I/O for most I/O. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-11-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/ioend.c | 90 +++++++++++++++++++++++++++++++++++++++++++ include/linux/iomap.h | 5 +++ 2 files changed, 95 insertions(+) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 332dbfb2230fb7..ba97eec4ee2c87 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -514,6 +514,96 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend, } EXPORT_SYMBOL_GPL(iomap_split_ioend); +void iomap_bounce_read(struct iomap_ioend *orig_ioend, unsigned int minsize, + void (*submit_ioend)(struct iomap_ioend *ioend)) +{ + struct inode *inode = orig_ioend->io_inode; + struct bio *orig_bio = &orig_ioend->io_bio; + loff_t file_offset = orig_ioend->io_offset; + sector_t sector = orig_ioend->io_sector; + size_t total_len = round_up(orig_ioend->io_size, minsize); + + WARN_ON_ONCE(!(orig_ioend->io_flags & IOMAP_IOEND_DIRECT)); + + /* We can't poll a bio that is not passed on to hardware */ + orig_bio->bi_opf &= ~REQ_POLLED; + + do { + struct iomap_ioend *ioend; + struct bio *bio; + int error; + + bio = bio_alloc_bioset(orig_bio->bi_bdev, + min(total_len / minsize, BIO_MAX_VECS), + orig_bio->bi_opf, GFP_KERNEL, + &iomap_ioend_split_bioset); + error = bio_alloc_bounce_folios(bio, total_len, minsize); + if (error) { + bio_put(bio); + orig_bio->bi_status = errno_to_blk_status(error); + break; + } + bio->bi_ioprio = orig_bio->bi_ioprio; + bio->bi_write_hint = orig_bio->bi_write_hint; + bio->bi_write_stream = orig_bio->bi_write_stream; + bio->bi_iter.bi_sector = sector; + + ioend = iomap_init_ioend(inode, bio, file_offset, + orig_ioend->io_flags); + + total_len -= bio->bi_iter.bi_size; + file_offset += bio->bi_iter.bi_size; + sector += (bio->bi_iter.bi_size >> SECTOR_SHIFT); + + bio->bi_private = orig_bio; + bio_inc_remaining(orig_bio); + submit_ioend(ioend); + } while (total_len > 0); + + bio_endio(&orig_ioend->io_bio); +} +EXPORT_SYMBOL_GPL(iomap_bounce_read); + +static void iomap_ioend_unbounce(struct iomap_ioend *orig_ioend, + struct iomap_ioend *ioend) +{ + struct bio *orig_bio = &orig_ioend->io_bio; + struct iov_iter to; + struct bio_vec *bv; + int i; + + iov_iter_bvec(&to, ITER_DEST, orig_bio->bi_io_vec, orig_bio->bi_vcnt, + orig_ioend->io_size); + to.iov_offset = orig_ioend->io_bvec_offset; + + if (ioend->io_offset != orig_ioend->io_offset) { + WARN_ON_ONCE(ioend->io_offset < orig_ioend->io_offset); + iov_iter_advance(&to, ioend->io_offset - orig_ioend->io_offset); + } + + /* copying to pinned pages should always work */ + bio_for_each_bvec_all(bv, &ioend->io_bio, i) + WARN_ON_ONCE(copy_to_iter(bvec_virt(bv), bv->bv_len, &to) != + bv->bv_len); +} + +void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio, + int error) +{ + if (error) + orig_bio->bi_status = errno_to_blk_status(error); + else + iomap_ioend_unbounce(iomap_ioend_from_bio(orig_bio), ioend); + + bio_free_folios(&ioend->io_bio); + if (bio_integrity(&ioend->io_bio)) + fs_bio_integrity_free(&ioend->io_bio); + bio_put(&ioend->io_bio); + + bio_endio(orig_bio); +} +EXPORT_SYMBOL_GPL(iomap_bounce_read_end_io); + static int __init iomap_ioend_init(void) { const unsigned int nr_mempool_entries = 4 * (PAGE_SIZE / SECTOR_SIZE); diff --git a/include/linux/iomap.h b/include/linux/iomap.h index bffdc217ad850b..fbe051f0b0325b 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -607,6 +607,11 @@ void iomap_finish_folio_write(struct inode *inode, struct folio *folio, int iomap_writeback_folio(struct iomap_writepage_ctx *wpc, struct folio *folio); int iomap_writepages(struct iomap_writepage_ctx *wpc); +void iomap_bounce_read(struct iomap_ioend *orig_ioend, unsigned int minsize, + void (*submit_ioend)(struct iomap_ioend *ioend)); +void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio, + int error); + struct iomap_read_folio_ctx { const struct iomap_read_ops *ops; struct folio *cur_folio; From b69e24b7507c8477a2cb3a37c2922fb938e49917 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:00 +0300 Subject: [PATCH 0426/2286] xfs: use BIO_COMPLETE_IN_TASK for bounce buffered read I/Os Stop using the xfs per-inode work struct for completing read bios, as unlike writes we don't want to serialize reads on a single inode as there is no exclusive resource contention for them. Factor the code for kicking off a read that needs and ioend and the task context completion into a single helper so that it is split off the xfs_end_bio machinery, which is not only used for writes. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-12-hch@lst.de Signed-off-by: Jens Axboe --- fs/xfs/xfs_aops.c | 10 ++++------ fs/xfs/xfs_file.c | 8 +------- fs/xfs/xfs_ioend.c | 32 +++++++++++++++++++++++++++----- fs/xfs/xfs_ioend.h | 2 ++ 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c index d2cdb7cffa6b5c..c30e688cfc9f6e 100644 --- a/fs/xfs/xfs_aops.c +++ b/fs/xfs/xfs_aops.c @@ -583,12 +583,10 @@ xfs_bio_submit_read( const struct iomap_iter *iter, struct iomap_read_folio_ctx *ctx) { - struct bio *bio = ctx->read_ctx; - - /* defer read completions to the ioend workqueue */ - iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, - iomap_ioend_flags(&iter->iomap)); - iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio); + xfs_ioend_submit_read(iter->inode, ctx->read_ctx, + ctx->read_ctx_file_offset, + iomap_ioend_flags(&iter->iomap)); + ctx->read_ctx = NULL; } static const struct iomap_read_ops xfs_iomap_read_ops = { diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 0e029db2e85b45..077744b3f6c3f5 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -242,14 +242,8 @@ xfs_dio_read_bounce_submit_io( struct bio *bio, loff_t file_offset) { - struct iomap_ioend *ioend; - - ioend = iomap_init_ioend(iter->inode, bio, file_offset, + xfs_ioend_submit_read(iter->inode, bio, file_offset, iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT); - if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) - fs_bio_integrity_alloc(bio); - bio->bi_end_io = xfs_end_bio; - submit_bio(bio); } static const struct iomap_dio_ops xfs_dio_read_bounce_ops = { diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index 40695d18dac081..37a3ae8066e9df 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -16,6 +16,32 @@ #include "xfs_reflink.h" #include "xfs_zone_alloc.h" #include "xfs_ioend.h" +#include + +static void +xfs_end_io_read( + struct bio *bio) +{ + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + int error = blk_status_to_errno(bio->bi_status); + + iomap_finish_ioends(ioend, error); +} + +void +xfs_ioend_submit_read( + struct inode *inode, + struct bio *bio, + loff_t file_offset, + u16 ioend_flags) +{ + iomap_init_ioend(inode, bio, file_offset, ioend_flags); + if (ioend_flags & IOMAP_IOEND_INTEGRITY) + fs_bio_integrity_alloc(bio); + bio->bi_end_io = xfs_end_io_read; + bio_set_flag(bio, BIO_COMPLETE_IN_TASK); + submit_bio(bio); +} static void xfs_ioend_put_open_zones( @@ -148,11 +174,7 @@ xfs_end_io( io_list))) { list_del_init(&ioend->io_list); iomap_ioend_try_merge(ioend, &tmp); - if (bio_op(&ioend->io_bio) == REQ_OP_READ) - iomap_finish_ioends(ioend, - blk_status_to_errno(ioend->io_bio.bi_status)); - else - xfs_end_ioend_write(ioend); + xfs_end_ioend_write(ioend); cond_resched(); } } diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h index 525865767fcac9..7c2a1ea3e6eddc 100644 --- a/fs/xfs/xfs_ioend.h +++ b/fs/xfs/xfs_ioend.h @@ -12,5 +12,7 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend) } void xfs_end_bio(struct bio *bio); +void xfs_ioend_submit_read(struct inode *inode, struct bio *bio, + loff_t file_offset, u16 ioend_flags); #endif /* __XFS_IOEND_H */ From 85fa731672d1c62f8c4254a6a47f1960c0fd5b5e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:01 +0300 Subject: [PATCH 0427/2286] iomap,xfs: move integrity verification to the file system Integrity support in file systems already requires file system-specific completion handling because it must be run in process context. Move the actual verification to the file system so that it can better handle errors in file system specific ways, and to support lazy bounce buffering. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-13-hch@lst.de Signed-off-by: Jens Axboe --- fs/iomap/ioend.c | 10 ++++------ fs/xfs/xfs_ioend.c | 3 +++ include/linux/iomap.h | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index ba97eec4ee2c87..bbebecc3167083 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -309,12 +309,15 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, } EXPORT_SYMBOL_GPL(iomap_add_to_ioend); -static int iomap_ioend_integrity_verify(struct iomap_ioend *ioend) +#ifdef CONFIG_BLK_DEV_INTEGRITY +int iomap_ioend_integrity_verify(struct iomap_ioend *ioend) { struct bvec_iter data_iter = BVEC_ITER_IOEND(ioend); return fs_bio_integrity_verify(&ioend->io_bio, &data_iter); } +EXPORT_SYMBOL_GPL(iomap_ioend_integrity_verify); +#endif /* CONFIG_BLK_DEV_INTEGRITY */ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error) { @@ -331,11 +334,6 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error) if (!atomic_dec_and_test(&ioend->io_remaining)) return 0; - if (!ioend->io_error && - bio_integrity(&ioend->io_bio) && - bio_op(&ioend->io_bio) == REQ_OP_READ) - ioend->io_error = iomap_ioend_integrity_verify(ioend); - if (ioend->io_flags & IOMAP_IOEND_DIRECT) return iomap_finish_ioend_direct(ioend); if (bio_op(&ioend->io_bio) == REQ_OP_READ) diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index 37a3ae8066e9df..a095cf2178635f 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -25,6 +25,9 @@ xfs_end_io_read( struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); int error = blk_status_to_errno(bio->bi_status); + if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) + error = iomap_ioend_integrity_verify(ioend); + iomap_finish_ioends(ioend, error); } diff --git a/include/linux/iomap.h b/include/linux/iomap.h index fbe051f0b0325b..59718f73c15a03 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -595,6 +595,7 @@ void iomap_finish_ioends(struct iomap_ioend *ioend, int error); void iomap_ioend_try_merge(struct iomap_ioend *ioend, struct list_head *more_ioends); void iomap_sort_ioends(struct list_head *ioend_list); +int iomap_ioend_integrity_verify(struct iomap_ioend *ioend); ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, loff_t pos, loff_t end_pos, unsigned int dirty_len); int iomap_ioend_writeback_submit(struct iomap_writepage_ctx *wpc, int error); From a9a7b9e8686da4aba9cf8e9c81097401860107e4 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:02 +0300 Subject: [PATCH 0428/2286] xfs: add support for lazy direct read bounce buffering Currently direct I/O reads always bounce buffer the I/O to deal with the case where userspace is modifying the buffer in-flight while reading data into it. This is a very expensive countermeasure for something no sane application should do, so try to avoid it by reading without a bounce buffer first, and retrying the read on a checksum failure. This avoids the cost of bounce buffering for sanely behave applications. For the rare case of an application regularly modifying in-flight buffers, allow forcing the always bounce buffer behavior through sysfs. And now that we have that knob, allow disabling read-side bounce buffering entirely for those who live fast and dangerous. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-14-hch@lst.de Signed-off-by: Jens Axboe --- fs/xfs/xfs_file.c | 3 +- fs/xfs/xfs_ioend.c | 103 +++++++++++++++++++++++++++++++++++++++++++-- fs/xfs/xfs_mount.h | 8 ++++ fs/xfs/xfs_super.c | 1 + fs/xfs/xfs_sysfs.c | 78 +++++++++++++++++++++++++++++++++- fs/xfs/xfs_trace.h | 1 + 6 files changed, 187 insertions(+), 7 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 077744b3f6c3f5..6f25879b6510a4 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -271,8 +271,7 @@ xfs_file_dio_read( return ret; if (mapping_stable_writes(iocb->ki_filp->f_mapping)) { ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, - &xfs_dio_read_bounce_ops, IOMAP_DIO_BOUNCE, - NULL, 0); + &xfs_dio_read_bounce_ops, 0, NULL, 0); } else { ret = iomap_dio_read_simple(iocb, to, xfs_read_iomap_begin); if (ret == -ENOTBLK) diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index a095cf2178635f..4fba8558d8d246 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2016-2025 Christoph Hellwig. + * Copyright (c) 2016-2026 Christoph Hellwig. * All Rights Reserved. */ #include "xfs_platform.h" @@ -18,15 +18,101 @@ #include "xfs_ioend.h" #include +static void +xfs_dio_bounce_end_io( + struct bio *bio) +{ + struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + int error = blk_status_to_errno(bio->bi_status); + struct bio *orig_bio = bio->bi_private; + + if ((ioend->io_flags & IOMAP_IOEND_INTEGRITY) && !bio->bi_status) + error = iomap_ioend_integrity_verify(ioend); + iomap_bounce_read_end_io(ioend, orig_bio, error); +} + +static void +xfs_bounce_submit_ioend( + struct iomap_ioend *ioend) +{ + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY) + fs_bio_integrity_alloc(&ioend->io_bio); + ioend->io_bio.bi_end_io = xfs_dio_bounce_end_io; + bio_set_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK); + submit_bio(&ioend->io_bio); +} + +static void +xfs_end_bio_bounced( + struct bio *bio) +{ + /* + * Just complete the original ioends as all verification is done by the + * end_io handlers for the clone bio(s). + */ + iomap_finish_ioends(iomap_ioend_from_bio(bio), + blk_status_to_errno(bio->bi_status)); +} + +static void +xfs_read_bounce_and_resubmit( + struct iomap_ioend *ioend) +{ + struct bio *bio = &ioend->io_bio; + struct xfs_inode *ip = XFS_I(ioend->io_inode); + unsigned int nofs_flag = memalloc_nofs_save(); + + trace_xfs_bounce_reread(ip, ioend->io_offset, ioend->io_size); + + /* + * Free the bio integrity data for the original bio, as we'll allocate + * a new one for each sub-I/O, which could deadlock if we keep the + * integrity data for the original bio around. + */ + if (bio_integrity(bio)) + fs_bio_integrity_free(bio); + + /* + * Resubmit the bio through the iomap bounce machinery. The original + * bio itself is not resubmitted to the block layer, but just used to + * track I/O completion of the cloned bios. + */ + bio_prepare_reissue(bio, xfs_inode_buftarg(ip)->bt_bdev); + bio->bi_iter = (struct bvec_iter) { + .bi_sector = ioend->io_sector, + .bi_size = ioend->io_size, + .bi_offset = ioend->io_bvec_offset, + }; + bio->bi_end_io = xfs_end_bio_bounced; + iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev), + xfs_bounce_submit_ioend); + memalloc_nofs_restore(nofs_flag); +} + static void xfs_end_io_read( struct bio *bio) { struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_mount *mp = ip->i_mount; int error = blk_status_to_errno(bio->bi_status); - if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) + if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) { error = iomap_ioend_integrity_verify(ioend); + if ((ioend->io_flags & IOMAP_IOEND_DIRECT) && + READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_LAZY) { + /* + * We only really need to retry for guard tag errors, + * but right now we can't distinguish them from other + * (i.e, reftag) errors. + */ + if (error) { + xfs_read_bounce_and_resubmit(ioend); + return; + } + } + } iomap_finish_ioends(ioend, error); } @@ -38,7 +124,18 @@ xfs_ioend_submit_read( loff_t file_offset, u16 ioend_flags) { - iomap_init_ioend(inode, bio, file_offset, ioend_flags); + struct xfs_inode *ip = XFS_I(inode); + struct xfs_mount *mp = ip->i_mount; + struct iomap_ioend *ioend; + + ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags); + if ((ioend_flags & IOMAP_IOEND_DIRECT) && + READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) { + iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev), + xfs_bounce_submit_ioend); + return; + } + if (ioend_flags & IOMAP_IOEND_INTEGRITY) fs_bio_integrity_alloc(bio); bio->bi_end_io = xfs_end_io_read; diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 216a38a354e7c2..894ff2f4ecbd9a 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -142,6 +142,12 @@ struct xfs_freecounter { uint64_t res_saved; }; +enum xfs_read_bounce { + XFS_READ_BOUNCE_NEVER, + XFS_READ_BOUNCE_ALWAYS, + XFS_READ_BOUNCE_LAZY, +}; + /* * The struct xfsmount layout is optimised to separate read-mostly variables * from variables that are frequently modified. We put the read-mostly variables @@ -177,6 +183,7 @@ typedef struct xfs_mount { struct workqueue_struct *m_sync_workqueue; struct workqueue_struct *m_blockgc_wq; struct workqueue_struct *m_inodegc_wq; + enum xfs_read_bounce m_read_bounce; int m_bsize; /* fs logical block size */ uint8_t m_blkbit_log; /* blocklog + NBBY */ @@ -291,6 +298,7 @@ typedef struct xfs_mount { struct xfs_zone_info *m_zone_info; /* zone allocator information */ struct dentry *m_debugfs; /* debugfs parent */ struct xfs_kobj m_kobj; + struct xfs_kobj m_csum_kobj; struct xfs_kobj m_error_kobj; struct xfs_kobj m_error_meta_kobj; struct xfs_error_cfg m_error_cfg[XFS_ERR_CLASS_MAX][XFS_ERR_ERRNO_MAX]; diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index b24db75eaedc5f..fce1d2905c9408 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -2317,6 +2317,7 @@ xfs_init_fs_context( mp->m_logbufs = -1; mp->m_logbsize = -1; mp->m_allocsize_log = 16; /* 64k */ + mp->m_read_bounce = XFS_READ_BOUNCE_LAZY; xfs_hooks_init(&mp->m_dir_update_hooks); diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c index b6271218732412..e77917ac179d47 100644 --- a/fs/xfs/xfs_sysfs.c +++ b/fs/xfs/xfs_sysfs.c @@ -392,6 +392,71 @@ const struct kobj_type xfs_stats_ktype = { .default_groups = xfs_stats_groups, }; +static inline struct xfs_mount *csum_to_mp(struct kobject *kobj) +{ + return container_of(to_kobj(kobj), struct xfs_mount, m_csum_kobj); +} + +static bool +xfs_has_read_bounce( + struct xfs_mount *mp) +{ + if (bdev_has_integrity_csum(mp->m_ddev_targp->bt_bdev)) + return true; + if (mp->m_rtdev_targp && + bdev_has_integrity_csum(mp->m_rtdev_targp->bt_bdev)) + return true; + return false; +} + +static const char * const bounce_modes[] = { + [XFS_READ_BOUNCE_NEVER] = "never", + [XFS_READ_BOUNCE_ALWAYS] = "always", + [XFS_READ_BOUNCE_LAZY] = "lazy", +}; + +static ssize_t +read_bounce_show( + struct kobject *kobj, + char *buf) +{ + struct xfs_mount *mp = csum_to_mp(kobj); + + return sysfs_emit(buf, "%s\n", + bounce_modes[READ_ONCE(mp->m_read_bounce)]); +} + +static ssize_t +read_bounce_store( + struct kobject *kobj, + const char *buf, + size_t count) +{ + struct xfs_mount *mp = csum_to_mp(kobj); + int ret; + + if (!xfs_has_read_bounce(mp)) + return -EINVAL; + ret = sysfs_match_string(bounce_modes, buf); + if (ret < 0) + return ret; + WRITE_ONCE(mp->m_read_bounce, ret); + return count; +} +XFS_SYSFS_ATTR_RW(read_bounce); + +static struct attribute *xfs_csum_attrs[] = { + ATTR_LIST(read_bounce), + NULL, +}; +ATTRIBUTE_GROUPS(xfs_csum); + +static const struct kobj_type xfs_csum_ktype = { + .release = xfs_sysfs_release, + .sysfs_ops = &xfs_sysfs_ops, + .default_groups = xfs_csum_groups, +}; + /* xlog */ static inline struct xlog * @@ -817,11 +882,17 @@ xfs_mount_sysfs_init( if (error) goto out_remove_fsdir; + /* .../xfs//csum/ */ + error = xfs_sysfs_init(&mp->m_csum_kobj, &xfs_csum_ktype, &mp->m_kobj, + "csum"); + if (error) + goto out_remove_stats_dir; + /* .../xfs//error/ */ error = xfs_sysfs_init(&mp->m_error_kobj, &xfs_error_ktype, &mp->m_kobj, "error"); if (error) - goto out_remove_stats_dir; + goto out_remove_csum_dir; /* .../xfs//error/fail_at_unmount */ error = sysfs_create_file(&mp->m_error_kobj.kobject, @@ -835,12 +906,14 @@ xfs_mount_sysfs_init( "metadata", &mp->m_error_meta_kobj, xfs_error_meta_init); if (error) - goto out_remove_error_dir; + goto out_remove_csum_dir; return 0; out_remove_error_dir: xfs_sysfs_del(&mp->m_error_kobj); +out_remove_csum_dir: + xfs_sysfs_del(&mp->m_csum_kobj); out_remove_stats_dir: xfs_sysfs_del(&mp->m_stats.xs_kobj); out_remove_fsdir: @@ -864,6 +937,7 @@ xfs_mount_sysfs_del( } xfs_sysfs_del(&mp->m_error_meta_kobj); xfs_sysfs_del(&mp->m_error_kobj); + xfs_sysfs_del(&mp->m_csum_kobj); xfs_sysfs_del(&mp->m_stats.xs_kobj); xfs_sysfs_del(&mp->m_kobj); } diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index f333c938fbd935..2af9a1429ae9d1 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -1896,6 +1896,7 @@ DEFINE_SIMPLE_IO_EVENT(xfs_zero_eof); DEFINE_SIMPLE_IO_EVENT(xfs_end_io_direct_write); DEFINE_SIMPLE_IO_EVENT(xfs_file_splice_read); DEFINE_SIMPLE_IO_EVENT(xfs_zoned_map_blocks); +DEFINE_SIMPLE_IO_EVENT(xfs_bounce_reread); DECLARE_EVENT_CLASS(xfs_itrunc_class, TP_PROTO(struct xfs_inode *ip, xfs_fsize_t new_size), From c7bfe2427953d07b881125a3f4fca974709241ff Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:03 +0300 Subject: [PATCH 0429/2286] xfs: add error injection for lazy bounce buffering Add an error injection knob to exercise the lazy bounce buffering code path, i.e. to inject direct I/O re-read using the bounce buffer. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-15-hch@lst.de Signed-off-by: Jens Axboe --- fs/xfs/libxfs/xfs_errortag.h | 6 ++++-- fs/xfs/xfs_ioend.c | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 6de207fed2d892..2dc441da033344 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -75,7 +75,8 @@ #define XFS_ERRTAG_METAFILE_RESV_CRITICAL 45 #define XFS_ERRTAG_FORCE_ZERO_RANGE 46 #define XFS_ERRTAG_ZONE_RESET 47 -#define XFS_ERRTAG_MAX 48 +#define XFS_ERRTAG_BOUNCE_REREAD 48 +#define XFS_ERRTAG_MAX 49 /* * Random factors for above tags, 1 means always, 2 means 1/2 time, etc. @@ -137,7 +138,8 @@ XFS_ERRTAG(WRITE_DELAY_MS, write_delay_ms, 3000) \ XFS_ERRTAG(EXCHMAPS_FINISH_ONE, exchmaps_finish_one, 1) \ XFS_ERRTAG(METAFILE_RESV_CRITICAL, metafile_resv_crit, 4) \ XFS_ERRTAG(FORCE_ZERO_RANGE, force_zero_range, 4) \ -XFS_ERRTAG(ZONE_RESET, zone_reset, 1) +XFS_ERRTAG(ZONE_RESET, zone_reset, 1) \ +XFS_ERRTAG(BOUNCE_REREAD, bounce_reread, XFS_RANDOM_DEFAULT) #endif /* XFS_ERRTAG */ #endif /* __XFS_ERRORTAG_H_ */ diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index 4fba8558d8d246..e70be5b86f0b7a 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -16,6 +16,8 @@ #include "xfs_reflink.h" #include "xfs_zone_alloc.h" #include "xfs_ioend.h" +#include "xfs_error.h" +#include "xfs_errortag.h" #include static void @@ -107,7 +109,8 @@ xfs_end_io_read( * but right now we can't distinguish them from other * (i.e, reftag) errors. */ - if (error) { + if (error || + XFS_TEST_ERROR(mp, XFS_ERRTAG_BOUNCE_REREAD)) { xfs_read_bounce_and_resubmit(ioend); return; } From 9fae7cbc54a54aa1d732c3f89d4b6fab926325e6 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:04 +0300 Subject: [PATCH 0430/2286] xfs: log a message at mount time when using integrity protection Log a message in the kernel when using T10 protection information. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-16-hch@lst.de Signed-off-by: Jens Axboe --- fs/xfs/xfs_buf.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c index ee7c2e9c0340c9..eee491c01d8c70 100644 --- a/fs/xfs/xfs_buf.c +++ b/fs/xfs/xfs_buf.c @@ -5,6 +5,7 @@ */ #include "xfs_platform.h" #include +#include #include #include "xfs_shared.h" @@ -1694,6 +1695,7 @@ xfs_configure_buftarg( struct xfs_mount *mp = btp->bt_mount; if (btp->bt_bdev) { + struct blk_integrity *bi = bdev_get_integrity(btp->bt_bdev); int error; error = bdev_validate_blocksize(btp->bt_bdev, sectorsize); @@ -1706,6 +1708,15 @@ xfs_configure_buftarg( if (bdev_can_atomic_write(btp->bt_bdev)) xfs_configure_buftarg_atomic_writes(btp); + + if (!bi) + ; + else if (btp->bt_bdev == btp->bt_mount->m_super->s_bdev) + xfs_info(mp, "using %s integrity profile", + blk_integrity_profile_name(bi)); + else + xfs_info(mp, "using %s integrity profile for %pg", + blk_integrity_profile_name(bi), btp->bt_bdev); } btp->bt_meta_sectorsize = sectorsize; From a6c30409d10099fe4b744ae89d22c44789a3dae7 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:09:05 +0300 Subject: [PATCH 0431/2286] block,iomap: remove the old read side bounce buffering support bio_iov_iter_bounce_read turned to generate suboptimal I/O sizes and isn't usable for lazy bounce buffering. Now that is has been replaced with the iomap side implementation that requires extra bounce bios, it is unused and can be removed. Change the interface so that the previously hidden write-side implementation is directly exposed. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20260909060924.1102037-17-hch@lst.de Signed-off-by: Jens Axboe --- block/bio.c | 150 ++++++------------------------------------- fs/iomap/direct-io.c | 7 +- include/linux/bio.h | 5 +- 3 files changed, 23 insertions(+), 139 deletions(-) diff --git a/block/bio.c b/block/bio.c index 3c0c423a31f7d6..b48091c7663fa4 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1223,8 +1223,9 @@ bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter) * for the next iteration. */ static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter, - struct bio_vec *bv, unsigned len_align_mask) + unsigned len_align_mask) { + struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1]; size_t nbytes = bio->bi_iter.bi_size & len_align_mask; if (!nbytes) @@ -1354,8 +1355,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, if (is_pci_p2pdma_page(bio->bi_io_vec->bv_page)) bio->bi_opf |= REQ_NOMERGE; - return bio_iov_iter_align_down(bio, iter, - &bio->bi_io_vec[bio->bi_vcnt - 1], len_align_mask); + return bio_iov_iter_align_down(bio, iter, len_align_mask); } static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size, @@ -1426,7 +1426,20 @@ int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize) return 0; } -static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, +/** + * bio_iov_iter_bounce_write - bounce buffer data from an iter into a bio + * @bio: bio to send + * @iter: iter to read from + * @maxlen: maximum size to bounce + * @minsize: minimum folio allocation size + * + * Helper for direct I/O write implementations that need to bounce buffer + * because they need need to checksum the data or perform other operations that + * require consistency. Allocates folios to back the bounce buffer, and copies + * the data into it. Needs to be paired with bio_free_folios() called on + * completion. + */ +int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, size_t maxlen, size_t minsize) { size_t total_len = min(maxlen, iov_iter_count(iter)); @@ -1458,134 +1471,7 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, return 0; } - -static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter, - size_t maxlen, size_t minsize) -{ - size_t len = min3(iov_iter_count(iter), maxlen, SZ_1M); - struct folio *folio; - ssize_t ret; - - folio = folio_alloc_greedy(GFP_KERNEL, &len, minsize); - if (!folio) - return -ENOMEM; - - do { - ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec + 1, len, - &bio->bi_vcnt, bio->bi_max_vecs - 1, 0, 0); - if (ret <= 0) { - if (!bio->bi_vcnt) - goto out_folio_put; - break; - } - len -= ret; - bio->bi_iter.bi_size += ret; - } while (len && bio->bi_vcnt < bio->bi_max_vecs - 1); - - /* - * Set the folio directly here. The above loop has already calculated - * the correct bi_size, and we use bi_vcnt for the user buffers. That - * is safe as bi_vcnt is only used by the submitter and not the actual - * I/O path. - */ - bvec_set_folio(&bio->bi_io_vec[0], folio, bio->bi_iter.bi_size, 0); - if (iov_iter_extract_will_pin(iter)) - bio_set_flag(bio, BIO_PAGE_PINNED); - - /* The first vec stores the bounce buffer, so do not subtract 1 here. */ - ret = bio_iov_iter_align_down(bio, iter, - &bio->bi_io_vec[bio->bi_vcnt], minsize - 1); - if (ret) - goto out_folio_put; - - /* Update the bounc buffer bv_len to the aligned down size. */ - bio->bi_io_vec[0].bv_len = bio->bi_iter.bi_size; - return 0; - -out_folio_put: - folio_put(folio); - return ret; -} - -/** - * bio_iov_iter_bounce - bounce buffer data from an iter into a bio - * @bio: bio to send - * @iter: iter to read from / write into - * @maxlen: maximum size to bounce - * @minsize: minimum folio allocation size - * - * Helper for direct I/O implementations that need to bounce buffer because - * we need to checksum the data or perform other operations that require - * consistency. Allocates folios to back the bounce buffer, and for writes - * copies the data into it. Needs to be paired with bio_iov_iter_unbounce() - * called on completion. - */ -int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen, - size_t minsize) -{ - if (op_is_write(bio_op(bio))) - return bio_iov_iter_bounce_write(bio, iter, maxlen, minsize); - return bio_iov_iter_bounce_read(bio, iter, maxlen, minsize); -} - -static void bvec_unpin(struct bio_vec *bv, bool mark_dirty) -{ - struct folio *folio = bvec_folio(bv); - size_t nr_pages = (bv->bv_offset + bv->bv_len - 1) / PAGE_SIZE - - bv->bv_offset / PAGE_SIZE + 1; - - if (mark_dirty) - folio_mark_dirty_lock(folio); - unpin_user_folio(folio, nr_pages); -} - -static void bio_iov_iter_unbounce_read(struct bio *bio, bool is_error, - bool mark_dirty) -{ - unsigned int len = bio->bi_io_vec[0].bv_len; - - if (likely(!is_error)) { - void *buf = bvec_virt(&bio->bi_io_vec[0]); - struct iov_iter to; - - iov_iter_bvec(&to, ITER_DEST, bio->bi_io_vec + 1, bio->bi_vcnt, - len); - /* copying to pinned pages should always work */ - WARN_ON_ONCE(copy_to_iter(buf, len, &to) != len); - } else { - /* No need to mark folios dirty if never copied to them */ - mark_dirty = false; - } - - if (bio_flagged(bio, BIO_PAGE_PINNED)) { - int i; - - for (i = 0; i < bio->bi_vcnt; i++) - bvec_unpin(&bio->bi_io_vec[1 + i], mark_dirty); - } - - folio_put(bvec_folio(&bio->bi_io_vec[0])); -} - -/** - * bio_iov_iter_unbounce - finish a bounce buffer operation - * @bio: completed bio - * @is_error: %true if an I/O error occurred and data should not be copied - * @mark_dirty: If %true, folios will be marked dirty. - * - * Helper for direct I/O implementations that need to bounce buffer because - * we need to checksum the data or perform other operations that require - * consistency. Called to complete a bio set up by bio_iov_iter_bounce(). - * Copies data back for reads, and marks the original folios dirty if - * requested and then frees the bounce buffer. - */ -void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty) -{ - if (op_is_write(bio_op(bio))) - bio_free_folios(bio); - else - bio_iov_iter_unbounce_read(bio, is_error, mark_dirty); -} +EXPORT_SYMBOL_GPL(bio_iov_iter_bounce_write); static void bio_wait_end_io(struct bio *bio) { diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 4154717a09de90..41fdc90a9094ed 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -255,8 +255,7 @@ static void __iomap_dio_bio_end_io(struct bio *bio, bool inline_completion) fs_bio_integrity_free(bio); if (dio->flags & IOMAP_DIO_BOUNCE) { - bio_iov_iter_unbounce(bio, !!dio->error, - dio->flags & IOMAP_DIO_USER_BACKED); + bio_free_folios(bio); bio_put(bio); } else if (dio->flags & IOMAP_DIO_USER_BACKED) { bio_check_pages_dirty(bio); @@ -364,7 +363,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, bio->bi_end_io = iomap_dio_bio_end_io; if (dio->flags & IOMAP_DIO_BOUNCE) - ret = bio_iov_iter_bounce(bio, dio->submit.iter, maxsize, + ret = bio_iov_iter_bounce_write(bio, dio->submit.iter, maxsize, alignment); else ret = bio_iov_iter_get_pages(bio, dio->submit.iter, maxsize, @@ -398,7 +397,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, out_bio_release_pages: if (dio->flags & IOMAP_DIO_BOUNCE) - bio_iov_iter_unbounce(bio, true, false); + bio_free_folios(bio); else bio_release_pages(bio, false); out_put_bio: diff --git a/include/linux/bio.h b/include/linux/bio.h index bfa3c0e97b6f5e..17944e44b58494 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -525,11 +525,10 @@ void __bio_release_pages(struct bio *bio, bool mark_dirty); extern void bio_set_pages_dirty(struct bio *bio); extern void bio_check_pages_dirty(struct bio *bio); -int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen, - size_t minsize); -void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty); int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize); void bio_free_folios(struct bio *bio); +int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter, + size_t maxlen, size_t minsize); extern void bio_copy_data(struct bio *dst, struct bio *src); extern void bio_free_pages(struct bio *bio); From 57a354d20a01287bd173b13231eb5afcda337eee Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Fri, 11 Sep 2026 17:36:11 +0100 Subject: [PATCH 0432/2286] x86,fs/resctrl: Add resctrl_arch_preconvert_bw() On MPAM systems the rounding behaviour of the MBA control would be improved if the rounding in the fs/resctrl code is removed but this is not the case for x86. To allow any rounding or conversion of the bandwidth value provided by the user to be specified by the arch code a new arch hook is required. Introduce resctrl_arch_preconvert_bw(), and add its x86 implementation. This is currently unused in resctrl but when plumbed in it will replace the call to roundup() in bw_validate(). Signed-off-by: Dave Martin Signed-off-by: Ben Horgan Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Reviewed-by: Gavin Shan Link: https://patch.msgid.link/20260911163613.1131447-2-ben.horgan@arm.com --- arch/x86/kernel/cpu/resctrl/ctrlmondata.c | 6 ++++++ include/linux/resctrl.h | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/arch/x86/kernel/cpu/resctrl/ctrlmondata.c b/arch/x86/kernel/cpu/resctrl/ctrlmondata.c index e74f1ed54b86c4..62044489b05297 100644 --- a/arch/x86/kernel/cpu/resctrl/ctrlmondata.c +++ b/arch/x86/kernel/cpu/resctrl/ctrlmondata.c @@ -16,9 +16,15 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include "internal.h" +u32 resctrl_arch_preconvert_bw(const struct rdt_resource *r, u32 val) +{ + return roundup(val, (unsigned long)r->membw.bw_gran); +} + int resctrl_arch_update_one(struct rdt_resource *r, struct rdt_ctrl_domain *d, u32 closid, enum resctrl_conf_type t, u32 cfg_val) { diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h index dd09c2ce9a0f2d..10dfdca7f4bf5e 100644 --- a/include/linux/resctrl.h +++ b/include/linux/resctrl.h @@ -505,6 +505,25 @@ bool resctrl_arch_mbm_cntr_assign_enabled(struct rdt_resource *r); */ int resctrl_arch_mbm_cntr_assign_set(struct rdt_resource *r, bool enable); +/** + * resctrl_arch_preconvert_bw() - Prepare bandwidth control value for arch use. + * @r: Resource whose schema was written. + * @val: Bandwidth control value written to the schemata file by userspace. + * + * Convert the user provided bandwidth control value to an appropriate form for + * consumption by the hardware driver for resource @r. Converted value is stored + * in rdt_ctrl_domain::staged_config[] for later consumption by + * resctrl_arch_update_domains(). Is not called when MBA software controller is + * enabled. + * + * Architectures for which this pre-conversion hook is not useful should supply + * an implementation of this function that just returns @val unmodified. + * + * Return: + * The converted value. + */ +u32 resctrl_arch_preconvert_bw(const struct rdt_resource *r, u32 val); + /* * Update the ctrl_val and apply this config right now. * Must be called on one of the domain's CPUs. From 255f92316ac1017478e9ace42cafdeb6c98c1f25 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Fri, 11 Sep 2026 17:36:12 +0100 Subject: [PATCH 0433/2286] arm_mpam: resctrl: Add pass-through resctrl_arch_preconvert_bw() resctrl rounds up the percentage value of the MBA based on the bw_gran. As MPAM uses a binary fixed point fraction format for MBA rather than a decimal percentage, this introduces rounding errors. Without this additional rounding, if the user reads the value in an MB schema and then writes it back to the schema, the value in hardware won't change. However, with this additional rounding, this guarantee is broken for systems with mbw_wd < 7. resctrl is introducing resctrl_arch_preconvert_bw() to allow the arch code to specify the conversion resctrl does to the user-provided bandwidth value. Add the MPAM version of resctrl_arch_preconvert_bw(). This does no conversion. Signed-off-by: Ben Horgan Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Reviewed-by: Gavin Shan Link: https://patch.msgid.link/20260911163613.1131447-3-ben.horgan@arm.com --- drivers/resctrl/mpam_resctrl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c index 9d223057953abc..f2c651e9f6ea19 100644 --- a/drivers/resctrl/mpam_resctrl.c +++ b/drivers/resctrl/mpam_resctrl.c @@ -148,6 +148,11 @@ bool resctrl_arch_get_cdp_enabled(enum resctrl_res_level rid) return mpam_resctrl_controls[rid].cdp_enabled; } +u32 resctrl_arch_preconvert_bw(const struct rdt_resource *r, u32 val) +{ + return val; +} + /** * resctrl_reset_task_closids() - Reset the PARTID/PMG values for all tasks. * From 942135446059a63778f1121a75c5b3c959341b47 Mon Sep 17 00:00:00 2001 From: Dave Martin Date: Fri, 11 Sep 2026 17:36:13 +0100 Subject: [PATCH 0434/2286] fs/resctrl: Factor MBA parse-time conversion to be per-arch The control value parser for the MB resource currently coerces the memory bandwidth percentage value from userspace to be an exact multiple of the rdt_resource::resctrl_membw::bw_gran parameter. On MPAM systems, this results in somewhat worse-than-worst-case rounding, since the bandwidth granularity advertised to resctrl by the MPAM driver is in general only an approximation to the actual hardware granularity on these systems, and the hardware bandwidth allocation control value is not natively a percentage -- necessitating a further conversion in the resctrl_arch_update_domains() path, regardless of the conversion done at parse time. For MPAM and x86 use their custom pre-prepared parse-time conversion, resctrl_arch_preconvert_bw(). This will avoid accumulated error from rounding the value twice on MPAM systems. For x86 systems there is no functional change. Clarify the documentation, but avoid overly exact promises. Clamping to bw_min and bw_max still feels generic: leave it in the core code, for now. Signed-off-by: Dave Martin Signed-off-by: Ben Horgan Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Ben Horgan Reviewed-by: Reinette Chatre Reviewed-by: Gavin Shan Link: https://patch.msgid.link/20260911163613.1131447-4-ben.horgan@arm.com --- Documentation/filesystems/resctrl.rst | 17 +++++++++-------- fs/resctrl/ctrlmondata.c | 6 +++--- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst index e4b66af55ffba0..dd4f80da419c84 100644 --- a/Documentation/filesystems/resctrl.rst +++ b/Documentation/filesystems/resctrl.rst @@ -236,12 +236,11 @@ with respect to allocation: user can request. "bandwidth_gran": - The granularity in which the memory bandwidth - percentage is allocated. The allocated - b/w percentage is rounded off to the next - control step available on the hardware. The - available bandwidth control steps are: - min_bandwidth + N * bandwidth_gran. + The approximate granularity in which the memory bandwidth + percentage is allocated. The allocated bandwidth percentage is + rounded up or down to the closest control step available on the + hardware. The available hardware steps are no larger than this + value. "delay_linear": Indicates if the delay scale is linear or @@ -881,8 +880,10 @@ The minimum bandwidth percentage value for each cpu model is predefined and can be looked up through "info/MB/min_bandwidth". The bandwidth granularity that is allocated is also dependent on the cpu model and can be looked up at "info/MB/bandwidth_gran". The available bandwidth -control steps are: min_bw + N * bw_gran. Intermediate values are rounded -to the next control step available on the hardware. +control steps are, approximately, min_bw + N * bw_gran. The steps may +appear irregular due to rounding to an exact percentage: bw_gran is the +maximum interval between the percentage values corresponding to any two +adjacent steps in the hardware. The bandwidth throttling is a core specific mechanism on some of Intel SKUs. Using a high bandwidth and a low bandwidth setting on two threads diff --git a/fs/resctrl/ctrlmondata.c b/fs/resctrl/ctrlmondata.c index 18ec9f564b5a1b..cafebdff70dca0 100644 --- a/fs/resctrl/ctrlmondata.c +++ b/fs/resctrl/ctrlmondata.c @@ -37,8 +37,8 @@ typedef int (ctrlval_parser_t)(struct rdt_parse_data *data, /* * Check whether MBA bandwidth percentage value is correct. The value is * checked against the minimum and max bandwidth values specified by the - * hardware. The allocated bandwidth percentage is rounded to the next - * control step available on the hardware. + * hardware. The allocated bandwidth percentage is converted as appropriate + * for consumption by the specific hardware driver. */ static bool bw_validate(char *buf, u32 *data, struct rdt_resource *r) { @@ -71,7 +71,7 @@ static bool bw_validate(char *buf, u32 *data, struct rdt_resource *r) return false; } - *data = roundup(bw, (unsigned long)r->membw.bw_gran); + *data = resctrl_arch_preconvert_bw(r, bw); return true; } From d54229a6b6d68296a4d9c5067c3bc061da639a66 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 10 Sep 2026 16:37:27 -0400 Subject: [PATCH 0435/2286] PNP: Detach device after resource transition failure pnp_device_probe() attaches the PNP device before it activates or disables its resources. The attach changes the status from PNP_READY to PNP_ATTACHED, but errors from either resource transition return directly and leave that status behind. A later bind or manual PNP configuration attempt then sees a device that is still marked in use. Route both errors through the existing failure path so it restores the status to PNP_READY. Do not disable the device or clean its resources: pnp_activate_dev() only marks it active after a successful start, while pnp_disable_dev() preserves the active state and resource table when stopping fails. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260910203727.63489-1-mhun512@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/pnp/driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pnp/driver.c b/drivers/pnp/driver.c index 6d58b146508136..80c2a0f15b808f 100644 --- a/drivers/pnp/driver.c +++ b/drivers/pnp/driver.c @@ -96,13 +96,13 @@ static int pnp_device_probe(struct device *dev) if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) { error = pnp_activate_dev(pnp_dev); if (error < 0) - return error; + goto fail; } } else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE) == PNP_DRIVER_RES_DISABLE) { error = pnp_disable_dev(pnp_dev); if (error < 0) - return error; + goto fail; } error = 0; if (pnp_drv->probe) { From 2b30842e52d9a364428b598f5edd4b12fa772f5a Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 12 Sep 2026 19:33:55 +0800 Subject: [PATCH 0436/2286] ACPI: SBS: Fix NULL pointer dereference on allocation failure acpi_sbs_probe() jumps to the common error path when kzalloc_obj() fails, but platform_set_drvdata() is only called after the allocation succeeds. The error path calls acpi_sbs_remove(), which retrieves the unset driver data and dereferences the resulting NULL pointer when locking sbs->lock, causing a NULL pointer dereference. Return -ENOMEM directly when the allocation fails, since no resources have been initialized at that point and there is nothing to clean up. This issue was found by manual code inspection. Fixes: 9460eaae2ee42 ("ACPI: SBS: Convert the driver to a platform one") Cc: All applicable Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260912113355.644238-1-lgs201920130244@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sbs.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 862cb94a8b43ba..f10bbf13c242f3 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -639,10 +639,8 @@ static int acpi_sbs_probe(struct platform_device *pdev) return -ENODEV; sbs = kzalloc_obj(struct acpi_sbs); - if (!sbs) { - result = -ENOMEM; - goto end; - } + if (!sbs) + return -ENOMEM; mutex_init(&sbs->lock); From efed63bc78ede446f8bdc3b7b1730882016d332d Mon Sep 17 00:00:00 2001 From: Dirga Yuza Date: Fri, 11 Sep 2026 21:33:06 +0700 Subject: [PATCH 0437/2286] ACPI: video: add backlight=native DMI quirk on Acer Nitro AN515-58 The Acer Nitro AN515-58 requires the same quirk entry as AN515-46, since its firmware also advertises the NVIDIA WMI EC backlight GUID. However, on this hybrid graphics setup, the internal display is wired to the Integrated GPU (Intel Iris Xe Graphics). As a result, the nvidia_wmi_ec_backlight driver fails to probe, leaving no backlight device registered and rendering the brightness keys and desktop brightness slider non-functional. Setting acpi_backlight=native allows intel_backlight to register its backlight interface and restoring brightness control. Add a DMI quirk entry so this model uses the native backlight by default. Signed-off-by: Dirga Yuza Link: https://patch.msgid.link/20260911143306.49015-1-dirgayuza123@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index e3b69f876d2474..3037fd18d42064 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -929,6 +929,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-46"), }, }, + { + .callback = video_detect_force_native, + /* Acer Nitro AN515-58 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"), + }, + }, /* * x86 android tablets which directly control the backlight through From eb221044db2d687be7253cd203df4ee648232c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Thu, 10 Sep 2026 16:48:33 +0200 Subject: [PATCH 0438/2286] lsm: Preserve full ioctl commands in audit records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ioctl commands are unsigned int values, with their encoded direction and size in the upper 16 bits. However, struct lsm_ioctlop_audit keeps only a u16, and the common audit formatter uses %hx. This logs 0xc00ffeee as 0xfeee and makes distinct commands with matching low 16 bits indistinguishable. Change cmd to unsigned int and use %x. Pass the full command through ioctl_has_perm() as well. Its driver and xperm values remain u8 values derived from the same low 16 bits, so SELinux enforcement does not change. Commands that fit in 16 bits keep the same audit text. Cc: Jeff Vander Stoep Cc: Paul Moore Fixes: 671a2781ff01 ("security: add ioctl specific auditing to lsm_audit") Signed-off-by: Mickaël Salaün Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 2 +- security/lsm_audit.c | 2 +- security/selinux/hooks.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 526a8e7471c80c..5cf0b479506571 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -44,7 +44,7 @@ struct lsm_network_audit { struct lsm_ioctlop_audit { struct path path; - u16 cmd; + unsigned int cmd; }; struct lsm_ibpkey_audit { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 29116ef2986b67..955b2e7b2c8e4e 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -231,7 +231,7 @@ void audit_log_lsm_data(struct audit_buffer *ab, audit_log_format(ab, " ino=%llu", inode->i_ino); } - audit_log_format(ab, " ioctlcmd=0x%hx", a->u.op->cmd); + audit_log_format(ab, " ioctlcmd=0x%x", a->u.op->cmd); break; } case LSM_AUDIT_DATA_DENTRY: { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 967984d0af300d..368f9dacdcef7e 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3866,7 +3866,7 @@ static int selinux_backing_file_alloc(struct file *backing_file, * operation to an inode. */ static int ioctl_has_perm(const struct cred *cred, struct file *file, - u32 requested, u16 cmd) + u32 requested, unsigned int cmd) { struct common_audit_data ad; struct file_security_struct *fsec = selinux_file(file); @@ -3937,14 +3937,14 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, case FIOCLEX: case FIONCLEX: if (!selinux_policycap_ioctl_skip_cloexec()) - error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + error = ioctl_has_perm(cred, file, FILE__IOCTL, cmd); break; /* default case assumes that the command will go * to the file's ioctl() function. */ default: - error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + error = ioctl_has_perm(cred, file, FILE__IOCTL, cmd); } return error; } From 63b4c8561b436e8950409d6041fce43f12936f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= Date: Thu, 10 Sep 2026 16:48:34 +0200 Subject: [PATCH 0439/2286] selftests/landlock: Check full ioctl commands in audit records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ioctl audit test uses FIONREAD, whose command value fits in 16 bits. It therefore cannot detect truncation of encoded direction and size bits. Use an architecture-independent unknown command with set upper bits and require its complete value in the audit record. Landlock rejects the command before the device handles it, and the existing ioctl enforcement test already exercises the same request. The new expectation fails with ioctlcmd=0xfeee on an unfixed kernel and passes with ioctlcmd=0xc00ffeee after the shared audit field is widened. Signed-off-by: Mickaël Salaün Reviewed-by: Günther Noack Signed-off-by: Paul Moore --- tools/testing/selftests/landlock/fs_test.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 18dbdb99aebab4..1abdcc9ee7840a 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4146,6 +4146,9 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl) ASSERT_EQ(0, close(fd)); } +/* Arbitrary command with nonzero bits in both 16-bit halves. */ +static const unsigned int unknown_ioctl_cmd = 0xc00ffeee; + /* * ioctl_error - generically call the given ioctl with a pointer to a * sufficiently large zeroed-out memory region. @@ -4249,7 +4252,7 @@ TEST_F_FORK(layout1, blanket_permitted_ioctls) EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE)); /* Default case is also blocked. */ - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); ASSERT_EQ(0, close(fd)); } @@ -7943,6 +7946,7 @@ TEST_F(audit_layout1, truncate) EXPECT_EQ(1, records.domain); } +/* Checks that audit records preserve every ioctl command bit. */ TEST_F(audit_layout1, ioctl_dev) { struct audit_records records; @@ -7952,10 +7956,10 @@ TEST_F(audit_layout1, ioctl_dev) fd = open("/dev/null", O_RDONLY | O_CLOEXEC); ASSERT_LE(0, fd); - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd, "fs\\.ioctl_dev", "/dev/null", - " ioctlcmd=0x541b")); + " ioctlcmd=0xc00ffeee")); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); From 68a3e57814dea71157450ed5fb96c5cf62da8435 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 17:25:00 -0400 Subject: [PATCH 0440/2286] EDAC/ie31200: Disable PCI device when probing fails ie31200_init_one() enables the PCI device before calling ie31200_probe1(). If probing fails, it returns without balancing the successful enable, leaving the PCI enable count elevated. Call pci_disable_device() on that failure path. This also covers the direct ie31200_init_one() call from the module initialization fallback, while preserving the existing return values and successful probe path. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Tony Luck Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260913212500.60272-1-mhun512@gmail.com --- drivers/edac/ie31200_edac.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/edac/ie31200_edac.c b/drivers/edac/ie31200_edac.c index 6f5fdf43e7732c..88af77ee07fe36 100644 --- a/drivers/edac/ie31200_edac.c +++ b/drivers/edac/ie31200_edac.c @@ -660,7 +660,9 @@ static int ie31200_init_one(struct pci_dev *pdev, if (pci_enable_device(pdev) < 0) return -EIO; rc = ie31200_probe1(pdev, (struct res_config *)ent->driver_data); - if (rc == 0 && !mci_pdev) + if (rc) + pci_disable_device(pdev); + else if (!mci_pdev) mci_pdev = pci_dev_get(pdev); return rc; From 558c19e274efb75bd7bfb8cf9fe562c6497c9420 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Tue, 7 Jul 2026 14:53:07 -0700 Subject: [PATCH 0441/2286] EDAC/dummy: Add a dummy EDAC driver A dummy EDAC driver is useful for testing purposes in a VM when one doesn't have all the hardware needed to test aspects of the EDAC subsystem code. Fix edac/Makefile alignment while at it. Assisted-by: LLM Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260707215307.396571-1-bp@kernel.org --- drivers/edac/Kconfig | 9 ++ drivers/edac/Makefile | 3 +- drivers/edac/dummy_edac.c | 191 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 drivers/edac/dummy_edac.c diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index f7964cd9545e3f..4c70729b0d3af3 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -594,4 +594,13 @@ config EDAC_VERSALNET and other system errors from various IP subsystems like RPU, NOCs, HNICX, PL on the AMD Versal NET DDR memory controller. +config EDAC_DUMMY + tristate "Dummy EDAC driver" + default n + help + A dummy EDAC driver is useful for testing purposes in a VM when one + doesn't have all the hardware needed to test aspects of the EDAC + subsystem code. + + You definitely wanna say N here. endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index c2bae03f247b7e..4020ea60738a25 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -35,7 +35,7 @@ obj-$(CONFIG_EDAC_I7300) += i7300_edac.o obj-$(CONFIG_EDAC_I7CORE) += i7core_edac.o obj-$(CONFIG_EDAC_SBRIDGE) += sb_edac.o obj-$(CONFIG_EDAC_PND2) += pnd2_edac.o -obj-$(CONFIG_EDAC_IGEN6) += igen6_edac.o +obj-$(CONFIG_EDAC_IGEN6) += igen6_edac.o obj-$(CONFIG_EDAC_E7XXX) += e7xxx_edac.o obj-$(CONFIG_EDAC_E752X) += e752x_edac.o obj-$(CONFIG_EDAC_I82875P) += i82875p_edac.o @@ -93,3 +93,4 @@ obj-$(CONFIG_EDAC_VERSAL) += versal_edac.o obj-$(CONFIG_EDAC_LOONGSON) += loongson_edac.o obj-$(CONFIG_EDAC_VERSALNET) += versalnet_edac.o obj-$(CONFIG_EDAC_CORTEX_A72) += a72_edac.o +obj-$(CONFIG_EDAC_DUMMY) += dummy_edac.o diff --git a/drivers/edac/dummy_edac.c b/drivers/edac/dummy_edac.c new file mode 100644 index 00000000000000..193f561a9d513b --- /dev/null +++ b/drivers/edac/dummy_edac.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Dummy EDAC driver + * + * A minimal EDAC device driver that registers its own platform device + * unconditionally, so it probes successfully on any system -- including + * VMs and guests that have no real ECC-capable cache/memory hardware + * exposed to them. Useful for exercising the EDAC core, sysfs interface, + * and userspace tooling (edac-utils, mcelog, etc.) without needing the + * physical hardware the "real" drivers depend on. + * + * Modeled after drivers/edac/a72_edac.c (Cortex A72 EDAC L1/L2 cache + * error detection), but with all of the hardware-specific bits (system + * register reads, SMP cross-calls, devicetree compatible matching) + * replaced by a software-only error source that can be poked from + * debugfs or left to generate nothing at all. + */ + +#include +#include +#include +#include +#include + +#include "edac_module.h" + +#define DRVNAME "dummy-edac" +#define MESSAGE_SIZE 64 + +/* + * Simulated "error syndrome". In real hardware drivers this would be + * read out of a per-CPU system/MMIO register. Here userspace (via + * debugfs) or nothing at all drives it, which is exactly the point: + * the driver has no dependency on any physical error source. + */ +struct dummy_edac_priv { + struct dentry *debugfs_dir; + atomic_t inject_ce; + atomic_t inject_ue; +}; + +static void dummy_edac_check(struct edac_device_ctl_info *edac_ctl) +{ + struct dummy_edac_priv *priv = edac_ctl->pvt_info; + int cpu = raw_smp_processor_id(); + char msg[MESSAGE_SIZE]; + + if (atomic_xchg(&priv->inject_ce, 0)) { + snprintf(msg, MESSAGE_SIZE, + "simulated correctable error on CPU %d", cpu); + edac_device_handle_ce(edac_ctl, cpu, 0, msg); + } + + if (atomic_xchg(&priv->inject_ue, 0)) { + snprintf(msg, MESSAGE_SIZE, + "simulated uncorrectable error on CPU %d", cpu); + edac_device_handle_ue(edac_ctl, cpu, 0, msg); + } +} + +/* + * debugfs knobs so you can drive the driver from userspace inside the + * guest, e.g.: + * echo 1 > /sys/kernel/debug/dummy-edac/inject_ce + * echo 1 > /sys/kernel/debug/dummy-edac/inject_ue + */ +static int inject_ce_set(void *data, u64 val) +{ + struct dummy_edac_priv *priv = data; + + if (val) + atomic_set(&priv->inject_ce, 1); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(inject_ce_fops, NULL, inject_ce_set, "%llu\n"); + +static int inject_ue_set(void *data, u64 val) +{ + struct dummy_edac_priv *priv = data; + + if (val) + atomic_set(&priv->inject_ue, 1); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(inject_ue_fops, NULL, inject_ue_set, "%llu\n"); + +static int dummy_edac_probe(struct platform_device *pdev) +{ + struct edac_device_ctl_info *edac_ctl; + struct dummy_edac_priv *priv; + struct device *dev = &pdev->dev; + int rc; + + edac_ctl = edac_device_alloc_ctl_info(sizeof(*priv), "cpu", + num_possible_cpus(), "L", 1, 1, + edac_device_alloc_index()); + if (!edac_ctl) + return -ENOMEM; + + priv = edac_ctl->pvt_info; + atomic_set(&priv->inject_ce, 0); + atomic_set(&priv->inject_ue, 0); + + edac_ctl->edac_check = dummy_edac_check; + edac_ctl->dev = dev; + edac_ctl->mod_name = dev_name(dev); + edac_ctl->dev_name = dev_name(dev); + edac_ctl->ctl_name = DRVNAME; + /* Poll fairly slowly; this is a software source, not real hardware. */ + edac_ctl->poll_msec = 1000; + dev_set_drvdata(dev, edac_ctl); + + rc = edac_device_add_device(edac_ctl); + if (rc) + goto out_dev; + + priv->debugfs_dir = debugfs_create_dir(DRVNAME, NULL); + debugfs_create_file_unsafe("inject_ce", 0200, priv->debugfs_dir, + priv, &inject_ce_fops); + debugfs_create_file_unsafe("inject_ue", 0200, priv->debugfs_dir, + priv, &inject_ue_fops); + + return 0; + +out_dev: + edac_device_free_ctl_info(edac_ctl); + + return rc; +} + +static void dummy_edac_remove(struct platform_device *pdev) +{ + struct edac_device_ctl_info *edac_ctl = dev_get_drvdata(&pdev->dev); + struct dummy_edac_priv *priv = edac_ctl->pvt_info; + + debugfs_remove_recursive(priv->debugfs_dir); + edac_device_del_device(edac_ctl->dev); + edac_device_free_ctl_info(edac_ctl); +} + +static struct platform_driver dummy_edac_driver = { + .probe = dummy_edac_probe, + .remove = dummy_edac_remove, + .driver = { + .name = DRVNAME, + }, +}; + +/* + * No devicetree/ACPI match table, and no scan of CPU nodes for a "compatible" + * + enable property: simply register a platform device unconditionally so + * probe() always runs. There is nothing here that depends on the underlying + * platform actually exposing the corresponding hardware, so it loads fine + * under QEMU/KVM, containers-with-a-kernel, or any other guest environment. + */ +static struct platform_device *dummy_pdev; + +static int __init dummy_edac_driver_init(void) +{ + int rc; + + dummy_pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(dummy_pdev)) { + pr_err(DRVNAME ": failed to register dummy platform device\n"); + return PTR_ERR(dummy_pdev); + } + + rc = platform_driver_register(&dummy_edac_driver); + if (rc) { + platform_device_unregister(dummy_pdev); + return rc; + } + + pr_info("Loading %s\n", DRVNAME); + + return 0; +} + +static void __exit dummy_edac_driver_exit(void) +{ + platform_driver_unregister(&dummy_edac_driver); + platform_device_unregister(dummy_pdev); + + pr_info("Removing %s\n", DRVNAME); +} + +module_init(dummy_edac_driver_init); +module_exit(dummy_edac_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Dummy EDAC driver for testing without real ECC hardware"); From 9025f3bf73f0565eaa271ecc15f468b1d1e4cbaa Mon Sep 17 00:00:00 2001 From: Bill Roberts Date: Tue, 7 Jul 2026 13:45:42 -0500 Subject: [PATCH 0442/2286] x86/shstk: Return the correct error value when user shadow stacks are disabled The WRSS instruction (the special instruction that writes to shadow stacks) cannot be used in userspace unless IA32_U_CET.SH_STK_EN=1 (user shadow stack is enabled). So the kernel *should* return -EINVAL to userspace if it tries to enable it when shadow stack is disabled. However, currently, it will return -EPERM. But, that error code doesn't fit the condition as the failure is due to an invalid state change request not a permission issue. Investigating userspace call sites, like glibc and criu (checkpoint code), they do not rely on this specific error message, nor could a userspace effectively utilize this specific return error code to indicate a difference in "I cannot enable write because of invalid permissions" versus "I cannot enable write because the shadow stack is disabled". [ bp: Fixup commit title. ] Signed-off-by: Bill Roberts Signed-off-by: Dave Hansen Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Rick Edgecombe Link: https://patch.msgid.link/20260707184542.1721085-1-bill.roberts@arm.com --- arch/x86/kernel/shstk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c index 0ca64900192f83..eb690ba90180c7 100644 --- a/arch/x86/kernel/shstk.c +++ b/arch/x86/kernel/shstk.c @@ -490,7 +490,7 @@ static int wrss_control(bool enable) * when disabling. */ if (!features_enabled(ARCH_SHSTK_SHSTK)) - return -EPERM; + return -EINVAL; /* Already enabled/disabled? */ if (features_enabled(ARCH_SHSTK_WRSS) == enable) From 228200f695c0f92c5fd06ff0bdcf94bb0d2ca7f5 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Thu, 20 Aug 2026 17:02:10 -0700 Subject: [PATCH 0443/2286] x86/CPU/AMD: Fix Zen5 TLB sizes reporting Starting with Zen5, TLB sizes in CPUID_Fn80000006_E[AB]X are reported as multiples of 32. There's a CPUID bit which determines that: CPUID_Fn80000021_EAX [Extended Feature 2 EAX] (Core::X86::Cpuid::FeatureExt2Eax) ... 14: L2TlbSizeX32. Read-only. Reset: 1. Indicates that L2TLB sizes are encoded as multiples of 32. Update the places which report that information. With it, the numbers look correct now: -Last level iTLB entries: 4KB 64, 2MB 64, 4MB 32 -Last level dTLB entries: 4KB 128, 2MB 128, 4MB 64, 1GB 0 +Last level iTLB entries: 4KB 2048, 2MB 2048, 4MB 1024 +Last level dTLB entries: 4KB 4096, 2MB 4096, 4MB 2048, 1GB 0 /proc/cpuinfo -TLB size : 192 4K pages +TLB size : 6144 4K pages Signed-off-by: Borislav Petkov (AMD) Link: https://lore.kernel.org/r/20260821022031.946311-1-bp@kernel.org --- arch/x86/include/asm/cpufeatures.h | 2 ++ arch/x86/kernel/cpu/amd.c | 21 +++++++++++++-------- arch/x86/kernel/cpu/common.c | 4 +++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f9217..a7689e606b167a 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -483,6 +483,8 @@ #define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */ #define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */ +#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */ + #define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */ #define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */ diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c index 54e14ed276b521..e5279bc648d3ab 100644 --- a/arch/x86/kernel/cpu/amd.c +++ b/arch/x86/kernel/cpu/amd.c @@ -1192,7 +1192,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size) static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) { - u32 ebx, eax, ecx, edx; + u32 ebx, eax, ecx, edx, shift, tmp; u16 mask = 0xfff; if (c->x86 < 0xf) @@ -1201,10 +1201,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) if (c->extended_cpuid_level < 0x80000006) return; + shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; + cpuid(0x80000006, &eax, &ebx, &ecx, &edx); - tlb_lld_4k = (ebx >> 16) & mask; - tlb_lli_4k = ebx & mask; + tlb_lld_4k = ((ebx >> 16) & mask) << shift; + tlb_lli_4k = (ebx & mask) << shift; /* * K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB @@ -1216,16 +1218,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) } /* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ - if (!((eax >> 16) & mask)) + tmp = ((eax >> 16) & mask) << shift; + if (!tmp) tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff; else - tlb_lld_2m = (eax >> 16) & mask; + tlb_lld_2m = tmp; /* a 4M entry uses two 2M entries */ tlb_lld_4m = tlb_lld_2m >> 1; /* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */ - if (!(eax & mask)) { + tmp = (eax & mask) << shift; + if (!tmp) { /* Erratum 658 */ if (c->x86 == 0x15 && c->x86_model <= 0x1f) { tlb_lli_2m = 1024; @@ -1233,8 +1237,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c) cpuid(0x80000005, &eax, &ebx, &ecx, &edx); tlb_lli_2m = eax & 0xff; } - } else - tlb_lli_2m = eax & mask; + } else { + tlb_lli_2m = tmp; + } tlb_lli_4m = tlb_lli_2m >> 1; diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index c7352827f491d5..3716a6af13cba9 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c) void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) { - unsigned int n, dummy, ebx, ecx, edx, l2size; + unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused; n = c->extended_cpuid_level; @@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c) l2size = ecx >> 16; #ifdef CONFIG_X86_64 + shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5; c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff); + c->x86_tlbsize <<= shift; #else /* do processor-specific cache resizing */ if (this_cpu->legacy_cache_size) From 227c6bd8d3639f10375f557e611d56183e2e46b6 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 13 Sep 2026 23:06:15 -0700 Subject: [PATCH 0444/2286] firmware: zynqmp: use literal blocks for commands Use literal blocks for 'cat' and 'echo' commands to separate them from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260914060615.1398531-1-rdunlap@infradead.org> --- .../ABI/stable/sysfs-driver-firmware-zynqmp | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp index c3fec3c835af36..214e9ed4564e55 100644 --- a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp +++ b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp @@ -141,32 +141,44 @@ Description: Usage: - Select over temperature config ID to enable/disable feature + Select over temperature config ID to enable/disable feature:: + # echo 1 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id - Check over temperature config ID is selected or not + Check over temperature config ID is selected or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id + The expected result is 1. - Select over temperature config ID to configure OT limit + Select over temperature config ID to configure OT limit:: + # echo 2 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id - Check over temperature config ID is selected or not + Check over temperature config ID is selected or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id + The expected result is 2. - Select external watchdog config ID to enable/disable feature + Select external watchdog config ID to enable/disable feature:: + # echo 3 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id - Check external watchdog config ID is selected or not + Check external watchdog config ID is selected or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id + The expected result is 3. - Select external watchdog config ID to configure time interval + Select external watchdog config ID to configure time interval:: + # echo 4 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id - Check external watchdog config ID is selected or not + Check external watchdog config ID is selected or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id + The expected result is 4. Users: Xilinx @@ -205,52 +217,70 @@ Description: Usage: - Enable over temperature feature + Enable over temperature feature:: + # echo 1 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 1 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the over temperature feature is enabled or not + Check whether the over temperature feature is enabled or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 1. - Disable over temperature feature + Disable over temperature feature:: + # echo 1 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 0 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the over temperature feature is disabled or not + Check whether the over temperature feature is disabled or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 0. - Configure over temperature limit to 50 Degree Celsius + Configure over temperature limit to 50 Degree Celsius:: + # echo 2 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 50 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the over temperature limit is configured or not + Check whether the over temperature limit is configured or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 50. - Enable external watchdog feature + Enable external watchdog feature:: + # echo 3 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 1 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the external watchdog feature is enabled or not + Check whether the external watchdog feature is enabled or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 1. - Disable external watchdog feature + Disable external watchdog feature:: + # echo 3 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 0 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the external watchdog feature is disabled or not + Check whether the external watchdog feature is disabled or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 0. - Configure external watchdog timer interval to 500ms + Configure external watchdog timer interval to 500ms:: + # echo 4 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_id # echo 500 > /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value - Check whether the external watchdog timer interval is configured or not + Check whether the external watchdog timer interval is configured or not:: + # cat /sys/devices/platform/firmware\:zynqmp-firmware/feature_config_value + The expected result is 500. Users: Xilinx From 213a1765719d462808cdf923f14f4982a1c68975 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 11 Sep 2026 22:52:02 -0700 Subject: [PATCH 0445/2286] docs: kernel-doc: include "define" as an identifier type Update the list of identifier types to include "define", which was added in 2014 in commit cbb4d3e6510b ("scripts/kernel-doc: handle object-like macros") Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260912055202.103573-1-rdunlap@infradead.org> --- Documentation/doc-guide/kernel-doc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/doc-guide/kernel-doc.rst b/Documentation/doc-guide/kernel-doc.rst index 1c148fe8e1f9c4..8f4042be23104e 100644 --- a/Documentation/doc-guide/kernel-doc.rst +++ b/Documentation/doc-guide/kernel-doc.rst @@ -563,8 +563,8 @@ identifiers: *[ function/type ...]* Include documentation for each *function* and *type* in *source*. If no *function* is specified, the documentation for all functions and types in the *source* will be included. - *type* can be a ``struct``, ``union``, ``enum``, ``typedef`` or ``var`` - identifier. + *type* can be a ``struct``, ``union``, ``enum``, ``typedef``, ``var``, + or ``define`` identifier. Examples:: From 5fccf2b932d6bfbf5340d9087b533f6821d6d9bc Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 11 Sep 2026 11:42:13 -0700 Subject: [PATCH 0446/2286] docs: kernel-doc: inform that '@' applies to other fields The '@' sign applies to struct members, union members, and enum values as well as function parameters. Document that. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260911184213.4143776-1-rdunlap@infradead.org> --- Documentation/doc-guide/kernel-doc.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/doc-guide/kernel-doc.rst b/Documentation/doc-guide/kernel-doc.rst index 8f4042be23104e..48f62e59c455f1 100644 --- a/Documentation/doc-guide/kernel-doc.rst +++ b/Documentation/doc-guide/kernel-doc.rst @@ -422,7 +422,8 @@ Domain`_ references. Function reference. ``@parameter`` - Name of a function parameter. (No cross-referencing, just formatting.) + Name of a function parameter, struct member, union member, or + enum value. (No cross-referencing, just formatting.) ``%CONST`` Name of a constant. (No cross-referencing, just formatting.) From 11d447e9f26cd84f9a2478f4eb5bdfedb6b9722d Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:38 +0200 Subject: [PATCH 0447/2286] docs: kernel-parameters: Drop the atarimouse= entry Nothing in the tree parses atarimouse=. drivers/input/mouse/atarimouse.c only takes a module parameter, and git log -S finds no __setup() for the boot parameter anywhere in the git history, so the entry has been stale since before it. Drop the entry. Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Michael Schmitz Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-2-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..c8d96de4c00dfe 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -591,8 +591,6 @@ Kernel parameters ataflop= [HW,M68k] - atarimouse= [HW,MOUSE] Atari Mouse - atkbd.extra= [HW] Enable extra LEDs and keys on IBM RapidAccess, EzKey and similar keyboards From 9cff73421bbe84cf70c9fe0a5ee02b7475592dc2 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:39 +0200 Subject: [PATCH 0448/2286] docs: kernel-parameters: Drop the bau= entry bau= went away with commit 39297dde7390 ("x86/platform/uv: Remove UV BAU TLB Shootdown Handler"); nothing parses it any more. Drop the entry. Fixes: 39297dde7390 ("x86/platform/uv: Remove UV BAU TLB Shootdown Handler") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Reviewed-by: Steve Wahl Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-3-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index c8d96de4c00dfe..99d5bb68968a9e 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -624,13 +624,6 @@ Kernel parameters Format: (must be >=0) Default: 64 - bau= [X86_UV] Enable the BAU on SGI UV. The default - behavior is to disable the BAU (i.e. bau=0). - Format: { "0" | "1" } - 0 - Disable the BAU. - 1 - Enable the BAU. - unset - Disable the BAU. - bdev_allow_write_mounted= Format: Control the ability to open a mounted block device From e165c1a08aa8358ed689607b701be4f5dcaaab06 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:40 +0200 Subject: [PATCH 0449/2286] docs: kernel-parameters: Drop the edac_report= entry edac_report= became the edac_core.edac_report module parameter with commit fee27d7d9788 ("EDAC: Delete edac_stub.c") and went away with commit 7fc0b9b995f2 ("EDAC: Drop the EDAC report status checks"); nothing parses it any more. Drop the entry. Fixes: 7fc0b9b995f2 ("EDAC: Drop the EDAC report status checks") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-4-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 99d5bb68968a9e..2a8d862ef30f55 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1578,14 +1578,6 @@ Kernel parameters PCI device even when its classcode is not of the UART class. - edac_report= [HW,EDAC] Control how to report EDAC event - Format: {"on" | "off" | "force"} - on: enable EDAC to report H/W event. May be overridden - by other higher priority error reporting module. - off: disable H/W event reporting through EDAC. - force: enforce the use of EDAC to report H/W event. - default: on. - edd= [EDD] Format: {"off" | "on" | "skip[mbr]"} From 67a8d2024f6295edb98e793a29da9b1af37665ab Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:41 +0200 Subject: [PATCH 0450/2286] docs: kernel-parameters: Drop the enable_timer_pin_1 entry enable_timer_pin_1 lost its early_param() in commit efa2559f6516 ("x86: order variables in io_apic_xx.c"); nothing parses it any more. Drop the entry. Fixes: efa2559f6516 ("x86: order variables in io_apic_xx.c") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Arnd Bergmann Acked-by: Thomas Gleixner Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-5-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 2a8d862ef30f55..f4314492953da7 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1647,12 +1647,6 @@ Kernel parameters to discrete, to make X server driver able to add WB entry later. This parameter enables that. - enable_timer_pin_1 [X86] - Enable PIN 1 of APIC timer - Can be useful to work around chipset bugs - (in particular on some ATI chipsets). - The kernel tries to set a reasonable default. - enforcing= [SELINUX] Set initial enforcing status. Format: {"0" | "1"} See security/selinux/Kconfig help text. From c7be8d92b77b7da627b64d5cf7bcd2f40bdf4e72 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:42 +0200 Subject: [PATCH 0451/2286] docs: kernel-parameters: Drop the mini2440= entry mini2440= went away with commit 61b7f8920b17 ("ARM: s3c: remove all s3c24xx support"); nothing parses it any more. Drop the entry. Fixes: 61b7f8920b17 ("ARM: s3c: remove all s3c24xx support") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Krzysztof Kozlowski Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-6-kmehltretter@gmail.com> --- .../admin-guide/kernel-parameters.txt | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index f4314492953da7..2ed1e2c03c61d7 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4052,27 +4052,6 @@ Kernel parameters Enable or disable the microcode minimal revision enforcement for the runtime microcode loader. - mini2440= [ARM,HW,KNL] - Format:[0..2][b][c][t] - Default: "0tb" - MINI2440 configuration specification: - 0 - The attached screen is the 3.5" TFT - 1 - The attached screen is the 7" TFT - 2 - The VGA Shield is attached (1024x768) - Leaving out the screen size parameter will not load - the TFT driver, and the framebuffer will be left - unconfigured. - b - Enable backlight. The TFT backlight pin will be - linked to the kernel VESA blanking code and a GPIO - LED. This parameter is not necessary when using the - VGA shield. - c - Enable the s3c camera interface. - t - Reserved for enabling touchscreen support. The - touchscreen support is not enabled in the mainstream - kernel as of 2.6.30, a preliminary port can be found - in the "bleeding edge" mini2440 support kernel at - https://repo.or.cz/w/linux-2.6/mini2440.git - mitigations= [X86,PPC,S390,ARM64,EARLY] Control optional mitigations for CPU vulnerabilities. This is a set of curated, From 582f575b9b660719487441b8c857063bdcc69a3e Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:43 +0200 Subject: [PATCH 0452/2286] docs: kernel-parameters: Drop the nomfgpt entry nomfgpt went away with commit c95d1e53ed89 ("cs5535: drop the Geode-specific MFGPT/GPIO code"); nothing parses it any more. Drop the entry. Fixes: c95d1e53ed89 ("cs5535: drop the Geode-specific MFGPT/GPIO code") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-7-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 2ed1e2c03c61d7..d44728a45506cb 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4553,9 +4553,6 @@ Kernel parameters nomce [X86-32] Disable Machine Check Exception - nomfgpt [X86-32] Disable Multi-Function General Purpose - Timer usage (for AMD Geode machines). - nomodeset Disable kernel modesetting. Most systems' firmware sets up a display mode and provides framebuffer memory for output. With nomodeset, DRM and fbdev drivers will From e74e01e917fa56df7cf8aa094cd4951a2863327b Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:44 +0200 Subject: [PATCH 0453/2286] docs: kernel-parameters: Drop the omap_mux= entry omap_mux= went away with commit e9f5f1e45608 ("ARM: OMAP2+: Remove legacy mux code"); nothing parses it any more. Drop the entry. Fixes: e9f5f1e45608 ("ARM: OMAP2+: Remove legacy mux code") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Andreas Kemnade Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-8-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index d44728a45506cb..48dd273eaf6cd9 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4765,11 +4765,6 @@ Kernel parameters waiting for the ACK, so if this is set too high interrupts *may* be lost! - omap_mux= [OMAP] Override bootloader pin multiplexing. - Format: ... - For example, to override I2C bus2: - omap_mux=i2c2_scl.i2c2_scl=0x100,i2c2_sda.i2c2_sda=0x100 - onenand.bdry= [HW,MTD] Flex-OneNAND Boundary Configuration Format: [die0_boundary][,die0_lock][,die1_boundary][,die1_lock] From 496a8c6bac54227d153931c305f10afdbf93603c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 10 Sep 2026 03:32:45 +0200 Subject: [PATCH 0454/2286] docs: kernel-parameters: Drop the pcmv= entry pcmv= went away with commit 7aeffbf2ddec ("pcmcia: remove unused pxa/sa1100 drivers"); nothing parses it any more. Drop the entry. Fixes: 7aeffbf2ddec ("pcmcia: remove unused pxa/sa1100 drivers") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Arnd Bergmann Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260910013245.8619-9-kmehltretter@gmail.com> --- Documentation/admin-guide/kernel-parameters.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 48dd273eaf6cd9..4864e37005bee0 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -5311,8 +5311,6 @@ Kernel parameters nomsi Do not use MSI for native PCIe PME signaling (this makes all PCIe root ports use INTx for all services). - pcmv= [HW,PCMCIA] BadgePAD 4 - pd_ignore_unused [PM] Keep all power-domains already enabled by bootloader on, From fdd100ee60a0b1c1f9296ff60396f5d28d0d757c Mon Sep 17 00:00:00 2001 From: Ruben Sutton Date: Thu, 10 Sep 2026 00:41:00 +1000 Subject: [PATCH 0455/2286] docs: process: use gender-neutral pronouns Several process documents use gendered pronouns when referring generically to developers and maintainers. Use singular they instead, making the language inclusive without changing its meaning. Leave pronouns referring to named people and text in attributed quotations unchanged. Assisted-by: LLM Signed-off-by: Ruben Sutton Signed-off-by: Jonathan Corbet Message-ID: <20260909144100.78177-1-ruben@scstudios.tech> --- Documentation/process/2.Process.rst | 2 +- Documentation/process/5.Posting.rst | 2 +- Documentation/process/6.Followthrough.rst | 2 +- Documentation/process/handling-regressions.rst | 2 +- Documentation/process/howto.rst | 2 +- Documentation/process/submitting-patches.rst | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/process/2.Process.rst b/Documentation/process/2.Process.rst index d09fa23d6c42ae..a8e09ca4e7c709 100644 --- a/Documentation/process/2.Process.rst +++ b/Documentation/process/2.Process.rst @@ -227,7 +227,7 @@ tree, usually (but certainly not always) using the Git source management tool. Tools like Git (and related tools like Quilt or Mercurial) allow maintainers to track a list of patches, including authorship information and other metadata. At any given time, the maintainer can identify which -patches in his or her repository are not found in the mainline. +patches in their repository are not found in the mainline. When the merge window opens, top-level maintainers will ask Linus to "pull" the patches they have selected for merging from their repositories. If diff --git a/Documentation/process/5.Posting.rst b/Documentation/process/5.Posting.rst index b8a449980452eb..976cf45a565ad6 100644 --- a/Documentation/process/5.Posting.rst +++ b/Documentation/process/5.Posting.rst @@ -233,7 +233,7 @@ the patch. Each of these uses this format:: The tags in common use are: - - Signed-off-by: this is a developer's certification that he or she has + - Signed-off-by: this is a developer's certification that they have the right to submit the patch for inclusion into the kernel. It is an agreement to the Developer's Certificate of Origin, the full text of which can be found in :ref:`Documentation/process/submitting-patches.rst ` diff --git a/Documentation/process/6.Followthrough.rst b/Documentation/process/6.Followthrough.rst index 66fa400c6d9405..ebb0e4cc95840e 100644 --- a/Documentation/process/6.Followthrough.rst +++ b/Documentation/process/6.Followthrough.rst @@ -114,7 +114,7 @@ What happens next If a patch is considered to be a good thing to add to the kernel, and once most of the review issues have been resolved, the next step is usually entry into a subsystem maintainer's tree. How that works varies from one -subsystem to the next; each maintainer has his or her own way of doing +subsystem to the next; each maintainer has their own way of doing things. In particular, there may be more than one tree - one, perhaps, dedicated to patches planned for the next merge window, and another for longer-term work. diff --git a/Documentation/process/handling-regressions.rst b/Documentation/process/handling-regressions.rst index c71b5d403f0c9c..6958a2a8b03411 100644 --- a/Documentation/process/handling-regressions.rst +++ b/Documentation/process/handling-regressions.rst @@ -283,7 +283,7 @@ sure your patch description makes this aspect obvious. Once the change is merged, tell the Linux kernel's regression tracker and the regressions mailing list about the risk, so everyone has the change on the radar in case reports trickle in. Depending on the risk, you also might want to ask the subsystem -maintainer to mention the issue in his mainline pull request. +maintainer to mention the issue in their mainline pull request. What else is there to known about regressions? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/process/howto.rst b/Documentation/process/howto.rst index 9438e03d6f503f..8c60138d03f271 100644 --- a/Documentation/process/howto.rst +++ b/Documentation/process/howto.rst @@ -305,7 +305,7 @@ The maintainers of the various kernel subsystems --- and also many kernel subsystem developers --- expose their current state of development in source repositories. That way, others can see what is happening in the different areas of the kernel. In areas where -development is rapid, a developer may be asked to base his submissions +development is rapid, a developer may be asked to base their submissions onto such a subsystem kernel tree so that conflicts between the submission and other already ongoing work are avoided. diff --git a/Documentation/process/submitting-patches.rst b/Documentation/process/submitting-patches.rst index 7ae79452e1b46d..4c28a239e8cb58 100644 --- a/Documentation/process/submitting-patches.rst +++ b/Documentation/process/submitting-patches.rst @@ -456,7 +456,7 @@ When to use Acked-by:, Cc:, and Co-developed-by: ------------------------------------------------ The Signed-off-by: tag indicates that the signer was involved in the -development of the patch, or that he/she was in the patch's delivery path. +development of the patch, or that they were in the patch's delivery path. If a person was not directly involved in the preparation or handling of a patch but wishes to signify and record their approval of it then they can From 28e747da9807b2edbf5f308f175bce800f787ced Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 9 Sep 2026 15:41:34 +0200 Subject: [PATCH 0456/2286] docs: verify/bisect: fix typos, formatting and punctuation Fix a few typos, formatting and some grammar issues. Signed-off-by: Manuel Ebner Acked-by: Thorsten Leemhuis Signed-off-by: Jonathan Corbet Message-ID: <20260909134135.1358766-2-manuelebnerli@mailbox.org> --- .../verify-bugs-and-bisect-regressions.rst | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Documentation/admin-guide/verify-bugs-and-bisect-regressions.rst b/Documentation/admin-guide/verify-bugs-and-bisect-regressions.rst index 7d38393f31fb16..91ebb631a25418 100644 --- a/Documentation/admin-guide/verify-bugs-and-bisect-regressions.rst +++ b/Documentation/admin-guide/verify-bugs-and-bisect-regressions.rst @@ -163,7 +163,7 @@ will be considered the 'good' release and used to prepare the .config file. section 'Complementary tasks: cleanup during and after the process' below. - d) Once your finished the bisection, put a few things away:: + d) Once you have finished the bisection, put a few things away:: cd ~/linux/ git bisect log > ~/bisect-log @@ -178,9 +178,9 @@ will be considered the 'good' release and used to prepare the .config file. ./scripts/config --set-str CONFIG_LOCALVERSION '-local-cafec0cacaca0-reverted' This is optional, as some commits are impossible to revert. But if the - second command worked flawlessly, build, install, and boot one more kernel - kernel; just this time skip the first command copying the base .config file - over, as that already has been taken care off. + second command worked flawlessly, build, install, and boot one more kernel; + just this time skip the first command copying the base .config file over, + as that already has been taken care off. * **Complementary tasks**: cleanup during and after the process. @@ -229,10 +229,10 @@ depends on your issue: Execute all steps till the end of *segment 1* to **verify if your kernel problem is present in code supported by Linux kernel developers**. If it is, you are all set to report the bug -- unless it did not happen with earlier kernel versions, -as then your want to at least continue with *segment 2* to **check if the issue +as then you want to at least continue with *segment 2* to **check if the issue qualifies as regression** which receive priority treatment. Depending on the outcome you then are ready to report a bug or submit a preliminary regression -report; instead of the latter your could also head straight on and follow +report; instead of the latter you could also head straight on and follow *segment 3* to **perform a bisection** for a full-fledged regression report developers are obliged to act upon. @@ -550,7 +550,7 @@ be a waste of time. [:ref:`details `] cd ~/linux/ git switch --discard-changes --detach stable/linux-6.1.y - Your series is unsupported, if is not listed or carrying a 'end of life' + Your series is unsupported if it is not listed or it is carrying a 'end of life' tag. In that case you might want to check if a successor series (say linux-6.2.y) or mainline (see next point) fix the bug. @@ -1183,9 +1183,9 @@ Space requirements The numbers mentioned are rough estimates with a big extra charge to be on the safe side, so often you will need less. -If you have space constraints, be sure to hay attention to the :ref:`step about -debug symbols' ` and its :ref:`accompanying reference -section' `, as disabling then will reduce the consumed disk +If you have space constraints, be sure to pay attention to the :ref:`step about +debug symbols ` and its :ref:`accompanying reference +section `, as disabling them will reduce the consumed disk space by quite a few gigabytes. [:ref:`back to step-by-step guide `] @@ -1254,8 +1254,8 @@ distributions: kernel-install-tools libelf-devel make modutils openssl openssl-devel \ perl-base zlib-devel rpm-build ncurses-devel qt6-base-devel -These commands install a few packages that are often, but not always needed. You -for example might want to skip installing the development headers for ncurses, +These commands install a few packages that are often, but not always needed. For +example, you might want to skip installing the development headers for ncurses, which you will only need in case you later might want to adjust the kernel build configuration using make the targets 'menuconfig' or 'nconfig'; likewise omit the headers of Qt6 if you do not plan to adjust the .config using 'xconfig'. @@ -1407,7 +1407,7 @@ Occasionally odd things happen when trying to use a config file prepared for one kernel (say 6.1) on an older mainline release -- especially if it is much older (say 5.15). That's one of the reasons why the previous step in the guide told you to boot the kernel where everything works. If you manually add a .config -file you thus want to ensure it's from the working kernel and not from a one +file you thus want to ensure it's from the working kernel and not from one that shows the regression. In case you want to build kernels for another machine, locate its kernel build From d5ac3d36e6ba1347c76919978865a7d16c06a9de Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 9 Sep 2026 09:48:44 -0300 Subject: [PATCH 0457/2286] docs: pt_BR: translate coding-style.rst Translate Documentation/process/coding-style.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. This document provides the coding style guidelines for Linux kernel developers, ensuring consistency and readability across the codebase. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260909124905.6156-2-danielmaraboo@gmail.com> --- .../pt_BR/process/coding-style.rst | 1318 +++++++++++++++++ .../translations/pt_BR/process/index.rst | 1 + 2 files changed, 1319 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/coding-style.rst diff --git a/Documentation/translations/pt_BR/process/coding-style.rst b/Documentation/translations/pt_BR/process/coding-style.rst new file mode 100644 index 00000000000000..26dcd1246de6e1 --- /dev/null +++ b/Documentation/translations/pt_BR/process/coding-style.rst @@ -0,0 +1,1318 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Estilo de codificação do kernel Linux +===================================== + +Este é um breve documento descrevendo o estilo de codificação preferido +para o kernel Linux. O estilo de codificação é muito pessoal, e eu não +**forçarei** minhas opiniões a ninguém, mas isso é o que vale para tudo +que eu tenha que manter, e eu preferiria isso para a maioria das outras +coisas também. Por favor, considere pelo menos os pontos aqui apresentados. + +Primeiramente, eu sugiro imprimir uma cópia dos padrões de codificação do GNU, +e NÃO lê-la. Queime-as, é um grande gesto simbólico. + +De qualquer forma, aqui vai: + + +1) Indentação +------------- + +As tabulações têm 8 caracteres, e portanto as indentações também têm 8 +caracteres. Há movimentos heréticos que tentam fazer com que as indentações +tenham 4 (ou até 2!) caracteres de profundidade, e isso é semelhante a +tentar definir o valor de PI como 3. + +Justificativa: A ideia central da indentação é definir claramente onde um +bloco de controle começa e termina. Especialmente quando você esteve olhando +para a tela por 20 horas seguidas, você vai achar muito mais fácil ver como +a indentação funciona se ela for mais ampla. + +Agora, algumas pessoas afirmarão que ter indentações de 8 caracteres faz +o código se deslocar demais para a direita e dificultam a leitura em um +terminal de 80 caracteres. A resposta é que, se você precisar de mais de 3 +níveis de indentação, você já está em apuros de qualquer forma, e deve +corrigir seu programa. + +Em resumo, indentações de 8 caracteres deixam as coisas mais fáceis de ler, e +têm o benefício adicional de avisar quando você está aninhando suas funções +em excesso. Atenda a esse aviso. + +A maneira preferida de aliviar vários níveis de indentação em uma instrução +``switch`` é alinhar o ``switch`` e seus rótulos subordinados ``case`` na +mesma coluna, em vez de indentar duplamente os rótulos ``case``. Por exemplo: + +.. code-block:: c + + switch (suffix) { + case 'G': + case 'g': + mem <<= 30; + break; + case 'M': + case 'm': + mem <<= 20; + break; + case 'K': + case 'k': + mem <<= 10; + fallthrough; + default: + break; + } + +Não coloque múltiplas instruções em uma única linha, a menos que você tenha +algo para esconder: + +.. code-block:: c + + if (condition) do_this; + do_something_everytime; + +Não use vírgulas para evitar usar chaves: + +.. code-block:: c + + if (condition) + do_this(), do_that(); + +Sempre use chaves para múltiplas instruções: + +.. code-block:: c + + if (condition) { + do_this(); + do_that(); + } + +Também não coloque múltiplas atribuições em uma única linha. O estilo de +codificação do kernel é extremamente simples. Evite expressões complicadas. + + +Fora de comentários, documentação e, exceto em Kconfig, espaços nunca são +usados para indentação, e o exemplo acima foi deliberadamente quebrado. + +Obtenha um editor decente e não deixe espaços em branco no final das linhas. + + +2) Quebrando linhas longas e strings +------------------------------------ + +O estilo de codificação trata principalmente da legibilidade e da +manutenibilidade usando ferramentas comumente disponíveis. + +O limite preferido para o comprimento de uma única linha é de 80 colunas. + +Instruções com mais de 80 colunas devem ser quebradas em partes sensatas, +a menos que exceder 80 colunas aumente significativamente a legibilidade e +não esconda informações. + +Os descendentes são sempre substancialmente mais curtos do que o pai e são +colocados substancialmente à direita. Um estilo muito usado é alinhar os +descendentes ao parêntese de abertura de uma função. + +Essas mesmas regras são aplicadas aos cabeçalhos de função com uma lista de +argumentos longa. + +No entanto, nunca quebre strings visíveis ao usuário, como mensagens +``printk``, porque isso prejudica a capacidade de fazer ``grep`` nelas. + + +3) Posicionamento de chaves e espaços +------------------------------------- + +A outra questão que sempre surge no estilo em C é o posicionamento das +chaves. Ao contrário do tamanho da indentação, há poucos motivos técnicos +para escolher uma estratégia de posicionamento em vez de outra, mas a forma +preferida, como nos mostraram os profetas Kernighan e Ritchie, é colocar a +chave de abertura no final da linha e a chave de fechamento no início, assim: + +.. code-block:: c + + if (x is true) { + we do y + } + +Isso se aplica a todos os blocos de instruções que não sejam funções (if, +switch, for, while, do). Por exemplo: + +.. code-block:: c + + switch (action) { + case KOBJ_ADD: + return "add"; + case KOBJ_REMOVE: + return "remove"; + case KOBJ_CHANGE: + return "change"; + default: + return NULL; + } + +No entanto, há um caso especial: as funções, que têm a chave de abertura no +início da linha seguinte, assim: + +.. code-block:: c + + int function(int x) + { + body of function + } + +Pessoas heréticas por todo o mundo afirmaram que essa inconsistência é ... +bem ... inconsistente, mas todas as pessoas de bom senso sabem que (a) K&R +estão **corretos** e (b) K&R estão certos. Além disso, as funções são +especiais de qualquer forma (você não pode aninhá-las em C). + +Observe que a chave de fechamento fica vazia em uma linha própria, **exceto** +nos casos em que ela é seguida por uma continuação da mesma instrução, ou +seja, um ``while`` em um do-statement ou um ``else`` em um if-statement, como +neste exemplo: + +.. code-block:: c + + do { + body of do-loop + } while (condition); + +e + +.. code-block:: c + + if (x == y) { + .. + } else if (x > y) { + ... + } else { + .... + } + +Justificativa: K&R. + +Além disso, observe que esse posicionamento de chaves também minimiza o +número de linhas vazias (ou quase vazias), sem qualquer perda de +legibilidade. Assim, como o suprimento de linhas novas na sua tela não é um +recurso renovável (pense em telas de terminal de 25 linhas), você tem mais +linhas vazias para colocar comentários. + +Não use chaves desnecessariamente quando uma única instrução basta. + +.. code-block:: c + + if (condition) + action(); + +e + +.. code-block:: c + + if (condition) + do_this(); + else + do_that(); + +Isso não se aplica se apenas um ramo de uma instrução condicional for uma +única instrução; nesse último caso, use chaves em ambos os ramos: + +.. code-block:: c + + if (condition) { + do_this(); + do_that(); + } else { + otherwise(); + } + +Além disso, use chaves quando um laço contiver mais de uma instrução simples: + +.. code-block:: c + + while (condition) { + if (test) + do_something(); + } + +3.1) Espaços +************ + +O estilo do kernel Linux para o uso de espaços depende (em grande parte) do +uso de função versus palavra-chave. Use um espaço após (a maioria das) +palavras-chave. As exceções notáveis são ``sizeof``, ``typeof``, ``alignof`` e +``__attribute__``, que parecem um pouco com funções (e geralmente são usadas +com parênteses no Linux, embora não sejam obrigatórias na linguagem, como em: +``sizeof info`` depois que ``struct fileinfo info;`` é declarado). + +Então use um espaço após estas palavras-chave:: + + if, switch, case, for, do, while + +mas não com ``sizeof``, ``typeof``, ``alignof`` ou ``__attribute__``. Por +exemplo, + +.. code-block:: c + + + s = sizeof(struct file); + +Não adicione espaços ao redor (dentro) de expressões entre parênteses. Este +exemplo é **ruim**: + +.. code-block:: c + + + s = sizeof( struct file ); + +Ao declarar dados de ponteiro ou uma função que retorna um tipo de ponteiro, o +uso preferido de ``*`` fica adjacente ao nome dos dados ou ao nome da função e +não adjacente ao nome do tipo. Exemplos: + +.. code-block:: c + + + char *linux_banner; + unsigned long long memparse(char *ptr, char **retptr); + char *match_strdup(substring_t *s); + +Use um espaço em volta (de cada lado) da maioria dos operadores binários e +ternários, como qualquer um destes:: + + = + - < > * / % | & ^ <= >= == != ? : + +mas sem espaço após operadores unários:: + + & * + - ~ ! sizeof typeof alignof __attribute__ defined + +sem espaço antes dos operadores unários pós-fixados de incremento e +decremento:: + + ++ -- + +sem espaço após os operadores unários prefixados de incremento e +decremento:: + + ++ -- + +e sem espaço ao redor dos operadores de membro de estrutura ``.`` e ``->``. + +Não deixe espaços em branco no final das linhas. Alguns editores com +indentação ``inteligente`` inserem espaços no início das novas linhas conforme +apropriado, para que você possa começar a digitar a próxima linha de código +imediatamente. No entanto, alguns desses editores não removem o espaço em +branco se você acabar não colocando uma linha de código ali, como quando deixa +uma linha em branco. Como resultado, você termina com linhas contendo espaço +em branco no final. + +O Git avisará você sobre patches que introduzem espaço em branco no final e +pode remover esse espaço automaticamente para você; entretanto, se você +aplicar uma série de patches, isso pode fazer com que patches posteriores da +série falhem ao alterar suas linhas de contexto. + + +4) Nomeação +----------- + +C é uma linguagem espartana, e suas convenções de nomenclatura devem seguir o +mesmo caminho. Ao contrário dos programadores em Modula-2 e Pascal, os +programadores em C não usam nomes bonitinhos como ThisVariableIsATemporaryCounter. +Um programador em C chamaria essa variável de ``tmp``, o que é muito mais fácil +de escrever e não é menos fácil de entender. + +ENTRETANTO, embora nomes em camelCase sejam desencorajados, nomes descritivos +para variáveis globais são essenciais. Chamar uma função global de ``foo`` é +um crime. + +Variáveis GLOBAIS (a serem usadas somente se você **realmente** precisar) devem +ter nomes descritivos, assim como as funções globais. Se você tiver uma função +que conta o número de usuários ativos, você deve chamá-la de +``count_active_users()`` ou algo parecido; você **não** deve chamá-la de +``cntusr()``. + +Codificar o tipo de uma função no nome (a chamada notação Húngara) é absurdo - +o compilador conhece os tipos de qualquer forma e pode verificar isso, e isso +só confunde o programador. + +Nomes de variáveis locais devem ser curtos e diretos. Se você tiver algum +contador inteiro aleatório de loop, provavelmente deve ser chamado de ``i``. +Chamá-lo de ``loop_counter`` é improdutivo, se não houver chance de ser mal +interpretado. Da mesma forma, ``tmp`` pode ser praticamente qualquer tipo de +variável usada para manter um valor temporário. + +Se você tem medo de misturar os nomes de variáveis locais, você tem outro +problema, chamado síndrome de desequilíbrio de hormônio de crescimento da +função. Veja o capítulo 6 (Funções). + +Para nomes de símbolos e documentação, evite introduzir o uso novo de +'master / slave' (ou 'slave' independente de 'master') e 'blacklist / +whitelist'. + +Substituições recomendadas para 'master / slave' são: + '{primary,main} / {secondary,replica,subordinate}' + '{initiator,requester} / {target,responder}' + '{controller,host} / {device,worker,proxy}' + 'leader / follower' + 'director / performer' + +Substituições recomendadas para 'blacklist/whitelist' são: + 'denylist / allowlist' + 'blocklist / passlist' + +Exceções para introduzir novos usos são manter uma ABI/API do espaço do +usuário, ou ao atualizar código para um hardware ou especificação de +protocolo existente (a partir de 2020) que exija esses termos. Para novas +especificações, traduza o uso da terminologia na especificação para o padrão +de codificação do kernel quando possível. + +5) Tipos definidos (typedefs) +----------------------------- + +Por favor, não use coisas como ``vps_t``. +É um **erro** usar ``typedef`` para estruturas e ponteiros. Quando você vê + +.. code-block:: c + + + vps_t a; + +no código-fonte, o que isso significa? +Em contraste, se diz + +.. code-block:: c + + struct virtual_container *a; + +você consegue dizer o que ``a`` é. + +Muitas pessoas pensam que ``typedef``s ``ajudam na legibilidade``. Não é bem +assim. Eles são úteis apenas para: + + (a) objetos totalmente opacos (onde o ``typedef`` é usado ativamente para + **ocultar** o que o objeto é). + + Exemplo: ``pte_t`` etc. Objetos opacos que você só pode acessar usando + as funções de acesso apropriadas. + + .. note:: + + Opacidade e ``funções de acesso`` não são boas em si mesmas. + A razão pela qual as temos para coisas como ``pte_t`` etc. é que + realmente existe absolutamente **zero** informação acessível de forma portátil + ali. + + (b) tipos inteiros claros, em que a abstração **ajuda** a evitar confusão + sobre se é ``int`` ou ``long``. + + ``u8/u16/u32`` são typedefs perfeitamente aceitáveis, embora se + encaixem melhor na categoria (d) do que aqui. + + .. note:: + + Novamente - precisa haver uma **razão** para isso. Se algo é + ``unsigned long``, não há motivo para fazer + + typedef unsigned long myflags_t; + + mas se houver uma razão clara para que em certas circunstâncias possa + ser ``unsigned int`` e em outras configurações possa ser ``unsigned + long``, então, claro, use um ``typedef``. + + (c) quando você usa ``sparse`` para criar literalmente um **novo** tipo para + verificação de tipos. + + (d) novos tipos idênticos aos tipos padrão do C99, em certas + circunstâncias excepcionais. + + Embora levasse apenas um curto período para os olhos e o cérebro se + acostumarem aos tipos padrão como ``uint32_t``, algumas pessoas ainda + se opõem ao seu uso. + + Portanto, os tipos específicos do Linux ``u8/u16/u32/u64`` e seus + equivalentes assinados, que são idênticos aos tipos padrão, são + permitidos -- embora não sejam obrigatórios em código novo seu. + + Ao editar código existente que já usa um ou outro conjunto de tipos, você + deve seguir as escolhas existentes nesse código. + + (e) tipos seguros para uso em espaço do usuário. + + Em certas estruturas visíveis ao espaço do usuário, não podemos exigir + tipos C99 nem usar a forma ``u32`` acima. Portanto, usamos ``__u32`` e + tipos semelhantes em todas as estruturas compartilhadas com o espaço do + usuário. + +Pode haver outros casos também, mas a regra básica deve ser: NUNCA use um +``typedef`` a menos que você consiga encaixar claramente em uma dessas regras. + +Em geral, um ponteiro, ou uma estrutura com elementos que podem ser acessados +diretamente, **nunca** deve ser um ``typedef``. + + +6) Funções +---------- + +As funções devem ser curtas e diretas, e fazer apenas uma coisa. Elas devem +caber em uma ou duas telas de texto (o tamanho de tela ISO/ANSI é 80x24, +como todos sabem), e fazer uma coisa e fazê-la bem. + +O comprimento máximo de uma função é inversamente proporcional à complexidade +e ao nível de indentação dessa função. Então, se você tiver uma função +conceitualmente simples que seja apenas uma longa (mas simples) instrução +``switch``, em que você precisa fazer várias pequenas coisas para muitos casos +diferentes, é aceitável ter uma função mais longa. + +No entanto, se você tiver uma função complexa e suspeitar que um estudante +do primeiro ano do ensino médio, menos talentoso, talvez nem entenda do que +se trata a função, você deve obedecer aos limites máximos ainda mais de +perto. Use funções auxiliares com nomes descritivos (você pode pedir ao +compilador para incorporá-las em linha se achar que é crítico para o +desempenho, e provavelmente ele fará um trabalho melhor do que você faria). + +Outra medida da função é o número de variáveis locais. Elas não devem exceder +5-10, ou algo está errado. Reflita sobre a função e divida em partes menores. +Um cérebro humano geralmente consegue rastrear facilmente cerca de 7 coisas +diferentes; qualquer quantidade acima disso o confunde. Você sabe que é +brilhante, mas talvez queira entender o que fez daqui a 2 semanas. + +Nos arquivos de código-fonte, separe as funções com uma linha em branco. Se a +função for exportada, a macro **EXPORT** para ela deve seguir imediatamente +depois da linha da chave de fechamento da função. Por exemplo: + +.. code-block:: c + + int system_is_up(void) + { + return system_state == SYSTEM_RUNNING; + } + EXPORT_SYMBOL(system_is_up); + +6.1) Protótipos de função +************************* + +Nos protótipos de função, inclua nomes de parâmetros junto com seus tipos de +dados. Embora isso não seja obrigatório pela linguagem C, é preferido no Linux +porque é uma maneira simples de adicionar informações valiosas para o leitor. + +Não use a palavra-chave ``extern`` em declarações de função, pois isso torna +as linhas mais longas e não é estritamente necessário. + +Ao escrever protótipos de função, por favor mantenha a `ordem dos elementos +regular `_. +Por exemplo, usando este exemplo de declaração de função:: + + __init void * __must_check action(enum magic value, size_t size, u8 count, + char *fmt, ...) __printf(4, 5) __malloc; + +A ordem preferida dos elementos para um protótipo de função é: + +- classe de armazenamento (abaixo, ``static __always_inline``, observando que + ``__always_inline`` é tecnicamente um atributo, mas é tratado como ``inline``) +- atributos de classe de armazenamento (aqui, ``__init`` -- ou seja, + declarações de seção, mas também coisas como ``__cold``) +- tipo de retorno (aqui, ``void *``) +- atributos do tipo de retorno (aqui, ``__must_check``) +- nome da função (aqui, ``action``) +- parâmetros da função (aqui, ``(enum magic value, size_t size, u8 count, + char *fmt, ...)``, observando que os nomes dos parâmetros devem sempre ser + incluídos) +- atributos dos parâmetros da função (aqui, ``__printf(4, 5)``) +- atributos de comportamento da função (aqui, ``__malloc``) + +Observe que, para uma **definição** de função (ou seja, o corpo real da +função), o compilador não permite atributos de parâmetro da função depois dos +parâmetros da função. Nesses casos, eles devem vir depois dos atributos da +classe de armazenamento (por exemplo, observe a posição alterada de +``__printf(4, 5)`` abaixo, em comparação com o exemplo de **declaração** +acima):: + + static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value, + size_t size, u8 count, char *fmt, ...) __malloc + { + ... + } + +7) Saída centralizada de funções +-------------------------------- + +Embora seja depreciado por algumas pessoas, o equivalente da instrução +``goto`` é usado com frequência pelos compiladores na forma da instrução de +salto incondicional. + +A instrução ``goto`` é útil quando uma função sai de vários pontos e é +necessária alguma tarefa comum, como limpeza. Se não for necessária nenhuma +limpeza, basta retornar diretamente. + +Escolha nomes de rótulos que indiquem o que o ``goto`` faz ou por que ele +existe. Um exemplo de nome bom poderia ser ``out_free_buffer:`` se o goto +liberar ``buffer``. Evite usar nomes do GW-BASIC como ``err1:`` e ``err2:``, +porque você teria que renumerá-los se adicionasse ou removesse caminhos de +saída, e isso também torna a correção mais difícil de verificar. + +A justificativa para usar gotos é: + +- instruções incondicionais são mais fáceis de entender e seguir +- o aninhamento é reduzido +- erros por não atualizar pontos de saída individuais ao fazer + modificações são evitados +- economiza o trabalho do compilador de otimizar e remover código redundante ;) + +.. code-block:: c + + int fun(int a) + { + int result = 0; + char *buffer; + + buffer = kmalloc(SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (condition1) { + while (loop1) { + ... + } + result = 1; + goto out_free_buffer; + } + ... + out_free_buffer: + kfree(buffer); + return result; + } + +Um tipo comum de bug do qual você deve estar ciente é o ``one err bugs`` +(o "bug de um erro"), que se parece com isto: + +.. code-block:: c + + err: + kfree(foo->bar); + kfree(foo); + return ret; + +O problema neste código é que, em alguns caminhos de saída, ``foo`` é NULL. +Normalmente, a correção é dividir em dois rótulos de erro +``err_free_bar:`` e ``err_free_foo:``: + +.. code-block:: c + + err_free_bar: + kfree(foo->bar); + err_free_foo: + kfree(foo); + return ret; + +Idealmente, você deve simular erros para testar todos os caminhos de saída. + + +8) Comentários +-------------- + +Comentários são bons, mas também há o perigo de comentar demais. NUNCA tente +explicar COMO o seu código funciona em um comentário: é muito melhor escrever +o código de forma que o **funcionamento** seja óbvio, e é um desperdício de +tempo explicar código mal escrito. + +Em geral, você quer que seus comentários digam O QUE o seu código faz, não +COMO. Também tente evitar colocar comentários dentro do corpo de uma função: +se a função for tão complexa que você precisa comentar partes separadas dela, +provavelmente você deveria voltar ao capítulo 6 por um tempo. Você pode fazer +pequenos comentários para notar ou avisar sobre algo particularmente esperto +(ou feio), mas tente evitar excesso. Em vez disso, coloque os comentários no +início da função, dizendo às pessoas o que ela faz e, possivelmente, POR QUE +ela faz isso. + +Ao comentar as funções da API do kernel, por favor use o formato kernel-doc. +Veja os arquivos em :ref:`Documentation/doc-guide/ ` e +``tools/docs/kernel-doc`` para detalhes. Observe que o perigo de comentar em +excesso se aplica aos comentários kernel-doc da mesma forma. Não adicione +kernel-doc genérico que apenas repete o que já é óbvio pela assinatura da +função. + +O estilo preferido para comentários longos (em várias linhas) é: + +.. code-block:: c + + /* + * Este é o estilo preferido para comentários em várias linhas + * no código-fonte do kernel Linux. + * Por favor, use-o de forma consistente. + * + * Descrição: uma coluna de asteriscos à esquerda, + * com linhas de início e fim quase vazias. + */ + +Também é importante comentar dados, sejam tipos básicos ou tipos derivados. +Para isso, use apenas uma declaração de dado por linha (sem vírgulas para +múltiplas declarações de dados). Isso deixa espaço para um pequeno comentário +em cada item explicando seu uso. + + +9) Você fez uma bagunça +----------------------- + +Tudo bem, todos fazemos isso. Você provavelmente foi informado por seu +auxiliar de longa data em Unix que o ``GNU emacs`` formata automaticamente os +arquivos-fonte em C para você, e você percebeu que ele realmente faz isso, mas as +configurações padrão que ele usa são menos do que desejáveis (na verdade, +elas são piores do que digitação aleatória - um número infinito de macacos +digitando no GNU emacs nunca faria um bom programa). + +Então, você pode ou se livrar do GNU emacs, ou mudar para usar valores mais +sãos. Para fazer isso, você pode colocar o seguinte no seu arquivo .emacs: + +.. code-block:: elisp + + (defun c-lineup-arglist-tabs-only (ignored) + "Line up argument lists by tabs, not spaces" + (let* ((anchor (c-langelem-pos c-syntactic-element)) + (column (c-langelem-2nd-pos c-syntactic-element)) + (offset (- (1+ column) anchor)) + (steps (floor offset c-basic-offset))) + (* (max steps 1) + c-basic-offset))) + + (dir-locals-set-class-variables + 'linux-kernel + '((c-mode . ( + (c-basic-offset . 8) + (c-label-minimum-indentation . 0) + (c-offsets-alist . ( + (arglist-close . c-lineup-arglist-tabs-only) + (arglist-cont-nonempty . + (c-lineup-gcc-asm-reg c-lineup-arglist-tabs-only)) + (arglist-intro . +) + (brace-list-intro . +) + (c . c-lineup-C-comments) + (case-label . 0) + (comment-intro . c-lineup-comment) + (cpp-define-intro . +) + (cpp-macro . -1000) + (cpp-macro-cont . +) + (defun-block-intro . +) + (else-clause . 0) + (func-decl-cont . +) + (inclass . +) + (inher-cont . c-lineup-multi-inher) + (knr-argdecl-intro . 0) + (label . -1000) + (statement . 0) + (statement-block-intro . +) + (statement-case-intro . +) + (statement-cont . +) + (substatement . +) + )) + (indent-tabs-mode . t) + (show-trailing-whitespace . t) + )))) + + (dir-locals-set-directory-class + (expand-file-name "~/src/linux-trees") + 'linux-kernel) + +Isso fará o emacs funcionar melhor com o estilo de codificação do kernel para +arquivos C abaixo de ``~/src/linux-trees``. + +Mas, mesmo que você falhe em fazer o emacs formatar de maneira sensata, nem +tudo está perdido: use ``indent``. + +Agora, novamente, o GNU indent tem as mesmas configurações sem cérebro do GNU +emacs, e é por isso que você precisa dar a ele algumas opções de linha de +comando. No entanto, isso não é tão ruim, porque até os criadores do GNU +indent reconhecem a autoridade do K&R (as pessoas do GNU não são más, apenas +estão gravemente equivocadas nessa questão), então você apenas dá ao indent as +opções ``-kr -i8`` (que significa ``K&R, indentações de 8 caracteres``), ou +usa ``scripts/Lindent``, que indenta no estilo mais recente. + +``indent`` tem muitas opções, e especialmente quando se trata de reformatação +de comentários, você pode querer dar uma olhada na página de manual. Mas +lembre-se: ``indent`` não é uma solução para programação ruim. + +Observe que você também pode usar a ferramenta ``clang-format`` para ajudá-lo +com essas regras, para reformatar rapidamente partes do seu código +automaticamente e revisar arquivos completos para detectar erros de estilo de +codificação, erros de digitação e possíveis melhorias. Também é útil para +ordenar ``#includes``, alinhar variáveis/macros, reorganizar texto e outras +tarefas semelhantes. Consulte o arquivo +:ref:`Documentation/dev-tools/clang-format.rst ` +para obter mais detalhes. + +Algumas configurações básicas do editor, como indentação e finais de linha, +serão definidas automaticamente se você estiver usando um editor compatível com +o EditorConfig. Consulte o site oficial do EditorConfig para obter mais +informação: https://editorconfig.org/ + +10) Arquivos de configuração Kconfig +------------------------------------ + +Para todos os arquivos de configuração Kconfig* em toda a árvore de origem, +a indentação é um pouco diferente. Linhas sob uma definição ``config`` são +indentadas com uma tabulação, enquanto o texto de ajuda é indentado com mais +dois espaços. Exemplo:: + + config AUDIT + bool "Auditing support" + depends on NET + help + Enable auditing infrastructure that can be used with another + kernel subsystem, such as SELinux (which requires this for + logging of avc messages output). Does not do system-call + auditing without CONFIG_AUDITSYSCALL. + +Recursos seriamente perigosos (como suporte de gravação para certos sistemas +de arquivos) devem anunciar isso de forma proeminente na string do prompt:: + + config ADFS_FS_RW + bool "ADFS write support (DANGEROUS)" + depends on ADFS_FS + ... + +Para documentação completa sobre os arquivos de configuração, consulte o +arquivo Documentation/kbuild/kconfig-language.rst. + + +11) Estruturas de dados +----------------------- + +Estruturas de dados que tenham visibilidade fora do ambiente monothread em que +são criadas e destruídas devem ter contadores de referência. No kernel, coleta +de lixo não existe (e fora do kernel, a coleta de lixo é lenta e +ineficiente), o que significa que você absolutamente **precisa** contar todas +as referências de uso. + +Contagem de referência significa que você pode evitar bloqueios e permite que +múltiplos usuários tenham acesso à estrutura de dados em paralelo - sem se +preocupar com a estrutura desaparecendo debaixo deles do nada apenas porque +eles dormiram ou fizeram outra coisa por um tempo. + +Observe que bloqueio **não** substitui contagem de referência. O bloqueio é +usado para manter estruturas de dados coerentes, enquanto a contagem de +referência é uma técnica de gerenciamento de memória. Normalmente, ambos são +necessários, e não devem ser confundidos entre si. + +Muitas estruturas de dados podem, de fato, ter dois níveis de contagem de +referência, quando existem usuários de diferentes ``classes``. A contagem da +subclasse conta o número de usuários da subclasse e decrementa a contagem +global apenas uma vez quando a contagem da subclasse chega a zero. + +Exemplos desse tipo de ``multi-level-reference-counting`` podem ser encontrados +em gerenciamento de memória (``struct mm_struct``: mm_users e mm_count) e em +código de sistema de arquivos (``struct super_block``: s_count e s_active). + +Lembre-se: se outra thread puder encontrar sua estrutura de dados, e você não +tiver contagem de referência nela, quase certamente há um bug. + + +12) Macros, enums e RTL +----------------------- + +Nomes de macros que definem constantes e rótulos em enums são maiúsculos. + +.. code-block:: c + + #define CONSTANT 0x12345 + +Enums são preferidos quando várias constantes relacionadas são definidas. + +Nomes de macro em maiúsculas são apreciados, mas macros que se parecem com +funções podem ser nomeadas em minúsculas. + +Em geral, funções inline são preferíveis a macros que se parecem com funções. + +Macros com múltiplas instruções devem ser envoltas em um bloco do-while: + +.. code-block:: c + + #define macrofun(a, b, c) \ + do { \ + if (a == 5) \ + do_this(b, c); \ + } while (0) + +Macros do tipo função com parâmetros não usados devem ser substituídas por +funções estáticas inline para evitar o problema de variáveis não usadas: + +.. code-block:: c + + static inline void fun(struct foo *foo) + { + } + +Devido a práticas históricas, muitos arquivos ainda empregam a abordagem +"cast para (void)" para avaliar parâmetros. No entanto, esse método não é +aconselhável. +Funções inline resolvem o problema de "expressão com efeitos colaterais +avaliada mais de uma vez", contornam problemas de variáveis não usadas e, por +algum motivo, geralmente são mais bem documentadas do que macros. + +.. code-block:: c + + /* + * Evite fazer isto sempre que possível e prefira funções estáticas + * inline + */ + #define macrofun(foo) do { (void) (foo); } while (0) + +Coisas a evitar ao usar macros: + +1) macros que afetam o fluxo de controle: + +.. code-block:: c + + #define FOO(x) \ + do { \ + if (blah(x) < 0) \ + return -EBUGGERED; \ + } while (0) + +é uma ideia **muito** ruim. Ela parece uma chamada de função, mas sai da +função ``calling``; não quebre os parsers internos de quem lerá o código. + +2) macros que dependem de ter uma variável local com um nome mágico: + +.. code-block:: c + + #define FOO(val) bar(index, val) + +pode parecer uma boa coisa, mas é confuso pra caramba para quem lê o código e +é propenso a quebrar com mudanças aparentemente inocentes. + +3) macros com argumentos usados como l-values: FOO(x) = y; vai te morder se +alguém, por exemplo, transformar FOO em uma função inline. + +4) esquecer da precedência: macros que definem constantes usando expressões +devem colocar a expressão entre parênteses. Cuidado com problemas semelhantes +com macros que usam parâmetros. + +.. code-block:: c + + #define CONSTANT 0x4000 + #define CONSTEXP (CONSTANT | 3) + +5) colisões de namespace ao definir variáveis locais em macros que se +parecem com funções: + +.. code-block:: c + + #define FOO(x) \ + ({ \ + typeof(x) ret; \ + ret = calc_ret(x); \ + (ret); \ + }) + +``ret`` é um nome comum para uma variável local - ``__foo_ret`` tem menos +chance de colidir com uma variável existente. + +O manual do cpp trata de macros de forma exaustiva. O manual interno do gcc +também cobre o RTL, que é usado frequentemente com linguagem de montagem no +kernel. + + +13) Imprimindo mensagens do kernel +---------------------------------- + +Desenvolvedores do kernel gostam de ser vistos como letrados. Preste atenção à +ortografia das mensagens do kernel para causar uma boa impressão. Não use +contrações incorretas como ``dont``; use ``do not`` ou ``don't`` em vez +disso. Faça as mensagens concisas, claras e inequívocas. + +Mensagens do kernel não precisam terminar com ponto. + +Imprimir números entre parênteses (%d) não agrega valor e deve ser evitado. + +Há vários macros de diagnóstico do modelo de driver em +que você deve usar para garantir que as mensagens sejam correspondidas ao +dispositivo e driver corretos e sejam marcadas com o nível certo: ``dev_err()``, +``dev_warn()``, ``dev_info()`` e assim por diante. Para mensagens que não +estão associadas a um device específico, define +``pr_notice()``, ``pr_info()``, ``pr_warn()``, ``pr_err()`` etc. Quando os +drivers funcionam corretamente, eles ficam silenciosos, então prefira usar +``dev_dbg``/``pr_debug`` a menos que algo esteja errado. + +Encontrar boas mensagens de depuração pode ser um desafio; e, uma vez que +você tenha essas mensagens, elas podem ajudar bastante para solução de +problemas remota. No entanto, a impressão de mensagens de depuração é tratada +diferentemente da impressão de outras mensagens não de depuração. Enquanto as +outras funções ``pr_XXX()`` imprimem incondicionalmente, ``pr_debug()`` não; +ela é compilada fora por padrão, a menos que ``DEBUG`` seja definido ou +``CONFIG_DYNAMIC_DEBUG`` esteja configurado. Isso também vale para +``dev_dbg()``, e uma convenção relacionada usa ``VERBOSE_DEBUG`` para adicionar +mensagens ``dev_vdbg()`` às já habilitadas por ``DEBUG``. + +Muitos subsistemas têm opções de depuração do Kconfig para ativar ``-DDEBUG`` +no Makefile correspondente; em outros casos, arquivos específicos fazem +``#define DEBUG``. E quando uma mensagem de depuração deve ser impressa +incondicionalmente, por exemplo, se ela já estiver dentro de uma seção +``#ifdef`` relacionada à depuração, pode-se usar ``printk(KERN_DEBUG ...)``. + + +14) Alocando memória +-------------------- + +O kernel fornece os seguintes alocadores de memória de uso geral: +``kmalloc()``, ``kzalloc()``, ``kmalloc_objs()``, ``kzalloc_objs()``, +``vmalloc()`` e ``vzalloc()``. Consulte a documentação da API para obter mais +informações sobre eles. :ref:`Documentation/core-api/memory-allocation.rst +` + +A forma preferida de passar o tamanho de uma estrutura é a seguinte: + +.. code-block:: c + + p = kmalloc_obj(*p, ...); + +A forma alternativa em que o nome da estrutura é escrito explicitamente piora a +legibilidade e cria oportunidade para um bug quando o tipo da variável ponteiro +é alterado, mas o ``sizeof`` correspondente passado para um alocador de memória +não é. + +Casting do valor de retorno, que é um ponteiro ``void``, é redundante. A +conversão de ponteiro ``void`` para qualquer outro tipo de ponteiro é garantida +pela linguagem de programação C. + +A forma preferida para alocar um array é a seguinte: + +.. code-block:: c + + p = kmalloc_objs(*p, n, ...); + +A forma preferida para alocar um array zerado é a seguinte: + +.. code-block:: c + + p = kzalloc_objs(*p, n, ...); + +As duas formas verificam estouro no tamanho de alocação ``n * sizeof(...)`` e +retornam ``NULL`` se isso ocorrer. + +Essas funções genéricas de alocação emitem um dump de pilha em caso de falha +quando usadas sem ``__GFP_NOWARN``, então não há utilidade em emitir uma +mensagem de falha adicional quando ``NULL`` é retornado. + +15) A doença do inline +---------------------- + +Parece haver uma percepção errônea comum de que o gcc tem uma opção mágica de +aceleração chamada ``inline``. Embora o uso de ``inline`` possa ser apropriado +(por exemplo, como uma forma de substituir macros; veja o Capítulo 12), muitas +vezes não é. O uso abundante da palavra-chave ``inline`` leva a um kernel +muito maior, o que, por sua vez, torna o sistema mais lento como um todo, por +causa de uma maior ocupação de i-cache para a CPU e simplesmente porque há menos +memória disponível para o ``pagecache``. Pense nisso: uma falha no pagecache +causa um seek no disco, que facilmente leva 5 milissegundos. Há MUITOS ciclos +de CPU que podem entrar nesses 5 milissegundos. + +Uma regra prática razoável é não colocar ``inline`` em funções com mais de 3 +linhas de código. Uma exceção a essa regra são os casos em que um parâmetro é +conhecido como uma constante em tempo de compilação, e como resultado dessa +constância você *sabe* que o compilador será capaz de otimizar grande parte da +sua função em tempo de compilação. Para um bom exemplo desse caso posterior, +veja a função inline ``kmalloc()``. + +Muitas pessoas argumentam que adicionar ``inline`` a funções ``static`` usadas +apenas uma vez é sempre uma vantagem, porque não há custo de espaço. Embora +isso seja tecnicamente correto, o gcc é capaz de fazer esse inline +automaticamente sem ajuda, e a questão de manutenção de remover o ``inline`` +quando um segundo usuário aparece supera o valor potencial da dica que diz ao +gcc para fazer algo que ele faria de qualquer forma. + + +16) Valores e nomes de retorno de função +---------------------------------------- + +Funções podem retornar valores de vários tipos, e um dos mais comuns é um valor +que indica se a função teve sucesso ou falhou. Esse valor pode ser representado +como um inteiro de código de erro (-Exxx = falha, 0 = sucesso) ou como um +booleano ``succeeded`` (0 = falha, diferente de zero = sucesso). + +Misturar esses dois tipos de representação é uma fonte fértil de bugs difíceis +de encontrar. Se a linguagem C incluísse uma distinção forte entre inteiros e +booleanos, o compilador encontraria esses erros para nós... mas não inclui. Para +ajudar a evitar esses bugs, siga sempre esta convenção:: + + Se o nome de uma função for uma ação ou um comando imperativo, + a função deve retornar um inteiro de código de erro. Se o nome + for um predicado, a função deve retornar um booleano de "sucesso". + +Por exemplo, ``add work`` é um comando, e a função ``add_work()`` retorna 0 +para sucesso ou -EBUSY para falha. Da mesma forma, ``PCI device present`` é um +predicado, e a função ``pci_dev_present()`` retorna 1 se encontrar um device +correspondente ou 0 se não encontrar. + +Todas as funções ``EXPORT`` devem respeitar esta convenção, e assim também +devem todas as funções públicas. Funções privadas (``static``) não precisam, +mas é recomendável que o façam. + +Funções cujo valor de retorno é o resultado real de um cálculo, em vez de uma +indicação de se o cálculo teve sucesso, não estão sujeitas a essa regra. +Normalmente, elas indicam falha retornando algum resultado fora do intervalo. +Exemplos típicos seriam funções que retornam ponteiros; elas usam ``NULL`` ou o +mecanismo ``ERR_PTR`` para informar falha. + + +17) Usando bool +--------------- + +O tipo ``bool`` do kernel Linux é um alias do tipo C99 ``_Bool``. Valores +``bool`` só podem avaliar para 0 ou 1, e conversão implícita ou explícita para +``bool`` converte automaticamente o valor para verdadeiro ou falso. Ao usar +tipos ``bool``, a construção ``!!`` não é necessária, o que elimina uma classe +de bugs. + +Ao trabalhar com valores ``bool``, as definições ``true`` e ``false`` devem ser +usadas em vez de 1 e 0. + +Tipos de retorno de função ``bool`` e variáveis locais na pilha são sempre +válidos quando apropriados. O uso de ``bool`` é encorajado para melhorar a +legibilidade e muitas vezes é uma opção melhor do que ``int`` para armazenar +valores booleanos. + +Não use ``bool`` se o layout da linha de cache ou o tamanho do valor importar, +porque seu tamanho e alinhamento variam conforme a arquitetura compilada. +Estruturas otimizadas para alinhamento e tamanho não devem usar ``bool``. + +Se uma estrutura tiver muitos valores verdadeiro/falso, considere consolidá-los +em um ``bitfield`` com membros de 1 bit, ou usar um tipo de largura fixa +apropriado, como ``u8``. + +Da mesma forma, para argumentos de função, muitos valores verdadeiro/falso podem +ser consolidados em um único argumento de sinalizadores bit a bit, e +``flags`` muitas vezes pode ser uma alternativa mais legível se os pontos de +chamada tiverem constantes verdadeiras/falsas "nuas". + +Caso contrário, o uso limitado de ``bool`` em estruturas e argumentos pode +melhorar a legibilidade. + +18) Não reinventando as macros do kernel +---------------------------------------- + +Existem muitos arquivos de cabeçalho em ``include/linux/`` que contêm várias +macros que você deve usar em vez de escrever explicitamente alguma variante +delas. Por exemplo, se você precisa calcular o comprimento de um array, aproveite +a macro + +.. code-block:: c + + #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +que é definida em ``array_size.h``. + +Da mesma forma, se você precisar calcular o tamanho de um membro de alguma +estrutura, use + +.. code-block:: c + + #define sizeof_field(t, f) (sizeof(((t*)0)->f)) + +que é definida em ``stddef.h``. + +Também existem macros ``min()`` e ``max()`` definidas em ``minmax.h`` que fazem +verificação estrita de tipos se você precisar delas. Sinta-se à vontade para +explorar os arquivos de cabeçalho para ver o que já está definido e não deve +ser reproduzido no seu código. + + +19) Modelines do editor e outros restos +--------------------------------------- + +Alguns editores podem interpretar informações de configuração embutidas em +arquivos de origem, indicadas por marcadores especiais. Por exemplo, o emacs +interpreta linhas marcadas assim: + +.. code-block:: c + + -*- mode: c -*- + +Ou assim: + +.. code-block:: c + + /* + Local Variables: + compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c" + End: + */ + +O Vim interpreta marcadores que parecem com isto: + +.. code-block:: c + + /* vim:set sw=8 noet */ + +Não inclua nenhum desses em arquivos de origem. As pessoas têm suas próprias +configurações pessoais de editor, e seus arquivos de origem não devem +substituí-las. Isso inclui marcadores para indentação e configuração de modo. +As pessoas podem usar seu próprio modo personalizado, ou podem ter algum outro +método mágico para fazer a indentação funcionar corretamente. + + +20) Montagem inline +------------------- + +Em código específico de arquitetura, pode ser necessário usar montagem inline +para interagir com a funcionalidade da CPU ou da plataforma. Não hesite em +fazê-lo quando necessário. No entanto, não use montagem inline de forma +gratuita quando o C puder fazer o trabalho. Você pode e deve mexer em hardware +em C quando possível. + +Considere escrever funções auxiliares simples que encapsulem partes comuns de +montagem inline, em vez de escrevê-las repetidamente com pequenas variações. +Lembre-se de que a montagem inline pode usar parâmetros C. + +Funções grandes e não triviais de montagem devem ir para arquivos ``.S``, com +protótipos C correspondentes definidos em arquivos de cabeçalho C. Os +protótipos C para funções de montagem devem usar ``asmlinkage``. + +Você pode precisar marcar sua instrução ``asm`` como ``volatile`` para impedir +que o GCC a remova se o GCC não perceber efeitos colaterais. No entanto, você +nem sempre precisa fazer isso, e fazê-lo desnecessariamente pode limitar a +otimização. + +Ao escrever uma única instrução de montagem inline contendo várias instruções, +coloque cada instrução em uma linha separada em uma string separada e termine +cada string, exceto a última, com ``\n\t`` para indentar corretamente a +próxima instrução na saída de montagem: + +.. code-block:: c + + asm ("magic %reg1, #42\n\t" + "more_magic %reg2, %reg3" + : /* outputs */ : /* inputs */ : /* clobbers */); + + +21) Compilação condicional +-------------------------- + +Sempre que possível, não use condicionais do pré-processador (#if, #ifdef) em +arquivos ``.c``; isso torna o código mais difícil de ler e a lógica mais difícil +de seguir. Em vez disso, use esses condicionais em um arquivo de cabeçalho que +defina funções para uso nesses arquivos ``.c``, fornecendo versões de stub sem +efeito no caso ``#else``, e então chame essas funções incondicionalmente nos +arquivos ``.c``. O compilador evitará gerar qualquer código para as chamadas de +stub, produzindo resultados idênticos, mas a lógica permanecerá fácil de +seguir. + +Prefira compilar funções inteiras fora do código, em vez de partes de funções +ou partes de expressões. Em vez de colocar um ``ifdef`` em uma expressão, +extraia parte ou toda a expressão para uma função auxiliar separada e aplique a +condicional a essa função. + +Se você tiver uma função ou variável que pode potencialmente ficar sem uso em +uma configuração específica, e o compilador avisaria sobre a definição ficar sem +uso, marque a definição como ``__maybe_unused`` em vez de envolvê-la em uma +condicional do pré-processador. (No entanto, se uma função ou variável +*sempre* ficar sem uso, elimine-a.) + +Dentro do código, quando possível, use a macro ``IS_ENABLED`` para converter um +símbolo Kconfig em uma expressão booleana C e usá-la em uma condicional C +normal: + +.. code-block:: c + + if (IS_ENABLED(CONFIG_SOMETHING)) { + ... + } + +O compilador reduzirá a condicional a um valor constante e incluirá ou +excluirá o bloco de código assim como com um ``#ifdef``, então isso não +adicionará nenhum custo de runtime. No entanto, essa abordagem ainda permite +que o compilador C veja o código dentro do bloco e verifique sua correção +(sintaxe, tipos, referências de símbolo etc.). Portanto, você ainda precisa usar +um ``#ifdef`` se o código dentro do bloco referenciar símbolos que não existirão +se a condição não for atendida. + +No final de qualquer bloco ``#if`` ou ``#ifdef`` não trivial (mais de algumas +linhas), coloque um comentário após o ``#endif`` na mesma linha, indicando a +expressão condicional usada. Por exemplo: + +.. code-block:: c + + #ifdef CONFIG_SOMETHING + ... + #endif /* CONFIG_SOMETHING */ + + +22) Não derrube o kernel +------------------------ + +Em geral, a decisão de derrubar o kernel pertence ao usuário, e não ao +desenvolvedor do kernel. + +Evite ``panic()`` +***************** + +``panic()`` deve ser usado com cuidado e principalmente apenas durante a inicialização +do sistema. ``panic()`` é, por exemplo, aceitável ao ficar sem memória durante +a inicialização e não ser possível continuar. + +Use ``WARN()`` em vez de ``BUG()`` +********************************** + +Não adicione novo código que use nenhuma das variantes de ``BUG()``, como +``BUG()``, ``BUG_ON()`` ou ``VM_BUG_ON()``. Em vez disso, use uma variante de +``WARN*()``, preferencialmente ``WARN_ON_ONCE()``, e possivelmente com código de +recuperação. O código de recuperação não é obrigatório se não houver uma +maneira razoável de pelo menos recuperar parcialmente. + +"Sou preguiçoso para tratar erros" não é uma desculpa para usar ``BUG()``. +Corrupções internas graves sem como continuar ainda podem usar ``BUG()``, mas +precisam de uma boa justificativa. + +Use ``WARN_ON_ONCE()`` em vez de ``WARN()`` ou ``WARN_ON()`` +************************************************************ + +``WARN_ON_ONCE()`` geralmente é preferido em relação a ``WARN()`` ou +``WARN_ON()``, porque é comum que uma dada condição de aviso, se ocorrer, +ocorra várias vezes. Isso pode encher e sobrescrever o log do kernel e até +diminuir o sistema o suficiente para que o registro excessivo vire um problema +adicional. + +Não emita ``WARN`` levianamente +******************************* + +``WARN*()`` foi concebido para situações inesperadas, em que "isso nunca devia +acontecer". Macros ``WARN*()`` não devem ser usadas para nada que se espere que +aconteça durante a operação normal. Esses não são asserts de pré- ou pós- +condição, por exemplo. Novamente: ``WARN*()`` não deve ser usado para uma +condição que se espera que seja acionada facilmente, por exemplo, por ações do +espaço do usuário. ``pr_warn_once()`` é uma alternativa possível, se você +precisar notificar o usuário sobre um problema. + +Não se preocupe com usuários de ``panic_on_warn`` +************************************************* + +Mais algumas palavras sobre ``panic_on_warn``: lembre-se de que +``panic_on_warn`` é uma opção disponível do kernel, e muitos usuários a +habilitam. É por isso que existe um texto "Não emita WARN levianamente" acima. +Entretanto, a existência de usuários de ``panic_on_warn`` não é uma razão válida +para evitar o uso judicioso de ``WARN*()``. Isso ocorre porque quem habilita +``panic_on_warn`` explicitamente pediu ao kernel para travar se um ``WARN*()`` +for disparado, e esses usuários devem estar preparados para lidar com as +consequências de um sistema que tem uma chance um pouco maior de travar. + +Use ``BUILD_BUG_ON()`` para assertivas em tempo de compilação +************************************************************* + +O uso de ``BUILD_BUG_ON()`` é aceitável e encorajado, porque é uma assertiva +em tempo de compilação que não tem efeito em tempo de execução. + +Apêndice I) Referências +----------------------- + +The C Programming Language, Second Edition +by Brian W. Kernighan and Dennis M. Ritchie. +Prentice Hall, Inc., 1988. +ISBN 0-13-110362-8 (paperback), 0-13-110370-9 (hardback). + +The Practice of Programming +by Brian W. Kernighan and Rob Pike. +Addison-Wesley, Inc., 1999. +ISBN 0-201-61586-X. + +Manuais GNU - onde em conformidade com K&R e este texto - para cpp, gcc, +gcc internals e indent, todos disponíveis em https://www.gnu.org/manual/ + +WG14 é o grupo de trabalho de padronização internacional para a linguagem de +programação C, URL: http://www.open-std.org/JTC1/SC22/WG14/ + +Kernel CodingStyle, by greg@kroah.com at OLS 2002: +http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/ diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 7841eca6bf457a..38150e5666de46 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -38,6 +38,7 @@ devem estar familiarizados. :maxdepth: 1 Requisitos mínimos + Estilo de codificação do kernel Linux Informações sobre clientes de email para Linux Como aplicar patches Backporting e resolução de conflitos From bfa93ca5697d7b6f5f0a1d172ffa6fde02a79280 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 9 Sep 2026 09:48:45 -0300 Subject: [PATCH 0458/2286] docs: pt_BR: translate kernel-enforcement-statement.rst Translate Documentation/process/kernel-enforcement-statement.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. This document outlines the Linux kernel developers' statement regarding how the license for the software is enforced and compliance expectations. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260909124905.6156-3-danielmaraboo@gmail.com> --- .../translations/pt_BR/process/index.rst | 1 + .../process/kernel-enforcement-statement.rst | 163 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/kernel-enforcement-statement.rst diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 38150e5666de46..63282b053918e2 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -59,6 +59,7 @@ Estas são as regras pelas quais tentamos viver na comunidade do kernel Código de Conduta de Compromisso do Colaborador Interpretação do Código de Conduta do Kernel Linux Modelos de Maturidade para Contribuição no Kernel Linux + Declaração de Aplicação do Kernel Linux Declaração sobre Drivers do Kernel Estilo de gerenciamento do kernel Linux Assistentes de código diff --git a/Documentation/translations/pt_BR/process/kernel-enforcement-statement.rst b/Documentation/translations/pt_BR/process/kernel-enforcement-statement.rst new file mode 100644 index 00000000000000..3833c433c36b12 --- /dev/null +++ b/Documentation/translations/pt_BR/process/kernel-enforcement-statement.rst @@ -0,0 +1,163 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Declaração de Aplicação do Kernel Linux +--------------------------------------- + +Como desenvolvedores do kernel Linux, temos um grande interesse em como nosso +software é usado e como a licença do nosso software é aplicada. A conformidade +com as obrigações de compartilhamento recíproco da GPL-2.0 é crítica para a +sustentabilidade a longo prazo do nosso software e da nossa comunidade. + +Embora exista o direito de aplicar os interesses de direitos autorais separados nas +contribuições feitas à nossa comunidade, compartilhamos o interesse em garantir +que ações de aplicação individuais sejam conduzidas de uma maneira que beneficie +nossa comunidade e não tenham um impacto negativo não intencional na saúde e no +crescimento do nosso ecossistema de software. A fim de deter ações de aplicação +inúteis, concordamos que é do melhor interesse da nossa comunidade de +desenvolvimento assumir o seguinte compromisso com os usuários do kernel Linux, +em nosso nome e em nome de quaisquer sucessores dos nossos interesses de +direitos autorais: + + Não obstante as disposições de rescisão da GPL-2.0, concordamos que + é do melhor interesse da nossa comunidade de desenvolvimento adotar as + seguintes disposições da GPL-3.0 como permissões adicionais sob nossa + licença com relação a qualquer reivindicação não defensiva de direitos sob a + licença. + + No entanto, se você cessar toda violação desta Licença, sua licença + de um detentor de direitos autorais específico será restabelecida (a) + provisoriamente, a menos e até que o detentor de direitos autorais encerre + explícita e definitivamente sua licença, e (b) permanentemente, se o + detentor de direitos autorais não notificar você sobre a violação por + algum meio razoável antes de 60 dias após a cessação. + + Além disso, sua licença de um detentor de direitos autorais específico é + restabelecida permanentemente se o detentor de direitos autorais + notificá-lo da violação por algum meio razoável, se esta for a primeira + vez que você recebe um aviso de violação desta Licença (para qualquer + trabalho) daquele detentor de direitos autorais, e você sanar a violação + antes de 30 dias após o recebimento do aviso. + +Nossa intenção ao fornecer essas garantias é incentivar um maior uso do +software. Queremos que empresas e indivíduos usem, modifiquem e distribuam +este software. Queremos trabalhar com os usuários de forma aberta e transparente +para eliminar qualquer incerteza sobre nossas expectativas em relação à +conformidade ou aplicação que possa limitar a adoção do nosso software. Vemos +a ação judicial como um último recurso, a ser iniciada apenas quando outros +esforços da comunidade falharem em resolver o problema. + +Por fim, uma vez que um problema de não conformidade seja resolvido, esperamos +que o usuário se sinta bem-vindo para se juntar a nós em nossos esforços neste +projeto. Trabalhando juntos, seremos mais fortes. + +Exceto onde indicado abaixo, falamos apenas por nós mesmos, e não por qualquer +empresa para a qual possamos trabalhar hoje, no passado ou no futuro. + + - Bjorn Andersson (Linaro) + - Andrea Arcangeli + - Neil Armstrong + - Jens Axboe + - Pablo Neira Ayuso + - Khalid Aziz + - Ralf Baechle + - Felipe Balbi + - Arnd Bergmann + - Ard Biesheuvel + - Tim Bird + - Paolo Bonzini + - Christian Borntraeger + - Mark Brown (Linaro) + - Paul Burton + - Javier Martinez Canillas + - Rob Clark + - Kees Cook (Google) + - Jonathan Corbet + - Dennis Dalessandro + - Vivien Didelot (Savoir-faire Linux) + - Hans de Goede + - Mel Gorman (SUSE) + - Sven Eckelmann + - Alex Elder (Linaro) + - Fabio Estevam + - Larry Finger + - Bhumika Goyal + - Andy Gross + - Juergen Gross + - Shawn Guo + - Ulf Hansson + - Stephen Hemminger (Microsoft) + - Tejun Heo + - Rob Herring + - Masami Hiramatsu + - Michal Hocko + - Simon Horman + - Johan Hovold (Hovold Consulting AB) + - Christophe JAILLET + - Olof Johansson + - Lee Jones (Linaro) + - Heiner Kallweit + - Srinivas Kandagatla + - Jan Kara + - Shuah Khan (Samsung) + - David Kershner + - Jaegeuk Kim + - Namhyung Kim + - Colin Ian King + - Jeff Kirsher + - Greg Kroah-Hartman (Linux Foundation) + - Christian König + - Vinod Koul + - Krzysztof Kozlowski + - Viresh Kumar + - Aneesh Kumar K.V + - Julia Lawall + - Doug Ledford + - Chuck Lever (Oracle) + - Daniel Lezcano + - Shaohua Li + - Xin Long + - Tony Luck + - Catalin Marinas (Arm Ltd) + - Mike Marshall + - Chris Mason + - Paul E. McKenney + - Arnaldo Carvalho de Melo + - David S. Miller + - Ingo Molnar + - Kuninori Morimoto + - Trond Myklebust + - Martin K. Petersen (Oracle) + - Borislav Petkov + - Jiri Pirko + - Josh Poimboeuf + - Sebastian Reichel (Collabora) + - Guenter Roeck + - Joerg Roedel + - Leon Romanovsky + - Steven Rostedt (VMware) + - Frank Rowand + - Ivan Safonov + - Anna Schumaker + - Jes Sorensen + - K.Y. Srinivasan + - David Sterba (SUSE) + - Heiko Stuebner + - Jiri Kosina (SUSE) + - Willy Tarreau + - Dmitry Torokhov + - Linus Torvalds + - Thierry Reding + - Rik van Riel + - Luis R. Rodriguez + - Geert Uytterhoeven (Glider bvba) + - Eduardo Valentin (Amazon.com) + - Daniel Vetter + - Linus Walleij + - Richard Weinberger + - Dan Williams + - Rafael J. Wysocki + - Arvind Yadav + - Masahiro Yamada + - Wei Yongjun + - Lv Zheng + - Marc Zyngier (Arm Ltd) From 8353b533716bf6a5cc3fd9410095cdff97fe679f Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 9 Sep 2026 09:48:46 -0300 Subject: [PATCH 0459/2286] docs: pt_BR: translate maintainer-tip.rst Translate Documentation/process/maintainer-tip.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. This document serves as the handbook for the tip tree, providing subsystem specific information supplementary to the general development process. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260909124905.6156-4-danielmaraboo@gmail.com> --- .../translations/pt_BR/process/index.rst | 1 + .../pt_BR/process/maintainer-tip.rst | 847 ++++++++++++++++++ 2 files changed, 848 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/maintainer-tip.rst diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 63282b053918e2..e011b55a10a74f 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -63,6 +63,7 @@ Estas são as regras pelas quais tentamos viver na comunidade do kernel Declaração sobre Drivers do Kernel Estilo de gerenciamento do kernel Linux Assistentes de código + O manual da árvore tip Conclave (Continuidade do projeto) Lidando com bugs diff --git a/Documentation/translations/pt_BR/process/maintainer-tip.rst b/Documentation/translations/pt_BR/process/maintainer-tip.rst new file mode 100644 index 00000000000000..e583bad4282d61 --- /dev/null +++ b/Documentation/translations/pt_BR/process/maintainer-tip.rst @@ -0,0 +1,847 @@ +.. SPDX-License-Identifier: GPL-2.0 + +O manual da árvore tip +====================== + +O que é a árvore tip? +--------------------- + +A árvore tip é uma coleção de vários subsistemas e áreas de +desenvolvimento. A árvore tip é tanto uma árvore de desenvolvimento direto quanto uma +árvore de agregação para várias árvores de sub-mantenedores. A URL gitweb da árvore tip +é: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git + +A árvore tip contém os seguintes subsistemas: + + - **Arquitetura x86** + + O desenvolvimento da arquitetura x86 ocorre na árvore tip, exceto + pelas partes específicas do KVM e XEN no x86, que são mantidas nos + subsistemas correspondentes e roteadas diretamente para a mainline a partir + dali. Ainda é uma boa prática enviar Cc para os mantenedores x86 nos + patches do KVM e XEN específicos para x86. + + Alguns subsistemas x86 têm seus próprios mantenedores além dos + mantenedores gerais do x86. Por favor, envie Cc para os mantenedores gerais do x86 em + patches que toquem em arquivos em arch/x86, mesmo quando não forem indicados + pelo arquivo MAINTAINER. + + Note que ``x86@kernel.org`` não é uma lista de discussão. É meramente um + alias de e-mail que distribui mensagens para a equipe de mantenedores de nível superior + do x86. Por favor, sempre envie Cc para a lista de discussão do Linux Kernel (LKML) + ``linux-kernel@vger.kernel.org``, caso contrário, seu e-mail acabará apenas nas + caixas de entrada privadas dos mantenedores. + + - **Scheduler** + + O desenvolvimento do scheduler ocorre na árvore -tip, na + branch sched/core - com ocasionais árvores de subtópicos para + conjuntos de patches em progresso. + + - **Locking e atomics** + + O desenvolvimento de locking (incluindo atomics e outras primitivas de + sincronização que estão conectadas ao locking) ocorre na árvore -tip, + na branch locking/core - com ocasionais árvores de subtópicos + para conjuntos de patches em progresso. + + - **Subsistema genérico de interrupções e drivers de chip de interrupção**: + + - o desenvolvimento do núcleo de interrupções ocorre na branch irq/core + + - o desenvolvimento do driver de chip de interrupção também ocorre na branch + irq/core, mas os patches geralmente são aplicados em uma árvore de mantenedor + separada e depois agregados na irq/core + + - **Tempo, timers, timekeeping, NOHZ e drivers de chip relacionados**: + + - o desenvolvimento do timekeeping, núcleo clocksource, NTP e alarmtimer + ocorre na branch timers/core, mas os patches geralmente são aplicados em + uma árvore de mantenedor separada e depois agregados na timers/core + + - o desenvolvimento do driver clocksource/event ocorre na branch + timers/core, mas os patches são em sua maioria aplicados em uma árvore de mantenedor + separada e depois agregados na timers/core + + - **Núcleo de contadores de desempenho, suporte a arquitetura e ferramentas**: + + - o desenvolvimento do núcleo perf e suporte a arquitetura ocorre na + branch perf/core + + - o desenvolvimento de ferramentas perf ocorre na árvore do mantenedor + de ferramentas perf e é agregado à árvore tip. + + - **Núcleo de hotplug de CPU** + + - **Núcleo RAS** + + Em sua maioria, os patches RAS específicos para x86 são coletados na branch + ras/core da árvore tip. + + - **Núcleo EFI** + + Desenvolvimento EFI na árvore git efi. Os patches coletados são + agregados na branch efi/core da árvore tip. + + - **RCU** + + O desenvolvimento do RCU ocorre na árvore linux-rcu. As mudanças resultantes + são agregadas na branch core/rcu da árvore tip. + + - **Vários componentes de código do núcleo**: + + - debugobjects + + - objtool + + - partes e peças aleatórias + + +Notas de submissão de patch +--------------------------- + +Selecionando a árvore/branch +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Em geral, o desenvolvimento contra o head da branch master da árvore tip é +adequado, mas para os subsistemas que são mantidos separadamente, possuem sua +própria árvore git e são apenas agregados na árvore tip, o desenvolvimento deve +ocorrer contra a árvore ou branch do subsistema relevante. + +Correções de bugs que visam a mainline devem sempre ser aplicáveis contra a +árvore do kernel mainline. Potenciais conflitos contra mudanças que já estão +na fila da árvore tip são resolvidos pelos mantenedores. + +Assunto do patch +^^^^^^^^^^^^^^^^ + +O formato preferido da árvore tip para prefixos de assunto do patch é +'subsys/component:', ex. 'x86/apic:', 'x86/mm/fault:', 'sched/fair:', +'genirq/core:'. Por favor, não use nomes de arquivos ou caminhos de arquivos completos como +prefixo. 'git log path/to/file' deve lhe dar uma dica razoável na maioria +dos casos. + +A descrição condensada do patch na linha de assunto deve começar com uma +letra maiúscula e deve ser escrita em tom imperativo. + + +Changelog +^^^^^^^^^ + +As regras gerais sobre changelogs no :ref:`Guia de submissão de patches +`, se aplicam. + +Os mantenedores da árvore tip valorizam seguir essas regras, especialmente no +pedido para escrever changelogs no modo imperativo e não personificando +o código ou sua execução. Isso não é apenas um capricho dos +mantenedores. Changelogs escritos em palavras abstratas são mais precisos e +tendem a ser menos confusos do que aqueles escritos em forma de romances. + +Também é útil estruturar o changelog em vários parágrafos e não +juntar tudo em um só. Uma boa estrutura é explicar +o contexto, o problema e a solução em parágrafos separados e nesta +ordem. + +Exemplos para ilustração: + + Exemplo 1:: + + x86/intel_rdt/mbm: Corrigir o manipulador de overflow do MBM durante hot cpu + + Quando uma CPU está morrendo, cancelamos o worker e agendamos um novo worker em uma + CPU diferente no mesmo domínio. Mas se o timer já está prestes a + expirar (digamos 0.99s) então essencialmente dobramos o intervalo. + + Modificamos o tratamento de hot cpu para cancelar o trabalho atrasado na cpu + que está morrendo e executar o worker imediatamente em uma cpu diferente no mesmo domínio. Não + fazemos o flush do worker porque o worker de overflow do MBM reagenda o + worker na mesma CPU e escaneia a domain->cpu_mask para obter o ponteiro + do domínio. + + Versão melhorada:: + + x86/intel_rdt/mbm: Corrigir o manipulador de overflow do MBM durante hotplug de CPU + + Quando uma CPU está morrendo, o worker de overflow é cancelado e reagendado em uma + CPU diferente no mesmo domínio. Mas se o timer já estiver prestes a + expirar isso essencialmente dobra o intervalo, o que pode resultar em um overflow + não detectado. + + Cancele o worker de overflow e reagende-o imediatamente em uma CPU diferente + no mesmo domínio. O trabalho também poderia sofrer um flush, mas isso iria + reagendá-lo na mesma CPU. + + Exemplo 2:: + + time: POSIX CPU timers: Garantir que a variável seja inicializada + + Se cpu_timer_sample_group retornar -EINVAL, ela não terá escrito em + *sample. Checar o valor de retorno de cpu_timer_sample_group previne o + uso potencial de um valor não inicializado de now no bloco seguinte. + Dado um clock_idx inválido, o código anterior poderia caso contrário sobrescrever + *oldval de maneira indefinida. Isso agora é prevenido. Também exploramos + o curto-circuito do && para amostrar o timer apenas se o resultado for + realmente usado para atualizar *oldval. + + Versão melhorada:: + + posix-cpu-timers: Tornar set_process_cpu_timer() mais robusto + + Como o valor de retorno de cpu_timer_sample_group() não é checado, + compiladores e checadores estáticos podem legitimamente avisar sobre um uso potencial + da variável não inicializada 'now'. Isso não é um problema de tempo de execução pois todos + os locais de chamada passam ids de clock válidos. + + Além disso, cpu_timer_sample_group() é invocado incondicionalmente mesmo quando o + resultado não é usado porque *oldval é NULL. + + Torne a invocação condicional e cheque o valor de retorno. + + Exemplo 3:: + + A entidade também pode ser usada para outros propósitos. + + Vamos renomeá-la para ser mais genérica. + + Versão melhorada:: + + A entidade também pode ser usada para outros propósitos. + + Renomeie para ser mais genérica. + + +Para cenários complexos, especialmente condições de corrida (race conditions) e problemas +de ordenação de memória, é valioso descrever o cenário com uma tabela que mostra +o paralelismo e a ordem temporal dos eventos. Aqui está um exemplo:: + + CPU0 CPU1 + free_irq(X) interrupt X + spin_lock(desc->lock) + wake irq thread() + spin_unlock(desc->lock) + spin_lock(desc->lock) + remove action() + shutdown_irq() + release_resources() thread_handler() + spin_unlock(desc->lock) access released resources. + ^^^^^^^^^^^^^^^^^^^^^^^^^ + synchronize_irq() + +O Lockdep fornece uma saída útil semelhante para descrever um possível cenário +de deadlock:: + + CPU0 CPU1 + rtmutex_lock(&rcu->rt_mutex) + spin_lock(&rcu->rt_mutex.wait_lock) + local_irq_disable() + spin_lock(&timer->it_lock) + spin_lock(&rcu->mutex.wait_lock) + --> Interrupt + spin_lock(&timer->it_lock) + +Referências a funções em changelogs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Quando uma função é mencionada no changelog, seja no corpo do texto ou na +linha de assunto, por favor use o formato 'nome_da_funcao()'. Omitir os +parênteses após o nome da função pode ser ambíguo:: + + Subject: subsys/component: Make reservation_count static + + reservation_count is only used in reservation_stats. Make it static. + +A variante com parênteses é mais precisa:: + + Subject: subsys/component: Make reservation_count() static + + reservation_count() is only called from reservation_stats(). Make it + static. + + +Backtraces em changelogs +^^^^^^^^^^^^^^^^^^^^^^^^ + +Veja :ref:`pt_BR_backtraces`. + +Ordenação das tags de commit +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Para ter uma visão uniforme das tags de commit, os mantenedores da tip usam o +seguinte esquema de ordenação de tags: + + - Fixes: 12+char-SHA1 ("sub/sys: Original subject line") + + Uma tag Fixes deve ser adicionada mesmo para alterações que não precisam ser + portadas de volta (backported) para kernels estáveis, ou seja, quando abordar um + problema recém-introduzido que afeta apenas a árvore tip ou o head atual da linha principal (mainline). Estas tags + são úteis para identificar o commit original e são muito mais valiosas + do que mencionar de forma proeminente o commit que introduziu um problema no + próprio texto do changelog, porque elas podem ser automaticamente + extraídas. + + O exemplo a seguir ilustra a diferença:: + + Commit + + abcdef012345678 ("x86/xxx: Replace foo with bar") + + deixou uma instância não utilizada da variável foo. Remova-a. + + Signed-off-by: J.Dev + + Por favor, diga em vez disso:: + + A recente substituição de foo por bar deixou uma instância não utilizada da + variável foo. Remova-a. + + Fixes: abcdef012345678 ("x86/xxx: Replace foo with bar") + Signed-off-by: J.Dev + + O último coloca as informações sobre o patch em foco e + as complementa com a referência ao commit que introduziu o problema, + em vez de colocar o foco no commit original em primeiro lugar. + + - Reported-by: ``Reporter `` + + - Closes: ``URL or Message-ID of the bug report this is fixing`` + + - Originally-by: ``Original author `` + + - Suggested-by: ``Suggester `` + + - Co-developed-by: ``Co-author `` + + Signed-off-by: ``Co-author `` + + Note que Co-developed-by e Signed-off-by do(s) co-autor(es) devem + vir em pares. + + - Signed-off-by: ``Author `` + + O primeiro Signed-off-by (SOB) após o último par Co-developed-by/SOB é o + SOB do autor, ou seja, a pessoa marcada como autora pelo git. + + - Signed-off-by: ``Patch handler `` + + SOBs após o SOB do autor são de pessoas que lidam e transportam + o patch, mas não estiveram envolvidas no desenvolvimento. As cadeias de SOB devem + refletir a rota **real** que um patch tomou conforme foi propagado para nós, + com a primeira entrada de SOB sinalizando a autoria principal de um único + autor. Acks devem ser dados como linhas Acked-by e aprovações de revisão + como linhas Reviewed-by. + + Se o manipulador fez modificações no patch ou no changelog, então + isso deve ser mencionado **após** o texto do changelog e **acima** + de todas as tags de commit no seguinte formato:: + + ... o texto do changelog termina. + + [ handler: Substituiu foo por bar e atualizou o changelog ] + + First-tag: ..... + + Observe as duas novas linhas vazias que separam o texto do changelog e as + tags de commit daquele aviso. + + Se um patch for enviado para a lista de discussão por um manipulador, então o autor tem + que ser notado na primeira linha do changelog com:: + + From: Author + + O texto do changelog começa aqui.... + + assim a autoria é preservada. A linha 'From:' tem que ser seguida + por uma nova linha vazia. Se essa linha 'From:' estiver faltando, então o patch + seria atribuído à pessoa que o enviou (transportou, manipulou). + A linha 'From:' é automaticamente removida quando o patch é aplicado + e não aparece no changelog final do git. Ela meramente afeta + a informação de autoria do commit resultante do Git. + + - Tested-by: ``Tester `` + + - Reviewed-by: ``Reviewer `` + + - Acked-by: ``Acker `` + + - Cc: ``cc-ed-person `` + + Se o patch deve ser portado para stable, então por favor adicione uma tag '``Cc: + stable@vger.kernel.org``', mas não coloque em Cc o stable ao enviar o seu + e-mail. + + - Link: ``https://link/to/information`` + + Para se referir a um e-mail postado nas listas de discussão do kernel, por favor + use o URL de redirecionamento lore.kernel.org:: + + Link: https://lore.kernel.org/email-message-id@here + + Esta URL deve ser usada ao se referir a tópicos de lista de discussão relevantes, + conjuntos de patches relacionados, ou outras threads de discussão notáveis. + Uma maneira conveniente de associar os trailers ``Link:`` com a mensagem de commit + é usar a notação de colchetes semelhante ao markdown, por exemplo:: + + A similar approach was attempted before as part of a different + effort [1], but the initial implementation caused too many + regressions [2], so it was backed out and reimplemented. + + Link: https://lore.kernel.org/some-msgid@here # [1] + Link: https://bugzilla.example.org/bug/12345 # [2] + + Você também pode usar os trailers ``Link:`` para indicar a origem do + patch ao aplicá-lo em sua árvore git. Neste caso, por favor use o + domínio dedicado ``patch.msgid.link`` em vez de ``lore.kernel.org``. + Esta prática torna possível que as ferramentas automatizadas identifiquem + qual link usar para recuperar o envio do patch original. Por + exemplo:: + + Link: https://patch.msgid.link/patch-source-message-id@here + +Por favor não use tags combinadas, ex. ``Reported-and-tested-by``, pois +elas apenas complicam a extração automatizada de tags. + + +Links para documentação +^^^^^^^^^^^^^^^^^^^^^^^ + +Fornecer links para a documentação no changelog é uma grande ajuda para depuração e +análise posteriores. Infelizmente, os URLs costumam quebrar muito rapidamente +porque as empresas reestruturam seus sites frequentemente. Exceções não 'voláteis' +incluem o Intel SDM e o AMD APM. + +Portanto, para documentos 'voláteis', por favor crie uma entrada no bugzilla do kernel +https://bugzilla.kernel.org e anexe uma cópia desses documentos +à entrada do bugzilla. Finalmente, forneça o URL da entrada do bugzilla no +changelog. + +Reenvio de patch ou lembretes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Veja :ref:`pt_BR_resend_reminders`. + +Janela de merge +^^^^^^^^^^^^^^^ + +Por favor, não espere que os patches sejam revisados ou mesclados pelos mantenedores da árvore tip +em torno ou durante a janela de merge. As árvores ficam fechadas +para todos, exceto correções urgentes, durante esse tempo. Elas reabrem assim que a janela de merge +fecha e um novo kernel -rc1 é lançado. + +Grandes séries devem ser enviadas em estado mesclável (mergeable state) *pelo* *menos* uma semana +antes da janela de merge abrir. Exceções são feitas para correções de bugs e +*às vezes* para pequenos drivers independentes para novos hardwares ou patches minimamente +invasivos para ativação de hardware. + +Durante a janela de merge, os mantenedores se concentram em seguir as +alterações upstream, corrigir problemas resultantes da janela de merge, coletar correções de bugs, e +se permitir um respiro. Por favor, respeite isso. + +Os chamados branches _urgent_ serão mesclados na linha principal (mainline) durante a +fase de estabilização de cada versão. + + +Git +^^^ + +Os mantenedores da árvore tip aceitam pull requests do git de mantenedores que fornecem +alterações de subsistema para agregação na árvore tip. + +Pull requests para novos envios de patches normalmente não são aceitos e não +substituem o envio adequado de patch para a lista de discussão. O principal motivo para +isso é que o fluxo de trabalho de revisão é baseado em e-mail. + +Se você enviar uma série maior de patches, é útil fornecer um branch git +em um repositório privado que permita que pessoas interessadas façam pull da +série facilmente para testes. A maneira usual de oferecer isso é uma URL do git na carta de apresentação (cover letter) +da série de patches. + +Testes +^^^^^^ + +O código deve ser testado antes de ser enviado para os mantenedores da árvore tip. Qualquer coisa +além de alterações menores deve ser construída, inicializada e testada com +opções abrangentes (e pesadas) de depuração do kernel ativadas. + +Essas opções de depuração podem ser encontradas em kernel/configs/x86_debug.config +e podem ser adicionadas a uma configuração de kernel existente executando: + + make x86_debug.config + +Algumas dessas opções são específicas do x86 e podem ser deixadas de fora ao testar +em outras arquiteturas. + +.. _pt_BR_maintainer-tip-coding-style: + +Notas de estilo de código +------------------------- + +Estilo de comentário +^^^^^^^^^^^^^^^^^^^^ + +Frases em comentários começam com uma letra maiúscula. + +Comentários de linha única:: + + /* Este é um comentário de linha única */ + +Comentários de várias linhas:: + + /* + * This is a properly formatted + * multi-line comment. + * + * Larger multi-line comments should be split into paragraphs. + */ + +Sem comentários no fim da linha (veja abaixo): + + Por favor, abstenha-se de usar comentários no fim da linha. Comentários no fim da linha atrapalham o + fluxo de leitura em quase todos os contextos, mas especialmente em código:: + + if (somecondition_is_true) /* Não coloque um comentário aqui */ + dostuff(); /* Nem aqui */ + + seed = MAGIC_CONSTANT; /* Nem aqui */ + + Use comentários independentes em vez disso:: + + /* Esta condição não é óbvia sem um comentário */ + if (somecondition_is_true) { + /* Isso realmente precisa ser documentado */ + dostuff(); + } + + /* Esta inicialização mágica precisa de um comentário. Talvez não? */ + seed = MAGIC_CONSTANT; + + Use o estilo C++, comentários no fim da linha ao documentar structs em headers para + alcançar um layout mais compacto e melhor legibilidade:: + + // eax + u32 x2apic_shift : 5, // Número de bits para deslocar o ID APIC para a direita + // para o ID de topologia no próximo nível + : 27; // Reservado + // ebx + u32 num_processors : 16, // Número de processadores no nível atual + : 16; // Reservado + + versus:: + + /* eax */ + /* + * Número de bits para deslocar o ID APIC para a direita para o ID de topologia + * no próximo nível + */ + u32 x2apic_shift : 5, + /* Reservado */ + : 27; + + /* ebx */ + /* Número de processadores no nível atual */ + u32 num_processors : 16, + /* Reservado */ + : 16; + +Comente as coisas importantes: + + Comentários devem ser adicionados onde a operação não é óbvia. Documentar + o óbvio é apenas uma distração:: + + /* Decrementa o refcount e verifica por zero */ + if (refcount_dec_and_test(&p->refcnt)) { + do; + lots; + of; + magic; + things; + } + + Em vez disso, os comentários devem explicar os detalhes não óbvios e documentar + as restrições:: + + if (refcount_dec_and_test(&p->refcnt)) { + /* + * Explicação muito boa de por que as coisas mágicas abaixo + * precisam ser feitas, restrições de ordenação e locking, + * etc.. + */ + do; + lots; + of; + magic; + /* Precisa ser a última operação porque ... */ + things; + } + +Comentários de documentação de função: + + Para documentar funções e seus argumentos por favor use o formato kernel-doc + e não comentários de formato livre:: + + /** + * magic_function - Faz muitas coisas mágicas + * @magic: Ponteiro para os dados mágicos nos quais operar + * @offset: Deslocamento no array de dados de @magic + * + * Explicação profunda das coisas misteriosas feitas com @magic junto + * com a documentação dos valores de retorno. + * + * Note que os descritores de argumento acima estão dispostos + * de forma tabular. + */ + + Isto se aplica especialmente a funções visíveis globalmente e funções + inline em arquivos de cabeçalho públicos. Pode ser um exagero usar o formato + kernel-doc para cada função (estática) que precisa de uma pequena explicação. O + uso de nomes de funções descritivos frequentemente substitui esses pequenos comentários. + Aplique o bom senso como sempre. + + +Documentando requisitos de locking +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Documentar requisitos de locking é uma coisa boa, mas comentários não + são necessariamente a melhor escolha. Em vez de escrever:: + + /* Caller must hold foo->lock */ + void func(struct foo *foo) + { + ... + } + + Por favor, use:: + + void func(struct foo *foo) + { + lockdep_assert_held(&foo->lock); + ... + } + + Em kernels PROVE_LOCKING, lockdep_assert_held() emite um aviso + se o chamador não detém o lock. Comentários não podem fazer isso. + +Regras de chaves +^^^^^^^^^^^^^^^^ + +Chaves devem ser omitidas apenas se a instrução que se segue a 'if', 'for', +'while' etc. for verdadeiramente uma única linha:: + + if (foo) + do_something(); + +O seguinte não é considerado uma instrução de linha única mesmo +que o C não exija chaves:: + + for (i = 0; i < end; i++) + if (foo[i]) + do_something(foo[i]); + +Adicionar chaves ao redor do loop externo melhora o fluxo de leitura:: + + for (i = 0; i < end; i++) { + if (foo[i]) + do_something(foo[i]); + } + + +Declarações de variáveis +^^^^^^^^^^^^^^^^^^^^^^^^ + +A ordem preferida das declarações de variáveis no início de uma +função é a ordem de árvore de abeto invertida (reverse fir tree order):: + + struct long_struct_name *descriptive_name; + unsigned long foo, bar; + unsigned int tmp; + int ret; + +O que está acima é mais rápido de analisar do que a ordem invertida:: + + int ret; + unsigned int tmp; + unsigned long foo, bar; + struct long_struct_name *descriptive_name; + +E ainda mais do que uma ordem aleatória:: + + unsigned long foo, bar; + int ret; + struct long_struct_name *descriptive_name; + unsigned int tmp; + +Também por favor tente agregar variáveis do mesmo tipo em uma única +linha. Não há sentido em desperdiçar espaço na tela:: + + unsigned long a; + unsigned long b; + unsigned long c; + unsigned long d; + +É realmente suficiente fazer:: + + unsigned long a, b, c, d; + +Por favor, evite também introduzir divisões de linha em declarações de variáveis:: + + struct long_struct_name *descriptive_name = container_of(bar, + struct long_struct_name, + member); + struct foobar foo; + +É muito melhor mover a inicialização para uma linha separada após as +declarações:: + + struct long_struct_name *descriptive_name; + struct foobar foo; + + descriptive_name = container_of(bar, struct long_struct_name, member); + + +Tipos de variáveis +^^^^^^^^^^^^^^^^^^ + +Por favor use os tipos u8, u16, u32, u64 adequados para variáveis que são destinadas +a descrever hardware ou são usadas como argumentos para funções que acessam +hardware. Estes tipos definem claramente a largura em bits e evitam +truncamento, expansão e confusão entre 32/64 bits. + +u64 também é recomendado em código que se tornaria ambíguo para kernels +de 32 bits quando 'unsigned long' fosse usado em vez disso. Embora em tais +situações 'unsigned long long' pudesse ser usado também, u64 é mais curto +e também mostra claramente que a operação requer uma largura de 64 bits +independente da CPU alvo. + +Por favor use 'unsigned int' em vez de 'unsigned'. + + +Constantes +^^^^^^^^^^ + +Por favor, não use números (hexa)decimais literais em código ou inicializadores. +Ou use defines adequados que tenham nomes descritivos ou considere usar +um enum. + + +Declarações e inicializadores de struct +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +As declarações de struct devem alinhar os nomes dos membros da struct de forma +tabular:: + + struct bar_order { + unsigned int guest_id; + int ordered_item; + struct menu *menu; + }; + +Por favor, evite documentar os membros da struct dentro da declaração, pois +isso frequentemente resulta em comentários formatados de maneira estranha e os membros da struct +ficam ofuscados:: + + struct bar_order { + unsigned int guest_id; /* ID único do convidado */ + int ordered_item; + /* Ponteiro para uma instância de menu que contém todas as bebidas */ + struct menu *menu; + }; + +Em vez disso, por favor considere usar o formato kernel-doc em um comentário precedendo +a declaração da struct, que é mais fácil de ler e tem a vantagem adicional +de incluir a informação na documentação do kernel, por exemplo, da +seguinte forma:: + + + /** + * struct bar_order - Descrição de um pedido de bar + * @guest_id: ID único do convidado + * @ordered_item: O número do item do menu + * @menu: Ponteiro para o menu do qual o item + * foi pedido + * + * Informação suplementar para usar a struct. + * + * Note que os descritores dos membros da struct acima estão dispostos + * de forma tabular. + */ + struct bar_order { + unsigned int guest_id; + int ordered_item; + struct menu *menu; + }; + +Inicializadores de struct estáticos devem usar inicializadores C99 e também devem ser +alinhados de forma tabular:: + + static struct foo statfoo = { + .a = 0, + .plain_integer = CONSTANT_DEFINE_OR_ENUM, + .bar = &statbar, + }; + +Note que embora a sintaxe C99 permita a omissão da vírgula final, +nós recomendamos o uso de uma vírgula na última linha porque isso torna +o reordenamento e a adição de novas linhas mais fáceis, e também torna tais +patches futuros ligeiramente mais fáceis de ler. + +Quebras de linha +^^^^^^^^^^^^^^^^ + +Restringir o comprimento da linha a 80 caracteres torna código profundamente indentado difícil de +ler. Considere dividir o código em funções auxiliares para evitar quebra de +linha excessiva. + +A regra de 80 caracteres não é uma regra estrita, então por favor use bom senso ao +quebrar linhas. Especialmente strings de formato nunca devem ser divididas. + +Ao dividir declarações de funções ou chamadas de funções, então por favor alinhe +o primeiro argumento na segunda linha com o primeiro argumento na primeira +linha:: + + static int long_function_name(struct foobar *barfoo, unsigned int id, + unsigned int offset) + { + + if (!id) { + ret = longer_function_name(barfoo, DEFAULT_BARFOO_ID, + offset); + ... + +Namespaces +^^^^^^^^^^ + +Namespaces de funções/variáveis melhoram a legibilidade e permitem +grepping fácil. Estes namespaces são prefixos de string para nomes +de funções e variáveis visíveis globalmente, incluindo inlines. Estes prefixos devem +combinar o subsistema e o nome do componente como 'x86_comp\_', +'sched\_', 'irq\_', e 'mutex\_'. + +Isso também inclui funções estáticas de escopo de arquivo que são imediatamente colocadas +em templates de driver visíveis globalmente - é útil que esses símbolos +também carreguem um bom prefixo, para legibilidade do backtrace. + +Prefixos de namespace podem ser omitidos para funções e variáveis +estáticas locais. Funções verdadeiramente locais, chamadas apenas por outras funções locais, +podem ter nomes descritivos mais curtos - nossa preocupação principal é a facilidade de grepping +e a legibilidade do backtrace. + +Por favor note que os prefixos 'xxx_vendor\_' e 'vendor_xxx\_' não são +úteis para funções estáticas em arquivos específicos de fornecedores. Afinal, +já está claro que o código é específico do fornecedor. Além disso, nomes +de fornecedores devem ser apenas para funcionalidades verdadeiramente específicas de fornecedores. + +Como sempre, aplique o bom senso e vise a consistência e a legibilidade. + + +Notificações de commit +---------------------- + +A árvore tip é monitorada por um bot por novos commits. O bot envia um email +para cada novo commit para uma lista de discussão dedicada +(``linux-tip-commits@vger.kernel.org``) e coloca em Cc todas as pessoas que são +mencionadas em uma das tags de commit. Ele usa o ID da mensagem de email da +tag Link no final da lista de tags para definir o cabeçalho de email In-Reply-To para que +a mensagem seja encadeada corretamente com o email de submissão do patch. + +Os mantenedores e submantenedores tip tentam responder ao remetente +ao fazer o merge de um patch, mas às vezes eles esquecem ou isso não se encaixa no +fluxo de trabalho do momento. Embora a mensagem do bot seja puramente mecânica, ela +também implica em um 'Obrigado! Aplicado.'. From 0fcf270588d60091f3fdd4bea6bc4e4403d9528c Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 9 Sep 2026 09:48:47 -0300 Subject: [PATCH 0460/2286] docs: pt_BR: translate submitting-patches.rst Translate Documentation/process/submitting-patches.rst into Brazilian Portuguese and add it to the pt_BR process documentation index. This document contains guidelines and essential instructions for developers submitting code and patches to the Linux kernel. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260909124905.6156-5-danielmaraboo@gmail.com> --- .../translations/pt_BR/process/2.Process.rst | 2 +- .../translations/pt_BR/process/index.rst | 1 + .../pt_BR/process/maintainer-kvm-x86.rst | 6 +- .../pt_BR/process/submitting-patches.rst | 961 ++++++++++++++++++ 4 files changed, 966 insertions(+), 4 deletions(-) create mode 100644 Documentation/translations/pt_BR/process/submitting-patches.rst diff --git a/Documentation/translations/pt_BR/process/2.Process.rst b/Documentation/translations/pt_BR/process/2.Process.rst index 03e288d469502b..0019b7404d33a7 100644 --- a/Documentation/translations/pt_BR/process/2.Process.rst +++ b/Documentation/translations/pt_BR/process/2.Process.rst @@ -455,7 +455,7 @@ Existem algumas dicas que podem ajudar na sobrevivência na lista linux-kernel: - Use respostas intercaladas, o que torna sua resposta mais fácil de ler. (ou seja, evite o "top-posting" — a prática de colocar sua resposta acima do texto citado ao qual você está respondendo). Para mais detalhes, veja - :ref:`Documentation/process/submitting-patches.rst `. + :ref:`Documentation/process/submitting-patches.rst `. - Pergunte na lista de discussão correta. A lista linux-kernel pode até ser o ponto de encontro geral, mas não é o melhor lugar para encontrar desenvolvedores diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index e011b55a10a74f..2c63d82bff67fd 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -26,6 +26,7 @@ sua entrada na comunidade do kernel. Como começar Guia do Processo de Desenvolvimento + Enviando patches: o guia essencial para colocar o seu código no kernel Lista de verificação para submissão de patches do kernel Linux Ferramentas e guias técnicos para desenvolvedores do kernel diff --git a/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst b/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst index 6480ff08b9d8be..d9fd272fb866d6 100644 --- a/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst +++ b/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst @@ -122,7 +122,7 @@ Quando se trata de estilo, nomenclatura, padrões, etc., a consistência é a prioridade número um no KVM x86. Se tudo mais falhar, siga o que já existe. Com algumas ressalvas listadas abaixo, siga o estilo de codificação preferido -dos mantenedores da árvore "tip" (:ref:`maintainer-tip-coding-style`), já que +dos mantenedores da árvore "tip" (:ref:`pt_BR_maintainer-tip-coding-style`), já que patches/séries frequentemente tocam tanto arquivos do KVM quanto arquivos x86 não-KVM, ou seja, atraem a atenção de mantenedores do KVM *e* da árvore "tip". @@ -206,7 +206,7 @@ Novos tópicos surgem ocasionalmente, mas, por favor, inicie uma discussão na lista se desejar propor a introdução de um novo tópico; ou seja, não aja por conta própria. -Veja :ref:`the_canonical_patch_format` para mais informações, com uma ressalva: +Veja :ref:`pt_BR_the_canonical_patch_format` para mais informações, com uma ressalva: não trate o limite de 70-75 caracteres como um limite absoluto e rígido. Em vez disso, use 75 caracteres como um limite firme, mas não rígido, e use 80 caracteres como um limite intransponível. Ou seja, permita que o shortlog @@ -218,7 +218,7 @@ Changelog O mais importante: escreva os changelogs usando o modo imperativo e evite o uso de pronomes. -Veja :ref:`describe_changes` para mais informações, com uma ressalva: comece com +Veja :ref:`pt_BR_describe_changes` para mais informações, com uma ressalva: comece com uma breve descrição das mudanças reais e, em seguida, apresente o contexto e o histórico. Note! Esta ordem entra em conflito direto com a abordagem preferida da árvore "tip"! Por favor, siga o estilo preferido da árvore "tip" ao enviar diff --git a/Documentation/translations/pt_BR/process/submitting-patches.rst b/Documentation/translations/pt_BR/process/submitting-patches.rst new file mode 100644 index 00000000000000..8cf1aae52005d0 --- /dev/null +++ b/Documentation/translations/pt_BR/process/submitting-patches.rst @@ -0,0 +1,961 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Enviando patches: o guia essencial para colocar o seu código no kernel +====================================================================== + +Para uma pessoa ou empresa que deseja enviar uma mudança para o +kernel Linux, o processo pode, por vezes, ser intimidador se você não +estiver familiarizado com "o sistema". Este texto é uma coleção de sugestões +que podem aumentar muito as chances de sua mudança ser aceita. + +Este documento contém um grande número de sugestões em um formato relativamente +conciso. Para informações detalhadas sobre como funciona o processo de +desenvolvimento do kernel, consulte Documentation/process/development-process.rst. +Além disso, leia Documentation/process/submit-checklist.rst +para uma lista de itens a serem verificados antes de enviar o código. +Para patches de binding de device tree, leia +Documentation/devicetree/bindings/submitting-patches.rst. + +Esta documentação assume que você está usando o ``git`` para preparar seus +patches. Se você não está familiarizado com o ``git``, é muito recomendado que +você aprenda a usá-lo, ele tornará a sua vida como um desenvolvedor do kernel e, +em geral, muito mais fácil. + +Alguns subsistemas e árvores de mantenedores possuem informações adicionais +sobre seus fluxos de trabalho e expectativas, consulte +Documentation/process/maintainer-handbooks.rst. + +Obtenha uma árvore de código-fonte atual +---------------------------------------- + +Se você não tiver um repositório com o código-fonte atual do kernel em mãos, +use o ``git`` para obter um. Você vai querer começar com o repositório mainline, +que pode ser obtido com:: + + git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + +Note, no entanto, que você pode não querer desenvolver diretamente na +árvore mainline. A maioria dos mantenedores de subsistemas mantém suas +próprias árvores e desejam ver os patches preparados em relação a essas árvores. +Consulte a entrada **T:** do subsistema no arquivo MAINTAINERS para encontrar +essa árvore, ou simplesmente pergunte ao mantenedor se a árvore não estiver +listada lá. + +.. _pt_BR_describe_changes: + +Descreva as suas mudanças +------------------------- + +Descreva o seu problema. Seja o seu patch uma correção de bug de uma linha ou +5000 linhas de um novo recurso, deve haver um problema subjacente que o motivou +a fazer esse trabalho. Convença o revisor de que existe um problema que vale a +pena corrigir e que faz sentido que ele leia além do primeiro parágrafo. + +Descreva o impacto visível ao usuário. Travamentos e bloqueios diretos são +bastante convincentes, mas nem todos os bugs são tão evidentes. Mesmo que o +problema tenha sido identificado durante a revisão do código, descreva o impacto +que você acredita que ele pode ter sobre os usuários. Tenha em mente que a +maioria das instalações Linux executa kernels de árvores estáveis secundárias +ou árvores específicas de fornecedores/produtos que selecionam apenas patches +específicos do upstream, então inclua qualquer coisa que possa ajudar a +direcionar sua mudança downstream: circunstâncias provocadoras, trechos do +dmesg, descrições do travamento, regressões de desempenho, picos de latência, +bloqueios, etc. + +Quantifique as otimizações e compensações. Se você afirma haver melhorias no +desempenho, consumo de memória, uso da pilha ou tamanho do binário, inclua +números que as comprovem. Mas também descreva custos que não são óbvios. +Otimizações geralmente não são gratuitas, sendo trocas entre CPU, memória e +legibilidade; ou, quando se trata de heurísticas, entre diferentes cargas de +trabalho. Descreva as desvantagens esperadas da sua otimização para que o +revisor possa pesar os custos contra os benefícios. + +Uma vez estabelecido o problema, descreva o que você está efetivamente fazendo +sobre ele, com detalhes técnicos. É importante descrever a mudança em inglês +claro para o revisor verificar que o código está se comportando como +você pretendia. + +O mantenedor agradecerá se você escrever a descrição do seu patch em uma forma +que possa ser facilmente inserida no sistema de gerenciamento de código-fonte do +Linux, o ``git``, como uma "mensagem de commit". Veja +:ref:`pt_BR_the_canonical_patch_format`. + +Resolva apenas um problema por patch. Se a sua descrição começar a ficar longa, +isso é um sinal de que você provavelmente precisa dividir o seu patch. +Consulte :ref:`pt_BR_split_changes`. + +Quando você enviar ou reenviar um patch ou uma série de patches, inclua a +descrição completa do patch e a justificativa para ele. Não diga apenas +que esta é a versão N do patch (ou série). Não espere que o mantenedor +do subsistema consulte versões anteriores do patch ou URLs de referência +para encontrar a descrição do patch e colocá-la no patch. +Ou seja, o patch (ou a série) e sua descrição devem ser autossuficientes. +Isso beneficia tanto os mantenedores quanto os revisores. Alguns revisores +provavelmente nem chegaram a receber as versões anteriores do patch. + +Descreva suas alterações no modo imperativo, por exemplo, "faça xyzzy executar +frotz" em vez de "[Este patch] faz xyzzy executar frotz" ou "[Eu] mudei xyzzy +para executar frotz", como se você estivesse dando ordens à base de código para +mudar o seu comportamento. + +Se você quiser se referir a um commit específico, não se refira apenas ao +ID SHA-1 do commit. Por favor, inclua também o resumo de uma linha do +commit, para tornar mais fácil para os revisores saberem sobre o que se trata. +Exemplo:: + + Commit e21d2170f36602ae2708 ("video: remove unnecessary + platform_set_drvdata()") removed the unnecessary + platform_set_drvdata(), but left the variable "dev" unused, + delete it. + +Você também deve ter a certeza de usar pelo menos os primeiros doze caracteres do +ID SHA-1. O repositório do kernel possui um número *muito* grande de objetos, o que torna as +colisões com IDs mais curtos uma possibilidade real. Tenha em mente que, mesmo que +não haja colisão com o seu ID de seis caracteres agora, essa condição pode +mudar daqui a cinco anos. + +Se discussões relacionadas ou qualquer outra informação de contexto por trás da mudança +puderem ser encontradas na web, adicione tags 'Link:' apontando para isso. Se o patch é o +resultado de algumas discussões anteriores na lista de e-mails ou algo documentado na +web, aponte para ele. + +Ao criar links para arquivos de listas de e-mails, de preferência use o serviço +de arquivo de mensagens lore.kernel.org. Para criar a URL do link, use o +conteúdo do cabeçalho ``Message-ID`` da mensagem, sem os colchetes angulares +circundantes. Por exemplo:: + + Link: https://lore.kernel.org/30th.anniversary.repost@klaava.Helsinki.FI + +Por favor, verifique o link para se certificar de que ele está realmente +funcionando e aponta para a mensagem relevante. + +No entanto, tente tornar a sua explicação compreensível sem recursos +externos. Além de fornecer um URL para um arquivo da lista de e-mails ou bug, +resuma os pontos relevantes da discussão que levaram ao patch conforme enviado. + +Caso o seu patch corrija um bug, use a tag 'Closes:' com um URL que referencie o +relato nos arquivos da lista de e-mails ou em um rastreador público de bugs. Por exemplo:: + + Closes: https://example.com/issues/1234 + +Alguns rastreadores de bugs têm a capacidade de fechar os problemas +automaticamente quando um commit com tal tag é aplicado. Alguns bots que monitoram as listas de +e-mails também podem rastrear tais tags e tomar certas ações. Rastreadores de bugs +privados e URLs inválidos são proibidos. + +Se o seu patch corrige um bug em um commit específico, por exemplo, você encontrou um problema usando +``git bisect``, por favor, use a tag 'Fixes:' com pelo menos os primeiros 12 +caracteres do ID SHA-1 e o resumo de uma linha. Não divida a tag em +várias linhas, as tags estão isentas da regra de "quebra de linha nas 75 colunas" para +simplificar os scripts de parsing. Por exemplo:: + + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") + +As seguintes configurações do ``git config`` podem ser usadas para adicionar um formato aprimorado para +exibir o estilo acima nos comandos ``git log`` ou ``git show``:: + + [core] + abbrev = 12 + [pretty] + fixes = Fixes: %h (\"%s\") + +Um exemplo de chamada:: + + $ git log -1 --pretty=fixes 54a4f0239f2e + Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") + +.. _pt_BR_split_changes: + +Separe as suas mudanças +----------------------- + +Separe cada **mudança lógica** em um patch separado. + +Por exemplo, se as suas alterações incluírem tanto correções de bugs quanto melhorias +de desempenho para um único driver, separe essas alterações em dois +ou mais patches. Se as suas alterações incluírem uma atualização de API e um novo +driver que utiliza essa nova API, separe-os em dois patches. + +Por outro lado, se você fizer uma única alteração em vários arquivos, +agrupe essas alterações em um único patch. Assim, uma única mudança +lógica está contida em um único patch. + +O ponto a lembrar é que cada patch deve fazer uma mudança facilmente compreendida +que possa ser verificada pelos revisores. Cada patch deve ser justificável +por seus próprios méritos. + +Se um patch depender de outro patch para que uma mudança seja +completa, não tem problema. Simplesmente note **"this patch depends on patch X"** +na descrição do seu patch. + +Ao dividir a sua mudança em uma série de patches, tome um cuidado especial para +garantir que o kernel compile e seja executado adequadamente após cada patch da +série. Desenvolvedores que usam o ``git bisect`` para rastrear um problema podem acabar +dividindo a sua série de patches em qualquer ponto; eles não ficarão gratos se você +introduzir bugs no meio do processo. + +Se você não conseguir condensar o seu conjunto de patches em um conjunto menor +de patches, então publique, digamos, apenas uns 15 de cada vez e aguarde pela +revisão e integração. + + + +Verifique o estilo das suas mudanças +------------------------------------ + +Verifique o seu patch quanto a violações básicas de estilo, cujos detalhes podem ser +encontrados em Documentation/process/coding-style.rst. +Não fazer isso simplesmente desperdiça +o tempo dos revisores e fará com que o seu patch seja rejeitado, provavelmente +sem sequer ser lido. + +Uma exceção significativa é quando se move código de um arquivo para +outro -- neste caso você não deve modificar o código movido no +mesmo patch que o move. Isso delineia claramente o ato de +mover o código e as suas alterações. Isso ajuda muito a revisão das +diferenças reais e permite que as ferramentas rastreiem melhor o histórico do +próprio código. + +Verifique os seus patches com o verificador de estilo de patch antes de os submeter +(scripts/checkpatch.pl). Note, porém, que o verificador de estilo deve ser +visto como um guia, e não como um substituto para o julgamento humano. Se o seu +código parecer melhor com uma violação, provavelmente é melhor deixá-lo como está. + +O verificador emite relatórios em três níveis: + - ERROR: coisas que muito provavelmente estão erradas + - WARNING: coisas que requerem uma revisão cuidadosa + - CHECK: coisas que requerem reflexão + +Você deve ser capaz de justificar todas as violações que permanecerem no seu +patch. + +Selecione os destinatários do seu patch +--------------------------------------- + +Você deve sempre copiar o(s) mantenedor(es) e a(s) lista(s) do subsistema +apropriado(s) em qualquer patch para o código que eles mantêm; dê uma +olhada no arquivo MAINTAINERS e no histórico de revisão do código-fonte +para ver quem são esses mantenedores. O script scripts/get_maintainer.pl +pode ser muito útil nesta etapa (passe os caminhos para seus patches +como argumentos para scripts/get_maintainer.pl). Se você não conseguir +encontrar um mantenedor para o subsistema em que está trabalhando, +Andrew Morton (akpm@linux-foundation.org) serve como um mantenedor de +último recurso. + +linux-kernel@vger.kernel.org deve ser usado por padrão para todos os +patches, mas o volume dessa lista fez com que vários desenvolvedores a +ignorassem. Por favor, não envie spam para listas e pessoas não +relacionadas. + +Muitas listas relacionadas ao kernel estão hospedadas em kernel.org; +você pode encontrar uma lista delas em https://subspace.kernel.org. +Existem listas relacionadas ao kernel hospedadas em outros lugares +também, no entanto. + +Linus Torvalds é o árbitro final de todas as mudanças aceitas no +kernel do Linux. Seu endereço de e-mail é . +Ele recebe muitos e-mails e, neste momento, muito poucos patches passam +por Linus diretamente, então, normalmente, você deve fazer o seu melhor +para -evitar- enviar e-mails para ele. + +Se você tiver um patch que corrija um bug de segurança explorável, +envie esse patch para security@kernel.org. Para bugs severos, um +curto embargo pode ser considerado para permitir que os distribuidores +disponibilizem o patch aos usuários; em tais casos, obviamente, o +patch não deve ser enviado a nenhuma lista pública. Veja também +Documentation/process/security-bugs.rst. + +Patches que corrigem um bug severo em um kernel já lançado devem ser +direcionados aos mantenedores stable (estáveis), colocando uma linha como esta:: + + Cc: stable@vger.kernel.org + +na área de sign-off do seu patch (note, NÃO como um destinatário de e-mail). +Você também deve ler Documentation/process/stable-kernel-rules.rst +além deste documento. + +Se as alterações afetarem as interfaces userland-kernel, +por favor, envie ao mantenedor das MAN-PAGES (como listado no arquivo MAINTAINERS) +um patch para as páginas de manual, ou pelo menos uma notificação da alteração, +para que alguma informação chegue às páginas de manual. Mudanças na API +do espaço de usuário também devem ser copiadas para linux-api@vger.kernel.org. + + +Sem MIME, sem links, sem compressão, sem anexos. Apenas texto puro +------------------------------------------------------------------ + +Linus e outros desenvolvedores do kernel precisam ser capazes de ler e +comentar as mudanças que você está enviando. É importante que um +desenvolvedor do kernel seja capaz de "citar" suas mudanças, usando +ferramentas de e-mail padrão, para que eles possam comentar em partes +específicas do seu código. + +Por esse motivo, todos os patches devem ser enviados por e-mail "inline". A +maneira mais fácil de fazer isso é com ``git send-email``, que é +fortemente recomendado. Um tutorial interativo para ``git send-email`` +está disponível em https://git-send-email.io. + +Se você optar por não usar ``git send-email``: + +.. warning:: + + Tenha cuidado com a quebra de linha do seu editor corrompendo seu patch, + se você optar por recortar e colar o seu patch. + +Não anexe o patch como um anexo MIME, comprimido ou não. +Muitos aplicativos populares de e-mail nem sempre transmitirão um +anexo MIME como texto puro, tornando impossível comentar o seu +código. Um anexo MIME também leva um pouco mais de tempo para Linus +processar, diminuindo a probabilidade da sua alteração anexada em MIME +ser aceita. + +Exceção: Se o seu cliente de e-mail estiver danificando os patches, +alguém pode pedir que você os reenvie usando MIME. + +Veja Documentation/process/email-clients.rst para dicas sobre como +configurar seu cliente de e-mail para que ele envie seus patches intocados. + +Responda aos comentários de revisão +----------------------------------- + +Seu patch quase certamente receberá comentários dos revisores sobre maneiras +pelas quais o patch pode ser melhorado, na forma de uma resposta ao seu +e-mail. Você deve responder a esses comentários; ignorar revisores é uma +boa maneira de ser ignorado em troca. Você pode simplesmente responder aos +e-mails deles para responder aos seus comentários. Comentários de revisão +ou perguntas que não levam a uma alteração no código devem quase certamente +resultar em um comentário ou entrada no changelog para que o próximo +revisor entenda melhor o que está acontecendo. + +Certifique-se de dizer aos revisores quais alterações você está fazendo e +de agradecê-los pelo tempo dedicado. A revisão de código é um processo +cansativo e demorado, e os revisores às vezes ficam mal-humorados. Mesmo +nesse caso, no entanto, responda educadamente e resolva os problemas que +eles apontaram. Ao enviar uma próxima versão, adicione um ``changelog do patch`` +à carta de apresentação (cover letter) ou aos patches individuais, +explicando a diferença em relação ao envio anterior (veja +:ref:`pt_BR_the_canonical_patch_format`). +Notifique as pessoas que comentaram no seu patch sobre as novas versões +adicionando-as à lista de CC dos patches. + +Veja Documentation/process/email-clients.rst para recomendações sobre +clientes de e-mail e etiqueta de listas de discussão. + +.. _pt_BR_interleaved_replies: + +Use respostas intercaladas e aparadas em discussões por e-mail +-------------------------------------------------------------- +O top-posting (responder no topo) é fortemente desencorajado em +discussões de desenvolvimento do kernel do Linux. Respostas +intercaladas (ou "inline") tornam as conversas muito mais fáceis de +acompanhar. Para mais detalhes, veja: +https://en.wikipedia.org/wiki/Posting_style#Interleaved_style + +Como é frequentemente citado na lista de discussão:: + + A: http://en.wikipedia.org/wiki/Top_post + Q: Onde encontro informações sobre essa coisa chamada top-posting? + A: Porque bagunça a ordem em que as pessoas normalmente leem o texto. + Q: Por que o top-posting é algo tão ruim? + A: Top-posting. + Q: Qual é a coisa mais irritante no e-mail? + +Da mesma forma, por favor, apare (corte) todas as citações +desnecessárias que não são relevantes para a sua resposta. Isso torna +as respostas mais fáceis de encontrar, e economiza tempo e espaço. Para +mais detalhes, veja: http://daringfireball.net/2007/07/on_top :: + + A: Não. + Q: Devo incluir citações após minha resposta? + +.. _pt_BR_resend_reminders: + +Não desanime - nem fique impaciente +----------------------------------- + +Depois de ter enviado a sua alteração, seja paciente e espere. Os +revisores são pessoas ocupadas e podem não chegar ao seu patch +imediatamente. + +Era uma vez, patches costumavam desaparecer no vazio sem comentários, +mas o processo de desenvolvimento funciona de forma mais suave do que +isso agora. Você deve receber comentários dentro de algumas semanas +(normalmente 2-3); se isso não acontecer, certifique-se de que você +enviou seus patches para o lugar certo. Espere por no mínimo uma +semana antes de reenviar ou dar um "ping" nos revisores - +possivelmente mais tempo durante períodos ocupados, como as janelas +de mesclagem (merge windows). + +Também não há problema em reenviar o patch ou a série de patches após +algumas semanas com a palavra "RESEND" adicionada à linha de Assunto:: + + [PATCH Vx RESEND] sub/sys: Resumo condensado do patch + +Não adicione "RESEND" quando você estiver enviando uma versão +modificada do seu patch ou série de patches - "RESEND" se aplica +apenas ao reenvio de um patch ou série de patches que não foram +modificados de forma alguma em relação ao envio anterior. + + +Inclua PATCH no Assunto +----------------------- + +Devido ao alto tráfego de e-mails para Linus e para a linux-kernel, é +uma convenção comum prefixar a sua linha de Assunto com [PATCH]. Isso +permite que Linus e outros desenvolvedores do kernel distingam +mais facilmente os patches de outras discussões por e-mail. + +O ``git send-email`` fará isso por você automaticamente. + + +Assine seu trabalho - o Certificado de Origem do Desenvolvedor +-------------------------------------------------------------- + +Para melhorar o rastreamento de quem fez o que, especialmente com patches +que podem percolar até o seu local de descanso final no kernel através de +várias camadas de mantenedores, nós introduzimos um procedimento de +"sign-off" nos patches que estão sendo enviados por e-mail. + +O sign-off é uma linha simples no final da explicação do patch, que +certifica que você o escreveu ou que de outra forma tem o direito de +repassá-lo como um patch de código aberto. As regras são bem simples: +se você pode certificar o seguinte:: + + Certificado de Origem do Desenvolvedor 1.1 + + Ao fazer uma contribuição para este projeto, eu certifico que: + + (a) A contribuição foi criada no todo ou em parte por mim e eu + tenho o direito de enviá-la sob a licença de código aberto + indicada no arquivo; ou + + (b) A contribuição baseia-se em trabalho anterior que, até onde eu + sei, é coberto por uma licença de código aberto apropriada + e eu tenho o direito, sob essa licença, de enviar esse + trabalho com modificações, tenham sido criadas no todo ou + em parte por mim, sob a mesma licença de código aberto (a menos que eu + tenha permissão para enviar sob uma licença diferente), conforme + indicado no arquivo; ou + + (c) A contribuição foi fornecida diretamente a mim por alguma outra + pessoa que certificou (a), (b) ou (c) e eu não a modifiquei. + + (d) Eu entendo e concordo que este projeto e a contribuição + são públicos e que um registro da contribuição (incluindo todas + as informações pessoais que eu envio com ela, incluindo meu + sign-off) é mantido indefinidamente e pode ser redistribuído de forma + consistente com este projeto ou com a(s) licença(s) de código + aberto envolvida(s). + +então você apenas adiciona uma linha dizendo:: + + Signed-off-by: Random J Developer + +usando uma identidade conhecida (desculpe, sem contribuições anônimas.) +Isso será feito para você automaticamente se você usar o ``git commit -s``. +As reversões também devem incluir "Signed-off-by". O ``git revert -s`` +faz isso por você. + +Algumas pessoas também colocam tags extras no final. Elas serão +apenas ignoradas por enquanto, mas você pode fazer isso para marcar +procedimentos internos da empresa ou apenas para apontar algum +detalhe especial sobre o sign-off. + +Quaisquer outros SoBs (Signed-off-by:'s) seguindo o SoB do autor +são de pessoas que manusearam e transportaram o patch, mas não +estiveram envolvidas no seu desenvolvimento. As cadeias de SoB devem +refletir a rota **real** que um patch percorreu à medida que foi +propagado aos mantenedores e, finalmente, para Linus, com a primeira +entrada de SoB sinalizando a autoria principal de um único autor. + + +Quando usar Acked-by:, Cc: e Co-developed-by: +--------------------------------------------- + +A tag Signed-off-by: indica que o signatário esteve envolvido no +desenvolvimento do patch, ou que ele/ela estava no caminho de +entrega do patch. + +Se uma pessoa não esteve diretamente envolvida na preparação ou manuseio de um +patch, mas deseja manifestar e registrar sua aprovação, ela pode +pedir para ter uma linha Acked-by: adicionada ao changelog do patch. + +Acked-by: destina-se a ser usado por aqueles responsáveis ou envolvidos com o +código afetado de uma forma ou de outra. Mais comumente, o mantenedor quando esse +mantenedor não contribuiu nem encaminhou o patch. + +Acked-by: também pode ser usado por outras partes interessadas, como pessoas com conhecimento de +domínio (por exemplo, o autor original do código sendo modificado), revisores +do lado do espaço de usuário para um patch uAPI do kernel ou usuários-chave de um recurso. Opcionalmente, +nestes casos, pode ser útil adicionar um "# Sufixo" para esclarecer seu significado:: + + Acked-by: The Stakeholder # As primary user + +Acked-by: não é tão formal quanto Signed-off-by:. É um registro de que o avaliador +pelo menos revisou o patch e indicou aceitação. Por isso, os responsáveis pela fusão de +patches às vezes converterão manualmente um "sim, parece bom para mim" de um avaliador +em um Acked-by: (mas note que geralmente é melhor pedir um +ack explícito). + +Acked-by: também é menos formal do que Reviewed-by:. Por exemplo, mantenedores podem +usá-lo para sinalizar que estão de acordo com a inclusão de um patch, mas podem não tê-lo +revisado tão minuciosamente como se um Reviewed-by: fosse fornecido. Da mesma forma, um +usuário-chave pode não ter realizado uma revisão técnica do patch, mas ainda assim estar +satisfeito com a abordagem geral, o recurso ou a interface voltada para o usuário. + +Acked-by: não indica necessariamente o reconhecimento de todo o patch. +Por exemplo, se um patch afeta vários subsistemas e tem um Acked-by: de +um mantenedor de subsistema, isso geralmente indica o reconhecimento apenas +da parte que afeta o código desse mantenedor. O bom senso deve ser usado aqui. +Em caso de dúvida, as pessoas devem consultar a discussão original nos arquivos da +lista de discussão. Um "# Sufixo" também pode ser usado neste caso para esclarecer. + +Se uma pessoa teve a oportunidade de comentar em um patch, mas não +forneceu tais comentários, você pode opcionalmente adicionar uma tag ``Cc:`` ao patch. +Esta tag documenta que partes potencialmente interessadas foram incluídas na +discussão. Note que esta é uma de apenas três tags que você pode usar +sem a permissão explícita da pessoa nomeada (veja 'Marcar pessoas requer +permissão' abaixo para detalhes). + +Co-developed-by: afirma que o patch foi co-criado por múltiplos desenvolvedores; +é usado para dar atribuição a coautores (além do autor +atribuído pela tag From:) quando várias pessoas trabalham em um único patch. Como +Co-developed-by: denota autoria, cada Co-developed-by: deve ser imediatamente +seguido por um Signed-off-by: do coautor associado. O procedimento padrão de assinatura +se aplica, ou seja, a ordem das tags Signed-off-by: deve refletir a +história cronológica do patch na medida do possível, independentemente se +o autor for atribuído via From: ou Co-developed-by:. Notavelmente, o último +Signed-off-by: deve ser sempre o do desenvolvedor que está enviando o patch. + +Note que a tag From: é opcional quando o autor no From: também é a pessoa (e +e-mail) listada na linha From: do cabeçalho do e-mail. + +Exemplo de um patch enviado pelo autor do From::: + + + + Co-developed-by: First Co-Author + Signed-off-by: First Co-Author + Co-developed-by: Second Co-Author + Signed-off-by: Second Co-Author + Signed-off-by: From Author + +Exemplo de um patch enviado por um autor do Co-developed-by::: + + From: From Author + + + + Co-developed-by: Random Co-Author + Signed-off-by: Random Co-Author + Signed-off-by: From Author + Co-developed-by: Submitting Co-Author + Signed-off-by: Submitting Co-Author + + +Usando Reported-by:, Tested-by:, Reviewed-by:, Suggested-by: e Fixes: +--------------------------------------------------------------------- + +A tag Reported-by dá crédito às pessoas que encontram bugs e os relatam e +espera-se que isso as inspire a nos ajudar novamente no futuro. A tag destina-se a +bugs; por favor, não a use para dar crédito a solicitações de recursos. A tag deve ser +seguida por uma tag Closes: apontando para o relato, a menos que o relato não +esteja disponível na web. A tag Link: pode ser usada em vez de Closes: se o patch +corrigir uma parte do(s) problema(s) sendo relatado(s). Note que a tag Reported-by é +uma de apenas três tags que você pode usar sem a permissão explícita da +pessoa nomeada (veja 'Marcar pessoas requer permissão' abaixo para detalhes). + +Uma tag Tested-by: indica que o patch foi testado com sucesso (em +algum ambiente) pela pessoa nomeada. Esta tag informa aos mantenedores que +algum teste foi realizado, fornece um meio para localizar testadores para +patches futuros e garante crédito para os testadores. + +Reviewed-by:, por sua vez, indica que o patch foi revisado e considerado +aceitável de acordo com a Declaração do Revisor:: + + Declaração de supervisão do revisor + + Ao oferecer minha tag Reviewed-by:, eu declaro que: + + (a) Eu realizei uma revisão técnica deste patch para + avaliar sua adequação e prontidão para inclusão no + kernel mainline. + + (b) Quaisquer problemas, preocupações ou perguntas relacionadas ao patch + foram comunicadas de volta ao remetente. Eu estou satisfeito + com a resposta do remetente aos meus comentários. + + (c) Embora possa haver coisas que poderiam ser melhoradas com este + envio, eu acredito que é, neste momento, (1) uma + modificação que vale a pena para o kernel, e (2) livre de problemas + conhecidos que argumentariam contra sua inclusão. + + (d) Embora eu tenha revisado o patch e acredite que seja sólido, eu + não faço (a menos que explicitamente declarado em outro lugar) + garantias de que alcançará seu propósito + declarado ou funcionará adequadamente em qualquer situação. + +Uma tag Reviewed-by é uma declaração de opinião de que o patch é uma +modificação apropriada do kernel sem nenhum problema técnico sério +restante. Qualquer revisor interessado (que tenha feito o trabalho e seja uma +pessoa com identidade conhecida) pode oferecer uma tag Reviewed-by para um patch. Esta tag +serve para dar crédito aos revisores e para informar os mantenedores do grau de +revisão que foi feito no patch. Tags Reviewed-by:, quando fornecidas por +revisores conhecidos por entender a área de assunto e realizar revisões completas, +normalmente aumentarão a probabilidade de seu patch entrar no kernel. + +Ambas as tags Tested-by e Reviewed-by, uma vez recebidas na lista de discussão do testador +ou revisor, devem ser adicionadas pelo autor aos patches aplicáveis ao enviar as +próximas versões. No entanto, se o patch mudou substancialmente na versão +seguinte, essas tags podem não ser mais aplicáveis e, portanto, devem ser removidas. +Normalmente, a remoção das tags Acked-by, Tested-by ou Reviewed-by de alguém deve ser +mencionada no changelog do patch com uma explicação (após o separador '---'). + +Uma tag Suggested-by: indica que a ideia do patch foi sugerida pela pessoa +nomeada e garante crédito à pessoa pela ideia: se creditarmos diligentemente +nossos relatores de ideias, eles serão, com sorte, inspirados a nos ajudar novamente no +futuro. Note que esta é uma de apenas três tags que você pode usar sem +permissão explícita da pessoa nomeada (veja 'Marcar pessoas requer +permissão' abaixo para detalhes). + +Uma tag Fixes: indica que o patch corrige um bug em um commit anterior. Ela +é usada para facilitar a determinação de onde um problema se originou, o que pode ajudar +na revisão da correção de um bug. Esta tag também auxilia a equipe do kernel estável a determinar +quais versões do kernel estável devem receber sua correção. Este é o método preferido +para indicar um bug corrigido pelo patch. Veja :ref:`pt_BR_describe_changes` +para mais detalhes. + +Nota: Anexar uma tag Fixes: não subverte o processo de regras do kernel +estável, nem o requisito de enviar em Cc: para stable@vger.kernel.org em todos os patches +candidatos estáveis. Para mais informações, por favor, leia +Documentation/process/stable-kernel-rules.rst. + +Por fim, embora fornecer tags seja bem-vindo e tipicamente muito apreciado, por favor +note que os signatários (ou seja, remetentes e mantenedores) podem usar sua discrição ao +aplicar as tags oferecidas. + + +Marcar pessoas requer permissão +------------------------------- + +Tenha cuidado ao adicionar as tags mencionadas acima aos seus patches, pois todas +exceto Cc:, Reported-by: e Suggested-by: precisam de permissão explícita da +pessoa nomeada. Para essas três, a permissão implícita é suficiente se a pessoa +contribuiu para o kernel Linux usando esse nome e endereço de e-mail de acordo +com os arquivos do lore ou o histórico de commits -- e no caso de Reported-by: +e Suggested-by: tenha feito o relato ou sugestão em público. Note que o +bugzilla.kernel.org é um local público nesse sentido, mas os endereços de e-mail +usados lá são privados; portanto, não os exponha em tags, a menos que a pessoa +os tenha usado em contribuições anteriores. + +Usando Assisted-by: +------------------- + +Se você usou qualquer tipo de ferramenta avançada de codificação na criação do seu patch, +você precisa reconhecer esse uso adicionando uma tag Assisted-by. A falha em +fazer isso pode impedir a aceitação do seu trabalho. Por favor, veja +Documentation/process/coding-assistants.rst para detalhes sobre o +reconhecimento de assistentes de codificação. + + +.. _pt_BR_the_canonical_patch_format: + +O formato canônico do patch +--------------------------- + +Esta seção descreve como o próprio patch deve ser formatado. Note +que, se você tiver seus patches armazenados em um repositório ``git``, a formatação +adequada do patch pode ser obtida com ``git format-patch``. As ferramentas não podem criar +o texto necessário, no entanto, portanto, leia as instruções abaixo de qualquer maneira. + +Linha de Assunto +^^^^^^^^^^^^^^^^ + +A linha de assunto canônica do patch é:: + + Assunto: [PATCH 001/123] subsistema: frase de resumo + +O corpo canônico da mensagem do patch contém o seguinte: + + - Uma linha ``from`` especificando o autor do patch, seguida por uma linha + vazia (necessário apenas se a pessoa enviando o patch não for o autor). + + - O corpo da explicação, com quebra de linha em 75 colunas, que será + copiado para o changelog permanente para descrever este patch. + + - Uma linha vazia. + + - As linhas ``Signed-off-by:``, descritas acima, que também + irão para o changelog. + + - Uma linha de marcador contendo simplesmente ``---``. + + - Quaisquer comentários adicionais não adequados para o changelog. + + - O próprio patch (saída do ``diff``). + +O formato da linha de Assunto torna muito fácil classificar os e-mails +alfabeticamente pela linha de assunto - praticamente qualquer leitor de e-mail +suportará isso - pois, como o número de sequência é preenchido com zeros, +a classificação numérica e alfabética é a mesma. + +O ``subsystem`` no Assunto do e-mail deve identificar qual +área ou subsistema do kernel está recebendo o patch. + +A ``frase de resumo`` no Assunto do e-mail deve descrever de forma concisa +o patch que esse e-mail contém. A ``frase de resumo`` não deve ser um nome de arquivo. +Não use a mesma ``frase de resumo`` para cada patch em uma série de patches inteira (onde uma ``série +de patches`` é uma sequência ordenada de múltiplos patches relacionados). + +Tenha em mente que a ``frase de resumo`` do seu e-mail se torna um +identificador globalmente único para aquele patch. Ela se propaga por todo o caminho +até o changelog do ``git``. A ``frase de resumo`` pode ser usada posteriormente em +discussões de desenvolvedores que se referem ao patch. As pessoas vão querer +pesquisar no Google pela ``frase de resumo`` para ler a discussão sobre esse +patch. Também será a única coisa que as pessoas poderão ver rapidamente +quando, dois ou três meses depois, estiverem passando por talvez +milhares de patches usando ferramentas como ``gitk`` ou ``git log +--oneline``. + +Por essas razões, o ``resumo`` não deve ter mais de 70-75 +caracteres, e deve descrever tanto o que o patch altera, quanto +por que o patch pode ser necessário. É um desafio ser +sucinto e descritivo, mas é isso que um resumo bem escrito +deve fazer. + +A ``frase de resumo`` pode ser prefixada por tags delimitadas por colchetes +retos: "Assunto: [PATCH ...] ". As tags não +são consideradas parte da frase de resumo, mas descrevem como o patch +deve ser tratado. Tags comuns podem incluir um descritor de versão se +as múltiplas versões do patch tiverem sido enviadas em resposta a +comentários (ou seja, "v1, v2, v3"), ou "RFC" para indicar um pedido de +comentários. + +Se houver quatro patches em uma série de patches, os patches individuais podem +ser numerados assim: 1/4, 2/4, 3/4, 4/4. Isso garante que os desenvolvedores +entendam a ordem na qual os patches devem ser aplicados e que +eles tenham revisado ou aplicado todos os patches na série de patches. + +Aqui estão alguns bons exemplos de Assuntos:: + + Subject: [PATCH 2/5] ext2: improve scalability of bitmap searching + Subject: [PATCH v2 01/27] x86: fix eflags tracking + Subject: [PATCH v2] sub/sys: Condensed patch summary + Subject: [PATCH v2 M/N] sub/sys: Condensed patch summary + +Linha From +^^^^^^^^^^ + +A linha ``from`` deve ser a primeira linha no corpo da mensagem, +e tem a forma: + + From: Patch Author + +A linha ``from`` especifica quem será creditado como o autor do +patch no changelog permanente. Se a linha ``from`` estiver faltando, +então a linha ``From:`` do cabeçalho do e-mail será usada para determinar +o autor do patch no changelog. + +O autor pode indicar sua afiliação ou o patrocinador do trabalho +adicionando o nome de uma organização às linhas ``from`` e ``SoB``, +por exemplo: + + From: Patch Author (Company) + +Corpo da Explicação +^^^^^^^^^^^^^^^^^^^ + +O corpo da explicação será commitado no changelog +permanente da fonte, então deve fazer sentido para um leitor competente que já +esqueceu há muito tempo os detalhes imediatos da discussão que podem ter levado a +este patch. Incluir sintomas da falha que o patch aborda +(mensagens de log do kernel, mensagens oops, etc.) é especialmente útil para +pessoas que possam estar pesquisando nas mensagens de commit procurando pelo patch +aplicável. O texto deve ser escrito com detalhes suficientes para que, quando lido +semanas, meses ou até anos depois, possa dar ao leitor os detalhes +necessários para compreender o raciocínio do **por que** o patch foi criado. + +Se um patch corrige uma falha de compilação, pode não ser necessário incluir +_todas_ as falhas de compilação; apenas o suficiente para que seja provável que +alguém pesquisando pelo patch possa encontrá-lo. Como na ``frase de resumo``, +é importante ser tanto sucinto quanto descritivo. + +.. _pt_BR_backtraces: + +Backtraces em mensagens de commit +""""""""""""""""""""""""""""""""" + +Backtraces ajudam a documentar a cadeia de chamadas que leva a um problema. No entanto, +nem todos os backtraces são úteis. Por exemplo, as cadeias de chamadas iniciais de boot são +únicas e óbvias. Copiar a saída dmesg completa verbatim, no entanto, +adiciona informações que distraem, como timestamps, listas de módulos, dumps de +registradores e pilhas. + +Portanto, os backtraces mais úteis devem destilar as informações +relevantes do dump, o que facilita o foco no problema +real. Aqui está um exemplo de um backtrace bem aparado:: + + unchecked MSR access error: WRMSR to 0xd51 (tried to write 0x0000000000000064) + at rIP: 0xffffffffae059994 (native_write_msr+0x4/0x20) + Call Trace: + mba_wrmsr + update_domains + rdtgroup_mkdir + +Comentários +^^^^^^^^^^^ + +A linha marcadora ``---`` serve ao propósito essencial de marcar para +as ferramentas de manipulação de patches onde a mensagem do changelog termina. + +Um bom uso para os comentários adicionais após o marcador ``---`` é +para um ``diffstat``, para mostrar quais arquivos mudaram, e o número de +linhas inseridas e excluídas por arquivo. Um ``diffstat`` é especialmente útil +em patches maiores. Se você for incluir um ``diffstat`` após o +marcador ``---``, por favor, use as opções do ``diffstat`` ``-p 1 -w 70`` para que +os nomes dos arquivos sejam listados a partir do topo da árvore de código-fonte do kernel e não +usem muito espaço horizontal (cabem facilmente em 80 colunas, talvez com algum +recuo). (o ``git`` gera diffstats apropriados por padrão.) + +Outros comentários relevantes apenas para o momento ou para o mantenedor, não +adequados para o changelog permanente, também devem ir aqui. Um bom +exemplo de tais comentários podem ser ``changelogs do patch`` que descrevem +o que mudou entre as versões v1 e v2 do patch. + +Por favor, coloque esta informação **após** a linha ``---`` que separa +o changelog do restante do patch. A informação da versão não +faz parte do changelog que é commitado na árvore git. É +informação adicional para os revisores. Se for colocada acima das +tags de commit, precisará de interação manual para removê-la. Se estiver abaixo +da linha separadora, ela é automaticamente removida ao aplicar o +patch. Se disponíveis, adicionar links para as versões anteriores do patch (por exemplo, +link do arquivo lore.kernel.org) é recomendado para ajudar os revisores:: + + + ... + Signed-off-by: Author + --- + V2 -> V3: Removed redundant helper function + V1 -> V2: Cleaned up coding style and addressed review comments + + v2: https://lore.kernel.org/bar + v1: https://lore.kernel.org/foo + + path/to/file | 5+++-- + ... + +Veja mais detalhes sobre o formato de patch adequado nas seguintes +referências. + + +Cabeçalhos In-Reply-To explícitos +--------------------------------- + +Pode ser útil adicionar manualmente cabeçalhos In-Reply-To: a um patch +(por exemplo, ao usar ``git send-email``) para associar o patch com +discussões relevantes anteriores, por exemplo, para vincular uma correção de bug ao e-mail com +o relatório do bug. No entanto, para uma série de múltiplos patches, geralmente é +melhor evitar usar In-Reply-To: para vincular a versões mais antigas da +série. Desta forma, múltiplas versões do patch não se tornam uma +floresta incontrolável de referências nos clientes de e-mail. Se um link for +útil, você pode usar o redirecionador https://lore.kernel.org/ (por exemplo, no +texto do e-mail de capa) para vincular a uma versão anterior da série de patches. + + +Informações sobre a árvore base +------------------------------- + +Quando outros desenvolvedores recebem seus patches e iniciam o processo de revisão, +é absolutamente necessário que eles saibam qual é o commit/branch +base no qual seu trabalho se aplica, considerando a enorme quantidade de +árvores de mantenedores presentes hoje em dia. Note novamente a entrada **T:** no +arquivo MAINTAINERS explicado acima. + +Isso é ainda mais importante para processos automatizados de CI que tentam +executar uma série de testes a fim de estabelecer a qualidade da sua +submissão antes que o mantenedor inicie a revisão. + +Se você estiver usando ``git format-patch`` para gerar seus patches, você pode +incluir automaticamente as informações da árvore base em sua submissão ao +usar a flag ``--base``. A maneira mais fácil e conveniente de usar +esta opção é com branches de tópicos (topical branches):: + + $ git checkout -t -b my-topical-branch master + Branch 'my-topical-branch' set up to track local branch 'master'. + Switched to a new branch 'my-topical-branch' + + [perform your edits and commits] + + $ git format-patch --base=auto --cover-letter -o outgoing/ master + outgoing/0000-cover-letter.patch + outgoing/0001-First-Commit.patch + outgoing/... + +Quando você abrir ``outgoing/0000-cover-letter.patch`` para edição, você +notará que ele terá o trailer ``base-commit:`` bem no +final, o qual fornece ao revisor e às ferramentas de CI informações suficientes +para realizar o ``git am`` adequadamente sem se preocupar com conflitos:: + + $ git checkout -b patch-review [base-commit-id] + Switched to a new branch 'patch-review' + $ git am patches.mbox + Applying: First Commit + Applying: ... + +Por favor, veja ``man git-format-patch`` para mais informações sobre esta +opção. + +.. note:: + + A funcionalidade ``--base`` foi introduzida no git versão 2.9.0. + +Se você não estiver usando git para formatar seus patches, você ainda pode incluir +o mesmo trailer ``base-commit`` para indicar o hash do commit da árvore +na qual seu trabalho se baseia. Você deve adicioná-lo na cover +letter (carta de apresentação) ou no primeiro patch da série e ele deve ser colocado +abaixo da linha ``---`` ou bem no final de todo o outro +conteúdo, logo antes da sua assinatura de e-mail. + +Certifique-se de que o commit base está em uma árvore oficial de mantenedor/mainline +e não em alguma árvore interna acessível apenas por você - caso contrário seria +inútil. + +Ferramentas +----------- + +Muitos dos aspectos técnicos deste processo podem ser automatizados usando +b4, documentado em . Isso pode +ajudar com coisas como rastreamento de dependências, execução do checkpatch e +com a formatação e o envio de e-mails. + +Referências +----------- + +Andrew Morton, "The perfect patch" (tpp). + + +Jeff Garzik, "Linux kernel patch submission format". + + +Greg Kroah-Hartman, "How to piss off a kernel subsystem maintainer". + + + + + + + + + + + + +Kernel Documentation/process/coding-style.rst + +Linus Torvalds's mail on the canonical patch format: + + +Andi Kleen, "On submitting kernel patches" + Some strategies to get difficult or controversial changes in. + + http://halobates.de/on-submitting-patches.pdf From e0e7f6cc8d81ef39293fbe0d19141680ec414599 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Tue, 8 Sep 2026 13:55:50 +0200 Subject: [PATCH 0461/2286] Input: yealink.rst: Fix wording, quotation and typo Improve readability. Signed-off-by: Manuel Ebner Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260908115551.1345764-2-manuelebnerli@mailbox.org> --- Documentation/input/devices/yealink.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/input/devices/yealink.rst b/Documentation/input/devices/yealink.rst index bb5a1aafeca2a0..53191a3c83caf1 100644 --- a/Documentation/input/devices/yealink.rst +++ b/Documentation/input/devices/yealink.rst @@ -120,9 +120,10 @@ Reading /sys/../lineX will return the format string with its current value. Writing to /sys/../lineX will set the corresponding LCD line. - Excess characters are ignored. - - If less characters are written than allowed, the remaining digits are + - If fewer characters than allowed are written, the remaining digits are unchanged. - - The tab '\t'and '\n' char does not overwrite the original content. + - The tab ``\t`` and newline ``\n`` characters do not overwrite the original + content. - Writing a space to an icon will always hide its content. Example:: @@ -205,7 +206,7 @@ Troubleshooting :Q: Module yealink compiled and installed without any problem but phone is not initialized and does not react to any actions. :A: If you see something like: - hiddev0: USB HID v1.00 Device [Yealink Network Technology Ltd. VOIP USB Phone + hiddev0: USB HID v1.00 Device [Yealink Network Technology Ltd. VOIP USB Phone] in dmesg, it means that the hid driver has grabbed the device first. Try to load module yealink before any other usb hid driver. Please see the instructions provided by your distribution on module configuration. From ee288798d3cf15a0a5eb13404658dfd83c93c332 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Mon, 7 Sep 2026 02:49:25 +0200 Subject: [PATCH 0462/2286] docs: powerpc: vas-api: Fix the structure name and the ioctl number in the example struct vas_setup_attr does not exist; the name is struct vas_tx_win_open_attr. The example also defines VAS_TX_WIN_OPEN with the request number 1, while arch/powerpc/include/uapi/asm/vas-api.h has used 0x20 since the ioctl was added. Use struct vas_tx_win_open_attr and 0x20. Fixes: c12e38b1d52e ("Documentation/powerpc: VAS API") Suggested-by: Ritesh Harjani (IBM) Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Jonathan Corbet Message-ID: <20260907004925.10948-1-kmehltretter@gmail.com> --- Documentation/arch/powerpc/vas-api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/arch/powerpc/vas-api.rst b/Documentation/arch/powerpc/vas-api.rst index 1d0d055356e3cc..f74ea9fbc8a093 100644 --- a/Documentation/arch/powerpc/vas-api.rst +++ b/Documentation/arch/powerpc/vas-api.rst @@ -136,7 +136,7 @@ a connection with NX co-processor engine: follows:: #define VAS_MAGIC 'v' - #define VAS_TX_WIN_OPEN _IOW(VAS_MAGIC, 1, + #define VAS_TX_WIN_OPEN _IOW(VAS_MAGIC, 0x20, struct vas_tx_win_open_attr) struct vas_tx_win_open_attr attr; @@ -265,7 +265,7 @@ Simple example { int rc, fd; void *addr; - struct vas_setup_attr txattr; + struct vas_tx_win_open_attr txattr; fd = open("/dev/crypto/nx-gzip", O_RDWR); if (fd < 0) { From 5fa39b00e47c48c8a1519a096e8c1bd57dc9816a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 11:29:42 +0200 Subject: [PATCH 0463/2286] Documentation: virt: Fix the struct snp_guest_request_ioctl name in sev-guest.rst struct snp_user_guest_request does not exist; the name is struct snp_guest_request_ioctl. Use the real name. Fixes: fce96cf04430 ("virt: Add SEV-SNP guest driver") Assisted-by: LLM Signed-off-by: Karl Mehltretter Acked-by: Randy Dunlap Acked-by: Tom Lendacky Signed-off-by: Jonathan Corbet Message-ID: <20260905092942.41558-1-kmehltretter@gmail.com> --- Documentation/virt/coco/sev-guest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/virt/coco/sev-guest.rst b/Documentation/virt/coco/sev-guest.rst index 93debceb6eb0ef..3371ebc29e8b81 100644 --- a/Documentation/virt/coco/sev-guest.rst +++ b/Documentation/virt/coco/sev-guest.rst @@ -38,7 +38,7 @@ along with a description: are not detailed, but errors with specific meanings are. The guest ioctl should be issued on a file descriptor of the /dev/sev-guest -device. The ioctl accepts struct snp_user_guest_request. The input and +device. The ioctl accepts struct snp_guest_request_ioctl. The input and output structure is specified through the req_data and resp_data field respectively. If the ioctl fails to execute due to a firmware error, then the fw_error code will be set, otherwise fw_error will be set to -1. From be3d025c3c0ddcb2bf55948ffffacacd1f1f342c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 11:24:41 +0200 Subject: [PATCH 0464/2286] Documentation: RAS: Use the real EDAC PCI parity control names The document names the sysfs control /sys/devices/system/edac/pci/check_pci_parity and the module parameter edac_panic_on_pci_pe. Neither exists. check_pci_parity was renamed to check_pci_errors by commit 91b99041c1d5 ("drivers/edac: updated PCI monitoring"), after the document had been written. The module parameter has been edac_pci_panic_on_pe (drivers/edac/edac_pci_sysfs.c) since it was introduced, and the document described it as edac_panic_on_pci_pe from the start. Use the real names. Fixes: 91b99041c1d5 ("drivers/edac: updated PCI monitoring") Fixes: 327dafb1c61c ("edac: core fix redundant sysfs controls to parameters") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905092441.41401-1-kmehltretter@gmail.com> --- Documentation/admin-guide/RAS/main.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Documentation/admin-guide/RAS/main.rst b/Documentation/admin-guide/RAS/main.rst index 5a45db32c49b09..c1040e5a5b3537 100644 --- a/Documentation/admin-guide/RAS/main.rst +++ b/Documentation/admin-guide/RAS/main.rst @@ -623,7 +623,7 @@ Under ``/sys/devices/system/edac/pci`` are control and attribute files as follows: -- ``check_pci_parity`` - Enable/Disable PCI Parity checking control file +- ``check_pci_errors`` - Enable/Disable PCI Parity checking control file This control file enables or disables the PCI Bus Parity scanning operation. Writing a 1 to this file enables the scanning. Writing @@ -631,11 +631,11 @@ follows: Enable:: - echo "1" >/sys/devices/system/edac/pci/check_pci_parity + echo "1" >/sys/devices/system/edac/pci/check_pci_errors Disable:: - echo "0" >/sys/devices/system/edac/pci/check_pci_parity + echo "0" >/sys/devices/system/edac/pci/check_pci_errors - ``pci_parity_count`` - Parity Count @@ -725,15 +725,15 @@ Module parameters module/kernel parameter:: - edac_panic_on_pci_pe=[0|1] + edac_pci_panic_on_pe=[0|1] Enable:: - echo "1" > /sys/module/edac_core/parameters/edac_panic_on_pci_pe + echo "1" > /sys/module/edac_core/parameters/edac_pci_panic_on_pe Disable:: - echo "0" > /sys/module/edac_core/parameters/edac_panic_on_pci_pe + echo "0" > /sys/module/edac_core/parameters/edac_pci_panic_on_pe From 2bed2f65647529527410a3131afef31b49180312 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Sat, 5 Sep 2026 09:41:42 +0200 Subject: [PATCH 0465/2286] Documentation: input: Fix bracket Add missing '(' to C snippet. Signed-off-by: Manuel Ebner Signed-off-by: Jonathan Corbet Message-ID: <20260905074142.810529-2-manuelebnerli@mailbox.org> --- Documentation/input/notifier.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/input/notifier.rst b/Documentation/input/notifier.rst index 824379399e611e..e7facd1a1003e3 100644 --- a/Documentation/input/notifier.rst +++ b/Documentation/input/notifier.rst @@ -31,7 +31,7 @@ In a rough C snippet, we have:: kbd_keycode(keycode) { ... params.value = keycode; - if (notifier_call_chain(KBD_KEYCODE,¶ms) == NOTIFY_STOP) + if ((notifier_call_chain(KBD_KEYCODE,¶ms) == NOTIFY_STOP) || !bound) { notifier_call_chain(KBD_UNBOUND_KEYCODE,¶ms); return; From eb0b0d222a832b6ec851e56d4922a775ac762955 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 5 Sep 2026 03:36:55 +0200 Subject: [PATCH 0466/2286] loadpin: Fix stale KEXEC_VERIFY_SIG reference in documentation Commit 99d5cadfde2b ("kexec_file: split KEXEC_VERIFY_SIG into KEXEC_SIG and KEXEC_SIG_FORCE") removed CONFIG_KEXEC_VERIFY_SIG but left the LoadPin documentation pointing at it. Refer to CONFIG_KEXEC_SIG instead. While at it, clean up the grammar of the sentence. Fixes: 99d5cadfde2b ("kexec_file: split KEXEC_VERIFY_SIG into KEXEC_SIG and KEXEC_SIG_FORCE") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260905013655.58665-1-kmehltretter@gmail.com> --- Documentation/admin-guide/LSM/LoadPin.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/admin-guide/LSM/LoadPin.rst b/Documentation/admin-guide/LSM/LoadPin.rst index dd3ca68b5df188..a90fd9dac9606f 100644 --- a/Documentation/admin-guide/LSM/LoadPin.rst +++ b/Documentation/admin-guide/LSM/LoadPin.rst @@ -23,9 +23,9 @@ sysctl allows for easy testing on systems with a mutable filesystem.) It's also possible to exclude specific file types from LoadPin using kernel command line option "``loadpin.exclude``". By default, all files are included, but they can be excluded using kernel command line option such -as "``loadpin.exclude=kernel-module,kexec-image``". This allows to use +as "``loadpin.exclude=kernel-module,kexec-image``". This allows using different mechanisms such as ``CONFIG_MODULE_SIG`` and -``CONFIG_KEXEC_VERIFY_SIG`` to verify kernel module and kernel image while -still use LoadPin to protect the integrity of other files kernel loads. The -full list of valid file types can be found in ``kernel_read_file_str`` -defined in ``include/linux/kernel_read_file.h``. +``CONFIG_KEXEC_SIG`` to verify kernel modules and kernel images while +still using LoadPin to protect the integrity of other files the kernel +loads. The full list of valid file types can be found in +``kernel_read_file_str`` defined in ``include/linux/kernel_read_file.h``. From 5c11629e622fafd34e9139be9f48fadf5baaffcb Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Wed, 2 Sep 2026 16:35:41 +0200 Subject: [PATCH 0467/2286] Documentation: fuse: harmonize numeration Replace with '1.', '2.' with '1)', '2)' to harmonize file. Signed-off-by: Manuel Ebner Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260902143541.703277-2-manuelebnerli@mailbox.org> --- Documentation/filesystems/fuse/fuse.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/filesystems/fuse/fuse.rst b/Documentation/filesystems/fuse/fuse.rst index 0fbd5a03fdc94c..063d99959396dc 100644 --- a/Documentation/filesystems/fuse/fuse.rst +++ b/Documentation/filesystems/fuse/fuse.rst @@ -173,10 +173,10 @@ the error set to EINTR. It is also possible that there's a race between processing the original request and its INTERRUPT request. There are two possibilities: - 1. The INTERRUPT request is processed before the original request is + 1) The INTERRUPT request is processed before the original request is processed - 2. The INTERRUPT request is processed after the original request has + 2) The INTERRUPT request is processed after the original request has been answered If the filesystem cannot find the original request, it should wait for @@ -239,9 +239,9 @@ How are requirements fulfilled? A) The mount owner could gain elevated privileges by either: - 1. creating a filesystem containing a device file, then opening this device + 1) creating a filesystem containing a device file, then opening this device - 2. creating a filesystem containing a suid or sgid application, then executing this application + 2) creating a filesystem containing a suid or sgid application, then executing this application The solution is not to allow opening device files and ignore setuid and setgid bits when executing programs. To ensure this From baab8aa73d69ef80ea13a205c16d8cb0bfe4d6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:19 -0300 Subject: [PATCH 0468/2286] docs: futex2: fix documented errno names for futex_waitv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The documentation cited -EAGAIN and -ETIMEOUT, but futex_waitv() returns -EWOULDBLOCK on value mismatch and -ETIMEDOUT when the timeout expires, per kernel/futex/waitwake.c. Signed-off-by: Iván Ezequiel Rodriguez Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-2-ivanrwcm25@gmail.com> --- Documentation/userspace-api/futex2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/userspace-api/futex2.rst b/Documentation/userspace-api/futex2.rst index 9693f47a7e6287..615616305c57cb 100644 --- a/Documentation/userspace-api/futex2.rst +++ b/Documentation/userspace-api/futex2.rst @@ -51,10 +51,10 @@ future extension. For each entry in ``waiters`` array, the current value at ``uaddr`` is compared to ``val``. If it's different, the syscall undo all the work done so far and -return ``-EAGAIN``. If all tests and verifications succeeds, syscall waits until +return ``-EWOULDBLOCK``. If all tests and verifications succeeds, syscall waits until one of the following happens: -- The timeout expires, returning ``-ETIMEOUT``. +- The timeout expires, returning ``-ETIMEDOUT``. - A signal was sent to the sleeping task, returning ``-ERESTARTSYS``. - Some futex at the list was woken, returning the index of some waked futex. From 2ef355c4e68648b9ba77d39c1b0129d06c0c18a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:20 -0300 Subject: [PATCH 0469/2286] docs: landlock: fix ENOMSG condition in create_ruleset kernel-doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel-doc stated ENOMSG applies when handled_access_fs is empty, but landlock_create_ruleset() returns ENOMSG only when handled_access_fs, handled_access_net, and scoped are all zero. Signed-off-by: Iván Ezequiel Rodriguez Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-3-ivanrwcm25@gmail.com> --- security/landlock/syscalls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 1d02d57f4c48c4..400a2f928de313 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -204,7 +204,7 @@ const int landlock_abi_version = 11; * scoped; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; - * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. + * - %ENOMSG: empty handled_access_fs, handled_access_net, and scoped fields. * * .. kernel-doc:: include/uapi/linux/landlock.h * :identifiers: landlock_create_ruleset_flags From f0756b6ebd923d5fefe2d18a6f948fa5ce69db44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:21 -0300 Subject: [PATCH 0470/2286] docs: gpio: note lineinfo padding must be zero filled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPIO_V2_GET_LINEINFO_IOCTL and GPIO_V2_GET_LINEINFO_WATCH_IOCTL reject non-zero lineinfo.padding with -EINVAL; align the uapi comment with gpio_v2_line_request.padding. Signed-off-by: Iván Ezequiel Rodriguez Reviewed-by: Bartosz Golaszewski Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-4-ivanrwcm25@gmail.com> --- include/uapi/linux/gpio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h index f7cb8ae87df72c..5515b718886ec3 100644 --- a/include/uapi/linux/gpio.h +++ b/include/uapi/linux/gpio.h @@ -218,7 +218,7 @@ struct gpio_v2_line_request { * gpio_v2_line_flag, such as %GPIO_V2_LINE_FLAG_ACTIVE_LOW, * %GPIO_V2_LINE_FLAG_OUTPUT etc, added together * @attrs: the configuration attributes associated with the line - * @padding: reserved for future use + * @padding: reserved for future use, must be zero filled */ struct gpio_v2_line_info { char name[GPIO_MAX_NAME_SIZE]; From a3d89b0eaae5ec25981776e3e4a90efc059b3c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:22 -0300 Subject: [PATCH 0471/2286] docs: dma-buf-heaps: fix CMA heap name typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Line 39 used default-cma-region with hyphens; the registered heap name is default_cma_region, already named correctly at line 26. Signed-off-by: Iván Ezequiel Rodriguez Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-5-ivanrwcm25@gmail.com> --- Documentation/userspace-api/dma-buf-heaps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/userspace-api/dma-buf-heaps.rst b/Documentation/userspace-api/dma-buf-heaps.rst index f56b743cdb365d..92099188893e56 100644 --- a/Documentation/userspace-api/dma-buf-heaps.rst +++ b/Documentation/userspace-api/dma-buf-heaps.rst @@ -36,7 +36,7 @@ following heaps: - A heap will be created for each reusable region in the device tree with the ``shared-dma-pool`` compatible, using the full device tree node name as its name. The buffer semantics are identical to - ``default-cma-region``. + ``default_cma_region``. Naming Convention ================= From 61b8aea52f539ca5e6a9e20c226c5bdfa53df4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:23 -0300 Subject: [PATCH 0472/2286] docs: vduse: align documentation with current uapi and driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update supported device IDs (block, net, fs), all six read/write message types, the VDUSE_VQ_INJECT_IRQ ioctl name, and VQ_SETUP group vs asid usage per include/uapi/linux/vduse.h. Signed-off-by: Iván Ezequiel Rodriguez Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-6-ivanrwcm25@gmail.com> --- Documentation/userspace-api/vduse.rst | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/Documentation/userspace-api/vduse.rst b/Documentation/userspace-api/vduse.rst index 81479d47c8b91f..d316857ca5bdeb 100644 --- a/Documentation/userspace-api/vduse.rst +++ b/Documentation/userspace-api/vduse.rst @@ -11,11 +11,10 @@ to make the device emulation more secure, the emulated vDPA device's control path is handled in the kernel and only the data path is implemented in the userspace. -Note that only virtio block device is supported by VDUSE framework now, -which can reduce security risks when the userspace process that implements -the data path is run by an unprivileged user. The support for other device -types can be added after the security issue of corresponding device driver -is clarified or fixed in the future. +Note that virtio block, network, and filesystem device types are supported +by the VDUSE framework. Other device types may be added after the security +implications of the corresponding device driver are clarified or fixed in +the future. Create/Destroy VDUSE devices ---------------------------- @@ -135,7 +134,8 @@ module as follows: return 0; } -There are now three types of messages introduced by VDUSE framework: +The following control message types may be delivered via read(2) on +/dev/vduse/$NAME: - VDUSE_GET_VQ_STATE: Get the state for virtqueue, userspace should return avail index for split virtqueue or the device/driver ring wrap counters and @@ -151,6 +151,15 @@ There are now three types of messages introduced by VDUSE framework: IOVA range, userspace should firstly remove the old mapping, then setup the new mapping via the VDUSE_IOTLB_GET_FD ioctl. +- VDUSE_SET_VQ_GROUP_ASID: Notify userspace to change the address space of a + virtqueue group (API version 1). + +- VDUSE_SET_VQ_READY: Notify userspace that a virtqueue should become ready or + not ready (when VDUSE_F_QUEUE_READY is negotiated). + +- VDUSE_SUSPEND: Notify userspace that the device is being suspended (when + VDUSE_F_SUSPEND is negotiated). + After DRIVER_OK status bit is set via the VDUSE_SET_STATUS message, userspace is able to start the dataplane processing as follows: @@ -227,7 +236,7 @@ able to start the dataplane processing as follows: described by the descriptors in the descriptor table should be also mapped into userspace via the VDUSE_IOTLB_GET_FD ioctl before accessing. -5. Inject an interrupt for specific virtqueue with the VDUSE_INJECT_VQ_IRQ ioctl +5. Inject an interrupt for specific virtqueue with the VDUSE_VQ_INJECT_IRQ ioctl after the used ring is filled. Enabling ASID (API version 1) @@ -238,11 +247,11 @@ version 1. Set it up with ioctl(VDUSE_SET_API_VERSION) on `/dev/vduse/control` and pass `VDUSE_API_VERSION_1` before creating a new VDUSE instance with ioctl(VDUSE_CREATE_DEV). -Afterwards, you can use the member asid of ioctl(VDUSE_VQ_SETUP) argument to -select the address space of the IOTLB you are querying. The driver could -change the address space of any virtqueue group by using the -VDUSE_SET_VQ_GROUP_ASID VDUSE message type, and the VDUSE instance needs to -reply with VDUSE_REQ_RESULT_OK if it was possible to change it. +Afterwards, ioctl(VDUSE_VQ_SETUP) takes a virtqueue group index in +struct vduse_vq_config::group. The driver can change the address space +of any virtqueue group by using the VDUSE_SET_VQ_GROUP_ASID message type, +and the VDUSE instance needs to reply with VDUSE_REQ_RESULT_OK if it was +possible to change it. Similarly, you can use ioctl(VDUSE_IOTLB_GET_FD2) to obtain the file descriptor describing an IOVA region of a specific ASID. Example usage: From 322695d64f868da8d1091ea93c7c8f47bcc41220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ezequiel=20Rodriguez?= Date: Mon, 31 Aug 2026 11:31:24 -0300 Subject: [PATCH 0473/2286] docs: ioctl: fix stale NVMe registry entry and note N conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NVMe row pointed at drivers/block/nvme.c, which no longer exists, and ioctl numbers 0x80-0x83 overlap between ntsync.h and nvme_ioctl.h without a conflict! marker. Signed-off-by: Iván Ezequiel Rodriguez Signed-off-by: Jonathan Corbet Message-ID: <20260831143125.151360-7-ivanrwcm25@gmail.com> --- Documentation/userspace-api/ioctl/ioctl-number.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 32c0d08e92e401..35f495bc54f7e2 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -181,8 +181,9 @@ Code Seq# Include File Comments 'M' 01-03 drivers/scsi/megaraid/megaraid_sas.h 'M' 00-0F drivers/video/fsl-diu-fb.h conflict! 'N' 00-1F drivers/usb/scanner.h -'N' 40-7F drivers/block/nvme.c -'N' 80-8F uapi/linux/ntsync.h NT synchronization primitives +'N' 40-7F uapi/linux/nvme_ioctl.h +'N' 80-83 uapi/linux/ntsync.h and uapi/linux/nvme_ioctl.h conflict! +'N' 84-8F uapi/linux/ntsync.h NT synchronization primitives 'O' 00-06 mtd/ubi-user.h UBI 'P' all linux/soundcard.h conflict! From 8be71e1ba093cb3aee90c7cf6b403d1fd71a77e9 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 11 Sep 2026 12:37:35 -0400 Subject: [PATCH 0474/2286] selinux: mark status and policy inodes as immutable, drop status mmap write checks The selinuxfs "status" and "policy" files are read-only interfaces that are also mmap'd by userspace. They are created 0444 by simple_fill_super() but a CAP_DAC_OVERRIDE caller can still open them O_WRONLY/O_RDWR, open(O_RDONLY|O_TRUNC) them, or truncate(2) them. Mark both inodes S_IMMUTABLE at fill_super time. inode_permission() tests IS_IMMUTABLE before the DAC / capability checks, so all of the above are rejected at the VFS layer without ever reaching the file operations. Since a writable file can no longer exist, do_mmap() clear VM_MAYWRITE for MAP_SHARED mappings on its own, and the sel_mmap_handle_status() write/mprotect guards are dead; drop them. MAP_PRIVATE writable mappings become permitted (they were previously -EPERM) and CoW harmlessly to a private page, matching how sel_mmap_policy() has always treated the private case. The sel_mmap_policy() VM_SHARED guard becomes redundant for the same reason; leave dropping it to the pending "selinux: reject writable opens of policy file, drop mmap shared/write check" patch so that the patches do not conflict. Link: https://lore.kernel.org/selinux/aqPUqU3eZeFrykj3@gremlin/ cc: ljs@kernel.org cc: jannh@google.com cc: jack@suse.cz cc: cgzones@googlemail.com cc: brauner@kernel.org Suggested-by: Jan Kara Signed-off-by: Stephen Smalley Reviewed-by: Jan Kara Acked-by: Lorenzo Stoakes (ARM) Signed-off-by: Paul Moore --- security/selinux/selinuxfs.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 292302eb60f3a1..0941ce79ea0bdb 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -247,11 +247,6 @@ static int sel_mmap_handle_status(struct file *filp, /* only allows one page from the head */ if (vma->vm_pgoff > 0 || size != PAGE_SIZE) return -EIO; - /* disallow writable mapping */ - if (vma->vm_flags & VM_WRITE) - return -EPERM; - /* disallow mprotect() turns it into writable */ - vm_flags_clear(vma, VM_MAYWRITE); return remap_pfn_range(vma, vma->vm_start, page_to_pfn(status), @@ -1818,6 +1813,17 @@ static struct dentry *sel_make_swapover_dir(struct super_block *sb, u64 *ino) #define NULL_FILE_NAME "null" +static void sel_mark_immutable(struct dentry *root, const char *name) +{ + struct qstr q = QSTR(name); + struct dentry *dentry = try_lookup_noperm(&q, root); + + if (!IS_ERR_OR_NULL(dentry)) { + d_inode(dentry)->i_flags |= S_IMMUTABLE; + dput(dentry); + } +} + static int sel_fill_super(struct super_block *sb, struct fs_context *fc) { struct selinux_fs_info *fsi; @@ -1857,6 +1863,9 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) if (ret) goto err; + sel_mark_immutable(sb->s_root, "status"); + sel_mark_immutable(sb->s_root, "policy"); + fsi = sb->s_fs_info; fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino); if (IS_ERR(fsi->bool_dir)) { From b4e7b9a70de3cb6261be55d5771fe209f7394eeb Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Tue, 15 Sep 2026 16:29:19 -0400 Subject: [PATCH 0475/2286] selinux: avoid 64-bit division of inode numbers As reported by Arnd, a previous change to convert the selinuxfs inode numbers to u64 to match the rest of the kernel caused a problem on 32-bit systems: arm-linux-gnueabi-ld: .../selinuxfs.o: in function `sel_read_class': selinuxfs.c:(.text+0xdcc): undefined reference to `__aeabi_uldivmod' arm-linux-gnueabi-ld: .../selinuxfs.o: in function `sel_read_perm': selinuxfs.c:(.text+0xed0): undefined reference to `__aeabi_uldivmod' Since we don't currently use more than 24-bits in the selinuxfs inodes (see SEL_INO_MASK), fix this by applying the SEL_INO_MASK to the selinuxfs inode numbers and casting to a u32 before performing any division operations. Fixes: fc68b6a45f16 ("selinux: convert selinuxfs inode numbers from unsigned long to u64") Reported-by: Arnd Bergmann Signed-off-by: Paul Moore --- security/selinux/selinuxfs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 0941ce79ea0bdb..b3eaf3823fb128 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -1569,7 +1569,8 @@ static inline u64 sel_class_to_ino(u16 class) static inline u16 sel_ino_to_class(u64 ino) { - return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1); + u32 ino_masked = ino & SEL_INO_MASK; + return ino_masked / (SEL_VEC_MAX + 1); } static inline u64 sel_perm_to_ino(u16 class, u32 perm) @@ -1579,7 +1580,8 @@ static inline u64 sel_perm_to_ino(u16 class, u32 perm) static inline u32 sel_ino_to_perm(u64 ino) { - return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); + u32 ino_masked = ino & SEL_INO_MASK; + return ino_masked % (SEL_VEC_MAX + 1); } static ssize_t sel_read_class(struct file *file, char __user *buf, From c8beda268a6b21910d1325584ce1d45bce4f22f0 Mon Sep 17 00:00:00 2001 From: Manuel Ebner Date: Tue, 15 Sep 2026 12:31:37 +0200 Subject: [PATCH 0476/2286] x86/mtrr: Fix kernel-doc notation of amd_set_mtrr() Add ':' to arguments in kernel-doc comment to fix: $ ./scripts/kernel-doc -none arch/x86/kernel/cpu/mtrr/amd.c Warning: arch/x86/kernel/cpu/mtrr/amd.c:63 function parameter 'reg' not described in 'amd_set_mtrr' Warning: arch/x86/kernel/cpu/mtrr/amd.c:63 function parameter 'base' not described in 'amd_set_mtrr' Warning: arch/x86/kernel/cpu/mtrr/amd.c:63 function parameter 'size' not described in 'amd_set_mtrr' Warning: arch/x86/kernel/cpu/mtrr/amd.c:63 function parameter 'type' not described in 'amd_set_mtrr' [ bp: Massage commit message. ] Signed-off-by: Manuel Ebner Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260915103136.171924-3-manuelebnerli@mailbox.org --- arch/x86/kernel/cpu/mtrr/amd.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/arch/x86/kernel/cpu/mtrr/amd.c b/arch/x86/kernel/cpu/mtrr/amd.c index a73715d6f05c12..9e440e30c1796b 100644 --- a/arch/x86/kernel/cpu/mtrr/amd.c +++ b/arch/x86/kernel/cpu/mtrr/amd.c @@ -51,11 +51,10 @@ amd_get_mtrr(unsigned int reg, unsigned long *base, /** * amd_set_mtrr - Set variable MTRR register on the local CPU. - * - * @reg The register to set. - * @base The base address of the region. - * @size The size of the region. If this is 0 the region is disabled. - * @type The type of the region. + * @reg: The register to set. + * @base: The base address of the region. + * @size: The size of the region. If this is 0 the region is disabled. + * @type: The type of the region. * * Returns nothing. */ From 322b0463f30b0a02180c9528e38c2afb73d20fa0 Mon Sep 17 00:00:00 2001 From: Aayansh Ranjan Date: Tue, 15 Sep 2026 11:56:13 +0530 Subject: [PATCH 0477/2286] kbuild: fix grammar in output Makefile .gitignore generation comment Fix the grammar in the comment describing the generation of the output Makefile .gitignore file. Signed-off-by: Aayansh Ranjan Link: https://patch.msgid.link/20260915062613.13761-1-computergod47@gmail.com Signed-off-by: Nathan Chancellor --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 9bd226726358e4..2c6b04954bd80e 100644 --- a/Makefile +++ b/Makefile @@ -681,8 +681,8 @@ ifdef building_out_of_srctree # outputmakefile generates a Makefile in the output directory, if using a # separate output directory. This allows convenient use of make in the # output directory. -# At the same time when output Makefile generated, generate .gitignore to -# ignore whole output directory +# At the same time as the output Makefile is generated, generate .gitignore to +# ignore the whole output directory ifdef KBUILD_EXTMOD print_env_for_makefile = \ From 4ecb68572a96709aecaa9a1b4f1e908c45c2c346 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Mon, 14 Sep 2026 19:03:17 +0200 Subject: [PATCH 0478/2286] fanotify: Re-add Matt Bobrowski as a reviewer Matt has resurfaced and I have his working email again. Re-add him as a reviewer since hopes to have more time for that now. Acked-by: Matt Bobrowski Signed-off-by: Jan Kara --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 627595e245f311..6b698f8e52e841 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9975,6 +9975,7 @@ F: net/core/failover.c FANOTIFY M: Jan Kara R: Amir Goldstein +R: Matt Bobrowski L: linux-fsdevel@vger.kernel.org S: Maintained F: fs/notify/fanotify/ From 68a385dbe05fd8da69ec8a7753591fd653f97d89 Mon Sep 17 00:00:00 2001 From: Ruslan Vagner Date: Tue, 15 Sep 2026 05:56:13 -0700 Subject: [PATCH 0479/2286] ACPI: sysfs: use strscpy() instead of strcpy() strcpy() is deprecated in the kernel, so use strscpy() with the compile-time known size of the trace_method_name buffer instead of it. The existing length check already rejects overlong input, so the behavior is unchanged. Signed-off-by: Ruslan Vagner [ rjw: Changelog edits ] Link: https://patch.msgid.link/CAF9RqJ4ip_egVwRKnwn3=X-Qxuwz0u_9pNrgfujBw8RSf2DTPQ@mail.gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sysfs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index dd4a99f09efea6..a993735471261c 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "internal.h" @@ -181,10 +182,10 @@ static int param_set_trace_method_name(const char *val, /* This is a hack. We can't kmalloc in early boot. */ if (is_abs_path) - strcpy(trace_method_name, val); + strscpy(trace_method_name, val, sizeof(trace_method_name)); else { trace_method_name[0] = '\\'; - strcpy(trace_method_name+1, val); + strscpy(trace_method_name + 1, val, sizeof(trace_method_name) - 1); } /* Restore the original tracer state */ From 047c834d939df75386cfc0603c7ddeaa23a349a4 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:23 +0800 Subject: [PATCH 0480/2286] ACPI: tables: handle failed initrd override mappings The runtime AML table-loading path can invoke initrd override after permanent mappings are enabled. Mapping bookkeeping can then fail. Return AE_NO_MEMORY in that case to prevent a NULL pointer dereference when attempting to read the table length. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: b2a35003dfbc ("ACPI: Implement physical address table override") Assisted-by: LLM Signed-off-by: Slavin Liu [ rjw: Added empty code line after conditional, changelog edits ] Link: https://patch.msgid.link/20260913125123.109803-1-bolin.liu@seu.edu.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/tables.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index eb93c060426ffd..aaa1de79bccfd7 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -560,6 +560,9 @@ acpi_table_initrd_override(struct acpi_table_header *existing_table, while (table_offset + ACPI_HEADER_SIZE <= all_tables_size) { table = acpi_os_map_memory(acpi_tables_addr + table_offset, ACPI_HEADER_SIZE); + if (!table) + return AE_NO_MEMORY; + if (table_offset + table->length > all_tables_size) { acpi_os_unmap_memory(table, ACPI_HEADER_SIZE); WARN_ON(1); From 7117334f022ba0cc7475f688efa68dd56e60f65b Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:21 +0200 Subject: [PATCH 0481/2286] posix-timers: Move posixtimer_exec_cleanup() out of exec.c Move it to the POSIX timer code and provide a proper stub when POSIX timers are disabled in Kconfig. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.680668094@kernel.org --- fs/exec.c | 13 +------------ include/linux/posix-timers.h | 3 +++ kernel/time/posix-timers.c | 9 +++++++++ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 819643408e6df8..1fc567155f3403 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1115,17 +1115,6 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) return bprm->file; } -static void posixtimer_exec(struct task_struct *me) -{ -#ifdef CONFIG_POSIX_TIMERS - spin_lock_irq(&me->sighand->siglock); - posix_cpu_timers_exit(me); - spin_unlock_irq(&me->sighand->siglock); - exit_itimers(me); - flush_itimer_signals(); -#endif -} - /* * Calling this is the point of no return. None of the failures will be * seen by userspace since either the process is already taking a fatal @@ -1171,7 +1160,7 @@ int begin_new_exec(struct linux_binprm * bprm) * timer would not remove an enqueued timer because the TID lookup * of the old TID fails. */ - posixtimer_exec(me); + posixtimer_exec(); /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 9a1a0c61361cb4..7b31da68363caa 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -232,6 +232,8 @@ void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx, int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS +void posixtimer_exec(void); + static inline void posixtimer_putref(struct k_itimer *tmr) { if (rcuref_put(&tmr->rcuref)) @@ -259,6 +261,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) return !(val & 0x1UL); } #else /* CONFIG_POSIX_TIMERS */ +static inline void posixtimer_exec(void) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 436ba794cc0b58..9611ed4014d13f 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1120,6 +1120,15 @@ void exit_itimers(struct task_struct *tsk) } } +void posixtimer_exec(void) +{ + scoped_guard(spinlock_irq, ¤t->sighand->siglock) + posix_cpu_timers_exit(current); + + exit_itimers(current); + flush_itimer_signals(); +} + SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock, const struct __kernel_timespec __user *, tp) { From 24adce0b86c940ac3dcee0bcd75e799634fc9664 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:26 +0200 Subject: [PATCH 0482/2286] posix-timers: Move POSIX timer group exit related code out of do_exit() Move the POSIX timer group exit handling into the posix timer code and provide a proper stub when POSIX timers are disabled in Kconfig. No functional change. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.733966325@kernel.org --- include/linux/posix-timers.h | 2 ++ include/linux/sched/task.h | 1 - kernel/exit.c | 7 +++---- kernel/time/posix-timers.c | 16 +++++++++------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 7b31da68363caa..3050f9e44feedc 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -233,6 +233,7 @@ int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS void posixtimer_exec(void); +void posixtimer_exit(void); static inline void posixtimer_putref(struct k_itimer *tmr) { @@ -262,6 +263,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) } #else /* CONFIG_POSIX_TIMERS */ static inline void posixtimer_exec(void) { } +static inline void posixtimer_exit(void) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h index e0c1ca8c6a1884..90ed5bc3c7afee 100644 --- a/include/linux/sched/task.h +++ b/include/linux/sched/task.h @@ -94,7 +94,6 @@ static inline void exit_thread(struct task_struct *tsk) extern __noreturn void do_group_exit(int); extern void exit_files(struct task_struct *); -extern void exit_itimers(struct task_struct *); extern pid_t kernel_clone(struct kernel_clone_args *kargs); struct task_struct *copy_process(struct pid *pid, int trace, int node, diff --git a/kernel/exit.c b/kernel/exit.c index 424c44a42a4d71..4ca9759ea6adf5 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -966,13 +966,12 @@ void __noreturn do_exit(long code) panic("Attempted to kill init! exitcode=0x%08x\n", tsk->signal->group_exit_code ?: (int)code); -#ifdef CONFIG_POSIX_TIMERS - hrtimer_cancel(&tsk->signal->real_timer); - exit_itimers(tsk); -#endif + posixtimer_exit(); + if (tsk->mm) setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm); } + acct_collect(code, group_dead); if (group_dead) tty_audit_exit(); diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 9611ed4014d13f..8fb040a040cb04 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1077,13 +1077,9 @@ SYSCALL_DEFINE1(timer_delete, timer_t, timer_id) return 0; } -/* - * Invoked from do_exit() when the last thread of a thread group exits. - * At that point no other task can access the timers of the dying - * task anymore. - */ -void exit_itimers(struct task_struct *tsk) +static void posixtimer_delete_timers(void) { + struct task_struct *tsk = current; struct hlist_head timers; struct hlist_node *next; struct k_itimer *timer; @@ -1120,12 +1116,18 @@ void exit_itimers(struct task_struct *tsk) } } +void posixtimer_exit(void) +{ + hrtimer_cancel(¤t->signal->real_timer); + posixtimer_delete_timers(); +} + void posixtimer_exec(void) { scoped_guard(spinlock_irq, ¤t->sighand->siglock) posix_cpu_timers_exit(current); - exit_itimers(current); + posixtimer_delete_timers(); flush_itimer_signals(); } From 8e9ed3b66f409e324dade4a07fc08dae43160ec6 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:31 +0200 Subject: [PATCH 0483/2286] posix-cpu-timers: Move inlines out of public header They are only used in the POSIX CPU timer code. No point in exposing them globally and parsing them for nothing. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260911090541.785749315@kernel.org --- include/linux/posix-timers.h | 32 -------------------------------- kernel/time/posix-cpu-timers.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 3050f9e44feedc..0033a065e98aec 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -66,38 +66,6 @@ struct cpu_timer { struct task_struct __rcu *handling; }; -static inline bool cpu_timer_enqueue(struct timerqueue_head *head, - struct cpu_timer *ctmr) -{ - ctmr->head = head; - return timerqueue_add(head, &ctmr->node); -} - -static inline bool cpu_timer_queued(struct cpu_timer *ctmr) -{ - return !!ctmr->head; -} - -static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr) -{ - if (cpu_timer_queued(ctmr)) { - timerqueue_del(ctmr->head, &ctmr->node); - ctmr->head = NULL; - return true; - } - return false; -} - -static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr) -{ - return ctmr->node.expires; -} - -static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp) -{ - ctmr->node.expires = exp; -} - static inline void posix_cputimers_init(struct posix_cputimers *pct) { memset(pct, 0, sizeof(*pct)); diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index d73d31c7994fe1..e6358342c84cfc 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -438,6 +438,38 @@ static void trigger_base_recalc_expires(struct k_itimer *timer, base->nextevt = 0; } +static inline bool cpu_timer_enqueue(struct timerqueue_head *head, + struct cpu_timer *ctmr) +{ + ctmr->head = head; + return timerqueue_add(head, &ctmr->node); +} + +static inline bool cpu_timer_queued(struct cpu_timer *ctmr) +{ + return !!ctmr->head; +} + +static inline bool cpu_timer_dequeue(struct cpu_timer *ctmr) +{ + if (cpu_timer_queued(ctmr)) { + timerqueue_del(ctmr->head, &ctmr->node); + ctmr->head = NULL; + return true; + } + return false; +} + +static inline u64 cpu_timer_getexpires(struct cpu_timer *ctmr) +{ + return ctmr->node.expires; +} + +static inline void cpu_timer_setexpires(struct cpu_timer *ctmr, u64 exp) +{ + ctmr->node.expires = exp; +} + /* * Dequeue the timer and reset the base if it was its earliest expiration. * It makes sure the next tick recalculates the base next expiration so we From 760ad335d600b1d45f05bc5fad59c05d909710be Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:35 +0200 Subject: [PATCH 0484/2286] posix-cpu-timers: Use PF_EXITING to indicate exit Right now POSIX CPU timers use task::exit_state to check whether a task is exiting. That works correctly, but exit_state is set later in do_exit() and too late for allowing to cleanup POSIX timers earlier. It does not matter in case of exit whether the cutoff is a bit earlier. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.837831386@kernel.org --- kernel/time/posix-cpu-timers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index e6358342c84cfc..cf4bd1f9d74c85 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -1505,7 +1505,7 @@ void run_posix_cpu_timers(void) * posix_cpu_timer_del() may fail to lock_task_sighand(tsk) and * miss timer->it.cpu.firing != 0. */ - if (tsk->exit_state) + if (tsk->flags & PF_EXITING) return; /* From 54ad1e0ea42c3dc25de39b09220af408e4913820 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:40 +0200 Subject: [PATCH 0485/2286] posix-cpu-timers: Prevent enqueueing when PF_EXITING is set To prepare for cleaning up POSIX CPU timers in do_exit(), prevent enqueueing POSIX CPU timers on a task which has PF_EXITING set. Queueing a timer on such a task is pointless because the task won't expire the timer anymore. The same applies to process wide timers when tsk::signal::flags has SIGNAL_GROUP_EXIT set. Pretending that the timer is armed allows to keep the POSIX timer mechanism "working" so that the timer stays accessible up to the point where a task is unhashed. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.892445443@kernel.org --- kernel/time/posix-cpu-timers.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index cf4bd1f9d74c85..ddaf81bb23fa33 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -628,6 +628,7 @@ static int posix_cpu_timer_del(struct k_itimer *timer) } if (!ret) { + WARN_ON_ONCE(cpu_timer_queued(&timer->it.cpu)); put_pid(timer->it.cpu.pid); timer->it_status = POSIX_TIMER_DISARMED; } @@ -674,6 +675,27 @@ void posix_cpu_timers_exit_group(struct task_struct *tsk) cleanup_timers(&tsk->signal->posix_cputimers); } +/* + * This function validates that POSIX CPU timers can be safely enqueued on the + * target task. + * + * Enqueue is allowed when PF_EXITING is not set. If set then it is only allowed + * for process shared timers (type = PIDTYPE_TGID) as long as tsk::signal::flags + * does not have SIGNAL_GROUP_EXIT set. PIDTYPE_PID targets are not allowed at + * all when the task has PF_EXITING set. + * + * This guarantees that after the POSIX timer cleanup in posixtimer_exit() no + * POSIX CPU timers are queued on the task or in case of a group exit on the + * process. + */ +static inline bool task_can_enqueue_timer(struct task_struct *tsk, enum pid_type type) +{ + if (likely(!(tsk->flags & PF_EXITING))) + return true; + + return type == PIDTYPE_TGID && !(tsk->signal->flags & SIGNAL_GROUP_EXIT); +} + /* * Insert the timer on the appropriate list before any timers that * expire later. This must be called with the sighand lock held. @@ -684,7 +706,13 @@ static void arm_timer(struct k_itimer *timer, struct task_struct *p) struct cpu_timer *ctmr = &timer->it.cpu; u64 newexp = cpu_timer_getexpires(ctmr); + lockdep_assert_held(&p->sighand->siglock); + timer->it_status = POSIX_TIMER_ARMED; + + if (unlikely(!task_can_enqueue_timer(p, clock_pid_type(timer->it_clock)))) + return; + if (!cpu_timer_enqueue(&base->tqhead, ctmr)) return; From 7b07d15ed1d78cb49309754a2d14dacdddb058c7 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Fri, 11 Sep 2026 11:09:44 +0200 Subject: [PATCH 0486/2286] posix-timers: Handle exit in do_exit() completely Now that POSIX CPU timers cannot be enqueued on a task after PF_EXITING is set and process wide timers cannot be enqueued when PF_EXITING is set and the last thread in the group is exiting, it is possible to mop up POSIX timers in do_exit() completely. This requires to cancel an eventually pending POSIX CPU timer task work right there because do_exit() invokes exit_task_work() later, which would be acting on torn down data. Signed-off-by: Thomas Gleixner Tested-by: Kijo Park Reviewed-by: Oleg Nesterov Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260911090541.947206186@kernel.org --- include/linux/posix-timers.h | 6 ++--- kernel/exit.c | 10 ++------- kernel/time/posix-cpu-timers.c | 41 ++++++++++++++++++++++++++++------ kernel/time/posix-timers.c | 15 ++++++++----- kernel/time/posix-timers.h | 3 +++ 5 files changed, 50 insertions(+), 25 deletions(-) diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h index 0033a065e98aec..00767acbc111e3 100644 --- a/include/linux/posix-timers.h +++ b/include/linux/posix-timers.h @@ -192,8 +192,6 @@ struct k_itimer { } ____cacheline_aligned_in_smp; void run_posix_cpu_timers(void); -void posix_cpu_timers_exit(struct task_struct *task); -void posix_cpu_timers_exit_group(struct task_struct *task); void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx, u64 *newval, u64 *oldval); @@ -201,7 +199,7 @@ int update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new); #ifdef CONFIG_POSIX_TIMERS void posixtimer_exec(void); -void posixtimer_exit(void); +void posixtimer_exit(bool group_dead); static inline void posixtimer_putref(struct k_itimer *tmr) { @@ -231,7 +229,7 @@ static inline bool posixtimer_valid(const struct k_itimer *timer) } #else /* CONFIG_POSIX_TIMERS */ static inline void posixtimer_exec(void) { } -static inline void posixtimer_exit(void) { } +static inline void posixtimer_exit(bool group_dead) { } static inline void posixtimer_sigqueue_getref(struct sigqueue *q) { } static inline void posixtimer_sigqueue_putref(struct sigqueue *q) { } #endif /* !CONFIG_POSIX_TIMERS */ diff --git a/kernel/exit.c b/kernel/exit.c index 4ca9759ea6adf5..06a1eb1fdfcd19 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -167,12 +167,6 @@ static void __exit_signal(struct release_task_post *post, struct task_struct *ts lockdep_tasklist_lock_is_held()); spin_lock(&sighand->siglock); -#ifdef CONFIG_POSIX_TIMERS - posix_cpu_timers_exit(tsk); - if (group_dead) - posix_cpu_timers_exit_group(tsk); -#endif - if (group_dead) { tty = sig->tty; sig->tty = NULL; @@ -966,12 +960,12 @@ void __noreturn do_exit(long code) panic("Attempted to kill init! exitcode=0x%08x\n", tsk->signal->group_exit_code ?: (int)code); - posixtimer_exit(); - if (tsk->mm) setmax_mm_hiwater_rss(&tsk->signal->maxrss, tsk->mm); } + posixtimer_exit(group_dead); + acct_collect(code, group_dead); if (group_dead) tty_audit_exit(); diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c index ddaf81bb23fa33..9f90500894dc9b 100644 --- a/kernel/time/posix-cpu-timers.c +++ b/kernel/time/posix-cpu-timers.c @@ -661,18 +661,29 @@ static void cleanup_timers(struct posix_cputimers *pct) cleanup_timerqueue(&pct->bases[CPUCLOCK_SCHED].tqhead); } +static inline void posix_cpu_timers_exit_work(void); + /* - * These are both called with the siglock held, when the current thread - * is being reaped. When the final (leader) thread in the group is reaped, - * posix_cpu_timers_exit_group will be called after posix_cpu_timers_exit. + * Invoked from posixtimer_exit_task() after PF_EXITING was set in tsk::flags or + * from posixtimer_exec_cleanup(). */ -void posix_cpu_timers_exit(struct task_struct *tsk) +void posix_cpu_timers_exit_task(void) { - cleanup_timers(&tsk->posix_cputimers); + posix_cpu_timers_exit_work(); + + guard(spinlock_irq)(¤t->sighand->siglock); + cleanup_timers(¤t->posix_cputimers); } -void posix_cpu_timers_exit_group(struct task_struct *tsk) + +/* + * Invoked from posixtimer_exit_group() after PF_EXITING was set in tsk::flags. + */ +void posix_cpu_timers_exit_group(void) { - cleanup_timers(&tsk->signal->posix_cputimers); + posix_cpu_timers_exit_task(); + + guard(spinlock_irq)(¤t->sighand->siglock); + cleanup_timers(¤t->signal->posix_cputimers); } /* @@ -1257,6 +1268,20 @@ static void posix_cpu_timers_work(struct callback_head *work) mutex_unlock(&cw->mutex); } +static inline void posix_cpu_timers_exit_work(void) +{ + /* Canceling the work is only valid for exit() but not for exec() */ + if (!(current->flags & PF_EXITING)) + return; + /* + * current->flags has PF_EXITING set so this can be done lockless and + * with interrupts enabled as PF_EXITING prevents the interrupt from + * scheduling the work. + */ + if (current->posix_cputimers_work.scheduled) + task_work_cancel(current, ¤t->posix_cputimers_work.work); +} + /* * Invoked from the posix-timer core when a cancel operation failed because * the timer is marked firing. The caller holds rcu_read_lock(), which @@ -1387,6 +1412,8 @@ static inline void __run_posix_cpu_timers(struct task_struct *tsk) lockdep_posixtimer_exit(); } +static inline void posix_cpu_timers_exit_work(void) { } + static void posix_cpu_timer_wait_running(struct k_itimer *timr) { cpu_relax(); diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c index 8fb040a040cb04..188dbedbffcac4 100644 --- a/kernel/time/posix-timers.c +++ b/kernel/time/posix-timers.c @@ -1116,17 +1116,20 @@ static void posixtimer_delete_timers(void) } } -void posixtimer_exit(void) +void posixtimer_exit(bool group_dead) { - hrtimer_cancel(¤t->signal->real_timer); - posixtimer_delete_timers(); + if (group_dead) { + hrtimer_cancel(¤t->signal->real_timer); + posix_cpu_timers_exit_group(); + posixtimer_delete_timers(); + } else { + posix_cpu_timers_exit_task(); + } } void posixtimer_exec(void) { - scoped_guard(spinlock_irq, ¤t->sighand->siglock) - posix_cpu_timers_exit(current); - + posix_cpu_timers_exit_task(); posixtimer_delete_timers(); flush_itimer_signals(); } diff --git a/kernel/time/posix-timers.h b/kernel/time/posix-timers.h index 4ea9611dd7165c..79fd7ea7104695 100644 --- a/kernel/time/posix-timers.h +++ b/kernel/time/posix-timers.h @@ -51,3 +51,6 @@ int common_timer_set(struct k_itimer *timr, int flags, struct itimerspec64 *old_setting); void posix_timer_set_common(struct k_itimer *timer, struct itimerspec64 *new_setting); int common_timer_del(struct k_itimer *timer); + +void posix_cpu_timers_exit_task(void); +void posix_cpu_timers_exit_group(void); From dbdd18404e33ed44436bb9294fe8f2609b93af03 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 17:23:50 +0530 Subject: [PATCH 0487/2286] tools: power: pm-graph: fix typo "hierachy" in comment Correct "hierachy" to "hierarchy", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260904115351.9356-1-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- tools/power/pm-graph/sleepgraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/power/pm-graph/sleepgraph.py b/tools/power/pm-graph/sleepgraph.py index f6d172254829b2..c8bc3bed3f5a67 100755 --- a/tools/power/pm-graph/sleepgraph.py +++ b/tools/power/pm-graph/sleepgraph.py @@ -1448,7 +1448,7 @@ def xtraInfo(self): # Class: DeviceNode # Description: -# A container used to create a device hierachy, with a single root node +# A container used to create a device hierarchy, with a single root node # and a tree of child nodes. Used by Data.deviceTopology() class DeviceNode: def __init__(self, nodename, nodedepth): From f70b009ef62c7ef1cebe9ed984561bfc6fc5a32d Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Tue, 15 Sep 2026 18:27:46 -0700 Subject: [PATCH 0488/2286] MAINTAINERS: Orphan drivers/edac/i82975x_edac.c The driver has only been "carried forward" without any recent maintainership interest or testing. Orphan it. Signed-off-by: Borislav Petkov (AMD) Link: https://lore.kernel.org/r/20260916013846.GIaqnzJg9VuUaiH2JG@fat_crate.local --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 77383c50e240a1..4f57a3234fb8ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9472,9 +9472,8 @@ S: Maintained F: drivers/edac/i7core_edac.c EDAC-I82975X -M: "Arvind R." L: linux-edac@vger.kernel.org -S: Maintained +S: Orphan F: drivers/edac/i82975x_edac.c EDAC-IE31200 From 74c3eff4fe5a0503ce24a8a0ae50e23878ef5073 Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Wed, 16 Sep 2026 12:25:37 -0700 Subject: [PATCH 0489/2286] MAINTAINERS: Orphan drivers/edac/x38_edac.c This driver has been receiving odd fixes and API adjustments without anyone actually using it, TTBOMK. So orphan it. Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260916192814.GDaqrtzr-YHpILZImx@fat_crate.local --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 4f57a3234fb8ac..9497c4e5b1f9ea 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9546,6 +9546,11 @@ L: linux-edac@vger.kernel.org S: Odd Fixes F: drivers/edac/ti_edac.c +EDAC-X38 +L: linux-edac@vger.kernel.org +S: Orphan +F: drivers/edac/x38_edac.c + EDIROL UA-101/UA-1000 DRIVER M: Clemens Ladisch L: linux-sound@vger.kernel.org From 2caf9f28c38b8a4062355df7a53fcaeb2c107f1e Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Tue, 15 Sep 2026 19:12:01 -0700 Subject: [PATCH 0490/2286] MAINTAINERS: Orphan drivers/edac/xgene_edac.c It looks forgotten, no one has the hardware to test, orphan it. Signed-off-by: Borislav Petkov (AMD) Link: https://lore.kernel.org/r/20260814181549.GCan9bVSKYJ4Dmj_Sp@fat_crate.local --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 9497c4e5b1f9ea..2fabe0b5a49c01 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2076,8 +2076,7 @@ S: Supported F: arch/arm64/boot/dts/apm/ APPLIED MICRO (APM) X-GENE SOC EDAC -M: Khuong Dinh -S: Supported +S: Orphan F: Documentation/devicetree/bindings/edac/apm,xgene-edac.yaml F: drivers/edac/xgene_edac.c From fc21598c6477874a7c5451863a10ef63f49ec1e2 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Mon, 6 Jul 2026 11:03:02 +0100 Subject: [PATCH 0491/2286] x86/platform: Remove Technology Systems 5500 board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"), this board is no longer supported. Remove the platform support too. See: https://www.embeddedts.com/products/TS-5500 for more info. [ bp: Massage commit message. ] Signed-off-by: Sean Young Signed-off-by: Borislav Petkov (AMD) Acked-by: Jérôme Oufella Link: https://patch.msgid.link/20260706100304.303951-1-sean@mess.org --- .../ABI/testing/sysfs-platform-ts5500 | 54 --- MAINTAINERS | 5 - arch/x86/Kconfig | 9 - arch/x86/platform/Makefile | 1 - arch/x86/platform/ts5500/Makefile | 2 - arch/x86/platform/ts5500/ts5500.c | 341 ------------------ 6 files changed, 412 deletions(-) delete mode 100644 Documentation/ABI/testing/sysfs-platform-ts5500 delete mode 100644 arch/x86/platform/ts5500/Makefile delete mode 100644 arch/x86/platform/ts5500/ts5500.c diff --git a/Documentation/ABI/testing/sysfs-platform-ts5500 b/Documentation/ABI/testing/sysfs-platform-ts5500 deleted file mode 100644 index e685957caa1236..00000000000000 --- a/Documentation/ABI/testing/sysfs-platform-ts5500 +++ /dev/null @@ -1,54 +0,0 @@ -What: /sys/devices/platform/ts5500/adc -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Indicates the presence of an A/D Converter. If it is present, - it will display "1", otherwise "0". - -What: /sys/devices/platform/ts5500/ereset -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Indicates the presence of an external reset. If it is present, - it will display "1", otherwise "0". - -What: /sys/devices/platform/ts5500/id -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Product ID of the TS board. TS-5500 ID is 0x60. - -What: /sys/devices/platform/ts5500/jumpers -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Bitfield showing the jumpers' state. If a jumper is present, - the corresponding bit is set. For instance, 0x0e means jumpers - 2, 3 and 4 are set. - -What: /sys/devices/platform/ts5500/name -Date: July 2014 -KernelVersion: 3.16 -Contact: "Savoir-faire Linux Inc." -Description: - Model name of the TS board, e.g. "TS-5500". - -What: /sys/devices/platform/ts5500/rs485 -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Indicates the presence of the RS485 option. If it is present, - it will display "1", otherwise "0". - -What: /sys/devices/platform/ts5500/sram -Date: January 2013 -KernelVersion: 3.7 -Contact: "Savoir-faire Linux Inc." -Description: - Indicates the presence of the SRAM option. If it is present, - it will display "1", otherwise "0". diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..cd1dfa3ca93b86 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26805,11 +26805,6 @@ S: Maintained F: Documentation/process/contribution-maturity-model.rst F: Documentation/process/researcher-guidelines.rst -TECHNOLOGIC SYSTEMS TS-5500 PLATFORM SUPPORT -M: "Savoir-faire Linux Inc." -S: Maintained -F: arch/x86/platform/ts5500/ - TECHNOTREND USB IR RECEIVER M: Sean Young L: linux-media@vger.kernel.org diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 15fd9ec5ecacb7..ae1beb3c3af82a 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -3059,15 +3059,6 @@ config GEOS help This option enables system support for the Traverse Technologies GEOS. -config TS5500 - bool "Technologic Systems TS-5500 platform support" - depends on MELAN - select CHECK_SIGNATURE - select NEW_LEDS - select LEDS_CLASS - help - This option enables system support for the Technologic Systems TS-5500. - endif # X86_32 config AMD_NB diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile index 3ed03a2552d012..727b92d0ca25e3 100644 --- a/arch/x86/platform/Makefile +++ b/arch/x86/platform/Makefile @@ -10,5 +10,4 @@ obj-y += intel-mid/ obj-y += intel-quark/ obj-y += olpc/ obj-y += scx200/ -obj-y += ts5500/ obj-y += uv/ diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile deleted file mode 100644 index 910fe9e3ffb4ad..00000000000000 --- a/arch/x86/platform/ts5500/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_TS5500) += ts5500.o diff --git a/arch/x86/platform/ts5500/ts5500.c b/arch/x86/platform/ts5500/ts5500.c deleted file mode 100644 index 0b67da056fd927..00000000000000 --- a/arch/x86/platform/ts5500/ts5500.c +++ /dev/null @@ -1,341 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Technologic Systems TS-5500 Single Board Computer support - * - * Copyright (C) 2013-2014 Savoir-faire Linux Inc. - * Vivien Didelot - * - * This driver registers the Technologic Systems TS-5500 Single Board Computer - * (SBC) and its devices, and exposes information to userspace such as jumpers' - * state or available options. For further information about sysfs entries, see - * Documentation/ABI/testing/sysfs-platform-ts5500. - * - * This code may be extended to support similar x86-based platforms. - * Actually, the TS-5500 and TS-5400 are supported. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -/* Product code register */ -#define TS5500_PRODUCT_CODE_ADDR 0x74 -#define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */ -#define TS5400_PRODUCT_CODE 0x40 /* TS-5400 product code */ - -/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */ -#define TS5500_SRAM_RS485_ADC_ADDR 0x75 -#define TS5500_SRAM BIT(0) /* SRAM option */ -#define TS5500_RS485 BIT(1) /* RS-485 option */ -#define TS5500_ADC BIT(2) /* A/D converter option */ -#define TS5500_RS485_RTS BIT(6) /* RTS for RS-485 */ -#define TS5500_RS485_AUTO BIT(7) /* Automatic RS-485 */ - -/* External Reset/Industrial Temperature Range options register */ -#define TS5500_ERESET_ITR_ADDR 0x76 -#define TS5500_ERESET BIT(0) /* External Reset option */ -#define TS5500_ITR BIT(1) /* Indust. Temp. Range option */ - -/* LED/Jumpers register */ -#define TS5500_LED_JP_ADDR 0x77 -#define TS5500_LED BIT(0) /* LED flag */ -#define TS5500_JP1 BIT(1) /* Automatic CMOS */ -#define TS5500_JP2 BIT(2) /* Enable Serial Console */ -#define TS5500_JP3 BIT(3) /* Write Enable Drive A */ -#define TS5500_JP4 BIT(4) /* Fast Console (115K baud) */ -#define TS5500_JP5 BIT(5) /* User Jumper */ -#define TS5500_JP6 BIT(6) /* Console on COM1 (req. JP2) */ -#define TS5500_JP7 BIT(7) /* Undocumented (Unused) */ - -/* A/D Converter registers */ -#define TS5500_ADC_CONV_BUSY_ADDR 0x195 /* Conversion state register */ -#define TS5500_ADC_CONV_BUSY BIT(0) -#define TS5500_ADC_CONV_INIT_LSB_ADDR 0x196 /* Start conv. / LSB register */ -#define TS5500_ADC_CONV_MSB_ADDR 0x197 /* MSB register */ -#define TS5500_ADC_CONV_DELAY 12 /* usec */ - -/** - * struct ts5500_sbc - TS-5500 board description - * @name: Board model name. - * @id: Board product ID. - * @sram: Flag for SRAM option. - * @rs485: Flag for RS-485 option. - * @adc: Flag for Analog/Digital converter option. - * @ereset: Flag for External Reset option. - * @itr: Flag for Industrial Temperature Range option. - * @jumpers: Bitfield for jumpers' state. - */ -struct ts5500_sbc { - const char *name; - int id; - bool sram; - bool rs485; - bool adc; - bool ereset; - bool itr; - u8 jumpers; -}; - -/* Board signatures in BIOS shadow RAM */ -static const struct { - const char * const string; - const ssize_t offset; -} ts5500_signatures[] __initconst = { - { "TS-5x00 AMD Elan", 0xb14 }, -}; - -static int __init ts5500_check_signature(void) -{ - void __iomem *bios; - int i, ret = -ENODEV; - - bios = ioremap(0xf0000, 0x10000); - if (!bios) - return -ENOMEM; - - for (i = 0; i < ARRAY_SIZE(ts5500_signatures); i++) { - if (check_signature(bios + ts5500_signatures[i].offset, - ts5500_signatures[i].string, - strlen(ts5500_signatures[i].string))) { - ret = 0; - break; - } - } - - iounmap(bios); - return ret; -} - -static int __init ts5500_detect_config(struct ts5500_sbc *sbc) -{ - u8 tmp; - int ret = 0; - - if (!request_region(TS5500_PRODUCT_CODE_ADDR, 4, "ts5500")) - return -EBUSY; - - sbc->id = inb(TS5500_PRODUCT_CODE_ADDR); - if (sbc->id == TS5500_PRODUCT_CODE) { - sbc->name = "TS-5500"; - } else if (sbc->id == TS5400_PRODUCT_CODE) { - sbc->name = "TS-5400"; - } else { - pr_err("ts5500: unknown product code 0x%x\n", sbc->id); - ret = -ENODEV; - goto cleanup; - } - - tmp = inb(TS5500_SRAM_RS485_ADC_ADDR); - sbc->sram = tmp & TS5500_SRAM; - sbc->rs485 = tmp & TS5500_RS485; - sbc->adc = tmp & TS5500_ADC; - - tmp = inb(TS5500_ERESET_ITR_ADDR); - sbc->ereset = tmp & TS5500_ERESET; - sbc->itr = tmp & TS5500_ITR; - - tmp = inb(TS5500_LED_JP_ADDR); - sbc->jumpers = tmp & ~TS5500_LED; - -cleanup: - release_region(TS5500_PRODUCT_CODE_ADDR, 4); - return ret; -} - -static ssize_t name_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ts5500_sbc *sbc = dev_get_drvdata(dev); - - return sprintf(buf, "%s\n", sbc->name); -} -static DEVICE_ATTR_RO(name); - -static ssize_t id_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ts5500_sbc *sbc = dev_get_drvdata(dev); - - return sprintf(buf, "0x%.2x\n", sbc->id); -} -static DEVICE_ATTR_RO(id); - -static ssize_t jumpers_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ts5500_sbc *sbc = dev_get_drvdata(dev); - - return sprintf(buf, "0x%.2x\n", sbc->jumpers >> 1); -} -static DEVICE_ATTR_RO(jumpers); - -#define TS5500_ATTR_BOOL(_field) \ - static ssize_t _field##_show(struct device *dev, \ - struct device_attribute *attr, char *buf) \ - { \ - struct ts5500_sbc *sbc = dev_get_drvdata(dev); \ - \ - return sprintf(buf, "%d\n", sbc->_field); \ - } \ - static DEVICE_ATTR_RO(_field) - -TS5500_ATTR_BOOL(sram); -TS5500_ATTR_BOOL(rs485); -TS5500_ATTR_BOOL(adc); -TS5500_ATTR_BOOL(ereset); -TS5500_ATTR_BOOL(itr); - -static struct attribute *ts5500_attributes[] = { - &dev_attr_id.attr, - &dev_attr_name.attr, - &dev_attr_jumpers.attr, - &dev_attr_sram.attr, - &dev_attr_rs485.attr, - &dev_attr_adc.attr, - &dev_attr_ereset.attr, - &dev_attr_itr.attr, - NULL -}; - -static const struct attribute_group ts5500_attr_group = { - .attrs = ts5500_attributes, -}; - -static struct resource ts5500_dio1_resource[] = { - DEFINE_RES_IRQ_NAMED(7, "DIO1 interrupt"), -}; - -static struct platform_device ts5500_dio1_pdev = { - .name = "ts5500-dio1", - .id = -1, - .resource = ts5500_dio1_resource, - .num_resources = 1, -}; - -static struct resource ts5500_dio2_resource[] = { - DEFINE_RES_IRQ_NAMED(6, "DIO2 interrupt"), -}; - -static struct platform_device ts5500_dio2_pdev = { - .name = "ts5500-dio2", - .id = -1, - .resource = ts5500_dio2_resource, - .num_resources = 1, -}; - -static void ts5500_led_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - outb(!!brightness, TS5500_LED_JP_ADDR); -} - -static enum led_brightness ts5500_led_get(struct led_classdev *led_cdev) -{ - return (inb(TS5500_LED_JP_ADDR) & TS5500_LED) ? LED_FULL : LED_OFF; -} - -static struct led_classdev ts5500_led_cdev = { - .name = "ts5500:green:", - .brightness_set = ts5500_led_set, - .brightness_get = ts5500_led_get, -}; - -static int ts5500_adc_convert(u8 ctrl) -{ - u8 lsb, msb; - - /* Start conversion (ensure the 3 MSB are set to 0) */ - outb(ctrl & 0x1f, TS5500_ADC_CONV_INIT_LSB_ADDR); - - /* - * The platform has CPLD logic driving the A/D converter. - * The conversion must complete within 11 microseconds, - * otherwise we have to re-initiate a conversion. - */ - udelay(TS5500_ADC_CONV_DELAY); - if (inb(TS5500_ADC_CONV_BUSY_ADDR) & TS5500_ADC_CONV_BUSY) - return -EBUSY; - - /* Read the raw data */ - lsb = inb(TS5500_ADC_CONV_INIT_LSB_ADDR); - msb = inb(TS5500_ADC_CONV_MSB_ADDR); - - return (msb << 8) | lsb; -} - -static struct max197_platform_data ts5500_adc_pdata = { - .convert = ts5500_adc_convert, -}; - -static struct platform_device ts5500_adc_pdev = { - .name = "max197", - .id = -1, - .dev = { - .platform_data = &ts5500_adc_pdata, - }, -}; - -static int __init ts5500_init(void) -{ - struct platform_device *pdev; - struct ts5500_sbc *sbc; - int err; - - /* - * There is no DMI available or PCI bridge subvendor info, - * only the BIOS provides a 16-bit identification call. - * It is safer to find a signature in the BIOS shadow RAM. - */ - err = ts5500_check_signature(); - if (err) - return err; - - pdev = platform_device_register_simple("ts5500", -1, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - sbc = devm_kzalloc(&pdev->dev, sizeof(struct ts5500_sbc), GFP_KERNEL); - if (!sbc) { - err = -ENOMEM; - goto error; - } - - err = ts5500_detect_config(sbc); - if (err) - goto error; - - platform_set_drvdata(pdev, sbc); - - err = sysfs_create_group(&pdev->dev.kobj, &ts5500_attr_group); - if (err) - goto error; - - if (sbc->id == TS5500_PRODUCT_CODE) { - ts5500_dio1_pdev.dev.parent = &pdev->dev; - if (platform_device_register(&ts5500_dio1_pdev)) - dev_warn(&pdev->dev, "DIO1 block registration failed\n"); - ts5500_dio2_pdev.dev.parent = &pdev->dev; - if (platform_device_register(&ts5500_dio2_pdev)) - dev_warn(&pdev->dev, "DIO2 block registration failed\n"); - } - - if (led_classdev_register(&pdev->dev, &ts5500_led_cdev)) - dev_warn(&pdev->dev, "LED registration failed\n"); - - if (sbc->adc) { - ts5500_adc_pdev.dev.parent = &pdev->dev; - if (platform_device_register(&ts5500_adc_pdev)) - dev_warn(&pdev->dev, "ADC registration failed\n"); - } - - return 0; -error: - platform_device_unregister(pdev); - return err; -} -device_initcall(ts5500_init); From 54a2882ab9f75ab8a77a4f113145d4d709d2387c Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 16 Sep 2026 12:51:59 -0700 Subject: [PATCH 0492/2286] x86/cpu: Factor init_cpu_info() out of identify_cpu() identify_cpu() unconditionally resets the struct cpuinfo_x86 fields to their default values and clears the capability arrays with memset() before rescanning the CPU to fill it in again. However the boot CPU capabilities have already been scanned by early_identify_cpu(), with interrupts disabled. Introduce init_cpu_info() helper in preparation for letting the callers decide whether the reset is needed. No functional changes. Signed-off-by: Ihor Solodrai Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260916195203.1099646-2-ihor.solodrai@linux.dev --- arch/x86/kernel/cpu/common.c | 51 ++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 3716a6af13cba9..173748a7787730 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -1784,6 +1784,32 @@ static void __init cpu_parse_early_param(void) } } +static void init_cpu_info(struct cpuinfo_x86 *c) +{ + c->x86_cache_size = 0; + c->x86_vendor = X86_VENDOR_UNKNOWN; + c->x86_model = c->x86_stepping = 0; /* So far unknown... */ + c->x86_vendor_id[0] = '\0'; /* Unset */ + c->x86_model_id[0] = '\0'; /* Unset */ +#ifdef CONFIG_X86_64 + c->x86_clflush_size = 64; + c->x86_phys_bits = 36; + c->x86_virt_bits = 48; +#else + c->cpuid_level = -1; /* CPUID not detected */ + c->x86_clflush_size = 32; + c->x86_phys_bits = 32; + c->x86_virt_bits = 32; +#endif + c->x86_cache_alignment = c->x86_clflush_size; + memset(&c->x86_capability, 0, sizeof(c->x86_capability)); + memset(&c->cpuid, 0, sizeof(c->cpuid)); +#ifdef CONFIG_X86_VMX_FEATURE_NAMES + memset(&c->vmx_capability, 0, sizeof(c->vmx_capability)); +#endif + c->extended_cpuid_level = 0; +} + /* * Do minimum CPU detection early. * Fields really needed: vendor, cpuid_level, family, model, mask, @@ -1968,8 +1994,6 @@ void check_null_seg_clears_base(struct cpuinfo_x86 *c) static void generic_identify(struct cpuinfo_x86 *c) { - c->extended_cpuid_level = 0; - if (!cpuid_feature()) identify_cpu_without_cpuid(c); @@ -2013,27 +2037,8 @@ static void identify_cpu(struct cpuinfo_x86 *c) int i; c->loops_per_jiffy = loops_per_jiffy; - c->x86_cache_size = 0; - c->x86_vendor = X86_VENDOR_UNKNOWN; - c->x86_model = c->x86_stepping = 0; /* So far unknown... */ - c->x86_vendor_id[0] = '\0'; /* Unset */ - c->x86_model_id[0] = '\0'; /* Unset */ -#ifdef CONFIG_X86_64 - c->x86_clflush_size = 64; - c->x86_phys_bits = 36; - c->x86_virt_bits = 48; -#else - c->cpuid_level = -1; /* CPUID not detected */ - c->x86_clflush_size = 32; - c->x86_phys_bits = 32; - c->x86_virt_bits = 32; -#endif - c->x86_cache_alignment = c->x86_clflush_size; - memset(&c->x86_capability, 0, sizeof(c->x86_capability)); - memset(&c->cpuid, 0, sizeof(c->cpuid)); -#ifdef CONFIG_X86_VMX_FEATURE_NAMES - memset(&c->vmx_capability, 0, sizeof(c->vmx_capability)); -#endif + + init_cpu_info(c); generic_identify(c); From 7a762b51a149b7bfe431a7571bc7df4beea52e5c Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 16 Sep 2026 12:52:00 -0700 Subject: [PATCH 0493/2286] x86/cpu: Initialize boot CPU cpuinfo defaults early early_identify_cpu() clears the capability array, the CPUID table and extended_cpuid_level, but the architectural defaults for the rest of struct cpuinfo_x86 are set only later, in identify_cpu(). Use the same defaults from the start, so that the boot CPU does not depend on a later reset to end up with the right ones. Signed-off-by: Ihor Solodrai Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260916195203.1099646-3-ihor.solodrai@linux.dev --- arch/x86/kernel/cpu/common.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 173748a7787730..d1a949ad25bd19 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -1814,16 +1814,15 @@ static void init_cpu_info(struct cpuinfo_x86 *c) * Do minimum CPU detection early. * Fields really needed: vendor, cpuid_level, family, model, mask, * cache alignment. - * The others are not touched to avoid unwanted side effects. + * The others are reset to their defaults here and only filled in later, + * by identify_cpu(). * * WARNING: this function is only called on the boot CPU. Don't add code * here that is supposed to run on all CPUs. */ static void __init early_identify_cpu(struct cpuinfo_x86 *c) { - memset(&c->x86_capability, 0, sizeof(c->x86_capability)); - memset(&c->cpuid, 0, sizeof(c->cpuid)); - c->extended_cpuid_level = 0; + init_cpu_info(c); if (!cpuid_feature()) identify_cpu_without_cpuid(c); From ad86fe2134ccdc97a967693d2723a731c99fb7bc Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 16 Sep 2026 12:52:01 -0700 Subject: [PATCH 0494/2286] x86/cpu: Inline generic_identify() into identify_cpu() generic_identify() has exactly one call site: at the top of identify_cpu(). Fold it into identify_cpu() so that a single function does the job for both the boot CPU and the secondary CPUs. While at it, fix up both copies of the Cyrix comment. No functional changes. Signed-off-by: Ihor Solodrai Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260916195203.1099646-4-ihor.solodrai@linux.dev --- arch/x86/kernel/cpu/common.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index d1a949ad25bd19..79a1260853749e 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -1827,7 +1827,7 @@ static void __init early_identify_cpu(struct cpuinfo_x86 *c) if (!cpuid_feature()) identify_cpu_without_cpuid(c); - /* cyrix could have cpuid enabled via c_identify()*/ + /* Cyrix could have CPUID enabled via c_identify(). */ if (cpuid_feature()) { cpuid_scan_cpu(c); cpu_detect(c); @@ -1991,14 +1991,23 @@ void check_null_seg_clears_base(struct cpuinfo_x86 *c) set_cpu_bug(c, X86_BUG_NULL_SEG); } -static void generic_identify(struct cpuinfo_x86 *c) +/* + * This does the hard work of actually picking apart the CPU stuff... + */ +static void identify_cpu(struct cpuinfo_x86 *c) { + int i; + + c->loops_per_jiffy = loops_per_jiffy; + + init_cpu_info(c); + if (!cpuid_feature()) identify_cpu_without_cpuid(c); - /* cyrix could have cpuid enabled via c_identify()*/ + /* Cyrix could have CPUID enabled via c_identify(). */ if (!cpuid_feature()) - return; + goto no_cpuid; cpuid_scan_cpu(c); cpu_detect(c); @@ -2026,21 +2035,8 @@ static void generic_identify(struct cpuinfo_x86 *c) #ifdef CONFIG_X86_32 set_cpu_bug(c, X86_BUG_ESPFIX); #endif -} - -/* - * This does the hard work of actually picking apart the CPU stuff... - */ -static void identify_cpu(struct cpuinfo_x86 *c) -{ - int i; - - c->loops_per_jiffy = loops_per_jiffy; - - init_cpu_info(c); - - generic_identify(c); +no_cpuid: cpu_parse_topology(c); if (this_cpu->c_identify) From db349783ca7aa99345f3809523bff2db8f8714de Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 16 Sep 2026 12:52:02 -0700 Subject: [PATCH 0495/2286] x86/cpu: Move 32-bit SEP setup into identify_cpu() identify_boot_cpu() and identify_secondary_cpu() both call enable_sep_cpu() under CONFIG_X86_32 immediately after identify_cpu(). Do it once and drop the ifdefs while at it. No functional changes. Signed-off-by: Ihor Solodrai Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Nikolay Borisov Link: https://patch.msgid.link/20260916195203.1099646-5-ihor.solodrai@linux.dev --- arch/x86/kernel/cpu/common.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 79a1260853749e..156a7b8ec1c13b 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -2128,6 +2128,9 @@ static void identify_cpu(struct cpuinfo_x86 *c) mcheck_cpu_init(c); numa_add_cpu(smp_processor_id()); + + if (IS_ENABLED(CONFIG_X86_32)) + enable_sep_cpu(); } /* @@ -2165,9 +2168,6 @@ static __init void identify_boot_cpu(void) identify_cpu(&boot_cpu_data); if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT)) pr_info("CET detected: Indirect Branch Tracking enabled\n"); -#ifdef CONFIG_X86_32 - enable_sep_cpu(); -#endif cpu_detect_tlb(&boot_cpu_data); setup_cr_pinning(); @@ -2187,9 +2187,6 @@ void identify_secondary_cpu(unsigned int cpu) c->cpu_index = cpu; identify_cpu(c); -#ifdef CONFIG_X86_32 - enable_sep_cpu(); -#endif x86_spec_ctrl_setup_ap(); update_srbds_msr(); if (boot_cpu_has_bug(X86_BUG_GDS)) From 2711d67bc3a776abcfc651ad87be5d29ada19166 Mon Sep 17 00:00:00 2001 From: Ihor Solodrai Date: Wed, 16 Sep 2026 12:52:03 -0700 Subject: [PATCH 0496/2286] x86/cpu: Don't transiently clear the boot CPU's capabilities On the boot CPU, identify_cpu() runs from arch_cpu_finalize_init(), with interrupts enabled and before alternatives are patched. So cpu_feature_enabled() still evaluates against boot_cpu_data. identify_cpu() rebuilds c->x86_capability from scratch: the reset zeroes the array and the CPUID rescan fills it in again. An interrupt delivered in that window finds X86_FEATURE_LA57 clear in boot_cpu_data, so pgtable_l5_enabled() is false and KASAN checks a 5-level address against the 4-level addressability limit. The result is a bogus "wild-memory-access" report, and under kasan_multi_shot a report storm that wedges the boot. The boot CPU has already been scanned by early_identify_cpu(), with interrupts disabled, and its capabilities cannot have changed since. Reset only the CPUs which have not been scanned yet. The window is as old as identify_cpu() rebuilding the capabilities. Commit 39b9552281ab ("x86/mm: Optimize boot-time paging mode switching cost") merely let KASAN notice it by making pgtable_l5_enabled() read the feature bit. So no Fixes: tag. Closes: https://lore.kernel.org/bpf/20260610175651.647515-1-ihor.solodrai@linux.dev/ Signed-off-by: Ihor Solodrai Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260916195203.1099646-6-ihor.solodrai@linux.dev --- arch/x86/kernel/cpu/common.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 156a7b8ec1c13b..7d4ff292bdf566 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -2000,8 +2000,6 @@ static void identify_cpu(struct cpuinfo_x86 *c) c->loops_per_jiffy = loops_per_jiffy; - init_cpu_info(c); - if (!cpuid_feature()) identify_cpu_without_cpuid(c); @@ -2186,6 +2184,7 @@ void identify_secondary_cpu(unsigned int cpu) *c = boot_cpu_data; c->cpu_index = cpu; + init_cpu_info(c); identify_cpu(c); x86_spec_ctrl_setup_ap(); update_srbds_msr(); From 6db0f16f27e338e3b8b71be30609aa5f7823c8be Mon Sep 17 00:00:00 2001 From: Jan Polensky Date: Tue, 8 Sep 2026 15:58:43 +0200 Subject: [PATCH 0497/2286] s390/rwlock: Add contention tracepoints to rwlock slowpath Instrument arch_read_lock_wait() and arch_write_lock_wait() with trace_contention_begin() and trace_contention_end(). These tracepoints are used by lock contention analysis tools such as perf lock contention to identify contended rwlocks and measure wait times. The generic implementation in kernel/locking/qrwlock.c already emits the same events from its read and write slowpaths. For arch_read_lock_wait(), the in_interrupt() fast-path is intentionally left outside the tracepoint scope. That path spins without entering the wait queue, so it does not represent queue-based contention and its duration is not comparable to the normal slowpath. This matches the behaviour of queued_read_lock_slowpath() in qrwlock.c. The include for is already present from commit ffa796cc1f45 ("s390/spinlock: Add contention tracepoints to lock slowpath"). Signed-off-by: Jan Polensky Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/lib/spinlock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/s390/lib/spinlock.c b/arch/s390/lib/spinlock.c index dbabca35c008ca..c84991a1be200d 100644 --- a/arch/s390/lib/spinlock.c +++ b/arch/s390/lib/spinlock.c @@ -318,6 +318,7 @@ void arch_read_lock_wait(arch_rwlock_t *rw) /* Remove this reader again to allow recursive read locking */ __atomic_add_const(-1, &rw->cnts); + trace_contention_begin(rw, LCB_F_SPIN | LCB_F_READ); /* Put the reader into the wait queue */ arch_spin_lock(&rw->wait); /* Now add this reader to the count value again */ @@ -326,6 +327,7 @@ void arch_read_lock_wait(arch_rwlock_t *rw) while (READ_ONCE(rw->cnts) & 0x10000) barrier(); arch_spin_unlock(&rw->wait); + trace_contention_end(rw, 0); } EXPORT_SYMBOL(arch_read_lock_wait); @@ -333,6 +335,7 @@ void arch_write_lock_wait(arch_rwlock_t *rw) { int old; + trace_contention_begin(rw, LCB_F_SPIN | LCB_F_WRITE); /* Add this CPU to the write waiters */ __atomic_add(0x20000, &rw->cnts); @@ -349,6 +352,7 @@ void arch_write_lock_wait(arch_rwlock_t *rw) } arch_spin_unlock(&rw->wait); + trace_contention_end(rw, 0); } EXPORT_SYMBOL(arch_write_lock_wait); From 9c5eebc9de1fe6fcc2adad8a6f5fbb506d1eb9d0 Mon Sep 17 00:00:00 2001 From: Euan Bourke Date: Fri, 11 Sep 2026 11:13:09 +0200 Subject: [PATCH 0498/2286] s390/hiperdispatch: Use system_dfl_wq to reschedule hd_capacity_work In hd_enable_hiperdispatch(), hd_capacity_work is scheduled on system_dfl_wq, however the work function reschedules itself with schedule_delayed_work(), which uses system_percpu_wq. Hiperdispatch does not benefit from using the percpu workqueue. Use queue_delayed_work() with system_dfl_wq for consistency. Changing the workqueue from system_percpu_wq to system_dfl_wq causes no functional changes. Signed-off-by: Euan Bourke Tested-by: Mete Durlu Reviewed-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/kernel/hiperdispatch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/hiperdispatch.c b/arch/s390/kernel/hiperdispatch.c index 2172065222661b..8494823559b10e 100644 --- a/arch/s390/kernel/hiperdispatch.c +++ b/arch/s390/kernel/hiperdispatch.c @@ -265,7 +265,7 @@ static void hd_capacity_work_fn(struct work_struct *work) } trace_s390_hd_work_fn(steal_percentage, hd_entitled_cores, hd_high_capacity_cores); mutex_unlock(&smp_cpu_state_mutex); - schedule_delayed_work(&hd_capacity_work, HD_DELAY_INTERVAL); + queue_delayed_work(system_dfl_wq, &hd_capacity_work, HD_DELAY_INTERVAL); } static int hiperdispatch_ctl_handler(const struct ctl_table *ctl, int write, From 5ad03af184342c760a83a18dba32043f7cec24c6 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Wed, 9 Sep 2026 21:54:33 -0700 Subject: [PATCH 0499/2286] kernfs: allocate the open node outside the open file mutex kernfs_get_open_node() allocates the kernfs_open_node while holding one of the hashed kernfs_open_file mutexes, so opening a file nobody has open yet can enter reclaim with that mutex held. The mutex is shared by every node that hashes to it, so unrelated opens, closes and xattr updates in the same bucket wait. It is the most contended lock in kernfs on our fleet: 2.19M waiters over 29 days, more than two hundred times the waiters on kernfs_rwsem, and the highest median hold of any kernfs lock at 77ms. Most of it is monitoring daemons opening cgroup control files, and the open node is freed once the last descriptor closes, so an open-read-close loop allocates every time. Look at kn->attr.open before taking the mutex, and allocate then if nothing has the file open. Should the peek be wrong, which needs the last descriptor to close inside the window, fall back to allocating under the mutex as before. Eight tasks opening and closing four cgroup files 20000 times each, with lock_stat on the hashed mutex: contentions hold total hold avg before 198 4.19 s 8.67 us after 119 2.31 s 4.78 us and the same number of acquisitions either way. This does not empty the bucket. kernfs_fop_release() and kernfs_xattr_set() still sleep under the same mutex. It stops opens doing it. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260910045433.489413-1-shakeel.butt@linux.dev Acked-by: Tejun Heo Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/file.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c index 8e0e90c933720b..d140e769998329 100644 --- a/fs/kernfs/file.c +++ b/fs/kernfs/file.c @@ -525,18 +525,31 @@ static int kernfs_fop_mmap(struct file *file, struct vm_area_struct *vma) static int kernfs_get_open_node(struct kernfs_node *kn, struct kernfs_open_file *of) { - struct kernfs_open_node *on; + struct kernfs_open_node *on, *new_on = NULL; struct mutex *mutex; + /* + * Peek without the mutex: if nothing has this open, we will need a + * node and can allocate before taking a mutex shared by every node + * hashing to it. + */ + if (!rcu_access_pointer(kn->attr.open)) + new_on = kzalloc_obj(*new_on); + mutex = kernfs_open_file_mutex_lock(kn); on = kernfs_deref_open_node_locked(kn); if (!on) { /* not there, initialize a new one */ - on = kzalloc_obj(*on); + on = new_on; + new_on = NULL; if (!on) { - mutex_unlock(mutex); - return -ENOMEM; + /* the peek raced; rare, so allocate here */ + on = kzalloc_obj(*on); + if (!on) { + mutex_unlock(mutex); + return -ENOMEM; + } } atomic_set(&on->event, 1); init_waitqueue_head(&on->poll); @@ -549,6 +562,7 @@ static int kernfs_get_open_node(struct kernfs_node *kn, on->nr_to_release++; mutex_unlock(mutex); + kfree(new_on); return 0; } From 23985e2b1f31623468244a2149b2388cda4925eb Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Wed, 9 Sep 2026 21:54:06 -0700 Subject: [PATCH 0500/2286] kernfs: don't hold kernfs_rwsem across notification delivery Recently at Meta we noticed that on multi-tenant systems, a workload under extreme memory pressure negatively impacts unrelated workloads and system daemons like below [1] and fb-oomd [2]. Mainly we observed that system daemons were stuck on kernfs_rwsem in cgroup-related interfaces for long periods of time. On further inspection, we found that the workload under pressure had registered inotify watches on its memory.events and was continuously receiving limit-hitting notifications. The cgroup notification is done in kernfs_notify_workfn(), which holds kernfs_supers_rwsem and kernfs_rwsem and calls fsnotify(), which allocates the event with kmalloc(GFP_KERNEL_ACCOUNT|__GFP_RETRY_MAYFAIL) charged to the watching cgroup, which is already under memory pressure. So the notify worker ended up in memory reclaim of a cgroup already under pressure, and itself kept triggering the limit notifications. This notification path takes the locks in read mode, but a waiting writer blocks all future readers. That is exactly what is happening in the Meta fleet, and it leaves daemons that are critical to the reliability of the system stuck for long periods of time. Commit 400188ae361a ("kernfs: Acquire kernfs_rwsem in kernfs_notify_workfn().") added kernfs_rwsem just to safely read kernfs_node::name. Let's sample the name once before the loop and drop the lock. kernfs_supers_rwsem still covers the list, and the removal paths take that one for reading too, so a stalled worker can still hold a removal up once a mount or unmount is queued behind it. Two things change for a watcher. A name longer than NAME_MAX now gives an event with no name, where before it gave one with the full name; fsnotify() takes the name as optional, so the event still arrives. And the name and the parent are no longer sampled under one lock, so a rename between the two would name a file against the directory it moved to. That cannot happen today: kernfs_notify() rejects anything that is not a file, and no caller of kernfs_rename_ns() renames one. [1] https://github.com/facebookincubator/below [2] https://github.com/facebookincubator/oomd Fixes: 400188ae361a ("kernfs: Acquire kernfs_rwsem in kernfs_notify_workfn().") Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260910045406.485295-1-shakeel.butt@linux.dev Acked-by: Tejun Heo Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/file.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/kernfs/file.c b/fs/kernfs/file.c index d140e769998329..cca9f83fc9b5a9 100644 --- a/fs/kernfs/file.c +++ b/fs/kernfs/file.c @@ -918,9 +918,12 @@ static loff_t kernfs_fop_llseek(struct file *file, loff_t offset, int whence) static void kernfs_notify_workfn(struct work_struct *work) { - struct kernfs_node *kn; + char name_buf[NAME_MAX + 1]; struct kernfs_super_info *info; + struct kernfs_node *kn; struct kernfs_root *root; + struct qstr name; + bool have_name; repeat: /* pop one off the notify_list */ spin_lock_irq(&kernfs_notify_lock); @@ -936,14 +939,20 @@ static void kernfs_notify_workfn(struct work_struct *work) root = kernfs_root(kn); /* kick fsnotify */ + /* + * Sample the name once so kernfs_rwsem need not be held across the + * loop. A name that does not fit is reported without one; fsnotify() + * takes the name as optional, so a watcher loses the name and not the + * event. + */ + have_name = kernfs_name(kn, name_buf, sizeof(name_buf)) >= 0; + name = QSTR(name_buf); + down_read(&root->kernfs_supers_rwsem); - down_read(&root->kernfs_rwsem); - list_for_each_entry(info, &kernfs_root(kn)->supers, node) { + list_for_each_entry(info, &root->supers, node) { struct kernfs_node *parent; struct inode *p_inode = NULL; - const char *kn_name; struct inode *inode; - struct qstr name; /* * We want fsnotify_modify() on @kn but as the @@ -955,15 +964,14 @@ static void kernfs_notify_workfn(struct work_struct *work) if (!inode) continue; - kn_name = kernfs_rcu_name(kn); - name = QSTR(kn_name); parent = kernfs_get_parent(kn); if (parent) { p_inode = ilookup(info->sb, kernfs_ino(parent)); if (p_inode) { fsnotify(FS_MODIFY | FS_EVENT_ON_CHILD, inode, FSNOTIFY_EVENT_INODE, - p_inode, &name, inode, 0); + p_inode, have_name ? &name : NULL, + inode, 0); iput(p_inode); } @@ -976,7 +984,6 @@ static void kernfs_notify_workfn(struct work_struct *work) iput(inode); } - up_read(&root->kernfs_rwsem); up_read(&root->kernfs_supers_rwsem); kernfs_put(kn); goto repeat; From 5ebcb578adeec7407dd501108ab2f423ca8e3e55 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 11 Sep 2026 11:28:12 -0700 Subject: [PATCH 0501/2286] kernfs: don't repeat or skip an entry when readdir resumes readdir keeps its place as a name hash in ctx->pos and pins the entry in file->private_data. If that entry is gone when the listing comes back, kernfs_dir_pos() searches the rbtree for the hash and keeps whatever node the descent stopped on. That is the entry before or after the missing one, depending on the shape of the tree. Landing before it repeats an entry the previous getdents(2) call already reported. Landing after it, kernfs_dir_next_pos() calls rb_next() and steps over an unreported entry. With children A(10), B(20), C(30): report A, ctx->pos = 10 A removed kernfs_dir_next_pos(10, A) A is gone, the search for 10 stops at B rb_next(B) -> C, so B is never reported Only the repeat happens today, between two getdents(2) calls. The skip needs the pinned entry to go away inside one call, which the next patch allows when it drops kernfs_rwsem around dir_emit(). Before the commit 4e4d6d860b93 the descent kept a node only on the way left, which is a search for the first entry at or after the hash. That commit moved the assignment to the top of the loop, where it runs on right turns too. Restore that search, and step forward only when the pinned entry is still there rather than when the hash matches, since two entries in one directory can share a hash. While here, kernfs_dir_next_pos() called the hash @ino. It is never an inode number, so name it @hash. Fixes: 4e4d6d860b93 ("sysfs: Add s_hash to sysfs_dirent and order directory entries by hash") Acked-by: Tejun Heo Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260911182815.1992483-2-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 07abf59f0264a6..24a927c851239b 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1903,9 +1903,16 @@ static int kernfs_dir_fop_release(struct inode *inode, struct file *filp) return 0; } +/* + * Find where a listing left off. @resumed says whether @pos is still that + * entry; if not, the first entry at or after @hash is returned instead. + */ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, - struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos) + struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos, + bool *resumed) { + if (resumed) + *resumed = false; if (pos) { int valid = kernfs_active(pos) && rcu_access_pointer(pos->__parent) == parent && @@ -1913,23 +1920,26 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, kernfs_put(pos); if (!valid) pos = NULL; + else if (resumed) + *resumed = true; } if (!pos && (hash > 1) && (hash < INT_MAX)) { struct rb_node *node = parent->dir.children.rb_node; - u64 ns_id = kernfs_ns_id(ns); + + /* + * Keep a node only on the way left, so the search ends on the + * first entry at or after @hash. The empty name sorts before + * every entry sharing the hash, so it lands on the first. + */ while (node) { - pos = rb_to_kn(node); + struct kernfs_node *kn = rb_to_kn(node); - if (hash < pos->hash) + if (kernfs_name_compare(hash, "", ns, kn) < 0) { + pos = kn; node = node->rb_left; - else if (hash > pos->hash) + } else { node = node->rb_right; - else if (ns_id < kernfs_ns_id(pos->ns)) - node = node->rb_left; - else if (ns_id > kernfs_ns_id(pos->ns)) - node = node->rb_right; - else - break; + } } } /* Skip over entries which are dying/dead or in the wrong namespace */ @@ -1945,10 +1955,13 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, } static struct kernfs_node *kernfs_dir_next_pos(const struct ns_common *ns, - struct kernfs_node *parent, ino_t ino, struct kernfs_node *pos) + struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos) { - pos = kernfs_dir_pos(ns, parent, ino, pos); - if (pos) { + bool resumed; + + pos = kernfs_dir_pos(ns, parent, hash, pos, &resumed); + /* Step over @pos only if it survived; two entries can share a hash. */ + if (pos && resumed) { do { struct rb_node *node = rb_next(&pos->rb); if (!node) @@ -1978,7 +1991,7 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx) if (kernfs_ns_enabled(parent)) ns = kernfs_info(dentry->d_sb)->ns; - for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos); + for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos, NULL); pos; pos = kernfs_dir_next_pos(ns, parent, ctx->pos, pos)) { const char *name = kernfs_rcu_name(pos); From 45126ed80bb1c5eaf3d83aebc963826798d555b9 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 11 Sep 2026 11:28:13 -0700 Subject: [PATCH 0502/2286] kernfs: resume readdir on the full entry key readdir resumes at the entry pinned in file->private_data, and falls back to the hash in ctx->pos when that entry is gone. Two entries in one directory can share a hash, so the hash alone does not say where the listing stopped. Copy the name under the lock, emit the copy, and keep it as the resume key. The pinned entry counts as the same entry only while its name still matches, and the fallback search starts after (hash, ns, name) rather than at the first entry with the hash. No behaviour change yet. kernfs_rwsem is held across the loop, so nothing can move between two entries of one call, and a resume across getdents(2) calls has no saved name and keys on the hash as before. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260911182815.1992483-3-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 50 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 24a927c851239b..6d7d9c9ba33a38 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1905,18 +1905,24 @@ static int kernfs_dir_fop_release(struct inode *inode, struct file *filp) /* * Find where a listing left off. @resumed says whether @pos is still that - * entry; if not, the first entry at or after @hash is returned instead. + * entry; if not, the search falls back to @hash, keyed by @name if given. */ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos, - bool *resumed) + const char *name, bool *resumed) { if (resumed) *resumed = false; if (pos) { + /* + * A rename keeps the hash if the new name hashes the same, so + * check @name too. Otherwise the caller would step over the + * entry now sitting where @pos used to be. + */ int valid = kernfs_active(pos) && rcu_access_pointer(pos->__parent) == parent && - hash == pos->hash; + hash == pos->hash && + (!name || !strcmp(name, kernfs_rcu_name(pos))); kernfs_put(pos); if (!valid) pos = NULL; @@ -1928,13 +1934,13 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, /* * Keep a node only on the way left, so the search ends on the - * first entry at or after @hash. The empty name sorts before - * every entry sharing the hash, so it lands on the first. + * first entry after the key. An empty @name sorts before all + * entries sharing the hash, so it lands on the first of them. */ while (node) { struct kernfs_node *kn = rb_to_kn(node); - if (kernfs_name_compare(hash, "", ns, kn) < 0) { + if (kernfs_name_compare(hash, name ?: "", ns, kn) < 0) { pos = kn; node = node->rb_left; } else { @@ -1955,12 +1961,13 @@ static struct kernfs_node *kernfs_dir_pos(const struct ns_common *ns, } static struct kernfs_node *kernfs_dir_next_pos(const struct ns_common *ns, - struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos) + struct kernfs_node *parent, loff_t hash, struct kernfs_node *pos, + const char *name) { bool resumed; - pos = kernfs_dir_pos(ns, parent, hash, pos, &resumed); - /* Step over @pos only if it survived; two entries can share a hash. */ + pos = kernfs_dir_pos(ns, parent, hash, pos, name, &resumed); + /* Step over @pos only if it survived; @name finds the spot if not. */ if (pos && resumed) { do { struct rb_node *node = rb_next(&pos->rb); @@ -1979,25 +1986,42 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx) struct dentry *dentry = file->f_path.dentry; struct kernfs_node *parent = kernfs_dentry_node(dentry); struct kernfs_node *pos = file->private_data; + char *name __free(kfree) = NULL; struct kernfs_root *root; const struct ns_common *ns = NULL; if (!dir_emit_dots(file, ctx)) return 0; + /* + * One buffer for the call, holding the name of the entry the listing + * is on. PATH_MAX: kernfs bounds no single name. + */ + name = kmalloc(PATH_MAX, GFP_KERNEL); + if (!name) + return -ENOMEM; + root = kernfs_root(parent); down_read(&root->kernfs_rwsem); if (kernfs_ns_enabled(parent)) ns = kernfs_info(dentry->d_sb)->ns; - for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos, NULL); + for (pos = kernfs_dir_pos(ns, parent, ctx->pos, pos, NULL, NULL); pos; - pos = kernfs_dir_next_pos(ns, parent, ctx->pos, pos)) { - const char *name = kernfs_rcu_name(pos); + pos = kernfs_dir_next_pos(ns, parent, ctx->pos, pos, name)) { unsigned int type = fs_umode_to_dtype(pos->mode); - int len = strlen(name); ino_t ino = kernfs_ino(pos); + int len; + + /* + * The copy is also the resume key, so a truncated name would + * resume here again. getname() caps a path, so only an + * in-kernel caller can get here; end the listing instead. + */ + len = strscpy(name, kernfs_rcu_name(pos), PATH_MAX); + if (WARN_ON_ONCE(len < 0)) + break; ctx->pos = pos->hash; file->private_data = pos; From 341c65579de5b632eb7913bb68b3e375eeeb3ddf Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 11 Sep 2026 11:28:14 -0700 Subject: [PATCH 0503/2286] kernfs: don't hold kernfs_rwsem across dir_emit() kernfs_fop_readdir() holds kernfs_rwsem for reading across the whole listing, dir_emit() included. dir_emit() copies to userspace, so it can fault into reclaim while holding the lock that every create, remove and rename in the hierarchy needs. sysfs and cgroupfs have one per machine. Under memory pressure the monitoring daemons fault on their own getdents(2) buffer with it held: below: page allocation stall for 120 secs: order:0, mode:0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO|__GFP_COMP) nodemask=(null),cpuset=hostcritical.slice,mems_allowed=0 Call Trace: dump_stack_lvl+0x5d/0x80 __alloc_frozen_pages_noprof+0x5f4d/0x6300 ? memcg_list_lru_alloc+0x73/0x320 ? ima_file_check+0xd0/0x7d0 vma_alloc_folio_noprof+0x145/0x560 handle_mm_fault+0x17c9/0x2720 ? find_vma+0x27/0x30 do_user_addr_fault+0x39f/0x6e0 exc_page_fault+0x8f/0x110 asm_exc_page_fault+0x22/0x30 RIP: 0010:filldir64+0xd7/0x1a0 [Code:/RSP:/RAX:..R15: register block elided] kernfs_fop_readdir+0x2de/0x420 iterate_dir+0x8c/0x1f0 __se_sys_getdents64+0x61/0xe0 ? copy_page_from_iter+0x860/0x860 do_syscall_64+0x6a/0x250 entry_SYSCALL_64_after_hwframe+0x4b/0x53 Commit 9aab10a0249e ("kernfs: Don't re-lock kernfs_root::kernfs_rwsem in kernfs_fop_readdir().") took the lock drop out because dir_emit() was handed kernfs_node::name, which a rename can free. The previous patch emits a copy instead, so drop the lock around dir_emit(). A listing is no longer atomic within one getdents(2) call, which for most sysfs and cgroup directories is all of it. POSIX leaves that unspecified for an entry added or removed since opendir(3). Fixes: 9aab10a0249e ("kernfs: Don't re-lock kernfs_root::kernfs_rwsem in kernfs_fop_readdir().") Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260911182815.1992483-4-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 6d7d9c9ba33a38..cc6288d5b4cc40 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -2027,10 +2027,14 @@ static int kernfs_fop_readdir(struct file *file, struct dir_context *ctx) file->private_data = pos; kernfs_get(pos); - if (!dir_emit(ctx, name, len, ino, type)) { - up_read(&root->kernfs_rwsem); + /* + * dir_emit() can fault, so run it unlocked. @pos is pinned + * above and kernfs_dir_pos() rechecks it on the way back. + */ + up_read(&root->kernfs_rwsem); + if (!dir_emit(ctx, name, len, ino, type)) return 0; - } + down_read(&root->kernfs_rwsem); } up_read(&root->kernfs_rwsem); file->private_data = NULL; From a4354906005d66a485a3258b6d7ddfe6fad0fef3 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Fri, 11 Sep 2026 11:28:15 -0700 Subject: [PATCH 0504/2286] selftests: cover readdir resuming at a removed entry The kernfs tests list only directories that do not change, so nothing covers the entry a listing stopped on being gone when it comes back. readdir_resume_at_removed_entry takes a telldir(3) cookie for every entry, then removes each entry in turn, seeks to its cookie and reads the rest; nothing reported before it may come back. This is a resume between two getdents(2) calls, and it fails without "kernfs: don't repeat or skip an entry when readdir resumes". readdir_resume_vs_internal_remove is a resume inside one call, which "kernfs: don't hold kernfs_rwsem across dir_emit()" opens. It churns cgroup.subtree_control rather than calling rmdir(2), which cannot reach that window because iterate_dir() holds the listed directory's i_rwsem for the whole listing. It is a stress test, has not been seen to catch the ordering bug, and keeps the window busy for lockdep and KASAN. It fails if the churn died and skips if the listings never overlapped it, so it cannot pass having listed a static directory. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260911182815.1992483-5-shakeel.butt@linux.dev Signed-off-by: Christian Brauner (Amutable) --- .../selftests/filesystems/kernfs_test.c | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/tools/testing/selftests/filesystems/kernfs_test.c b/tools/testing/selftests/filesystems/kernfs_test.c index 6e74da91ebca5f..01cd58e5e41e7e 100644 --- a/tools/testing/selftests/filesystems/kernfs_test.c +++ b/tools/testing/selftests/filesystems/kernfs_test.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include "kselftest_harness.h" @@ -472,6 +474,204 @@ TEST_F(kernfs_cgroup, readdir_no_duplicates) EXPECT_STRNE(names[i], names[j]); } +#define RESUME_DIRS 24 + +/* + * Resuming at an entry that has gone must carry on after it, never before. + * Take a cookie for every entry, then remove each one, seek to its cookie + * and read the rest; nothing already reported may come back. + */ +TEST_F(kernfs_cgroup, readdir_resume_at_removed_entry) +{ + /* The cgroup's own control files are listed alongside ours. */ + char names[128][NAME_MAX + 1]; + long pos[128]; + char path[PATH_MAX]; + struct dirent *de; + int n = 0, i, j; + DIR *d; + + for (i = 0; i < RESUME_DIRS; i++) { + snprintf(path, sizeof(path), "%s/e%02d", self->scratch, i); + ASSERT_EQ(mkdir(path, 0755), 0); + } + + /* Record the cookie before reading each entry, with its name. */ + d = opendir(self->scratch); + ASSERT_NE(d, NULL); + while (1) { + long here = telldir(d); + + de = readdir(d); + if (!de) + break; + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + ASSERT_LT(n, (int)ARRAY_SIZE(pos)); + pos[n] = here; + strncpy(names[n], de->d_name, NAME_MAX); + names[n][NAME_MAX] = '\0'; + n++; + } + closedir(d); + ASSERT_GT(n, 1); + + for (i = 0; i < n; i++) { + /* Only the directories we made can be removed and put back. */ + if (strncmp(names[i], "e", 1)) + continue; + + snprintf(path, sizeof(path), "%s/%s", self->scratch, names[i]); + ASSERT_EQ(rmdir(path), 0); + + /* Reopen so the seek has to reach the kernel. */ + d = opendir(self->scratch); + ASSERT_NE(d, NULL); + seekdir(d, pos[i]); + while ((de = readdir(d))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + for (j = 0; j < i; j++) + ASSERT_STRNE(de->d_name, names[j]) + TH_LOG("resuming at %s (gone) went back to %s", + names[i], names[j]); + } + closedir(d); + + ASSERT_EQ(mkdir(path, 0755), 0); + } + + for (i = 0; i < RESUME_DIRS; i++) { + snprintf(path, sizeof(path), "%s/e%02d", self->scratch, i); + EXPECT_EQ(rmdir(path), 0); + } +} + +#define CHURN_ROUNDS 400 +#define CHURN_BUFSZ 512 /* small, so a listing takes several calls */ + +/* + * The files appear at the end of the enabling write and go at the start of + * the disabling one, so the window where they exist is the short one. + */ +#define CHURN_DWELL_ON 2000 +#define CHURN_DWELL_OFF 200 + +struct kernfs_dirent64 { + unsigned long long d_ino; + long long d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[]; +}; + +/* + * The same resume, but inside one getdents(2) call. rmdir(2) cannot reach + * that window because iterate_dir() holds the listed directory's i_rwsem + * for the whole listing; cgroup.subtree_control can, having no VFS + * operation on the names it adds and removes. The files that are not the + * controller's stay throughout, so each must appear exactly once. + * + * A stress test: it has not been seen to catch the ordering bug, and is + * here to keep the unlocked window under load for lockdep and KASAN. + */ +TEST_F(kernfs_cgroup, readdir_resume_vs_internal_remove) +{ + char buf[CHURN_BUFSZ] __attribute__((aligned(8))); + char stable[128][NAME_MAX + 1]; + int nstable = 0, i, r; + int withctl = 0, without = 0; + int seen[128], status; + pid_t churner; + DIR *d; + + /* With the controller off, whatever is left is what must persist. */ + ASSERT_EQ(write_file(self->scratch_sc, self->disable), 0); + d = opendir(self->child); + ASSERT_NE(d, NULL); + for (;;) { + struct dirent *de = readdir(d); + + if (!de) + break; + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + ASSERT_LT(nstable, (int)ARRAY_SIZE(stable)); + strncpy(stable[nstable], de->d_name, NAME_MAX); + stable[nstable][NAME_MAX] = '\0'; + nstable++; + } + closedir(d); + ASSERT_GT(nstable, 0); + + churner = fork(); + ASSERT_GE(churner, 0); + if (churner == 0) { + for (;;) { + if (write_file(self->scratch_sc, self->enable)) + _exit(10); + usleep(CHURN_DWELL_ON); + if (write_file(self->scratch_sc, self->disable)) + _exit(11); + usleep(CHURN_DWELL_OFF); + } + } + + for (r = 0; r < CHURN_ROUNDS; r++) { + int fd = open(self->child, O_RDONLY | O_DIRECTORY); + int extra = 0; + int n; + + ASSERT_GE(fd, 0); + memset(seen, 0, sizeof(seen)); + + while ((n = syscall(SYS_getdents64, fd, buf, sizeof(buf))) > 0) { + int off = 0; + + while (off < n) { + struct kernfs_dirent64 *de = (void *)(buf + off); + bool known = false; + + off += de->d_reclen; + for (i = 0; i < nstable; i++) + if (!strcmp(de->d_name, stable[i])) { + seen[i]++; + known = true; + } + if (!known && strcmp(de->d_name, ".") && + strcmp(de->d_name, "..")) + extra++; + } + } + ASSERT_GE(n, 0); + EXPECT_EQ(close(fd), 0); + + if (extra) + withctl++; + else + without++; + + for (i = 0; i < nstable; i++) + ASSERT_EQ(seen[i], 1) + TH_LOG("round %d: %s seen %d times", + r, stable[i], seen[i]); + } + + /* The churn must have been running, or the listings prove nothing. */ + EXPECT_EQ(kill(churner, SIGKILL), 0); + ASSERT_EQ(waitpid(churner, &status, 0), churner); + ASSERT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) + TH_LOG("churner exited on its own: status %d", status); + + /* + * They also have to have overlapped it. How much depends on the + * machine, so say the race could not be arranged rather than fail. + */ + if (!withctl || !without) + SKIP(return, "listings did not span the churn: %d with, %d without", + withctl, without); +} + /* * A telldir() cookie must resolve back to the same entry after seekdir(). * kernfs encodes the cookie as the node's name hash, so this covers From 161ce1e692d01ddec0522f24a29535ac59a3afaf Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Mon, 3 Aug 2026 14:51:38 +0200 Subject: [PATCH 0505/2286] fs: avoid spurious dentry ref/unref cycle on open Opening a file grabs a reference on the terminal dentry in __legitimize_path(), then another one in do_dentry_open() and finally drops the initial reference in terminate_walk(). That's 2 modifications which don't need to be there -- do_dentry_open() can consume the already held reference instead. When benchmarking on a 20-core vm using will-it-scale to open the same file read-only, the results are (ops/s): before: 4043375 after: 5629378 (+39%) Signed-off-by: Mateusz Guzik Link: https://patch.msgid.link/20260803125138.1937674-1-mjguzik@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/internal.h | 1 + fs/namei.c | 15 ++++++++++++--- fs/open.c | 27 ++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/fs/internal.h b/fs/internal.h index c658c8a5ebd569..9632239036accf 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -205,6 +205,7 @@ int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group, int flag); int chown_common(const struct path *path, uid_t user, gid_t group); extern int vfs_open(const struct path *, struct file *); +int vfs_open_consume(struct path *, struct file *); /* * inode.c diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3efff..cffc98f50f3e89 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4789,6 +4789,7 @@ static const char *open_last_lookups(struct nameidata *nd, static int do_open(struct nameidata *nd, struct file *file, const struct open_flags *op) { + struct vfsmount *mnt; struct mnt_idmap *idmap; int open_flag = op->open_flag; bool do_truncate; @@ -4830,11 +4831,17 @@ static int do_open(struct nameidata *nd, error = mnt_want_write(nd->path.mnt); if (error) return error; + /* + * A dedicated reference is needed because after the call to + * vfs_open_consume() we no longer own the reference in nd->path.mnt + * while we need to undo write acess below. + */ + mnt = mntget(nd->path.mnt); do_truncate = true; } error = may_open(idmap, &nd->path, acc_mode, open_flag); if (!error && !(file->f_mode & FMODE_OPENED)) - error = vfs_open(&nd->path, file); + error = vfs_open_consume(&nd->path, file); if (!error) error = security_file_post_open(file, op->acc_mode); if (!error && do_truncate) @@ -4843,8 +4850,10 @@ static int do_open(struct nameidata *nd, WARN_ON(1); error = -EINVAL; } - if (do_truncate) - mnt_drop_write(nd->path.mnt); + if (do_truncate) { + mnt_drop_write(mnt); + mntput(mnt); + } return error; } diff --git a/fs/open.c b/fs/open.c index 6b1c14e684a93b..2a7697cee00bcb 100644 --- a/fs/open.c +++ b/fs/open.c @@ -931,6 +931,11 @@ static inline int file_get_write_access(struct file *f) return error; } +/* + * Populate struct file + * + * NOTE: it assumes f_path is populated and consumes the caller's reference. + */ static int do_dentry_open(struct file *f, int (*open)(struct inode *, struct file *)) { @@ -938,7 +943,6 @@ static int do_dentry_open(struct file *f, struct inode *inode = f->f_path.dentry->d_inode; int error; - path_get(&f->f_path); f->f_inode = inode; f->f_mapping = inode->i_mapping; f->f_wb_err = filemap_sample_wb_err(f->f_mapping); @@ -1055,6 +1059,7 @@ int finish_open(struct file *file, struct dentry *dentry, BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ file->__f_path.dentry = dentry; + path_get(&file->f_path); return do_dentry_open(file, open); } EXPORT_SYMBOL(finish_open); @@ -1098,6 +1103,7 @@ int vfs_open(const struct path *path, struct file *file) int ret; file->__f_path = *path; + path_get(&file->f_path); ret = do_dentry_open(file, NULL); if (!ret) { /* @@ -1110,6 +1116,25 @@ int vfs_open(const struct path *path, struct file *file) return ret; } +/** + * vfs_open_consume - open the file at the given path and consume the reference + * @path: path to open + * @file: newly allocated file with f_flag initialized + */ +int vfs_open_consume(struct path *path, struct file *file) +{ + int ret; + + file->__f_path = *path; + path->mnt = NULL; + path->dentry = NULL; + ret = do_dentry_open(file, NULL); + if (!ret) { + fsnotify_open(file); + } + return ret; +} + struct file *dentry_open(const struct path *path, int flags, const struct cred *cred) { From 46767b17fda3fba1ed91dd73d7d432b0bb6851c1 Mon Sep 17 00:00:00 2001 From: Saranya Gopal Date: Fri, 4 Sep 2026 13:08:44 +0530 Subject: [PATCH 0506/2286] ACPI: PM: s2idle: Add _Ixx GPE wake indicator support ACPI 6.6 defines _Ixx indicator objects under \_GPE to mark GPEs that should remain wake-enabled in s2idle without a _PRW association. To support them, add new function acpi_setup_ixx_gpes() that looks for _Ixx objects for all GPEs in the FADT 0/1 blocks and if it finds any, it marks the corresponding GPE as wake-capable, in which case the GPE is also enabled so long as it has a handler method because marking it as wake-capable causes acpi_update_all_gpes() to skip it. Since the above only needs to be done if suspend-to-idle is enabled, make acpi_s2idle_setup() call that new function. For now, this is limited to x86 systems and it assumes that the GPEs with _Ixx will not cause spurious wakeups to occur while the system is suspended. Signed-off-by: Saranya Gopal [ rjw: Added three empty code lines, subject tweak, changelog edits ] [ rjw: New debug message adjustment ] Link: https://patch.msgid.link/20260904073844.2740931-1-saranya.gopal@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index b6b1dd76a06ba0..c278ea14c0b36c 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -24,6 +24,34 @@ #ifdef CONFIG_SUSPEND +static void acpi_setup_ixx_gpes(void) +{ + acpi_handle gpe_root; + unsigned int i; + char gpe_nr_str[5]; + + if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_GPE", &gpe_root))) + return; + + for (i = 0; i <= 0xff; i++) { + scnprintf(gpe_nr_str, sizeof(gpe_nr_str), "_I%02X", i); + if (!acpi_has_method(gpe_root, gpe_nr_str)) + continue; + + /* + * Enable the GPE if it has a handler method because marking it + * as wake-capable causes acpi_update_all_gpes() to skip it. + */ + if (ACPI_FAILURE(acpi_enable_gpe_cond(NULL, i, ACPI_GPE_DISPATCH_METHOD))) + continue; + + acpi_mark_gpe_for_wake(NULL, i); + acpi_set_gpe_wake_mask(NULL, i, ACPI_GPE_ENABLE); + + pm_pr_dbg("ACPI: GPE0x%02x armed for wake via %s\n", i, gpe_nr_str); + } +} + static bool sleep_no_lps0 __read_mostly; module_param(sleep_no_lps0, bool, 0644); MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); @@ -649,6 +677,7 @@ void __init acpi_s2idle_setup(void) { acpi_scan_add_handler(&lps0_handler); s2idle_set_ops(&acpi_s2idle_ops_lps0); + acpi_setup_ixx_gpes(); } int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) From ff87968e4e564acb2fadce363b1b41e7fa3aad2f Mon Sep 17 00:00:00 2001 From: Erkan Erdem Date: Mon, 7 Sep 2026 13:37:37 +0300 Subject: [PATCH 0507/2286] docs: kconfig: fix shell function syntax in caveats Kconfig separates a function name from its arguments with a comma, but the caveats section uses Make-style whitespace in its shell calls. These expressions expand as undefined variables rather than invoking the shell function, so the supposedly working CC_HAS_ENDIAN_FLAG example fails to parse. Add the missing commas to the shell calls in this section. Keep the Make examples unchanged. Fixes: 316d55d55f49 ("Documentation: kconfig: document a new Kconfig macro language") Assisted-by: LLM Signed-off-by: Erkan Erdem Reviewed-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260907103739.23212-1-hexvalid@gmail.com Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kconfig-macro-language.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/kbuild/kconfig-macro-language.rst b/Documentation/kbuild/kconfig-macro-language.rst index 6163467f6ae4f0..c427bece3148a6 100644 --- a/Documentation/kbuild/kconfig-macro-language.rst +++ b/Documentation/kbuild/kconfig-macro-language.rst @@ -225,7 +225,7 @@ not work:: $(MY_TYPE) "foo" default y -Obviously from the design, $(shell command) is expanded in the textual +Obviously from the design, $(shell,command) is expanded in the textual substitution phase. You cannot pass symbols to the 'shell' function. The following does not work as expected:: @@ -236,12 +236,12 @@ The following does not work as expected:: default "-mlittle-endian" if CPU_LITTLE_ENDIAN config CC_HAS_ENDIAN_FLAG - def_bool $(shell $(srctree)/scripts/gcc-check-flag ENDIAN_FLAG) + def_bool $(shell,$(srctree)/scripts/gcc-check-flag ENDIAN_FLAG) Instead, you can do like follows so that any function call is statically expanded:: config CC_HAS_ENDIAN_FLAG bool - default $(shell $(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN - default $(shell $(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN + default $(shell,$(srctree)/scripts/gcc-check-flag -mbig-endian) if CPU_BIG_ENDIAN + default $(shell,$(srctree)/scripts/gcc-check-flag -mlittle-endian) if CPU_LITTLE_ENDIAN From 4b31656d917c46c6d9f874162ede4f54b2a5959f Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 21 Aug 2026 11:11:27 +0300 Subject: [PATCH 0508/2286] fs/resctrl: Avoid extra call to strlen() in schemata_list_add() After passing an overflow check, it's safe to assume that snprintf() returns the number of characters emitted. So drop the unnecessary call to strlen(). Signed-off-by: Dmitry Antipov Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Link: https://patch.msgid.link/20260821081128.19242-1-dmantipov@yandex.ru --- fs/resctrl/rdtgroup.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c index 5dcbb0a964e815..68be9b903ac666 100644 --- a/fs/resctrl/rdtgroup.c +++ b/fs/resctrl/rdtgroup.c @@ -2858,7 +2858,7 @@ static int schemata_list_add(struct rdt_resource *r, enum resctrl_conf_type type { struct resctrl_schema *s; const char *suffix = ""; - int ret, cl; + int cl; s = kzalloc_obj(*s); if (!s) @@ -2882,14 +2882,12 @@ static int schemata_list_add(struct rdt_resource *r, enum resctrl_conf_type type break; } - ret = snprintf(s->name, sizeof(s->name), "%s%s", r->name, suffix); - if (ret >= sizeof(s->name)) { + cl = snprintf(s->name, sizeof(s->name), "%s%s", r->name, suffix); + if (cl >= sizeof(s->name)) { kfree(s); return -EINVAL; } - cl = strlen(s->name); - /* * If CDP is supported by this resource, but not enabled, * include the suffix. This ensures the tabular format of the From 0fa55c5539173bceaeb400c69b1974aaa0cdb8dc Mon Sep 17 00:00:00 2001 From: Shengming Hu Date: Wed, 16 Sep 2026 14:09:26 +0800 Subject: [PATCH 0509/2286] cpufreq: conservative: Restrict deferred downscaling to low load For a shared cpufreq policy, dbs_update() derives the load used for the frequency decision from the maximum load among its CPUs, but it may also record deferred idle periods accumulated by an idle CPU in the policy. Consequently, a single update can contain both a high decision load from one CPU and multiple deferred idle periods from another CPU. The conservative governor applies the deferred down steps before evaluating the decision load. If that load subsequently triggers an up step, the deferred down steps can outweigh the single up step and produce a net frequency reduction. This was observed on a policy shared by CPUs 2 and 3. A CPU-bound SCHED_EXT task kept CPU 2 fully utilized while CPU 3 remained idle. On this system, SCHED_EXT generated update-util callbacks less frequently than CFS, resulting in sparse DBS updates such as: load=100 idle_periods=7 interval=59 ms load=100 idle_periods=4 interval=39 ms load=100 idle_periods=2 interval=19 ms load=100 idle_periods=7 interval=59 ms With the default 5% frequency step and a 2.6 GHz policy maximum, seven deferred periods reduce the requested frequency by seven 130 MHz steps before the high decision load adds only one step. Repeated updates therefore keep the policy near 530 MHz even though CPU 2 is fully utilized. There are two load values relevant to this behavior: * sample_load is calculated from the busy and elapsed time of the current sample. * decision_load is returned by dbs_update() for the governor's frequency decision. After a long idle interval, it may reuse prev_load to improve the response to a waking task and can therefore differ from sample_load. The problem is that idle_periods is derived from accumulated idle time and is applied independently of whether the current sample load is in the conservative governor's downscaling region. A high sample load and multiple deferred idle periods can therefore coexist, particularly when updates are sparse. As suggested by Zhongqiu Han, calculate the sample load separately from the decision load. If no time has elapsed, use prev_load for the sample load as the existing load calculation does, since no load can be calculated for that interval. Record the maximum sample load for the policy and apply deferred down steps only when it is below down_threshold. This makes deferred downscaling follow the same threshold that the conservative governor normally uses to determine whether the policy load is low enough to reduce the frequency. The existing code applies deferred down steps whenever idle_periods is valid, regardless of the sample load. When the decision load is in the hold region, the locally adjusted frequency is not submitted unless another frequency-update branch is taken. Thus, when the sample and decision loads are the same and both are in the hold region, this change does not alter the requested frequency. The difference becomes visible when the decision load causes a frequency update. In particular, if it exceeds up_threshold, the governor applies a single up step after the deferred down steps and submits the resulting request. Multiple deferred down steps can then outweigh that up step and cause a frequency reduction even when the sample load is above down_threshold. With this change, deferred down steps are skipped whenever the maximum sample load is at or above down_threshold, keeping deferred downscaling consistent with the conservative governor's threshold semantics. The requested frequency may therefore be higher than with the existing behavior when deferred idle periods are present and the decision load causes a frequency update. This is intentional, because the lower request previously resulted from applying deferred downscaling while the sampled policy load was outside the governor's downscaling region. Accelerated deferred downscaling remains available when the policy sample load is below down_threshold, including after a workload has completed and the policy has become idle. Fixes: 00bfe05889e9 ("cpufreq: conservative: Decrease frequency faster for deferred updates") Cc: All applicable Reviewed-by: Luo Haiyang Reviewed-by: Run Zhang Suggested-by: Zhongqiu Han Signed-off-by: Shengming Hu Link: https://patch.msgid.link/20260916140926740-DCokXh6dAgtqDn1EWdCh@zte.com.cn Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cpufreq_conservative.c | 8 ++++--- drivers/cpufreq/cpufreq_governor.c | 29 +++++++++++++++++++------- drivers/cpufreq/cpufreq_governor.h | 2 ++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/drivers/cpufreq/cpufreq_conservative.c b/drivers/cpufreq/cpufreq_conservative.c index 0b32ae28ec857f..02bfd46543e99c 100644 --- a/drivers/cpufreq/cpufreq_conservative.c +++ b/drivers/cpufreq/cpufreq_conservative.c @@ -85,10 +85,12 @@ static unsigned int cs_dbs_update(struct cpufreq_policy *policy) freq_step = get_freq_step(cs_tuners, policy); /* - * Decrease requested_freq one freq_step for each idle period that - * we didn't update the frequency. + * Apply deferred down steps only when the policy sample load is + * below down_threshold. Otherwise, multiple deferred down steps may + * cause a net frequency decrease outside the downscaling region. */ - if (policy_dbs->idle_periods < UINT_MAX) { + if (policy_dbs->max_sample_load < cs_tuners->down_threshold && + policy_dbs->idle_periods < UINT_MAX) { unsigned int freq_steps = policy_dbs->idle_periods * freq_step; if (requested_freq > policy->min + freq_steps) diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c index 710d93ec89b533..f3bd3dbe7c419c 100644 --- a/drivers/cpufreq/cpufreq_governor.c +++ b/drivers/cpufreq/cpufreq_governor.c @@ -124,7 +124,8 @@ unsigned int dbs_update(struct cpufreq_policy *policy) struct policy_dbs_info *policy_dbs = policy->governor_data; struct dbs_data *dbs_data = policy_dbs->dbs_data; unsigned int ignore_nice = dbs_data->ignore_nice_load; - unsigned int max_load = 0, idle_periods = UINT_MAX; + unsigned int max_load = 0, max_sample_load = 0; + unsigned int idle_periods = UINT_MAX; unsigned int sampling_rate, io_busy, j; u64 cur_nice; @@ -147,7 +148,7 @@ unsigned int dbs_update(struct cpufreq_policy *policy) struct cpu_dbs_info *j_cdbs = &per_cpu(cpu_dbs, j); u64 update_time, cur_idle_time; unsigned int idle_time, time_elapsed; - unsigned int load; + unsigned int load, sample_load; cur_idle_time = get_cpu_idle_time(j, &update_time, io_busy); @@ -186,6 +187,20 @@ unsigned int dbs_update(struct cpufreq_policy *policy) j_cdbs->prev_cpu_nice = cur_nice; + /* + * Compute the sample load separately from the prev_load value + * that may be reused after a long idle interval. The conservative + * governor uses it to decide whether to apply deferred down steps. + * If no time has elapsed, retain the existing behavior and use + * prev_load. + */ + if (unlikely(!time_elapsed)) + sample_load = j_cdbs->prev_load; + else if (time_elapsed > idle_time) + sample_load = 100 * (time_elapsed - idle_time) / time_elapsed; + else + sample_load = 0; + if (unlikely(!time_elapsed)) { /* * That can only happen when this function is called @@ -220,11 +235,7 @@ unsigned int dbs_update(struct cpufreq_policy *policy) load = j_cdbs->prev_load; j_cdbs->prev_load = 0; } else { - if (time_elapsed > idle_time) - load = 100 * (time_elapsed - idle_time) / time_elapsed; - else - load = 0; - + load = sample_load; j_cdbs->prev_load = load; } @@ -237,9 +248,13 @@ unsigned int dbs_update(struct cpufreq_policy *policy) if (load > max_load) max_load = load; + + if (sample_load > max_sample_load) + max_sample_load = sample_load; } policy_dbs->idle_periods = idle_periods; + policy_dbs->max_sample_load = max_sample_load; return max_load; } diff --git a/drivers/cpufreq/cpufreq_governor.h b/drivers/cpufreq/cpufreq_governor.h index 73b8ed7cfaae76..806d8fb4dff1d9 100644 --- a/drivers/cpufreq/cpufreq_governor.h +++ b/drivers/cpufreq/cpufreq_governor.h @@ -95,6 +95,8 @@ struct policy_dbs_info { /* Multiplier for increasing sample delay temporarily. */ unsigned int rate_mult; unsigned int idle_periods; /* For conservative */ + /* Maximum load from the current policy sample. */ + unsigned int max_sample_load; /* Status indicators */ bool is_shared; /* This object is used by multiple CPUs */ bool work_in_progress; /* Work is being queued up or in progress */ From a3f5e6eba41885dcffb635b54d6c4a18aa579cd5 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Fri, 21 Aug 2026 11:11:28 +0300 Subject: [PATCH 0510/2286] fs/resctrl: Simplify pseudo_lock_measure_trigger() Use convenient kstrtoint_from_user() to simplify pseudo_lock_measure_trigger(). No functional changes. Signed-off-by: Dmitry Antipov Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Link: https://patch.msgid.link/20260821081128.19242-2-dmantipov@yandex.ru --- fs/resctrl/pseudo_lock.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/fs/resctrl/pseudo_lock.c b/fs/resctrl/pseudo_lock.c index dea2b4bf966fa5..56ab63f19badf1 100644 --- a/fs/resctrl/pseudo_lock.c +++ b/fs/resctrl/pseudo_lock.c @@ -750,17 +750,10 @@ static ssize_t pseudo_lock_measure_trigger(struct file *file, size_t count, loff_t *ppos) { struct rdtgroup *rdtgrp = file->private_data; - size_t buf_size; - char buf[32]; int ret; int sel; - buf_size = min(count, (sizeof(buf) - 1)); - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - - buf[buf_size] = '\0'; - ret = kstrtoint(buf, 10, &sel); + ret = kstrtoint_from_user(user_buf, count, 10, &sel); if (ret == 0) { if (sel != 1 && sel != 2 && sel != 3) return -EINVAL; From c298301279bd233363de3aca5a32c055bf2f3239 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 16:52:23 -0300 Subject: [PATCH 0511/2286] nilfs2: validate segment number in nilfs_sufile_get_suinfo() nilfs_sufile_get_suinfo() subtracts the caller-provided segment number from the total number of segments without first checking its range. If the requested number is greater than the total, the unsigned subtraction wraps and the function may process segment numbers outside the filesystem. Cache the total while holding the metadata semaphore and return no entries when the starting segment number is at or beyond the end. Fixes: 6c98cd4ecb0a ("nilfs2: segment usage file") Cc: stable+noautosel@kernel.org # Non-fatal bug fix; defer backport until a real issue is reported Signed-off-by: Aldo Ariel Panzardo Acked-by: Ryusuke Konishi Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/sufile.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c index 6918be6fce2eb3..fec5af70c6dd58 100644 --- a/fs/nilfs2/sufile.c +++ b/fs/nilfs2/sufile.c @@ -870,10 +870,14 @@ ssize_t nilfs_sufile_get_suinfo(struct inode *sufile, __u64 segnum, void *buf, down_read(&NILFS_MDT(sufile)->mi_sem); + nsegs = nilfs_sufile_get_nsegments(sufile); + if (segnum >= nsegs) { + ret = 0; + goto out; + } + segusages_per_block = nilfs_sufile_segment_usages_per_block(sufile); - nsegs = min_t(unsigned long, - nilfs_sufile_get_nsegments(sufile) - segnum, - nsi); + nsegs = min_t(unsigned long, nsegs - segnum, nsi); for (i = 0; i < nsegs; i += n, segnum += n) { n = min_t(unsigned long, segusages_per_block - From b5aa69375d2fb46ca9c3cd43ecd843ffbce38be7 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 4 Sep 2026 14:12:06 -0700 Subject: [PATCH 0512/2286] PM: runtime: kerneldoc fixes When included into a Documentation/.../*.rst file, `make htmldocs` complains: ./include/linux/pm_runtime.h:359: WARNING: Bullet list ends without a blank line; unexpected unindent. [docutils] [... more ...] We should fix up the list format here to look nicer in HTML form, and avoid warnings. Adjust to a few other kerneldoc-isms (formatting, "Return:") while we're at it too. The result now passes 'make htmldocs' without warning, once these files are included in Documentation/.../*.rst. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260904141215.1.Ib31d6be7e93f6c02321d03c78ea410e02d7380fc@changeid Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 24 ++- include/linux/pm_runtime.h | 291 ++++++++++++++++++++--------------- 2 files changed, 183 insertions(+), 132 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 4d84d97f09c4e5..29e5ce69d04cf3 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -162,7 +162,7 @@ static void pm_runtime_cancel_pending(struct device *dev) dev->power.request = RPM_REQ_NONE; } -/* +/** * pm_runtime_autosuspend_expiration - Get a device's autosuspend-delay expiration time. * @dev: Device to handle. * @@ -200,7 +200,7 @@ static int dev_memalloc_noio(struct device *dev, void *data) return dev->power.memalloc_noio; } -/* +/** * pm_runtime_set_memalloc_noio - Set a device's memalloc_noio flag. * @dev: Device to handle. * @enable: True for setting the flag and False for clearing the flag. @@ -1044,6 +1044,11 @@ static enum hrtimer_restart pm_suspend_timer_fn(struct hrtimer *timer) * pm_schedule_suspend - Set up a timer to submit a suspend request in future. * @dev: Device to suspend. * @delay: Time to wait before submitting a suspend request, in milliseconds. + * + * Return: + * * %1: Success; @dev is already %RPM_SUSPENDED. + * * %0: Success. + * * Error code on failure. */ int pm_schedule_suspend(struct device *dev, unsigned int delay) { @@ -1106,7 +1111,7 @@ static int rpm_drop_usage_count(struct device *dev) * warning, increment it, and return an error). Then carry out an idle * notification, either synchronous or asynchronous. * - * This routine may be called in atomic context if the RPM_ASYNC flag is set, + * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. */ int __pm_runtime_idle(struct device *dev, int rpmflags) @@ -1144,7 +1149,7 @@ EXPORT_SYMBOL_GPL(__pm_runtime_idle); * warning, increment it, and return an error). Then carry out a suspend, * either synchronous or asynchronous. * - * This routine may be called in atomic context if the RPM_ASYNC flag is set, + * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. */ int __pm_runtime_suspend(struct device *dev, int rpmflags) @@ -1180,7 +1185,7 @@ EXPORT_SYMBOL_GPL(__pm_runtime_suspend); * If the RPM_GET_PUT flag is set, increment the device's usage count. Then * carry out a resume, either synchronous or asynchronous. * - * This routine may be called in atomic context if the RPM_ASYNC flag is set, + * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. */ int __pm_runtime_resume(struct device *dev, int rpmflags) @@ -1255,9 +1260,12 @@ static int pm_runtime_get_conditional(struct device *dev, bool ign_usage_count) * @dev: Target device. * * Increment the runtime PM usage counter of @dev if its runtime PM status is - * %RPM_ACTIVE, in which case it returns 1. If the device is in a different - * state, 0 is returned. -EINVAL is returned if runtime PM is disabled for the - * device, in which case also the usage_count will remain unmodified. + * already %RPM_ACTIVE + * + * Return: + * * %-EINVAL: Runtime PM is disabled for @dev. The usage counter is not incremented. + * * %1: Success; usage counter is incremented. + * * %0: @dev was not active. */ int pm_runtime_get_if_active(struct device *dev) { diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 64921b10ac74f4..ab6a19a85880ff 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -137,13 +137,14 @@ static inline void pm_runtime_put_noidle(struct device *dev) * pm_runtime_suspended - Check whether or not a device is runtime-suspended. * @dev: Target device. * - * Return %true if runtime PM is enabled for @dev and its runtime PM status is - * %RPM_SUSPENDED, or %false otherwise. - * * Note that the return value of this function can only be trusted if it is * called under the runtime PM lock of @dev or under conditions in which * runtime PM cannot be either disabled or enabled for @dev and its runtime PM * status cannot change. + * + * Return: + * * %true: @dev has runtime PM enabled and its status is %RPM_SUSPENDED. + * * %false: Otherwise. */ static inline bool pm_runtime_suspended(struct device *dev) { @@ -155,13 +156,14 @@ static inline bool pm_runtime_suspended(struct device *dev) * pm_runtime_active - Check whether or not a device is runtime-active. * @dev: Target device. * - * Return %true if runtime PM is disabled for @dev or its runtime PM status is - * %RPM_ACTIVE, or %false otherwise. - * * Note that the return value of this function can only be trusted if it is * called under the runtime PM lock of @dev or under conditions in which * runtime PM cannot be either disabled or enabled for @dev and its runtime PM * status cannot change. + * + * Return: + * * %true: Runtime PM is disabled for @dev or its status is %RPM_ACTIVE. + * * %false: Otherwise. */ static inline bool pm_runtime_active(struct device *dev) { @@ -173,12 +175,13 @@ static inline bool pm_runtime_active(struct device *dev) * pm_runtime_status_suspended - Check if runtime PM status is "suspended". * @dev: Target device. * - * Return %true if the runtime PM status of @dev is %RPM_SUSPENDED, or %false - * otherwise, regardless of whether or not runtime PM has been enabled for @dev. - * * Note that the return value of this function can only be trusted if it is * called under the runtime PM lock of @dev or under conditions in which the * runtime PM status of @dev cannot change. + * + * Return: + * * %true: Runtime PM status of @dev is %RPM_SUSPENDED. + * * %false: Otherwise. */ static inline bool pm_runtime_status_suspended(struct device *dev) { @@ -189,11 +192,13 @@ static inline bool pm_runtime_status_suspended(struct device *dev) * pm_runtime_enabled - Check if runtime PM is enabled. * @dev: Target device. * - * Return %true if runtime PM is enabled for @dev or %false otherwise. - * * Note that the return value of this function can only be trusted if it is * called under the runtime PM lock of @dev or under conditions in which * runtime PM cannot be either disabled or enabled for @dev. + * + * Return: + * * %true: Runtime PM is enabled for @dev. + * * %false: Otherwise. */ static inline bool pm_runtime_enabled(struct device *dev) { @@ -205,6 +210,10 @@ static inline bool pm_runtime_enabled(struct device *dev) * @dev: Target device. * * Do not call this function outside system suspend/resume code paths. + * + * Return: + * * %true: Runtime PM enabling is blocked for @dev. + * * %false: Otherwise. */ static inline bool pm_runtime_blocked(struct device *dev) { @@ -215,8 +224,9 @@ static inline bool pm_runtime_blocked(struct device *dev) * pm_runtime_has_no_callbacks - Check if runtime PM callbacks may be present. * @dev: Target device. * - * Return %true if @dev is a special device without runtime PM callbacks or - * %false otherwise. + * Return: + * * %true: @dev is marked as having no runtime PM callbacks. + * * %false: Otherwise. */ static inline bool pm_runtime_has_no_callbacks(struct device *dev) { @@ -239,9 +249,11 @@ static inline void pm_runtime_mark_last_busy(struct device *dev) * pm_runtime_is_irq_safe - Check if runtime PM can work in interrupt context. * @dev: Target device. * - * Return %true if @dev has been marked as an "IRQ-safe" device (with respect - * to runtime PM), in which case its runtime PM callabcks can be expected to - * work correctly when invoked from interrupt handlers. + * Return: + * * %true: @dev has been marked as an "IRQ-safe" device, in which case its + * runtime PM callbacks can be expected to work correctly from interrupt + * handlers. + * * %false: Otherwise. */ static inline bool pm_runtime_is_irq_safe(struct device *dev) { @@ -348,17 +360,17 @@ static inline int pm_runtime_force_resume(struct device *dev) { return -ENXIO; } * autosuspend has been enabled for it). * * Return: - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter non-zero, Runtime PM status change - * ongoing or device not in %RPM_ACTIVE state. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM idle and suspend callbacks. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter non-zero, Runtime PM status change + * ongoing or device not in %RPM_ACTIVE state. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM idle and suspend callbacks. */ static inline int pm_runtime_idle(struct device *dev) { @@ -370,17 +382,17 @@ static inline int pm_runtime_idle(struct device *dev) * @dev: Target device. * * Return: - * * 1: Success; device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change - * ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM suspend callbacks. + * * %1: Success; device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change + * ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM suspend callbacks. */ static inline int pm_runtime_suspend(struct device *dev) { @@ -397,17 +409,17 @@ static inline int pm_runtime_suspend(struct device *dev) * engaging its "idle check" callback. * * Return: - * * 1: Success; device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change - * ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM suspend callbacks. + * * %1: Success; device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change + * ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM suspend callbacks. */ static inline int pm_runtime_autosuspend(struct device *dev) { @@ -418,6 +430,11 @@ static inline int pm_runtime_autosuspend(struct device *dev) /** * pm_runtime_resume - Resume a device synchronously. * @dev: Target device. + * + * Return: + * * %1: Success; @dev is already %RPM_ACTIVE. + * * %0: Success. + * * Error code on failure. */ static inline int pm_runtime_resume(struct device *dev) { @@ -432,15 +449,15 @@ static inline int pm_runtime_resume(struct device *dev) * asynchronously. * * Return: - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter non-zero, Runtime PM status change - * ongoing or device not in %RPM_ACTIVE state. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter non-zero, Runtime PM status change + * ongoing or device not in %RPM_ACTIVE state. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. */ static inline int pm_request_idle(struct device *dev) { @@ -450,6 +467,11 @@ static inline int pm_request_idle(struct device *dev) /** * pm_request_resume - Queue up runtime-resume of a device. * @dev: Target device. + * + * Return: + * * %1: Success; @dev is already %RPM_ACTIVE. + * * %0: Success. + * * Error code on failure. */ static inline int pm_request_resume(struct device *dev) { @@ -465,16 +487,16 @@ static inline int pm_request_resume(struct device *dev) * equivalent pm_runtime_autosuspend() for @dev asynchronously. * * Return: - * * 1: Success; device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change - * ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. + * * %1: Success; device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter non-zero or Runtime PM status change + * ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. */ static inline int pm_request_autosuspend(struct device *dev) { @@ -488,6 +510,11 @@ static inline int pm_request_autosuspend(struct device *dev) * * Bump up the runtime PM usage counter of @dev and queue up a work item to * carry out runtime-resume of it. + * + * Return: + * * %1: Success; @dev is already %RPM_ACTIVE. + * * %0: Success; runtime-resume was queued. + * * Error code on failure. */ static inline int pm_runtime_get(struct device *dev) { @@ -507,6 +534,11 @@ static inline int pm_runtime_get(struct device *dev) * Consider using pm_runtime_resume_and_get() instead of it, especially * if its return value is checked by the caller, as this is likely to result * in cleaner code. + * + * Return: + * * %1: Success; @dev is already %RPM_ACTIVE. + * * %0: Success. + * * Error code on failure. */ static inline int pm_runtime_get_sync(struct device *dev) { @@ -531,8 +563,11 @@ static inline int pm_runtime_get_active(struct device *dev, int rpmflags) * @dev: Target device. * * Resume @dev synchronously and if that is successful, increment its runtime - * PM usage counter. Return 0 if the runtime PM usage counter of @dev has been - * incremented or a negative error code otherwise. + * PM usage counter. + * + * Return: + * * %0: Success; @dev is active and its usage counter has been incremented. + * * Negative error code on failure; usage counter is unchanged. */ static inline int pm_runtime_resume_and_get(struct device *dev) { @@ -559,16 +594,16 @@ static inline void pm_runtime_put(struct device *dev) * equal to 0, queue up a work item for @dev like in pm_request_autosuspend(). * * Return: - * * 1: Success. Usage counter dropped to zero, but device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status - * change ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. + * * %1: Success. Usage counter dropped to zero, but device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status + * change ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. */ static inline int __pm_runtime_put_autosuspend(struct device *dev) { @@ -585,16 +620,16 @@ static inline int __pm_runtime_put_autosuspend(struct device *dev) * in pm_request_autosuspend(). * * Return: - * * 1: Success. Usage counter dropped to zero, but device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status - * change ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. + * * %1: Success. Usage counter dropped to zero, but device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status + * change ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. */ static inline int pm_runtime_put_autosuspend(struct device *dev) { @@ -662,17 +697,17 @@ DEFINE_GUARD_COND(pm_runtime_active_auto, _try_enabled, * if it returns an error code. * * Return: - * * 1: Success. Usage counter dropped to zero, but device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status - * change ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM suspend callbacks. + * * %1: Success. Usage counter dropped to zero, but device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status + * change ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM suspend callbacks. */ static inline int pm_runtime_put_sync(struct device *dev) { @@ -690,17 +725,17 @@ static inline int pm_runtime_put_sync(struct device *dev) * if it returns an error code. * * Return: - * * 1: Success. Usage counter dropped to zero, but device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status - * change ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM suspend callbacks. + * * %1: Success. Usage counter dropped to zero, but device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status + * change ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM suspend callbacks. */ static inline int pm_runtime_put_sync_suspend(struct device *dev) { @@ -721,18 +756,18 @@ static inline int pm_runtime_put_sync_suspend(struct device *dev) * if it returns an error code. * * Return: - * * 1: Success. Usage counter dropped to zero, but device was already suspended. - * * 0: Success. - * * -EINVAL: Runtime PM error. - * * -EACCES: Runtime PM disabled. - * * -EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status - * change ongoing. - * * -EBUSY: Runtime PM child_count non-zero. - * * -EPERM: Device PM QoS resume latency 0. - * * -EINPROGRESS: Suspend already in progress. - * * -ENOSYS: CONFIG_PM not enabled. - * Other values and conditions for the above values are possible as returned by - * Runtime PM suspend callbacks. + * * %1: Success. Usage counter dropped to zero, but device was already suspended. + * * %0: Success. + * * %-EINVAL: Runtime PM error. + * * %-EACCES: Runtime PM disabled. + * * %-EAGAIN: Runtime PM usage counter became non-zero or Runtime PM status + * change ongoing. + * * %-EBUSY: Runtime PM child_count non-zero. + * * %-EPERM: Device PM QoS resume latency 0. + * * %-EINPROGRESS: Suspend already in progress. + * * %-ENOSYS: %CONFIG_PM not enabled. + * * Other values and conditions for the above values are possible as returned + * by Runtime PM suspend callbacks. */ static inline int pm_runtime_put_sync_autosuspend(struct device *dev) { @@ -748,6 +783,10 @@ static inline int pm_runtime_put_sync_autosuspend(struct device *dev) * of it will be taken into account. * * It is not valid to call this function for devices with runtime PM enabled. + * + * Return: + * * %0: Success. + * * Error code on failure. */ static inline int pm_runtime_set_active(struct device *dev) { @@ -762,6 +801,10 @@ static inline int pm_runtime_set_active(struct device *dev) * dependencies of it will be taken into account. * * It is not valid to call this function for devices with runtime PM enabled. + * + * Return: + * * %0: Success. + * * Error code on failure. */ static inline int pm_runtime_set_suspended(struct device *dev) { From 4711357dd298594beac51de081d9ba4326a189e3 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 4 Sep 2026 14:12:07 -0700 Subject: [PATCH 0513/2286] PM: runtime: Improve set_{status,active,suspended} docs The set_active()/set_suspended() docs don't mention that they also clear the 'runtime_error' field. This is a very important note, since that's one key purpose for using them. Fix a typo in __pm_runtime_set_status() while we're at it. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260904141215.2.If02a538531ea004987c9a28a8d030ee9f71221cd@changeid Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 4 ++-- include/linux/pm_runtime.h | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 29e5ce69d04cf3..3c874b056af537 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1296,7 +1296,7 @@ int pm_runtime_get_if_in_use(struct device *dev) EXPORT_SYMBOL_GPL(pm_runtime_get_if_in_use); /** - * __pm_runtime_set_status - Set runtime PM status of a device. + * __pm_runtime_set_status - Set runtime PM status of a device and clear errors. * @dev: Device to handle. * @status: New runtime PM status of the device. * @@ -1315,7 +1315,7 @@ EXPORT_SYMBOL_GPL(pm_runtime_get_if_in_use); * If @dev has any suppliers (as reflected by device links to them), and @status * is RPM_ACTIVE, they will be activated upfront and if the activation of one * of them fails, the status of @dev will be changed to RPM_SUSPENDED (instead - * of the @status value) and the suppliers will be deacticated on exit. The + * of the @status value) and the suppliers will be deactivated on exit. The * error returned by the failing supplier activation will be returned in that * case. */ diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index ab6a19a85880ff..1ffd9d5c3010da 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -776,13 +776,18 @@ static inline int pm_runtime_put_sync_autosuspend(struct device *dev) } /** - * pm_runtime_set_active - Set runtime PM status to "active". + * pm_runtime_set_active - Set runtime PM status to "active" and clear errors. * @dev: Target device. * - * Set the runtime PM status of @dev to %RPM_ACTIVE and ensure that dependencies - * of it will be taken into account. + * Set the runtime PM status of @dev to %RPM_ACTIVE and ensure that its + * dependencies will be taken into account. Also clear the device's error + * status (@dev->power.runtime_error). * - * It is not valid to call this function for devices with runtime PM enabled. + * It is only valid to call this function if runtime PM is disabled or if + * @dev->power.runtime_error is set. + * + * This will fail if suppliers cannot be resumed, or if the parent is not in + * the correct state. * * Return: * * %0: Success. @@ -794,13 +799,15 @@ static inline int pm_runtime_set_active(struct device *dev) } /** - * pm_runtime_set_suspended - Set runtime PM status to "suspended". + * pm_runtime_set_suspended - Set runtime PM status to "suspended" and clear errors. * @dev: Target device. * - * Set the runtime PM status of @dev to %RPM_SUSPENDED and ensure that - * dependencies of it will be taken into account. + * Set the runtime PM status of @dev to %RPM_SUSPENDED and ensure that its + * dependencies will be taken into account. Also clear the device's error + * status (@dev->power.runtime_error). * - * It is not valid to call this function for devices with runtime PM enabled. + * It is only valid to call this function if runtime PM is disabled or if + * @dev->power.runtime_error is set. * * Return: * * %0: Success. From 3baf1652fde8e54754df0b8bc9a6014c1752383d Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 4 Sep 2026 14:12:08 -0700 Subject: [PATCH 0514/2286] PM: runtime: kerneldoc wording improvements In preparation for removing duplicate documentation from Documentation/power/runtime_pm.rst, borrow some of the useful wording from runtime_pm.rst, and update other language for clarity, ease of reading, and completeness. Other guiding principles in this change: * Try to highlight "core", as in, "functions that are not for driver use but are exported because the real entrypoints are inline functions" * Rework pm_runtime_barrier() docs significantly. More below. * Include some clarifying cross-references and recommendations for pm_runtime_put_sync{,_suspend,_autosuspend}() * Attempt to deemphasize some of the implementation details (e.g., "asynchronous" instead of "queue") * Try for more clear user-facing language. For example, "set up autosuspend" isn't quite clear whether we're configuring autosuspend, or if we're initiating an attempt to autosuspend (i.e., setting a timer). pm_runtime_barrier(): currently, we speak a lot about implementation details and sequences of events, but obscure the key point that it treats "pending resume" and "pending suspend" very differently -- I try to improve that. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260904141215.3.I3bdec7dcda46e1bee6d4f91530518fbe9c9096fd@changeid Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 65 +++++++++++++----------- include/linux/pm_runtime.h | 95 +++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 73 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 3c874b056af537..bb008dfe85a181 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1102,14 +1102,13 @@ static int rpm_drop_usage_count(struct device *dev) } /** - * __pm_runtime_idle - Entry point for runtime idle operations. + * __pm_runtime_idle - Core entry point for runtime idle operations. * @dev: Device to send idle notification for. * @rpmflags: Flag bits. * - * If the RPM_GET_PUT flag is set, decrement the device's usage count and - * return immediately if it is larger than zero (if it becomes negative, log a - * warning, increment it, and return an error). Then carry out an idle - * notification, either synchronous or asynchronous. + * Carry out an idle check for @dev, either synchronous or asynchronous. + * If %RPM_GET_PUT is set in @rpmflags, decrement the device's usage count + * first, proceeding with idle notification only if the counter drops to zero. * * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. @@ -1140,14 +1139,13 @@ int __pm_runtime_idle(struct device *dev, int rpmflags) EXPORT_SYMBOL_GPL(__pm_runtime_idle); /** - * __pm_runtime_suspend - Entry point for runtime put/suspend operations. + * __pm_runtime_suspend - Core entry point for runtime put/suspend operations. * @dev: Device to suspend. * @rpmflags: Flag bits. * - * If the RPM_GET_PUT flag is set, decrement the device's usage count and - * return immediately if it is larger than zero (if it becomes negative, log a - * warning, increment it, and return an error). Then carry out a suspend, - * either synchronous or asynchronous. + * Carry out a suspend operation for @dev, either synchronous or asynchronous. + * If %RPM_GET_PUT is set in @rpmflags, decrement the device's usage count + * first, proceeding with suspend only if the counter drops to zero. * * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. @@ -1178,12 +1176,13 @@ int __pm_runtime_suspend(struct device *dev, int rpmflags) EXPORT_SYMBOL_GPL(__pm_runtime_suspend); /** - * __pm_runtime_resume - Entry point for runtime resume operations. + * __pm_runtime_resume - Core entry point for runtime resume operations. * @dev: Device to resume. * @rpmflags: Flag bits. * - * If the RPM_GET_PUT flag is set, increment the device's usage count. Then - * carry out a resume, either synchronous or asynchronous. + * Carry out a runtime resume operation for @dev, either synchronous or + * asynchronous. If %RPM_GET_PUT is set in @rpmflags, increment the device's + * usage count first, then bring the device to %RPM_ACTIVE state. * * This routine may be called in atomic context if the %RPM_ASYNC flag is set, * or if pm_runtime_irq_safe() has been called. @@ -1277,17 +1276,15 @@ EXPORT_SYMBOL_GPL(pm_runtime_get_if_active); * pm_runtime_get_if_in_use - Conditionally bump up runtime PM usage counter. * @dev: Target device. * - * Increment the runtime PM usage counter of @dev if its runtime PM status is - * %RPM_ACTIVE and its runtime PM usage counter is greater than 0 or it is not - * ignoring children and its active child count is nonzero. 1 is returned in - * this case. - * - * If @dev is in a different state or it is not in use (that is, its usage - * counter is 0, or it is ignoring children, or its active child count is 0), - * 0 is returned. + * Increment the runtime PM usage counter of @dev if it is "in use." A device + * is considered in use if its runtime PM status is %RPM_ACTIVE and its runtime + * PM usage counter is greater than 0, or if it is not ignoring children and + * its active child count is nonzero. * - * -EINVAL is returned if runtime PM is disabled for the device, in which case - * also the usage counter of @dev is not updated. + * Return: + * * %-EINVAL: Runtime PM is disabled for @dev. The usage counter is not incremented. + * * %1: Success; usage counter is incremented. + * * %0: @dev was not in use; usage counter is not incremented. */ int pm_runtime_get_if_in_use(struct device *dev) { @@ -1471,11 +1468,13 @@ static void __pm_runtime_barrier(struct device *dev) * pm_runtime_barrier - Flush pending requests and wait for completions. * @dev: Device to handle. * - * Prevent the device from being suspended by incrementing its usage counter and - * if there's a pending resume request for the device, wake the device up. - * Next, make sure that all pending requests for the device have been flushed - * from pm_wq and wait for all runtime PM operations involving the device in - * progress to complete. + * If the device has a pending resume request, resume it synchronously. For all + * other request types, cancel any queued request, and wait for running + * operations to complete. + * + * Note that this is intentionally asymmetric, as it guarantees any queued + * asynchronous resume request will complete, but it may cancel asynchronous + * suspend requests. */ void pm_runtime_barrier(struct device *dev) { @@ -1559,8 +1558,16 @@ void __pm_runtime_disable(struct device *dev, bool check_resume) EXPORT_SYMBOL_GPL(__pm_runtime_disable); /** - * pm_runtime_enable - Enable runtime PM of a device. + * pm_runtime_enable - Enable runtime PM for a device. * @dev: Device to handle. + * + * Enable runtime PM transitions for @dev by decrementing its disable counter. + * Once the counter reaches zero, the PM core is permitted to execute runtime + * PM callbacks for @dev as power conditions change. + * + * Callers should ensure that the device's runtime PM status accurately reflects + * its physical hardware state (via pm_runtime_set_active() or + * pm_runtime_set_suspended()) before enabling runtime PM. */ void pm_runtime_enable(struct device *dev) { diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h index 1ffd9d5c3010da..322e3b17f98713 100644 --- a/include/linux/pm_runtime.h +++ b/include/linux/pm_runtime.h @@ -352,11 +352,11 @@ static inline int pm_runtime_force_resume(struct device *dev) { return -ENXIO; } #endif /* CONFIG_PM_SLEEP */ /** - * pm_runtime_idle - Conditionally set up autosuspend of a device or suspend it. + * pm_runtime_idle - Conditionally initiate autosuspend of a device or suspend it. * @dev: Target device. * * Invoke the "idle check" callback of @dev and, depending on its return value, - * set up autosuspend of @dev or suspend it (depending on whether or not + * initiate autosuspend of @dev or suspend it (depending on whether or not * autosuspend has been enabled for it). * * Return: @@ -400,13 +400,13 @@ static inline int pm_runtime_suspend(struct device *dev) } /** - * pm_runtime_autosuspend - Update the last access time and set up autosuspend + * pm_runtime_autosuspend - Update the last access time and initiate autosuspend * of a device. * @dev: Target device. * - * First update the last access time, then set up autosuspend of @dev or suspend - * it (depending on whether or not autosuspend is enabled for it) without - * engaging its "idle check" callback. + * First update the last access time, then initiate autosuspend of @dev or + * suspend it (depending on whether or not autosuspend is enabled for it) + * without engaging its "idle check" callback. * * Return: * * %1: Success; device was already suspended. @@ -442,11 +442,11 @@ static inline int pm_runtime_resume(struct device *dev) } /** - * pm_request_idle - Queue up "idle check" execution for a device. + * pm_request_idle - Request an asynchronous idle check for a device. * @dev: Target device. * - * Queue up a work item to run an equivalent of pm_runtime_idle() for @dev - * asynchronously. + * Asynchronously request the PM core to evaluate whether @dev can be idled + * or suspended, invoking its ->runtime_idle() callback if provided. * * Return: * * %0: Success. @@ -465,9 +465,12 @@ static inline int pm_request_idle(struct device *dev) } /** - * pm_request_resume - Queue up runtime-resume of a device. + * pm_request_resume - Request an asynchronous runtime resume for a device. * @dev: Target device. * + * Asynchronously request the PM core to resume @dev to %RPM_ACTIVE state + * without modifying its usage counter. + * * Return: * * %1: Success; @dev is already %RPM_ACTIVE. * * %0: Success. @@ -479,12 +482,11 @@ static inline int pm_request_resume(struct device *dev) } /** - * pm_request_autosuspend - Update the last access time and queue up autosuspend - * of a device. + * pm_request_autosuspend - Update access time and request delayed suspension. * @dev: Target device. * - * Update the last access time of a device and queue up a work item to run an - * equivalent pm_runtime_autosuspend() for @dev asynchronously. + * Update the last access time of @dev and asynchronously request the PM core + * to suspend it after the autosuspend delay has elapsed. * * Return: * * %1: Success; device was already suspended. @@ -505,11 +507,11 @@ static inline int pm_request_autosuspend(struct device *dev) } /** - * pm_runtime_get - Bump up usage counter and queue up resume of a device. + * pm_runtime_get - Increment usage counter and request asynchronous resume. * @dev: Target device. * - * Bump up the runtime PM usage counter of @dev and queue up a work item to - * carry out runtime-resume of it. + * Increment the runtime PM usage counter of @dev and, if the device is + * currently suspended, asynchronously request the PM core to resume it. * * Return: * * %1: Success; @dev is already %RPM_ACTIVE. @@ -528,12 +530,10 @@ static inline int pm_runtime_get(struct device *dev) * Bump up the runtime PM usage counter of @dev and carry out runtime-resume of * it synchronously. * - * The possible return values of this function are the same as for - * pm_runtime_resume() and the runtime PM usage counter of @dev remains - * incremented in all cases, even if it returns an error code. - * Consider using pm_runtime_resume_and_get() instead of it, especially - * if its return value is checked by the caller, as this is likely to result - * in cleaner code. + * Note that the runtime PM usage counter of @dev remains incremented in all + * cases, even if it returns an error code. Consider using + * pm_runtime_resume_and_get() instead, especially if the return value is + * checked by the caller, as this is likely to result in cleaner code. * * Return: * * %1: Success; @dev is already %RPM_ACTIVE. @@ -575,11 +575,12 @@ static inline int pm_runtime_resume_and_get(struct device *dev) } /** - * pm_runtime_put - Drop device usage counter and queue up "idle check" if 0. + * pm_runtime_put - Drop device usage counter and request asynchronous idle check. * @dev: Target device. * - * Decrement the runtime PM usage counter of @dev and if it turns out to be - * equal to 0, queue up a work item for @dev like in pm_request_idle(). + * Decrement the runtime PM usage counter of @dev. If the counter reaches zero + * and the device has no active child dependencies, asynchronously request the + * PM core to idle or suspend the device. */ static inline void pm_runtime_put(struct device *dev) { @@ -611,13 +612,13 @@ static inline int __pm_runtime_put_autosuspend(struct device *dev) } /** - * pm_runtime_put_autosuspend - Update the last access time of a device, drop - * its usage counter and queue autosuspend if the usage counter becomes 0. + * pm_runtime_put_autosuspend - Update the last access time, drop usage counter + * and request autosuspend. * @dev: Target device. * - * Update the last access time of @dev, decrement runtime PM usage counter of - * @dev and if it turns out to be equal to 0, queue up a work item for @dev like - * in pm_request_autosuspend(). + * Update the last access time of @dev and decrement its runtime PM usage + * counter. If the counter drops to zero, asynchronously request the PM core to + * suspend the device once its autosuspend delay has elapsed. * * Return: * * %1: Success. Usage counter dropped to zero, but device was already suspended. @@ -688,10 +689,12 @@ DEFINE_GUARD_COND(pm_runtime_active_auto, _try_enabled, * pm_runtime_put_sync - Drop device usage counter and run "idle check" if 0. * @dev: Target device. * - * Decrement the runtime PM usage counter of @dev and if it turns out to be - * equal to 0, invoke the "idle check" callback of @dev and, depending on its - * return value, set up autosuspend of @dev or suspend it (depending on whether - * or not autosuspend has been enabled for it). + * Decrement the runtime PM usage counter of @dev. If the counter drops to zero, + * synchronously evaluate and trigger idle/suspend handling. + * + * Note that this does not update the last access time, but it does respect + * existing autosuspend timers. If @dev uses autosuspend, consider using + * pm_runtime_put_sync_autosuspend() or pm_runtime_put_sync_suspend() instead. * * The runtime PM usage counter of @dev remains decremented in all cases, even * if it returns an error code. @@ -718,8 +721,12 @@ static inline int pm_runtime_put_sync(struct device *dev) * pm_runtime_put_sync_suspend - Drop device usage counter and suspend if 0. * @dev: Target device. * - * Decrement the runtime PM usage counter of @dev and if it turns out to be - * equal to 0, carry out runtime-suspend of @dev synchronously. + * Decrement the runtime PM usage counter of @dev. If the counter drops to zero, + * suspend the device synchronously. + * + * This API differs from pm_runtime_put_sync() and + * pm_runtime_put_sync_autosuspend() in that it ignores any outstanding + * autosuspend delays. * * The runtime PM usage counter of @dev remains decremented in all cases, even * if it returns an error code. @@ -747,10 +754,11 @@ static inline int pm_runtime_put_sync_suspend(struct device *dev) * drop device usage counter and autosuspend if 0. * @dev: Target device. * - * Update the last access time of @dev, decrement the runtime PM usage counter - * of @dev and if it turns out to be equal to 0, set up autosuspend of @dev or - * suspend it synchronously (depending on whether or not autosuspend has been - * enabled for it). + * Update the last access time of @dev and decrement its runtime PM usage + * counter. If the counter drops to zero, synchronously suspend the device (or + * schedule autosuspend if the delay has not elapsed). + * + * Prefer this API over pm_runtime_put_sync() for devices that use autosuspend. * * The runtime PM usage counter of @dev remains decremented in all cases, even * if it returns an error code. @@ -827,9 +835,8 @@ static inline int pm_runtime_set_suspended(struct device *dev) * * If the counter is zero when this function runs and there is a pending runtime * resume request for @dev, it will be resumed. If the counter is still zero at - * that point, all of the pending runtime PM requests for @dev will be canceled - * and all runtime PM operations in progress involving it will be waited for to - * complete. + * that point, this function cancels all pending runtime PM requests for @dev + * and waits for its runtime PM operations to complete (if any). * * For each invocation of this function for @dev, there must be a matching * pm_runtime_enable() call, so that runtime PM is eventually enabled for it From 3d6174fe309012184226eb159e26b478efcd8291 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 4 Sep 2026 14:12:09 -0700 Subject: [PATCH 0515/2286] PM: runtime: Pull API docs from kerneldoc This avoids staleness and duplication, as the same APIs were previously documented twice. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260904141215.4.I2cb2c43192ef46bd9dfad6699080d8a1d56756d5@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 280 +---------------------------- 1 file changed, 6 insertions(+), 274 deletions(-) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index a53ab09c37d53a..380dad7590a924 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -307,219 +307,10 @@ All of the above fields are members of the 'power' member of 'struct device'. The following runtime PM helper functions are defined in drivers/base/power/runtime.c and include/linux/pm_runtime.h: - `void pm_runtime_init(struct device *dev);` - - initialize the device runtime PM fields in 'struct dev_pm_info' - - `void pm_runtime_remove(struct device *dev);` - - make sure that the runtime PM of the device will be disabled after - removing the device from device hierarchy - - `int pm_runtime_idle(struct device *dev);` - - execute the subsystem-level idle callback for the device; returns an - error code on failure, where -EINPROGRESS means that ->runtime_idle() is - already being executed; if there is no callback or the callback returns 0 - then run pm_runtime_autosuspend(dev) and return its result - - `int pm_runtime_suspend(struct device *dev);` - - execute the subsystem-level suspend callback for the device; returns 0 on - success, 1 if the device's runtime PM status was already 'suspended', or - error code on failure, where -EAGAIN or -EBUSY means it is safe to attempt - to suspend the device again in future and -EACCES means that - 'power.disable_depth' is different from 0 - - `int pm_runtime_autosuspend(struct device *dev);` - - same as pm_runtime_suspend() except that a call to - pm_runtime_mark_last_busy() is made and an autosuspend is scheduled for - the appropriate time and 0 is returned - - `int pm_runtime_resume(struct device *dev);` - - execute the subsystem-level resume callback for the device; returns 0 on - success, 1 if the device's runtime PM status is already 'active' (also if - 'power.disable_depth' is nonzero, but the status was 'active' when it was - changing from 0 to 1) or error code on failure, where -EAGAIN means it may - be safe to attempt to resume the device again in future, but - 'power.runtime_error' should be checked additionally, and -EACCES means - that the callback could not be run, because 'power.disable_depth' was - different from 0 - - `int pm_runtime_resume_and_get(struct device *dev);` - - run pm_runtime_resume(dev) and if successful, increment the device's - usage counter; returns 0 on success (whether or not the device's - runtime PM status was already 'active') or the error code from - pm_runtime_resume() on failure. - - `int pm_request_idle(struct device *dev);` - - submit a request to execute the subsystem-level idle callback for the - device (the request is represented by a work item in pm_wq); returns 0 on - success or error code if the request has not been queued up - - `int pm_request_autosuspend(struct device *dev);` - - Call pm_runtime_mark_last_busy() and schedule the execution of the - subsystem-level suspend callback for the device when the autosuspend delay - expires - - `int pm_schedule_suspend(struct device *dev, unsigned int delay);` - - schedule the execution of the subsystem-level suspend callback for the - device in future, where 'delay' is the time to wait before queuing up a - suspend work item in pm_wq, in milliseconds (if 'delay' is zero, the work - item is queued up immediately); returns 0 on success, 1 if the device's PM - runtime status was already 'suspended', or error code if the request - hasn't been scheduled (or queued up if 'delay' is 0); if the execution of - ->runtime_suspend() is already scheduled and not yet expired, the new - value of 'delay' will be used as the time to wait - - `int pm_request_resume(struct device *dev);` - - submit a request to execute the subsystem-level resume callback for the - device (the request is represented by a work item in pm_wq); returns 0 on - success, 1 if the device's runtime PM status was already 'active', or - error code if the request hasn't been queued up - - `void pm_runtime_get_noresume(struct device *dev);` - - increment the device's usage counter - - `int pm_runtime_get(struct device *dev);` - - increment the device's usage counter, run pm_request_resume(dev) and - return its result - - `int pm_runtime_get_sync(struct device *dev);` - - increment the device's usage counter, run pm_runtime_resume(dev) and - return its result; - note that it does not drop the device's usage counter on errors, so - consider using pm_runtime_resume_and_get() instead of it, especially - if its return value is checked by the caller, as this is likely to - result in cleaner code. - - `int pm_runtime_get_if_in_use(struct device *dev);` - - return -EINVAL if 'power.disable_depth' is nonzero; otherwise, if the - runtime PM status is RPM_ACTIVE and the runtime PM usage counter is - nonzero, increment the counter and return 1; otherwise return 0 without - changing the counter - - `int pm_runtime_get_if_active(struct device *dev);` - - return -EINVAL if 'power.disable_depth' is nonzero; otherwise, if the - runtime PM status is RPM_ACTIVE, increment the counter and - return 1; otherwise return 0 without changing the counter - - `void pm_runtime_put_noidle(struct device *dev);` - - decrement the device's usage counter - - `int pm_runtime_put(struct device *dev);` - - decrement the device's usage counter; if the result is 0 then run - pm_request_idle(dev) and return its result - - `int pm_runtime_put_autosuspend(struct device *dev);` - - set the power.last_busy field to the current time and decrement the - device's usage counter; if the result is 0 then run - pm_request_autosuspend(dev) and return its result - - `int __pm_runtime_put_autosuspend(struct device *dev);` - - decrement the device's usage counter; if the result is 0 then run - pm_request_autosuspend(dev) and return its result - - `int pm_runtime_put_sync(struct device *dev);` - - decrement the device's usage counter; if the result is 0 then run - pm_runtime_idle(dev) and return its result - - `int pm_runtime_put_sync_suspend(struct device *dev);` - - decrement the device's usage counter; if the result is 0 then run - pm_runtime_suspend(dev) and return its result - - `int pm_runtime_put_sync_autosuspend(struct device *dev);` - - set the power.last_busy field to the current time and decrement the - device's usage counter; if the result is 0 then run - pm_runtime_autosuspend(dev) and return its result - - `void pm_runtime_enable(struct device *dev);` - - decrement the device's 'power.disable_depth' field; if that field is equal - to zero, the runtime PM helper functions can execute subsystem-level - callbacks described in Section 2 for the device - - `int pm_runtime_disable(struct device *dev);` - - increment the device's 'power.disable_depth' field (if the value of that - field was previously zero, this prevents subsystem-level runtime PM - callbacks from being run for the device), make sure that all of the - pending runtime PM operations on the device are either completed or - canceled; returns 1 if there was a resume request pending and it was - necessary to execute the subsystem-level resume callback for the device - to satisfy that request, otherwise 0 is returned - - `void pm_runtime_barrier(struct device *dev);` - - check if there's a resume request pending for the device and resume it - (synchronously) in that case, cancel any other pending runtime PM requests - regarding it and wait for all runtime PM operations on it in progress to - complete - - `void pm_suspend_ignore_children(struct device *dev, bool enable);` - - set/unset the power.ignore_children flag of the device - - `int pm_runtime_set_active(struct device *dev);` - - clear the device's 'power.runtime_error' flag, set the device's runtime - PM status to 'active' and update its parent's counter of 'active' - children as appropriate (it is only valid to use this function if - 'power.runtime_error' is set or 'power.disable_depth' is greater than - zero); it will fail and return error code if the device has a parent - which is not active and the 'power.ignore_children' flag of which is unset - - `void pm_runtime_set_suspended(struct device *dev);` - - clear the device's 'power.runtime_error' flag, set the device's runtime - PM status to 'suspended' and update its parent's counter of 'active' - children as appropriate (it is only valid to use this function if - 'power.runtime_error' is set or 'power.disable_depth' is greater than - zero) - - `bool pm_runtime_active(struct device *dev);` - - return true if the device's runtime PM status is 'active' or its - 'power.disable_depth' field is not equal to zero, or false otherwise - - `bool pm_runtime_suspended(struct device *dev);` - - return true if the device's runtime PM status is 'suspended' and its - 'power.disable_depth' field is equal to zero, or false otherwise - - `bool pm_runtime_status_suspended(struct device *dev);` - - return true if the device's runtime PM status is 'suspended' - - `void pm_runtime_no_callbacks(struct device *dev);` - - set the power.no_callbacks flag for the device and remove the runtime - PM attributes from /sys/devices/.../power (or prevent them from being - added when the device is registered) - - `void pm_runtime_irq_safe(struct device *dev);` - - set the power.irq_safe flag for the device, causing the runtime-PM - callbacks to be invoked with interrupts off - - `bool pm_runtime_is_irq_safe(struct device *dev);` - - return true if power.irq_safe flag was set for the device, causing - the runtime-PM callbacks to be invoked with interrupts off - - `void pm_runtime_mark_last_busy(struct device *dev);` - - set the power.last_busy field to the current time - - `void pm_runtime_use_autosuspend(struct device *dev);` - - set the power.use_autosuspend flag, enabling autosuspend delays; call - pm_runtime_get_sync if the flag was previously cleared and - power.autosuspend_delay is negative - - `void pm_runtime_dont_use_autosuspend(struct device *dev);` - - clear the power.use_autosuspend flag, disabling autosuspend delays; - decrement the device's usage counter if the flag was previously set and - power.autosuspend_delay is negative; call pm_runtime_idle - - `void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);` - - set the power.autosuspend_delay value to 'delay' (expressed in - milliseconds); if 'delay' is negative then runtime suspends are - prevented; if power.use_autosuspend is set, pm_runtime_get_sync may be - called or the device's usage counter may be decremented and - pm_runtime_idle called depending on if power.autosuspend_delay is - changed to or from a negative value; if power.use_autosuspend is clear, - pm_runtime_idle is called - - `unsigned long pm_runtime_autosuspend_expiration(struct device *dev);` - - calculate the time when the current autosuspend delay period will expire, - based on power.last_busy and power.autosuspend_delay; if the delay time - is 1000 ms or larger then the expiration time is rounded up to the - nearest second; returns 0 if the delay period has already expired or - power.use_autosuspend isn't set, otherwise returns the expiration time - in jiffies +.. kernel-doc:: drivers/base/power/runtime.c + :export: + +.. kernel-doc:: include/linux/pm_runtime.h It is safe to execute the following helper functions from interrupt context: @@ -728,67 +519,8 @@ Subsystems may wish to conserve code space by using the set of generic power management callbacks provided by the PM core, defined in driver/base/power/generic_ops.c: - `int pm_generic_runtime_suspend(struct device *dev);` - - invoke the ->runtime_suspend() callback provided by the driver of this - device and return its result, or return 0 if not defined - - `int pm_generic_runtime_resume(struct device *dev);` - - invoke the ->runtime_resume() callback provided by the driver of this - device and return its result, or return 0 if not defined - - `int pm_generic_suspend(struct device *dev);` - - if the device has not been suspended at run time, invoke the ->suspend() - callback provided by its driver and return its result, or return 0 if not - defined - - `int pm_generic_suspend_noirq(struct device *dev);` - - if pm_runtime_suspended(dev) returns "false", invoke the ->suspend_noirq() - callback provided by the device's driver and return its result, or return - 0 if not defined - - `int pm_generic_resume(struct device *dev);` - - invoke the ->resume() callback provided by the driver of this device and, - if successful, change the device's runtime PM status to 'active' - - `int pm_generic_resume_noirq(struct device *dev);` - - invoke the ->resume_noirq() callback provided by the driver of this device - - `int pm_generic_freeze(struct device *dev);` - - if the device has not been suspended at run time, invoke the ->freeze() - callback provided by its driver and return its result, or return 0 if not - defined - - `int pm_generic_freeze_noirq(struct device *dev);` - - if pm_runtime_suspended(dev) returns "false", invoke the ->freeze_noirq() - callback provided by the device's driver and return its result, or return - 0 if not defined - - `int pm_generic_thaw(struct device *dev);` - - if the device has not been suspended at run time, invoke the ->thaw() - callback provided by its driver and return its result, or return 0 if not - defined - - `int pm_generic_thaw_noirq(struct device *dev);` - - if pm_runtime_suspended(dev) returns "false", invoke the ->thaw_noirq() - callback provided by the device's driver and return its result, or return - 0 if not defined - - `int pm_generic_poweroff(struct device *dev);` - - if the device has not been suspended at run time, invoke the ->poweroff() - callback provided by its driver and return its result, or return 0 if not - defined - - `int pm_generic_poweroff_noirq(struct device *dev);` - - if pm_runtime_suspended(dev) returns "false", run the ->poweroff_noirq() - callback provided by the device's driver and return its result, or return - 0 if not defined - - `int pm_generic_restore(struct device *dev);` - - invoke the ->restore() callback provided by the driver of this device and, - if successful, change the device's runtime PM status to 'active' - - `int pm_generic_restore_noirq(struct device *dev);` - - invoke the ->restore_noirq() callback provided by the device's driver +.. kernel-doc:: drivers/base/power/generic_ops.c + :export: These functions are the defaults used by the PM core if a subsystem doesn't provide its own callbacks for ->runtime_idle(), ->runtime_suspend(), From 6881d8de92c70783fdc934024edeaa48cf6d8baa Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Fri, 4 Sep 2026 14:12:10 -0700 Subject: [PATCH 0516/2286] PM: core: Document struct dev_pm_info with kerneldoc Documentation/power/runtime_pm.rst includes several descriptions of dev_pm_info fields, but many of them are wrong these days, as the types or behaviors have changed. This is a prime reason for keeping docs closer to the code where possible. Adapt and rewrite some of these descriptions, and add them to include/linux/pm.h directly. Then pull these docs into the generated HTML. Tested with `make htmldocs`. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260904141215.5.Ib0272869f8b1178426106d911441c449ac299048@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 101 ++--------------------------- include/linux/pm.h | 93 ++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 96 deletions(-) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 380dad7590a924..39fdeeda7a1e92 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -203,103 +203,12 @@ rules: 3. Runtime PM Device Fields =========================== -The following device runtime PM fields are present in 'struct dev_pm_info', as -defined in include/linux/pm.h: +Device PM fields are found in 'struct dev_pm_info', as defined in +include/linux/pm.h. Many of those fields track runtime PM configuration and +state. - `struct timer_list suspend_timer;` - - timer used for scheduling (delayed) suspend and autosuspend requests - - `unsigned long timer_expires;` - - timer expiration time, in jiffies (if this is different from zero, the - timer is running and will expire at that time, otherwise the timer is not - running) - - `struct work_struct work;` - - work structure used for queuing up requests (i.e. work items in pm_wq) - - `wait_queue_head_t wait_queue;` - - wait queue used if any of the helper functions needs to wait for another - one to complete - - `spinlock_t lock;` - - lock used for synchronization - - `atomic_t usage_count;` - - the usage counter of the device - - `atomic_t child_count;` - - the count of 'active' children of the device - - `unsigned int ignore_children;` - - if set, the value of child_count is ignored (but still updated) - - `unsigned int disable_depth;` - - used for disabling the helper functions (they work normally if this is - equal to zero); the initial value of it is 1 (i.e. runtime PM is - initially disabled for all devices) - - `int runtime_error;` - - if set, there was a fatal error (one of the callbacks returned error code - as described in Section 2), so the helper functions will not work until - this flag is cleared; this is the error code returned by the failing - callback - - `unsigned int idle_notification;` - - if set, ->runtime_idle() is being executed - - `unsigned int request_pending;` - - if set, there's a pending request (i.e. a work item queued up into pm_wq) - - `enum rpm_request request;` - - type of request that's pending (valid if request_pending is set) - - `unsigned int deferred_resume;` - - set if ->runtime_resume() is about to be run while ->runtime_suspend() is - being executed for that device and it is not practical to wait for the - suspend to complete; means "start a resume as soon as you've suspended" - - `enum rpm_status runtime_status;` - - the runtime PM status of the device; this field's initial value is - RPM_SUSPENDED, which means that each device is initially regarded by the - PM core as 'suspended', regardless of its real hardware status - - `enum rpm_status last_status;` - - the last runtime PM status of the device captured before disabling runtime - PM for it (invalid initially and when disable_depth is 0) - - `unsigned int runtime_auto;` - - if set, indicates that the user space has allowed the device driver to - power manage the device at run time via the /sys/devices/.../power/control - `interface;` it may only be modified with the help of the - pm_runtime_allow() and pm_runtime_forbid() helper functions - - `unsigned int no_callbacks;` - - indicates that the device does not use the runtime PM callbacks (see - Section 8); it may be modified only by the pm_runtime_no_callbacks() - helper function - - `unsigned int irq_safe;` - - indicates that the ->runtime_suspend() and ->runtime_resume() callbacks - will be invoked with the spinlock held and interrupts disabled - - `unsigned int use_autosuspend;` - - indicates that the device's driver supports delayed autosuspend (see - Section 9); it may be modified only by the - pm_runtime{_dont}_use_autosuspend() helper functions - - `unsigned int timer_autosuspends;` - - indicates that the PM core should attempt to carry out an autosuspend - when the timer expires rather than a normal suspend - - `int autosuspend_delay;` - - the delay time (in milliseconds) to be used for autosuspend - - `unsigned long last_busy;` - - the time (in jiffies) when the pm_runtime_mark_last_busy() helper - function was last called for this device; used in calculating inactivity - periods for autosuspend - -All of the above fields are members of the 'power' member of 'struct device'. +.. kernel-doc:: include/linux/pm.h + :identifiers: dev_pm_info 4. Runtime PM Device Helper Functions ===================================== diff --git a/include/linux/pm.h b/include/linux/pm.h index afcaaa37a81266..ef3f1310e749c3 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -663,6 +663,99 @@ struct pm_subsys_data { #define DPM_FLAG_SMART_SUSPEND BIT(2) #define DPM_FLAG_MAY_SKIP_RESUME BIT(3) +/** + * struct dev_pm_info - Device power management information. + * + * @power_state: Legacy power state (mostly unused in modern kernels). + * @can_wakeup: Device is capable of generating wakeup signals. + * @async_suspend: Device can be suspended and resumed asynchronously. + * @in_dpm_list: Device is on the dpm_list. + * @is_prepared: Device's ->prepare() callback has run successfully. + * @is_suspended: Device is suspended during a system sleep transition. + * @is_noirq_suspended: Device's noirq suspend callback has run successfully. + * @is_late_suspended: Device's late suspend callback has run successfully. + * @no_pm: Device does not participate in power management transitions. + * @early_init: Device was initialized before standard PM initialization. + * @direct_complete: Device can skip suspend/resume callbacks and remain + * runtime-suspended during system sleep. + * @driver_flags: Driver flags (e.g. %DPM_FLAG_SMART_SUSPEND) set at probe time. + * @lock: Spinlock used for synchronizing PM state transitions and runtime PM + * operations. + * @entry: List head for device power management lists. + * @completion: Completion for synchronization during asynchronous system + * suspend/resume. + * @wakeup: Wakeup source object associated with the device. + * @work_in_progress: Asynchronous PM operation in progress. + * @wakeup_path: Device is in the wakeup path or can wake the system up. + * @syscore: Device participates in syscore power management operations. + * @no_pm_callbacks: Device has no PM callbacks; handled by parent or subsystem. + * @smart_suspend: Driver requested smart-suspend behavior. + * @must_resume: Device must be resumed during system resume. + * @may_skip_resume: Set by subsystems to indicate driver resume callbacks may + * be skipped. + * @out_band_wakeup: Out-of-band wakeup is supported. + * @strict_midlayer: Middle layer code does not want callbacks invoked via + * pm_runtime_force_suspend() / pm_runtime_force_resume(). + * @should_wakeup: Wakeup flag when system sleep is not enabled. + * @suspend_timer: High-resolution timer used for scheduling delayed runtime + * suspend and autosuspend requests. + * @timer_expires: Timer expiration time in nanoseconds monotonic time + * (runtime PM). + * @work: Work structure used for queuing up requests into pm_wq (runtime PM). + * @wait_queue: Wait queue used if any helper functions need to wait for another + * state change to complete (runtime PM). + * @wakeirq: Dedicated wakeup interrupt for the device. + * @usage_count: Device runtime PM usage counter. + * @child_count: Count of active children of the device (runtime PM). + * @disable_depth: Disable counter for runtime PM (runtime PM is enabled when + * this is 0; initial value is 1). + * @idle_notification: Set if ->runtime_idle() is being executed. + * @request_pending: Set if a work item is queued into pm_wq (runtime PM). + * @deferred_resume: Set if ->runtime_resume() should run as soon as + * ->runtime_suspend() completes. + * @needs_force_resume: Indicates the device was forced into suspend by + * pm_runtime_force_suspend() and must be resumed by + * pm_runtime_force_resume(). + * @runtime_auto: User space has allowed the driver to power manage the device + * at runtime via sysfs control attribute; also can be set by + * pm_runtime_allow() or pm_runtime_forbid(). + * @ignore_children: If set, the value of child_count is ignored for runtime + * suspend and idle decisions. + * @no_callbacks: Indicates the device does not use runtime PM callbacks. + * @irq_safe: Indicates runtime PM callbacks will be invoked with the spinlock + * held and interrupts disabled. + * @use_autosuspend: Indicates the device driver supports delayed runtime + * autosuspend. + * @timer_autosuspends: Indicates the runtime PM core should attempt an + * autosuspend rather than a normal suspend when the timer expires. + * @memalloc_noio: Indicates memory allocation during runtime PM transitions + * must avoid I/O (GFP_NOIO). + * @links_count: Number of device links that require runtime PM coordination. + * @request: Type of pending runtime PM request (valid if request_pending is + * set). + * @runtime_status: Runtime PM status of the device. + * @last_status: Last status captured before disabling runtime PM, or + * %RPM_BLOCKED / %RPM_INVALID. + * @runtime_error: Fatal error code returned by a failing callback, blocking + * helpers until cleared. + * @autosuspend_delay: Delay time in milliseconds to be used for runtime + * autosuspend. + * @last_busy: Timestamp in nanoseconds when pm_runtime_mark_last_busy() was + * last called. Used in calculating inactivity periods for autosuspend. + * @active_time: Accumulated time in nanoseconds spent in %RPM_ACTIVE state. + * @suspended_time: Accumulated time in nanoseconds spent in %RPM_SUSPENDED + * state. + * @accounting_timestamp: Timestamp in nanoseconds of the last runtime PM state + * accounting update. + * @subsys_data: Subsystem-specific power management data. + * @set_latency_tolerance: Callback for setting latency tolerance. + * @qos: Per-device PM Quality of Service (QoS) constraints. + * @detach_power_off: Indicates device should be detached from PM domain on + * power off. + * + * Device power management information stored in the "power" member of struct + * device. + */ struct dev_pm_info { pm_message_t power_state; bool can_wakeup:1; From cb6702e63e0a16afd83875294e5ce53becdd1b1f Mon Sep 17 00:00:00 2001 From: Jiaming Zhang Date: Wed, 2 Sep 2026 19:21:21 +0800 Subject: [PATCH 0517/2286] nilfs2: force clear dirty state when restoring from the shadow map When garbage collection fails, nilfs2 rolls the DAT metadata file's page cache back to a shadow copy taken before GC started. nilfs_clear_dirty_pages() drops the dirty state of the folios in the DAT cache, then nilfs_copy_back_pages() overwrites them with the saved contents and warns if one is still dirty: /* overwrite existing folio in the destination cache */ WARN_ON(folio_test_dirty(dfolio)); nilfs_clear_dirty_pages() used to clear the dirty state unconditionally, which is safe here because the rollback runs with the log writer stopped, so nothing else can dirty the cache while it runs. The same helper is also used when writeback finds dirty folios after the filesystem has degraded to read-only, where it does run concurrently with the log writer, so commit ca76bb226bf4 ("nilfs2: do not force clear folio if buffer is referenced") made nilfs_clear_folio_dirty() skip a folio if any of its buffer heads is busy. The rollback caller shares that helper, so its clearing step can now return with a folio still dirty. A DAT folio can hold a busy buffer head without anyone modifying the folio: nilfs_mdt_read_block() submits read-ahead for the blocks following the one it was asked for and waits only for the first, so the read-ahead buffers are still locked when it returns. With a block size smaller than the page size, a locked read-ahead buffer can share a folio with a block that GC dirtied and keep the whole folio dirty past the clearing step. Add a force flag to nilfs_clear_dirty_pages() and nilfs_clear_folio_dirty() that skips the busy buffer check, and set it in the two calls from nilfs_mdt_restore_from_shadow_map(). The read-only fallback callers keep passing false, so that commit still protects them and the WARN_ON() is left alone. Fixes: ca76bb226bf4 ("nilfs2: do not force clear folio if buffer is referenced") Closes: https://lore.kernel.org/lkml/CANypQFZSYrtcshnUzOPiqatyLd-M8_OReOewQoAi_V5yY0dTtg@mail.gmail.com/ Cc: stable@vger.kernel.org Suggested-by: Ryusuke Konishi Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Jiaming Zhang Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/inode.c | 2 +- fs/nilfs2/mdt.c | 6 +++--- fs/nilfs2/page.c | 50 +++++++++++++++++++++++++++-------------------- fs/nilfs2/page.h | 4 ++-- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 778274203f4ca1..cc2f67eca0562a 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -163,7 +163,7 @@ static int nilfs_writepages(struct address_space *mapping, int err = 0; if (sb_rdonly(inode->i_sb)) { - nilfs_clear_dirty_pages(mapping); + nilfs_clear_dirty_pages(mapping, false); return -EROFS; } diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c index 2a435349fd219d..b50c88b6518399 100644 --- a/fs/nilfs2/mdt.c +++ b/fs/nilfs2/mdt.c @@ -405,7 +405,7 @@ static int nilfs_mdt_write_folio(struct folio *folio, * have dirty folios that try to be flushed in background. * So, here we simply discard this dirty folio. */ - nilfs_clear_folio_dirty(folio); + nilfs_clear_folio_dirty(folio, false); folio_unlock(folio); return -EROFS; } @@ -648,10 +648,10 @@ void nilfs_mdt_restore_from_shadow_map(struct inode *inode) if (mi->mi_palloc_cache) nilfs_palloc_clear_cache(inode); - nilfs_clear_dirty_pages(inode->i_mapping); + nilfs_clear_dirty_pages(inode->i_mapping, true); nilfs_copy_back_pages(inode->i_mapping, shadow->inode->i_mapping); - nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping); + nilfs_clear_dirty_pages(ii->i_assoc_inode->i_mapping, true); nilfs_copy_back_pages(ii->i_assoc_inode->i_mapping, NILFS_I(shadow->inode)->i_assoc_inode->i_mapping); diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c index cf4f1c6798f5e5..857926da9f21d8 100644 --- a/fs/nilfs2/page.c +++ b/fs/nilfs2/page.c @@ -369,8 +369,9 @@ void nilfs_copy_back_pages(struct address_space *dmap, /** * nilfs_clear_dirty_pages - discard dirty pages in address space * @mapping: address space with dirty pages for discarding + * @force: whether to clear the dirty state regardless of busy buffer heads */ -void nilfs_clear_dirty_pages(struct address_space *mapping) +void nilfs_clear_dirty_pages(struct address_space *mapping, bool force) { struct folio_batch fbatch; unsigned int i; @@ -391,7 +392,7 @@ void nilfs_clear_dirty_pages(struct address_space *mapping) * was acquired. Skip processing in that case. */ if (likely(folio->mapping == mapping)) - nilfs_clear_folio_dirty(folio); + nilfs_clear_folio_dirty(folio, force); folio_unlock(folio); } @@ -403,13 +404,16 @@ void nilfs_clear_dirty_pages(struct address_space *mapping) /** * nilfs_clear_folio_dirty - discard dirty folio * @folio: dirty folio that will be discarded + * @force: whether to clear the states regardless of busy buffer heads * * nilfs_clear_folio_dirty() clears working states including dirty state for - * the folio and its buffers. If the folio has buffers, clear only if it is - * confirmed that none of the buffer heads are busy (none have valid - * references and none are locked). + * the folio and its buffers. If the folio has buffers and force is false, + * clear only if it is confirmed that none of the buffer heads are busy (none + * have valid references and none are locked). If force is true, the states + * are cleared unconditionally, the caller should guarantee that the folio is + * not being modified concurrently. */ -void nilfs_clear_folio_dirty(struct folio *folio) +void nilfs_clear_folio_dirty(struct folio *folio, bool force) { struct buffer_head *bh, *head; @@ -422,24 +426,28 @@ void nilfs_clear_folio_dirty(struct folio *folio) BIT(BH_Async_Write) | BIT(BH_NILFS_Volatile) | BIT(BH_NILFS_Checked) | BIT(BH_NILFS_Redirected) | BIT(BH_Delay)); - bool busy, invalidated = false; + + if (!force) { + bool busy, invalidated = false; recheck_buffers: - busy = false; - bh = head; - do { - if (atomic_read(&bh->b_count) | buffer_locked(bh)) { - busy = true; - break; + busy = false; + bh = head; + do { + if (atomic_read(&bh->b_count) | + buffer_locked(bh)) { + busy = true; + break; + } + } while (bh = bh->b_this_page, bh != head); + + if (busy) { + if (invalidated) + return; + invalidate_bh_lrus(); + invalidated = true; + goto recheck_buffers; } - } while (bh = bh->b_this_page, bh != head); - - if (busy) { - if (invalidated) - return; - invalidate_bh_lrus(); - invalidated = true; - goto recheck_buffers; } bh = head; diff --git a/fs/nilfs2/page.h b/fs/nilfs2/page.h index 136cd1c143c9c1..c3ba3468af5cbe 100644 --- a/fs/nilfs2/page.h +++ b/fs/nilfs2/page.h @@ -41,8 +41,8 @@ void nilfs_folio_bug(struct folio *); int nilfs_copy_dirty_pages(struct address_space *, struct address_space *); void nilfs_copy_back_pages(struct address_space *, struct address_space *); -void nilfs_clear_folio_dirty(struct folio *folio); -void nilfs_clear_dirty_pages(struct address_space *mapping); +void nilfs_clear_folio_dirty(struct folio *folio, bool force); +void nilfs_clear_dirty_pages(struct address_space *mapping, bool force); unsigned int nilfs_page_count_clean_buffers(struct folio *folio, unsigned int from, unsigned int to); unsigned long nilfs_find_uncommitted_extent(struct inode *inode, From b3eb16109ae108c3464b0f5f8850ca4b6085561a Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 5 Aug 2026 21:21:29 -0700 Subject: [PATCH 0518/2286] doc: Update stallwarn.rst based on RCU Tasks Trace as SRCU Now that RCU Tasks Trace has been re-implemented in terms of SRCU, update stallwarn.rst to remove obsolete verbiage about RCU Tasks Trace stall warnings. Signed-off-by: Paul E. McKenney --- Documentation/RCU/stallwarn.rst | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Documentation/RCU/stallwarn.rst b/Documentation/RCU/stallwarn.rst index d7c8eff6331740..a986358a5c2795 100644 --- a/Documentation/RCU/stallwarn.rst +++ b/Documentation/RCU/stallwarn.rst @@ -122,10 +122,11 @@ warnings: of RCU CPU stall warnings, eventually leading to the realization that the CPU had failed. -The RCU, RCU-sched, RCU-tasks, and RCU-tasks-trace implementations have -CPU stall warning. Note that SRCU does *not* have CPU stall warnings. -Please note that RCU only detects CPU stalls when there is a grace period -in progress. No grace period, no CPU stall warnings. +The RCU and RCU-tasks implementations have CPU stall warning. Note that +SRCU does *not* have CPU stall warnings, and now that RCU-tasks-trace +is implemented in terms of SRCU, neither does it. Please note that +RCU only detects CPU stalls when there is a grace period in progress. +No grace period, no CPU stall warnings. To diagnose the cause of the stall, inspect the stack traces. The offending function will usually be near the top of the stack. @@ -213,21 +214,16 @@ RCU_STALL_RAT_DELAY rcupdate.rcu_task_stall_timeout ------------------------------- - This boot/sysfs parameter controls the RCU-tasks and - RCU-tasks-trace stall warning intervals. A value of zero or less - suppresses RCU-tasks stall warnings. A positive value sets the - stall-warning interval in seconds. An RCU-tasks stall warning - starts with the line: + This boot/sysfs parameter controls the RCU-tasks stall warning + interval. A value of zero or less suppresses RCU-tasks stall + warnings. A positive value sets the stall-warning interval + in seconds. An RCU-tasks stall warning starts with the line: INFO: rcu_tasks detected stalls on tasks: And continues with the output of sched_show_task() for each task stalling the current RCU-tasks grace period. - An RCU-tasks-trace stall warning starts (and continues) similarly: - - INFO: rcu_tasks_trace detected stalls on tasks - Interpreting RCU's CPU Stall-Detector "Splats" ============================================== From 4c714f015d9ecc37d7a330b31822ccbcaff5ddc9 Mon Sep 17 00:00:00 2001 From: Longlong Xia Date: Wed, 26 Aug 2026 16:21:50 +0800 Subject: [PATCH 0519/2286] rcu: fix shrink budget underflow in lazy_rcu_shrink_scan The lazy RCU shrinker decremented sc->nr_to_scan (unsigned long) and then tested the result with <= 0. When a single CPU's lazy callback count exceeds the remaining budget, the subtraction wraps to a large positive value and the <= 0 comparison, which is equivalent to == 0 for an unsigned type, never fires again. The scan loop then iterates through every nocb CPU instead of honouring the reclaim budget. Accumulate into count and stop once count >= nr_to_scan. Fixes: c945b4da7a448 ("rcu: Shrinker for lazy rcu") Assisted-by: Zcode:GLM-5.2 Signed-off-by: Longlong Xia Signed-off-by: Paul E. McKenney --- kernel/rcu/tree_nocb.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h index 19bb42672baf8c..b6cfee4e503997 100644 --- a/kernel/rcu/tree_nocb.h +++ b/kernel/rcu/tree_nocb.h @@ -1332,9 +1332,8 @@ lazy_rcu_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) rcu_nocb_try_flush_bypass(rdp, jiffies); rcu_nocb_unlock_irqrestore(rdp, flags); wake_nocb_gp(rdp); - sc->nr_to_scan -= _count; count += _count; - if (sc->nr_to_scan <= 0) + if (count >= sc->nr_to_scan) break; } From dbe1ae2304a3b02c3bf6ae82895ff232a29f3fee Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Sun, 30 Aug 2026 23:11:29 +0100 Subject: [PATCH 0520/2286] kconfig: fix extra output from savedefconfig on out-of-range defaults The Kconfig interpreter currently allows defaults that are outside of the range bounds. In these cases, the 'sym_validate_range' function will adjust the default value to the nearest range bound. For example, see this example: config A int range 1 2 default 16 Here, since the default value of 16 is greater than the bounds, the effective default value gets adjusted down to the upper bound, 2. However, 'savedefconfig' writes non-default values, and without being aware of the automatic adjustment to the range bound, it would write: A=2 This limitation is also documented in a comment: "The following fails to handle the situation where a default value is further limited by the valid range." To resolve this, let's factor out the default-range adjustment logic from the existing 'sym_validate_range' function into its own 'sym_get_near_range_bound' function for 'savedefconfig' to use too, so that it compares against the effective value. Adds tests, accordingly. Fixes: 7cf3d73b4360 ("kconfig: add savedefconfig") Assisted-by: Codex:gpt-5.6-sol Reported-by: Geert Uytterhoeven Closes: https://lore.kernel.org/lkml/CAMuHMdVyUAA3L4mUkSjmnuE3cvj-+N8z-Bhxsh1wa-FQWc=fjw@mail.gmail.com/ Signed-off-by: Julian Braha Tested-by: Geert Uytterhoeven Reviewed-by: Nathan Chancellor Link: https://patch.msgid.link/20260830221129.2668354-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 39 ++++++++---- .../kconfig/tests/savedefconfig_range/Kconfig | 60 +++++++++++++++++++ .../tests/savedefconfig_range/__init__.py | 8 +++ .../kconfig/tests/savedefconfig_range/config | 7 +++ .../savedefconfig_range/expected_defconfig | 0 5 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 scripts/kconfig/tests/savedefconfig_range/Kconfig create mode 100644 scripts/kconfig/tests/savedefconfig_range/__init__.py create mode 100644 scripts/kconfig/tests/savedefconfig_range/config create mode 100644 scripts/kconfig/tests/savedefconfig_range/expected_defconfig diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index e82f330fb8ee77..01aa58d92c3b53 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -157,7 +157,12 @@ static long long sym_get_range_val(struct symbol *sym, int base) return strtoll(sym->curr.val, NULL, base); } -static void sym_validate_range(struct symbol *sym) +/* + * Return the nearest range bound for an out-of-range default value. + * Return NULL if the value is valid or the symbol has no active range. + */ +static struct symbol *sym_get_near_range_bound(struct symbol *sym, + const char *value) { struct property *prop; struct symbol *range_sym; @@ -172,21 +177,31 @@ static void sym_validate_range(struct symbol *sym) base = 16; break; default: - return; + return NULL; } prop = sym_get_range_prop(sym); if (!prop) - return; - val = strtoll(sym->curr.val, NULL, base); + return NULL; + val = strtoll(value, NULL, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); if (val >= val2) { range_sym = prop->expr->right.sym; val2 = sym_get_range_val(range_sym, base); if (val <= val2) - return; + return NULL; } - sym->curr.val = range_sym->curr.val; + + return range_sym; +} + +static void sym_validate_range(struct symbol *sym) +{ + struct symbol *range_sym; + + range_sym = sym_get_near_range_bound(sym, sym->curr.val); + if (range_sym) + sym->curr.val = range_sym->curr.val; } static void sym_set_changed(struct symbol *sym) @@ -812,7 +827,7 @@ bool sym_set_string_value(struct symbol *sym, const char *newval) const char *sym_get_string_default(struct symbol *sym) { struct property *prop; - struct symbol *ds; + struct symbol *ds, *range_sym; const char *str = ""; tristate val; @@ -830,11 +845,6 @@ const char *sym_get_string_default(struct symbol *sym) val = EXPR_AND(expr_calc_value(prop->expr), prop->visible.tri); break; default: - /* - * The following fails to handle the situation - * where a default value is further limited by - * the valid range. - */ ds = prop_get_symbol(prop); if (ds != NULL) { sym_calc_value(ds); @@ -878,6 +888,11 @@ const char *sym_get_string_default(struct symbol *sym) default: break; } + + range_sym = sym_get_near_range_bound(sym, str); + if (range_sym) + str = range_sym->curr.val; + return str; } diff --git a/scripts/kconfig/tests/savedefconfig_range/Kconfig b/scripts/kconfig/tests/savedefconfig_range/Kconfig new file mode 100644 index 00000000000000..fd59d9082ed5ba --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/Kconfig @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Static default and range values + +config INT_DEFAULT_ABOVE_RANGE + int + range 1 1 + default 16 + +config INT_DEFAULT_BELOW_RANGE + int + range 4 8 + default 2 + +# Default and range values determined by other options + +config RANGE_UPPER_BOUND + int + default 3 + +config INT_DYNAMIC_RANGE + int + range 0 RANGE_UPPER_BOUND + default 4 + +# Hex + +config HEX_DEFAULT_ABOVE_RANGE + hex + range 0x10 0x20 + default 0x40 + +# Implicit default value of 0 + +config INT_IMPLICIT_DEFAULT_ZERO + int + range 1 4 + +# Conditional range + +config USE_FIRST_RANGE + bool + default y + +config INT_CONDITIONAL_RANGE + int + range 1 2 if USE_FIRST_RANGE + range 3 4 if !USE_FIRST_RANGE + default 3 + +# Conditional default + +config USE_DEFAULT + bool + default y + +config INT_CONDITIONAL_DEFAULT + int + range 1 2 + default 3 if USE_DEFAULT diff --git a/scripts/kconfig/tests/savedefconfig_range/__init__.py b/scripts/kconfig/tests/savedefconfig_range/__init__.py new file mode 100644 index 00000000000000..961454be732c2f --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Test savedefconfig with numerical defaults outside of ranges.""" + + +def test(conf): + assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', + out_file='defconfig') == 0 + assert conf.config_matches('expected_defconfig') diff --git a/scripts/kconfig/tests/savedefconfig_range/config b/scripts/kconfig/tests/savedefconfig_range/config new file mode 100644 index 00000000000000..d939cfe5fd3494 --- /dev/null +++ b/scripts/kconfig/tests/savedefconfig_range/config @@ -0,0 +1,7 @@ +CONFIG_INT_DEFAULT_ABOVE_RANGE=1 +CONFIG_INT_DEFAULT_BELOW_RANGE=4 +CONFIG_INT_DYNAMIC_RANGE=3 +CONFIG_HEX_DEFAULT_ABOVE_RANGE=0x20 +CONFIG_INT_IMPLICIT_DEFAULT_ZERO=1 +CONFIG_INT_CONDITIONAL_RANGE=2 +CONFIG_INT_CONDITIONAL_DEFAULT=2 diff --git a/scripts/kconfig/tests/savedefconfig_range/expected_defconfig b/scripts/kconfig/tests/savedefconfig_range/expected_defconfig new file mode 100644 index 00000000000000..e69de29bb2d1d6 From a2a402bd5e5a4fa466d6034e70b18b6ac22f873d Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 17 Sep 2026 16:23:32 -0700 Subject: [PATCH 0521/2286] erofs: Revert "Fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS default logic" With commit dbe1ae2304a3 ("kconfig: fix extra output from savedefconfig on out-of-range defaults") applied, commit ab74edaeb1ae ("erofs: Fix EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS default logic") is no longer necessary, so revert it. Signed-off-by: Nathan Chancellor --- fs/erofs/Kconfig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 37495e95a78d9b..2dfc313588d283 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -132,13 +132,11 @@ config EROFS_FS_ZIP_LZMA Say N if you want to disable LZMA compression support. config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS - int "EROFS LZMA default maximum decompression streams" if SMP + int "EROFS LZMA default maximum decompression streams" depends on EROFS_FS_ZIP_LZMA range 1 NR_CPUS if SMP range 1 1 if !SMP - default 16 if SMP && NR_CPUS >= 16 - default NR_CPUS if SMP - default 1 + default 16 help By default EROFS allocates one LZMA decompression stream per CPU. Each stream can hold a dictionary of up to 8 MiB taken from the @@ -146,7 +144,7 @@ config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS of memory. This caps the default; the lzma_streams module parameter still overrides it. - If unsure, keep the suggested default (16 or lower). + If unsure, keep the default of 16. config EROFS_FS_ZIP_DEFLATE bool "EROFS DEFLATE compressed data support" From 95fde4bcc0b7c78b2db6e89a43d47074b79f2afa Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:04 +0100 Subject: [PATCH 0522/2286] kconfig: promote invalid numeric reference from warning to error The Kconfig interpreter already warns if a numeric option attempts to use a non-numeric option (bool, tristate, or string) to set its value (for example, with a 'default' or 'range'). Since there is nowhere in the tree that attempts this, we can safely promote this check from warning to error. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-2-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/lkc.h | 2 +- scripts/kconfig/menu.c | 39 ++++++---- scripts/kconfig/parser.y | 2 +- .../tests/err_num_non_numeric_ref/Kconfig | 75 +++++++++++++++++++ .../tests/err_num_non_numeric_ref/__init__.py | 9 +++ .../err_num_non_numeric_ref/expected_stderr | 14 ++++ 6 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py create mode 100644 scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h index 7e6f6ca299cf1a..bbc99f75b416da 100644 --- a/scripts/kconfig/lkc.h +++ b/scripts/kconfig/lkc.h @@ -89,7 +89,7 @@ struct property *menu_add_prompt(enum prop_type type, const char *prompt, struct expr *dep); void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); -void menu_finalize(void); +int menu_finalize(void); void menu_set_type(int type); extern struct menu rootmenu; diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 9c079e92a9ed62..99a57ce0fdc9bf 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -240,11 +240,12 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2) (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); } -static void sym_check_prop(struct symbol *sym) +static int sym_check_prop(struct symbol *sym) { struct property *prop; struct symbol *sym2; char *use; + int errors = 0; for (prop = sym->prop; prop; prop = prop->next) { switch (prop->type) { @@ -258,10 +259,13 @@ static void sym_check_prop(struct symbol *sym) break; sym2 = prop_get_symbol(prop); if (sym->type == S_HEX || sym->type == S_INT) { - if (!menu_validate_number(sym, sym2)) - prop_warn(prop, - "'%s': number is invalid", - sym->name); + if (!menu_validate_number(sym, sym2)) { + fprintf(stderr, + "%s:%d: error: '%s': number is invalid\n", + prop->filename, prop->lineno, + sym->name); + errors++; + } } if (sym_is_choice(sym)) { struct menu *choice = sym_get_choice_menu(sym2); @@ -293,21 +297,28 @@ static void sym_check_prop(struct symbol *sym) prop_warn(prop, "range is only allowed " "for int or hex symbols"); if (!menu_validate_number(sym, prop->expr->left.sym) || - !menu_validate_number(sym, prop->expr->right.sym)) - prop_warn(prop, "range is invalid"); + !menu_validate_number(sym, prop->expr->right.sym)) { + fprintf(stderr, + "%s:%d: error: range is invalid\n", + prop->filename, prop->lineno); + errors++; + } break; default: ; } } + + return errors; } -static void _menu_finalize(struct menu *parent, bool inside_choice) +static int _menu_finalize(struct menu *parent, bool inside_choice) { struct menu *menu, *last_menu; struct symbol *sym; struct property *prop; struct expr *basedep, *dep, *dep2; + int errors = 0; sym = parent->sym; if (parent->list) { @@ -393,7 +404,7 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) * moving on */ for (menu = parent->list; menu; menu = menu->next) - _menu_finalize(menu, sym && sym_is_choice(sym)); + errors += _menu_finalize(menu, sym && sym_is_choice(sym)); } else if (!inside_choice && sym) { /* * Automatic submenu creation. If sym is a symbol and A, B, C, @@ -461,7 +472,7 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) } /* Superset, put in submenu */ next: - _menu_finalize(menu, false); + errors += _menu_finalize(menu, false); menu->parent = parent; last_menu = menu; } @@ -519,14 +530,16 @@ static void _menu_finalize(struct menu *parent, bool inside_choice) menu_warn(parent, "config symbol defined without type"); /* Check properties connected to this symbol */ - sym_check_prop(sym); + errors += sym_check_prop(sym); sym->flags |= SYMBOL_WARNED; } + + return errors; } -void menu_finalize(void) +int menu_finalize(void) { - _menu_finalize(&rootmenu, false); + return _menu_finalize(&rootmenu, false); } bool menu_has_prompt(const struct menu *menu) diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y index 5fb6f07b6ad292..40ceb9908c6fba 100644 --- a/scripts/kconfig/parser.y +++ b/scripts/kconfig/parser.y @@ -587,7 +587,7 @@ void conf_parse(const char *name) menu_add_prompt(P_MENU, "Main menu", NULL); } - menu_finalize(); + yynerrs += menu_finalize(); menu_for_each_entry(menu) { struct menu *child; diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig b/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig new file mode 100644 index 00000000000000..0ca7a4a460f6cf --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/Kconfig @@ -0,0 +1,75 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test non-numeric symbol references from numeric symbols + +config BOOL_SOURCE + bool + +config TRISTATE_SOURCE + tristate + +config STRING_SOURCE + string + +# Invalid int defaults + +config INT_DEFAULT_BOOL + int + default BOOL_SOURCE + +config INT_DEFAULT_TRISTATE + int + default TRISTATE_SOURCE + +config INT_DEFAULT_STRING + int + default STRING_SOURCE + +# Invalid hex defaults + +config HEX_DEFAULT_BOOL + hex + default BOOL_SOURCE + +config HEX_DEFAULT_TRISTATE + hex + default TRISTATE_SOURCE + +config HEX_DEFAULT_STRING + hex + default STRING_SOURCE + +# Invalid int ranges + +config INT_RANGE_BOOL + int + range BOOL_SOURCE 1 + +config INT_RANGE_TRISTATE + int + range TRISTATE_SOURCE 1 + +config INT_RANGE_STRING + int + range STRING_SOURCE 1 + +config INT_RANGE_MULTIPLE + int + range BOOL_SOURCE TRISTATE_SOURCE + +# Invalid hex ranges + +config HEX_RANGE_BOOL + hex + range BOOL_SOURCE 0x1 + +config HEX_RANGE_TRISTATE + hex + range TRISTATE_SOURCE 0x1 + +config HEX_RANGE_STRING + hex + range STRING_SOURCE 0x1 + +config HEX_RANGE_MULTIPLE + hex + range BOOL_SOURCE TRISTATE_SOURCE diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py b/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py new file mode 100644 index 00000000000000..9632907abead8b --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Reject nonnumeric symbol references from int and hex properties. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr new file mode 100644 index 00000000000000..4974ba2fcd9cd3 --- /dev/null +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr @@ -0,0 +1,14 @@ +Kconfig:17: error: 'INT_DEFAULT_BOOL': number is invalid +Kconfig:21: error: 'INT_DEFAULT_TRISTATE': number is invalid +Kconfig:25: error: 'INT_DEFAULT_STRING': number is invalid +Kconfig:31: error: 'HEX_DEFAULT_BOOL': number is invalid +Kconfig:35: error: 'HEX_DEFAULT_TRISTATE': number is invalid +Kconfig:39: error: 'HEX_DEFAULT_STRING': number is invalid +Kconfig:45: error: range is invalid +Kconfig:49: error: range is invalid +Kconfig:53: error: range is invalid +Kconfig:57: error: range is invalid +Kconfig:63: error: range is invalid +Kconfig:67: error: range is invalid +Kconfig:71: error: range is invalid +Kconfig:75: error: range is invalid From a5e84d6f6df1fdb64c06fdb64894199aaf5ec899 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:05 +0100 Subject: [PATCH 0523/2286] kconfig: check for out-of-bounds numeric constants The Kconfig interpreter internally represents constants as strings, then attempts to parse them as 64-bit signed integers for 'int' options, and 64-bit unsigned integers for 'hex' options. However, there is currently no check that the conversion succeeds, leading to failures when the values are actually used. For example: config LARGE_INT int default 10000000000000000000 config BUGGED_INT_COMPARISON bool default y if LARGE_INT < 2 Obviously 10000000000000000000 is larger than 2, but the Kconfig interpreter will fallback to comparing the two values with strcmp() after the numeric conversion fails, causing the first character, '1', to be compared with '2', and giving the wrong result. Since none of these out-of-bounds values are used as constants anywhere in the tree, we can already make these error out. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-3-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 60 ++++++++++---- scripts/kconfig/tests/err_num_bounds/Kconfig | 79 +++++++++++++++++++ .../kconfig/tests/err_num_bounds/__init__.py | 12 +++ .../tests/err_num_bounds/expected_stderr | 10 +++ .../err_num_non_numeric_ref/expected_stderr | 30 +++---- 5 files changed, 160 insertions(+), 31 deletions(-) create mode 100644 scripts/kconfig/tests/err_num_bounds/Kconfig create mode 100644 scripts/kconfig/tests/err_num_bounds/__init__.py create mode 100644 scripts/kconfig/tests/err_num_bounds/expected_stderr diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 99a57ce0fdc9bf..ede791a2fe1b61 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -234,10 +235,46 @@ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) menu_add_prop(type, expr_alloc_symbol(sym), dep); } -static int menu_validate_number(struct symbol *sym, struct symbol *sym2) +/* Validate the sym2 value for numeric sym. */ +static int menu_validate_number(struct symbol *sym, struct symbol *sym2, + const struct property *prop) { - return sym2->type == S_INT || sym2->type == S_HEX || - (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); + const char *type_bounds; + + if (sym->type != S_INT && sym->type != S_HEX) + return 0; + + if (sym2->type == S_INT || sym2->type == S_HEX) + return 0; + + if (sym2->type != S_UNKNOWN || + !sym_string_valid(sym, sym2->name)) { + fprintf(stderr, "%s:%d: error: '%s' is an invalid value for '%s'\n", + prop->filename, prop->lineno, sym2->name, + sym_type_name(sym->type)); + return 1; + } + + errno = 0; + if (sym->type == S_INT) { + type_bounds = "64-bit signed integer"; + strtoll(sym2->name, NULL, 10); + } else { + /* hex */ + type_bounds = "64-bit unsigned integer"; + strtoull(sym2->name, NULL, 16); + } + + if (errno == ERANGE) { + fprintf(stderr, + "%s:%d: error: %s constant '%s' is outside the %s bounds\n", + prop->filename, prop->lineno, sym_type_name(sym->type), + sym2->name, type_bounds); + + return 1; + } + + return 0; } static int sym_check_prop(struct symbol *sym) @@ -259,13 +296,7 @@ static int sym_check_prop(struct symbol *sym) break; sym2 = prop_get_symbol(prop); if (sym->type == S_HEX || sym->type == S_INT) { - if (!menu_validate_number(sym, sym2)) { - fprintf(stderr, - "%s:%d: error: '%s': number is invalid\n", - prop->filename, prop->lineno, - sym->name); - errors++; - } + errors += menu_validate_number(sym, sym2, prop); } if (sym_is_choice(sym)) { struct menu *choice = sym_get_choice_menu(sym2); @@ -296,13 +327,8 @@ static int sym_check_prop(struct symbol *sym) if (sym->type != S_INT && sym->type != S_HEX) prop_warn(prop, "range is only allowed " "for int or hex symbols"); - if (!menu_validate_number(sym, prop->expr->left.sym) || - !menu_validate_number(sym, prop->expr->right.sym)) { - fprintf(stderr, - "%s:%d: error: range is invalid\n", - prop->filename, prop->lineno); - errors++; - } + errors += menu_validate_number(sym, prop->expr->left.sym, prop); + errors += menu_validate_number(sym, prop->expr->right.sym, prop); break; default: ; diff --git a/scripts/kconfig/tests/err_num_bounds/Kconfig b/scripts/kconfig/tests/err_num_bounds/Kconfig new file mode 100644 index 00000000000000..c439366c03b676 --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/Kconfig @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test bounds checks for 'int' and 'hex' constants + +config INT_SOURCE + int + +config HEX_SOURCE + hex + +config BOOL_SOURCE + bool + +# Valid values at the limits of the type + +config INT_MIN + int + default -9223372036854775808 + +config INT_MAX + int + default 9223372036854775807 + +config HEX_MIN + hex + default 0x0 + +config HEX_MAX + hex + default 0xffffffffffffffff + +config INT_RANGE_LIMITS + int + range -9223372036854775808 9223372036854775807 + +config HEX_RANGE_LIMITS + hex + range 0 0xffffffffffffffff + +config INT_FROM_INT + int + default INT_SOURCE + +config HEX_FROM_HEX + hex + default HEX_SOURCE + +# Constants outside the bounds + +config INT_DEFAULT_TOO_HIGH + int + default 10000000000000000000 + +config INT_DEFAULT_TOO_LOW + int + default -9223372036854775809 + +config INT_RANGE_TOO_HIGH + int + range 0 10000000000000000000 + +config INT_RANGE_TOO_LOW + int + range -10000000000000000000 0 + +config INT_RANGE_BOTH_OUTSIDE + int + range -9223372036854775809 10000000000000000000 + +config HEX_DEFAULT_TOO_HIGH + hex + default 0x10000000000000000 + +config HEX_RANGE_TOO_HIGH + hex + range 0 0x10000000000000000 + +config HEX_RANGE_BOTH_TOO_HIGH + hex + range 0x10000000000000000 0x20000000000000000 diff --git a/scripts/kconfig/tests/err_num_bounds/__init__.py b/scripts/kconfig/tests/err_num_bounds/__init__.py new file mode 100644 index 00000000000000..72ac6aa244918b --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/__init__.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Detect constants outside the 'int' and 'hex' bounds. + +An int constant must fit in a signed 64-bit integer, and a hex constant must +fit in an unsigned 64-bit integer. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_bounds/expected_stderr b/scripts/kconfig/tests/err_num_bounds/expected_stderr new file mode 100644 index 00000000000000..3f06e13359ef25 --- /dev/null +++ b/scripts/kconfig/tests/err_num_bounds/expected_stderr @@ -0,0 +1,10 @@ +Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds +Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds +Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds +Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned integer bounds diff --git a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr index 4974ba2fcd9cd3..005f855ecbddf4 100644 --- a/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr +++ b/scripts/kconfig/tests/err_num_non_numeric_ref/expected_stderr @@ -1,14 +1,16 @@ -Kconfig:17: error: 'INT_DEFAULT_BOOL': number is invalid -Kconfig:21: error: 'INT_DEFAULT_TRISTATE': number is invalid -Kconfig:25: error: 'INT_DEFAULT_STRING': number is invalid -Kconfig:31: error: 'HEX_DEFAULT_BOOL': number is invalid -Kconfig:35: error: 'HEX_DEFAULT_TRISTATE': number is invalid -Kconfig:39: error: 'HEX_DEFAULT_STRING': number is invalid -Kconfig:45: error: range is invalid -Kconfig:49: error: range is invalid -Kconfig:53: error: range is invalid -Kconfig:57: error: range is invalid -Kconfig:63: error: range is invalid -Kconfig:67: error: range is invalid -Kconfig:71: error: range is invalid -Kconfig:75: error: range is invalid +Kconfig:17: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:21: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:25: error: 'STRING_SOURCE' is an invalid value for 'integer' +Kconfig:31: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:35: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' +Kconfig:39: error: 'STRING_SOURCE' is an invalid value for 'hex' +Kconfig:45: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:49: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:53: error: 'STRING_SOURCE' is an invalid value for 'integer' +Kconfig:57: error: 'BOOL_SOURCE' is an invalid value for 'integer' +Kconfig:57: error: 'TRISTATE_SOURCE' is an invalid value for 'integer' +Kconfig:63: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:67: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' +Kconfig:71: error: 'STRING_SOURCE' is an invalid value for 'hex' +Kconfig:75: error: 'BOOL_SOURCE' is an invalid value for 'hex' +Kconfig:75: error: 'TRISTATE_SOURCE' is an invalid value for 'hex' From d13bad83d89d1022fee254c08f6e01084eb4395e Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:06 +0100 Subject: [PATCH 0524/2286] kconfig: check for hex and int mismatches Using a numeric option of one type (e.g. 'int') to determine the value of a different numeric type (e.g. 'hex') currently fails silently in various ways if attempted, because the underlying string representation is naively reused. Example 1: config I int default -1 config HEX_DEFAULT_INT hex default I Here, HEX_DEFAULT_INT actually gets set to '0x-1', which is of course not a valid hex value. Example 2: config H hex default A config INT_DEFAULT_HEX int default H Here, INT_DEFAULT_HEX actually gets set to 'A', without even converting into the base-10 equivalent of 10. This value, 'A', is otherwise a rejected int value if entered in the frontend, or read in from an existing .config file. These int-hex mismatches currently do not appear anywhere in the tree, so it is already safe to make these error out. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-4-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 2 +- .../kconfig/tests/err_num_mismatch/Kconfig | 38 +++++++++++++++++++ .../tests/err_num_mismatch/__init__.py | 9 +++++ .../tests/err_num_mismatch/expected_stderr | 4 ++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 scripts/kconfig/tests/err_num_mismatch/Kconfig create mode 100644 scripts/kconfig/tests/err_num_mismatch/__init__.py create mode 100644 scripts/kconfig/tests/err_num_mismatch/expected_stderr diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index ede791a2fe1b61..2d8b0c65ce1ee3 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -244,7 +244,7 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2, if (sym->type != S_INT && sym->type != S_HEX) return 0; - if (sym2->type == S_INT || sym2->type == S_HEX) + if (sym2->type == sym->type) return 0; if (sym2->type != S_UNKNOWN || diff --git a/scripts/kconfig/tests/err_num_mismatch/Kconfig b/scripts/kconfig/tests/err_num_mismatch/Kconfig new file mode 100644 index 00000000000000..8406f3bb6419ff --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0 +# Test 'int' and 'hex' symbols that reference each other + +config INT_SOURCE + int + +config HEX_SOURCE + hex + +# 'hex' reference from 'int' + +config INT_DEFAULT_HEX + int + default HEX_SOURCE + +config INT_RANGE_HEX + int + range HEX_SOURCE 1 + +# A hex symbol must not reference an int symbol + +config HEX_DEFAULT_INT + hex + default INT_SOURCE + +config HEX_RANGE_INT + hex + range INT_SOURCE 0x1 + +# Referencing the same type is valid + +config INT_FROM_INT + int + default INT_SOURCE + +config HEX_FROM_HEX + hex + range 0 HEX_SOURCE diff --git a/scripts/kconfig/tests/err_num_mismatch/__init__.py b/scripts/kconfig/tests/err_num_mismatch/__init__.py new file mode 100644 index 00000000000000..275f2a6e9a5bf2 --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/__init__.py @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Reject direct references ('default' or 'range') between int and hex options. +""" + + +def test(conf): + assert conf.olddefconfig() == 1 + assert conf.stderr_matches('expected_stderr') diff --git a/scripts/kconfig/tests/err_num_mismatch/expected_stderr b/scripts/kconfig/tests/err_num_mismatch/expected_stderr new file mode 100644 index 00000000000000..587c34467ae632 --- /dev/null +++ b/scripts/kconfig/tests/err_num_mismatch/expected_stderr @@ -0,0 +1,4 @@ +Kconfig:14: error: 'HEX_SOURCE' is an invalid value for 'integer' +Kconfig:18: error: 'HEX_SOURCE' is an invalid value for 'integer' +Kconfig:24: error: 'INT_SOURCE' is an invalid value for 'hex' +Kconfig:28: error: 'INT_SOURCE' is an invalid value for 'hex' From 54f11924e4854177b357af499ebc447dc8c3c590 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:07 +0100 Subject: [PATCH 0525/2286] kconfig: prevent out-of-bounds user input for numeric options Currently, user input of out-of-bounds values for 'int' and 'hex' options is possible, leading to later silent failures in Kconfig if that value is used in a comparison, or a warning by GCC or error by Clang if used in the C code. Let's factor out the out-of-bounds check on constants, so that it can be reused for checking user input. The frontend will now reject an attempted user input of an out-of-bounds numeric value, similarly to how a value outside the active 'range' already does. For migration of existing configurations, a .config file with an out-of-bounds numeric value will be allowed for now, but will warn the user when read in by confdata.c Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-5-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/confdata.c | 6 +++++ scripts/kconfig/lkc_proto.h | 1 + scripts/kconfig/menu.c | 19 +++----------- scripts/kconfig/symbol.c | 20 +++++++++++++++ .../tests/err_num_bounds/expected_stderr | 20 +++++++-------- scripts/kconfig/tests/warn_num_bounds/Kconfig | 24 ++++++++++++++++++ .../kconfig/tests/warn_num_bounds/__init__.py | 25 +++++++++++++++++++ scripts/kconfig/tests/warn_num_bounds/config | 7 ++++++ .../tests/warn_num_bounds/expected_config | 11 ++++++++ .../warn_num_bounds/expected_config_stderr | 3 +++ .../warn_num_bounds/expected_frontend_config | 11 ++++++++ .../warn_num_bounds/expected_frontend_stderr | 0 12 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 scripts/kconfig/tests/warn_num_bounds/Kconfig create mode 100644 scripts/kconfig/tests/warn_num_bounds/__init__.py create mode 100644 scripts/kconfig/tests/warn_num_bounds/config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_config_stderr create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_frontend_config create mode 100644 scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c index 4234a51d16fd18..2227d89d6328f8 100644 --- a/scripts/kconfig/confdata.c +++ b/scripts/kconfig/confdata.c @@ -354,6 +354,12 @@ static int conf_set_sym_val(struct symbol *sym, int def, int def_flags, char *p) case S_INT: case S_HEX: if (sym_string_valid(sym, p)) { + if (def != S_DEF_AUTO && + !sym_string_check_bounds(sym, p)) + /* hex uses 64-bit unsigned integer */ + conf_warning("value '%s' for %s is outside the 64-bit %s integer bounds", + p, sym->name, + sym->type == S_INT ? "signed" : "unsigned"); sym->def[def].val = xstrdup(p); sym->flags |= def_flags; } else { diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h index 8914b4e8f2a811..8b436c87ba4ae3 100644 --- a/scripts/kconfig/lkc_proto.h +++ b/scripts/kconfig/lkc_proto.h @@ -31,6 +31,7 @@ bool sym_set_tristate_value(struct symbol *sym,tristate tri); void choice_set_value(struct menu *choice, struct symbol *sym); tristate sym_toggle_tristate_value(struct symbol *sym); bool sym_string_valid(struct symbol *sym, const char *newval); +bool sym_string_check_bounds(struct symbol *sym, const char *str); bool sym_string_within_range(struct symbol *sym, const char *str); bool sym_set_string_value(struct symbol *sym, const char *newval); bool sym_is_changeable(const struct symbol *sym); diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 2d8b0c65ce1ee3..6b9ef738ee712b 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -4,7 +4,6 @@ */ #include -#include #include #include #include @@ -239,8 +238,6 @@ void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) static int menu_validate_number(struct symbol *sym, struct symbol *sym2, const struct property *prop) { - const char *type_bounds; - if (sym->type != S_INT && sym->type != S_HEX) return 0; @@ -255,21 +252,11 @@ static int menu_validate_number(struct symbol *sym, struct symbol *sym2, return 1; } - errno = 0; - if (sym->type == S_INT) { - type_bounds = "64-bit signed integer"; - strtoll(sym2->name, NULL, 10); - } else { - /* hex */ - type_bounds = "64-bit unsigned integer"; - strtoull(sym2->name, NULL, 16); - } - - if (errno == ERANGE) { + if (!sym_string_check_bounds(sym, sym2->name)) { fprintf(stderr, - "%s:%d: error: %s constant '%s' is outside the %s bounds\n", + "%s:%d: error: %s constant '%s' is outside the 64-bit %s bounds\n", prop->filename, prop->lineno, sym_type_name(sym->type), - sym2->name, type_bounds); + sym2->name, sym->type == S_INT ? "signed" : "unsigned"); return 1; } diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 01aa58d92c3b53..8ccd9e7e2cf0c9 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -726,6 +727,21 @@ bool sym_string_valid(struct symbol *sym, const char *str) } } +bool sym_string_check_bounds(struct symbol *sym, const char *str) +{ + errno = 0; + + if (sym->type == S_INT) + strtoll(str, NULL, 10); + else if (sym->type == S_HEX) + strtoull(str, NULL, 16); + else + /* string */ + return true; + + return errno != ERANGE; +} + bool sym_string_within_range(struct symbol *sym, const char *str) { struct property *prop; @@ -737,6 +753,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) case S_INT: if (!sym_string_valid(sym, str)) return false; + if (!sym_string_check_bounds(sym, str)) + return false; prop = sym_get_range_prop(sym); if (!prop) return true; @@ -746,6 +764,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) case S_HEX: if (!sym_string_valid(sym, str)) return false; + if (!sym_string_check_bounds(sym, str)) + return false; prop = sym_get_range_prop(sym); if (!prop) return true; diff --git a/scripts/kconfig/tests/err_num_bounds/expected_stderr b/scripts/kconfig/tests/err_num_bounds/expected_stderr index 3f06e13359ef25..5f89cf4afff8ac 100644 --- a/scripts/kconfig/tests/err_num_bounds/expected_stderr +++ b/scripts/kconfig/tests/err_num_bounds/expected_stderr @@ -1,10 +1,10 @@ -Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds -Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed integer bounds -Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed integer bounds -Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned integer bounds -Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned integer bounds +Kconfig:51: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:55: error: integer constant '-9223372036854775809' is outside the 64-bit signed bounds +Kconfig:59: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:63: error: integer constant '-10000000000000000000' is outside the 64-bit signed bounds +Kconfig:67: error: integer constant '-9223372036854775809' is outside the 64-bit signed bounds +Kconfig:67: error: integer constant '10000000000000000000' is outside the 64-bit signed bounds +Kconfig:71: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:75: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:79: error: hex constant '0x10000000000000000' is outside the 64-bit unsigned bounds +Kconfig:79: error: hex constant '0x20000000000000000' is outside the 64-bit unsigned bounds diff --git a/scripts/kconfig/tests/warn_num_bounds/Kconfig b/scripts/kconfig/tests/warn_num_bounds/Kconfig new file mode 100644 index 00000000000000..a810225a58a397 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 + +mainmenu "Numeric bounds test" + +config INT_TOO_LOW + int "Integer below its type bounds" + +config INT_TOO_HIGH + int "Integer above its type bounds" + +config HEX_TOO_HIGH + hex "Hex value above its type bounds" + +config INT_MIN + int "Minimum valid integer" + +config INT_MAX + int "Maximum valid integer" + +config HEX_MIN + hex "Minimum valid hex value" + +config HEX_MAX + hex "Maximum valid hex value" diff --git a/scripts/kconfig/tests/warn_num_bounds/__init__.py b/scripts/kconfig/tests/warn_num_bounds/__init__.py new file mode 100644 index 00000000000000..db75182582178b --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/__init__.py @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +"""Test user values outside the numeric type bounds.""" + + +def test(conf): + in_keys = ( + '-9223372036854775809\n' + '-1\n' + '9223372036854775808\n' + '1\n' + '0x10000000000000000\n' + '0x1\n' + '-9223372036854775808\n' + '9223372036854775807\n' + '0x0\n' + '0xffffffffffffffff\n' + ) + + assert conf.oldaskconfig(in_keys=in_keys) == 0 + assert conf.stderr_matches('expected_frontend_stderr') + assert conf.config_matches('expected_frontend_config') + + assert conf.olddefconfig('config') == 0 + assert conf.stderr_matches('expected_config_stderr') + assert conf.config_matches('expected_config') diff --git a/scripts/kconfig/tests/warn_num_bounds/config b/scripts/kconfig/tests/warn_num_bounds/config new file mode 100644 index 00000000000000..74bf263f99bf23 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/config @@ -0,0 +1,7 @@ +CONFIG_INT_TOO_LOW=-9223372036854775809 +CONFIG_INT_TOO_HIGH=9223372036854775808 +CONFIG_HEX_TOO_HIGH=0x10000000000000000 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_config b/scripts/kconfig/tests/warn_num_bounds/expected_config new file mode 100644 index 00000000000000..8b1aef612bc6b2 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_config @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Numeric bounds test +# +CONFIG_INT_TOO_LOW=-9223372036854775809 +CONFIG_INT_TOO_HIGH=9223372036854775808 +CONFIG_HEX_TOO_HIGH=0x10000000000000000 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr b/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr new file mode 100644 index 00000000000000..be2ed7fbf64851 --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_config_stderr @@ -0,0 +1,3 @@ +.config:1:warning: value '-9223372036854775809' for INT_TOO_LOW is outside the 64-bit signed integer bounds +.config:2:warning: value '9223372036854775808' for INT_TOO_HIGH is outside the 64-bit signed integer bounds +.config:3:warning: value '0x10000000000000000' for HEX_TOO_HIGH is outside the 64-bit unsigned integer bounds diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config new file mode 100644 index 00000000000000..a7e842517026ef --- /dev/null +++ b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_config @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Numeric bounds test +# +CONFIG_INT_TOO_LOW=-1 +CONFIG_INT_TOO_HIGH=1 +CONFIG_HEX_TOO_HIGH=0x1 +CONFIG_INT_MIN=-9223372036854775808 +CONFIG_INT_MAX=9223372036854775807 +CONFIG_HEX_MIN=0x0 +CONFIG_HEX_MAX=0xffffffffffffffff diff --git a/scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr b/scripts/kconfig/tests/warn_num_bounds/expected_frontend_stderr new file mode 100644 index 00000000000000..e69de29bb2d1d6 From 9b8719973ee2c5ec23aab9b958e5c6fbdf352f90 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Tue, 15 Sep 2026 22:15:08 +0100 Subject: [PATCH 0526/2286] kconfig: use unsigned integers for hex range checks While 'hex' values should use the bounds of 64-bit unsigned integers, the range values are treated as signed (checks use strtoll). For example: config HEX_RANGE hex range 0 0xfffffffffffffffe default 0xffffffffffffffff the default value should adjust down to the upper bound of 0xfffffffffffffffe, but since the check incorrectly parses both as LLONG_MAX, the value stays at the out-of-range default of 0xffffffffffffffff. To fix this, let's use a union of signed and unsigned integers to represent both 'int' and 'hex' range values, then use both strtoll and strtoull where appropriate. Assisted-by: LLM Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260915211508.291790-6-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 41 ++++++++++++++----- .../kconfig/tests/warn_changed_input/Kconfig | 5 +++ .../tests/warn_changed_input/expected_config | 1 + 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index 8ccd9e7e2cf0c9..dcb4b45e674b80 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -142,8 +142,15 @@ struct property *sym_get_range_prop(struct symbol *sym) return NULL; } -static long long sym_get_range_val(struct symbol *sym, int base) +union range_value { + long long s; + unsigned long long u; +}; + +static union range_value sym_get_range_val(struct symbol *sym, int base) { + union range_value val; + sym_calc_value(sym); switch (sym->type) { case S_INT: @@ -155,7 +162,12 @@ static long long sym_get_range_val(struct symbol *sym, int base) default: break; } - return strtoll(sym->curr.val, NULL, base); + if (base == 10) + val.s = strtoll(sym->curr.val, NULL, base); + else + /* HEX */ + val.u = strtoull(sym->curr.val, NULL, base); + return val; } /* @@ -168,7 +180,7 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, struct property *prop; struct symbol *range_sym; int base; - long long val, val2; + union range_value val, val2; switch (sym->type) { case S_INT: @@ -183,13 +195,19 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, prop = sym_get_range_prop(sym); if (!prop) return NULL; - val = strtoll(value, NULL, base); + val = sym_get_range_val(sym, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); - if (val >= val2) { + + if (base == 10 && val.s >= val2.s) { + range_sym = prop->expr->right.sym; + val2 = sym_get_range_val(range_sym, base); + if (val.s <= val2.s) + return NULL; + } else if (base == 16 && val.u >= val2.u) { range_sym = prop->expr->right.sym; val2 = sym_get_range_val(range_sym, base); - if (val <= val2) + if (val.u <= val2.u) return NULL; } @@ -746,6 +764,7 @@ bool sym_string_within_range(struct symbol *sym, const char *str) { struct property *prop; long long val; + unsigned long long uval; switch (sym->type) { case S_STRING: @@ -759,8 +778,8 @@ bool sym_string_within_range(struct symbol *sym, const char *str) if (!prop) return true; val = strtoll(str, NULL, 10); - return val >= sym_get_range_val(prop->expr->left.sym, 10) && - val <= sym_get_range_val(prop->expr->right.sym, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10).s && + val <= sym_get_range_val(prop->expr->right.sym, 10).s; case S_HEX: if (!sym_string_valid(sym, str)) return false; @@ -769,9 +788,9 @@ bool sym_string_within_range(struct symbol *sym, const char *str) prop = sym_get_range_prop(sym); if (!prop) return true; - val = strtoll(str, NULL, 16); - return val >= sym_get_range_val(prop->expr->left.sym, 16) && - val <= sym_get_range_val(prop->expr->right.sym, 16); + uval = strtoull(str, NULL, 16); + return uval >= sym_get_range_val(prop->expr->left.sym, 16).u && + uval <= sym_get_range_val(prop->expr->right.sym, 16).u; case S_BOOLEAN: case S_TRISTATE: switch (str[0]) { diff --git a/scripts/kconfig/tests/warn_changed_input/Kconfig b/scripts/kconfig/tests/warn_changed_input/Kconfig index 69845e2f3fb34b..df4183a08420f9 100644 --- a/scripts/kconfig/tests/warn_changed_input/Kconfig +++ b/scripts/kconfig/tests/warn_changed_input/Kconfig @@ -26,6 +26,11 @@ config NUM Kconfig resolves it to the constrained in-range value. The warning should report that adjustment. +config HEX_RANGE + hex + range 0 0xfffffffffffffffe + default 0xffffffffffffffff + config DUP bool "DUP" depends on DEP diff --git a/scripts/kconfig/tests/warn_changed_input/expected_config b/scripts/kconfig/tests/warn_changed_input/expected_config index fe8bbec66c5358..6a8d98a69b7e77 100644 --- a/scripts/kconfig/tests/warn_changed_input/expected_config +++ b/scripts/kconfig/tests/warn_changed_input/expected_config @@ -4,3 +4,4 @@ # # CONFIG_DEP is not set CONFIG_NUM=20 +CONFIG_HEX_RANGE=0xfffffffffffffffe From 35abc3cd8ab65dd7d07d4fc48a0d7048ce47d055 Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 7 Aug 2026 18:50:41 +0100 Subject: [PATCH 0527/2286] tee: remove TZMEM_MODE_GENERIC 'select' does not work on config options in a 'choice', so currently it is possible to enable QCOMTEE without QCOM_TZMEM_MODE_SHMBRIDGE, even though this is needed at runtime. There are no users of the generic allocator option, QCOM_TZMEM_MODE_GENERIC, so let's remove it. Then, we can remove the containing choice..endchoice, which allows the 'select' to work as intended. Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Reviewed-by: Arnd Bergmann Reviewed-by: Nathan Chancellor Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260807175041.3299349-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- drivers/firmware/qcom/Kconfig | 26 +++++--------------------- drivers/firmware/qcom/qcom_tzmem.c | 4 ++-- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index c7f8413ab996cf..95b968d88dc39e 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -34,33 +34,17 @@ config QCOM_TZMEM tristate select GENERIC_ALLOCATOR -choice - prompt "TrustZone interface memory allocator mode" - depends on QCOM_TZMEM - default QCOM_TZMEM_MODE_GENERIC - help - Selects the mode of the memory allocator providing memory buffers of - suitable format for sharing with the TrustZone. If in doubt, select - 'Generic'. - -config QCOM_TZMEM_MODE_GENERIC - bool "Generic" - help - Use the generic allocator mode. The memory is page-aligned, non-cachable - and physically contiguous. - config QCOM_TZMEM_MODE_SHMBRIDGE - bool "SHM Bridge" + bool "TrustZone interface memory allocator: SHM Bridge" + depends on QCOM_TZMEM help - Use Qualcomm Shared Memory Bridge. The memory has the same alignment as - in the 'Generic' allocator but is also explicitly marked as an SHM Bridge - buffer. + Use Qualcomm Shared Memory Bridge as memory allocator. The memory has the + same alignment as in the 'Generic' allocator, which is used when this option + is disabled, but is also explicitly marked as an SHM Bridge buffer. With this selected, all buffers passed to the TrustZone must be allocated using the TZMem allocator or else the TrustZone will refuse to use them. -endchoice - config QCOM_QSEECOM bool "Qualcomm QSEECOM interface driver" depends on QCOM_SCM=y diff --git a/drivers/firmware/qcom/qcom_tzmem.c b/drivers/firmware/qcom/qcom_tzmem.c index 0fd9581275f173..510474902c3a53 100644 --- a/drivers/firmware/qcom/qcom_tzmem.c +++ b/drivers/firmware/qcom/qcom_tzmem.c @@ -50,7 +50,7 @@ static struct device *qcom_tzmem_dev; static RADIX_TREE(qcom_tzmem_chunks, GFP_ATOMIC); static DEFINE_SPINLOCK(qcom_tzmem_chunks_lock); -#if IS_ENABLED(CONFIG_QCOM_TZMEM_MODE_GENERIC) +#ifndef CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE static int qcom_tzmem_init(void) { @@ -67,7 +67,7 @@ static void qcom_tzmem_cleanup_area(struct qcom_tzmem_area *area) } -#elif IS_ENABLED(CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE) +#else #include #include From 47ef078cf9ad37a35b20094a824ec09b845532ff Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 4 Aug 2026 14:45:32 +0200 Subject: [PATCH 0528/2286] kconfig: Warn on dead select or imply for choice values Update the internal kconfig check to print a warning on 'select' and 'imply' statements that target on choice values. Julian Braha reported that kconfig's 'select' and 'imply' do not work for choice values but have no effect all. Show a warning instead of error-out to allow kconfig to check the whole kconfig rule set. Reported-by: Julian Braha Closes: https://lore.kernel.org/linux-kbuild/20260715220149.48700-1-julianbraha@gmail.com/ # [1] Suggested-by: Kees Cook Suggested-by: Jani Nikula Signed-off-by: Nicolas Schier Reviewed-by: Julian Braha Tested-by: Julian Braha Link: https://patch.msgid.link/20260804-kconfig-warn-on-dead-select-for-choice-value-v1-1-e5c9a274d291@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kconfig/menu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c index 6b9ef738ee712b..bda3e26502161f 100644 --- a/scripts/kconfig/menu.c +++ b/scripts/kconfig/menu.c @@ -309,6 +309,10 @@ static int sym_check_prop(struct symbol *sym) "'%s' has wrong type. '%s' only " "accept arguments of bool and " "tristate type", sym2->name, use); + if (sym_is_choice_value(sym2)) + prop_warn(prop, + "config symbol '%s' uses %s for '%s', but '%s' is a choice value", + sym->name, use, sym2->name, sym2->name); break; case P_RANGE: if (sym->type != S_INT && sym->type != S_HEX) From 0247a1ed52941de97b8f39d054d1aca498c8d47c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 31 Aug 2026 12:24:11 -0700 Subject: [PATCH 0529/2286] fscrypt: Use aes_ecb_encrypt() for v1 key derivation Now that there's a library function aes_ecb_encrypt(), use it instead of a loop of aes_encrypt() calls. Note that the use of AES-ECB here is purely for the key derivation function used by the deprecated v1 encryption policies. v2 encryption policies use HKDF-SHA512 instead. Nevertheless, the original version has to continue to be supported for backwards compatibility. Link: https://patch.msgid.link/20260831192411.99035-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/Kconfig | 2 +- fs/crypto/keysetup_v1.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/crypto/Kconfig b/fs/crypto/Kconfig index cd934e31dec40b..4cfbf4b51be7a6 100644 --- a/fs/crypto/Kconfig +++ b/fs/crypto/Kconfig @@ -5,7 +5,7 @@ config FS_ENCRYPTION select BLK_INLINE_ENCRYPTION_FALLBACK if BLOCK select CRYPTO select CRYPTO_SKCIPHER - select CRYPTO_LIB_AES + select CRYPTO_LIB_AES_ECB # for deprecated v1 key derivation function select CRYPTO_LIB_SHA256 select CRYPTO_LIB_SHA512 select KEYS diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index 87fe13ccb25359..d9ac7c4328f822 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -20,7 +20,7 @@ * managed alongside the master keys in the filesystem-level keyring) */ -#include +#include #include #include #include @@ -246,8 +246,7 @@ static int setup_v1_file_key_derived(struct fscrypt_inode_info *ci, static_assert(FSCRYPT_FILE_NONCE_SIZE == AES_KEYSIZE_128); aes_prepareenckey(&aes, ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE); - for (unsigned int i = 0; i < derived_keysize; i += AES_BLOCK_SIZE) - aes_encrypt(&aes, &derived_key[i], &raw_master_key[i]); + aes_ecb_encrypt(derived_key, raw_master_key, derived_keysize, &aes); err = fscrypt_set_per_file_enc_key(ci, derived_key); From 7cbf4366da396f415e9c9f1d5b11b45ebdf59ec3 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Thu, 17 Sep 2026 13:30:02 +0530 Subject: [PATCH 0530/2286] tools/thermal: Fix typo "binded" in comments "binded" is not a word; the past participle of "bind" is "bound". The tmon.h comment counts cooling devices, so the noun is plural there, matching the wording already used for MAX_NR_CDEV_TRIP. The tui.c clause needs "is" to read as a sentence. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Signed-off-by: Daniel Lezcano Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260917080002.23952-1-hemanth.selam@gmail.com --- tools/thermal/tmon/sysfs.c | 2 +- tools/thermal/tmon/tmon.h | 2 +- tools/thermal/tmon/tui.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c index cb1108bc924989..5d64aff12a8629 100644 --- a/tools/thermal/tmon/sysfs.c +++ b/tools/thermal/tmon/sysfs.c @@ -225,7 +225,7 @@ static int find_tzone_cdev(struct dirent *nl, char *tz_name, cdev_name, cdev_name_linked, cdev_id); tzi->cdev_binding |= (1 << cdev_id); - /* find the trip point in which the cdev is binded to + /* find the trip point in which the cdev is bound to * in this tzone */ snprintf(cdev_trip_name, sizeof(cdev_trip_name) - 1, diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h index 44d16d778f044c..b7767056f18954 100644 --- a/tools/thermal/tmon/tmon.h +++ b/tools/thermal/tmon/tmon.h @@ -77,7 +77,7 @@ struct tz_info { char type[256]; /* e.g. acpitz */ int instance; int passive; /* active zone has passive node to force passive mode */ - int nr_cdev; /* number of cooling device binded */ + int nr_cdev; /* number of cooling devices bound */ int nr_trip_pts; struct trip_point tp[MAX_NR_TRIP]; unsigned long cdev_binding; /* bitmap for attached cdevs */ diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c index 7f5dd2b87f15fb..dbc3ca95c72424 100644 --- a/tools/thermal/tmon/tui.c +++ b/tools/thermal/tmon/tui.c @@ -238,7 +238,7 @@ void show_cooling_device(void) unsigned long trip_binding = ptdata.tzi[i].trip_binding[cdev_inst]; int k = 0; /* per zone trip point id that - * binded to this cdev, one to + * is bound to this cdev, one to * many possible based on the * binding bitmask. */ From 7efceedd23678cd58e98b2c64d4b2b7be03f6cf9 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 15 Sep 2026 20:41:28 +0100 Subject: [PATCH 0531/2286] thermal/drivers/airoha: Request the EN7581 IRQ after zone registration The EN7581 IRQ is requested before the thermal zone is registered, and the handler passes priv->tz to thermal_zone_device_update(). Unbind leaves the monitor interrupt enabled, so an interrupt pending on the next bind fires while priv->tz is NULL. On a Nokia XG-040G-MD this oopses in thermal_zone_device_update(). On unbind, devres also frees the zone before the IRQ. Request the IRQ in post_probe, after zone registration. Fixes: 42de37f40e1b ("thermal/drivers: Add support for Airoha EN7581 thermal sensor") Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260915194128.42508-1-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 77b3a1af271fdb..ea3e2649c280c9 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -528,7 +528,7 @@ static int en7581_thermal_probe(struct platform_device *pdev, struct device_node *chip_scu_np; struct device *dev = &pdev->dev; void __iomem *base; - int i, irq, ret; + int i; base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(base)) @@ -567,16 +567,6 @@ static int en7581_thermal_probe(struct platform_device *pdev, of_address_to_resource(chip_scu_np, 0, &priv->scu_adc_res); of_node_put(chip_scu_np); - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, - en7581_thermal_irq, IRQF_ONESHOT, - pdev->name, priv); - if (ret) - return ret; - en7581_thermal_setup_monitor(priv); en7581_thermal_setup_adc_val(dev, priv); @@ -586,6 +576,18 @@ static int en7581_thermal_probe(struct platform_device *pdev, static int en7581_thermal_post_probe(struct platform_device *pdev) { struct airoha_thermal_priv *priv = platform_get_drvdata(pdev); + int irq, ret; + + /* After zone registration: the handler uses priv->tz */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + en7581_thermal_irq, IRQF_ONESHOT, + pdev->name, priv); + if (ret) + return ret; /* Enable LOW and HIGH interrupt (if supported) */ regmap_write(priv->map, EN7581_TEMPMONINT, From e7cfcccd8c38ba674240636d882da879c904296a Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 8 Aug 2026 22:23:33 +0200 Subject: [PATCH 0532/2286] thermal/drivers/imx9: Constify struct thermal_zone_device_ops 'struct thermal_zone_device_ops' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increases overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 8567 2144 0 10711 29d7 drivers/thermal/imx91_thermal.o After: ===== text data bss dec hex filename 8695 2016 0 10711 29d7 drivers/thermal/imx91_thermal.o Signed-off-by: Christophe JAILLET Signed-off-by: Daniel Lezcano Reviewed-by: Frank Li Link: https://patch.msgid.link/5cce9ea713edcf2aa3c0efd5d31e9d9fd9f40a88.1786220585.git.christophe.jaillet@wanadoo.fr --- drivers/thermal/imx91_thermal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thermal/imx91_thermal.c b/drivers/thermal/imx91_thermal.c index 274eee3031429e..196ee20fce1e3e 100644 --- a/drivers/thermal/imx91_thermal.c +++ b/drivers/thermal/imx91_thermal.c @@ -225,7 +225,7 @@ static int imx91_tmu_change_mode(struct thermal_zone_device *tz, enum thermal_de return 0; } -static struct thermal_zone_device_ops tmu_tz_ops = { +static const struct thermal_zone_device_ops tmu_tz_ops = { .get_temp = imx91_tmu_get_temp, .change_mode = imx91_tmu_change_mode, .set_trips = imx91_tmu_set_trips, From 072c3e6ac8cd959b523fb589ad9ddf9d7d2a52b4 Mon Sep 17 00:00:00 2001 From: Bhargav Joshi Date: Tue, 11 Aug 2026 00:19:28 +0530 Subject: [PATCH 0533/2286] dt-bindings: thermal: ti,omap-bandgap: Convert to DT schema Convert Texas Instruments OMAP SCM bandgap from legacy text to DT schema Add "ti,dra752-bandgap" to compatible which was missing in text binding but used and matched by same driver. Document the "#thermal-sensor-cells" property, which was missing from the original text binding but is required in practice by all in-tree users via the common thermal-sensor.yaml binding. Signed-off-by: Bhargav Joshi Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260811-ti-bandgap-v2-1-6fec2a71c394@gmail.com --- .../bindings/thermal/ti,omap-bandgap.yaml | 177 ++++++++++++++++++ .../bindings/thermal/ti_soc_thermal.txt | 88 --------- 2 files changed, 177 insertions(+), 88 deletions(-) create mode 100644 Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml delete mode 100644 Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt diff --git a/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml b/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml new file mode 100644 index 00000000000000..091b7be28461be --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/ti,omap-bandgap.yaml @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/ti,omap-bandgap.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OMAP SCM bandgap + +maintainers: + - Eduardo Valentin + +description: + In the System Control Module, OMAP supplies a voltage reference and a + temperature sensor feature that are gathered in the band gap voltage and + temperature sensor (VBGAPTS) module. The band gap provides current and + voltage reference for its internal circuits and other analog IP blocks. The + analog-to-digital converter (ADC) produces an output value that is + proportional to the silicon temperature. + +properties: + compatible: + enum: + - ti,omap34xx-bandgap + - ti,omap36xx-bandgap + - ti,omap4430-bandgap + - ti,omap4460-bandgap + - ti,omap4470-bandgap + - ti,omap5430-bandgap + - ti,dra752-bandgap + + reg: + minItems: 1 + maxItems: 6 + + interrupts: + maxItems: 1 + + gpios: + maxItems: 1 + description: + used to inform which GPIO line the tshut signal is routed to. The + informed GPIO will be treated as an IRQ; + + "#thermal-sensor-cells": + enum: [0, 1] + +required: + - compatible + - reg + +allOf: + - $ref: thermal-sensor.yaml# + + - if: + properties: + compatible: + contains: + enum: + - ti,omap34xx-bandgap + - ti,omap36xx-bandgap + + then: + properties: + reg: + minItems: 1 + maxItems: 1 + + interrupts: false + + gpios: false + + "#thermal-sensor-cells": + const: 0 + + - if: + properties: + compatible: + contains: + const: ti,omap4430-bandgap + + then: + properties: + reg: + minItems: 2 + maxItems: 2 + + interrupts: false + + "#thermal-sensor-cells": + const: 0 + + required: + - gpios + + - if: + properties: + compatible: + contains: + enum: + - ti,omap4460-bandgap + - ti,omap4470-bandgap + + then: + properties: + reg: + minItems: 3 + maxItems: 3 + + "#thermal-sensor-cells": + const: 0 + + required: + - interrupts + - gpios + + - if: + properties: + compatible: + contains: + const: ti,omap5430-bandgap + + then: + properties: + reg: + minItems: 4 + maxItems: 4 + + gpios: false + + "#thermal-sensor-cells": + const: 1 + + required: + - interrupts + + - if: + properties: + compatible: + contains: + const: ti,dra752-bandgap + + then: + properties: + reg: + minItems: 6 + maxItems: 6 + + gpios: false + + "#thermal-sensor-cells": + const: 1 + + required: + - interrupts + +unevaluatedProperties: false + +examples: + - | + bandgap@48002524 { + compatible = "ti,omap34xx-bandgap"; + reg = <0x48002524 0x4>; + #thermal-sensor-cells = <0>; + }; + + - | + #include + #include + + bandgap@4a0021e0 { + compatible = "ti,dra752-bandgap"; + reg = <0x4a0021e0 0xc>, <0x4a00232c 0xc>, + <0x4a002380 0x2c>, <0x4a0023c0 0x3c>, + <0x4a002564 0x8>, <0x4a002574 0x50>; + interrupts = ; /* talert */ + #thermal-sensor-cells = <1>; + }; diff --git a/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt b/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt deleted file mode 100644 index 6299dd8de339ae..00000000000000 --- a/Documentation/devicetree/bindings/thermal/ti_soc_thermal.txt +++ /dev/null @@ -1,88 +0,0 @@ -* Texas Instrument OMAP SCM bandgap bindings - -In the System Control Module, OMAP supplies a voltage reference -and a temperature sensor feature that are gathered in the band -gap voltage and temperature sensor (VBGAPTS) module. The band -gap provides current and voltage reference for its internal -circuits and other analog IP blocks. The analog-to-digital -converter (ADC) produces an output value that is proportional -to the silicon temperature. - -Required properties: -- compatible : Should be: - - "ti,omap34xx-bandgap" : for OMAP34xx bandgap - - "ti,omap36xx-bandgap" : for OMAP36xx bandgap - - "ti,omap4430-bandgap" : for OMAP4430 bandgap - - "ti,omap4460-bandgap" : for OMAP4460 bandgap - - "ti,omap4470-bandgap" : for OMAP4470 bandgap - - "ti,omap5430-bandgap" : for OMAP5430 bandgap -- interrupts : this entry should indicate which interrupt line -the talert signal is routed to; -Specific: -- gpios : this entry should be used to inform which GPIO -line the tshut signal is routed to. The informed GPIO will -be treated as an IRQ; -- regs : this entry must also be specified and it is specific -to each bandgap version, because the mapping may change from -soc to soc, apart of depending on available features. - -Example: -OMAP34xx: -bandgap { - reg = <0x48002524 0x4>; - compatible = "ti,omap34xx-bandgap"; -}; - -OMAP36xx: -bandgap { - reg = <0x48002524 0x4>; - compatible = "ti,omap36xx-bandgap"; -}; - -OMAP4430: -bandgap { - reg = <0x4a002260 0x4 0x4a00232C 0x4>; - compatible = "ti,omap4430-bandgap"; -}; - -OMAP4460: -bandgap { - reg = <0x4a002260 0x4 - 0x4a00232C 0x4 - 0x4a002378 0x18>; - compatible = "ti,omap4460-bandgap"; - interrupts = <0 126 4>; /* talert */ - gpios = <&gpio3 22 0>; /* tshut */ -}; - -OMAP4470: -bandgap { - reg = <0x4a002260 0x4 - 0x4a00232C 0x4 - 0x4a002378 0x18>; - compatible = "ti,omap4470-bandgap"; - interrupts = <0 126 4>; /* talert */ - gpios = <&gpio3 22 0>; /* tshut */ -}; - -OMAP5430: -bandgap { - reg = <0x4a0021e0 0xc - 0x4a00232c 0xc - 0x4a002380 0x2c - 0x4a0023C0 0x3c>; - compatible = "ti,omap5430-bandgap"; - interrupts = <0 126 4>; /* talert */ -}; - -DRA752: -bandgap { - reg = <0x4a0021e0 0xc - 0x4a00232c 0xc - 0x4a002380 0x2c - 0x4a0023C0 0x3c - 0x4a002564 0x8 - 0x4a002574 0x50>; - compatible = "ti,dra752-bandgap"; - interrupts = <0 126 4>; /* talert */ -}; From e30decc3f9a0ded621d5b0f19a5253b84d21be0c Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 16 Sep 2026 13:11:32 +0100 Subject: [PATCH 0534/2286] rust: pin-init: internal: init: emit `slot` using mixed site hygiene Syn/quote's default is the call site hygiene. If user provides a field named `slot`, it will conflict with pin-init generated `slot` identifier. Change it to use mixed site hygiene instead. Fixes: 4883830e9784 ("rust: pin-init: rewrite the initializer macros using `syn`") Link: https://patch.msgid.link/20260916-dev-hygiene-v1-1-dfb6ae2588ba@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index dc5dfb2ca11427..6ade192d27e608 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt}; +use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt}; use syn::{ braced, parenthesized, parse::{End, Parse}, @@ -269,7 +269,7 @@ fn expand( }, |(_, err)| Box::new(err), ); - let slot = format_ident!("slot"); + let slot = Ident::new("slot", Span::mixed_site()); let (has_data_trait, get_data, init_from_closure) = if pinned { ( format_ident!("HasPinData"), @@ -302,7 +302,7 @@ fn expand( }; let this = match this { None => quote!(), - Some(This { ident, .. }) => quote! { + Some(This { ident, .. }) => quote_spanned! { Span::mixed_site() => // Create the `this` so it can be referenced by the user inside of the // expressions creating the individual fields. let #ident = unsafe { ::core::ptr::NonNull::new_unchecked(slot) }; @@ -312,7 +312,7 @@ fn expand( let data = Ident::new("__data", Span::mixed_site()); let init_fields = init_fields(&fields, pinned, &data, &slot); let field_check = make_field_check(&fields, init_kind, &path); - Ok(quote! {{ + Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. // SAFETY: TODO let #data = unsafe { @@ -512,7 +512,7 @@ fn make_field_check( ..::core::mem::zeroed() }), }; - quote! { + quote_spanned! { Span::mixed_site() => #[allow(unreachable_code)] // We use unreachable code to perform field checks. They're still checked by the compiler. // SAFETY: this code is never executed. From 7f849c19e22f105c756b32a6a00dfee9cc1a2c2c Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 16 Sep 2026 13:11:33 +0100 Subject: [PATCH 0535/2286] rust: pin-init: internal: init: use `slot` identifier directly with mixed site Use `quote_spanned!(Span::mixed_site() => ...)` directly instead of creating `slot` identifier and interpolate it later. Do the same for `__data`, too. This is needed so that we can customize the location of the span without having the `slot` identifier getting in the way. For example: quote_spanned!(Span::mixed_site().located_at(loc) => ...) with `slot` mentioned directly will have the diagnostic pointing to the desired location, while `#slot` won't. Link: https://patch.msgid.link/20260916-dev-hygiene-v1-2-dfb6ae2588ba@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 31 ++++++++++++------------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 6ade192d27e608..a77ffef9926b2e 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -269,7 +269,6 @@ fn expand( }, |(_, err)| Box::new(err), ); - let slot = Ident::new("slot", Span::mixed_site()); let (has_data_trait, get_data, init_from_closure) = if pinned { ( format_ident!("HasPinData"), @@ -286,7 +285,7 @@ fn expand( let init_kind = get_init_kind(rest, dcx); let zeroable_check = match init_kind { InitKind::Normal => quote!(), - InitKind::Zeroing => quote! { + InitKind::Zeroing => quote_spanned! { Span::mixed_site() => // The user specified `..Zeroable::zeroed()` at the end of the list of fields. // Therefore we check if the struct implements `Zeroable` and then zero the memory. // This allows us to also remove the check that all fields are present (since we @@ -295,9 +294,9 @@ fn expand( where T: ::pin_init::Zeroable {} // Ensure that the struct is indeed `Zeroable`. - assert_zeroable(#slot); + assert_zeroable(slot); // SAFETY: The type implements `Zeroable` by the check above. - unsafe { ::core::ptr::write_bytes(#slot, 0, 1) }; + unsafe { ::core::ptr::write_bytes(slot, 0, 1) }; }, }; let this = match this { @@ -309,20 +308,19 @@ fn expand( }, }; // `mixed_site` ensures that the data is not accessible to the user-controlled code. - let data = Ident::new("__data", Span::mixed_site()); - let init_fields = init_fields(&fields, pinned, &data, &slot); + let init_fields = init_fields(&fields, pinned); let field_check = make_field_check(&fields, init_kind, &path); Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. // SAFETY: TODO - let #data = unsafe { + let data = unsafe { use ::pin_init::__internal::#has_data_trait; // Can't use `<#path as #has_data_trait>::#get_data`, since the user is able to omit // generics (which need to be present with that syntax). #path::#get_data() }; - // Ensure that `#data` really is of type `#data` and help with type inference: - let init = #data.__make_closure::<_, #error>( + // Ensure that `data` really is of type `data` and help with type inference: + let init = data.__make_closure::<_, #error>( move |slot| { #zeroable_check #this @@ -380,12 +378,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi } /// Generate the code that initializes the fields of the struct using the initializers in `field`. -fn init_fields( - fields: &Punctuated, - pinned: bool, - data: &Ident, - slot: &Ident, -) -> TokenStream { +fn init_fields(fields: &Punctuated, pinned: bool) -> TokenStream { let mut guards = vec![]; let mut guard_attrs = vec![]; let mut res = TokenStream::new(); @@ -413,16 +406,16 @@ fn init_fields( let ident = member.as_ident(); let slot = if pinned { - quote! { + quote_spanned! { Span::mixed_site() => // SAFETY: // - `slot` is valid and properly aligned. // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. // - `make_field_check` prevents `#member` from being used twice, therefore // `(*slot).#member` is exclusively accessed and has not been initialized. - (unsafe { #data.#ident(#slot) }) + (unsafe { data.#ident(slot) }) } } else { - quote! { + quote_spanned! { Span::mixed_site() => // For `init!()` macro, everything is unpinned. // SAFETY: // - `&raw mut (*slot).#member` is valid. @@ -431,7 +424,7 @@ fn init_fields( // `(*slot).#member` is exclusively accessed and has not been initialized. (unsafe { ::pin_init::__internal::Slot::<::pin_init::__internal::Unpinned, _>::new( - &raw mut (*#slot).#member + &raw mut (*slot).#member ) }) } From fb3d5c6b4cca465c68472a918b861b2296768dad Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 16 Sep 2026 13:11:34 +0100 Subject: [PATCH 0536/2286] rust: pin-init: internal: init: provide span for slot projection When initializing a field, pin-init first projects the full slot to a slot of the field, then initialize it by invoking a method. Currently the span location is not explicitly set, so the error message points to the full macro invocation. Improve it by provide a span, so Rust will point to the specific fields causing the error when type check fails. Old error message: error[E0308]: mismatched types --> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:28 | 8 | let _ = init!(Foo { a: () }); | ---------------^^--- | | | | | expected `usize`, found `()` | arguments to this method are incorrect New error message: error[E0308]: mismatched types --> tests/ui/compile-fail/init/field_value_wrong_type.rs:8:28 | 8 | let _ = init!(Foo { a: () }); | ---^^ | | | | | expected `usize`, found `()` | arguments to this method are incorrect Link: https://patch.msgid.link/20260916-dev-hygiene-v1-3-dfb6ae2588ba@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index a77ffef9926b2e..1d2db93dd33d63 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -404,9 +404,10 @@ fn init_fields(fields: &Punctuated, pinned: bool) - } }; let ident = member.as_ident(); + let span = Span::mixed_site().located_at(ident.span()); let slot = if pinned { - quote_spanned! { Span::mixed_site() => + quote_spanned! { span => // SAFETY: // - `slot` is valid and properly aligned. // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. @@ -415,7 +416,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - (unsafe { data.#ident(slot) }) } } else { - quote_spanned! { Span::mixed_site() => + quote_spanned! { span => // For `init!()` macro, everything is unpinned. // SAFETY: // - `&raw mut (*slot).#member` is valid. @@ -432,6 +433,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // `mixed_site` ensures that the guard is not accessible to the user-controlled code. let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + let full_span = kind.span(); let init = match kind { InitializerKind::Value { value, .. } => { @@ -440,14 +442,13 @@ fn init_fields(fields: &Punctuated, pinned: bool) - .map(|(_, value)| quote!(#value)) .unwrap_or_else(|| quote!(#member)); - quote! { + quote_spanned! { full_span => #(#attrs)* let mut #guard = #slot.write(#value); - } } InitializerKind::Init { value, .. } => { - quote! { + quote_spanned! { full_span => #(#attrs)* let mut #guard = #slot.init(#value)?; } @@ -458,7 +459,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // A tuple field has no name that could be bound here (the `_0` identifiers are considered // implementation detail and not user-facing). let binding = match member { - Member::Named(ident) => quote! { + Member::Named(ident) => quote_spanned! { span => #(#cfgs)* // Allow `non_snake_case` since the same warning is going to be reported for the // struct field. From dfb6a037fd586f1ffcba3b143dab58f5c88294d1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 16 Sep 2026 19:41:30 +0100 Subject: [PATCH 0537/2286] rust: pin-init: internal: use span of `Index` for generated identifiers Use the span of `Index` for generated identifiers, so diagnostics can point to the span of the index. Link: https://patch.msgid.link/20260916184131.435059-1-gary@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index ed2c78f0658f30..0b5c28b2109152 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -41,7 +41,7 @@ impl MemberExt for Member { fn as_ident(&self) -> Ident { match self { Member::Named(ident) => ident.clone(), - Member::Unnamed(Index { index, .. }) => format_ident!("_{index}"), + Member::Unnamed(Index { index, span }) => format_ident!("_{index}", span = *span), } } From 5e3d33c3fdf3761cc95fcdd815259493b803fa4a Mon Sep 17 00:00:00 2001 From: Dhruvin Rajpura Date: Sun, 30 Aug 2026 18:10:25 +0800 Subject: [PATCH 0538/2286] dt-bindings: thermal: qcom,pm8775-mbg-tm: Document PMAU0102 Document MBG temperature monitor on PMAU0102 which is programming-model compatible with the one on PM8775. Signed-off-by: Dhruvin Rajpura Signed-off-by: Shawn Guo Signed-off-by: Daniel Lezcano Reviewed-by: Bartosz Golaszewski Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260830101025.191697-1-shengchao.guo@oss.qualcomm.com --- .../devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml index 2e084d040625c0..ef8361a553891f 100644 --- a/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom,pm8775-mbg-tm.yaml @@ -21,7 +21,12 @@ description: properties: compatible: - const: qcom,pm8775-mbg-tm + oneOf: + - const: qcom,pm8775-mbg-tm + - items: + - enum: + - qcom,pmau0102-mbg-tm + - const: qcom,pm8775-mbg-tm reg: maxItems: 1 From fb345025f161d17fe7c8a6048c7e80e467fa8ddf Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:46 +0100 Subject: [PATCH 0539/2286] ACPI: CPPC: Validate the _CPC package header The _CPC NumEntries field includes every package element, including NumEntries and Revision. acpi_cppc_processor_probe() nevertheless reads those first two elements before checking that they exist and trusts NumEntries when walking the remaining elements. Reject packages with fewer than two elements and NumEntries values which exceed the package count before accessing Revision or iterating over register descriptors. Although the specification defines NumEntries as the number of package elements, tolerate additional trailing elements because ignoring them is safe and avoids rejecting padded firmware. Revision is specified as a BYTE, but the parser assigns its 64-bit AML Integer to an unsigned int before validating it. Reject values above U8_MAX before conversion so, for example, 0x100000004 cannot truncate to revision 4. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Link: https://sashiko.dev/#/patchset/20260808082644.1251332-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-2-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index fef54fcd00b78f..3b8cdf88e31da7 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -793,6 +793,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } out_obj = (union acpi_object *) output.pointer; + if (out_obj->package.count < 2) { + pr_debug("Unexpected _CPC package count (%u) for CPU:%d\n", + out_obj->package.count, pr->id); + goto out_buf_free; + } cpc_ptr = kzalloc_obj(struct cpc_desc); if (!cpc_ptr) { @@ -803,12 +808,15 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) /* First entry is NumEntries. */ cpc_obj = &out_obj->package.elements[0]; if (cpc_obj->type == ACPI_TYPE_INTEGER) { - num_ent = cpc_obj->integer.value; - if (num_ent <= 1) { - pr_debug("Unexpected _CPC NumEntries value (%d) for CPU:%d\n", - num_ent, pr->id); + if (cpc_obj->integer.value < 2 || + cpc_obj->integer.value > out_obj->package.count) { + pr_debug("Invalid _CPC NumEntries (%llu) for package count (%u) on CPU:%d\n", + cpc_obj->integer.value, out_obj->package.count, + pr->id); goto out_free; } + + num_ent = cpc_obj->integer.value; } else { pr_debug("Unexpected _CPC NumEntries entry type (%d) for CPU:%d\n", cpc_obj->type, pr->id); @@ -818,6 +826,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) /* Second entry should be revision. */ cpc_obj = &out_obj->package.elements[1]; if (cpc_obj->type == ACPI_TYPE_INTEGER) { + if (cpc_obj->integer.value > U8_MAX) { + pr_debug("Invalid _CPC Revision (%llu) for CPU:%d\n", + cpc_obj->integer.value, pr->id); + ret = -EINVAL; + goto out_free; + } cpc_rev = cpc_obj->integer.value; } else { pr_debug("Unexpected _CPC Revision entry type (%d) for CPU:%d\n", From 32299b29a887ed5615695f42a19cd8a843903146 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:47 +0100 Subject: [PATCH 0540/2286] ACPI: CPPC: Validate _CPC entry and control semantics Writable _CPC controls are Register descriptors encoded as Buffer objects. Integer entries represent fixed values or legacy placeholders for unsupported optional registers; Autonomous Selection Integer 1 is the special immutable form which enables operation without Desired Performance. The parser accepts arbitrary object types and cpc_write() assumes that its argument contains a GAS. Malformed firmware can therefore make it interpret an Integer union member as a register. Validate the portion of each encoding consumed by the driver: bound Integer capabilities stored in u32 fields to U32_MAX, and require Buffer entries to start with a complete Generic Register descriptor with the expected header. Do not restrict an Integer Counter Wraparound Time. Both AML Integers and the kernel interface are 64-bit, despite the DWORD annotation in the ACPI table. Continue tolerating Integer 0 placeholders for absent optional Buffer registers to avoid breaking legacy platforms. Report their invalid encoding as a firmware bug. Retain type checks in cpc_write() as defense in depth. Reject an attempt to disable immutable Autonomous Selection instead of silently applying only the EPP part of the request. Capability registers are read into u64 temporaries but exposed through u32 fields. Reject values above U32_MAX instead of allowing them to be truncated. In particular, a truncated Highest Performance value can become a zero divisor in the performance-to-frequency conversion. Enforce the required ordering from Highest through Nominal, Lowest Nonlinear, and Lowest Performance, and constrain a present Guaranteed Performance to the inclusive Lowest-to-Nominal range. Also reject reversed frequency anchors and unequal frequency anchors with identical performance anchors. Those invalid tuples otherwise make affine-conversion differences wrap or divide by zero. Check mandatory object presence separately from the Integer-zero convention for absent optional fields. Reject Minimum and Maximum Performance readbacks above U32_MAX before narrowing them. A truncated minimum could otherwise become an explicit zero update when cppc-cpufreq writes the initial controls back to the platform. Minimum Performance also defines zero as a real no-limit value, but the exported cppc_set_perf() interface historically used zero to omit a bound. Add a minimum-valid flag so callers can request zero without changing that legacy convention. Set it when reading a supported Minimum Performance register, preserving zero across cppc-cpufreq initialization and exit. Keep zero-as-omit for Maximum Performance, whose no-limit encoding is all ones, not zero. Initialize amd-pstate's control structure with the new flag clear: its shared-memory backend does not record bios_min_perf, so its zero-valued restore requests must remain omitted. Performance Limited is listed as a required Buffer, but the interface does not depend on it to control performance and the specification permits a platform with no limiting indication to always report zero. Preserve compatibility with firmware that represents that case using a NULL register descriptor instead of disabling CPPC entirely. Emit an error when a present _CPC package fails parsing or initialization so such firmware and resource failures no longer silently suppress cpufreq. Initialize malformed-package failures to -EINVAL and preserve specific allocation, mapping, and unsupported-access errors in that diagnostic. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Suggested-by: Rafael J. Wysocki Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-3-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 183 +++++++++++++++++++++++++++++++---- drivers/cpufreq/amd-pstate.c | 10 +- include/acpi/cppc_acpi.h | 2 + 3 files changed, 169 insertions(+), 26 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3b8cdf88e31da7..965ac29ab8b1e1 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -129,6 +129,21 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); !!(cpc)->cpc_entry.int_value : \ !IS_NULL_REG(&(cpc)->cpc_entry.reg)) +static bool cpc_is_writable(const struct cpc_register_resource *cpc) +{ + return cpc->type == ACPI_TYPE_BUFFER && + !IS_NULL_REG(&cpc->cpc_entry.reg); +} + +static bool cpc_entry_present(const struct cpc_register_resource *cpc) +{ + if (cpc->type == ACPI_TYPE_INTEGER) + return true; + + return cpc->type == ACPI_TYPE_BUFFER && + !IS_NULL_REG(&cpc->cpc_entry.reg); +} + /* * Each bit indicates the optionality of the register in per-cpu * cpc_regs[] with the corresponding index. 0 means mandatory and 1 @@ -142,6 +157,36 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); */ #define IS_OPTIONAL_CPC_REG(reg_idx) (REG_OPTIONAL & (1U << (reg_idx))) +static bool cpc_integer_entry_valid(unsigned int reg_idx, u64 value, + bool *legacy_null) +{ + *legacy_null = false; + + switch (reg_idx) { + case HIGHEST_PERF: + case NOMINAL_PERF: + case LOW_NON_LINEAR_PERF: + case LOWEST_PERF: + case REFERENCE_PERF: + case LOWEST_FREQ: + case NOMINAL_FREQ: + return value <= U32_MAX; + case CTR_WRAP_TIME: + /* AML Integers and the kernel interface are both 64-bit. */ + return true; + case AUTO_SEL_ENABLE: + return value <= 1; + case DESIRED_PERF: + /* Validated against Autonomous Selection after parsing. */ + *legacy_null = value == 0; + return *legacy_null; + default: + /* Tolerate legacy Integer 0 placeholders for absent options. */ + *legacy_null = value == 0 && IS_OPTIONAL_CPC_REG(reg_idx); + return *legacy_null; + } +} + /* * Arbitrary Retries in case the remote processor is slow to respond * to PCC commands. Keeping it high enough to cover emulators where @@ -150,6 +195,8 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); #define NUM_RETRIES 500ULL #define OVER_16BTS_MASK ~0xFFFFULL +#define CPC_GENERIC_REGISTER_DESCRIPTOR 0x82 +#define CPC_GENERIC_REGISTER_LENGTH (sizeof(struct cpc_reg) - 3) #define define_one_cppc_ro(_name) \ static struct kobj_attribute _name = \ @@ -773,8 +820,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) acpi_handle handle = pr->handle; unsigned int num_ent, i, cpc_rev; int pcc_subspace_id = -1; + bool cpc_present = false; acpi_status status; int ret = -ENODATA; + int err; if (!osc_sb_cppc2_support_acked) { pr_debug("CPPC v2 _OSC not acked\n"); @@ -791,6 +840,8 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) ret = -ENODEV; goto out_buf_free; } + cpc_present = true; + ret = -EINVAL; out_obj = (union acpi_object *) output.pointer; if (out_obj->package.count < 2) { @@ -871,11 +922,38 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) cpc_obj = &out_obj->package.elements[i]; if (cpc_obj->type == ACPI_TYPE_INTEGER) { - cpc_ptr->cpc_regs[i-2].type = ACPI_TYPE_INTEGER; - cpc_ptr->cpc_regs[i-2].cpc_entry.int_value = cpc_obj->integer.value; + bool legacy_null; + + if (!cpc_integer_entry_valid(i - 2, + cpc_obj->integer.value, + &legacy_null)) { + pr_debug("Invalid Integer _CPC register %u for CPU:%d\n", + i - 2, pr->id); + ret = -EINVAL; + goto out_free; + } + if (legacy_null) + pr_warn_once(FW_BUG "_CPC register %u uses Integer 0 for an absent Buffer\n", + i - 2); + cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_INTEGER; + cpc_ptr->cpc_regs[i - 2].cpc_entry.int_value = cpc_obj->integer.value; } else if (cpc_obj->type == ACPI_TYPE_BUFFER) { + if (cpc_obj->buffer.length < sizeof(*gas_t)) { + pr_debug("Invalid register descriptor for CPU:%d\n", + pr->id); + ret = -EINVAL; + goto out_free; + } + gas_t = (struct cpc_reg *) cpc_obj->buffer.pointer; + if (gas_t->descriptor != CPC_GENERIC_REGISTER_DESCRIPTOR || + gas_t->length != CPC_GENERIC_REGISTER_LENGTH) { + pr_debug("Invalid register resource for CPU:%d\n", + pr->id); + ret = -EINVAL; + goto out_free; + } /* * The PCC Subspace index is encoded inside @@ -886,8 +964,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (gas_t->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { if (pcc_subspace_id < 0) { pcc_subspace_id = gas_t->access_width; - if (pcc_data_alloc(pcc_subspace_id)) + err = pcc_data_alloc(pcc_subspace_id); + if (err) { + ret = err; goto out_free; + } } else if (pcc_subspace_id != gas_t->access_width) { pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", pr->id); @@ -900,14 +981,18 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); + ret = -EOPNOTSUPP; if (!cpc_supported_by_cpu()) goto out_free; + ret = -EINVAL; } access_width = GET_BIT_WIDTH(gas_t) / 8; addr = ioremap(gas_t->address, access_width); - if (!addr) + if (!addr) { + ret = -ENOMEM; goto out_free; + } cpc_ptr->cpc_regs[i-2].sys_mem_vaddr = addr; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { @@ -929,14 +1014,17 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); + ret = -EOPNOTSUPP; if (!cpc_supported_by_cpu()) goto out_free; + ret = -EINVAL; } } else { if (gas_t->space_id != ACPI_ADR_SPACE_FIXED_HARDWARE || !cpc_ffh_supported()) { /* Support only PCC, SystemMemory, SystemIO, and FFH type regs. */ pr_debug("Unsupported register type (%d) in _CPC\n", gas_t->space_id); + ret = -EOPNOTSUPP; goto out_free; } } @@ -961,15 +1049,35 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_subspace_id; + /* + * Performance Limited is required by the specification, but tolerate a + * NULL descriptor used by firmware which cannot report limiting events. + * CPPC control does not depend on this status. + */ + for (i = 0; i < num_ent - 2; i++) { + if (i != DESIRED_PERF && i != PERF_LIMITED && + !IS_OPTIONAL_CPC_REG(i) && + !cpc_entry_present(&cpc_ptr->cpc_regs[i])) { + pr_debug("CPU:%d lacks mandatory _CPC register %u\n", + pr->id, i); + ret = -EINVAL; + goto out_free; + } + } + /* * In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional * only when AUTO_SEL_ENABLE is supported. */ - if (!CPC_SUPPORTED(&cpc_ptr->cpc_regs[DESIRED_PERF]) && + if (!cpc_is_writable(&cpc_ptr->cpc_regs[DESIRED_PERF]) && (!osc_sb_cppc2_support_acked || - !CPC_SUPPORTED(&cpc_ptr->cpc_regs[AUTO_SEL_ENABLE]))) - pr_warn("Desired perf. register is mandatory if CPPC v2 is not supported " - "or autonomous selection is disabled\n"); + cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].type != ACPI_TYPE_INTEGER || + cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].cpc_entry.int_value != 1)) { + pr_debug("CPU:%d lacks a writable Desired Performance register\n", + pr->id); + ret = -EINVAL; + goto out_free; + } /* * Initialize the remaining cpc_regs as unsupported. @@ -1037,6 +1145,8 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) kfree(cpc_ptr); out_buf_free: + if (cpc_present) + pr_err("CPU%d: failed to initialize _CPC: %d\n", pr->id, ret); kfree(output.pointer); return ret; } @@ -1217,11 +1327,18 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) u64 prev_val; void __iomem *vaddr = NULL; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); - struct cpc_reg *reg = ®_res->cpc_entry.reg; + struct cpc_reg *reg; struct cpc_desc *cpc_desc; unsigned long flags; bool locked = false; + if (reg_res->type != ACPI_TYPE_BUFFER) + return -EOPNOTSUPP; + + reg = ®_res->cpc_entry.reg; + if (IS_NULL_REG(reg)) + return -EOPNOTSUPP; + size = GET_BIT_WIDTH(reg); if (IS_ENABLED(CONFIG_HAS_IOPORT) && @@ -1364,7 +1481,9 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs reg_idx, u64 *val) reg = &cpc_desc->cpc_regs[reg_idx]; - if ((reg->type == ACPI_TYPE_INTEGER && IS_OPTIONAL_CPC_REG(reg_idx) && + /* Desired may be absent for immutable autonomous selection. */ + if ((reg->type == ACPI_TYPE_INTEGER && + (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx == DESIRED_PERF) && !reg->cpc_entry.int_value) || (reg->type != ACPI_TYPE_INTEGER && IS_NULL_REG(®->cpc_entry.reg))) { pr_debug("CPC register is not supported\n"); @@ -1415,7 +1534,7 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val) reg = &cpc_desc->cpc_regs[reg_idx]; /* if a register is writeable, it must be a buffer and not null */ - if ((reg->type != ACPI_TYPE_BUFFER) || IS_NULL_REG(®->cpc_entry.reg)) { + if (!cpc_is_writable(reg)) { pr_debug("CPC register is not supported\n"); return -EOPNOTSUPP; } @@ -1505,7 +1624,7 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) struct cpc_register_resource *highest_reg, *lowest_reg, *lowest_non_linear_reg, *nominal_reg, *reference_reg, *guaranteed_reg, *low_freq_reg = NULL, *nom_freq_reg = NULL; - u64 high, low, guaranteed, nom, ref, min_nonlinear, + u64 high, low, guaranteed = 0, nom, ref, min_nonlinear, low_f = 0, nom_f = 0; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data = NULL; @@ -1588,7 +1707,12 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) goto out_err; perf_caps->lowest_nonlinear_perf = min_nonlinear; - if (!high || !low || !nom || !ref || !min_nonlinear) { + if (!high || !low || !nom || !ref || !min_nonlinear || + high > U32_MAX || low > U32_MAX || guaranteed > U32_MAX || + nom > U32_MAX || ref > U32_MAX || min_nonlinear > U32_MAX || + high < nom || nom < min_nonlinear || min_nonlinear < low || + (CPC_SUPPORTED(guaranteed_reg) && + (guaranteed < low || guaranteed > nom))) { ret = -EFAULT; goto out_err; } @@ -1605,6 +1729,14 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) if (ret) goto out_err; } + /* Require ordered anchors and a nonzero slope when frequencies differ. */ + if (low_f > U32_MAX || nom_f > U32_MAX || + (low_f && nom_f && + (nom_f < low_f || nom < low || + (nom_f != low_f && nom == low)))) { + ret = -EFAULT; + goto out_err; + } perf_caps->lowest_freq = low_f; perf_caps->nominal_freq = nom_f; @@ -1779,6 +1911,9 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; epp_set_reg = &cpc_desc->cpc_regs[ENERGY_PERF]; + if (!enable && auto_sel_reg->type == ACPI_TYPE_INTEGER && + auto_sel_reg->cpc_entry.int_value == 1) + return -EOPNOTSUPP; epp_ffh_sysmem = CPC_SUPPORTED(epp_set_reg) && (CPC_IN_FFH(epp_set_reg) || CPC_IN_SYSTEM_MEMORY(epp_set_reg)); @@ -1791,13 +1926,13 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) return -ENODEV; } - if (CPC_SUPPORTED(auto_sel_reg)) { + if (cpc_is_writable(auto_sel_reg)) { ret = cpc_write(cpu, auto_sel_reg, enable); if (ret) return ret; } - if (CPC_SUPPORTED(epp_set_reg)) { + if (cpc_is_writable(epp_set_reg)) { ret = cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); if (ret) return ret; @@ -1996,6 +2131,7 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) max_perf_reg = &cpc_desc->cpc_regs[MAX_PERF]; energy_perf_reg = &cpc_desc->cpc_regs[ENERGY_PERF]; auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; + perf_ctrls->min_perf_valid = false; /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg) || @@ -2020,6 +2156,10 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) ret = cpc_read(cpu, max_perf_reg, &max); if (ret) goto out_err; + if (max > U32_MAX) { + ret = -EFAULT; + goto out_err; + } } perf_ctrls->max_perf = max; @@ -2027,6 +2167,11 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) ret = cpc_read(cpu, min_perf_reg, &min); if (ret) goto out_err; + if (min > U32_MAX) { + ret = -EFAULT; + goto out_err; + } + perf_ctrls->min_perf_valid = true; } perf_ctrls->min_perf = min; @@ -2113,12 +2258,8 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) if (CPC_SUPPORTED(desired_reg)) cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); - /* - * Only write if min_perf and max_perf not zero. Some drivers pass zero - * value to min and max perf, but they don't mean to set the zero value, - * they just don't want to write to those registers. - */ - if (perf_ctrls->min_perf && CPC_SUPPORTED(min_perf_reg)) + if (CPC_SUPPORTED(min_perf_reg) && + (perf_ctrls->min_perf || perf_ctrls->min_perf_valid)) cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); if (perf_ctrls->max_perf && CPC_SUPPORTED(max_perf_reg)) cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 8bfd46d60843dd..6ee2d43b7b9521 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -544,7 +544,11 @@ static int shmem_update_perf(struct cpufreq_policy *policy, u8 min_perf, u8 des_perf, u8 max_perf, u8 epp, bool fast_switch) { struct amd_cpudata *cpudata = policy->driver_data; - struct cppc_perf_ctrls perf_ctrls; + struct cppc_perf_ctrls perf_ctrls = { + .max_perf = max_perf, + .min_perf = min_perf, + .desired_perf = des_perf, + }; u64 value, prev; int ret; @@ -577,10 +581,6 @@ static int shmem_update_perf(struct cpufreq_policy *policy, u8 min_perf, if (value == prev) return 0; - perf_ctrls.max_perf = max_perf; - perf_ctrls.min_perf = min_perf; - perf_ctrls.desired_perf = des_perf; - ret = cppc_set_perf(cpudata->cpu, &perf_ctrls); if (ret) return ret; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 94a6277edab273..edf0f6ffb4378f 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -141,6 +141,8 @@ struct cppc_perf_ctrls { u32 desired_perf; u32 energy_perf; bool auto_sel; + /* Allow an explicit zero minimum; otherwise zero omits the update. */ + bool min_perf_valid; }; struct cppc_perf_fb_ctrs { From 3685d9829c7161a05d338cb3b3367c6a76d38ed8 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:48 +0100 Subject: [PATCH 0541/2286] ACPI: CPPC: Propagate performance-control write errors cppc_set_perf() can skip malformed controls, discard cpc_write() failures, and report success without programming the requested performance tuple. Return every control-write error to the caller and honor the explicit Minimum Performance validity flag, so an explicit zero minimum remains distinct from a legacy omitted bound. Validate every requested PCC field before changing either a direct control or the shared payload; after that check, PCC staging cannot fail partway through a tuple. Multiple Phase-I callers may set the pending flag to true while holding the shared side of pcc_lock. Mark that intentional same-value store with WRITE_ONCE(). A layout which mixes PCC with directly accessed controls needs stronger ordering. A fallible direct write cannot safely run alongside another CPU's staged PCC tuple: if it fails after changing a direct register, neither submitting nor discarding the shared batch can preserve the other request. Serialize the complete mixed transaction with the exclusive PCC lock. Drain an older pending batch before changing a direct control. Check the preceding PCC command for completion, then program the direct and PCC portions and submit the new command synchronously. Use the mixed-layout synchronization even when the current request omits its PCC-backed bounds, so a direct-only update cannot race a prior PCC command. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-4-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 263 ++++++++++++++++++++++++++++++--------- 1 file changed, 203 insertions(+), 60 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 965ac29ab8b1e1..74feb05195a16a 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -240,6 +240,19 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); (reg)->space_id != ACPI_ADR_SPACE_PLATFORM_COMM) ? \ (8 << ((reg)->access_width - 1)) : (reg)->bit_width) +static bool cpc_pcc_write_supported(const struct cpc_register_resource *reg) +{ + switch (GET_BIT_WIDTH(®->cpc_entry.reg)) { + case 8: + case 16: + case 32: + case 64: + return true; + default: + return false; + } +} + /* Shift and apply the mask for CPC reads/writes */ #define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \ GENMASK(((reg)->bit_width) - 1, 0)) @@ -380,13 +393,34 @@ static int check_pcc_chan(int pcc_ss_id, bool chk_err_bit) return ret; } +static void cppc_complete_pcc_write(struct cppc_pcc_data *pcc_ss_data, + int ret) +{ + int i; + + if (unlikely(ret)) { + for_each_possible_cpu(i) { + struct cpc_desc *desc = per_cpu(cpc_desc_ptr, i); + + if (!desc) + continue; + + if (desc->write_cmd_id == pcc_ss_data->pcc_write_cnt) + desc->write_cmd_status = ret; + } + } + + pcc_ss_data->pcc_write_cnt++; + wake_up_all(&pcc_ss_data->pcc_write_wait_q); +} + /* * This function transfers the ownership of the PCC to the platform * So it must be called while holding write_lock(pcc_lock) */ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) { - int ret = -EIO, i; + int ret = -EIO; struct cppc_pcc_data *pcc_ss_data = pcc_data[pcc_ss_id]; struct acpi_pcct_shared_memory __iomem *generic_comm_base = pcc_ss_data->pcc_channel->shmem; @@ -478,21 +512,8 @@ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) mbox_client_txdone(pcc_ss_data->pcc_channel->mchan, ret); end: - if (cmd == CMD_WRITE) { - if (unlikely(ret)) { - for_each_possible_cpu(i) { - struct cpc_desc *desc = per_cpu(cpc_desc_ptr, i); - - if (!desc) - continue; - - if (desc->write_cmd_id == pcc_ss_data->pcc_write_cnt) - desc->write_cmd_status = ret; - } - } - pcc_ss_data->pcc_write_cnt++; - wake_up_all(&pcc_ss_data->pcc_write_wait_q); - } + if (cmd == CMD_WRITE) + cppc_complete_pcc_write(pcc_ss_data, ret); return ret; } @@ -2211,7 +2232,9 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) struct cpc_register_resource *desired_reg, *min_perf_reg, *max_perf_reg; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; - bool regs_in_pcc; + bool desired_update, min_update, max_update; + bool desired_pcc, min_pcc, max_pcc, pcc_update; + bool pcc_layout, direct_layout, mixed_layout; int ret = 0; if (!cpc_desc) { @@ -2222,50 +2245,168 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; min_perf_reg = &cpc_desc->cpc_regs[MIN_PERF]; max_perf_reg = &cpc_desc->cpc_regs[MAX_PERF]; - regs_in_pcc = CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || - CPC_IN_PCC(max_perf_reg); + desired_update = cpc_is_writable(desired_reg); + min_update = cpc_is_writable(min_perf_reg) && + (perf_ctrls->min_perf || perf_ctrls->min_perf_valid); + max_update = cpc_is_writable(max_perf_reg) && + perf_ctrls->max_perf; + desired_pcc = desired_update && CPC_IN_PCC(desired_reg); + min_pcc = min_update && CPC_IN_PCC(min_perf_reg); + max_pcc = max_update && CPC_IN_PCC(max_perf_reg); + pcc_update = desired_pcc || min_pcc || max_pcc; + pcc_layout = (cpc_is_writable(desired_reg) && CPC_IN_PCC(desired_reg)) || + (cpc_is_writable(min_perf_reg) && CPC_IN_PCC(min_perf_reg)) || + (cpc_is_writable(max_perf_reg) && CPC_IN_PCC(max_perf_reg)); + direct_layout = (cpc_is_writable(desired_reg) && + !CPC_IN_PCC(desired_reg)) || + (cpc_is_writable(min_perf_reg) && + !CPC_IN_PCC(min_perf_reg)) || + (cpc_is_writable(max_perf_reg) && + !CPC_IN_PCC(max_perf_reg)); + mixed_layout = pcc_layout && direct_layout; + + /* Do not modify any control if a requested PCC field cannot be staged. */ + if ((desired_pcc && !cpc_pcc_write_supported(desired_reg)) || + (min_pcc && !cpc_pcc_write_supported(min_perf_reg)) || + (max_pcc && !cpc_pcc_write_supported(max_perf_reg))) + return -EFAULT; - /* - * This is Phase-I where we want to write to CPC registers - * -> We want all CPUs to be able to execute this phase in parallel - * - * Since read_lock can be acquired by multiple CPUs simultaneously we - * achieve that goal here - */ - if (regs_in_pcc) { + if (mixed_layout || pcc_update) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; } pcc_ss_data = pcc_data[pcc_ss_id]; - down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */ + if (!pcc_ss_data) + return -ENODEV; + } + + /* + * A mixed layout cannot batch fallible direct writes safely: another + * CPU's staged PCC values may no longer match if a direct write fails. + * Serialize the complete mixed transaction and drain an older batch + * before changing a direct control. + */ + if (mixed_layout) { + down_write(&pcc_ss_data->pcc_lock); + if (pcc_ss_data->pending_pcc_write_cmd) { + ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); + if (ret) + goto out_mixed_unlock; + } + if (pcc_ss_data->platform_owns_pcc) { ret = check_pcc_chan(pcc_ss_id, false); - if (ret) { - up_read(&pcc_ss_data->pcc_lock); + if (ret) + goto out_mixed_unlock; + } + + if (desired_update && !desired_pcc) { + ret = cpc_write(cpu, desired_reg, + perf_ctrls->desired_perf); + if (ret) + goto out_mixed_unlock; + } + if (min_update && !min_pcc) { + ret = cpc_write(cpu, min_perf_reg, + perf_ctrls->min_perf); + if (ret) + goto out_mixed_unlock; + } + if (max_update && !max_pcc) { + ret = cpc_write(cpu, max_perf_reg, + perf_ctrls->max_perf); + if (ret) + goto out_mixed_unlock; + } + + if (desired_pcc) { + ret = cpc_write(cpu, desired_reg, + perf_ctrls->desired_perf); + if (ret) + goto out_mixed_unlock; + } + if (min_pcc) { + ret = cpc_write(cpu, min_perf_reg, + perf_ctrls->min_perf); + if (ret) + goto out_mixed_unlock; + } + if (max_pcc) { + ret = cpc_write(cpu, max_perf_reg, + perf_ctrls->max_perf); + if (ret) + goto out_mixed_unlock; + } + + if (pcc_update) { + WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true); + cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; + cpc_desc->write_cmd_status = 0; + ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); + } + +out_mixed_unlock: + up_write(&pcc_ss_data->pcc_lock); + return ret; + } + + /* A request without PCC updates has no payload to coordinate. */ + if (!pcc_update) { + if (desired_update) { + ret = cpc_write(cpu, desired_reg, + perf_ctrls->desired_perf); + if (ret) return ret; - } } - /* - * Update the pending_write to make sure a PCC CMD_READ will not - * arrive and steal the channel during the switch to write lock - */ - pcc_ss_data->pending_pcc_write_cmd = true; - cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; - cpc_desc->write_cmd_status = 0; + if (min_update) { + ret = cpc_write(cpu, min_perf_reg, + perf_ctrls->min_perf); + if (ret) + return ret; + } + if (max_update) + ret = cpc_write(cpu, max_perf_reg, + perf_ctrls->max_perf); + return ret; } - if (CPC_SUPPORTED(desired_reg)) - cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */ + if (pcc_ss_data->platform_owns_pcc) { + ret = check_pcc_chan(pcc_ss_id, false); + if (ret) + goto out_pcc_read_unlock; + } - if (CPC_SUPPORTED(min_perf_reg) && - (perf_ctrls->min_perf || perf_ctrls->min_perf_valid)) - cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); - if (perf_ctrls->max_perf && CPC_SUPPORTED(max_perf_reg)) - cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); + /* + * This is Phase-I where we want to write to CPC registers + * -> We want all CPUs to be able to execute this phase in parallel + * + * Since read_lock can be acquired by multiple CPUs simultaneously we + * achieve that goal here. + */ + if (desired_pcc) { + ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (ret) + goto out_pcc_read_unlock; + } - if (regs_in_pcc) - up_read(&pcc_ss_data->pcc_lock); /* END Phase-I */ + if (min_pcc) { + ret = cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); + if (ret) + goto out_pcc_read_unlock; + } + if (max_pcc) { + ret = cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); + if (ret) + goto out_pcc_read_unlock; + } + + /* Block a PCC read until the staged payload has been submitted. */ + WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true); + cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt; + cpc_desc->write_cmd_status = 0; + up_read(&pcc_ss_data->pcc_lock); /* END Phase-I */ /* * This is Phase-II where we transfer the ownership of PCC to Platform * @@ -2312,20 +2453,22 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) * case during a CMD_READ and if there are pending writes it delivers * the write command before servicing the read command */ - if (regs_in_pcc) { - if (down_write_trylock(&pcc_ss_data->pcc_lock)) {/* BEGIN Phase-II */ - /* Update only if there are pending write commands */ - if (pcc_ss_data->pending_pcc_write_cmd) - send_pcc_cmd(pcc_ss_id, CMD_WRITE); - up_write(&pcc_ss_data->pcc_lock); /* END Phase-II */ - } else - /* Wait until pcc_write_cnt is updated by send_pcc_cmd */ - wait_event(pcc_ss_data->pcc_write_wait_q, - cpc_desc->write_cmd_id != pcc_ss_data->pcc_write_cnt); - - /* send_pcc_cmd updates the status in case of failure */ - ret = cpc_desc->write_cmd_status; + if (down_write_trylock(&pcc_ss_data->pcc_lock)) {/* BEGIN Phase-II */ + /* Update only if there are pending write commands */ + if (pcc_ss_data->pending_pcc_write_cmd) + send_pcc_cmd(pcc_ss_id, CMD_WRITE); + up_write(&pcc_ss_data->pcc_lock); /* END Phase-II */ + } else { + /* Wait until pcc_write_cnt is updated by send_pcc_cmd */ + wait_event(pcc_ss_data->pcc_write_wait_q, + cpc_desc->write_cmd_id != pcc_ss_data->pcc_write_cnt); } + + /* send_pcc_cmd updates the status in case of failure */ + return cpc_desc->write_cmd_status; + +out_pcc_read_unlock: + up_read(&pcc_ss_data->pcc_lock); return ret; } EXPORT_SYMBOL_GPL(cppc_set_perf); From 50affbde6410825b7bd6c0f4d12dab40ec987021 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:49 +0100 Subject: [PATCH 0542/2286] ACPI: CPPC: Serialize PCC single-register payload updates The PCC doorbell protocol requires OSPM to confirm ownership of the shared subspace before placing a command and its payload there. cppc_set_reg_val_in_pcc() instead modifies the payload before taking pcc_lock. A concurrent command can consequently overwrite or consume the staged value, and OSPM can write the shared region while the platform still owns it. Reject a register width which cpc_write() cannot stage before taking the exclusive lock. Otherwise that operation can steal Phase II from an older valid performance batch and then abort it for an unrelated error. Take the PCC write lock first, wait for the previous command to complete, and keep the lock held while staging the value and submitting CMD_WRITE. This follows the ownership sequence in ACPI 6.5 Section 14.5 and the existing contract documented by send_pcc_cmd(). If ownership acquisition or staging fails, abort any older performance batch before dropping the exclusive lock. This advances its generation and wakes cppc_set_perf() callers which otherwise wait indefinitely for a command this path did not submit. The generation counter is local to each PCC subspace, while error completion scans the descriptors of every CPU. Match both the subspace and generation so failure in one subspace cannot poison a request using the same generation in another. Fixes: e05c75072c2e ("ACPI: CPPC: Add cppc_set_reg_val()") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-5-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 74feb05195a16a..42f3714f82ab4e 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -393,8 +393,8 @@ static int check_pcc_chan(int pcc_ss_id, bool chk_err_bit) return ret; } -static void cppc_complete_pcc_write(struct cppc_pcc_data *pcc_ss_data, - int ret) +static void cppc_complete_pcc_write(int pcc_ss_id, + struct cppc_pcc_data *pcc_ss_data, int ret) { int i; @@ -402,7 +402,8 @@ static void cppc_complete_pcc_write(struct cppc_pcc_data *pcc_ss_data, for_each_possible_cpu(i) { struct cpc_desc *desc = per_cpu(cpc_desc_ptr, i); - if (!desc) + if (!desc || + per_cpu(cpu_pcc_subspace_idx, i) != pcc_ss_id) continue; if (desc->write_cmd_id == pcc_ss_data->pcc_write_cnt) @@ -414,6 +415,18 @@ static void cppc_complete_pcc_write(struct cppc_pcc_data *pcc_ss_data, wake_up_all(&pcc_ss_data->pcc_write_wait_q); } +/* The caller must hold pcc_lock for write. */ +static void cppc_abort_pending_pcc_write(int pcc_ss_id, + struct cppc_pcc_data *pcc_ss_data, + int ret) +{ + if (!pcc_ss_data->pending_pcc_write_cmd) + return; + + pcc_ss_data->pending_pcc_write_cmd = false; + cppc_complete_pcc_write(pcc_ss_id, pcc_ss_data, ret); +} + /* * This function transfers the ownership of the PCC to the platform * So it must be called while holding write_lock(pcc_lock) @@ -513,7 +526,7 @@ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) end: if (cmd == CMD_WRITE) - cppc_complete_pcc_write(pcc_ss_data, ret); + cppc_complete_pcc_write(pcc_ss_id, pcc_ss_data, ret); return ret; } @@ -1520,23 +1533,36 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs reg_idx, u64 *val) static int cppc_set_reg_val_in_pcc(int cpu, struct cpc_register_resource *reg, u64 val) { int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); - struct cppc_pcc_data *pcc_ss_data = NULL; + struct cppc_pcc_data *pcc_ss_data; int ret; if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; } - - ret = cpc_write(cpu, reg, val); - if (ret) - return ret; + if (!cpc_pcc_write_supported(reg)) + return -EFAULT; pcc_ss_data = pcc_data[pcc_ss_id]; + if (!pcc_ss_data) + return -ENODEV; down_write(&pcc_ss_data->pcc_lock); + + ret = check_pcc_chan(pcc_ss_id, false); + if (ret) + goto out; + + ret = cpc_write(cpu, reg, val); + if (ret) + goto out; + /* after writing CPC, transfer the ownership of PCC to platform */ ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); + +out: + if (ret) + cppc_abort_pending_pcc_write(pcc_ss_id, pcc_ss_data, ret); up_write(&pcc_ss_data->pcc_lock); return ret; From 76e6914a843ed2bc2bea152c30cc7dd445d02276 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:50 +0100 Subject: [PATCH 0543/2286] ACPI: CPPC: Serialize PCC EPP payload updates cppc_set_epp_perf() stages Autonomous Selection and Energy Performance Preference in the PCC shared region before taking pcc_lock. The platform may still own the subspace, or a concurrent command may consume or overwrite only part of the new payload. Take the PCC write lock and wait for OSPM ownership before staging either control. Keep the lock held until the complete payload has been submitted with CMD_WRITE, so firmware cannot observe a mixed transaction. For a mixed PCC/non-PCC description, complete every fallible non-PCC write before staging PCC data. Cross-address-space updates cannot be atomic, but a non-PCC failure can no longer leave an unsent value in shared memory for a later PCC command to consume. Reject an unsupported PCC width before any non-PCC control is changed. Classify every writable control as either PCC or non-PCC. For SystemIO, require port-I/O support and an access within the port range. Require natural alignment on non-x86 platforms, whose port-I/O accessors use MMIO; x86 port instructions support unaligned addresses. Also require a field covering the complete access unit. cpc_write() performs a raw whole-port write, so any other geometry could update the wrong bits or corrupt an adjacent field. If ownership acquisition or PCC staging fails, abort any older pending performance batch before releasing the exclusive lock so its Phase-II waiters receive the error instead of sleeping indefinitely. This follows the PCC ownership sequence in ACPI 6.5 Section 14.5. Fixes: 7bc1fcd39901 ("ACPI: CPPC: Add AMD pstate energy performance preference cppc control") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-6-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 100 +++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 42f3714f82ab4e..2198376a6a9cda 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -260,6 +260,22 @@ static bool cpc_pcc_write_supported(const struct cpc_register_resource *reg) ((((val) & GENMASK(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) | \ ((prev_val) & ~(GENMASK(((reg)->bit_width) - 1, 0) << (reg)->bit_offset))) \ +static bool +cpc_system_io_write_supported(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + unsigned int access_width = GET_BIT_WIDTH(gas); + u64 access_size; + + if (!IS_ENABLED(CONFIG_HAS_IOPORT) || !CPC_IN_SYSTEM_IO(reg) || + (access_width != 8 && access_width != 16 && access_width != 32)) + return false; + + access_size = access_width / 8; + return !gas->bit_offset && gas->bit_width == access_width && + gas->address <= U16_MAX - (access_size - 1) && + (IS_ENABLED(CONFIG_X86) || IS_ALIGNED(gas->address, access_size)); +} static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) { const struct cpc_reg *gas = ®->cpc_entry.reg; @@ -1947,8 +1963,10 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) struct cpc_register_resource *auto_sel_reg; struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; - bool autosel_ffh_sysmem; - bool epp_ffh_sysmem; + bool auto_sel_pcc; + bool auto_sel_non_pcc; + bool epp_pcc; + bool epp_non_pcc; int ret; if (!cpc_desc) { @@ -1962,52 +1980,74 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) auto_sel_reg->cpc_entry.int_value == 1) return -EOPNOTSUPP; - epp_ffh_sysmem = CPC_SUPPORTED(epp_set_reg) && - (CPC_IN_FFH(epp_set_reg) || CPC_IN_SYSTEM_MEMORY(epp_set_reg)); - autosel_ffh_sysmem = CPC_SUPPORTED(auto_sel_reg) && - (CPC_IN_FFH(auto_sel_reg) || CPC_IN_SYSTEM_MEMORY(auto_sel_reg)); + auto_sel_pcc = cpc_is_writable(auto_sel_reg) && + CPC_IN_PCC(auto_sel_reg); + epp_pcc = cpc_is_writable(epp_set_reg) && CPC_IN_PCC(epp_set_reg); + if ((auto_sel_pcc && !cpc_pcc_write_supported(auto_sel_reg)) || + (epp_pcc && !cpc_pcc_write_supported(epp_set_reg))) + return -EFAULT; + if ((cpc_is_writable(auto_sel_reg) && + CPC_IN_SYSTEM_IO(auto_sel_reg) && + !cpc_system_io_write_supported(auto_sel_reg)) || + (cpc_is_writable(epp_set_reg) && CPC_IN_SYSTEM_IO(epp_set_reg) && + !cpc_system_io_write_supported(epp_set_reg))) + return -EOPNOTSUPP; + + auto_sel_non_pcc = cpc_is_writable(auto_sel_reg) && !auto_sel_pcc; + epp_non_pcc = cpc_is_writable(epp_set_reg) && !epp_pcc; + + /* Complete fallible non-PCC writes before staging PCC data. */ + if (auto_sel_non_pcc) { + ret = cpc_write(cpu, auto_sel_reg, enable); + if (ret) + return ret; + } + if (epp_non_pcc) { + ret = cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); + if (ret) + return ret; + } - if (CPC_IN_PCC(epp_set_reg) || CPC_IN_PCC(auto_sel_reg)) { + if (epp_pcc || auto_sel_pcc) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu); return -ENODEV; } - if (cpc_is_writable(auto_sel_reg)) { + pcc_ss_data = pcc_data[pcc_ss_id]; + if (!pcc_ss_data) + return -ENODEV; + + down_write(&pcc_ss_data->pcc_lock); + + ret = check_pcc_chan(pcc_ss_id, false); + if (ret) + goto out_unlock; + + if (auto_sel_pcc) { ret = cpc_write(cpu, auto_sel_reg, enable); if (ret) - return ret; + goto out_unlock; } - if (cpc_is_writable(epp_set_reg)) { + if (epp_pcc) { ret = cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); if (ret) - return ret; + goto out_unlock; } - pcc_ss_data = pcc_data[pcc_ss_id]; - - down_write(&pcc_ss_data->pcc_lock); /* after writing CPC, transfer the ownership of PCC to platform */ ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); - up_write(&pcc_ss_data->pcc_lock); - } else if (osc_cpc_flexible_adr_space_confirmed && - (epp_ffh_sysmem || autosel_ffh_sysmem)) { - if (autosel_ffh_sysmem) { - ret = cpc_write(cpu, auto_sel_reg, enable); - if (ret) - return ret; - } - if (epp_ffh_sysmem) { - ret = cpc_write(cpu, epp_set_reg, - perf_ctrls->energy_perf); - if (ret) - return ret; - } +out_unlock: + if (ret) + cppc_abort_pending_pcc_write(pcc_ss_id, pcc_ss_data, ret); + up_write(&pcc_ss_data->pcc_lock); + } else if (epp_non_pcc || auto_sel_non_pcc) { + ret = 0; } else { - ret = -ENOTSUPP; - pr_debug("_CPC in PCC/FFH/SystemMemory are not supported\n"); + ret = -EOPNOTSUPP; + pr_debug("No writable EPP controls for CPU:%d\n", cpu); } return ret; From 27954e29d7de66e65ef1be4ce5a50505066c9bb6 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:51 +0100 Subject: [PATCH 0544/2286] ACPI: CPPC: Release CPC descriptors through kobject The CPC descriptor embeds the kobject used for its sysfs directory, but it has no release callback and processor exit frees the descriptor immediately after kobject_put(). It also unmaps register resources and releases PCC state before sysfs removal has drained active attribute callbacks. Provide a release callback which unmaps and frees the descriptor, and use the same cleanup helper before kobject initialization. Once initialized, let kobject_put() own descriptor cleanup so its error path cannot free the object twice. On processor exit, first unpublish the per-CPU descriptor and remove its sysfs directory, then release PCC state and drop the kobject reference. This keeps all resources valid while sysfs callbacks are active and also works with delayed kobject release. The frequency-invariance teardown can run after processor removal has unpublished a descriptor. Remember which PCC work items were initialized so policy exit drains them without looking up that descriptor. Also make the counter-transport query tolerate a missing descriptor. Fixes: 158c998ea44b ("ACPI / CPPC: add sysfs support to compute delivered performance") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-7-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 52 +++++++++++++++++++++------------- drivers/cpufreq/cppc_cpufreq.c | 15 +++++----- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 2198376a6a9cda..0f0cce3830076c 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -373,7 +373,27 @@ static struct attribute *cppc_attrs[] = { }; ATTRIBUTE_GROUPS(cppc); +static void cppc_free_desc(struct cpc_desc *cpc_ptr) +{ + unsigned int i; + + for (i = 2; i < cpc_ptr->num_entries; i++) { + void __iomem *addr = cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr; + + if (addr) + iounmap(addr); + } + + kfree(cpc_ptr); +} + +static void cppc_kobj_release(struct kobject *kobj) +{ + cppc_free_desc(to_cpc_desc(kobj)); +} + static const struct kobj_type cppc_ktype = { + .release = cppc_kobj_release, .sysfs_ops = &kobj_sysfs_ops, .default_groups = cppc_groups, }; @@ -1178,21 +1198,14 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (ret) { per_cpu(cpc_desc_ptr, pr->id) = NULL; kobject_put(&cpc_ptr->kobj); - goto out_free; + goto out_buf_free; } kfree(output.pointer); return 0; out_free: - /* Free all the mapped sys mem areas for this CPU */ - for (i = 2; i < cpc_ptr->num_entries; i++) { - void __iomem *addr = cpc_ptr->cpc_regs[i-2].sys_mem_vaddr; - - if (addr) - iounmap(addr); - } - kfree(cpc_ptr); + cppc_free_desc(cpc_ptr); out_buf_free: if (cpc_present) @@ -1211,10 +1224,14 @@ EXPORT_SYMBOL_GPL(acpi_cppc_processor_probe); void acpi_cppc_processor_exit(struct acpi_processor *pr) { struct cpc_desc *cpc_ptr; - unsigned int i; - void __iomem *addr; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); + cpc_ptr = per_cpu(cpc_desc_ptr, pr->id); + if (cpc_ptr) { + per_cpu(cpc_desc_ptr, pr->id) = NULL; + kobject_del(&cpc_ptr->kobj); + } + if (pcc_ss_id >= 0 && pcc_data[pcc_ss_id]) { if (pcc_data[pcc_ss_id]->pcc_channel_acquired) { pcc_data[pcc_ss_id]->refcount--; @@ -1225,20 +1242,12 @@ void acpi_cppc_processor_exit(struct acpi_processor *pr) } } } + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; - cpc_ptr = per_cpu(cpc_desc_ptr, pr->id); if (!cpc_ptr) return; - /* Free all the mapped sys mem areas for this CPU */ - for (i = 2; i < cpc_ptr->num_entries; i++) { - addr = cpc_ptr->cpc_regs[i-2].sys_mem_vaddr; - if (addr) - iounmap(addr); - } - kobject_put(&cpc_ptr->kobj); - kfree(cpc_ptr); } EXPORT_SYMBOL_GPL(acpi_cppc_processor_exit); @@ -1822,6 +1831,9 @@ bool cppc_perf_ctrs_in_pcc_cpu(unsigned int cpu) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); + if (!cpc_desc) + return false; + return CPC_IN_PCC(&cpc_desc->cpc_regs[DELIVERED_CTR]) || CPC_IN_PCC(&cpc_desc->cpc_regs[REFERENCE_CTR]) || CPC_IN_PCC(&cpc_desc->cpc_regs[CTR_WRAP_TIME]); diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 80893844353ca1..af46f913e907a1 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -41,6 +41,7 @@ MODULE_PARM_DESC(fie_disabled, "Disable Frequency Invariance Engine (FIE)"); /* Frequency invariance support */ struct cppc_freq_invariance { int cpu; + bool pcc_work_initialized; struct irq_work irq_work; struct kthread_work work; struct cppc_perf_fb_ctrs prev_perf_fb_ctrs; @@ -163,6 +164,7 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) if (cppc_perf_ctrs_in_pcc_cpu(cpu)) { kthread_init_work(&cppc_fi->work, cppc_scale_freq_workfn); init_irq_work(&cppc_fi->irq_work, cppc_irq_work); + cppc_fi->pcc_work_initialized = true; sftd = &cppc_sftd_pcc; } @@ -184,12 +186,8 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) } /* - * We free all the resources on policy's removal and not on CPU removal as the - * irq-work are per-cpu and the hotplug core takes care of flushing the pending - * irq-works (hint: smpcfd_dying_cpu()) on CPU hotplug. Even if the kthread-work - * fires on another CPU after the concerned CPU is removed, it won't harm. - * - * We just need to make sure to remove them all on policy->exit(). + * Drain work initialized by this policy even if processor removal has + * already unpublished the CPU's CPC descriptor. */ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy) { @@ -203,11 +201,12 @@ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy) topology_clear_scale_freq_source(SCALE_FREQ_SOURCE_CPPC, policy->related_cpus); for_each_cpu(cpu, policy->related_cpus) { - if (!cppc_perf_ctrs_in_pcc_cpu(cpu)) - continue; cppc_fi = &per_cpu(cppc_freq_inv, cpu); + if (!cppc_fi->pcc_work_initialized) + continue; irq_work_sync(&cppc_fi->irq_work); kthread_cancel_work_sync(&cppc_fi->work); + cppc_fi->pcc_work_initialized = false; } } From 968e29db97bbe45aecb5dac51ffbedbf62eb67de Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:52 +0100 Subject: [PATCH 0545/2286] ACPI: CPPC: Release PCC data after probe failures pcc_data_alloc() takes a per-processor reference while parsing the first PCC register. Every later probe failure currently frees the CPC descriptor without dropping that reference. Depending on where probe fails, this leaks an unacquired PCC object, an acquired mailbox channel, or an extra reference to a shared channel. Factor the teardown into pcc_data_put() so it handles both acquired and unacquired state, and invoke it on every failure after allocation. Allocation, reference updates, channel setup, and teardown of a shared subspace are also unsynchronized. Concurrent processor probe and removal can lose a reference update, free a live object, or request and initialize the same mailbox channel twice. Protect that lifecycle with a mutex and publish pcc_channel_acquired only after its lock and wait queue have been initialized. Do not store the temporary pcc_data_alloc() result in the eventual probe return value. A successful allocation must not make a later parsing failure run cleanup and then return success. A later processor-start step can fail after CPPC probe has published its descriptor. Reuse that descriptor when processor start is retried instead of resetting its PCC index and reparsing _CPC. This prevents the live descriptor from becoming detached from its PCC reference. The per-CPU PCC subspace index is zero-initialized. If probe returns before assigning it, a later processor exit can consequently drop the reference for subspace 0 even though this CPU never acquired one. Initialize the index to -1 before any probe return and only release it from exit after a CPC descriptor has been published. Fixes: 85b1407bf6d2 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subspace IDs") Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-8-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 153 ++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 52 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 0f0cce3830076c..2ad45725f3e0b1 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,7 @@ struct cppc_pcc_data { /* Array to represent the PCC channel per subspace ID */ static struct cppc_pcc_data *pcc_data[MAX_PCC_SUBSPACES]; +static DEFINE_MUTEX(pcc_data_lock); /* The cpu_pcc_subspace_idx contains per CPU subspace ID */ static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx); @@ -760,35 +762,51 @@ EXPORT_SYMBOL_GPL(acpi_get_psd_map); static int register_pcc_channel(int pcc_ss_idx) { + struct cppc_pcc_data *data; struct pcc_mbox_chan *pcc_chan; u64 usecs_lat; + int ret = 0; - if (pcc_ss_idx >= 0) { - pcc_chan = pcc_mbox_request_channel(&cppc_mbox_cl, pcc_ss_idx); - - if (IS_ERR(pcc_chan)) { - pr_err("Failed to find PCC channel for subspace %d\n", - pcc_ss_idx); - return -ENODEV; - } + if (pcc_ss_idx < 0 || pcc_ss_idx >= MAX_PCC_SUBSPACES) + return -EINVAL; - pcc_data[pcc_ss_idx]->pcc_channel = pcc_chan; - /* - * cppc_ss->latency is just a Nominal value. In reality - * the remote processor could be much slower to reply. - * So add an arbitrary amount of wait on top of Nominal. - */ - usecs_lat = NUM_RETRIES * pcc_chan->latency; - pcc_data[pcc_ss_idx]->deadline_us = usecs_lat; - pcc_data[pcc_ss_idx]->pcc_mrtt = pcc_chan->min_turnaround_time; - pcc_data[pcc_ss_idx]->pcc_mpar = pcc_chan->max_access_rate; - pcc_data[pcc_ss_idx]->pcc_nominal = pcc_chan->latency; + mutex_lock(&pcc_data_lock); + data = pcc_data[pcc_ss_idx]; + if (!data) { + ret = -ENODEV; + goto out_unlock; + } + if (data->pcc_channel_acquired) + goto out_unlock; - /* Set flag so that we don't come here for each CPU. */ - pcc_data[pcc_ss_idx]->pcc_channel_acquired = true; + pcc_chan = pcc_mbox_request_channel(&cppc_mbox_cl, pcc_ss_idx); + if (IS_ERR(pcc_chan)) { + pr_err("Failed to find PCC channel for subspace %d\n", + pcc_ss_idx); + ret = -ENODEV; + goto out_unlock; } - return 0; + data->pcc_channel = pcc_chan; + /* + * cppc_ss->latency is just a Nominal value. In reality + * the remote processor could be much slower to reply. + * So add an arbitrary amount of wait on top of Nominal. + */ + usecs_lat = NUM_RETRIES * pcc_chan->latency; + data->deadline_us = usecs_lat; + data->pcc_mrtt = pcc_chan->min_turnaround_time; + data->pcc_mpar = pcc_chan->max_access_rate; + data->pcc_nominal = pcc_chan->latency; + init_rwsem(&data->pcc_lock); + init_waitqueue_head(&data->pcc_write_wait_q); + + /* Reuse this channel when another CPU references the same subspace. */ + data->pcc_channel_acquired = true; + +out_unlock: + mutex_unlock(&pcc_data_lock); + return ret; } /** @@ -830,19 +848,49 @@ bool __weak cpc_supported_by_cpu(void) */ static int pcc_data_alloc(int pcc_ss_id) { + struct cppc_pcc_data *data; + int ret = 0; + if (pcc_ss_id < 0 || pcc_ss_id >= MAX_PCC_SUBSPACES) return -EINVAL; - if (pcc_data[pcc_ss_id]) { - pcc_data[pcc_ss_id]->refcount++; - } else { - pcc_data[pcc_ss_id] = kzalloc_obj(struct cppc_pcc_data); - if (!pcc_data[pcc_ss_id]) - return -ENOMEM; - pcc_data[pcc_ss_id]->refcount++; + mutex_lock(&pcc_data_lock); + data = pcc_data[pcc_ss_id]; + if (!data) { + data = kzalloc_obj(struct cppc_pcc_data); + if (!data) { + ret = -ENOMEM; + goto out_unlock; + } + pcc_data[pcc_ss_id] = data; } + data->refcount++; - return 0; +out_unlock: + mutex_unlock(&pcc_data_lock); + return ret; +} + +static void pcc_data_put(int pcc_ss_id) +{ + struct cppc_pcc_data *data; + + if (pcc_ss_id < 0 || pcc_ss_id >= MAX_PCC_SUBSPACES) + return; + + mutex_lock(&pcc_data_lock); + data = pcc_data[pcc_ss_id]; + if (!data || --data->refcount) + goto out_unlock; + + pcc_data[pcc_ss_id] = NULL; + if (data->pcc_channel_acquired) + pcc_mbox_free_channel(data->pcc_channel); + + kfree(data); + +out_unlock: + mutex_unlock(&pcc_data_lock); } /* @@ -890,11 +938,17 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) acpi_handle handle = pr->handle; unsigned int num_ent, i, cpc_rev; int pcc_subspace_id = -1; + bool pcc_data_ref = false; bool cpc_present = false; acpi_status status; int ret = -ENODATA; int err; + if (per_cpu(cpc_desc_ptr, pr->id)) + return 0; + + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; + if (!osc_sb_cppc2_support_acked) { pr_debug("CPPC v2 _OSC not acked\n"); if (!cpc_supported_by_cpu()) { @@ -1039,6 +1093,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) ret = err; goto out_free; } + pcc_data_ref = true; } else if (pcc_subspace_id != gas_t->access_width) { pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", pr->id); @@ -1171,13 +1226,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; /* Register PCC channel once for all PCC subspace ID. */ - if (pcc_subspace_id >= 0 && !pcc_data[pcc_subspace_id]->pcc_channel_acquired) { + if (pcc_subspace_id >= 0) { ret = register_pcc_channel(pcc_subspace_id); if (ret) goto out_free; - - init_rwsem(&pcc_data[pcc_subspace_id]->pcc_lock); - init_waitqueue_head(&pcc_data[pcc_subspace_id]->pcc_write_wait_q); } /* Everything looks okay */ @@ -1198,7 +1250,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (ret) { per_cpu(cpc_desc_ptr, pr->id) = NULL; kobject_put(&cpc_ptr->kobj); - goto out_buf_free; + goto out_pcc_put; } kfree(output.pointer); @@ -1207,6 +1259,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) out_free: cppc_free_desc(cpc_ptr); +out_pcc_put: + if (pcc_data_ref) + pcc_data_put(pcc_subspace_id); + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; + out_buf_free: if (cpc_present) pr_err("CPU%d: failed to initialize _CPC: %d\n", pr->id, ret); @@ -1224,28 +1281,20 @@ EXPORT_SYMBOL_GPL(acpi_cppc_processor_probe); void acpi_cppc_processor_exit(struct acpi_processor *pr) { struct cpc_desc *cpc_ptr; - int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); + int pcc_ss_id; cpc_ptr = per_cpu(cpc_desc_ptr, pr->id); - if (cpc_ptr) { - per_cpu(cpc_desc_ptr, pr->id) = NULL; - kobject_del(&cpc_ptr->kobj); + if (!cpc_ptr) { + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; + return; } - if (pcc_ss_id >= 0 && pcc_data[pcc_ss_id]) { - if (pcc_data[pcc_ss_id]->pcc_channel_acquired) { - pcc_data[pcc_ss_id]->refcount--; - if (!pcc_data[pcc_ss_id]->refcount) { - pcc_mbox_free_channel(pcc_data[pcc_ss_id]->pcc_channel); - kfree(pcc_data[pcc_ss_id]); - pcc_data[pcc_ss_id] = NULL; - } - } - } - per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; + pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); + per_cpu(cpc_desc_ptr, pr->id) = NULL; + kobject_del(&cpc_ptr->kobj); - if (!cpc_ptr) - return; + pcc_data_put(pcc_ss_id); + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; kobject_put(&cpc_ptr->kobj); } From 2221dc547b38eeef4893abb0f55352ebd920ddcb Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:53 +0100 Subject: [PATCH 0546/2286] ACPI: CPPC: Reject unsafe cross-CPU SystemMemory RMW A SystemMemory CPPC field narrower than its access unit needs a read-modify-write operation. The per-CPC-descriptor lock cannot serialize access units described by different processors, so concurrent writers can lose updates. _PSD domains do not define physical register ownership. Track SystemMemory access units in a probe-time interval tree and reject cross-descriptor partial writers sharing an access unit. Allow exact aliases only for aligned native-width accesses to the same logical field. Reject overlapping writable logical fields and full-width writes which would overwrite another field. Disjoint read-only fields may share a larger read access unit with a writer. Within one descriptor, mark both overlapping accesses for the RMW lock, including a full-width byte beside a wider partial field. Count only controls written by the CPPC library as competing writers, so the unused OSPM Nominal Performance control does not trigger a writer conflict. Mark successful relaxed MMIO writes pending before releasing the raw spinlock. On architectures which need an MMIO write barrier during lock handoff, raw_spin_unlock_irqrestore() then orders the write before the next writer enters the RMW critical section. Validate the GAS geometry and supported access widths. Require natural alignment where the architecture's MMIO accessors need it. Exact writable aliases require alignment on x86 too, because an unaligned instruction need not be one indivisible device transaction. Keep validation results separate from the probe return value, so a successful geometry check cannot make a later mapping failure return success. Remove the interval records during teardown and probe rollback. Fixes: 60949b7b8054 ("ACPI: CPPC: Fix MASK_VAL() usage") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loehle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian-loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-9-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 415 +++++++++++++++++++++++++++++++++++---- include/acpi/cppc_acpi.h | 5 +- 2 files changed, 382 insertions(+), 38 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 2ad45725f3e0b1..131dd3be1ec354 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -34,9 +34,12 @@ #define pr_fmt(fmt) "ACPI CPPC: " fmt #include +#include #include #include +#include #include +#include #include #include #include @@ -95,6 +98,40 @@ static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx); */ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); +struct cpc_sysmem_node { + struct rb_node rb; + u64 subtree_last; + u64 start; + u64 last; + struct cpc_desc *desc; + unsigned int reg_idx; + struct list_head aliases; + struct list_head alias_node; + struct cpc_sysmem_node *alias_of; + bool registered; +}; + +#define CPC_SYSMEM_START(node) ((node)->start) +#define CPC_SYSMEM_LAST(node) ((node)->last) + +INTERVAL_TREE_DEFINE(struct cpc_sysmem_node, rb, u64, subtree_last, + CPC_SYSMEM_START, CPC_SYSMEM_LAST, static inline, + cpc_sysmem_itree) + +static struct rb_root_cached cpc_sysmem_tree = RB_ROOT_CACHED; +static DEFINE_MUTEX(cpc_sysmem_lock); + +static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) +{ + return cpc_sysmem_itree_iter_first(&cpc_sysmem_tree, start, last); +} + +static struct cpc_sysmem_node *cpc_sysmem_next(struct cpc_sysmem_node *node, + u64 start, u64 last) +{ + return cpc_sysmem_itree_iter_next(node, start, last); +} + /* pcc mapped address + header size + offset within PCC subspace */ #define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_channel->shmem + \ 0x8 + (offs)) @@ -278,15 +315,21 @@ cpc_system_io_write_supported(const struct cpc_register_resource *reg) gas->address <= U16_MAX - (access_size - 1) && (IS_ENABLED(CONFIG_X86) || IS_ALIGNED(gas->address, access_size)); } -static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) -{ - const struct cpc_reg *gas = ®->cpc_entry.reg; - unsigned int width; - if (gas->access_width > 4) +static unsigned int cpc_reg_access_width(const struct cpc_reg *reg) +{ + if (reg->access_width > 4) return 0; - width = GET_BIT_WIDTH(gas); + if (reg->access_width) + return 8U << (reg->access_width - 1); + + return reg->bit_width; +} + +static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) +{ + unsigned int width = cpc_reg_access_width(®->cpc_entry.reg); if (width != 8 && width != 16 && width != 32 && width != 64) return 0; @@ -294,6 +337,12 @@ static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) return width / 8; } +static bool cpc_reg_access_aligned(const struct cpc_reg *reg, u64 access_size) +{ + /* x86 MMIO and port-I/O accessors support unaligned addresses. */ + return IS_ENABLED(CONFIG_X86) || IS_ALIGNED(reg->address, access_size); +} + static bool cpc_sysmem_access_units_overlap(const struct cpc_register_resource *a, const struct cpc_register_resource *b) { @@ -312,36 +361,314 @@ static bool cpc_sysmem_access_units_overlap(const struct cpc_register_resource * return a_gas->address - b_gas->address < b_size; } +static bool cpc_reg_is_writable(unsigned int reg_idx) +{ + /* Only controls written by this driver can be competing writers. */ + switch (reg_idx) { + case DESIRED_PERF: + case MIN_PERF: + case MAX_PERF: + case PERF_LIMITED: + case ENABLE: + case AUTO_SEL_ENABLE: + case AUTO_ACT_WINDOW: + case ENERGY_PERF: + return true; + default: + return false; + } +} + +static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + u64 access_size = cpc_sysmem_access_size(reg); + + return gas->bit_offset || gas->bit_width != access_size * 8; +} + +static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc, + const struct cpc_reg *gas, + unsigned int reg_idx) +{ + unsigned int access_width = cpc_reg_access_width(gas); + u64 access_size; + + if (access_width != 8 && access_width != 16 && + access_width != 32 && access_width != 64) + goto invalid; + + if (!gas->bit_width || gas->bit_width > access_width || + gas->bit_offset >= access_width || + gas->bit_width > access_width - gas->bit_offset) + goto invalid; + + access_size = access_width / 8; + if (!gas->address || gas->address > U64_MAX - (access_size - 1)) + goto invalid; + if (!cpc_reg_access_aligned(gas, access_size)) + goto invalid; + + return 0; + +invalid: + pr_debug("CPU:%d invalid SystemMemory GAS for _CPC register %u\n", + cpc_desc->cpu_id, reg_idx); + return -EINVAL; +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { - int i, j; + int i; for (i = 0; i < cpc_desc->num_entries - 2; i++) { - struct cpc_register_resource *a = &cpc_desc->cpc_regs[i]; - struct cpc_reg *gas; - u64 access_size; + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + + if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg)) + reg->cpc_entry.use_rmw_lock = + cpc_sysmem_reg_needs_rmw(reg); + } +} - if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a)) +struct cpc_bit_position { + u64 byte; + u8 bit; +}; + +static bool cpc_bit_position_before(const struct cpc_bit_position *a, + const struct cpc_bit_position *b) +{ + return a->byte < b->byte || (a->byte == b->byte && a->bit < b->bit); +} + +static bool cpc_sysmem_fields_overlap(const struct cpc_register_resource *a, + const struct cpc_register_resource *b) +{ + const struct cpc_reg *a_gas = &a->cpc_entry.reg; + const struct cpc_reg *b_gas = &b->cpc_entry.reg; + unsigned int a_last_bit = a_gas->bit_offset + a_gas->bit_width - 1; + unsigned int b_last_bit = b_gas->bit_offset + b_gas->bit_width - 1; + struct cpc_bit_position a_start = { + .byte = a_gas->address + a_gas->bit_offset / 8, + .bit = a_gas->bit_offset % 8, + }; + struct cpc_bit_position a_end = { + .byte = a_gas->address + a_last_bit / 8, + .bit = a_last_bit % 8, + }; + struct cpc_bit_position b_start = { + .byte = b_gas->address + b_gas->bit_offset / 8, + .bit = b_gas->bit_offset % 8, + }; + struct cpc_bit_position b_end = { + .byte = b_gas->address + b_last_bit / 8, + .bit = b_last_bit % 8, + }; + + return !cpc_bit_position_before(&a_end, &b_start) && + !cpc_bit_position_before(&b_end, &a_start); +} + +static bool cpc_same_sysmem_register(unsigned int a_idx, + const struct cpc_register_resource *a, + unsigned int b_idx, + const struct cpc_register_resource *b) +{ + const struct cpc_reg *a_gas = &a->cpc_entry.reg; + const struct cpc_reg *b_gas = &b->cpc_entry.reg; + + return a_idx == b_idx && + a_gas->address == b_gas->address && + a_gas->bit_width == b_gas->bit_width && + a_gas->bit_offset == b_gas->bit_offset && + cpc_reg_access_width(a_gas) == cpc_reg_access_width(b_gas); +} + +static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, + unsigned int a_idx, + const struct cpc_desc *b_desc, + unsigned int b_idx) +{ + const struct cpc_register_resource *a = &a_desc->cpc_regs[a_idx]; + const struct cpc_register_resource *b = &b_desc->cpc_regs[b_idx]; + bool a_writable, b_writable; + bool fields_overlap; + + if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a) || + !CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b) || + !cpc_sysmem_access_units_overlap(a, b)) + return 0; + + a_writable = cpc_reg_is_writable(a_idx); + b_writable = cpc_reg_is_writable(b_idx); + if (!a_writable && !b_writable) + return 0; + + if (cpc_same_sysmem_register(a_idx, a, b_idx, b)) { + u64 access_size = cpc_sysmem_access_size(a); + + /* + * Exact partial aliases update the same field and retain + * last-writer-wins semantics when the complete access is one native + * transaction. A 64-bit MMIO write may be split on 32-bit kernels, + * and an unaligned x86 access is not guaranteed to be one device + * transaction. + */ + if (!a_writable || + (IS_ALIGNED(a->cpc_entry.reg.address, access_size) && + (access_size < sizeof(u64) || + IS_ENABLED(CONFIG_64BIT)))) + return 0; + goto conflict; + } + + fields_overlap = cpc_sysmem_fields_overlap(a, b); + /* A full-width writer must not overwrite another logical field. */ + if (fields_overlap && + ((a_writable && b_writable) || + (a_writable && !cpc_sysmem_reg_needs_rmw(a)) || + (b_writable && !cpc_sysmem_reg_needs_rmw(b)))) + goto conflict; + + /* Different descriptors do not share their partial-write locks. */ + if (a_desc != b_desc && a_writable && b_writable) + goto conflict; + + return 0; + +conflict: + pr_err("CPU%d: SystemMemory _CPC register %u conflicts with CPU%d register %u\n", + a_desc->cpu_id, a_idx, b_desc->cpu_id, b_idx); + return -EINVAL; +} + +static void cpc_unregister_sysmem_desc_locked(struct cpc_desc *cpc_desc) +{ + unsigned int i; + + if (!cpc_desc->sysmem_nodes) + return; + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_sysmem_node *node = &cpc_desc->sysmem_nodes[i]; + struct cpc_sysmem_node *alias, *child; + + if (node->alias_of) { + list_del(&node->alias_node); + continue; + } + if (!node->registered) continue; - gas = &a->cpc_entry.reg; - access_size = cpc_sysmem_access_size(a); - if (gas->bit_offset || !access_size || - gas->bit_width != access_size * 8) - a->cpc_entry.use_rmw_lock = true; + cpc_sysmem_itree_remove(node, &cpc_sysmem_tree); + node->registered = false; + if (list_empty(&node->aliases)) + continue; - for (j = i + 1; j < cpc_desc->num_entries - 2; j++) { - struct cpc_register_resource *b = &cpc_desc->cpc_regs[j]; + /* Keep one representative for aliases owned by live descriptors. */ + alias = list_first_entry(&node->aliases, + struct cpc_sysmem_node, alias_node); + list_del_init(&alias->alias_node); + alias->alias_of = NULL; + alias->registered = true; + list_splice_init(&node->aliases, &alias->aliases); + list_for_each_entry(child, &alias->aliases, alias_node) + child->alias_of = alias; + cpc_sysmem_itree_insert(alias, &cpc_sysmem_tree); + } - if (!CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b)) - continue; - if (!cpc_sysmem_access_units_overlap(a, b)) - continue; + kfree(cpc_desc->sysmem_nodes); + cpc_desc->sysmem_nodes = NULL; +} - a->cpc_entry.use_rmw_lock = true; - b->cpc_entry.use_rmw_lock = true; +static int cpc_register_sysmem_desc(struct cpc_desc *cpc_desc) +{ + unsigned int nr_regs = cpc_desc->num_entries - 2; + unsigned int i; + int ret = 0; + bool found = false; + + for (i = 0; i < nr_regs; i++) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + + if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg)) { + found = true; + break; } } + if (!found) + return 0; + + cpc_desc->sysmem_nodes = kcalloc(nr_regs, + sizeof(*cpc_desc->sysmem_nodes), + GFP_KERNEL); + if (!cpc_desc->sysmem_nodes) + return -ENOMEM; + + mutex_lock(&cpc_sysmem_lock); + + for (i = 0; i < nr_regs; i++) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + struct cpc_sysmem_node *alias = NULL, *match, *node; + u64 size; + + if (!CPC_SUPPORTED(reg) || !CPC_IN_SYSTEM_MEMORY(reg)) + continue; + + node = &cpc_desc->sysmem_nodes[i]; + size = cpc_sysmem_access_size(reg); + node->start = reg->cpc_entry.reg.address; + node->last = node->start + size - 1; + node->desc = cpc_desc; + node->reg_idx = i; + INIT_LIST_HEAD(&node->aliases); + INIT_LIST_HEAD(&node->alias_node); + + match = cpc_sysmem_first(node->start, node->last); + while (match) { + struct cpc_register_resource *match_reg; + + ret = cpc_validate_sysmem_pair(cpc_desc, i, match->desc, + match->reg_idx); + if (ret) + goto out_unregister; + match_reg = &match->desc->cpc_regs[match->reg_idx]; + if (cpc_desc == match->desc) { + reg->cpc_entry.use_rmw_lock = true; + match_reg->cpc_entry.use_rmw_lock = true; + } + if (cpc_same_sysmem_register(i, reg, match->reg_idx, match_reg)) + alias = match; + + match = cpc_sysmem_next(match, node->start, node->last); + } + if (alias) { + node->alias_of = alias; + list_add_tail(&node->alias_node, &alias->aliases); + continue; + } + + cpc_sysmem_itree_insert(node, &cpc_sysmem_tree); + node->registered = true; + } + + mutex_unlock(&cpc_sysmem_lock); + return 0; + +out_unregister: + cpc_unregister_sysmem_desc_locked(cpc_desc); + mutex_unlock(&cpc_sysmem_lock); + return ret; +} + +static void cpc_unregister_sysmem_desc(struct cpc_desc *cpc_desc) +{ + if (!cpc_desc->sysmem_nodes) + return; + + mutex_lock(&cpc_sysmem_lock); + cpc_unregister_sysmem_desc_locked(cpc_desc); + mutex_unlock(&cpc_sysmem_lock); } static ssize_t show_feedback_ctrs(struct kobject *kobj, @@ -379,6 +706,8 @@ static void cppc_free_desc(struct cpc_desc *cpc_ptr) { unsigned int i; + cpc_unregister_sysmem_desc(cpc_ptr); + for (i = 2; i < cpc_ptr->num_entries; i++) { void __iomem *addr = cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr; @@ -979,6 +1308,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) ret = -ENOMEM; goto out_buf_free; } + cpc_ptr->cpu_id = pr->id; /* First entry is NumEntries. */ cpc_obj = &out_obj->package.elements[0]; @@ -1100,10 +1430,16 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - if (gas_t->address) { + if (!IS_NULL_REG(gas_t)) { void __iomem *addr; size_t access_width; + err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); + if (err) { + ret = err; + goto out_free; + } + if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); ret = -EOPNOTSUPP; @@ -1112,13 +1448,14 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) ret = -EINVAL; } - access_width = GET_BIT_WIDTH(gas_t) / 8; + access_width = cpc_reg_access_width(gas_t); + access_width /= 8; addr = ioremap(gas_t->address, access_width); if (!addr) { ret = -ENOMEM; goto out_free; } - cpc_ptr->cpc_regs[i-2].sys_mem_vaddr = addr; + cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr = addr; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { if (gas_t->access_width < 1 || gas_t->access_width > 3) { @@ -1154,8 +1491,9 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } } - cpc_ptr->cpc_regs[i-2].type = ACPI_TYPE_BUFFER; - memcpy(&cpc_ptr->cpc_regs[i-2].cpc_entry.reg, gas_t, sizeof(*gas_t)); + cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_BUFFER; + memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, + sizeof(*gas_t)); } else if (cpc_obj->type == ACPI_TYPE_PACKAGE && (i - 2) == RESOURCE_PRIORITY) { /* * ACPI 6.6, s8.4.6.1.2.7 defines Resource Priority as a @@ -1215,8 +1553,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } - /* Store CPU Logical ID */ - cpc_ptr->cpu_id = pr->id; cpc_mark_rmw_lock_users(cpc_ptr); raw_spin_lock_init(&cpc_ptr->rmw_lock); @@ -1225,6 +1561,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (ret) goto out_free; + ret = cpc_register_sysmem_desc(cpc_ptr); + if (ret) + goto out_free; + /* Register PCC channel once for all PCC subspace ID. */ if (pcc_subspace_id >= 0) { ret = register_pcc_channel(pcc_subspace_id); @@ -1249,6 +1589,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) "acpi_cppc"); if (ret) { per_cpu(cpc_desc_ptr, pr->id) = NULL; + cpc_unregister_sysmem_desc(cpc_ptr); kobject_put(&cpc_ptr->kobj); goto out_pcc_put; } @@ -1292,6 +1633,7 @@ void acpi_cppc_processor_exit(struct acpi_processor *pr) pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); per_cpu(cpc_desc_ptr, pr->id) = NULL; kobject_del(&cpc_ptr->kobj); + cpc_unregister_sysmem_desc(cpc_ptr); pcc_data_put(pcc_ss_id); per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; @@ -1479,11 +1821,7 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) val, size); if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - /* - * The _CPC layout is immutable after probe. The precomputed flag - * retains serialization for partial fields or overlapping access - * units; standalone full-width registers avoid the lock. - */ + /* Partial fields and local overlaps use the descriptor lock. */ locked = reg_res->cpc_entry.use_rmw_lock; if (locked) { cpc_desc = per_cpu(cpc_desc_ptr, cpu); @@ -1543,8 +1881,11 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) break; } - if (locked) + if (locked) { + if (!ret_val) + mmiowb_set_pending(); raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, flags); + } return ret_val; } diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index edf0f6ffb4378f..737219761c5280 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -77,6 +77,8 @@ struct cpc_register_resource { } cpc_entry; }; +struct cpc_sysmem_node; + /* Container to hold the CPC details for each CPU */ struct cpc_desc { int num_entries; @@ -84,10 +86,11 @@ struct cpc_desc { int cpu_id; int write_cmd_status; int write_cmd_id; - /* Lock used for RMW operations in cpc_write() */ + /* Serialize partial SystemMemory writes within this descriptor. */ raw_spinlock_t rmw_lock; struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT]; struct acpi_psd_package domain_info; + struct cpc_sysmem_node *sysmem_nodes; struct kobject kobj; }; From 09d911c9966333c45409440ec2d777cf80a3305f Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:54 +0100 Subject: [PATCH 0547/2286] ACPI: CPPC: Reject direct reads of write-only controls Between _CPC revision 3 and revision 4, Desired Performance changed from Read/Write to Write. Revision 4 also added the write-only OSPM Nominal Performance control. ACPI 6.6 section 4.6.3 says reads of write-only bit positions produce undefined results. The public Desired Performance helper already rejects revision-4 readback, but the common register accessor still permits either write-only control to be read. Move the check into the common accessor and reject both controls there, so callers cannot consume undefined values. A partial SystemMemory field still makes cpc_write() read its complete access unit to preserve bits outside the field. MASK_VAL_WRITE() replaces every bit of the field being written, so that field's undefined readback is not propagated. However, another writer sharing the access unit would preserve and replay the write-only field when performing its own RMW. Reject such pairs; a partial write-only field may share its access unit only with disjoint read-only fields. Also reject readable fields whose logical bits overlap a write-only field, even if the write-only descriptor is retained as an inaccessible range. Reading the containing access unit is safe when the undefined bits are masked out, but an overlapping read field would expose them to callers. Apply this check before the no-writer shortcut, and reserve the complete known access range when a write-only field has no usable Bit Width. Keep an invalid but locatable write-only SystemMemory descriptor represented during overlap validation, but mark it unreadable and unwritable. Otherwise a neighbouring RMW field could evade validation and replay undefined readback into the hidden control. Conservatively claim the larger of the declared access unit and logical field span. Check if the writer access unit covers the write-only field so harmless asymmetric geometries are not rejected. Preserve a decoded access-unit claim even when the malformed Bit Width is zero, and use that claimed range when testing whether another writer's access overlaps the hidden field. Count only accessible descriptors as writers in the generic conflict rules. The logical-field overlap check protects a retained field from a full-width write, and the dedicated write-only-field check also protects it from another writer's RMW. Mark an inaccessible OSPM Nominal Performance control unsupported because it is optional. Do the same for inaccessible Desired Performance while parsing, then let the post-parse control check accept it only for immutable autonomous selection. This preserves the autonomous-only exception without accepting an unusable Desired control in non-autonomous mode. Do not advertise fast switching or zero transition latency unless Desired Performance remains writable. An inaccessible descriptor retained only for overlap validation still carries its original address-space identity, but cannot service a performance request. Fixes: 71e1815113f7 ("ACPI: CPPC: Add support for CPPC v4") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com Link: https://sashiko.dev/#/patchset/20260808082644.1251332-1-christian.loehle%40arm.com Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-10-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 162 ++++++++++++++++++++++++++++++++------- include/acpi/cppc_acpi.h | 2 + 2 files changed, 137 insertions(+), 27 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 131dd3be1ec354..e93fb78afa13eb 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -171,7 +171,14 @@ static struct cpc_sysmem_node *cpc_sysmem_next(struct cpc_sysmem_node *node, static bool cpc_is_writable(const struct cpc_register_resource *cpc) { return cpc->type == ACPI_TYPE_BUFFER && - !IS_NULL_REG(&cpc->cpc_entry.reg); + !IS_NULL_REG(&cpc->cpc_entry.reg) && + !cpc->cpc_entry.write_unsupported; +} + +static bool cpc_is_readable(const struct cpc_register_resource *cpc) +{ + return cpc->type != ACPI_TYPE_BUFFER || + !cpc->cpc_entry.read_unsupported; } static bool cpc_entry_present(const struct cpc_register_resource *cpc) @@ -337,6 +344,22 @@ static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) return width / 8; } +static u64 cpc_sysmem_field_size(const struct cpc_reg *gas) +{ + return DIV_ROUND_UP((u64)gas->bit_offset + gas->bit_width, 8); +} + +static u64 cpc_sysmem_claim_size(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + u64 access_size = cpc_sysmem_access_size(reg); + + if (!gas->bit_width) + return access_size; + + return max(access_size, cpc_sysmem_field_size(gas)); +} + static bool cpc_reg_access_aligned(const struct cpc_reg *reg, u64 access_size) { /* x86 MMIO and port-I/O accessors support unaligned addresses. */ @@ -348,8 +371,8 @@ static bool cpc_sysmem_access_units_overlap(const struct cpc_register_resource * { const struct cpc_reg *a_gas = &a->cpc_entry.reg; const struct cpc_reg *b_gas = &b->cpc_entry.reg; - u64 a_size = cpc_sysmem_access_size(a); - u64 b_size = cpc_sysmem_access_size(b); + u64 a_size = cpc_sysmem_claim_size(a); + u64 b_size = cpc_sysmem_claim_size(b); /* Keep the conservative locking path for malformed access widths. */ if (!a_size || !b_size) @@ -379,6 +402,21 @@ static bool cpc_reg_is_writable(unsigned int reg_idx) } } +static bool cpc_reg_is_write_only(const struct cpc_desc *cpc_desc, + unsigned int reg_idx) +{ + return cpc_desc->version >= CPPC_V4_REV && + (reg_idx == DESIRED_PERF || reg_idx == OSPM_NOMINAL_PERF); +} + +static void cpc_disable_reg(struct cpc_desc *cpc_desc, unsigned int reg_idx) +{ + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; + + reg->type = ACPI_TYPE_INTEGER; + reg->cpc_entry.int_value = 0; +} + static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *reg) { const struct cpc_reg *gas = ®->cpc_entry.reg; @@ -387,7 +425,7 @@ static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *reg) return gas->bit_offset || gas->bit_width != access_size * 8; } -static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc, +static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc, const struct cpc_reg *gas, unsigned int reg_idx) { @@ -412,6 +450,23 @@ static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc, return 0; invalid: + access_size = 0; + if (access_width == 8 || access_width == 16 || + access_width == 32 || access_width == 64) + access_size = access_width / 8; + if (gas->bit_width) + access_size = max(access_size, cpc_sysmem_field_size(gas)); + if (cpc_reg_is_write_only(cpc_desc, reg_idx) && gas->address && + access_size && gas->address <= U64_MAX - (access_size - 1)) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; + + pr_warn("CPU%d: _CPC v%d register %u is inaccessible; keeping its range reserved\n", + cpc_desc->cpu_id, cpc_desc->version, reg_idx); + reg->cpc_entry.read_unsupported = true; + reg->cpc_entry.write_unsupported = true; + return 0; + } + pr_debug("CPU:%d invalid SystemMemory GAS for _CPC register %u\n", cpc_desc->cpu_id, reg_idx); return -EINVAL; @@ -469,6 +524,27 @@ static bool cpc_sysmem_fields_overlap(const struct cpc_register_resource *a, !cpc_bit_position_before(&b_end, &a_start); } +static bool cpc_sysmem_access_overlaps_field(const struct cpc_register_resource *access, + const struct cpc_register_resource *field) +{ + const struct cpc_reg *access_gas = &access->cpc_entry.reg; + const struct cpc_reg *field_gas = &field->cpc_entry.reg; + u64 access_last; + u64 field_start; + u64 field_last; + + if (!field_gas->bit_width) + return cpc_sysmem_access_units_overlap(access, field); + + access_last = access_gas->address + + cpc_sysmem_access_size(access) - 1; + field_start = field_gas->address + field_gas->bit_offset / 8; + field_last = field_gas->address + + (field_gas->bit_offset + field_gas->bit_width - 1) / 8; + + return access_gas->address <= field_last && field_start <= access_last; +} + static bool cpc_same_sysmem_register(unsigned int a_idx, const struct cpc_register_resource *a, unsigned int b_idx, @@ -491,6 +567,7 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, { const struct cpc_register_resource *a = &a_desc->cpc_regs[a_idx]; const struct cpc_register_resource *b = &b_desc->cpc_regs[b_idx]; + bool a_write_only, b_write_only; bool a_writable, b_writable; bool fields_overlap; @@ -499,8 +576,19 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, !cpc_sysmem_access_units_overlap(a, b)) return 0; - a_writable = cpc_reg_is_writable(a_idx); - b_writable = cpc_reg_is_writable(b_idx); + a_write_only = cpc_reg_is_write_only(a_desc, a_idx); + b_write_only = cpc_reg_is_write_only(b_desc, b_idx); + fields_overlap = !a->cpc_entry.reg.bit_width || + !b->cpc_entry.reg.bit_width || + cpc_sysmem_fields_overlap(a, b); + /* A readable field must not expose another field's undefined bits. */ + if (a_write_only != b_write_only && + cpc_is_readable(a_write_only ? b : a) && + fields_overlap) + goto conflict; + + a_writable = cpc_reg_is_writable(a_idx) && cpc_is_writable(a); + b_writable = cpc_reg_is_writable(b_idx) && cpc_is_writable(b); if (!a_writable && !b_writable) return 0; @@ -522,7 +610,6 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, goto conflict; } - fields_overlap = cpc_sysmem_fields_overlap(a, b); /* A full-width writer must not overwrite another logical field. */ if (fields_overlap && ((a_writable && b_writable) || @@ -534,6 +621,18 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, if (a_desc != b_desc && a_writable && b_writable) goto conflict; + /* + * RMW of either writer preserves the other field. If that field is + * write-only, its readback is undefined and cannot safely be replayed. + */ + if ((a_write_only && b_writable && + cpc_sysmem_reg_needs_rmw(b) && + cpc_sysmem_access_overlaps_field(b, a)) || + (b_write_only && a_writable && + cpc_sysmem_reg_needs_rmw(a) && + cpc_sysmem_access_overlaps_field(a, b))) + goto conflict; + return 0; conflict: @@ -616,7 +715,7 @@ static int cpc_register_sysmem_desc(struct cpc_desc *cpc_desc) continue; node = &cpc_desc->sysmem_nodes[i]; - size = cpc_sysmem_access_size(reg); + size = cpc_sysmem_claim_size(reg); node->start = reg->cpc_entry.reg.address; node->last = node->start + size - 1; node->desc = cpc_desc; @@ -1003,7 +1102,7 @@ bool cppc_allow_fast_switch(const struct cpumask *cpus) min_reg = &cpc_ptr->cpc_regs[MIN_PERF]; max_reg = &cpc_ptr->cpc_regs[MAX_PERF]; - if (!CPC_SUPPORTED(desired_reg) || + if (!cpc_is_writable(desired_reg) || (!CPC_IN_SYSTEM_MEMORY(desired_reg) && !CPC_IN_SYSTEM_IO(desired_reg)) || (CPC_SUPPORTED(min_reg) && @@ -1409,6 +1508,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } + cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_BUFFER; + memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, + sizeof(*gas_t)); + /* * The PCC Subspace index is encoded inside * the CPC table entries. The same PCC index @@ -1435,10 +1538,24 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) size_t access_width; err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); + if (err && (i - 2 == DESIRED_PERF || + i - 2 == OSPM_NOMINAL_PERF)) { + const char *name = i - 2 == DESIRED_PERF ? + "Desired Performance" : + "OSPM Nominal Performance"; + + pr_warn("CPU%d: disabling inaccessible %s register\n", + pr->id, name); + cpc_disable_reg(cpc_ptr, i - 2); + continue; + } if (err) { ret = err; goto out_free; } + if (!cpc_is_readable(&cpc_ptr->cpc_regs[i - 2]) && + !cpc_is_writable(&cpc_ptr->cpc_regs[i - 2])) + continue; if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); @@ -1490,10 +1607,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } } - - cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_BUFFER; - memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, - sizeof(*gas_t)); } else if (cpc_obj->type == ACPI_TYPE_PACKAGE && (i - 2) == RESOURCE_PRIORITY) { /* * ACPI 6.6, s8.4.6.1.2.7 defines Resource Priority as a @@ -1833,6 +1946,10 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) } if (reg->bit_offset || reg->bit_width != size) { + /* + * MASK_VAL_WRITE() discards the field's old bits, so undefined + * readback from a write-only field is not propagated. + */ switch (size) { case 8: prev_val = readb_relaxed(vaddr); @@ -1927,6 +2044,8 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs reg_idx, u64 *val) pr_debug("No CPC descriptor for CPU:%d\n", cpu); return -ENODEV; } + if (cpc_reg_is_write_only(cpc_desc, reg_idx)) + return -EOPNOTSUPP; reg = &cpc_desc->cpc_regs[reg_idx]; @@ -2007,11 +2126,6 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val) return cpc_write(cpu, reg, val); } -static bool cppc_desired_perf_readable(const struct cpc_desc *cpc_desc) -{ - return cpc_desc->version < CPPC_V4_REV; -} - /** * cppc_get_desired_perf - Get the desired performance register value. * @cpunum: CPU from which to get desired performance. @@ -2022,15 +2136,6 @@ static bool cppc_desired_perf_readable(const struct cpc_desc *cpc_desc) */ int cppc_get_desired_perf(int cpunum, u64 *desired_perf) { - struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); - - if (!cpc_desc) - return -ENODEV; - - /* _CPC revision 4 no longer specifies Desired Performance as readable. */ - if (!cppc_desired_perf_readable(cpc_desc)) - return -EOPNOTSUPP; - return cppc_get_reg_val(cpunum, DESIRED_PERF, desired_perf); } EXPORT_SYMBOL_GPL(cppc_get_desired_perf); @@ -3033,6 +3138,9 @@ int cppc_get_transition_latency(int cpu_num) return -ENODATA; desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; + if (!cpc_is_writable(desired_reg)) + return -ENODATA; + if (CPC_IN_SYSTEM_MEMORY(desired_reg) || CPC_IN_SYSTEM_IO(desired_reg)) return 0; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 737219761c5280..53186da6a654a4 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -72,6 +72,8 @@ struct cpc_register_resource { struct { struct cpc_reg reg; bool use_rmw_lock; + bool read_unsupported; + bool write_unsupported; }; u64 int_value; } cpc_entry; From fd1bb9a6cb1f6641840a99653a175b913c0358f4 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:55 +0100 Subject: [PATCH 0548/2286] ACPI: CPPC: Validate and access PCC register layouts PCC GAS Access Size identifies the subspace while Bit Width identifies the field size. Support zero-offset byte-multiple fields up to 64 bits with byte-oriented I/O and explicit little-endian encoding. This accepts the unaligned 32-bit Delivered Performance Counter in the ACPI implementation example and read-only widths such as 24 bits. A per-subspace raw spinlock serializes aliased payload copies made under the shared side of pcc_lock. Defer unsupported-field decisions until the package is known. A provided but inaccessible CPPC Enable or Autonomous Selection Enable control fails probe. OSPM must set the former and cannot discard the latter without knowing the hardware autonomous-mode state. An inaccessible Desired control can be ignored only for immutable autonomous operation. If both Minimum and Maximum controls are inaccessible, disable the pair coherently; a one-sided bound fails probe. Other inaccessible optional controls are marked absent. Validate PCC ranges after the eight-byte header and reject overlapping logical ranges when either is writable. Bit-level PCC RMW remains unsupported. Probe now rejects or disables unsupported writable PCC widths before publishing the descriptor. Remove the earlier setter preflights which repeat that validation. Fixes: 85b1407bf6d2 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subspace IDs") Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-11-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 452 ++++++++++++++++++++++++++++----------- 1 file changed, 327 insertions(+), 125 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index e93fb78afa13eb..fe1d109afbd56f 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -74,6 +74,8 @@ struct cppc_pcc_data { * Take write_lock for all purposes which gives exclusive access */ struct rw_semaphore pcc_lock; + /* Serialize byte-oriented accesses to aliased PCC payload fields. */ + raw_spinlock_t payload_lock; /* Wait queue for CPUs whose requests were batched */ wait_queue_head_t pcc_write_wait_q; @@ -132,9 +134,11 @@ static struct cpc_sysmem_node *cpc_sysmem_next(struct cpc_sysmem_node *node, return cpc_sysmem_itree_iter_next(node, start, last); } +#define CPC_PCC_HEADER_SIZE 0x8 + /* pcc mapped address + header size + offset within PCC subspace */ #define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_channel->shmem + \ - 0x8 + (offs)) + CPC_PCC_HEADER_SIZE + (offs)) /* Check if a CPC register is in PCC */ #define CPC_IN_PCC(cpc) ((cpc)->type == ACPI_TYPE_BUFFER && \ @@ -286,19 +290,6 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); (reg)->space_id != ACPI_ADR_SPACE_PLATFORM_COMM) ? \ (8 << ((reg)->access_width - 1)) : (reg)->bit_width) -static bool cpc_pcc_write_supported(const struct cpc_register_resource *reg) -{ - switch (GET_BIT_WIDTH(®->cpc_entry.reg)) { - case 8: - case 16: - case 32: - case 64: - return true; - default: - return false; - } -} - /* Shift and apply the mask for CPC reads/writes */ #define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \ GENMASK(((reg)->bit_width) - 1, 0)) @@ -472,6 +463,196 @@ static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc, return -EINVAL; } +static bool cpc_immutable_autonomous(const struct cpc_desc *cpc_desc) +{ + const struct cpc_register_resource *reg; + + reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; + return osc_sb_cppc2_support_acked && reg->type == ACPI_TYPE_INTEGER && + reg->cpc_entry.int_value == 1; +} + +static int cpc_resolve_unsupported(struct cpc_desc *cpc_desc, + u32 unsupported) +{ + unsigned int i; + u32 bounds = BIT(MIN_PERF) | BIT(MAX_PERF); + bool min_unusable, max_unusable; + + if (unsupported & bounds) { + min_unusable = (unsupported & BIT(MIN_PERF)) || + !cpc_is_writable(&cpc_desc->cpc_regs[MIN_PERF]); + max_unusable = (unsupported & BIT(MAX_PERF)) || + !cpc_is_writable(&cpc_desc->cpc_regs[MAX_PERF]); + if (min_unusable && max_unusable) { + pr_warn("CPU%d: ignoring inaccessible Minimum and Maximum Performance registers\n", + cpc_desc->cpu_id); + cpc_disable_reg(cpc_desc, MIN_PERF); + cpc_disable_reg(cpc_desc, MAX_PERF); + unsupported &= ~bounds; + } + } + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + if (!(unsupported & BIT(i))) + continue; + + if (i == DESIRED_PERF && cpc_immutable_autonomous(cpc_desc)) { + pr_warn("CPU%d: ignoring inaccessible Desired Performance register in autonomous mode\n", + cpc_desc->cpu_id); + cpc_disable_reg(cpc_desc, i); + continue; + } + + /* + * A present Enable or Autonomous Selection control must remain + * usable. Disabling the latter could leave autonomous selection + * enabled while OSPM believes that it has disabled it. + */ + if (i == ENABLE || + (i == AUTO_SEL_ENABLE && cpc_entry_present(&cpc_desc->cpc_regs[i])) || + i == MIN_PERF || i == MAX_PERF || + !IS_OPTIONAL_CPC_REG(i)) { + pr_err("CPU%d: cannot access _CPC register %u\n", + cpc_desc->cpu_id, i); + return -EINVAL; + } + + pr_warn("CPU%d: ignoring inaccessible optional _CPC register %u\n", + cpc_desc->cpu_id, i); + cpc_disable_reg(cpc_desc, i); + } + + return 0; +} + +static int cpc_validate_required_controls(struct cpc_desc *cpc_desc) +{ + unsigned int i; + + /* + * Performance Limited is required by the specification, but tolerate a + * NULL descriptor used by firmware which cannot report limiting events. + * CPPC control does not depend on this status. + */ + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + if (i != DESIRED_PERF && i != PERF_LIMITED && + !IS_OPTIONAL_CPC_REG(i) && + !cpc_entry_present(&cpc_desc->cpc_regs[i])) { + pr_debug("CPU:%d lacks mandatory _CPC register %u\n", + cpc_desc->cpu_id, i); + return -EINVAL; + } + } + + /* Desired may be absent only for immutable autonomous operation. */ + if (!cpc_is_writable(&cpc_desc->cpc_regs[DESIRED_PERF]) && + !cpc_immutable_autonomous(cpc_desc)) { + pr_debug("CPU:%d lacks a writable Desired Performance register\n", + cpc_desc->cpu_id); + return -EINVAL; + } + + return 0; +} + +static int cpc_validate_bound_controls(struct cpc_desc *cpc_desc) +{ + bool have_min, have_max; + + have_min = cpc_is_writable(&cpc_desc->cpc_regs[MIN_PERF]); + have_max = cpc_is_writable(&cpc_desc->cpc_regs[MAX_PERF]); + if (have_min != have_max) { + pr_err("CPU%d: _CPC must provide both Minimum and Maximum Performance or neither\n", + cpc_desc->cpu_id); + return -EINVAL; + } + + return 0; +} + +static void cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc, + struct cppc_pcc_data *data, + u32 *unsupported) +{ + u64 shmem_size = data->pcc_channel->shmem_size; + unsigned int i; + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + struct cpc_reg *gas; + u64 access_size; + + if ((*unsupported & BIT(i)) || !CPC_SUPPORTED(reg) || + !CPC_IN_PCC(reg)) + continue; + + gas = ®->cpc_entry.reg; + access_size = gas->bit_width / 8; + if (shmem_size >= CPC_PCC_HEADER_SIZE && + gas->address <= shmem_size - CPC_PCC_HEADER_SIZE && + access_size <= shmem_size - CPC_PCC_HEADER_SIZE - gas->address) + continue; + + pr_debug("CPU%d: _CPC register %u exceeds the PCC shared region\n", + cpc_desc->cpu_id, i); + *unsupported |= BIT(i); + } +} + +static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + + if (gas->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) + return gas->bit_width / 8; + + return cpc_reg_access_width(gas) / 8; +} + +static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, + u8 space_id, const char *name) +{ + unsigned int i, j; + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_register_resource *a = &cpc_desc->cpc_regs[i]; + const struct cpc_reg *a_gas; + u64 a_size; + + if (!CPC_SUPPORTED(a) || a->type != ACPI_TYPE_BUFFER || + a->cpc_entry.reg.space_id != space_id) + continue; + + a_gas = &a->cpc_entry.reg; + a_size = cpc_non_mmio_access_size(a); + for (j = i + 1; j < cpc_desc->num_entries - 2; j++) { + struct cpc_register_resource *b = &cpc_desc->cpc_regs[j]; + const struct cpc_reg *b_gas; + u64 b_size; + + if (!CPC_SUPPORTED(b) || b->type != ACPI_TYPE_BUFFER || + b->cpc_entry.reg.space_id != space_id) + continue; + + b_gas = &b->cpc_entry.reg; + b_size = cpc_non_mmio_access_size(b); + if (!cpc_reg_is_writable(i) && !cpc_reg_is_writable(j)) + continue; + if (a_gas->address < b_gas->address ? + b_gas->address - a_gas->address >= a_size : + a_gas->address - b_gas->address >= b_size) + continue; + + pr_err("CPU%d: overlapping writable %s _CPC registers %u and %u\n", + cpc_desc->cpu_id, name, i, j); + return -EINVAL; + } + } + + return 0; +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { int i; @@ -847,6 +1028,8 @@ static int check_pcc_chan(int pcc_ss_id, bool chk_err_bit) pcc_ss_data->deadline_us); if (likely(!ret)) { + /* Order completion status before reading the returned payload. */ + rmb(); pcc_ss_data->platform_owns_pcc = false; if (chk_err_bit && (status & PCC_ERROR_MASK)) ret = -EIO; @@ -1290,6 +1473,7 @@ static int pcc_data_alloc(int pcc_ss_id) ret = -ENOMEM; goto out_unlock; } + raw_spin_lock_init(&data->payload_lock); pcc_data[pcc_ss_id] = data; } data->refcount++; @@ -1365,11 +1549,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) struct device *cpu_dev; acpi_handle handle = pr->handle; unsigned int num_ent, i, cpc_rev; + u32 unsupported_regs = 0; int pcc_subspace_id = -1; bool pcc_data_ref = false; bool cpc_present = false; acpi_status status; - int ret = -ENODATA; + int ret = -EINVAL; int err; if (per_cpu(cpc_desc_ptr, pr->id)) @@ -1519,6 +1704,24 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) * so extract it only once. */ if (gas_t->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + /* These registers have no specified 32-bit upper bound. */ + bool wide_write = i - 2 == PERF_LIMITED || + i - 2 == ENABLE || + i - 2 == AUTO_SEL_ENABLE; + bool write_width_supported = gas_t->bit_width == 8 || + gas_t->bit_width == 16 || + gas_t->bit_width == 32 || + gas_t->bit_width == 64; + + if (!gas_t->bit_width || gas_t->bit_width > 64 || + gas_t->bit_offset || gas_t->bit_width % 8 || + (cpc_reg_is_writable(i - 2) && + (!write_width_supported || + (!wide_write && gas_t->bit_width > 32)))) { + unsupported_regs |= BIT(i - 2); + continue; + } + if (pcc_subspace_id < 0) { pcc_subspace_id = gas_t->access_width; err = pcc_data_alloc(pcc_subspace_id); @@ -1530,6 +1733,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } else if (pcc_subspace_id != gas_t->access_width) { pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", pr->id); + ret = -EINVAL; goto out_free; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { @@ -1537,21 +1741,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) void __iomem *addr; size_t access_width; - err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); - if (err && (i - 2 == DESIRED_PERF || - i - 2 == OSPM_NOMINAL_PERF)) { - const char *name = i - 2 == DESIRED_PERF ? - "Desired Performance" : - "OSPM Nominal Performance"; - - pr_warn("CPU%d: disabling inaccessible %s register\n", - pr->id, name); - cpc_disable_reg(cpc_ptr, i - 2); - continue; - } + err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, + i - 2); if (err) { - ret = err; - goto out_free; + unsupported_regs |= BIT(i - 2); + continue; } if (!cpc_is_readable(&cpc_ptr->cpc_regs[i - 2]) && !cpc_is_writable(&cpc_ptr->cpc_regs[i - 2])) @@ -1625,35 +1819,14 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_subspace_id; - /* - * Performance Limited is required by the specification, but tolerate a - * NULL descriptor used by firmware which cannot report limiting events. - * CPPC control does not depend on this status. - */ - for (i = 0; i < num_ent - 2; i++) { - if (i != DESIRED_PERF && i != PERF_LIMITED && - !IS_OPTIONAL_CPC_REG(i) && - !cpc_entry_present(&cpc_ptr->cpc_regs[i])) { - pr_debug("CPU:%d lacks mandatory _CPC register %u\n", - pr->id, i); - ret = -EINVAL; - goto out_free; - } - } + ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs); + if (ret) + goto out_free; + unsupported_regs = 0; - /* - * In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional - * only when AUTO_SEL_ENABLE is supported. - */ - if (!cpc_is_writable(&cpc_ptr->cpc_regs[DESIRED_PERF]) && - (!osc_sb_cppc2_support_acked || - cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].type != ACPI_TYPE_INTEGER || - cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].cpc_entry.int_value != 1)) { - pr_debug("CPU:%d lacks a writable Desired Performance register\n", - pr->id); - ret = -EINVAL; + ret = cpc_validate_required_controls(cpc_ptr); + if (ret) goto out_free; - } /* * Initialize the remaining cpc_regs as unsupported. @@ -1684,6 +1857,24 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (ret) goto out_free; } + if (pcc_subspace_id >= 0) { + cpc_validate_pcc_bounds(cpc_ptr, pcc_data[pcc_subspace_id], + &unsupported_regs); + + ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs); + if (ret) + goto out_free; + } + + ret = cpc_validate_non_mmio_overlaps(cpc_ptr, + ACPI_ADR_SPACE_PLATFORM_COMM, + "PCC"); + if (ret) + goto out_free; + + ret = cpc_validate_bound_controls(cpc_ptr); + if (ret) + goto out_free; /* Everything looks okay */ pr_debug("Parsed CPC struct for CPU: %d\n", pr->id); @@ -1810,6 +2001,9 @@ int __weak cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val) static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) { void __iomem *vaddr = NULL; + unsigned long flags; + u8 buf[sizeof(*val)]; + unsigned int i; int size; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cpc_reg *reg = ®_res->cpc_entry.reg; @@ -1837,15 +2031,30 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) *val = val_u32; return 0; - } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) { + } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id]) + return -ENODEV; + /* * For registers in PCC space, the register size is determined * by the bit width field; the access size is used to indicate * the PCC subspace id. */ vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id); - } - else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) + size = reg->bit_width / 8; + if (!size || size > sizeof(buf) || reg->bit_width % 8) + return -EFAULT; + + raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags); + memcpy_fromio(buf, vaddr, size); + raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock, + flags); + + *val = 0; + for (i = 0; i < size; i++) + *val |= (u64)buf[i] << (i * 8); + return 0; + } else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) return cpc_read_ffh(cpu, reg, val); @@ -1867,18 +2076,12 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) *val = readq_relaxed(vaddr); break; default: - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - pr_debug("Error: Cannot read %u bit width from system memory: 0x%llx\n", - size, reg->address); - } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { - pr_debug("Error: Cannot read %u bit width from PCC for ss: %d\n", - size, pcc_ss_id); - } + pr_debug("Error: Cannot read %u bit width from system memory: 0x%llx\n", + size, reg->address); return -EFAULT; } - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) - *val = MASK_VAL_READ(reg, *val); + *val = MASK_VAL_READ(reg, *val); return 0; } @@ -1893,6 +2096,8 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) struct cpc_reg *reg; struct cpc_desc *cpc_desc; unsigned long flags; + u8 buf[sizeof(val)]; + unsigned int i; bool locked = false; if (reg_res->type != ACPI_TYPE_BUFFER) @@ -1917,15 +2122,31 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) } return 0; - } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id >= 0) { + } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id]) + return -ENODEV; + /* * For registers in PCC space, the register size is determined * by the bit width field; the access size is used to indicate * the PCC subspace id. */ vaddr = GET_PCC_VADDR(reg->address, pcc_ss_id); - } - else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) + size = reg->bit_width / 8; + if (!size || size > sizeof(buf) || reg->bit_width % 8) + return -EFAULT; + + for (i = 0; i < size; i++) + buf[i] = val >> (i * 8); + + raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags); + memcpy_toio(vaddr, buf, size); + /* Publish every payload byte before another CPU can ring the doorbell. */ + wmb(); + raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock, + flags); + return 0; + } else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) return cpc_write_ffh(cpu, reg, val); @@ -1933,44 +2154,42 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) return acpi_os_write_memory((acpi_physical_address)reg->address, val, size); - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - /* Partial fields and local overlaps use the descriptor lock. */ - locked = reg_res->cpc_entry.use_rmw_lock; - if (locked) { - cpc_desc = per_cpu(cpc_desc_ptr, cpu); - if (!cpc_desc) { - pr_debug("No CPC descriptor for CPU:%d\n", cpu); - return -ENODEV; - } - raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags); + /* Partial fields and local overlaps use the descriptor lock. */ + locked = reg_res->cpc_entry.use_rmw_lock; + if (locked) { + cpc_desc = per_cpu(cpc_desc_ptr, cpu); + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpu); + return -ENODEV; } + raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags); + } - if (reg->bit_offset || reg->bit_width != size) { - /* - * MASK_VAL_WRITE() discards the field's old bits, so undefined - * readback from a write-only field is not propagated. - */ - switch (size) { - case 8: - prev_val = readb_relaxed(vaddr); - break; - case 16: - prev_val = readw_relaxed(vaddr); - break; - case 32: - prev_val = readl_relaxed(vaddr); - break; - case 64: - prev_val = readq_relaxed(vaddr); - break; - default: - if (locked) - raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, - flags); - return -EFAULT; - } - val = MASK_VAL_WRITE(reg, prev_val, val); + if (reg->bit_offset || reg->bit_width != size) { + /* + * MASK_VAL_WRITE() discards the field's old bits, so undefined + * readback from a write-only field is not propagated. + */ + switch (size) { + case 8: + prev_val = readb_relaxed(vaddr); + break; + case 16: + prev_val = readw_relaxed(vaddr); + break; + case 32: + prev_val = readl_relaxed(vaddr); + break; + case 64: + prev_val = readq_relaxed(vaddr); + break; + default: + if (locked) + raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, + flags); + return -EFAULT; } + val = MASK_VAL_WRITE(reg, prev_val, val); } switch (size) { @@ -1987,13 +2206,8 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) writeq_relaxed(val, vaddr); break; default: - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - pr_debug("Error: Cannot write %u bit width to system memory: 0x%llx\n", - size, reg->address); - } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { - pr_debug("Error: Cannot write %u bit width to PCC for ss: %d\n", - size, pcc_ss_id); - } + pr_debug("Error: Cannot write %u bit width to system memory: 0x%llx\n", + size, reg->address); ret_val = -EFAULT; break; } @@ -2074,9 +2288,6 @@ static int cppc_set_reg_val_in_pcc(int cpu, struct cpc_register_resource *reg, u pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; } - if (!cpc_pcc_write_supported(reg)) - return -EFAULT; - pcc_ss_data = pcc_data[pcc_ss_id]; if (!pcc_ss_data) return -ENODEV; @@ -2490,9 +2701,6 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) auto_sel_pcc = cpc_is_writable(auto_sel_reg) && CPC_IN_PCC(auto_sel_reg); epp_pcc = cpc_is_writable(epp_set_reg) && CPC_IN_PCC(epp_set_reg); - if ((auto_sel_pcc && !cpc_pcc_write_supported(auto_sel_reg)) || - (epp_pcc && !cpc_pcc_write_supported(epp_set_reg))) - return -EFAULT; if ((cpc_is_writable(auto_sel_reg) && CPC_IN_SYSTEM_IO(auto_sel_reg) && !cpc_system_io_write_supported(auto_sel_reg)) || @@ -2838,12 +3046,6 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) !CPC_IN_PCC(max_perf_reg)); mixed_layout = pcc_layout && direct_layout; - /* Do not modify any control if a requested PCC field cannot be staged. */ - if ((desired_pcc && !cpc_pcc_write_supported(desired_reg)) || - (min_pcc && !cpc_pcc_write_supported(min_perf_reg)) || - (max_pcc && !cpc_pcc_write_supported(max_perf_reg))) - return -EFAULT; - if (mixed_layout || pcc_update) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); From ea5ed1f82e2273bf8c16eace6d701e2ea4e820d2 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:56 +0100 Subject: [PATCH 0549/2286] ACPI: CPPC: Validate SystemIO register layouts cpc_read() and cpc_write() access SystemIO registers using the complete GAS access width. cpc_read() does not extract a partial field, while the writer does not preserve bits outside one. A partial register can therefore return the wrong value or clobber adjacent fields on write. Retain valid read-only fields in 8-, 16-, and 32-bit access units. Extract them after reading the complete port. Continue to require writable controls to cover the complete access unit at Bit Offset zero. A partial write would require a serialized RMW. Accept Access Size zero when Bit Width supplies one of the supported sizes. Require natural alignment on architectures which implement port I/O through MMIO and may fault on unaligned Device-memory accesses. Preserve port layouts on x86; its native port-I/O instructions support them. When CONFIG_HAS_IOPORT is disabled, report unavailable port-I/O support and mark SystemIO layouts inaccessible during probe. Also return -EOPNOTSUPP explicitly in cpc_read() and cpc_write() so a SystemIO entry can never fall through and treat its port number as a physical-memory address. Resolve inaccessible entries using the control-specific policy established for PCC: optional fields can be disabled, while mandatory or semantically required controls fail probe. System I/O may use the same numeric port for different read and write registers, but a direction-dependent alias is safe only when the control is write-only. A readable control such as Minimum Performance would otherwise return the read-side capability's value, which cppc-cpufreq can then write back as its initial performance limit. Reject such aliases and overlapping writers; retain read-only overlaps and aliases between a read-only entry and a revision-4 write-only control. Remove the EPP setter's duplicate SystemIO geometry preflight now that probe validates every published writable control. Fixes: a2c8f92bea5f ("ACPI: CPPC: Implement support for SystemIO registers") Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-12-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 110 +++++++++++++++++++++++---------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index fe1d109afbd56f..10cf96236dfbd5 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -244,7 +244,6 @@ static bool cpc_integer_entry_valid(unsigned int reg_idx, u64 value, */ #define NUM_RETRIES 500ULL -#define OVER_16BTS_MASK ~0xFFFFULL #define CPC_GENERIC_REGISTER_DESCRIPTOR 0x82 #define CPC_GENERIC_REGISTER_LENGTH (sizeof(struct cpc_reg) - 3) @@ -297,23 +296,6 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); ((((val) & GENMASK(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) | \ ((prev_val) & ~(GENMASK(((reg)->bit_width) - 1, 0) << (reg)->bit_offset))) \ -static bool -cpc_system_io_write_supported(const struct cpc_register_resource *reg) -{ - const struct cpc_reg *gas = ®->cpc_entry.reg; - unsigned int access_width = GET_BIT_WIDTH(gas); - u64 access_size; - - if (!IS_ENABLED(CONFIG_HAS_IOPORT) || !CPC_IN_SYSTEM_IO(reg) || - (access_width != 8 && access_width != 16 && access_width != 32)) - return false; - - access_size = access_width / 8; - return !gas->bit_offset && gas->bit_width == access_width && - gas->address <= U16_MAX - (access_size - 1) && - (IS_ENABLED(CONFIG_X86) || IS_ALIGNED(gas->address, access_size)); -} - static unsigned int cpc_reg_access_width(const struct cpc_reg *reg) { if (reg->access_width > 4) @@ -610,6 +592,17 @@ static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *reg) return cpc_reg_access_width(gas) / 8; } +static bool cpc_non_mmio_overlap_conflicts(u8 space_id, bool a_writable, + bool b_writable, bool a_write_only, + bool b_write_only) +{ + /* Only a write-only control can use a separate read-side port alias. */ + if (space_id == ACPI_ADR_SPACE_SYSTEM_IO && a_writable != b_writable) + return a_writable ? !a_write_only : !b_write_only; + + return a_writable || b_writable; +} + static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, u8 space_id, const char *name) { @@ -629,6 +622,7 @@ static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, for (j = i + 1; j < cpc_desc->num_entries - 2; j++) { struct cpc_register_resource *b = &cpc_desc->cpc_regs[j]; const struct cpc_reg *b_gas; + bool a_writable, b_writable; u64 b_size; if (!CPC_SUPPORTED(b) || b->type != ACPI_TYPE_BUFFER || @@ -637,7 +631,12 @@ static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, b_gas = &b->cpc_entry.reg; b_size = cpc_non_mmio_access_size(b); - if (!cpc_reg_is_writable(i) && !cpc_reg_is_writable(j)) + a_writable = cpc_reg_is_writable(i); + b_writable = cpc_reg_is_writable(j); + if (!cpc_non_mmio_overlap_conflicts(space_id, + a_writable, b_writable, + cpc_reg_is_write_only(cpc_desc, i), + cpc_reg_is_write_only(cpc_desc, j))) continue; if (a_gas->address < b_gas->address ? b_gas->address - a_gas->address >= a_size : @@ -1769,21 +1768,41 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr = addr; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { - if (gas_t->access_width < 1 || gas_t->access_width > 3) { - /* - * 1 = 8-bit, 2 = 16-bit, and 3 = 32-bit. - * SystemIO doesn't implement 64-bit - * registers. - */ - pr_debug("Invalid access width %d for SystemIO register in _CPC\n", - gas_t->access_width); - goto out_free; + u64 access_size; + const char *reason = "uses unsupported SystemIO geometry"; + unsigned int access_width; + bool unsupported; + + access_width = cpc_reg_access_width(gas_t); + unsupported = !IS_ENABLED(CONFIG_HAS_IOPORT); + if (unsupported) + reason = "requires unavailable SystemIO support"; + else + unsupported = access_width != 8 && + access_width != 16 && + access_width != 32; + if (!unsupported) { + access_size = access_width / 8; + unsupported = !gas_t->bit_width || + gas_t->bit_width > access_width || + gas_t->bit_offset >= access_width || + gas_t->bit_width > access_width - + gas_t->bit_offset; } - if (gas_t->address & OVER_16BTS_MASK) { - /* SystemIO registers use 16-bit integer addresses */ - pr_debug("Invalid IO port %llu for SystemIO register in _CPC\n", - gas_t->address); - goto out_free; + if (!unsupported) { + unsupported = (cpc_reg_is_writable(i - 2) && + (gas_t->bit_offset || + gas_t->bit_width != access_width)) || + !cpc_reg_access_aligned(gas_t, + access_size) || + gas_t->address > + U16_MAX - (access_size - 1); + } + if (unsupported) { + pr_debug("CPU%d: _CPC register %u %s\n", + pr->id, i - 2, reason); + unsupported_regs |= BIT(i - 2); + continue; } if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); @@ -1871,6 +1890,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) "PCC"); if (ret) goto out_free; + ret = cpc_validate_non_mmio_overlaps(cpc_ptr, + ACPI_ADR_SPACE_SYSTEM_IO, + "SystemIO"); + if (ret) + goto out_free; ret = cpc_validate_bound_controls(cpc_ptr); if (ret) @@ -2016,11 +2040,13 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) *val = 0; size = GET_BIT_WIDTH(reg); - if (IS_ENABLED(CONFIG_HAS_IOPORT) && - reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { u32 val_u32; acpi_status status; + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) + return -EOPNOTSUPP; + status = acpi_os_read_port((acpi_io_address)reg->address, &val_u32, size); if (ACPI_FAILURE(status)) { @@ -2029,7 +2055,7 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) return -EFAULT; } - *val = val_u32; + *val = MASK_VAL_READ(reg, val_u32); return 0; } else if (reg->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id]) @@ -2109,10 +2135,12 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) size = GET_BIT_WIDTH(reg); - if (IS_ENABLED(CONFIG_HAS_IOPORT) && - reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { + if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { acpi_status status; + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) + return -EOPNOTSUPP; + status = acpi_os_write_port((acpi_io_address)reg->address, (u32)val, size); if (ACPI_FAILURE(status)) { @@ -2701,12 +2729,6 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) auto_sel_pcc = cpc_is_writable(auto_sel_reg) && CPC_IN_PCC(auto_sel_reg); epp_pcc = cpc_is_writable(epp_set_reg) && CPC_IN_PCC(epp_set_reg); - if ((cpc_is_writable(auto_sel_reg) && - CPC_IN_SYSTEM_IO(auto_sel_reg) && - !cpc_system_io_write_supported(auto_sel_reg)) || - (cpc_is_writable(epp_set_reg) && CPC_IN_SYSTEM_IO(epp_set_reg) && - !cpc_system_io_write_supported(epp_set_reg))) - return -EOPNOTSUPP; auto_sel_non_pcc = cpc_is_writable(auto_sel_reg) && !auto_sel_pcc; epp_non_pcc = cpc_is_writable(epp_set_reg) && !epp_pcc; From 908a87126a7e484d60754104705d875b38c2cc20 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:57 +0100 Subject: [PATCH 0550/2286] ACPI: CPPC: Validate PCC overlaps across processors PCC shared-memory offsets are physical within a subspace, but the existing overlap check stops at one _CPC package. Two processors can therefore pass probe with writable ranges which overlap in the same PCC subspace. This is unsafe in the performance path, where CPUs may stage requests concurrently while holding the shared side of pcc_lock. Partially overlapping stores can construct a payload which belongs to neither request before a doorbell submits it. Index retained PCC byte ranges by subspace and physical interval. The registry checks entries within the new descriptor as well as other CPUs, replacing the separate same-descriptor PCC overlap pass. Permit read-only overlap and exact aliases of the same logical CPPC entry. The per-subspace payload lock serializes each exact-alias field store, including multi-byte fields copied with byte-oriented I/O. Reject every other overlap involving a writable entry. Each CPC descriptor owns its interval records. Remove them when processor teardown unpublishes the descriptor and on probe failure. Fixes: 80b8286aeec0 ("ACPI / CPPC: support for batching CPPC requests") Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-13-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 178 +++++++++++++++++++++++++++++++++++++-- include/acpi/cppc_acpi.h | 2 + 2 files changed, 175 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 10cf96236dfbd5..fb9766f2cdd84a 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -113,6 +113,18 @@ struct cpc_sysmem_node { bool registered; }; +struct cpc_non_mmio_node { + struct rb_node rb; + u64 subtree_last; + u64 start; + u64 last; + struct cpc_desc *desc; + unsigned int reg_idx; + u8 space_id; + u8 pcc_ss_id; + bool registered; +}; + #define CPC_SYSMEM_START(node) ((node)->start) #define CPC_SYSMEM_LAST(node) ((node)->last) @@ -123,6 +135,16 @@ INTERVAL_TREE_DEFINE(struct cpc_sysmem_node, rb, u64, subtree_last, static struct rb_root_cached cpc_sysmem_tree = RB_ROOT_CACHED; static DEFINE_MUTEX(cpc_sysmem_lock); +#define CPC_NON_MMIO_START(node) ((node)->start) +#define CPC_NON_MMIO_LAST(node) ((node)->last) + +INTERVAL_TREE_DEFINE(struct cpc_non_mmio_node, rb, u64, subtree_last, + CPC_NON_MMIO_START, CPC_NON_MMIO_LAST, static inline, + cpc_non_mmio_itree) + +static struct rb_root_cached cpc_pcc_trees[MAX_PCC_SUBSPACES]; +static DEFINE_MUTEX(cpc_non_mmio_lock); + static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) { return cpc_sysmem_itree_iter_first(&cpc_sysmem_tree, start, last); @@ -652,6 +674,150 @@ static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, return 0; } +static struct rb_root_cached *cpc_non_mmio_tree(u8 space_id, u8 pcc_ss_id) +{ + if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM) + return &cpc_pcc_trees[pcc_ss_id]; + return NULL; +} + +static int cpc_validate_non_mmio_pair(const struct cpc_non_mmio_node *a, + const struct cpc_non_mmio_node *b) +{ + bool a_writable = cpc_reg_is_writable(a->reg_idx); + bool b_writable = cpc_reg_is_writable(b->reg_idx); + const char *name; + + if (!a_writable && !b_writable) + return 0; + + if (a->reg_idx == b->reg_idx && a->start == b->start && + a->last == b->last) + return 0; + + name = "PCC"; + pr_err("CPU%d: %s _CPC register %u conflicts with CPU%d register %u\n", + a->desc->cpu_id, name, a->reg_idx, b->desc->cpu_id, + b->reg_idx); + return -EINVAL; +} + +static void cpc_unregister_non_mmio_desc_locked(struct cpc_desc *cpc_desc) +{ + unsigned int i; + + if (!cpc_desc->non_mmio_nodes) + return; + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_non_mmio_node *node = &cpc_desc->non_mmio_nodes[i]; + struct rb_root_cached *tree; + + if (!node->registered) + continue; + + tree = cpc_non_mmio_tree(node->space_id, node->pcc_ss_id); + cpc_non_mmio_itree_remove(node, tree); + } + + kfree(cpc_desc->non_mmio_nodes); + cpc_desc->non_mmio_nodes = NULL; +} + +static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, + int pcc_ss_id) +{ + unsigned int nr_regs = cpc_desc->num_entries - 2; + unsigned int i; + int ret = 0; + bool found = false; + + for (i = 0; i < nr_regs; i++) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + u8 space_id; + + if (!CPC_SUPPORTED(reg) || reg->type != ACPI_TYPE_BUFFER) + continue; + space_id = reg->cpc_entry.reg.space_id; + if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + found = true; + break; + } + } + if (!found) + return 0; + + cpc_desc->non_mmio_nodes = kcalloc(nr_regs, + sizeof(*cpc_desc->non_mmio_nodes), + GFP_KERNEL); + if (!cpc_desc->non_mmio_nodes) + return -ENOMEM; + + mutex_lock(&cpc_non_mmio_lock); + + for (i = 0; i < nr_regs; i++) { + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + struct cpc_non_mmio_node *match, *node; + struct rb_root_cached *tree; + u8 space_id; + u64 size; + + if (!CPC_SUPPORTED(reg) || reg->type != ACPI_TYPE_BUFFER) + continue; + + space_id = reg->cpc_entry.reg.space_id; + if (space_id != ACPI_ADR_SPACE_PLATFORM_COMM) + continue; + + if (pcc_ss_id < 0) { + ret = -EINVAL; + goto out_unregister; + } + + node = &cpc_desc->non_mmio_nodes[i]; + size = cpc_non_mmio_access_size(reg); + node->start = reg->cpc_entry.reg.address; + node->last = node->start + size - 1; + node->desc = cpc_desc; + node->reg_idx = i; + node->space_id = space_id; + node->pcc_ss_id = pcc_ss_id; + tree = cpc_non_mmio_tree(space_id, node->pcc_ss_id); + + match = cpc_non_mmio_itree_iter_first(tree, node->start, + node->last); + while (match) { + ret = cpc_validate_non_mmio_pair(node, match); + if (ret) + goto out_unregister; + + match = cpc_non_mmio_itree_iter_next(match, node->start, + node->last); + } + + cpc_non_mmio_itree_insert(node, tree); + node->registered = true; + } + + mutex_unlock(&cpc_non_mmio_lock); + return 0; + +out_unregister: + cpc_unregister_non_mmio_desc_locked(cpc_desc); + mutex_unlock(&cpc_non_mmio_lock); + return ret; +} + +static void cpc_unregister_non_mmio_desc(struct cpc_desc *cpc_desc) +{ + if (!cpc_desc->non_mmio_nodes) + return; + + mutex_lock(&cpc_non_mmio_lock); + cpc_unregister_non_mmio_desc_locked(cpc_desc); + mutex_unlock(&cpc_non_mmio_lock); +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { int i; @@ -985,6 +1151,7 @@ static void cppc_free_desc(struct cpc_desc *cpc_ptr) { unsigned int i; + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); for (i = 2; i < cpc_ptr->num_entries; i++) { @@ -1885,11 +2052,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } - ret = cpc_validate_non_mmio_overlaps(cpc_ptr, - ACPI_ADR_SPACE_PLATFORM_COMM, - "PCC"); - if (ret) - goto out_free; ret = cpc_validate_non_mmio_overlaps(cpc_ptr, ACPI_ADR_SPACE_SYSTEM_IO, "SystemIO"); @@ -1900,6 +2062,10 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (ret) goto out_free; + ret = cpc_register_non_mmio_desc(cpc_ptr, pcc_subspace_id); + if (ret) + goto out_free; + /* Everything looks okay */ pr_debug("Parsed CPC struct for CPU: %d\n", pr->id); @@ -1917,6 +2083,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) "acpi_cppc"); if (ret) { per_cpu(cpc_desc_ptr, pr->id) = NULL; + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); kobject_put(&cpc_ptr->kobj); goto out_pcc_put; @@ -1961,6 +2128,7 @@ void acpi_cppc_processor_exit(struct acpi_processor *pr) pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); per_cpu(cpc_desc_ptr, pr->id) = NULL; kobject_del(&cpc_ptr->kobj); + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); pcc_data_put(pcc_ss_id); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 53186da6a654a4..4b5d94f853dc70 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -80,6 +80,7 @@ struct cpc_register_resource { }; struct cpc_sysmem_node; +struct cpc_non_mmio_node; /* Container to hold the CPC details for each CPU */ struct cpc_desc { @@ -93,6 +94,7 @@ struct cpc_desc { struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT]; struct acpi_psd_package domain_info; struct cpc_sysmem_node *sysmem_nodes; + struct cpc_non_mmio_node *non_mmio_nodes; struct kobject kobj; }; From 3051865865a88b41c5df881d44a6e3d1e85675ba Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:58 +0100 Subject: [PATCH 0551/2286] ACPI: CPPC: Validate SystemIO overlaps across processors System I/O port space is global, but the overlap check only compares registers within one _CPC package. Whole-width accesses from different processors can therefore target partially overlapping port ranges and clobber each other. Extend the physical interval registry to SystemIO and remove the redundant same-descriptor overlap pass, now covered by the registry. Retain the direction-aware policy from the SystemIO layout check: a read-only entry may alias a write-only control, but not a readable control whose readback would then select the wrong register. Also permit read-only overlaps and exact full-width aliases of the same logical writable CPPC entry. Reject other overlaps involving a writable entry. Fixes: a2c8f92bea5f ("ACPI: CPPC: Implement support for SystemIO registers") Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-14-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 77 +++++++++------------------------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index fb9766f2cdd84a..b1c29a4e5d8e6b 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -143,6 +143,7 @@ INTERVAL_TREE_DEFINE(struct cpc_non_mmio_node, rb, u64, subtree_last, cpc_non_mmio_itree) static struct rb_root_cached cpc_pcc_trees[MAX_PCC_SUBSPACES]; +static struct rb_root_cached cpc_sysio_tree = RB_ROOT_CACHED; static DEFINE_MUTEX(cpc_non_mmio_lock); static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) @@ -625,59 +626,12 @@ static bool cpc_non_mmio_overlap_conflicts(u8 space_id, bool a_writable, return a_writable || b_writable; } -static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, - u8 space_id, const char *name) -{ - unsigned int i, j; - - for (i = 0; i < cpc_desc->num_entries - 2; i++) { - struct cpc_register_resource *a = &cpc_desc->cpc_regs[i]; - const struct cpc_reg *a_gas; - u64 a_size; - - if (!CPC_SUPPORTED(a) || a->type != ACPI_TYPE_BUFFER || - a->cpc_entry.reg.space_id != space_id) - continue; - - a_gas = &a->cpc_entry.reg; - a_size = cpc_non_mmio_access_size(a); - for (j = i + 1; j < cpc_desc->num_entries - 2; j++) { - struct cpc_register_resource *b = &cpc_desc->cpc_regs[j]; - const struct cpc_reg *b_gas; - bool a_writable, b_writable; - u64 b_size; - - if (!CPC_SUPPORTED(b) || b->type != ACPI_TYPE_BUFFER || - b->cpc_entry.reg.space_id != space_id) - continue; - - b_gas = &b->cpc_entry.reg; - b_size = cpc_non_mmio_access_size(b); - a_writable = cpc_reg_is_writable(i); - b_writable = cpc_reg_is_writable(j); - if (!cpc_non_mmio_overlap_conflicts(space_id, - a_writable, b_writable, - cpc_reg_is_write_only(cpc_desc, i), - cpc_reg_is_write_only(cpc_desc, j))) - continue; - if (a_gas->address < b_gas->address ? - b_gas->address - a_gas->address >= a_size : - a_gas->address - b_gas->address >= b_size) - continue; - - pr_err("CPU%d: overlapping writable %s _CPC registers %u and %u\n", - cpc_desc->cpu_id, name, i, j); - return -EINVAL; - } - } - - return 0; -} - static struct rb_root_cached *cpc_non_mmio_tree(u8 space_id, u8 pcc_ss_id) { if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM) return &cpc_pcc_trees[pcc_ss_id]; + if (space_id == ACPI_ADR_SPACE_SYSTEM_IO) + return &cpc_sysio_tree; return NULL; } @@ -688,14 +642,18 @@ static int cpc_validate_non_mmio_pair(const struct cpc_non_mmio_node *a, bool b_writable = cpc_reg_is_writable(b->reg_idx); const char *name; - if (!a_writable && !b_writable) + if (!cpc_non_mmio_overlap_conflicts(a->space_id, a_writable, + b_writable, + cpc_reg_is_write_only(a->desc, a->reg_idx), + cpc_reg_is_write_only(b->desc, b->reg_idx))) return 0; if (a->reg_idx == b->reg_idx && a->start == b->start && a->last == b->last) return 0; - name = "PCC"; + name = a->space_id == ACPI_ADR_SPACE_PLATFORM_COMM ? + "PCC" : "SystemIO"; pr_err("CPU%d: %s _CPC register %u conflicts with CPU%d register %u\n", a->desc->cpu_id, name, a->reg_idx, b->desc->cpu_id, b->reg_idx); @@ -739,7 +697,8 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, if (!CPC_SUPPORTED(reg) || reg->type != ACPI_TYPE_BUFFER) continue; space_id = reg->cpc_entry.reg.space_id; - if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM) { + if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM || + space_id == ACPI_ADR_SPACE_SYSTEM_IO) { found = true; break; } @@ -766,10 +725,11 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, continue; space_id = reg->cpc_entry.reg.space_id; - if (space_id != ACPI_ADR_SPACE_PLATFORM_COMM) + if (space_id != ACPI_ADR_SPACE_PLATFORM_COMM && + space_id != ACPI_ADR_SPACE_SYSTEM_IO) continue; - if (pcc_ss_id < 0) { + if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id < 0) { ret = -EINVAL; goto out_unregister; } @@ -781,7 +741,8 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, node->desc = cpc_desc; node->reg_idx = i; node->space_id = space_id; - node->pcc_ss_id = pcc_ss_id; + node->pcc_ss_id = space_id == ACPI_ADR_SPACE_PLATFORM_COMM ? + pcc_ss_id : 0; tree = cpc_non_mmio_tree(space_id, node->pcc_ss_id); match = cpc_non_mmio_itree_iter_first(tree, node->start, @@ -2052,12 +2013,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } - ret = cpc_validate_non_mmio_overlaps(cpc_ptr, - ACPI_ADR_SPACE_SYSTEM_IO, - "SystemIO"); - if (ret) - goto out_free; - ret = cpc_validate_bound_controls(cpc_ptr); if (ret) goto out_free; From a97aaf2aedae282eb668397428046cbd52435334 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:27:59 +0100 Subject: [PATCH 0552/2286] ACPI: CPPC: Clear Performance Limited without a stale read The Performance Limited status bits remain set until reset or OSPM clears them by writing zero. ACPI does not define the effect of writing one, so a plain read/write implementation may set a status bit when OSPM writes one. cppc_set_perf_limited() used a read-modify-write. An excursion reported between the transactions could be cleared by the stale write. Conversely, writing ones directly to bits not selected for clearing can manufacture an event. Clearing both bits with a literal zero is portable without a read; reject selective clears, which require an interlocked RMW that Linux cannot provide across all CPPC address spaces. A partial SystemMemory or SystemIO field needs an RMW to preserve the access unit. Keep it readable, but reject clears. Classify a field as a writer during overlap validation only when both its _CPC semantics and validated resource permit writing. Other writers can replay stale status, so drop only disjoint optional SystemMemory controls and reject required or overlapping writers. A present Autonomous Selection Enable control is required, because hiding it could leave autonomous mode enabled while OSPM believes it disabled. Retain inaccessible Performance Limited ranges without accessing them, so overlap validation cannot hide asynchronous status. For PCC, record the claim in its own subspace without letting it select the channel for usable controls. Bounds-check only entries in the acquired subspace. A status range that exceeds its shared region still protects its known inbounds portion from overlap with a writer. Likewise retain a SystemIO status range whose first port is valid but whose complete access extends beyond 0xffff. The retained range only rejects an overlapping valid writer. Fixes: 13c45a26635f ("ACPI: CPPC: add APIs and sysfs interface for perf_limited") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com Reported-by: Sumit Gupta Link: https://lore.kernel.org/lkml/d5f1ea9b-53b7-4db2-983a-b5be8e71a371@arm.com/ Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-15-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- .../ABI/testing/sysfs-devices-system-cpu | 16 +- drivers/acpi/cppc_acpi.c | 352 ++++++++++++++---- 2 files changed, 293 insertions(+), 75 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 82d10d556cc89f..73c8c204820df1 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -335,12 +335,16 @@ Description: Performance Limited Read to check if platform throttling (thermal/power/current limits) caused delivered performance to fall below the requested level. A non-zero value indicates throttling occurred. - - Write the bitmask of bits to clear: - - - 0x1 = clear bit 0 (desired performance excursion) - - 0x2 = clear bit 1 (minimum performance excursion) - - 0x3 = clear both bits + If firmware provides the register but Linux cannot access it + safely, reads return the literal string "". + + Write 0x3 to clear both bits. Selectively clearing one bit is + not supported because ACPI does not define the effect of writing + one to these status bits and an interlocked read-modify-write is + not generally available. + Writing 0x3 returns -EOPNOTSUPP when firmware describes the + register using an access which Linux cannot clear safely; reads + may remain available in that case. The platform sets these bits; OSPM can only clear them. diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index b1c29a4e5d8e6b..6d130381245ecc 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -413,6 +413,16 @@ static void cpc_disable_reg(struct cpc_desc *cpc_desc, unsigned int reg_idx) reg->cpc_entry.int_value = 0; } +static bool cpc_optional_writer_can_be_disabled(unsigned int reg_idx) +{ + if (!IS_OPTIONAL_CPC_REG(reg_idx) || !cpc_reg_is_writable(reg_idx) || + reg_idx == MIN_PERF || reg_idx == MAX_PERF || reg_idx == ENABLE || + reg_idx == AUTO_SEL_ENABLE) + return false; + + return true; +} + static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *reg) { const struct cpc_reg *gas = ®->cpc_entry.reg; @@ -443,6 +453,22 @@ static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc, if (!cpc_reg_access_aligned(gas, access_size)) goto invalid; + if (reg_idx == PERF_LIMITED) { + if (access_width == 64 && !IS_ENABLED(CONFIG_64BIT)) { + pr_warn_once("CPU%d: Performance Limited register cannot be accessed atomically; keeping its range reserved\n", + cpc_desc->cpu_id); + cpc_desc->cpc_regs[reg_idx].cpc_entry.read_unsupported = true; + cpc_desc->cpc_regs[reg_idx].cpc_entry.write_unsupported = true; + return 0; + } + + if (gas->bit_offset || gas->bit_width != access_width) { + pr_warn_once("CPU%d: Performance Limited register cannot be cleared safely; keeping it readable\n", + cpc_desc->cpu_id); + cpc_desc->cpc_regs[reg_idx].cpc_entry.write_unsupported = true; + } + } + return 0; invalid: @@ -452,12 +478,17 @@ static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc, access_size = access_width / 8; if (gas->bit_width) access_size = max(access_size, cpc_sysmem_field_size(gas)); - if (cpc_reg_is_write_only(cpc_desc, reg_idx) && gas->address && - access_size && gas->address <= U64_MAX - (access_size - 1)) { + if ((cpc_reg_is_write_only(cpc_desc, reg_idx) || + reg_idx == PERF_LIMITED) && gas->address && access_size && + gas->address <= U64_MAX - (access_size - 1)) { struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; - pr_warn("CPU%d: _CPC v%d register %u is inaccessible; keeping its range reserved\n", - cpc_desc->cpu_id, cpc_desc->version, reg_idx); + if (reg_idx == PERF_LIMITED) + pr_warn_once("CPU%d: _CPC v%d register %u is inaccessible; keeping its range reserved\n", + cpc_desc->cpu_id, cpc_desc->version, reg_idx); + else + pr_warn("CPU%d: _CPC v%d register %u is inaccessible; keeping its range reserved\n", + cpc_desc->cpu_id, cpc_desc->version, reg_idx); reg->cpc_entry.read_unsupported = true; reg->cpc_entry.write_unsupported = true; return 0; @@ -477,6 +508,9 @@ static bool cpc_immutable_autonomous(const struct cpc_desc *cpc_desc) reg->cpc_entry.int_value == 1; } +static bool cpc_retain_pcc_status(struct cpc_desc *cpc_desc, + unsigned int reg_idx); + static int cpc_resolve_unsupported(struct cpc_desc *cpc_desc, u32 unsupported) { @@ -502,6 +536,18 @@ static int cpc_resolve_unsupported(struct cpc_desc *cpc_desc, if (!(unsupported & BIT(i))) continue; + /* CPPC control does not depend on Performance Limited status. */ + if (i == PERF_LIMITED) { + if (CPC_IN_PCC(&cpc_desc->cpc_regs[i]) && + cpc_retain_pcc_status(cpc_desc, i)) + continue; + + pr_warn_once("CPU%d: ignoring inaccessible Performance Limited register\n", + cpc_desc->cpu_id); + cpc_disable_reg(cpc_desc, i); + continue; + } + if (i == DESIRED_PERF && cpc_immutable_autonomous(cpc_desc)) { pr_warn("CPU%d: ignoring inaccessible Desired Performance register in autonomous mode\n", cpc_desc->cpu_id); @@ -576,8 +622,60 @@ static int cpc_validate_bound_controls(struct cpc_desc *cpc_desc) return 0; } +static bool +cpc_retain_pcc_status(struct cpc_desc *cpc_desc, unsigned int reg_idx) +{ + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; + const struct cpc_reg *gas = ®->cpc_entry.reg; + u64 size; + + if (reg_idx != PERF_LIMITED || !gas->bit_width) + return false; + + size = DIV_ROUND_UP((u64)gas->bit_offset + gas->bit_width, 8); + if (!size || gas->address > U64_MAX - (size - 1)) + return false; + + reg->cpc_entry.read_unsupported = true; + reg->cpc_entry.write_unsupported = true; + pr_warn_once("CPU%d: Performance Limited register cannot be accessed; keeping its PCC range reserved\n", + cpc_desc->cpu_id); + return true; +} + +static bool +cpc_retain_sysio_status(struct cpc_desc *cpc_desc, unsigned int reg_idx) +{ + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; + const struct cpc_reg *gas = ®->cpc_entry.reg; + + if (reg_idx != PERF_LIMITED || !gas->bit_width) + return false; + + /* Retain any in-range portion for overlap validation only. */ + if (gas->address > U16_MAX) + return false; + + pr_warn_once("CPU%d: Performance Limited register cannot be accessed; keeping its SystemIO range reserved\n", + cpc_desc->cpu_id); + reg->cpc_entry.read_unsupported = true; + reg->cpc_entry.write_unsupported = true; + return true; +} + +static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + + if (gas->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) + return DIV_ROUND_UP((u64)gas->bit_offset + gas->bit_width, 8); + + return max((u64)cpc_reg_access_width(gas) / 8, + DIV_ROUND_UP((u64)gas->bit_offset + gas->bit_width, 8)); +} + static void cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc, - struct cppc_pcc_data *data, + int pcc_ss_id, struct cppc_pcc_data *data, u32 *unsupported) { u64 shmem_size = data->pcc_channel->shmem_size; @@ -593,7 +691,9 @@ static void cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc, continue; gas = ®->cpc_entry.reg; - access_size = gas->bit_width / 8; + if (gas->access_width != pcc_ss_id) + continue; + access_size = cpc_non_mmio_access_size(reg); if (shmem_size >= CPC_PCC_HEADER_SIZE && gas->address <= shmem_size - CPC_PCC_HEADER_SIZE && access_size <= shmem_size - CPC_PCC_HEADER_SIZE - gas->address) @@ -605,14 +705,19 @@ static void cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc, } } -static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *reg) +static bool cpc_pcc_access_needed(const struct cpc_desc *cpc_desc) { - const struct cpc_reg *gas = ®->cpc_entry.reg; + unsigned int i; - if (gas->space_id == ACPI_ADR_SPACE_PLATFORM_COMM) - return gas->bit_width / 8; + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + const struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; + + if (CPC_SUPPORTED(reg) && CPC_IN_PCC(reg) && + (cpc_is_readable(reg) || cpc_is_writable(reg))) + return true; + } - return cpc_reg_access_width(gas) / 8; + return false; } static bool cpc_non_mmio_overlap_conflicts(u8 space_id, bool a_writable, @@ -626,6 +731,14 @@ static bool cpc_non_mmio_overlap_conflicts(u8 space_id, bool a_writable, return a_writable || b_writable; } +static bool +cpc_sysio_perf_limited_conflicts(unsigned int a_idx, bool a_writable, + unsigned int b_idx, bool b_writable) +{ + return (a_idx == PERF_LIMITED && b_writable) || + (b_idx == PERF_LIMITED && a_writable); +} + static struct rb_root_cached *cpc_non_mmio_tree(u8 space_id, u8 pcc_ss_id) { if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM) @@ -635,21 +748,45 @@ static struct rb_root_cached *cpc_non_mmio_tree(u8 space_id, u8 pcc_ss_id) return NULL; } +static bool cpc_same_non_mmio_register(const struct cpc_non_mmio_node *a, + const struct cpc_non_mmio_node *b) +{ + const struct cpc_reg *a_gas = + &a->desc->cpc_regs[a->reg_idx].cpc_entry.reg; + const struct cpc_reg *b_gas = + &b->desc->cpc_regs[b->reg_idx].cpc_entry.reg; + + return a->space_id == b->space_id && a->pcc_ss_id == b->pcc_ss_id && + a->reg_idx == b->reg_idx && a->start == b->start && + a->last == b->last && a_gas->bit_offset == b_gas->bit_offset && + a_gas->bit_width == b_gas->bit_width && + (a->space_id == ACPI_ADR_SPACE_PLATFORM_COMM || + cpc_reg_access_width(a_gas) == cpc_reg_access_width(b_gas)); +} + static int cpc_validate_non_mmio_pair(const struct cpc_non_mmio_node *a, const struct cpc_non_mmio_node *b) { - bool a_writable = cpc_reg_is_writable(a->reg_idx); - bool b_writable = cpc_reg_is_writable(b->reg_idx); + const struct cpc_register_resource *a_reg; + const struct cpc_register_resource *b_reg; + bool a_writable, b_writable; const char *name; + a_reg = &a->desc->cpc_regs[a->reg_idx]; + b_reg = &b->desc->cpc_regs[b->reg_idx]; + a_writable = cpc_reg_is_writable(a->reg_idx) && cpc_is_writable(a_reg); + b_writable = cpc_reg_is_writable(b->reg_idx) && cpc_is_writable(b_reg); + if (!cpc_non_mmio_overlap_conflicts(a->space_id, a_writable, b_writable, cpc_reg_is_write_only(a->desc, a->reg_idx), - cpc_reg_is_write_only(b->desc, b->reg_idx))) + cpc_reg_is_write_only(b->desc, b->reg_idx)) && + !(a->space_id == ACPI_ADR_SPACE_SYSTEM_IO && + cpc_sysio_perf_limited_conflicts(a->reg_idx, a_writable, + b->reg_idx, b_writable))) return 0; - if (a->reg_idx == b->reg_idx && a->start == b->start && - a->last == b->last) + if (cpc_same_non_mmio_register(a, b)) return 0; name = a->space_id == ACPI_ADR_SPACE_PLATFORM_COMM ? @@ -682,8 +819,7 @@ static void cpc_unregister_non_mmio_desc_locked(struct cpc_desc *cpc_desc) cpc_desc->non_mmio_nodes = NULL; } -static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, - int pcc_ss_id) +static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc) { unsigned int nr_regs = cpc_desc->num_entries - 2; unsigned int i; @@ -729,11 +865,6 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, space_id != ACPI_ADR_SPACE_SYSTEM_IO) continue; - if (space_id == ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id < 0) { - ret = -EINVAL; - goto out_unregister; - } - node = &cpc_desc->non_mmio_nodes[i]; size = cpc_non_mmio_access_size(reg); node->start = reg->cpc_entry.reg.address; @@ -742,7 +873,7 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, node->reg_idx = i; node->space_id = space_id; node->pcc_ss_id = space_id == ACPI_ADR_SPACE_PLATFORM_COMM ? - pcc_ss_id : 0; + reg->cpc_entry.reg.access_width : 0; tree = cpc_non_mmio_tree(space_id, node->pcc_ss_id); match = cpc_non_mmio_itree_iter_first(tree, node->start, @@ -755,7 +886,6 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, match = cpc_non_mmio_itree_iter_next(match, node->start, node->last); } - cpc_non_mmio_itree_insert(node, tree); node->registered = true; } @@ -786,7 +916,8 @@ static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) for (i = 0; i < cpc_desc->num_entries - 2; i++) { struct cpc_register_resource *reg = &cpc_desc->cpc_regs[i]; - if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg)) + if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg) && + cpc_is_writable(reg)) reg->cpc_entry.use_rmw_lock = cpc_sysmem_reg_needs_rmw(reg); } @@ -878,6 +1009,7 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, bool a_writable, b_writable; bool fields_overlap; + /* The overlap helper includes each descriptor's conservative claim. */ if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a) || !CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b) || !cpc_sysmem_access_units_overlap(a, b)) @@ -917,6 +1049,15 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, goto conflict; } + /* + * The platform may set Performance Limited asynchronously. A write to + * another field in the same access unit could write back stale status + * bits, which an OSPM lock cannot prevent. + */ + if ((a_idx == PERF_LIMITED && b_writable) || + (b_idx == PERF_LIMITED && a_writable)) + goto conflict; + /* A full-width writer must not overwrite another logical field. */ if (fields_overlap && ((a_writable && b_writable) || @@ -948,6 +1089,43 @@ static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, return -EINVAL; } +static bool cpc_disable_new_sysmem_writer(struct cpc_desc *cpc_desc, + unsigned int reg_idx, + const struct cpc_sysmem_node *node) +{ + struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx]; + struct cpc_sysmem_node *match; + unsigned int status_cpu = 0; + bool found = false; + + if (!cpc_optional_writer_can_be_disabled(reg_idx) || + !cpc_is_writable(reg)) + return false; + + match = cpc_sysmem_first(node->start, node->last); + while (match) { + const struct cpc_register_resource *status; + + if (match->reg_idx == PERF_LIMITED) { + status = &match->desc->cpc_regs[PERF_LIMITED]; + if (!status->cpc_entry.reg.bit_width || + cpc_sysmem_fields_overlap(reg, status)) + return false; + status_cpu = match->desc->cpu_id; + found = true; + } + + match = cpc_sysmem_next(match, node->start, node->last); + } + if (!found) + return false; + + pr_warn_once("CPU%d: ignoring optional _CPC register %u sharing CPU%d Performance Limited access unit\n", + cpc_desc->cpu_id, reg_idx, status_cpu); + cpc_disable_reg(cpc_desc, reg_idx); + return true; +} + static void cpc_unregister_sysmem_desc_locked(struct cpc_desc *cpc_desc) { unsigned int i; @@ -1030,15 +1208,19 @@ static int cpc_register_sysmem_desc(struct cpc_desc *cpc_desc) INIT_LIST_HEAD(&node->aliases); INIT_LIST_HEAD(&node->alias_node); + /* Performance Limited precedes every optional writer we may disable. */ + if (cpc_disable_new_sysmem_writer(cpc_desc, i, node)) + continue; + match = cpc_sysmem_first(node->start, node->last); while (match) { struct cpc_register_resource *match_reg; + match_reg = &match->desc->cpc_regs[match->reg_idx]; ret = cpc_validate_sysmem_pair(cpc_desc, i, match->desc, match->reg_idx); if (ret) goto out_unregister; - match_reg = &match->desc->cpc_regs[match->reg_idx]; if (cpc_desc == match->desc) { reg->cpc_entry.use_rmw_lock = true; match_reg->cpc_entry.use_rmw_lock = true; @@ -1519,8 +1701,6 @@ static int register_pcc_channel(int pcc_ss_idx) pcc_chan = pcc_mbox_request_channel(&cppc_mbox_cl, pcc_ss_idx); if (IS_ERR(pcc_chan)) { - pr_err("Failed to find PCC channel for subspace %d\n", - pcc_ss_idx); ret = -ENODEV; goto out_unlock; } @@ -1686,7 +1866,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (per_cpu(cpc_desc_ptr, pr->id)) return 0; - per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; if (!osc_sb_cppc2_support_acked) { @@ -1839,29 +2018,37 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) gas_t->bit_width == 16 || gas_t->bit_width == 32 || gas_t->bit_width == 64; + bool unsupported; - if (!gas_t->bit_width || gas_t->bit_width > 64 || - gas_t->bit_offset || gas_t->bit_width % 8 || - (cpc_reg_is_writable(i - 2) && - (!write_width_supported || - (!wide_write && gas_t->bit_width > 32)))) { - unsupported_regs |= BIT(i - 2); + unsupported = !gas_t->bit_width || + gas_t->bit_width > 64 || + gas_t->bit_offset || + gas_t->bit_width % 8 || + (cpc_reg_is_writable(i - 2) && + (!write_width_supported || + (!wide_write && gas_t->bit_width > 32))); + if (unsupported) { + if (!cpc_retain_pcc_status(cpc_ptr, i - 2)) + unsupported_regs |= BIT(i - 2); continue; } if (pcc_subspace_id < 0) { pcc_subspace_id = gas_t->access_width; + } else if (pcc_subspace_id != gas_t->access_width) { + pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", + pr->id); + ret = -EINVAL; + goto out_free; + } + + if (!pcc_data_ref) { err = pcc_data_alloc(pcc_subspace_id); if (err) { ret = err; goto out_free; } pcc_data_ref = true; - } else if (pcc_subspace_id != gas_t->access_width) { - pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", - pr->id); - ret = -EINVAL; - goto out_free; } } else if (gas_t->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { if (!IS_NULL_REG(gas_t)) { @@ -1899,6 +2086,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) u64 access_size; const char *reason = "uses unsupported SystemIO geometry"; unsigned int access_width; + bool partial = false; bool unsupported; access_width = cpc_reg_access_width(gas_t); @@ -1916,22 +2104,32 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) gas_t->bit_offset >= access_width || gas_t->bit_width > access_width - gas_t->bit_offset; + partial = gas_t->bit_offset || + gas_t->bit_width != access_width; } if (!unsupported) { unsupported = (cpc_reg_is_writable(i - 2) && - (gas_t->bit_offset || - gas_t->bit_width != access_width)) || + i - 2 != PERF_LIMITED && + cpc_is_writable(&cpc_ptr->cpc_regs[i - 2]) && + partial) || !cpc_reg_access_aligned(gas_t, access_size) || gas_t->address > U16_MAX - (access_size - 1); } if (unsupported) { + if (cpc_retain_sysio_status(cpc_ptr, i - 2)) + continue; pr_debug("CPU%d: _CPC register %u %s\n", pr->id, i - 2, reason); unsupported_regs |= BIT(i - 2); continue; } + if (i - 2 == PERF_LIMITED && partial) { + pr_warn_once("CPU%d: Performance Limited register cannot be cleared safely; keeping it readable\n", + cpc_ptr->cpu_id); + cpc_ptr->cpc_regs[i - 2].cpc_entry.write_unsupported = true; + } if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); ret = -EOPNOTSUPP; @@ -1964,7 +2162,9 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } } - per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_subspace_id; + + per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_data_ref ? + pcc_subspace_id : -1; ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs); if (ret) @@ -1999,25 +2199,35 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; /* Register PCC channel once for all PCC subspace ID. */ - if (pcc_subspace_id >= 0) { + if (pcc_data_ref) { ret = register_pcc_channel(pcc_subspace_id); - if (ret) + if (ret) { + pr_err("Failed to find PCC channel for subspace %d\n", + pcc_subspace_id); goto out_free; - } - if (pcc_subspace_id >= 0) { - cpc_validate_pcc_bounds(cpc_ptr, pcc_data[pcc_subspace_id], + } + + cpc_validate_pcc_bounds(cpc_ptr, pcc_subspace_id, + pcc_data[pcc_subspace_id], &unsupported_regs); ret = cpc_resolve_unsupported(cpc_ptr, unsupported_regs); if (ret) goto out_free; + + /* A range-only status entry needs the channel only for bounds. */ + if (!cpc_pcc_access_needed(cpc_ptr)) { + pcc_data_put(pcc_subspace_id); + pcc_data_ref = false; + per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; + } } ret = cpc_validate_bound_controls(cpc_ptr); if (ret) goto out_free; - ret = cpc_register_non_mmio_desc(cpc_ptr, pcc_subspace_id); + ret = cpc_register_non_mmio_desc(cpc_ptr); if (ret) goto out_free; @@ -2155,6 +2365,9 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cpc_reg *reg = ®_res->cpc_entry.reg; + if (!cpc_is_readable(reg_res)) + return -EOPNOTSUPP; + if (reg_res->type == ACPI_TYPE_INTEGER) { *val = reg_res->cpc_entry.int_value; return 0; @@ -2249,13 +2462,10 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) unsigned int i; bool locked = false; - if (reg_res->type != ACPI_TYPE_BUFFER) + if (!cpc_is_writable(reg_res)) return -EOPNOTSUPP; reg = ®_res->cpc_entry.reg; - if (IS_NULL_REG(reg)) - return -EOPNOTSUPP; - size = GET_BIT_WIDTH(reg); if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { @@ -2414,14 +2624,20 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs reg_idx, u64 *val) reg = &cpc_desc->cpc_regs[reg_idx]; - /* Desired may be absent for immutable autonomous selection. */ + /* + * Desired and Performance Limited may be disabled despite not being + * generally optional. + */ if ((reg->type == ACPI_TYPE_INTEGER && - (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx == DESIRED_PERF) && + (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx == DESIRED_PERF || + reg_idx == PERF_LIMITED) && !reg->cpc_entry.int_value) || (reg->type != ACPI_TYPE_INTEGER && IS_NULL_REG(®->cpc_entry.reg))) { pr_debug("CPC register is not supported\n"); return -EOPNOTSUPP; } + if (!cpc_is_readable(reg)) + return -EOPNOTSUPP; if (CPC_IN_PCC(reg)) return cppc_get_reg_val_in_pcc(cpu, reg, val); @@ -3412,7 +3628,7 @@ EXPORT_SYMBOL_GPL(cppc_get_perf_limited); /** * cppc_set_perf_limited() - Clear bits in the Performance Limited register. * @cpu: CPU on which to write register. - * @bits_to_clear: Bitmask of bits to clear in the perf_limited register. + * @bits_to_clear: Zero for no-op or CPPC_PERF_LIMITED_MASK to clear both bits. * * The Performance Limited register contains two sticky bits set by platform: * - Bit 0 (Desired_Excursion): Set when delivered performance is constrained @@ -3421,31 +3637,29 @@ EXPORT_SYMBOL_GPL(cppc_get_perf_limited); * below minimum performance. * * These bits are sticky and remain set until OSPM explicitly clears them. - * This function only allows clearing bits (the platform sets them). + * Selective clears are unsupported because they require an interlocked RMW. * * Return: 0 for success, -EINVAL for invalid bits, -EIO on register * access failure, -EOPNOTSUPP if not supported. */ int cppc_set_perf_limited(int cpu, u64 bits_to_clear) { - u64 current_val, new_val; - int ret; - /* Only bits 0 and 1 are valid */ - if (bits_to_clear & ~CPPC_PERF_LIMITED_MASK) + if (bits_to_clear & ~(u64)CPPC_PERF_LIMITED_MASK) return -EINVAL; if (!bits_to_clear) return 0; - ret = cppc_get_perf_limited(cpu, ¤t_val); - if (ret) - return ret; - - /* Clear the specified bits */ - new_val = current_val & ~bits_to_clear; + /* + * Writing zero clears both bits without depending on how a platform + * treats written ones. ACPI does not define the effect of writing one, + * so a selective clear cannot be implemented without an interlocked RMW. + */ + if (bits_to_clear != CPPC_PERF_LIMITED_MASK) + return -EOPNOTSUPP; - return cppc_set_reg_val(cpu, PERF_LIMITED, new_val); + return cppc_set_reg_val(cpu, PERF_LIMITED, 0); } EXPORT_SYMBOL_GPL(cppc_set_perf_limited); From 487d1ad7535e4ef5a404985b170a356a9f827326 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 16 Sep 2026 17:28:00 +0100 Subject: [PATCH 0553/2286] ACPI: CPPC: Keep Performance Limited clearable on NVIDIA T41 NVIDIA T41 firmware describes Performance Limited as a two-bit field at offset zero in a DWord SystemMemory access unit. Generic CPPC code must keep such a field read-only because preserving the remainder when clearing it requires a read-modify-write which cannot be interlocked with platform updates. The remaining bits of this access unit are unimplemented on T41: they read as zero, writes have no side effects, and no other register uses them. The Performance Limited register therefore owns the complete access unit, but shipped firmware does not describe that property accurately. Add a CPPC platform-quirk table keyed by the DSDT header and carry quirk behavior through explicit flags. Cache a successful table lookup, copy each GAS into the driver's private descriptor, and apply fixups before layout validation, mapping and overlap registration. Distinguish a genuine non-match from a table-header lookup failure in acpi_match_platform_list(). Propagate lookup errors from CPPC probe without caching them, so a transient mapping failure cannot disable the workaround for every later processor. Existing matcher callers still treat all negative results as no match. For the known T41 layout only, widen a two-bit Performance Limited field at offset zero to its 32-bit access width. Clearing both status bits can then be issued as one DWord write of zero without a stale read. Corrected firmware which reports the full width is unchanged. The workaround therefore lapses automatically when corrected firmware ships. Link: https://lore.kernel.org/lkml/d5f1ea9b-53b7-4db2-983a-b5be8e71a371@arm.com/ Signed-off-by: Sumit Gupta [ Rework quirk matching and fixup placement; propagate lookup failures without caching them. ] Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-16-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 72 ++++++++++++++++++++++++++++++++++++++++ drivers/acpi/utils.c | 13 ++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 6d130381245ecc..0e218f2be0fe01 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -330,6 +330,68 @@ static unsigned int cpc_reg_access_width(const struct cpc_reg *reg) return reg->bit_width; } +enum cpc_platform_quirk { + CPC_QUIRK_PERF_LIMITED_OWNS_UNIT = BIT(0), +}; + +static const struct acpi_platform_list cpc_platform_quirk_list[] = { + { + .oem_id = "NVIDIA", + .oem_table_id = "T41", + .table = ACPI_SIG_DSDT, + .pred = all_versions, + .reason = "Performance Limited owns its access unit", + .data = CPC_QUIRK_PERF_LIMITED_OWNS_UNIT, + }, + { } +}; + +static DEFINE_MUTEX(cpc_platform_quirk_lock); +static bool cpc_platform_quirks_initialized; +static u32 cpc_platform_quirks; + +static int cpc_get_platform_quirks(u32 *quirks) +{ + int idx, ret = 0; + + mutex_lock(&cpc_platform_quirk_lock); + if (!cpc_platform_quirks_initialized) { + idx = acpi_match_platform_list(cpc_platform_quirk_list); + if (idx < 0 && idx != -ENODEV) { + ret = idx; + goto out; + } + if (idx >= 0) + cpc_platform_quirks = cpc_platform_quirk_list[idx].data; + cpc_platform_quirks_initialized = true; + } + *quirks = cpc_platform_quirks; +out: + mutex_unlock(&cpc_platform_quirk_lock); + + return ret; +} + +static void cpc_apply_platform_quirks(struct cpc_reg *reg, + unsigned int reg_idx, u32 quirks) +{ + unsigned int access_width; + + if (!(quirks & CPC_QUIRK_PERF_LIMITED_OWNS_UNIT) || + reg_idx != PERF_LIMITED || + reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY || + reg->bit_width != 2 || reg->bit_offset) + return; + + access_width = cpc_reg_access_width(reg); + if (access_width != 32) + return; + + reg->bit_width = access_width; + pr_info_once("firmware quirk: Performance Limited owns its access unit, using Bit Width %u\n", + access_width); +} + static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) { unsigned int width = cpc_reg_access_width(®->cpc_entry.reg); @@ -1857,6 +1919,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) acpi_handle handle = pr->handle; unsigned int num_ent, i, cpc_rev; u32 unsupported_regs = 0; + u32 platform_quirks; int pcc_subspace_id = -1; bool pcc_data_ref = false; bool cpc_present = false; @@ -1866,6 +1929,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) if (per_cpu(cpc_desc_ptr, pr->id)) return 0; + ret = cpc_get_platform_quirks(&platform_quirks); + if (ret) { + pr_err("CPU%d: failed to match CPPC platform quirks: %d\n", + pr->id, ret); + return ret; + } per_cpu(cpu_pcc_subspace_idx, pr->id) = -1; if (!osc_sb_cppc2_support_acked) { @@ -2002,6 +2071,9 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_BUFFER; memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, sizeof(*gas_t)); + gas_t = &cpc_ptr->cpc_regs[i - 2].cpc_entry.reg; + cpc_apply_platform_quirks(gas_t, i - 2, + platform_quirks); /* * The PCC Subspace index is encoded inside diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index d499b72574ab26..7c584d490d6a45 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -1069,19 +1069,26 @@ EXPORT_SYMBOL(acpi_dev_is_video_device); * @plat: pointer to acpi_platform_list table terminated by a NULL entry * * Return the matched index if the system is found in the platform list. - * Otherwise, return a negative error code. + * Return -ENODEV for no match, or another negative error code if a table + * header could not be read and no entry matched. */ int acpi_match_platform_list(const struct acpi_platform_list *plat) { struct acpi_table_header hdr; + acpi_status status; + int ret = -ENODEV; int idx = 0; if (acpi_disabled) return -ENODEV; for (; plat->oem_id[0]; plat++, idx++) { - if (ACPI_FAILURE(acpi_get_table_header(plat->table, 0, &hdr))) + status = acpi_get_table_header(plat->table, 0, &hdr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + ret = status == AE_NO_MEMORY ? -ENOMEM : -EIO; continue; + } if (strncmp(plat->oem_id, hdr.oem_id, ACPI_OEM_ID_SIZE)) continue; @@ -1096,6 +1103,6 @@ int acpi_match_platform_list(const struct acpi_platform_list *plat) return idx; } - return -ENODEV; + return ret; } EXPORT_SYMBOL(acpi_match_platform_list); From 6b016c5c96448c0a5f09328d37f66562d154a5e5 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:28:01 +0100 Subject: [PATCH 0554/2286] ACPI: CPPC: Validate FFH register fields before hardware access The x86 CPPC FFH accessors construct masks and shift values using the firmware's Bit Width and Bit Offset without checking that the field fits in an MSR. A zero-width field or a field extending beyond bit 63 can therefore cause an invalid shift. The safe MSR accessors only handle an access fault, not invalid field arithmetic after a successful read. Also, the 64-bit GAS address is implicitly narrowed to the 32-bit MSR number. A descriptor with nonzero upper address bits can access a different MSR from the one described by firmware. The arm64 AMU counter readers have the same unchecked field arithmetic. Validate the field bounds before reading a counter, including both descriptors in the paired counter path. Dispatch FFH accesses before decoding the GAS Access Size in the common read and write paths. Otherwise, a large access_width can trigger an invalid shift in GET_BIT_WIDTH() before the architecture validates its register. That field has architecture-specific FFH semantics and is not needed by the generic memory/port accessor on this path. Validate x86 MSR addresses and share the field-bounds checks between related accessors within each architecture. Fixes: a6cbcdd5ab5f ("ACPI / CPPC: Add support for functional fixed hardware address") Fixes: 68c5debcc06d ("arm64: implement CPPC FFH support using AMUs") Fixes: f489c948028b ("ACPI: CPPC: Fix access width used for PCC registers") Cc: All applicable Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-17-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- arch/arm64/kernel/topology.c | 12 +++++++++++- arch/x86/kernel/acpi/cppc.c | 14 ++++++++++++++ drivers/acpi/cppc_acpi.c | 10 ++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index d28438f8b83f12..2c030e78d5e453 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -470,6 +470,12 @@ static void amu_read_core_const_ctrs(void *val) cpu_read_corecnt(&ctrs->corecnt); } +static bool cpc_ffh_reg_valid(const struct cpc_reg *reg) +{ + return reg->bit_width && reg->bit_width <= 64 && + reg->bit_offset <= 64 - reg->bit_width; +} + static u64 cpc_ffh_extract_bits(const struct cpc_reg *reg, u64 val) { val &= GENMASK_ULL(reg->bit_offset + reg->bit_width - 1, @@ -506,7 +512,8 @@ int cpc_read_ffh_fb_ctrs(int cpu, struct cpc_reg *reg1, u64 *val1, struct amu_ffh_ctrs ctrs; int ret; - if (!is_amu_ctr_reg(reg1) || !is_amu_ctr_reg(reg2)) + if (!is_amu_ctr_reg(reg1) || !is_amu_ctr_reg(reg2) || + !cpc_ffh_reg_valid(reg1) || !cpc_ffh_reg_valid(reg2)) return -EINVAL; ret = counters_read_on_cpu(cpu, amu_read_core_const_ctrs, &ctrs); @@ -530,6 +537,9 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) { int ret = -EOPNOTSUPP; + if (!cpc_ffh_reg_valid(reg)) + return -EINVAL; + switch ((u64)reg->address) { case CPC_FFH_CTR_CORE: ret = counters_read_on_cpu(cpu, cpu_read_corecnt, val); diff --git a/arch/x86/kernel/acpi/cppc.c b/arch/x86/kernel/acpi/cppc.c index bbade0da5130f9..d2185b7a203061 100644 --- a/arch/x86/kernel/acpi/cppc.c +++ b/arch/x86/kernel/acpi/cppc.c @@ -5,6 +5,7 @@ */ #include +#include #include #include @@ -45,10 +46,20 @@ bool cpc_ffh_supported(void) return true; } +static bool cpc_ffh_reg_valid(const struct cpc_reg *reg) +{ + return reg->address <= U32_MAX && reg->bit_width && + reg->bit_width <= 64 && + reg->bit_offset <= 64 - reg->bit_width; +} + int cpc_read_ffh(int cpunum, struct cpc_reg *reg, u64 *val) { int err; + if (!cpc_ffh_reg_valid(reg)) + return -EINVAL; + err = rdmsrq_safe_on_cpu(cpunum, reg->address, val); if (!err) { u64 mask = GENMASK_ULL(reg->bit_offset + reg->bit_width - 1, @@ -65,6 +76,9 @@ int cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val) u64 rd_val; int err; + if (!cpc_ffh_reg_valid(reg)) + return -EINVAL; + err = rdmsrq_safe_on_cpu(cpunum, reg->address, &rd_val); if (!err) { u64 mask = GENMASK_ULL(reg->bit_offset + reg->bit_width - 1, diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 0e218f2be0fe01..96a94df2423127 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -2446,6 +2446,9 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) } *val = 0; + if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) + return cpc_read_ffh(cpu, reg, val); + size = GET_BIT_WIDTH(reg); if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { @@ -2490,8 +2493,6 @@ static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *val) return 0; } else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; - else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) - return cpc_read_ffh(cpu, reg, val); else return acpi_os_read_memory((acpi_physical_address)reg->address, val, size); @@ -2538,6 +2539,9 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) return -EOPNOTSUPP; reg = ®_res->cpc_entry.reg; + if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) + return cpc_write_ffh(cpu, reg, val); + size = GET_BIT_WIDTH(reg); if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { @@ -2581,8 +2585,6 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) return 0; } else if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr = reg_res->sys_mem_vaddr; - else if (reg->space_id == ACPI_ADR_SPACE_FIXED_HARDWARE) - return cpc_write_ffh(cpu, reg, val); else return acpi_os_write_memory((acpi_physical_address)reg->address, val, size); From 36e96b469da24868f727b4cd168d7cf01cbd163d Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:28:02 +0100 Subject: [PATCH 0555/2286] ACPI: CPPC: Propagate errors from cross-CPU FFH calls The arm64 and RISC-V CPPC FFH accessors ignore the return value of smp_call_function_single(). If the target CPU is offline, the call can return -ENXIO without running the callback. The accessors then consume callback output which was never initialized and may report success. CPU offlining leaves the CPU's CPPC sysfs attributes present. On arm64, the AMU feature mask does not exclude an offline CPU, so a paired counter read can expose uninitialized stack values through feedback_ctrs. RISC-V has the same problem in both SBI and CSR reads, and its write paths also inspect an uninitialized callback status after a failed cross-CPU call. Return the SMP call error before using callback output. The arm64 single and paired counter readers already check the helper's return value. For RISC-V, also check the callback status before copying a read value: an unsupported CSR sets the error but does not initialize the value. Fixes: 68c5debcc06d ("arm64: implement CPPC FFH support using AMUs") Fixes: 30f3ffbee86b ("ACPI: RISC-V: Add CPPC driver") Cc: All applicable Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-18-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- arch/arm64/kernel/topology.c | 2 +- drivers/acpi/riscv/cppc.c | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c index 2c030e78d5e453..55bd8fa3e95cfa 100644 --- a/arch/arm64/kernel/topology.c +++ b/arch/arm64/kernel/topology.c @@ -427,7 +427,7 @@ int counters_read_on_cpu(int cpu, smp_call_func_t func, void *val) return -EPERM; func(val); } else { - smp_call_function_single(cpu, func, val, 1); + return smp_call_function_single(cpu, func, val, 1); } return 0; diff --git a/drivers/acpi/riscv/cppc.c b/drivers/acpi/riscv/cppc.c index 42c1a90524707f..4ea4ddedd91f16 100644 --- a/drivers/acpi/riscv/cppc.c +++ b/drivers/acpi/riscv/cppc.c @@ -97,6 +97,7 @@ bool cpc_ffh_supported(void) int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) { struct sbi_cppc_data data; + int ret; if (WARN_ON_ONCE(irqs_disabled())) return -EPERM; @@ -107,19 +108,27 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) data.reg = FFH_CPPC_SBI_REG(reg->address); - smp_call_function_single(cpu, sbi_cppc_read, &data, 1); + ret = smp_call_function_single(cpu, sbi_cppc_read, &data, 1); + if (ret) + return ret; + if (data.ret.error) + return sbi_err_map_linux_errno(data.ret.error); *val = data.ret.value; - return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0; + return 0; } else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) { data.reg = FFH_CPPC_CSR_NUM(reg->address); - smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1); + ret = smp_call_function_single(cpu, cppc_ffh_csr_read, &data, 1); + if (ret) + return ret; + if (data.ret.error) + return data.ret.error; *val = data.ret.value; - return data.ret.error; + return 0; } return -EINVAL; @@ -128,6 +137,7 @@ int cpc_read_ffh(int cpu, struct cpc_reg *reg, u64 *val) int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val) { struct sbi_cppc_data data; + int ret; if (WARN_ON_ONCE(irqs_disabled())) return -EPERM; @@ -139,14 +149,18 @@ int cpc_write_ffh(int cpu, struct cpc_reg *reg, u64 val) data.reg = FFH_CPPC_SBI_REG(reg->address); data.val = val; - smp_call_function_single(cpu, sbi_cppc_write, &data, 1); + ret = smp_call_function_single(cpu, sbi_cppc_write, &data, 1); + if (ret) + return ret; return (data.ret.error) ? sbi_err_map_linux_errno(data.ret.error) : 0; } else if (FFH_CPPC_TYPE(reg->address) == FFH_CPPC_CSR) { data.reg = FFH_CPPC_CSR_NUM(reg->address); data.val = val; - smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1); + ret = smp_call_function_single(cpu, cppc_ffh_csr_write, &data, 1); + if (ret) + return ret; return data.ret.error; } From 4bcc60d326c5acaa28c5913259decb22a33a83a1 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:28:03 +0100 Subject: [PATCH 0556/2286] ACPI: CPPC: Accept requests to retain immutable autonomous selection ACPI permits Autonomous Selection Enable to be Integer 1 when the platform always selects performance autonomously. cppc_get_auto_sel() returns that value, but cppc_set_auto_sel() rejects the Integer even when asked to keep autonomous selection enabled. AMD shared-memory initialization consequently fails in active or guided mode after successfully reading the capability. Accept a request to keep this immutable field enabled without issuing a register access. Expose the immutable encoding to callers, separately from the current value of a writable register. Before an AMD shared-memory transition to passive mode, reject immutable autonomous selection on any present CPU. Otherwise a guided-to-passive transition ignores the failed disable and reports the wrong mode, while active-to-passive unregisters the working driver before passive policy initialization fails. Check under the driver mode lock before invoking either transition, including known offline CPUs. Serialize this capability query with descriptor publication and removal. ACPI processor removal drops the descriptor before taking the CPU hotplug write lock, so the caller's cpus_read_lock() alone cannot protect the lookup. Unpublish under the same mutex before releasing a descriptor, including when adding its kobject fails. The cppc-cpufreq auto_select attribute can also request one while autonomous selection is already enabled. If the subsequent bounds update fails, its rollback requests zero and clears the cached mode regardless of the previous state. For an immutable field the disable request fails, leaving the cached mode wrong. Save and restore the previous mode instead. This also avoids disabling an already-enabled writable control on that error path. Fixes: c984f5d5d45b ("ACPI: CPPC: Add auto select register read/write support") Fixes: ea3db45ae476 ("cpufreq: cppc: Update MIN_PERF/MAX_PERF in target callbacks") Cc: All applicable Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-19-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 42 +++++++++++++++++++++++++++++++--- drivers/cpufreq/amd-pstate.c | 21 +++++++++++++---- drivers/cpufreq/cppc_cpufreq.c | 5 ++-- include/acpi/cppc_acpi.h | 7 ++++++ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 96a94df2423127..80e2e6b32ce357 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -100,6 +100,15 @@ static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx); */ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); +/* Protect immutable capability queries against descriptor removal. */ +static DEFINE_MUTEX(cpc_desc_lock); + +static void cpc_set_desc(unsigned int cpu, struct cpc_desc *desc) +{ + guard(mutex)(&cpc_desc_lock); + per_cpu(cpc_desc_ptr, cpu) = desc; +} + struct cpc_sysmem_node { struct rb_node rb; u64 subtree_last; @@ -2314,12 +2323,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } /* Plug PSD data into this CPU's CPC descriptor. */ - per_cpu(cpc_desc_ptr, pr->id) = cpc_ptr; + cpc_set_desc(pr->id, cpc_ptr); ret = kobject_init_and_add(&cpc_ptr->kobj, &cppc_ktype, &cpu_dev->kobj, "acpi_cppc"); if (ret) { - per_cpu(cpc_desc_ptr, pr->id) = NULL; + cpc_set_desc(pr->id, NULL); cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); kobject_put(&cpc_ptr->kobj); @@ -2363,7 +2372,7 @@ void acpi_cppc_processor_exit(struct acpi_processor *pr) } pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, pr->id); - per_cpu(cpc_desc_ptr, pr->id) = NULL; + cpc_set_desc(pr->id, NULL); kobject_del(&cpc_ptr->kobj); cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); @@ -2766,6 +2775,11 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val) reg = &cpc_desc->cpc_regs[reg_idx]; + /* Integer 1 describes autonomous selection that is always enabled. */ + if (reg_idx == AUTO_SEL_ENABLE && reg->type == ACPI_TYPE_INTEGER && + reg->cpc_entry.int_value == 1) + return val == 1 ? 0 : -EOPNOTSUPP; + /* if a register is writeable, it must be a buffer and not null */ if (!cpc_is_writable(reg)) { pr_debug("CPC register is not supported\n"); @@ -3310,6 +3324,28 @@ int cppc_get_auto_sel(int cpu, bool *enable) } EXPORT_SYMBOL_GPL(cppc_get_auto_sel); +/** + * cppc_auto_sel_is_immutable - Check for always-enabled autonomous selection. + * @cpu: CPU whose _CPC descriptor to check. + * + * Context: Process context. + * Return: true for Integer 1, false for a register or an absent descriptor. + */ +bool cppc_auto_sel_is_immutable(int cpu) +{ + struct cpc_desc *cpc_desc; + struct cpc_register_resource *reg; + + guard(mutex)(&cpc_desc_lock); + cpc_desc = per_cpu(cpc_desc_ptr, cpu); + if (!cpc_desc) + return false; + + reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; + return reg->type == ACPI_TYPE_INTEGER && reg->cpc_entry.int_value == 1; +} +EXPORT_SYMBOL_GPL(cppc_auto_sel_is_immutable); + /** * cppc_set_auto_sel - Write autonomous selection register. * @cpu : CPU to which to write register. diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 6ee2d43b7b9521..0a8b1aaa266009 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1794,7 +1794,7 @@ EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); int amd_pstate_update_status(const char *buf, size_t size) { - int mode_idx; + int cpu, mode_idx; if (size > strlen("passive") || size < strlen("active")) return -EINVAL; @@ -1803,12 +1803,23 @@ int amd_pstate_update_status(const char *buf, size_t size) if (mode_idx < 0) return mode_idx; - if (mode_state_machine[cppc_state][mode_idx]) { - guard(mutex)(&amd_pstate_driver_lock); - return mode_state_machine[cppc_state][mode_idx](mode_idx); + guard(mutex)(&amd_pstate_driver_lock); + + if (!mode_state_machine[cppc_state][mode_idx]) + return 0; + + if (mode_idx == AMD_PSTATE_PASSIVE && + !cpu_feature_enabled(X86_FEATURE_CPPC)) { + guard(cpus_read_lock)(); + + /* Check offline CPUs too, before changing or removing the driver. */ + for_each_present_cpu(cpu) { + if (cppc_auto_sel_is_immutable(cpu)) + return -EOPNOTSUPP; + } } - return 0; + return mode_state_machine[cppc_state][mode_idx](mode_idx); } EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_update_status); diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index af46f913e907a1..9dde24d3e6455b 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -882,6 +882,7 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy, const char *buf, size_t count) { struct cppc_cpudata *cpu_data = policy->driver_data; + bool old_auto_sel = cpu_data->perf_ctrls.auto_sel; bool val; int ret; @@ -910,8 +911,8 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy, if (ret) { cpu_data->perf_ctrls.min_perf = old_min_perf; cpu_data->perf_ctrls.max_perf = old_max_perf; - cppc_set_auto_sel(policy->cpu, false); - cpu_data->perf_ctrls.auto_sel = false; + cppc_set_auto_sel(policy->cpu, old_auto_sel); + cpu_data->perf_ctrls.auto_sel = old_auto_sel; return ret; } } diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 4b5d94f853dc70..3f0005abac6487 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -197,6 +197,7 @@ extern int cppc_set_epp(int cpu, u64 epp_val); extern int cppc_get_auto_act_window(int cpu, u64 *auto_act_window); extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window); extern int cppc_get_auto_sel(int cpu, bool *enable); +bool cppc_auto_sel_is_immutable(int cpu); extern int cppc_set_auto_sel(int cpu, bool enable); extern int cppc_get_perf_limited(int cpu, u64 *perf_limited); extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear); @@ -298,6 +299,12 @@ static inline int cppc_get_auto_sel(int cpu, bool *enable) { return -EOPNOTSUPP; } + +static inline bool cppc_auto_sel_is_immutable(int cpu) +{ + return false; +} + static inline int cppc_set_auto_sel(int cpu, bool enable) { return -EOPNOTSUPP; From 21e018235d99bc86249b034c06646df8757b2852 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:28:04 +0100 Subject: [PATCH 0557/2286] cpufreq: CPPC: Select the frequency-invariance callback per CPU cppc_cpufreq_cpu_fie_init() initializes deferred work only for CPUs whose feedback counters use PCC. However, if any CPU in a shared policy uses PCC, it installs the PCC tick callback for the entire policy. With FIE enabled, a non-PCC CPU then queues an uninitialized irq_work and can call a NULL function pointer when that work runs. Choose the frequency-invariance callback separately for each CPU using the recorded work-initialization state. Processor removal can unpublish the CPC descriptor before callback registration, so querying its counter transport again can incorrectly select direct reads for a PCC CPU. Register the callbacks after all counter initialization has completed, so a failed initial read for an online CPU cannot leave a partially registered policy. PCC CPUs use their initialized deferred work, while non-PCC CPUs read their own counters directly from the tick. Fixes: 997c021abc6e ("cpufreq: CPPC: Update FIE arch_freq_scale in ticks for non-PCC regs") Cc: All applicable Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-20-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cppc_cpufreq.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 9dde24d3e6455b..c0fa9073e4d3e9 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -150,7 +150,7 @@ static struct scale_freq_data cppc_sftd_pcc = { static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) { - struct scale_freq_data *sftd = &cppc_sftd; + struct scale_freq_data *sftd; struct cppc_freq_invariance *cppc_fi; int cpu, ret; @@ -165,7 +165,6 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) kthread_init_work(&cppc_fi->work, cppc_scale_freq_workfn); init_irq_work(&cppc_fi->irq_work, cppc_irq_work); cppc_fi->pcc_work_initialized = true; - sftd = &cppc_sftd_pcc; } ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs); @@ -181,8 +180,15 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) } } - /* Register for freq-invariance */ - topology_set_scale_freq_source(sftd, policy->cpus); + /* A shared policy may contain both PCC and non-PCC counters. */ + for_each_cpu(cpu, policy->cpus) { + cppc_fi = &per_cpu(cppc_freq_inv, cpu); + if (cppc_fi->pcc_work_initialized) + sftd = &cppc_sftd_pcc; + else + sftd = &cppc_sftd; + topology_set_scale_freq_source(sftd, cpumask_of(cpu)); + } } /* From 995e7d4680706e47154adb68f0b682485ea8d873 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Wed, 16 Sep 2026 17:28:05 +0100 Subject: [PATCH 0558/2286] cpufreq: CPPC: Create the FIE worker before enabling PCC callbacks cppc_freq_invariance_init() creates the PCC worker only if an online CPU currently has PCC counters. A shared policy can also initialize an offline PCC member. When that CPU comes online, its callback queues work through the missing kworker_fie and dereferences NULL. A later hot-added PCC policy can encounter the same missing worker. Create the worker when policy initialization encounters PCC counters, before initializing their work and publishing any callbacks. Serialize creation and reuse the worker until driver teardown. This covers offline policy members and later PCC policies without creating an unused deadline worker on direct-counter-only systems. If worker allocation or scheduling setup fails, leave FIE unregistered for that policy. Keep the global FIE setting unchanged so direct-counter policies remain independent of worker setup and existing policies still drain their initialized work during teardown. Publish kworker_fie only after successful setup, allowing a later policy initialization to retry. Fixes: 997c021abc6e ("cpufreq: CPPC: Update FIE arch_freq_scale in ticks for non-PCC regs") Cc: All applicable Signed-off-by: Christian Loehle Tested-by: Sumit Gupta Link: https://patch.msgid.link/20260916162805.1039247-21-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cppc_cpufreq.c | 39 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index c0fa9073e4d3e9..09e55bfdca8857 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -50,10 +51,12 @@ struct cppc_freq_invariance { static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv); static struct kthread_worker *kworker_fie; +static DEFINE_MUTEX(cppc_fie_lock); static int cppc_perf_from_fbctrs(u64 reference_perf, struct cppc_perf_fb_ctrs *fb_ctrs_t0, struct cppc_perf_fb_ctrs *fb_ctrs_t1); +static int cppc_fie_kworker_init(void); /** * __cppc_scale_freq_tick - CPPC arch_freq_scale updater for frequency invariance @@ -162,6 +165,9 @@ static void cppc_cpufreq_cpu_fie_init(struct cpufreq_policy *policy) cppc_fi->cpu = cpu; cppc_fi->cpu_data = policy->driver_data; if (cppc_perf_ctrs_in_pcc_cpu(cpu)) { + if (cppc_fie_kworker_init()) + return; + kthread_init_work(&cppc_fi->work, cppc_scale_freq_workfn); init_irq_work(&cppc_fi->irq_work, cppc_irq_work); cppc_fi->pcc_work_initialized = true; @@ -216,8 +222,9 @@ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy) } } -static void cppc_fie_kworker_init(void) +static int cppc_fie_kworker_init(void) { + struct kthread_worker *worker; struct sched_attr attr = { .size = sizeof(struct sched_attr), .sched_policy = SCHED_DEADLINE, @@ -233,23 +240,28 @@ static void cppc_fie_kworker_init(void) }; int ret; - kworker_fie = kthread_run_worker(0, "cppc_fie"); - if (IS_ERR(kworker_fie)) { + guard(mutex)(&cppc_fie_lock); + + if (kworker_fie) + return 0; + + worker = kthread_run_worker(0, "cppc_fie"); + if (IS_ERR(worker)) { pr_warn("%s: failed to create kworker_fie: %ld\n", __func__, - PTR_ERR(kworker_fie)); - fie_disabled = FIE_DISABLED; - kworker_fie = NULL; - return; + PTR_ERR(worker)); + return PTR_ERR(worker); } - ret = sched_setattr_nocheck(kworker_fie->task, &attr); + ret = sched_setattr_nocheck(worker->task, &attr); if (ret) { pr_warn("%s: failed to set SCHED_DEADLINE: %d\n", __func__, ret); - kthread_destroy_worker(kworker_fie); - fie_disabled = FIE_DISABLED; - kworker_fie = NULL; + kthread_destroy_worker(worker); + return ret; } + + kworker_fie = worker; + return 0; } static void __init cppc_freq_invariance_init(void) @@ -264,11 +276,6 @@ static void __init cppc_freq_invariance_init(void) fie_disabled = FIE_ENABLED; } } - - if (fie_disabled || !perf_ctrs_in_pcc) - return; - - cppc_fie_kworker_init(); } static void cppc_freq_invariance_exit(void) From b1c75a77d494298b8dedf46062f2072239bbc33b Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Thu, 16 Jul 2026 19:11:14 +0100 Subject: [PATCH 0559/2286] ACPI: HMAT: preserve hmat_adist_nb notifier block Remove __meminitdata from hmat_adist_nb declaration preserving it even when CONFIG_MEMORY_HOTPLUG is not set. Otherwise further notifier registrations using register_mt_adistance_algorithm() will find a notifier chain likely corrupted. This is the case when cxl_region_probe() calls register_mt_adistance_algorithm() what can happen when CXL regions are dynamically created at any time from Type3 device(s) or when a Type2 device is initialized at its driver probe. Fixes: 3718c02dbd4c ("acpi, hmat: calculate abstract distance with HMAT") Signed-off-by: Alejandro Lucero Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260716181114.35962-2-alejandro.lucero-palau@amd.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/numa/hmat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c index 9792dc3947561e..3006789ae31feb 100644 --- a/drivers/acpi/numa/hmat.c +++ b/drivers/acpi/numa/hmat.c @@ -995,7 +995,7 @@ static int hmat_calculate_adistance(struct notifier_block *self, return NOTIFY_STOP; } -static struct notifier_block hmat_adist_nb __meminitdata = { +static struct notifier_block hmat_adist_nb = { .notifier_call = hmat_calculate_adistance, .priority = 100, }; From 36d4a11b56aa98da4b4652a2e34d229ffcf36070 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Thu, 17 Sep 2026 17:29:53 -0700 Subject: [PATCH 0560/2286] kbuild: Remove alignment on .modinfo section Commit d50f21091358 ("kbuild: align modinfo section for Secureboot Authenticode EDK2 compat") aligned .modinfo because it appeared in the x86_64 compressed kernel image after commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped"), breaking boot. Commit 8678591b4746 ("kbuild: Split .modinfo out from ELF_DETAILS") properly addresses .modinfo appearing in unexpected contexts like compressed images by moving it out of ELF_DETAILS and explicitly specifying its location in every architecture's vmlinux linker script or discarding it where it may appear elsewhere. Effectively revert commit d50f21091358 ("kbuild: align modinfo section for Secureboot Authenticode EDK2 compat") and its follow up fix, commit a26a6c93edfe ("kbuild: Strip trailing padding bytes from modules.builtin.modinfo"), as they are no longer necessary in the current tree. This simplifies future cleanups. Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260918005142.GA1585590@ax162/ Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- scripts/Makefile.vmlinux | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index b2988aa12f6645..26201821ea7ff0 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo : { *(.modinfo) . = ALIGN(8); } + .modinfo : { *(.modinfo) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index fcae1e432d9add..6833b517cacb6f 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -102,18 +102,8 @@ vmlinux: vmlinux.unstripped FORCE # modules.builtin.modinfo # --------------------------------------------------------------------------- -# .modinfo in vmlinux.unstripped is aligned to 8 bytes for compatibility with -# tools that expect vmlinux to have sufficiently aligned sections but the -# additional bytes used for padding .modinfo to satisfy this requirement break -# certain versions of kmod with -# -# depmod: ERROR: kmod_builtin_iter_next: unexpected string without modname prefix -# -# Strip the trailing padding bytes after extracting .modinfo to comply with -# what kmod expects to parse. quiet_cmd_modules_builtin_modinfo = GEN $@ cmd_modules_builtin_modinfo = $(cmd_objcopy); \ - sed -i 's/\x00\+$$/\x00/g' $@; \ chmod -x $@ OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary From 1ee97d0f7b20e1f71bdb9edf5950c8751538180a Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Mon, 10 Aug 2026 19:21:17 +0000 Subject: [PATCH 0561/2286] x86/cpufeatures: Add X86_FEATURE_RMPOPT feature flag Add a flag indicating whether RMPOPT instruction is supported. RMPOPT is a new instruction that reduces the performance overhead of RMP checks for the hypervisor and non-SNP guests by allowing those checks to be skipped when 1-GB memory regions are known to contain no SEV-SNP guest memory. For more information on the RMPOPT instruction, see the AMD64 RMPOPT technical documentation. [ bp: Zap respective tools/ change. ] Suggested-by: Borislav Petkov (AMD) Signed-off-by: Ashish Kalra Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Dave Hansen Reviewed-by: Ackerley Tng Reviewed-by: Tom Lendacky Link: https://patch.msgid.link/39e9ee269a572c516a3f4e937bfe12d00697d5e6.1782841284.git.ashish.kalra@amd.com --- arch/x86/include/asm/cpufeatures.h | 2 +- arch/x86/kernel/cpu/scattered.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h index f70ee74b5f9217..3b5b32d3391b56 100644 --- a/arch/x86/include/asm/cpufeatures.h +++ b/arch/x86/include/asm/cpufeatures.h @@ -76,7 +76,7 @@ #define X86_FEATURE_K8 ( 3*32+ 4) /* Opteron, Athlon64 */ #define X86_FEATURE_ZEN5 ( 3*32+ 5) /* CPU based on Zen5 microarchitecture */ #define X86_FEATURE_ZEN6 ( 3*32+ 6) /* CPU based on Zen6 microarchitecture */ -/* Free ( 3*32+ 7) */ +#define X86_FEATURE_RMPOPT ( 3*32+ 7) /* Support for AMD RMPOPT instruction */ #define X86_FEATURE_CONSTANT_TSC ( 3*32+ 8) /* "constant_tsc" TSC ticks at a constant rate */ /* free: was #define X86_FEATURE_UP ( 3*32+ 9) * "up" SMP kernel running on UP */ #define X86_FEATURE_ART ( 3*32+10) /* "art" Always running timer (ART) */ diff --git a/arch/x86/kernel/cpu/scattered.c b/arch/x86/kernel/cpu/scattered.c index 8665a6474806c9..d1795ce219dab3 100644 --- a/arch/x86/kernel/cpu/scattered.c +++ b/arch/x86/kernel/cpu/scattered.c @@ -67,6 +67,7 @@ static const struct cpuid_bit cpuid_bits[] = { { X86_FEATURE_PERFMON_V2, CPUID_EAX, 0, 0x80000022, 0 }, { X86_FEATURE_AMD_LBR_V2, CPUID_EAX, 1, 0x80000022, 0 }, { X86_FEATURE_AMD_LBR_PMC_FREEZE, CPUID_EAX, 2, 0x80000022, 0 }, + { X86_FEATURE_RMPOPT, CPUID_EDX, 0, 0x80000025, 0 }, { X86_FEATURE_AMD_HTR_CORES, CPUID_EAX, 30, 0x80000026, 0 }, { 0, 0, 0, 0, 0 } }; From 674bb17a32c7eab539a92c654c2971a8afaccbb4 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Wed, 16 Sep 2026 22:14:35 +0000 Subject: [PATCH 0562/2286] x86/sev: Disable CPU hotplug while SNP is active While SNP is active, every memory write is checked against the RMP to protect SNP guest memory. A core performs these RMP checks only once SNP has been initialized via SNP_INIT and the SNP-enable bit in SYSCFG is set on that core; the firmware requires the SNP-enable bit to be set on every present CPU before SNP initialization. A core that is not SNP-enabled and not SNP-initialized performs no RMP checks at all, so there is no valid configuration with SNP active and any CPU exempt from RMP checks. The firmware determines which CPUs are present from the processor and the BIOS/UEFI configuration (e.g. SMT disabled in the BIOS) and enumerates them at SNP init; it is not aware of the OS bringing CPUs online or offline afterwards. SNP_INIT fails unless SnpEn is set on all CPUs, so a CPU that is offline when SNP_INIT is issued, does not have SnpEn set, SNP_INIT fails, and there can be no SNP guest memory. OS CPU hotplug can thus diverge from the firmware's expectations and break SNP. Tie CPU hotplug to the SNP-enable bit: disable it in snp_prepare() before SNP is enabled, and re-enable it in snp_shutdown() once the firmware has disabled SNP. If snp_prepare() fails before enabling SNP it re-enables hotplug itself; once SNP is enabled hotplug stays disabled, including across a failed SNP_INIT and across the legacy SNP_SHUTDOWN_EX path, both of which leave SNP enabled. A kexec target that boots with SNP already enabled, disables hotplug once in snp_rmptable_init(), since snp_prepare() bails when SNP is already enabled. With CPU hotplug now disabled while SNP is active, the online CPU mask is stable, so the cpus_read_lock() previously taken in snp_prepare() to iterate it is redundant. Drop cpus_read_lock()/cpus_read_unlock() here. Suggested-by: Thomas Lendacky Signed-off-by: Ashish Kalra Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Tom Lendacky Link: https://patch.msgid.link/cd6962a88d691a020c2bf40fe77a6a3f3852fdfc.1789594774.git.ashish.kalra@amd.com --- arch/x86/virt/svm/sev.c | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c index cff285d8ad8e3e..558f7924a3f85e 100644 --- a/arch/x86/virt/svm/sev.c +++ b/arch/x86/virt/svm/sev.c @@ -513,7 +513,6 @@ static void clear_hsave_pa(void *arg) int snp_prepare(void) { - int ret; u64 val; /* @@ -526,14 +525,18 @@ int snp_prepare(void) clear_rmp(); - cpus_read_lock(); + /* + * No CPU may come online without SnpEn while SNP is active; disable + * hotplug here and re-enable it in snp_shutdown(). + */ + cpu_hotplug_disable(); if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) { - ret = -EOPNOTSUPP; + cpu_hotplug_enable(); pr_warn("SNP init failed: not all CPUs online. (%*pbl online <-> %*pbl present masks).\n", cpumask_pr_args(cpu_online_mask), cpumask_pr_args(cpu_present_mask)); - goto unlock; + return -EOPNOTSUPP; } wbinvd_on_all_cpus(); @@ -548,12 +551,7 @@ int snp_prepare(void) /* SNP_INIT requires MSR_VM_HSAVE_PA to be cleared on all CPUs. */ on_each_cpu(clear_hsave_pa, NULL, 1); - ret = 0; - -unlock: - cpus_read_unlock(); - - return ret; + return 0; } EXPORT_SYMBOL_FOR_MODULES(snp_prepare, "ccp"); @@ -567,6 +565,13 @@ void snp_shutdown(void) clear_rmp(); on_each_cpu(mfd_reconfigure, NULL, 1); + + /* + * The firmware has disabled SNP (SnpEn is clear), so re-enable CPU + * hotplug. A legacy SNP shutdown returns above with SnpEn still set and + * leaves hotplug disabled. + */ + cpu_hotplug_enable(); } EXPORT_SYMBOL_FOR_MODULES(snp_shutdown, "ccp"); @@ -577,6 +582,8 @@ EXPORT_SYMBOL_FOR_MODULES(snp_shutdown, "ccp"); */ int __init snp_rmptable_init(void) { + u64 val; + if (WARN_ON_ONCE(!cc_platform_has(CC_ATTR_HOST_SEV_SNP))) return -ENOSYS; @@ -586,6 +593,15 @@ int __init snp_rmptable_init(void) if (!setup_rmptable()) return -ENOSYS; + /* + * On a kexec boot SNP may already be enabled (legacy firmware leaves + * SnpEn set across shutdown), in which case snp_prepare() bails without + * disabling CPU hotplug, so disable it here. + */ + rdmsrq(MSR_AMD64_SYSCFG, val); + if (val & MSR_AMD64_SYSCFG_SNP_EN) + cpu_hotplug_disable(); + /* * Setting crash_kexec_post_notifiers to 'true' to ensure that SNP panic * notifier is invoked to do SNP IOMMU shutdown before kdump. From 15d974be0a3ba37091083028d16237ff49eb05b5 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Wed, 16 Sep 2026 22:14:52 +0000 Subject: [PATCH 0563/2286] x86/sev: Initialize RMPOPT configuration MSRs The new RMPOPT instruction helps manage per-CPU RMP optimization structures inside the CPU. It takes a 1GB-aligned physical address and either returns the status of the optimizations or tries to enable the optimizations. Initialize the per-CPU RMPOPT table base to the starting physical address. This enables RMP optimization for up to 2 TB of system RAM on all CPUs because this is the maximum the RMPOPT tables support. The RMPOPT_BASE MSR can only be written after SNP is enabled, so the enabling runs from the ccp SNP init path (and again on guest teardown) rather than from an initcall. This is also in line with the intention to not enable SNP by default but enable it on demand, when the ccp module is loaded. RMPOPT_BASE is programmed on all primary threads. RMPOPT_EN cannot be cleared while SNP is enabled, and CPU hotplug is disabled while SNP is active, so once programmed the MSRs stay set on all CPUs until SNP is disabled. A set RMPOPT_EN on the local CPU therefore means the programming has already been done and thus it can be skipped. [ bp: - Massage commit message - move enabling logic into the commit message - zap unnecessary comments - cleanup ] Suggested-by: Thomas Lendacky Suggested-by: Dave Hansen Signed-off-by: Ashish Kalra Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Tom Lendacky Link: https://patch.msgid.link/7fdf0f5b1be4b561c884c5200d6606a947cbc09d.1789594774.git.ashish.kalra@amd.com --- arch/x86/include/asm/msr-index.h | 3 ++ arch/x86/include/asm/sev.h | 2 ++ arch/x86/virt/svm/sev.c | 48 ++++++++++++++++++++++++++++---- drivers/crypto/ccp/sev-dev.c | 2 ++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/arch/x86/include/asm/msr-index.h b/arch/x86/include/asm/msr-index.h index 3a8e51a0c9e88c..1635e2e1c576af 100644 --- a/arch/x86/include/asm/msr-index.h +++ b/arch/x86/include/asm/msr-index.h @@ -761,6 +761,9 @@ #define MSR_AMD64_SEG_RMP_ENABLED_BIT 0 #define MSR_AMD64_SEG_RMP_ENABLED BIT_ULL(MSR_AMD64_SEG_RMP_ENABLED_BIT) #define MSR_AMD64_RMP_SEGMENT_SHIFT(x) (((x) & GENMASK_ULL(13, 8)) >> 8) +#define MSR_AMD64_RMPOPT_BASE 0xc0010139 +#define MSR_AMD64_RMPOPT_ENABLE_BIT 0 +#define MSR_AMD64_RMPOPT_ENABLE BIT_ULL(MSR_AMD64_RMPOPT_ENABLE_BIT) #define MSR_SVSM_CAA 0xc001f000 diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 9e7a077c445dcc..fa81aa004e8bf8 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -662,6 +662,7 @@ static inline void snp_leak_pages(u64 pfn, unsigned int pages) __snp_leak_pages(pfn, pages, true); } int snp_prepare(void); +void snp_enable_rmpopt(void); void snp_shutdown(void); #else static inline bool snp_probe_rmptable_info(void) { return false; } @@ -680,6 +681,7 @@ static inline void snp_leak_pages(u64 pfn, unsigned int npages) {} static inline void kdump_sev_callback(void) { } static inline void snp_fixup_e820_tables(void) {} static inline int snp_prepare(void) { return -ENODEV; } +static inline void snp_enable_rmpopt(void) {} static inline void snp_shutdown(void) {} #endif diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c index 558f7924a3f85e..1fecd246ce5f58 100644 --- a/arch/x86/virt/svm/sev.c +++ b/arch/x86/virt/svm/sev.c @@ -124,6 +124,8 @@ static void *rmp_bookkeeping __ro_after_init; static u64 probed_rmp_base, probed_rmp_size; +static phys_addr_t rmpopt_pa_start; + static LIST_HEAD(snp_leaked_pages_list); static DEFINE_SPINLOCK(snp_leaked_pages_list_lock); @@ -575,6 +577,34 @@ void snp_shutdown(void) } EXPORT_SYMBOL_FOR_MODULES(snp_shutdown, "ccp"); +static bool rmpopt_capable(void) +{ + return cpu_feature_enabled(X86_FEATURE_RMPOPT) && + cc_platform_has(CC_ATTR_HOST_SEV_SNP); +} + +void snp_enable_rmpopt(void) +{ + u64 base; + int cpu; + + if (!rmpopt_capable()) + return; + + rmpopt_pa_start = ALIGN_DOWN(PFN_PHYS(min_low_pfn), SZ_1G); + + /* + * Per-CPU RMPOPT tables cover at most 2 TB. Program each core's + * RMPOPT_BASE with the start of RAM to optimize up to 2 TB. + */ + rdmsrq(MSR_AMD64_RMPOPT_BASE, base); + if (!(base & MSR_AMD64_RMPOPT_ENABLE)) + for_each_cpu(cpu, cpu_primary_thread_mask) + wrmsrq_on_cpu(cpu, MSR_AMD64_RMPOPT_BASE, + rmpopt_pa_start | MSR_AMD64_RMPOPT_ENABLE); +} +EXPORT_SYMBOL_FOR_MODULES(snp_enable_rmpopt, "ccp"); + /* * Do the necessary preparations which are verified by the firmware as * described in the SNP_INIT_EX firmware command description in the SNP @@ -699,13 +729,21 @@ static bool probe_segmented_rmptable_info(void) bool snp_probe_rmptable_info(void) { - if (cpu_feature_enabled(X86_FEATURE_SEGMENTED_RMP)) + if (cpu_feature_enabled(X86_FEATURE_SEGMENTED_RMP)) { rdmsrq(MSR_AMD64_RMP_CFG, rmp_cfg); - if (rmp_cfg & MSR_AMD64_SEG_RMP_ENABLED) - return probe_segmented_rmptable_info(); - else - return probe_contiguous_rmptable_info(); + if (rmp_cfg & MSR_AMD64_SEG_RMP_ENABLED) { + if (probe_segmented_rmptable_info()) + return true; + + setup_clear_cpu_cap(X86_FEATURE_RMPOPT); + return false; + } + } else { + setup_clear_cpu_cap(X86_FEATURE_RMPOPT); + } + + return probe_contiguous_rmptable_info(); } /* diff --git a/drivers/crypto/ccp/sev-dev.c b/drivers/crypto/ccp/sev-dev.c index f833cb7e4da360..5c996ab6389594 100644 --- a/drivers/crypto/ccp/sev-dev.c +++ b/drivers/crypto/ccp/sev-dev.c @@ -1663,6 +1663,8 @@ static int __sev_snp_init_locked(int *error, unsigned int max_snp_asid) sev_es_tmr_size = SNP_TMR_SIZE; + snp_enable_rmpopt(); + return 0; } From 975809caf4b0e07330110b56ca2d8a3fa7e16ff5 Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Mon, 24 Aug 2026 16:27:23 -0300 Subject: [PATCH 0564/2286] x86/asm/string_64: Remove unused linux/jump_label.h include Remove the include from since it is no longer required. This include was introduced for the static key declaration first added by 3637efb00864 ("x86/mce: Add PCI quirks to identify Xeons with machine check recovery"). However, it was left when the static key declaration was later removed by ec6347bb4339 ("x86, powerpc: Rename memcpy_mcsafe() to copy_mc_to_{user, kernel}()"). Signed-off-by: Mauricio Faria de Oliveira Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260824-x86-inc-asm-str64-jmplbl-v2-1-df4d7bec9616@igalia.com --- arch/x86/include/asm/string_64.h | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/x86/include/asm/string_64.h b/arch/x86/include/asm/string_64.h index 4635616863f53d..0ea680bf403a91 100644 --- a/arch/x86/include/asm/string_64.h +++ b/arch/x86/include/asm/string_64.h @@ -3,7 +3,6 @@ #define _ASM_X86_STRING_64_H #ifdef __KERNEL__ -#include /* Written 2002 by Andi Kleen */ From e71035eb6e2d8af8bfde78938671695571e6d063 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 2 Sep 2026 17:30:02 -0700 Subject: [PATCH 0565/2286] rcu: Don't panic on already-ended RCU CPU stalls The current RCU CPU stall-warning code can say "INFO: Stall ended before state dump start" but then panic the system anyway. This commit therefore avoids panicking the system if the stall has already ended. However, the sched_ext scheduler will still be ejected, as it might have been the cause of the almost stall. This can easily be changed if needed. Signed-off-by: Paul E. McKenney --- kernel/rcu/tree_exp.h | 2 +- kernel/rcu/tree_stall.h | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/kernel/rcu/tree_exp.h b/kernel/rcu/tree_exp.h index 81d42b6e2e312d..01bdbd7e28cca7 100644 --- a/kernel/rcu/tree_exp.h +++ b/kernel/rcu/tree_exp.h @@ -678,7 +678,7 @@ static void synchronize_rcu_expedited_wait(void) nbcon_cpu_emergency_exit(); - panic_on_rcu_stall(&rcu_exp_stall_cpumask); + panic_on_rcu_stall(&rcu_exp_stall_cpumask, NULL, 0); } } diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h index 091e7850ab6ecc..93ba31a619b670 100644 --- a/kernel/rcu/tree_stall.h +++ b/kernel/rcu/tree_stall.h @@ -159,7 +159,8 @@ static int __init check_cpu_stall_init(void) early_initcall(check_cpu_stall_init); /* If so specified via sysctl, panic, yielding cleaner stall-warning output. */ -static void panic_on_rcu_stall(const struct cpumask *stalled_mask) +static void panic_on_rcu_stall(const struct cpumask *stalled_mask, + unsigned long *gsp, unsigned long gp_seq) { static int cpu_stall; @@ -170,6 +171,11 @@ static void panic_on_rcu_stall(const struct cpumask *stalled_mask) if (scx_rcu_cpu_stall(stalled_mask)) return; + if (gsp && rcu_seq_current(gsp) != gp_seq) { + pr_err("INFO: Stall ended before panic check.\n"); + return; + } + if (++cpu_stall < sysctl_max_rcu_stall_to_panic) return; @@ -703,7 +709,7 @@ static void print_other_cpu_stall(unsigned long gp_seq, unsigned long gps) nbcon_cpu_emergency_exit(); - panic_on_rcu_stall(&rcu_stall_cpumask); + panic_on_rcu_stall(&rcu_stall_cpumask, &rcu_state.gp_seq, gp_seq); rcu_force_quiescent_state(); /* Kick them all. */ } @@ -758,7 +764,7 @@ static void print_cpu_stall(unsigned long gp_seq, unsigned long gps) cpumask_clear(&rcu_stall_cpumask); cpumask_set_cpu(smp_processor_id(), &rcu_stall_cpumask); - panic_on_rcu_stall(&rcu_stall_cpumask); + panic_on_rcu_stall(&rcu_stall_cpumask, &rcu_state.gp_seq, gp_seq); /* * Attempt to revive the RCU machinery by forcing a context switch. From c6c6f156d192bf905eb6e46d8ad6229fa9bbe036 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Wed, 16 Sep 2026 22:15:14 +0000 Subject: [PATCH 0566/2286] x86/sev: Perform RMP optimizations asynchronously When SNP is enabled, all writes to memory are checked to ensure memory integrity. This imposes performance overhead on the whole system. RMPOPT is a new instruction that minimizes the performance overhead of RMP checks on the hypervisor and on non-SNP guests by allowing such checks to be skipped for 1GB regions of memory that are known not to contain any SNP guest memory. Add support for performing RMP optimizations asynchronously using a dedicated per-CPU workqueue. Shortly after SNP initialization, run an optimization pass over all physical memory. As SNP guests are launched, RMPUPDATE assigns their private pages to guest-owned state; when such a page falls within an optimized 1GB region, the hardware clears that region's RMPOPT optimization and RMP checks resume there to protect the guest memory. Since launching SNP guests clears these optimizations, perform them again asynchronously using the dedicated workqueue. [ bp: - Massage commit message and comments - simplify code - redo some of the logic ] Suggested-by: Thomas Lendacky Suggested-by: Dave Hansen Signed-off-by: Ashish Kalra Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/daa513fbe8f0672b00bc5e026e4399244be6dc72.1789594774.git.ashish.kalra@amd.com --- arch/x86/virt/svm/sev.c | 100 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c index 1fecd246ce5f58..2d2a8b144af744 100644 --- a/arch/x86/virt/svm/sev.c +++ b/arch/x86/virt/svm/sev.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -124,7 +125,27 @@ static void *rmp_bookkeeping __ro_after_init; static u64 probed_rmp_base, probed_rmp_size; -static phys_addr_t rmpopt_pa_start; +static u64 rmpopt_pa_start, rmpopt_pa_end; + +enum rmpopt_op_type { + RMPOPT_OP_VERIFY_AND_REPORT_STATUS, + RMPOPT_OP_REPORT_STATUS +}; + +static struct workqueue_struct *rmpopt_wq; +static struct delayed_work rmpopt_delayed_work; + +/* Software RMPOPT facilities initialized */ +static bool rmpopt_soft_init; + +/* + * Delay, in milliseconds, before the RMP re-optimization pass runs after an + * SNP guest is torn down. This coalesces a burst of teardowns into a single + * scan and gives each guest's pages time to be converted back to the shared, + * hypervisor-owned state. The 10 second value is a heuristic trading + * re-optimization latency against scanning too eagerly. + */ +#define RMPOPT_WORK_TIMEOUT (10 * MSEC_PER_SEC) static LIST_HEAD(snp_leaked_pages_list); static DEFINE_SPINLOCK(snp_leaked_pages_list_lock); @@ -565,6 +586,9 @@ void snp_shutdown(void) if (syscfg & MSR_AMD64_SYSCFG_SNP_EN) return; + if (rmpopt_soft_init) + cancel_delayed_work_sync(&rmpopt_delayed_work); + clear_rmp(); on_each_cpu(mfd_reconfigure, NULL, 1); @@ -577,21 +601,82 @@ void snp_shutdown(void) } EXPORT_SYMBOL_FOR_MODULES(snp_shutdown, "ccp"); -static bool rmpopt_capable(void) +/* + * RMPOPT optimizations skip RMP checks at 1GB granularity if this range of + * memory does not contain any SNP guest memory. + * + * @pa is a system physical address; RMPOPT operates on the containing 1GB. + */ +static void rmpopt(u64 pa) { - return cpu_feature_enabled(X86_FEATURE_RMPOPT) && - cc_platform_has(CC_ATTR_HOST_SEV_SNP); + enum rmpopt_op_type op = RMPOPT_OP_VERIFY_AND_REPORT_STATUS; + u64 pa_start = ALIGN_DOWN(pa, SZ_1G); + + /* Supported by binutils 2.48+ */ + asm volatile(".byte 0xf2, 0x0f, 0x01, 0xfc" + :: "a" (pa_start), "c" (op) + : "memory", "cc"); +} + +static void rmpopt_scan_range(void *arg) +{ + u64 pa; + + for (pa = rmpopt_pa_start; pa < rmpopt_pa_end; pa += SZ_1G) + rmpopt(pa); } +static void do_rmpopt_work(struct work_struct *work) +{ + /* + * Warm up the RMPOPT cache on this pinned per-CPU worker with interrupts + * enabled, so the IRQ-disabled fan-out below only issues cache-hit RMPOPTs. + */ + rmpopt_scan_range(NULL); + + on_each_cpu_mask(cpu_primary_thread_mask, rmpopt_scan_range, NULL, true); +} + +static int __init rmpopt_init(void) +{ + if (!cpu_feature_enabled(X86_FEATURE_RMPOPT)) + return -ENODEV; + + rmpopt_wq = alloc_workqueue("rmpopt_wq", WQ_PERCPU, 1); + if (!rmpopt_wq) { + pr_err("Failed to allocate RMPOPT workqueue\n"); + return -ENOMEM; + } + + INIT_DELAYED_WORK(&rmpopt_delayed_work, do_rmpopt_work); + + /* The optimization range is fixed at boot; compute it once. */ + rmpopt_pa_start = ALIGN_DOWN(PFN_PHYS(min_low_pfn), SZ_1G); + rmpopt_pa_end = ALIGN(PFN_PHYS(max_pfn), SZ_1G); + if ((rmpopt_pa_end - rmpopt_pa_start) > SZ_2T) + rmpopt_pa_end = rmpopt_pa_start + SZ_2T; + + pr_info("RMPOPT optimizations enabled\n"); + + rmpopt_soft_init = true; + + return 0; +} +device_initcall(rmpopt_init); + void snp_enable_rmpopt(void) { u64 base; int cpu; - if (!rmpopt_capable()) + if (!cpu_feature_enabled(X86_FEATURE_RMPOPT)) return; - rmpopt_pa_start = ALIGN_DOWN(PFN_PHYS(min_low_pfn), SZ_1G); + if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP)) + return; + + if (!rmpopt_soft_init) + return; /* * Per-CPU RMPOPT tables cover at most 2 TB. Program each core's @@ -602,6 +687,9 @@ void snp_enable_rmpopt(void) for_each_cpu(cpu, cpu_primary_thread_mask) wrmsrq_on_cpu(cpu, MSR_AMD64_RMPOPT_BASE, rmpopt_pa_start | MSR_AMD64_RMPOPT_ENABLE); + + mod_delayed_work(rmpopt_wq, &rmpopt_delayed_work, + msecs_to_jiffies(RMPOPT_WORK_TIMEOUT)); } EXPORT_SYMBOL_FOR_MODULES(snp_enable_rmpopt, "ccp"); From edb1b49e724ea102b3eaeb0b57f8ca33cd445887 Mon Sep 17 00:00:00 2001 From: Ashish Kalra Date: Wed, 16 Sep 2026 22:15:37 +0000 Subject: [PATCH 0567/2286] x86/sev: Do RMP optimizations on SNP guest shutdown The RMPOPT table is a per-CPU table which indicates whether 1GB regions of physical memory are entirely hypervisor-owned. When performing host memory accesses in hypervisor mode as well as non-SNP guest mode, the processor may consult the RMPOPT table to potentially skip an RMP access and improve performance. Normal guest events disable RMP optimizations: pages are converted from shared to private as SNP guests are launched, and large pages are split and collapsed during guest operation -- both disable the RMPOPT optimizations for the affected 1GB regions. When guests are torn down, their pages are converted back to shared, so those regions may become eligible for RMP optimizations again. Without some intervention, all those optimizations would eventually be lost, so optimize all of physical memory on SNP guest teardown. snp_enable_rmpopt() performs the re-optimization after a delay, which gets reset on each call. This way multiple guest terminations are batched into a single pass: the re-optimization runs 10 seconds after the *last* termination rather than after the first. mod_delayed_work() also re-queues work that is already in-flight, so a re-scan request during an active scan is not silently dropped. Guest teardown is currently the only event that returns guest memory to hypervisor ownership: SNP guests do not support ballooning or memory hotplug, so pages freed during a guest's lifetime remain guest-owned. It is therefore the only point at which memory becomes eligible for RMP re-optimization, which is why re-optimization is driven by guest teardown rather than by a periodic scan. [ bp: Massage commit message. ] Signed-off-by: Ashish Kalra Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Ackerley Tng Reviewed-by: Tom Lendacky Reviewed-by: Dave Hansen Link: https://patch.msgid.link/7a15a880ea6b6d53b3702def3b3434307656afd7.1789594774.git.ashish.kalra@amd.com --- arch/x86/kvm/svm/sev.c | 2 ++ arch/x86/virt/svm/sev.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index 5705723f1f4121..4d5e30af1adea0 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -3032,6 +3032,8 @@ void sev_vm_destroy(struct kvm *kvm) */ if (snp_decommission_context(kvm)) return; + + snp_enable_rmpopt(); } else { sev_unbind_asid(kvm, sev->handle); } diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c index 2d2a8b144af744..bd70c4d0b774a3 100644 --- a/arch/x86/virt/svm/sev.c +++ b/arch/x86/virt/svm/sev.c @@ -691,7 +691,7 @@ void snp_enable_rmpopt(void) mod_delayed_work(rmpopt_wq, &rmpopt_delayed_work, msecs_to_jiffies(RMPOPT_WORK_TIMEOUT)); } -EXPORT_SYMBOL_FOR_MODULES(snp_enable_rmpopt, "ccp"); +EXPORT_SYMBOL_FOR_MODULES(snp_enable_rmpopt, "ccp,kvm-amd"); /* * Do the necessary preparations which are verified by the firmware as From 8393ce53a5497552320a34794549c18503dd491b Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Tue, 15 Sep 2026 08:13:51 -0300 Subject: [PATCH 0568/2286] nilfs2: validate file block counts during recovery nilfs_scan_dsync_log() trusts the block counts in each on-disk file information entry. If fi_ndatablk is greater than fi_nblocks, the data block loop can consume excessive summary entries and the later subtraction used to derive the number of node blocks underflows. Reject inconsistent file information entries before consuming their block information. Fixes: 0f3e1c7f23f8 ("nilfs2: recovery functions") Cc: stable+noautosel@kernel.org # Non-fatal bug fix; defer backport until a real issue is reported Signed-off-by: Aldo Ariel Panzardo Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/recovery.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c index 45fb372156699c..9a70c28ec94c18 100644 --- a/fs/nilfs2/recovery.c +++ b/fs/nilfs2/recovery.c @@ -322,6 +322,7 @@ static void nilfs_skip_summary_info(struct the_nilfs *nilfs, * * Return: 0 on success, or one of the following negative error codes on * failure: + * * %-EINVAL - Invalid block counts in a file information entry. * * %-EIO - I/O error. * * %-ENOMEM - Insufficient memory available. */ @@ -359,6 +360,10 @@ static int nilfs_scan_dsync_log(struct the_nilfs *nilfs, sector_t start_blocknr, ino = le64_to_cpu(finfo->fi_ino); nblocks = le32_to_cpu(finfo->fi_nblocks); ndatablk = le32_to_cpu(finfo->fi_ndatablk); + if (ndatablk > nblocks) { + err = -EINVAL; + goto out; + } nnodeblk = nblocks - ndatablk; while (ndatablk-- > 0) { From f2212892b6a0c2adab438783e9fa8093c90786d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 14 Sep 2026 00:26:31 +0200 Subject: [PATCH 0569/2286] tools/nolibc/parisc: consistently use %dp to refer to register %r27 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parisc initialization code inconsistently uses both %dp and %r27 to refer to the data pointer register. Consistently use the %dp memnonic. Suggested-by: Helge Deller Link: https://lore.kernel.org/lkml/495ef25e-0bac-4f98-b0da-ae0d96dcea59@gmx.de/ Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260914-nolibc-parisc-dp-register-v1-1-6388f4aa8fbe@weissschuh.net --- tools/include/nolibc/arch-parisc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/arch-parisc.h b/tools/include/nolibc/arch-parisc.h index 417043ecbc539f..ca219cbbabb09f 100644 --- a/tools/include/nolibc/arch-parisc.h +++ b/tools/include/nolibc/arch-parisc.h @@ -169,7 +169,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote __asm__ volatile ( ".import $global$\n" /* Set up the dp register */ "ldil L%$global$, %dp\n" - "ldo R%$global$(%r27), %dp\n" + "ldo R%$global$(%dp), %dp\n" "b _start_c\n" /* Call _start_c, the load below is executed first */ From a59a940ff89fcaf305362443335f99010ff3b9cf Mon Sep 17 00:00:00 2001 From: Wang Shuaiwei Date: Fri, 4 Sep 2026 14:45:06 +0800 Subject: [PATCH 0570/2286] tick/nohz: Add BLOCK_SOFTIRQ to the hotplug safe mask During CPU-hotplug stress testing combined with an I/O workload, the following message is observed: "NOHZ tick-stop error: local softirq work is pending, handler #10!!!" On CPU offline, ksoftirqd is parked at CPUHP_AP_SMPBOOT_THREADS while the CPU still takes interrupts. A block completion raised after that point stays pending, and if the dying CPU goes idle before teardown, report_idle_softirq() sees it, keeps the tick alive and prints the error above. Such pending block completions on the outgoing CPU are drained later by blk_softirq_cpu_dead() (CPUHP_BLOCK_SOFTIRQ_DEAD), so a pending BLOCK_SOFTIRQ is harmless here. Add it to SOFTIRQ_HOTPLUG_SAFE_MASK like the other teardown-covered vectors. Signed-off-by: Wang Shuaiwei Signed-off-by: Thomas Gleixner Reviewed-by: Sebastian Andrzej Siewior Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260904064506.671203-1-wangshuaiwei1@xiaomi.com --- include/linux/interrupt.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h index 3bf969ad8fe078..52bb684090c0da 100644 --- a/include/linux/interrupt.h +++ b/include/linux/interrupt.h @@ -573,9 +573,11 @@ enum * _ IRQ_POLL: irq_poll_cpu_dead() migrates the queue * * _ (HR)TIMER_SOFTIRQ: (hr)timers_dead_cpu() migrates the queue + * + * _ BLOCK_SOFTIRQ: blk_softirq_cpu_dead() completes the remaining requests */ -#define SOFTIRQ_HOTPLUG_SAFE_MASK (BIT(TIMER_SOFTIRQ) | BIT(IRQ_POLL_SOFTIRQ) |\ - BIT(HRTIMER_SOFTIRQ) | BIT(RCU_SOFTIRQ)) +#define SOFTIRQ_HOTPLUG_SAFE_MASK (BIT(TIMER_SOFTIRQ) | BIT(BLOCK_SOFTIRQ) |\ + BIT(IRQ_POLL_SOFTIRQ) | BIT(HRTIMER_SOFTIRQ) | BIT(RCU_SOFTIRQ)) /* map softirq index to softirq name. update 'softirq_to_name' in From 14247bcd7ad87a9ccb0d5843b0a03c60dd062444 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:34 +0800 Subject: [PATCH 0571/2286] docs/zh_TW: process: localize terminology in 7.AdvancedTopics.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 軟件→軟體, 高級→進階, 項目→專案, 用戶→使用者, ...) and sync with the English original (Github → GitHub). update to commit 5ce70894f6ca ("Doc: correct spelling and wording mistakes") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/7.AdvancedTopics.rst | 110 +++++++++--------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/Documentation/translations/zh_TW/process/7.AdvancedTopics.rst b/Documentation/translations/zh_TW/process/7.AdvancedTopics.rst index b449d67e3ad92d..8090d482c4b0a9 100644 --- a/Documentation/translations/zh_TW/process/7.AdvancedTopics.rst +++ b/Documentation/translations/zh_TW/process/7.AdvancedTopics.rst @@ -12,28 +12,29 @@ 吳想成 Wu XiangCheng 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh .. _tw_development_advancedtopics: -高級主題 +進階主題 ======== 現在,希望您能夠掌握開發流程的工作方式。然而,還有更多的東西要學!本節將介紹 -一些主題,這些主題對希望成爲Linux內核開發過程常規部分的開發人員有幫助。 +一些主題,這些主題對希望成為Linux核心開發過程常規部分的開發人員有幫助。 使用Git管理補丁 --------------- -內核使用分佈式版本控制始於2002年初,當時Linus首次開始使用專有的Bitkeeper應用 -程序。雖然BitKeeper存在爭議,但它所體現的軟件版本管理方法卻肯定不是。分佈式 -版本控制可以立即加速內核開發項目。現在有好幾種免費的BitKeeper替代品。 -但無論好壞,內核項目都已經選擇了Git作爲其工具。 +核心使用分散式版本控制始於2002年初,當時Linus首次開始使用專有的Bitkeeper應用 +程式。雖然BitKeeper存在爭議,但它所體現的軟體版本管理方法卻肯定不是。分散式 +版本控制可以立即加速核心開發專案。現在有好幾種自由的BitKeeper替代品。 +但無論好壞,核心專案都已經選擇了Git作為其工具。 -使用Git管理補丁可以使開發人員的生活更加輕鬆,尤其是隨着補丁數量的增長。Git也 +使用Git管理補丁可以使開發人員的生活更加輕鬆,尤其是隨著補丁數量的增長。Git也 有其粗糙的邊角和一定的危險性,它是一個年輕和強大的工具,仍然在其開發人員完善 -中。本文檔不會試圖教會讀者如何使用git;這會是個巨長的文檔。相反,這裏的重點 -將是Git如何特別適合內核開發過程。想要加快用Git速度的開發人員可以在以下網站上 -找到更多信息: +中。本文件不會試圖教會讀者如何使用git;這會是個巨長的文件。相反,這裡的重點 +將是Git如何特別適合核心開發過程。想要加快用Git速度的開發人員可以在以下網站上 +找到更多資訊: https://git-scm.com/ @@ -41,44 +42,44 @@ 同時網上也能找到各種各樣的教程。 -在嘗試使用它生成補丁供他人使用之前,第一要務是閱讀上述網頁,對Git的工作方式 -有一個紮實的瞭解。使用Git的開發人員應能進行拉取主線存儲庫的副本,查詢修訂 +在嘗試使用它產生補丁供他人使用之前,第一要務是閱讀上述網頁,對Git的工作方式 +有一個紮實的瞭解。使用Git的開發人員應能進行拉取主線儲存庫的副本,查詢修訂 歷史,提交對樹的更改,使用分支等操作。瞭解Git用於重寫歷史的工具(如rebase) -也很有用。Git有自己的術語和概念;Git的新用戶應該瞭解引用、遠程分支、索引、 -快進合併、推拉、遊離頭等。一開始可能有點嚇人,但這些概念不難通過一點學習來 +也很有用。Git有自己的術語和概念;Git的新使用者應該瞭解引用、遠端分支、索引、 +快進合併、推拉、遊離頭等。一開始可能有點嚇人,但這些概念不難透過一點學習來 理解。 -使用git生成通過電子郵件提交的補丁是提高速度的一個很好的練習。 +使用git產生透過電子郵件提交的補丁是提高速度的一個很好的練習。 -當您準備好開始建立Git樹供其他人查看時,無疑需要一個可以從中拉取的服務器。 -如果您有一個可以訪問因特網的系統,那麼使用git-daemon設置這樣的服務器相對 -簡單。同時,免費的公共託管網站(例如github)也開始出現在網絡上。成熟的開發 -人員可以在kernel.org上獲得一個帳戶,但這些帳戶並不容易得到;更多有關信息, +當您準備好開始建立Git樹供其他人查看時,無疑需要一個可以從中拉取的伺服器。 +如果您有一個可以連上網際網路的系統,那麼使用git-daemon設定這樣的伺服器相對 +簡單。同時,免費的公共託管網站(例如GitHub)也開始出現在網路上。成熟的開發 +人員可以在kernel.org上獲得一個帳戶,但這些帳戶並不容易得到;更多有關資訊, 請參閱 https://kernel.org/faq/ 。 -正常的Git工作流程涉及到許多分支的使用。每一條開發線都可以分爲單獨的“主題 +正常的Git工作流程涉及到許多分支的使用。每一條開發線都可以分為單獨的“主題 分支”,並獨立維護。Git的分支很容易使用,沒有理由不使用它們。而且,在任何 情況下,您都不應該在任何您打算讓其他人從中拉取的分支中進行開發。應該小心地 -創建公開可用的分支;當開發分支處於完整狀態並已準備好時(而不是之前)才合併 +建立公開可用的分支;當開發分支處於完整狀態並已準備好時(而不是之前)才合併 開發分支的補丁。 Git提供了一些強大的工具,可以讓您重寫開發歷史。一個不方便的補丁(比如說, 一個打破二分法的補丁,或者有其他一些明顯的缺陷)可以在適當的位置修復,或者 完全從歷史中消失。一個補丁系列可以被重寫,就好像它是在今天的主線上寫的一樣, 即使你已經花了幾個月的時間在寫它。可以透明地將更改從一個分支轉移到另一個 -分支。等等。明智地使用git修改歷史的能力可以幫助創建問題更少的乾淨補丁集。 +分支。等等。明智地使用git修改歷史的能力可以幫助建立問題更少的乾淨補丁集。 -然而,過度使用這種功能可能會導致其他問題,而不僅僅是對創建完美項目歷史的 -簡單癡迷。重寫歷史將重寫該歷史中包含的更改,將經過測試(希望如此)的內核樹 -變爲未經測試的內核樹。除此之外,如果開發人員沒有共享項目歷史,他們就無法 -輕鬆地協作;如果您重寫了其他開發人員拉入他們存儲庫的歷史,您將使這些開發 -人員的生活更加困難。因此,這裏有一個簡單的經驗法則:被導出到其他地方的歷史 -在此後通常被認爲是不可變的。 +然而,過度使用這種功能可能會導致其他問題,而不僅僅是對建立完美專案歷史的 +簡單癡迷。重寫歷史將重寫該歷史中包含的更改,將經過測試(希望如此)的核心樹 +變為未經測試的核心樹。除此之外,如果開發人員沒有共享專案歷史,他們就無法 +輕鬆地協作;如果您重寫了其他開發人員拉入他們儲存庫的歷史,您將使這些開發 +人員的生活更加困難。因此,這裡有一個簡單的經驗法則:被導出到其他地方的歷史 +在此後通常被認為是不可變的。 -因此,一旦將一組更改推送到公開可用的服務器上,就不應該重寫這些更改。如果您 +因此,一旦將一組更改推送到公開可用的伺服器上,就不應該重寫這些更改。如果您 嘗試強制進行無法快進合併的更改(即不共享同一歷史記錄的更改),Git將嘗試強制 執行此規則。這可能覆蓋檢查,有時甚至需要重寫導出的樹。在樹之間移動變更集以 -避免linux-next中的衝突就是一個例子。但這種行爲應該是罕見的。這就是爲什麼 +避免linux-next中的衝突就是一個例子。但這種行為應該是罕見的。這就是為什麼 開發應該在私有分支中進行(必要時可以重寫)並且只有在公共分支處於合理的較新 狀態時才轉移到公共分支中的原因之一。 @@ -86,52 +87,53 @@ Git提供了一些強大的工具,可以讓您重寫開發歷史。一個不 對於一個私有的分支,rebasing 可能是一個很容易跟上另一棵樹的方法,但是一旦 一棵樹被導出到外界,rebasing就不可取了。一旦發生這種情況,就必須進行完全 合併(merge)。合併有時是很有意義的,但是過於頻繁的合併會不必要地擾亂歷史。 -在這種情況下建議的做法是不要頻繁合併,通常只在特定的發佈點(如主線-rc發佈) +在這種情況下建議的做法是不要頻繁合併,通常只在特定的發布點(如主線-rc發布) 合併。如果您對特定的更改感到緊張,則可以始終在私有分支中執行測試合併。在 這種情況下,git“rerere”工具很有用;它能記住合併衝突是如何解決的,這樣您 就不必重複相同的工作。 -關於Git這樣的工具的一個最大的反覆抱怨是:補丁從一個存儲庫到另一個存儲庫的 +關於Git這樣的工具的一個最大的反覆抱怨是:補丁從一個儲存庫到另一個儲存庫的 大量移動使得很容易陷入錯誤建議的變更中,這些變更避開審查雷達進入主線。當內 核開發人員看到這種情況發生時,他們往往會感到不高興;在Git樹上放置未審閱或 主題外的補丁可能會影響您將來讓樹被拉取的能力。引用Linus的話: :: - 你可以給我發補丁,但當我從你那裏拉取一個Git補丁時,我需要知道你清楚 + 你可以給我發補丁,但當我從你那裡拉取一個Git補丁時,我需要知道你清楚 自己在做什麼,我需要能夠相信事情而 *無需* 手動檢查每個單獨的更改。 (http://lwn.net/Articles/224135/)。 -爲了避免這種情況,請確保給定分支中的所有補丁都與相關主題緊密相關;“驅動程序 -修復”分支不應更改核心內存管理代碼。而且,最重要的是,不要使用Git樹來繞過 -審查過程。不時的將樹的摘要發佈到相關的列表中,在合適時候請求linux-next中 +為了避免這種情況,請確保給定分支中的所有補丁都與相關主題緊密相關;“驅動程式 +修復”分支不應更改核心記憶體管理程式碼。而且,最重要的是,不要使用Git樹來繞過 +審查過程。不時的將樹的摘要發布到相關的列表中,在合適時候請求linux-next中 包含該樹。 如果其他人開始發送補丁以包含到您的樹中,不要忘記審閱它們。還要確保您維護正確 -的作者信息; git “am”工具在這方面做得最好,但是如果補丁通過第三方轉發給您, +的作者資訊; git “am”工具在這方面做得最好,但是如果補丁透過第三方轉發給您, 您可能需要在補丁中添加“From:”行。 -請求拉取時,請務必提供所有相關信息:樹的位置、要拉取的分支以及拉取將導致的 +請求拉取時,請務必提供所有相關資訊:樹的位置、要拉取的分支以及拉取將導致的 更改。在這方面 git request-pull 命令非常有用;它將按照其他開發人員所期望的 -格式化請求,並檢查以確保您已記得將這些更改推送到公共服務器。 +格式化請求,並檢查以確保您已記得將這些更改推送到公共伺服器。 審閱補丁 -------- -一些讀者顯然會反對將本節與“高級主題”放在一起,因爲即使是剛開始的內核開發人員 -也應該審閱補丁。當然,沒有比查看其他人發佈的代碼更好的方法來學習如何在內核環境 -中編程了。此外,審閱者永遠供不應求;通過審閱代碼,您可以對整個流程做出重大貢獻。 - -審查代碼可能是一副令人生畏的圖景,特別是對一個新的內核開發人員來說,他們 -可能會對公開詢問代碼感到緊張,而這些代碼是由那些有更多經驗的人發佈的。不過, -即使是最有經驗的開發人員編寫的代碼也可以得到改進。也許對(所有)審閱者最好 -的建議是:把審閱評論當成問題而不是批評。詢問“在這條路徑中如何釋放鎖?” -總是比說“這裏的鎖是錯誤的”更好。 - -不同的開發人員將從不同的角度審查代碼。部分人會主要關注代碼風格以及代碼行是 -否有尾隨空格。其他人會主要關注補丁作爲一個整體實現的變更是否對內核有好處。 -同時也有人會檢查是否存在鎖問題、堆棧使用過度、可能的安全問題、在其他地方 -發現的代碼重複、足夠的文檔、對性能的不利影響、用戶空間ABI更改等。所有類型 -的檢查,只要它們能引導更好的代碼進入內核,都是受歡迎和值得的。 +一些讀者顯然會反對將本節與“進階主題”放在一起,因為即使是剛開始的核心開發人 +員也應該審閱補丁。當然,沒有比查看其他人發布的程式碼更好的方法來學習如何在 +核心環境中撰寫程式了。此外,審閱者永遠供不應求;透過審閱程式碼,您可以對整 +個流程做出重大貢獻。 + +審查程式碼可能是一副令人生畏的圖景,特別是對一個新的核心開發人員來說,他們 +可能會對公開詢問程式碼感到緊張,而這些程式碼是由那些有更多經驗的人發布的。 +不過,即使是最有經驗的開發人員編寫的程式碼也可以得到改進。也許對(所有)審 +閱者最好的建議是:把審閱評論當成問題而不是批評。詢問“在這條路徑中如何釋放 +鎖?”總是比說“這裡的鎖是錯誤的”更好。 + +不同的開發人員將從不同的角度審查程式碼。部分人會主要關注程式碼風格以及程式 +碼行是否有尾隨空格。其他人會主要關注補丁作為一個整體實作的變更是否對核心有 +好處。同時也有人會檢查是否存在鎖問題、堆疊使用過度、可能的安全問題、在其他 +地方發現的程式碼重複、足夠的文件、對效能的不利影響、使用者空間ABI更改等。 +所有類型的檢查,只要它們能引導更好的程式碼進入核心,都是受歡迎和值得的。 From 3f03b7328209637e1107b22e869eaa06cfebee2c Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:35 +0800 Subject: [PATCH 0572/2286] docs/zh_TW: process: localize terminology in 1.Intro.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 軟件→軟體, 免費軟件→自由軟體, 操作系統→作業系統, 模塊→模組, 社區→社群, ...) and sync with the English original: contributor identity wording now follows commit 43e9076a00b1 ("docs: Fix conflicting contributor identity info"). update to commit 5ce70894f6ca ("Doc: correct spelling and wording mistakes") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../translations/zh_TW/process/1.Intro.rst | 235 +++++++++--------- 1 file changed, 120 insertions(+), 115 deletions(-) diff --git a/Documentation/translations/zh_TW/process/1.Intro.rst b/Documentation/translations/zh_TW/process/1.Intro.rst index 345c4cbe9b5555..2639ab99793f18 100644 --- a/Documentation/translations/zh_TW/process/1.Intro.rst +++ b/Documentation/translations/zh_TW/process/1.Intro.rst @@ -12,6 +12,7 @@ 吳想成 Wu XiangCheng 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh .. _tw_development_process_intro: @@ -21,179 +22,183 @@ 內容提要 -------- -本節的其餘部分涵蓋了內核開發的過程,以及開發人員及其僱主在這方面可能遇到的 -各種問題。有很多原因使內核代碼應被合併到正式的(“主線”)內核中,包括對用戶 -的自動可用性、多種形式的社區支持以及影響內核開發方向的能力。提供給Linux內核 -的代碼必須在與GPL兼容的許可證下可用。 +本節的其餘部分涵蓋了核心開發的過程,以及開發人員及其僱主在這方面可能遇到的 +各種問題。有很多原因使核心程式碼應被合併到正式的(“主線”)核心中,包括對使用者 +的自動可用性、多種形式的社群支援以及影響核心開發方向的能力。提供給Linux核心 +的程式碼必須在與GPL相容的許可證下可用。 -:ref:`tw_development_process` 介紹了開發過程、內核發佈週期和合並窗口的機制。 -涵蓋了補丁開發、審查和合並週期中的各個階段。還有一些關於工具和郵件列表的討論? -鼓勵希望開始內核開發的開發人員跟蹤並修復缺陷以作爲初步練習。 +:ref:`tw_development_process` 介紹了開發過程、核心發布週期和合併視窗的機制。 +涵蓋了補丁開發、審查和合併週期中的各個階段。還有一些關於工具和郵件列表的討論。 +鼓勵希望開始核心開發的開發人員追蹤並修復缺陷以作為初步練習。 -:ref:`tw_development_early_stage` 包括項目的早期規劃,重點是儘快讓開發社區 +:ref:`tw_development_early_stage` 包括專案的早期規劃,重點是儘快讓開發社群 參與進來。 -:ref:`tw_development_coding` 是關於編程過程的;介紹了其他開發人員遇到的幾個 -陷阱。也涵蓋了對補丁的一些要求,並且介紹了一些工具,這些工具有助於確保內核 +:ref:`tw_development_coding` 是關於程式設計過程的;介紹了其他開發人員遇到的幾個 +陷阱。也涵蓋了對補丁的一些要求,並且介紹了一些工具,這些工具有助於確保核心 補丁是正確的。 -:ref:`tw_development_posting` 描述發佈補丁以供評審的過程。爲了讓開發社區能 +:ref:`tw_development_posting` 描述發布補丁以供評審的過程。為了讓開發社群能 認真對待,補丁必須被正確格式化和描述,並且必須發送到正確的地方。遵循本節中的 建議有助於確保您的工作能被較好地接納。 -:ref:`tw_development_followthrough` 介紹了發佈補丁之後發生的事情;工作在這時 +:ref:`tw_development_followthrough` 介紹了發布補丁之後發生的事情;工作在這時 還遠遠沒有完成。與審閱者一起工作是開發過程中的一個重要部分;本節提供了一些 關於如何在這個重要階段避免問題的提示。當補丁被合併到主線中時,開發人員要注意 不要假定任務已經完成。 -:ref:`tw_development_advancedtopics` 介紹了兩個“高級”主題:使用Git管理補丁 -和查看其他人發佈的補丁。 +:ref:`tw_development_advancedtopics` 介紹了兩個“進階”主題:使用Git管理補丁 +和查看其他人發布的補丁。 -:ref:`tw_development_conclusion` 總結了有關內核開發的更多信息,附帶有相關資源 -鏈接。 +:ref:`tw_development_conclusion` 總結了有關核心開發的更多資訊,附帶有相關資源 +連結。 -這個文檔是關於什麼的 +這個文件是關於什麼的 -------------------- -Linux內核有超過800萬行代碼,每個版本的貢獻者超過1000人,是現存最大、最活躍的 -免費軟件項目之一。從1991年開始,這個內核已經發展成爲一個最好的操作系統組件, -運行在袖珍數字音樂播放器、臺式電腦、現存最大的超級計算機以及所有類型的系統上。 -它是一種適用於幾乎任何情況的健壯、高效和可擴展的解決方案。 - -隨着Linux的發展,希望參與其開發的開發人員(和公司)的數量也在增加。硬件供應商 -希望確保Linux能夠很好地支持他們的產品,使這些產品對Linux用戶具有吸引力。嵌入 -式系統供應商使用Linux作爲集成產品的組件,希望Linux能夠儘可能地勝任手頭的任務。 -分銷商和其他基於Linux的軟件供應商切實關心Linux內核的功能、性能和可靠性。最終 -用戶也常常希望修改Linux,使之能更好地滿足他們的需求。 - -Linux最引人注目的特性之一是這些開發人員可以訪問它;任何具備必要技能的人都可以 -改進Linux並影響其開發方向。專有產品不能提供這種開放性,這是自由軟件的一個特點。 -如果有什麼不同的話,那就是內核比大多數其他自由軟件項目更開放。一個典型的三個 -月內核開發週期可以涉及1000多個開發人員,他們爲100多個不同的公司(或者根本不 -隸屬公司)工作。 - -與內核開發社區合作並不是特別困難。但儘管如此,仍有許多潛在的貢獻者在嘗試做 -內核工作時遇到了困難。內核社區已經發展出自己獨特的操作方式,使其能夠在每天 -都要更改數千行代碼的環境中順利運行(並生成高質量的產品)。因此,Linux內核開發 -過程與專有的開發模式有很大的不同也就不足爲奇了。 - -對於新開發人員來說,內核的開發過程可能會讓人感到奇怪和恐懼,但這背後有充分的 -理由和堅實的經驗。一個不瞭解內核社區工作方式的開發人員(或者更糟的是,他們 -試圖拋棄或規避之)會得到令人沮喪的體驗。開發社區在幫助那些試圖學習的人的同時, +Linux核心有超過800萬行程式碼,每個版本的貢獻者超過1000人,是現存最大、最活躍的 +自由軟體專案之一。從1991年開始,這個核心已經發展成為一個最好的作業系統元件, +執行在袖珍數位音樂播放器、桌上型電腦、現存最大的超級電腦以及所有類型的系統上。 +它是一種適用於幾乎任何情況的強健、高效和可擴展的解決方案。 + +隨著Linux的發展,希望參與其開發的開發人員(和公司)的數量也在增加。硬體供應商 +希望確保Linux能夠很好地支援他們的產品,使這些產品對Linux使用者具有吸引力。嵌入 +式系統供應商使用Linux作為整合產品的元件,希望Linux能夠儘可能地勝任手頭的任務。 +分銷商和其他基於Linux的軟體供應商切實關心Linux核心的功能、效能和可靠性。最終 +使用者也常常希望修改Linux,使之能更好地滿足他們的需求。 + +Linux最引人注目的特性之一是這些開發人員可以存取它;任何具備必要技能的人都 +可以改進Linux並影響其開發方向。專有產品不能提供這種開放性,這是自由軟體的 +一個特點。如果有什麼不同的話,那就是核心比大多數其他自由軟體專案更開放。一 +個典型的三個月核心開發週期可以涉及1000多個開發人員,他們為100多個不同的公 +司(或者根本不隸屬公司)工作。 + +與核心開發社群合作並不是特別困難。但儘管如此,仍有許多潛在的貢獻者在嘗試做 +核心工作時遇到了困難。核心社群已經發展出自己獨特的操作方式,使其能夠在每天 +都要更改數千行程式碼的環境中順利執行(並產生高品質的產品)。因此,Linux核 +心開發過程與專有的開發模式有很大的不同也就不足為奇了。 + +對於新開發人員來說,核心的開發過程可能會讓人感到奇怪和恐懼,但這背後有充分的 +理由和堅實的經驗。一個不瞭解核心社群工作方式的開發人員(或者更糟的是,他們 +試圖拋棄或規避之)會得到令人沮喪的體驗。開發社群在幫助那些試圖學習的人的同時, 沒有時間幫助那些不願意傾聽或不關心開發過程的人。 希望閱讀本文的人能夠避免這種令人沮喪的經歷。這些材料很長,但閱讀它們時所做的 -努力會在短時間內得到回報。開發社區總是需要能讓內核變更好的開發人員;下面的 -文字應該幫助您或爲您工作的人員加入我們的社區。 +努力會在短時間內得到回報。開發社群總是需要能讓核心變更好的開發人員;下面的 +文字應該幫助您或為您工作的人員加入我們的社群。 致謝 ---- -本文檔由Jonathan Corbet 撰寫。以下人員的建議使之更爲完善: +本文件由Jonathan Corbet 撰寫。以下人員的建議使之更為完善: Johannes Berg, James Berry, Alex Chiang, Roland Dreier, Randy Dunlap, Jake Edge, Jiri Kosina, Matt Mackall, Arthur Marsh, Amanda McPherson, Andrew Morton, Andrew Price, Tsugikazu Shibata 和 Jochen Voß 。 -這項工作得到了Linux基金會的支持,特別感謝Amanda McPherson,他看到了這項工作 +這項工作得到了Linux基金會的支援,特別感謝Amanda McPherson,他看到了這項工作 的價值並將其變成現實。 -代碼進入主線的重要性 --------------------- +程式碼進入主線的重要性 +---------------------- -有些公司和開發人員偶爾會想,爲什麼他們要費心學習如何與內核社區合作,並將代碼 -放入主線內核(“主線”是由Linus Torvalds維護的內核,Linux發行商將其用作基礎)。 -在短期內,貢獻代碼看起來像是一種可以避免的開銷;維護獨立代碼並直接支持用戶 -似乎更容易。事實上,保持代碼獨立(“樹外”)是在經濟上是錯誤的。 +有些公司和開發人員偶爾會想,為什麼他們要費心學習如何與核心社群合作,並將程 +式碼放入主線核心(“主線”是由Linus Torvalds維護的核心,Linux發行商將其用作 +基礎)。在短期內,貢獻程式碼看起來像是一種可以避免的開銷;維護獨立程式碼並 +直接支援使用者似乎更容易。事實上,保持程式碼獨立(“樹外”)是在經濟上是錯誤 +的。 -爲了說明樹外代碼成本,下面給出內核開發過程的一些相關方面;本文稍後將更詳細地 +為了說明樹外程式碼成本,下面給出核心開發過程的一些相關方面;本文稍後將更詳細地 討論其中的大部分內容。請考慮: -- 所有Linux用戶都可以使用合併到主線內核中的代碼。它將自動出現在所有啓用它的 - 發行版上。無需驅動程序磁盤、額外下載,也不需要爲多個發行版的多個版本提供 - 支持;這一切將方便所有開發人員和用戶。併入主線解決了大量的分發和支持問題。 +- 所有Linux使用者都可以使用合併到主線核心中的程式碼。它將自動出現在所有啟 + 用它的發行版上。無需驅動程式磁碟、額外下載,也不需要為多個發行版的多個版 + 本提供支援;這一切將方便所有開發人員和使用者。併入主線解決了大量的分發和 + 支援問題。 -- 當內核開發人員努力維護一個穩定的用戶空間接口時,內核內部API處於不斷變化之中。 - 不維持穩定的內部接口是一個慎重的設計決策;它允許在任何時候進行基本的改進, - 併產出更高質量的代碼。但該策略導致結果是,若要使用新的內核,任何樹外代碼都 - 需要持續的維護。維護樹外代碼會需要大量的工作才能使代碼保持正常運行。 +- 當核心開發人員努力維護一個穩定的使用者空間介面時,核心內部API處於不斷變 + 化之中。不維持穩定的內部介面是一個慎重的設計決策;它允許在任何時候進行基 + 本的改進,並產出更高品質的程式碼。但該策略導致結果是,若要使用新的核心, + 任何樹外程式碼都需要持續的維護。維護樹外程式碼會需要大量的工作才能使程式 + 碼保持正常執行。 - 相反,位於主線中的代碼不需要這樣做,因爲基本規則要求進行API更改的任何開發 - 人員也必須修復由於該更改而破壞的任何代碼。因此,合併到主線中的代碼大大降低 + 相反,位於主線中的程式碼不需要這樣做,因為基本規則要求進行API更改的任何開發 + 人員也必須修復由於該更改而破壞的任何程式碼。因此,合併到主線中的程式碼大大降低 了維護成本。 -- 除此之外,內核中的代碼通常會被其他開發人員改進。您授權的用戶社區和客戶對您 - 產品的改進可能會令人驚喜。 +- 除此之外,核心中的程式碼通常會被其他開發人員改進。您授權的使用者社群和客 + 戶對您產品的改進可能會令人驚喜。 -- 內核代碼在合併到主線之前和之後都要經過審查。無論原始開發人員的技能有多強, - 這個審查過程總是能找到改進代碼的方法。審查經常發現嚴重的錯誤和安全問題。 - 對於在封閉環境中開發的代碼尤其如此;這種代碼從外部開發人員的審查中獲益匪淺。 - 樹外代碼是低質量代碼。 +- 核心程式碼在合併到主線之前和之後都要經過審查。無論原始開發人員的技能有多 + 強,這個審查過程總是能找到改進程式碼的方法。審查經常發現嚴重的錯誤和安全 + 問題。對於在封閉環境中開發的程式碼尤其如此;這種程式碼從外部開發人員的審 + 查中獲益匪淺。樹外程式碼是低品質程式碼。 -- 參與開發過程是您影響內核開發方向的方式。旁觀者的抱怨會被聽到,但是活躍的 - 開發人員有更強的聲音——並且能夠實現使內核更好地滿足其需求的更改。 +- 參與開發過程是您影響核心開發方向的方式。旁觀者的抱怨會被聽到,但是活躍的 + 開發人員有更強的聲音——並且能夠實作使核心更好地滿足其需求的更改。 -- 當單獨維護代碼時,總是存在第三方爲類似功能提供不同實現的可能性。如果發生 - 這種情況,合併代碼將變得更加困難——甚至成爲不可能。之後,您將面臨以下令人 - 不快的選擇:(1)無限期地維護樹外的非標準特性,或(2)放棄代碼並將用戶遷移 - 到樹內版本。 +- 當單獨維護程式碼時,總是存在第三方為類似功能提供不同實作的可能性。如果發 + 生這種情況,合併程式碼將變得更加困難——甚至成為不可能。之後,您將面臨以下 + 令人不快的選擇:(1)無限期地維護樹外的非標準特性,或(2)放棄程式碼並將 + 使用者遷移到樹內版本。 -- 代碼的貢獻是使整個流程工作的根本。通過貢獻代碼,您可以向內核添加新功能,並 - 提供其他內核開發人員使用的功能和示例。如果您已經爲Linux開發了代碼(或者正在 - 考慮這樣做),那麼您顯然對這個平臺的持續成功感興趣;貢獻代碼是確保成功的 - 最好方法之一。 +- 程式碼的貢獻是使整個流程工作的根本。透過貢獻程式碼,您可以向核心添加新功 + 能,並提供其他核心開發人員使用的功能和範例。如果您已經為Linux開發了程式 + 碼(或者正在考慮這樣做),那麼您顯然對這個平臺的持續成功感興趣;貢獻程式 + 碼是確保成功的最好方法之一。 -上述所有理由都適用於任何樹外內核代碼,包括以專有的、僅二進制形式分發的代碼。 -然而,在考慮任何類型的純二進制內核代碼分佈之前,還需要考慮其他因素。包括: +上述所有理由都適用於任何樹外核心程式碼,包括以專有的、僅二進位形式分發的程 +式碼。然而,在考慮任何類型的純二進位核心程式碼分發之前,還需要考慮其他因素。 +包括: -- 圍繞專有內核模塊分發的法律問題其實較爲模糊;相當多的內核版權所有者認爲, - 大多數僅二進制的模塊是內核的派生產品,因此,它們的分發違反了GNU通用公共 - 許可證(下面將詳細介紹)。本文作者不是律師,本文檔中的任何內容都不可能被 - 視爲法律建議。封閉源代碼模塊的真實法律地位只能由法院決定。但不管怎樣,困擾 - 這些模塊的不確定性仍然存在。 +- 圍繞專有核心模組分發的法律問題其實較為模糊;相當多的核心版權所有者認為, + 大多數僅二進位的模組是核心的派生產品,因此,它們的分發違反了GNU通用公共 + 許可證(下面將詳細介紹)。本文作者不是律師,本文件中的任何內容都不可能被 + 視為法律建議。封閉原始程式碼模組的真實法律地位只能由法院決定。但不管怎樣, + 困擾這些模組的不確定性仍然存在。 -- 二進制模塊大大增加了調試內核問題的難度,以至於大多數內核開發人員甚至都不會 - 嘗試。因此,只分發二進制模塊將使您的用戶更難從社區獲得支持。 +- 二進位模組大大增加了除錯核心問題的難度,以至於大多數核心開發人員甚至都不會 + 嘗試。因此,只分發二進位模組將使您的使用者更難從社群獲得支援。 -- 對於僅二進制的模塊的發行者來說,支持也更加困難,他們必須爲他們希望支持的 - 每個發行版和每個內核版本提供不同版本的模塊。爲了提供較爲全面的覆蓋範圍, - 可能需要一個模塊的幾十個構建,並且每次升級內核時,您的用戶都必須單獨升級 - 這些模塊。 +- 對於僅二進位的模組的發行者來說,支援也更加困難,他們必須為他們希望支援的 + 每個發行版和每個核心版本提供不同版本的模組。為了提供較為全面的覆蓋範圍, + 可能需要一個模組的幾十個建置,並且每次升級核心時,您的使用者都必須單獨升級 + 這些模組。 -- 上面提到的關於代碼評審的所有問題都更加存在於封閉源代碼中。由於該代碼根本 - 不可得,因此社區無法對其進行審查,毫無疑問,它將存在嚴重問題。 +- 上面提到的關於程式碼評審的所有問題都更加存在於封閉原始程式碼中。由於該程 + 式碼根本不可得,因此社群無法對其進行審查,毫無疑問,它將存在嚴重問題。 -尤其是嵌入式系統的製造商,可能會傾向於忽視本節中所說的大部分內容;因爲他們 -相信自己正在商用一種使用凍結內核版本的獨立產品,在發佈後不需要再進行開發。 -這個論點忽略了廣泛的代碼審查的價值以及允許用戶向產品添加功能的價值。但這些 -產品的商業壽命有限,之後必須發佈新版本的產品。在這一點上,代碼在主線上並得到 +尤其是嵌入式系統的製造商,可能會傾向於忽視本節中所說的大部分內容;因為他們 +相信自己正在商用一種使用凍結核心版本的獨立產品,在發布後不需要再進行開發。 +這個論點忽略了廣泛的程式碼審查的價值以及允許使用者向產品添加功能的價值。但這些 +產品的商業壽命有限,之後必須發布新版本的產品。在這一點上,程式碼在主線上並得到 良好維護的供應商將能夠更好地佔位,以使新產品快速上市。 許可 ---- -代碼是根據一些許可證提供給Linux內核的,但是所有代碼都必須與GNU通用公共許可 -證(GPLV2)的版本2兼容,該版本是覆蓋整個內核分發的許可證。在實踐中,這意味 -着所有代碼貢獻都由GPLv2(可選地,語言允許在更高版本的GPL下分發)或3子句BSD -許可(New BSD License,譯者注)覆蓋。任何不包含在兼容許可證中的貢獻都不會 -被接受到內核中。 +程式碼是根據一些許可證提供給Linux核心的,但是所有程式碼都必須與GNU通用公共許可 +證(GPLv2)的版本2相容,該版本是覆蓋整個核心分發的許可證。在實踐中,這意味 +著所有程式碼貢獻都由GPLv2(可選地,語言允許在更高版本的GPL下分發)或3子句BSD +許可(New BSD License,譯者注)覆蓋。任何不包含在相容許可證中的貢獻都不會 +被接受到核心中。 -貢獻給內核的代碼不需要(或請求)版權分配。合併到主線內核中的所有代碼都保留 -其原始所有權;因此,內核現在擁有數千個所有者。 +貢獻給核心的程式碼不需要(或請求)版權分配。合併到主線核心中的所有程式碼都保留 +其原始所有權;因此,核心現在擁有數千個所有者。 -這種所有權結構也暗示着,任何改變內核許可的嘗試都註定會失敗。很少有實際情況 -可以獲得所有版權所有者的同意(或者從內核中刪除他們的代碼)。因此,尤其是在 +這種所有權結構也暗示著,任何改變核心許可的嘗試都註定會失敗。很少有實際情況 +可以獲得所有版權所有者的同意(或者從核心中刪除他們的程式碼)。因此,尤其是在 可預見的將來,許可證不大可能遷移到GPL的版本3。 -所有貢獻給內核的代碼都必須是合法的免費軟件。因此,不接受匿名(或化名)貢獻 -者的代碼。所有貢獻者都需要在他們的代碼上“sign off(簽發)”,聲明代碼可以 -在GPL下與內核一起分發。無法提供未被其所有者許可爲免費軟件的代碼,或可能爲 -內核造成版權相關問題的代碼(例如,由缺乏適當保護的反向工程工作派生的代碼) -不能被接受。 +所有貢獻給核心的程式碼都必須是合法的自由軟體。因此,不接受身分不明或匿名的 +貢獻者的程式碼。所有貢獻者都需要在他們的程式碼上“sign off(簽發)”,聲明程 +式碼可以在GPL下與核心一起分發。無法提供未被其所有者許可為自由軟體的程式碼, +或可能為核心造成版權相關問題的程式碼(例如,由缺乏適當保護的反向工程工作派 +生的程式碼)不能被接受。 -有關版權問題的提問在Linux開發郵件列表中很常見。這樣的問題通常會得到不少答案, -但請記住,回答這些問題的人不是律師,不能提供法律諮詢。如果您有關於Linux源代碼 -的法律問題,沒有什麼可以代替諮詢瞭解這一領域的律師。依賴從技術郵件列表中獲得 -的答案是一件冒險的事情。 +有關版權問題的提問在Linux開發郵件列表中很常見。這樣的問題通常會得到不少答 +案,但請記住,回答這些問題的人不是律師,不能提供法律諮詢。如果您有關於 +Linux原始程式碼的法律問題,沒有什麼可以代替諮詢瞭解這一領域的律師。依賴從 +技術郵件列表中獲得的答案是一件冒險的事情。 From 3e65003c04d2bff9e1eb804a1c6fcaf3ffa955ea Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:36 +0800 Subject: [PATCH 0573/2286] docs/zh_TW: process: localize terminology in code-of-conduct-interpretation.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 社區→社群, 軟件→軟體, ...) and sync with the English original: rewrite the enforcement section to match the current committee/TAB wording and add the "Enforcement for Unacceptable Behavior" section introduced by commit c818d5c64c9a ("Documentation/CoC: spell out enforcement for unacceptable behaviors"). update to commit 04c4bb90ae6e ("Documentation/CoC: Spell out the TAB role in enforcement decisions") Acked-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../code-of-conduct-interpretation.rst | 162 ++++++++++++------ 1 file changed, 113 insertions(+), 49 deletions(-) diff --git a/Documentation/translations/zh_TW/process/code-of-conduct-interpretation.rst b/Documentation/translations/zh_TW/process/code-of-conduct-interpretation.rst index fbe66b00132227..897faa825d1221 100644 --- a/Documentation/translations/zh_TW/process/code-of-conduct-interpretation.rst +++ b/Documentation/translations/zh_TW/process/code-of-conduct-interpretation.rst @@ -5,108 +5,172 @@ :Original: :ref:`Documentation/process/code-of-conduct-interpretation.rst ` :Translator: Alex Shi Hu Haowen <2023002089@link.tyut.edu.cn> + Chen-Yu Yeh .. _tw_code_of_conduct_interpretation: -Linux內核貢獻者契約行爲準則解釋 +Linux核心貢獻者契約行為準則解釋 =============================== -:ref:`tw_code_of_conduct` 準則是一個通用文檔,旨在爲幾乎所有開源社區提供一套規則。 -每個開源社區都是獨一無二的,Linux內核也不例外。因此,本文描述了Linux內核社區中 -如何解釋它。我們也不希望這種解釋隨着時間的推移是靜態的,並將根據需要進行調整。 +:ref:`tw_code_of_conduct` 準則是一個通用文件,旨在為幾乎所有開源社群提供一套規則。 +每個開源社群都是獨一無二的,Linux核心也不例外。因此,本文描述了Linux核心社群中 +如何解釋它。我們也不希望這種解釋隨著時間的推移是靜態的,並將根據需要進行調整。 -與開發軟件的“傳統”方法相比,Linux內核開發工作是一個非常個人化的過程。你的貢獻 +與開發軟體的“傳統”方法相比,Linux核心開發工作是一個非常個人化的過程。你的貢獻 和背後的想法將被仔細審查,往往導致批判和批評。審查將幾乎總是需要改進,材料才 -能包括在內核中。要知道這是因爲所有相關人員都希望看到Linux整體成功的最佳解決方 -案。這個開發過程已經被證明可以創建有史以來最健壯的操作系統內核,我們不想做任何 -事情來導致提交質量和最終結果的下降。 +能包括在核心中。要知道這是因為所有相關人員都希望看到Linux整體成功的最佳解決方 +案。這個開發過程已經被證明可以建立有史以來最強健的作業系統核心,我們不想做任何 +事情來導致提交品質和最終結果的下降。 維護者 ------ -行爲準則多次使用“維護者”一詞。在內核社區中,“維護者”是負責子系統、驅動程序或 -文件的任何人,並在內核源代碼樹的維護者文件中列出。 +行為準則多次使用“維護者”一詞。在核心社群中,“維護者”是負責子系統、驅動程式或 +文件的任何人,並在核心原始程式碼樹的維護者文件中列出。 責任 ---- -《行爲準則》提到了維護人員的權利和責任,這需要進一步澄清。 +《行為準則》提到了維護人員的權利和責任,這需要進一步澄清。 -首先,最重要的是,有一個合理的期望是由維護人員通過實例來領導。 +首先,最重要的是,有一個合理的期望是由維護人員透過實例來領導。 -也就是說,我們的社區是廣闊的,對維護者沒有新的要求,他們單方面處理其他人在 -他們活躍的社區的行爲。這一責任由我們所有人承擔,最終《行爲準則》記錄了最終的 -上訴路徑,以防有關行爲問題的問題懸而未決。 +也就是說,我們的社群是廣闊的,對維護者沒有新的要求,他們單方面處理其他人在 +他們活躍的社群的行為。這一責任由我們所有人承擔,最終《行為準則》記錄了最終的 +上訴路徑,以防有關行為問題的問題懸而未決。 -維護人員應該願意在出現問題時提供幫助,並在需要時與社區中的其他人合作。如果您 +維護人員應該願意在出現問題時提供幫助,並在需要時與社群中的其他人合作。如果您 不確定如何處理出現的情況,請不要害怕聯繫技術諮詢委員會(TAB)或其他維護人員。 -除非您願意,否則不會將其視爲違規報告。如果您不確定是否該聯繫TAB 或任何其他維 +除非您願意,否則不會將其視為違規報告。如果您不確定是否該聯繫TAB 或任何其他維 護人員,請聯繫我們的衝突調解人 Mishi Choudhary 。 -最後,“善待對方”纔是每個人的最終目標。我們知道每個人都是人,有時我們都會失敗, -但我們所有人的首要目標應該是努力友好地解決問題。執行行爲準則將是最後的選擇。 +最後,“善待對方”才是每個人的最終目標。我們知道每個人都是人,有時我們都會失敗, +但我們所有人的首要目標應該是努力友好地解決問題。執行行為準則將是最後的選擇。 -我們的目標是創建一個強大的、技術先進的操作系統,以及所涉及的技術複雜性,這自 +我們的目標是建立一個強大的、技術先進的作業系統,以及所涉及的技術複雜性,這自 然需要專業知識和決策。 所需的專業知識因貢獻領域而異。它主要由上下文和技術複雜性決定,其次由貢獻者和 維護者的期望決定。 -專家的期望和決策都要經過討論,但在最後,爲了取得進展,必須能夠做出決策。這一 -特權掌握在維護人員和項目領導的手中,預計將善意使用。 +專家的期望和決策都要經過討論,但在最後,為了取得進展,必須能夠做出決策。這一 +特權掌握在維護人員和專案領導的手中,預計將善意使用。 -因此,設定專業知識期望、作出決定和拒絕不適當的貢獻不被視爲違反行爲準則。 +因此,設定專業知識期望、作出決定和拒絕不適當的貢獻不被視為違反行為準則。 雖然維護人員一般都歡迎新來者,但他們幫助(新)貢獻者克服障礙的能力有限,因此 -他們必須確定優先事項。這也不應被視爲違反了行爲準則。內核社區意識到這一點,並 +他們必須確定優先事項。這也不應被視為違反了行為準則。核心社群意識到這一點,並 以各種形式提供入門級節目,如 kernelnewbies.org 。 範圍 ---- -Linux內核社區主要在一組公共電子郵件列表上進行交互,這些列表分佈在由多個不同 -公司或個人控制的多個不同服務器上。所有這些列表都在內核源代碼樹中的 -MAINTAINERS 文件中定義。發送到這些郵件列表的任何電子郵件都被視爲包含在行爲 +Linux核心社群主要在一組公共電子郵件列表上進行交互,這些列表分佈在由多個不同 +公司或個人控制的多個不同伺服器上。所有這些列表都在核心原始程式碼樹中的 +MAINTAINERS 文件中定義。發送到這些郵件列表的任何電子郵件都被視為包含在行為 準則中。 -使用 kernel.org bugzilla和其他子系統bugzilla 或bug跟蹤工具的開發人員應該遵循 -行爲準則的指導原則。Linux內核社區沒有“官方”項目電子郵件地址或“官方”社交媒體 -地址。使用kernel.org電子郵件帳戶執行的任何活動必須遵循爲kernel.org發佈的行爲 +使用 kernel.org bugzilla和其他子系統bugzilla 或bug追蹤工具的開發人員應該遵循 +行為準則的指導原則。Linux核心社群沒有“官方”專案電子郵件地址或“官方”社交媒體 +地址。使用kernel.org電子郵件帳戶執行的任何活動必須遵循為kernel.org發布的行為 準則,就像任何使用公司電子郵件帳戶的個人必須遵循該公司的特定規則一樣。 -行爲準則並不禁止在郵件列表消息、內核更改日誌消息或代碼註釋中繼續包含名稱、 -電子郵件地址和相關注釋。 +行為準則並不禁止在郵件列表消息、核心更改日誌消息或程式碼註解中繼續包含名稱、 +電子郵件地址和相關註解。 -其他論壇中的互動包括在適用於上述論壇的任何規則中,通常不包括在行爲準則中。 +其他論壇中的互動包括在適用於上述論壇的任何規則中,通常不包括在行為準則中。 除了在極端情況下可考慮的例外情況。 -提交給內核的貢獻應該使用適當的語言。在行爲準則之前已經存在的內容現在不會被 -視爲違反。然而,不適當的語言可以被視爲一個bug;如果任何相關方提交補丁, -這樣的bug將被更快地修復。當前屬於用戶/內核API的一部分的表達式,或者反映已 -發佈標準或規範中使用的術語的表達式,不被視爲bug。 +提交給核心的貢獻應該使用適當的語言。在行為準則之前已經存在的內容現在不會被 +視為違反。然而,不適當的語言可以被視為一個bug;如果任何相關方提交補丁, +這樣的bug將被更快地修復。當前屬於使用者/核心API的一部分的表達式,或者反映已 +發布標準或規範中使用的術語的表達式,不被視為bug。 執行 ---- -行爲準則中列出的地址屬於行爲準則委員會。https://kernel.org/code-of-conduct.html -列出了在任何給定時間接收這些電子郵件的確切成員。成員不能訪問在加入委員會之前 +行為準則中列出的地址屬於行為準則委員會。https://kernel.org/code-of-conduct.html +列出了在任何給定時間接收這些電子郵件的確切成員。成員不能存取在加入委員會之前 或離開委員會之後所做的報告。 -最初的行爲準則委員會由TAB的志願者以及作爲中立第三方的專業調解人組成。委員會 -的首要任務是建立文件化的流程,並將其公開。 +行為準則委員會由TAB任命的社群志願者成員以及作為中立第三方的專業調解人組成。 +行為準則委員會處理報告所使用的流程各不相同,將取決於個別情況,但本文件可 +作為其所使用之一般流程的說明。 如果報告人不希望將整個委員會納入投訴或關切,可直接聯繫委員會的任何成員,包括 調解人。 -行爲準則委員會根據流程審查案例(見上文),並根據需要和適當與TAB協商,例如請求 -和接收有關內核社區的信息。 +行為準則委員會根據流程審查案例(見上文),並根據需要和適當與TAB協商,例如請求 +和接收有關核心社群的資訊。 -委員會做出的任何決定都將提交到表中,以便在必要時與相關維護人員一起執行。行爲 -準則委員會的決定可以通過三分之二的投票推翻。 +任何有關執法建議的決定都將提交給TAB,以便在必要時與相關維護人員一起實施 +執法。一旦TAB以參與表決成員的三分之二多數批准了禁令範圍中列出的一項或多項 +措施,行為準則委員會將執行TAB批准的措施。任何同時在TAB任職的行為準則委員會 +成員不參與對這些措施的表決。 -每季度,行爲準則委員會和標籤將提供一份報告,概述行爲準則委員會收到的匿名報告 -及其狀態,以及任何否決決定的細節,包括完整和可識別的投票細節。 +每季度,行為準則委員會和TAB將提供一份報告,概述行為準則委員會收到的匿名 +報告及其狀態,以及任何TAB批准之決定的細節,包括完整和可識別的投票細節。 -我們希望在啓動期之後爲行爲準則委員會人員配備建立一個不同的流程。發生此情況時, -將使用該信息更新此文檔。 +由於我們對行為準則的解釋和執行會隨著時間演變,本文件將在必要時更新,以反映 +任何變化。 + +不可接受行為之行為準則違規的執法 +-------------------------------- + +行為準則委員會致力於確保我們的社群持續具有包容性,促進多元的討論和觀點,並 +致力於隨著時間改進這些特質。行為準則委員會收到的大多數報告,源於對開發過程 +以及維護者的角色、責任及其對程式碼接受與否的決定權的錯誤理解。這些報告透過 +澄清開發過程和行為準則的範圍來解決。 + +不可接受的行為可能在短時間內中斷相互尊重的協作,並對社群的健康造成長期的 +負面影響。當個人在違規發生的場合承認自己的行為並加以糾正時,不可接受的行為 +通常就能得到解決。 + +當不可接受的行為未能透過社群討論解決時,行為準則委員會會收到相關報告。當 +不可接受的行為對相互尊重的協作關係造成負面影響時,行為準則委員會會採取措施 +恢復有成效且相互尊重的協作。 + +行為準則委員會有義務對報告和報告者的資訊保密。報告可能來自受害方,也可能 +來自目睹不可接受行為的社群成員。行為準則委員會有責任與所有相關方合作,對這 +類報告進行調查並予以處理。 + +行為準則委員會與當事人合作,促使其理解以下事項的重要性:修復其行為對受害方 +造成的傷害,以及對社群的長期負面影響。 + +目標是達成所有各方都能接受的解決方案。如果與當事人的合作未能達到預期的結果, +行為準則委員會將評估其他措施,例如要求公開道歉以修復傷害。 + +要求為違規行為公開道歉 +~~~~~~~~~~~~~~~~~~~~~~ + +行為準則委員會在違規發生的場合公開指出該行為,要求為違規行為公開道歉。 + +為違規行為公開道歉是重建信任的第一步。信任對於一個以信任和尊重運作的社群的 +持續成功和健康至關重要。 + +若未為違規行為公開道歉的補救措施 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +行為準則委員會透過向TAB建議補救措施以供批准,決定恢復健康協作的下一步行動。 + +- 禁止違規者參與核心開發過程,期限最長可達一個完整的核心開發週期。行為準則 + 委員會可以要求公開道歉作為解除禁令的條件。 + +一段時間內禁令的範圍可能包括: + + a. 拒絕其補丁貢獻和拉取請求 + b. 透過忽略其貢獻和/或封鎖其電子郵件帳戶,暫停與違規者的協作 + c. 限制其透過kernel.org平臺(如郵件列表和社交媒體網站)進行交流的能力 + +一旦TAB以參與表決成員的三分之二多數批准了禁令範圍中列出的一項或多項措施, +行為準則委員會將與社群、維護人員、子維護人員和kernel.org管理員合作,執行 +TAB批准的措施。任何同時在TAB任職的行為準則委員會成員不參與對這些措施的表決。 + +行為準則委員會深知要求公開道歉和實施禁令可能對個人造成的負面影響。它也深知 +在發生此類嚴重的公開違規行為時不採取行動可能對社群造成的長期傷害。 + +TAB批准的補救措施的有效性,取決於社群、維護人員、子維護人員和kernel.org +管理員在執行這些措施時的信任與合作。 + +行為準則委員會衷心希望,需要要求公開道歉的不可接受行為,在未來仍然極為罕見。 From 9ae00bba72cd893cd9fe668eced6d47913f6e8fe Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:37 +0800 Subject: [PATCH 0574/2286] docs/zh_TW: process: localize terminology in license-rules.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 文件→檔案, 標識符→識別碼, 加載→載入, ...) and sync with the English original: mention SPDX-FileCopyrightText lines and the clarified "Proprietary" MODULE_LICENSE wording from commit d8c949c577b5 ("docs/licensing: Clarify wording about "GPL" and "Proprietary""). update to commit ebf1bafd0907 ("LICENSES: Explicitly allow SPDX-FileCopyrightText") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/license-rules.rst | 203 +++++++++--------- 1 file changed, 105 insertions(+), 98 deletions(-) diff --git a/Documentation/translations/zh_TW/process/license-rules.rst b/Documentation/translations/zh_TW/process/license-rules.rst index 594255856b682b..d9054cff505c33 100644 --- a/Documentation/translations/zh_TW/process/license-rules.rst +++ b/Documentation/translations/zh_TW/process/license-rules.rst @@ -5,21 +5,22 @@ :Original: :ref:`Documentation/process/license-rules.rst ` :Translator: Alex Shi Hu Haowen <2023002089@link.tyut.edu.cn> + Chen-Yu Yeh .. _tw_kernel_licensing: -Linux內核許可規則 +Linux核心許可規則 ================= -Linux內核根據LICENSES/preferred/GPL-2.0中提供的GNU通用公共許可證版本2 +Linux核心根據LICENSES/preferred/GPL-2.0中提供的GNU通用公共許可證版本2 (GPL-2.0)的條款提供,並在LICENSES/exceptions/Linux-syscall-note中顯式 -描述了例外的系統調用,如COPYING文件中所述。 +描述了例外的系統呼叫,如COPYING檔案中所述。 -此文檔文件提供瞭如何對每個源文件進行註釋以使其許可證清晰明確的說明。 -它不會取代內核的許可證。 +本文件提供了如何對每個原始檔進行註解以使其許可證清晰明確的說明。 +它不會取代核心的許可證。 -內核源代碼作爲一個整體適用於COPYING文件中描述的許可證,但是單個源文件可以 -具有不同的與GPL-20兼容的許可證:: +核心原始程式碼作為一個整體適用於COPYING檔案中描述的許可證,但是單個原始檔可以 +具有不同的與GPL-2.0相容的許可證:: GPL-1.0+ : GNU通用公共許可證v1.0或更高版本 GPL-2.0+ : GNU通用公共許可證v2.0或更高版本 @@ -28,43 +29,47 @@ Linux內核根據LICENSES/preferred/GPL-2.0中提供的GNU通用公共許可證 LGPL-2.1 : 僅限GNU寬通用公共許可證v2.1 LGPL-2.1+: GNU寬通用公共許可證v2.1或更高版本 -除此之外,個人文件可以在雙重許可下提供,例如一個兼容的GPL變體,或者BSD, +除此之外,個人檔案可以在雙重許可下提供,例如一個相容的GPL變體,或者BSD, MIT等許可。 -用戶空間API(UAPI)頭文件描述了用戶空間程序與內核的接口,這是一種特殊情況。 -根據內核COPYING文件中的註釋,syscall接口是一個明確的邊界,它不會將GPL要求 -擴展到任何使用它與內核通信的軟件。由於UAPI頭文件必須包含在創建在Linux內核 -上運行的可執行文件的任何源文件中,因此此例外必須記錄在特別的許可證表述中。 +使用者空間API(UAPI)標頭檔描述了使用者空間程式與核心的介面,這是一種特殊 +情況。根據核心COPYING檔案中的註解,syscall介面是一個明確的邊界,它不會將 +GPL要求擴展到任何使用它與核心通信的軟體。由於UAPI標頭檔必須包含在建立在 +Linux核心上執行的可執行檔案的任何原始檔中,因此此例外必須記錄在特別的許可 +證表述中。 -表達源文件許可證的常用方法是將匹配的樣板文本添加到文件的頂部註釋中。由於 -格式,拼寫錯誤等,這些“樣板”很難通過那些在上下文中使用的驗證許可證合規性 +表達原始檔許可證的常用方法是將匹配的樣板文本添加到檔案的頂部註解中。由於 +格式,拼寫錯誤等,這些“樣板”很難透過那些在上下文中使用的驗證許可證合規性 的工具。 -樣板文本的替代方法是在每個源文件中使用軟件包數據交換(SPDX)許可證標識符。 -SPDX許可證標識符是機器可解析的,並且是用於提供文件內容的許可證的精確縮寫。 -SPDX許可證標識符由Linux 基金會的SPDX 工作組管理,並得到了整個行業,工具 -供應商和法律團隊的合作伙伴的一致同意。有關詳細信息,請參閱 +樣板文本的替代方法是在每個原始檔中使用軟體套件資料交換(SPDX)許可證識別碼。 +SPDX許可證識別碼是機器可解析的,並且是用於提供檔案內容的許可證的精確縮寫。 +SPDX許可證識別碼由Linux 基金會的SPDX 工作組管理,並得到了整個行業,工具 +供應商和法律團隊的合作夥伴的一致同意。有關詳細資訊,請參閱 https://spdx.org/ -Linux內核需要所有源文件中的精確SPDX標識符。內核中使用的有效標識符在 -`許可標識符`_ 一節中進行了解釋,並且已可以在 +Linux核心需要所有原始檔中的精確SPDX識別碼。核心中使用的有效識別碼在 +`許可識別碼`_ 一節中進行了解釋,並且已可以在 https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶許可證 文本。 -許可標識符語法 +許可識別碼語法 -------------- 1.安置: -   內核文件中的SPDX許可證標識符應添加到可包含註釋的文件中的第一行。對於大多 - 數文件,這是第一行,除了那些在第一行中需要'#!PATH_TO_INTERPRETER'的腳本。 - 對於這些腳本,SPDX標識符進入第二行。 +   核心檔案中的SPDX許可證識別碼應添加到可包含註解的檔案中的第一行。對於大多 + 數檔案,這是第一行,除了那些在第一行中需要'#!PATH_TO_INTERPRETER'的腳本。 + 對於這些腳本,SPDX許可證識別碼進入第二行。 + + 如有需要,許可證識別碼行之後可以接上一行或多行SPDX-FileCopyrightText + 行。 | 2. 風格: - SPDX許可證標識符以註釋的形式添加。註釋樣式取決於文件類型:: + SPDX許可證識別碼以註解的形式添加。註解樣式取決於檔案類型:: C source: // SPDX-License-Identifier: C header: /* SPDX-License-Identifier: */ @@ -73,44 +78,44 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 .rst: .. SPDX-License-Identifier: .dts{i}: // SPDX-License-Identifier: - 如果特定工具無法處理標準註釋樣式,則應使用工具接受的相應註釋機制。這是在 - C 頭文件中使用“/\*\*/”樣式註釋的原因。過去在使用生成的.lds文件中觀察到 - 構建被破壞,其中'ld'無法解析C++註釋。現在已經解決了這個問題,但仍然有較 - 舊的彙編程序工具無法處理C++樣式的註釋。 + 如果特定工具無法處理標準註解樣式,則應使用工具接受的相應註解機制。這是在 + C 標頭檔中使用“/\*\*/”樣式註解的原因。過去在使用產生的.lds檔案中觀察到 + 建置被破壞,其中'ld'無法解析C++註解。現在已經解決了這個問題,但仍然有較 + 舊的組譯器工具無法處理C++樣式的註解。 | 3. 句法: - 是SPDX許可證列表中的SPDX短格式許可證標識符,或者在許可 - 證例外適用時由“WITH”分隔的兩個SPDX短格式許可證標識符的組合。當應用多個許 + 是SPDX許可證列表中的SPDX短格式許可證識別碼,或者在許可 + 證例外適用時由“WITH”分隔的兩個SPDX短格式許可證識別碼的組合。當應用多個許 可證時,表達式由分隔子表達式的關鍵字“AND”,“OR”組成,並由“(”,“)”包圍。 - 帶有“或更高”選項的[L]GPL等許可證的許可證標識符通過使用“+”來表示“或更高” - 選項來構建。:: + 帶有“或更高”選項的[L]GPL等許可證的許可證識別碼透過使用“+”來表示“或更高” + 選項來建置。:: // SPDX-License-Identifier: GPL-2.0+ // SPDX-License-Identifier: LGPL-2.1+ - 當需要修正的許可證時,應使用WITH。 例如,linux內核UAPI文件使用表達式:: + 當需要修正的許可證時,應使用WITH。 例如,linux核心UAPI檔案使用表達式:: // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note // SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note - 其它在內核中使用WITH例外的事例如下:: + 其它在核心中使用WITH例外的事例如下:: // SPDX-License-Identifier: GPL-2.0 WITH mif-exception // SPDX-License-Identifier: GPL-2.0+ WITH GCC-exception-2.0 - 例外只能與特定的許可證標識符一起使用。有效的許可證標識符列在異常文本文件 - 的標記中。有關詳細信息,請參閱 `許可標識符`_ 一章中的 `例外`_ 。 + 例外只能與特定的許可證識別碼一起使用。有效的許可證識別碼列在異常文本檔案 + 的標記中。有關詳細資訊,請參閱 `許可識別碼`_ 一章中的 `例外`_ 。 - 如果文件是雙重許可且只選擇一個許可證,則應使用OR。例如,一些dtsi文件在雙 + 如果檔案是雙重許可且只選擇一個許可證,則應使用OR。例如,一些dtsi檔案在雙 許可下可用:: // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - 內核中雙許可文件中許可表達式的示例:: + 核心中雙許可檔案中許可表達式的範例:: // SPDX-License-Identifier: GPL-2.0 OR MIT // SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause @@ -119,8 +124,8 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT // SPDX-License-Identifier: GPL-1.0+ OR BSD-3-Clause OR OpenSSL - 如果文件具有多個許可證,其條款全部適用於使用該文件,則應使用AND。例如, - 如果代碼是從另一個項目繼承的,並且已經授予了將其放入內核的權限,但原始 + 如果檔案具有多個許可證,其條款全部適用於使用該檔案,則應使用AND。例如, + 如果程式碼是從另一個專案繼承的,並且已經授予了將其放入核心的權限,但原始 許可條款需要保持有效:: // SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) AND MIT @@ -129,20 +134,20 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 // SPDX-License-Identifier: GPL-1.0+ AND LGPL-2.1+ -許可標識符 +許可識別碼 ---------- -當前使用的許可證以及添加到內核的代碼許可證可以分解爲: +當前使用的許可證以及添加到核心的程式碼許可證可以分解為: 1. _`優先許可`: - 應儘可能使用這些許可證,因爲它們已知完全兼容並廣泛使用。這些許可證在內核 + 應儘可能使用這些許可證,因為它們已知完全相容並廣泛使用。這些許可證在核心 目錄:: LICENSES/preferred/ - 此目錄中的文件包含完整的許可證文本和 `元標記`_ 。文件名與SPDX許可證標識 - 符相同,後者應用於源文件中的許可證。 + 此目錄中的檔案包含完整的許可證文本和 `元標記`_ 。檔名與SPDX許可證識別碼 + 相同,後者應用於原始檔中的許可證。 例如:: @@ -156,28 +161,28 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 _`元標記`: - 許可證文件中必須包含以下元標記: + 許可證檔案中必須包含以下元標記: - Valid-License-Identifier: -   一行或多行, 聲明那些許可標識符在項目內有效, 以引用此特定許可的文本。通 - 常這是一個有效的標識符,但是例如對於帶有'或更高'選項的許可證,兩個標識 +   一行或多行, 聲明那些許可識別碼在專案內有效, 以引用此特定許可的文本。通 + 常這是一個有效的識別碼,但是例如對於帶有'或更高'選項的許可證,兩個識別碼 符都有效。 - SPDX-URL: - SPDX頁面的URL,其中包含與許可證相關的其他信息. + SPDX頁面的URL,其中包含與許可證相關的其他資訊. - Usage-Guidance: - 使用建議的自由格式文本。該文本必須包含SPDX許可證標識符的正確示例,因爲 - 它們應根據 `許可標識符語法`_ 指南放入源文件中。 + 使用建議的自由格式文本。該文本必須包含SPDX許可證識別碼的正確範例,因為 + 它們應根據 `許可識別碼語法`_ 指南放入原始檔中。 - License-Text: - 此標記之後的所有文本都被視爲原始許可文本 + 此標記之後的所有文本都被視為原始許可文本 - 文件格式示例:: + 檔案格式範例:: Valid-License-Identifier: GPL-2.0 Valid-License-Identifier: GPL-2.0+ @@ -209,12 +214,12 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 2. 不推薦的許可證: - 這些許可證只應用於現有代碼或從其他項目導入代碼。這些許可證在內核目錄:: + 這些許可證只應用於現有程式碼或從其他專案導入程式碼。這些許可證在核心目錄:: LICENSES/other/ - 此目錄中的文件包含完整的許可證文本和 `元標記`_ 。文件名與SPDX許可證標識 - 符相同,後者應用於源文件中的許可證。 + 此目錄中的檔案包含完整的許可證文本和 `元標記`_ 。檔名與SPDX許可證識別碼 + 相同,後者應用於原始檔中的許可證。 例如:: @@ -230,7 +235,7 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 “其他”許可證的元標籤要求與 `優先許可`_ 的要求相同。 - 文件格式示例:: + 檔案格式範例:: Valid-License-Identifier: ISC SPDX-URL: https://spdx.org/licenses/ISC.html @@ -250,50 +255,50 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 3. _`例外`: 某些許可證可以修改,並允許原始許可證不具有的某些例外權利。這些例外在 - 內核目錄:: + 核心目錄:: LICENSES/exceptions/ - 此目錄中的文件包含完整的例外文本和所需的 `例外元標記`_ 。 + 此目錄中的檔案包含完整的例外文本和所需的 `例外元標記`_ 。 例如:: LICENSES/exceptions/Linux-syscall-note - 包含Linux內核的COPYING文件中記錄的Linux系統調用例外,該文件用於UAPI - 頭文件。例如:: + 包含Linux核心的COPYING檔案中記錄的Linux系統呼叫例外,該檔案用於UAPI + 標頭檔。例如:: LICENSES/exceptions/GCC-exception-2.0 - 包含GCC'鏈接例外',它允許獨立於其許可證的任何二進制文件與標記有此例外的 - 文件的編譯版本鏈接。這是從GPL不兼容源代碼創建可運行的可執行文件所必需的。 + 包含GCC'連結例外',它允許獨立於其許可證的任何二進位檔案與標記有此例外的 + 檔案的編譯版本連結。這是從GPL不相容原始程式碼建立可執行的可執行檔案所必需的。 _`例外元標記`: - 以下元標記必須在例外文件中可用: + 以下元標記必須在例外檔案中可用: - SPDX-Exception-Identifier: -   一個可與SPDX許可證標識符一起使用的例外標識符。 +   一個可與SPDX許可證識別碼一起使用的例外識別碼。 - SPDX-URL: - SPDX頁面的URL,其中包含與例外相關的其他信息。 + SPDX頁面的URL,其中包含與例外相關的其他資訊。 - SPDX-Licenses: -   以逗號分隔的例外可用的SPDX許可證標識符列表。 +   以逗號分隔的例外可用的SPDX許可證識別碼列表。 - Usage-Guidance: - 使用建議的自由格式文本。必須在文本後面加上SPDX許可證標識符的正確示例, - 因爲它們應根據 `許可標識符語法`_ 指南放入源文件中。 + 使用建議的自由格式文本。必須在文本後面加上SPDX許可證識別碼的正確範例, + 因為它們應根據 `許可識別碼語法`_ 指南放入原始檔中。 - Exception-Text: - 此標記之後的所有文本都被視爲原始異常文本 + 此標記之後的所有文本都被視為原始異常文本 - 文件格式示例:: + 檔案格式範例:: SPDX-Exception-Identifier: Linux-syscall-note SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html @@ -324,49 +329,51 @@ https://spdx.org/licenses/ 上的官方SPDX許可證列表中檢索,並附帶 Full exception text -所有SPDX許可證標識符和例外都必須在LICENSES子目錄中具有相應的文件。這是允許 +所有SPDX許可證識別碼和例外都必須在LICENSES子目錄中具有相應的檔案。這是允許 工具驗證(例如checkpatch.pl)以及準備好從源讀取和提取許可證所必需的, 這是 各種FOSS組織推薦的,例如 `FSFE REUSE initiative `_. -_`模塊許可` +_`模組許可` ----------------- - 可加載內核模塊還需要MODULE_LICENSE()標記。此標記既不替代正確的源代碼 - 許可證信息(SPDX-License-Identifier),也不以任何方式表示或確定提供模塊 - 源代碼的確切許可證。 + 可載入核心模組還需要MODULE_LICENSE()標記。此標記既不替代正確的原始程式碼 + 許可證資訊(SPDX-License-Identifier),也不以任何方式表示或確定提供模組 + 原始程式碼的確切許可證。 - 此標記的唯一目的是提供足夠的信息,該模塊是否是自由軟件或者是內核模塊加 - 載器和用戶空間工具的專有模塊。 + 此標記的唯一目的是提供足夠的資訊,該模組是否是自由軟體或者是核心模組加 + 載器和使用者空間工具的專有模組。 - MODULE_LICENSE()的有效許可證字符串是: + MODULE_LICENSE()的有效許可證字串是: ============================= ============================================= - "GPL" 模塊是根據GPL版本2許可的。這並不表示僅限於 + "GPL" 模組是根據GPL版本2許可的。這並不表示僅限於 GPL-2.0或GPL-2.0或更高版本之間的任何區別。 - 最正確許可證信息只能通過相應源文件中的許可證 - 信息來確定 + 最正確許可證資訊只能透過相應原始檔中的許可證 + 資訊來確定 - "GPL v2" 和"GPL"相同,它的存在是因爲歷史原因。 + "GPL v2" 和"GPL"相同,它的存在是因為歷史原因。 - "GPL and additional rights" 表示模塊源在GPL v2變體和MIT許可下雙重許可的 - 歷史變體。請不要在新代碼中使用。 + "GPL and additional rights" 表示模組源在GPL v2變體和MIT許可下雙重許可的 + 歷史變體。請不要在新程式碼中使用。 - "Dual MIT/GPL" 表達該模塊在GPL v2變體或MIT許可證選擇下雙重 + "Dual MIT/GPL" 表達該模組在GPL v2變體或MIT許可證選擇下雙重 許可的正確方式。 - "Dual BSD/GPL" 該模塊根據GPL v2變體或BSD許可證選擇進行雙重 - 許可。 BSD許可證的確切變體只能通過相應源文件 - 中的許可證信息來確定。 + "Dual BSD/GPL" 該模組根據GPL v2變體或BSD許可證選擇進行雙重 + 許可。 BSD許可證的確切變體只能透過相應原始檔 + 中的許可證資訊來確定。 - "Dual MPL/GPL" 該模塊根據GPL v2變體或Mozilla Public License + "Dual MPL/GPL" 該模組根據GPL v2變體或Mozilla Public License (MPL)選項進行雙重許可。 MPL許可證的確切變體 - 只能通過相應的源文件中的許可證信息來確定。 - - "Proprietary" 該模塊屬於專有許可。此字符串僅用於專有的第三 - 方模塊,不能用於在內核樹中具有源代碼的模塊。 - 以這種方式標記的模塊在加載時會使用'P'標記污 - 染內核,並且內核模塊加載器拒絕將這些模塊鏈接 - 到使用EXPORT_SYMBOL_GPL()導出的符號。 + 只能透過相應的原始檔中的許可證資訊來確定。 + + "Proprietary" 該模組屬於非GPL2相容的許可。“Proprietary + (專有)”應僅理解為“該許可證與GPLv2不相容”。 + 此字串僅用於非GPL2相容的第三方模組,不能用 + 於在核心樹中具有原始程式碼的模組。以這種方式 + 標記的模組在載入時會使用'P'標記污染核心,並 + 且核心模組載入器拒絕將這些模組連結到使用 + EXPORT_SYMBOL_GPL()匯出的符號。 ============================= ============================================= From 2c3739c254ee892ea29f125956775870ebe87887 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:38 +0800 Subject: [PATCH 0575/2286] docs/zh_TW: process: localize terminology in email-clients.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (文本→文字, 菜單→選單, 設置→設定, 鼠標→滑鼠, ...) and sync with the English original: mention the Thunderbird "Toggle Line Wrap" extension as an alternative to editing mailnews.wraplength. update to commit 4971ca2007e3 ("docs: process: email-client: add Thunderbird "Toggle Line Wrap" extension") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/email-clients.rst | 185 +++++++++--------- 1 file changed, 97 insertions(+), 88 deletions(-) diff --git a/Documentation/translations/zh_TW/process/email-clients.rst b/Documentation/translations/zh_TW/process/email-clients.rst index 4543c447d797aa..4fae2fdbb3bf84 100644 --- a/Documentation/translations/zh_TW/process/email-clients.rst +++ b/Documentation/translations/zh_TW/process/email-clients.rst @@ -16,87 +16,89 @@ - Xiaochen Wang - yaxinsn - Hu Haowen <2023002089@link.tyut.edu.cn> + - Chen-Yu Yeh -Linux郵件客戶端配置信息 +Linux郵件客戶端設定資訊 ======================= Git --- 現在大多數開發人員使用 ``git send-email`` 而不是常規的電子郵件客戶端。這方面 -的手冊非常好。在接收端,維護人員使用 ``git am`` 加載補丁。 +的手冊非常好。在接收端,維護人員使用 ``git am`` 載入補丁。 -如果你是 ``git`` 新手,那麼把你的第一個補丁發送給你自己。將其保存爲包含所有 -標題的原始文本。運行 ``git am raw_email.txt`` ,然後使用 ``git log`` 查看更 +如果你是 ``git`` 新手,那麼把你的第一個補丁發送給你自己。將其保存為包含所有 +標題的原始文字。執行 ``git am raw_email.txt`` ,然後使用 ``git log`` 查看更 改日誌。如果工作正常,再將補丁發送到相應的郵件列表。 -通用配置 +通用設定 -------- -Linux內核補丁是通過郵件被提交的,最好把補丁作爲郵件體的內嵌文本。有些維護者 +Linux核心補丁是透過郵件被提交的,最好把補丁作為郵件體的內嵌文字。有些維護者 接收附件,但是附件的內容格式應該是"text/plain"。然而,附件一般是不贊成的, -因爲這會使補丁的引用部分在評論過程中變的很困難。 +因為這會使補丁的引用部分在評論過程中變得很困難。 -同時也強烈建議在補丁或其他郵件的正文中使用純文本格式。https://useplaintext.email -有助於瞭解如何配置你喜歡的郵件客戶端,並在您還沒有首選的情況下列出一些推薦的 +同時也強烈建議在補丁或其他郵件的正文中使用純文字格式。https://useplaintext.email +有助於瞭解如何設定你喜歡的郵件客戶端,並在您還沒有首選的情況下列出一些推薦的 客戶端。 -用來發送Linux內核補丁的郵件客戶端在發送補丁時應該處於文本的原始狀態。例如, +用來發送Linux核心補丁的郵件客戶端在發送補丁時應該處於文字的原始狀態。例如, 他們不能改變或者刪除製表符或者空格,甚至是在每一行的開頭或者結尾。 -不要通過"format=flowed"模式發送補丁。這樣會引起不可預期以及有害的斷行。 +不要透過"format=flowed"模式發送補丁。這樣會引起不可預期以及有害的斷行。 不要讓你的郵件客戶端進行自動換行。這樣也會破壞你的補丁。 -郵件客戶端不能改變文本的字符集編碼方式。要發送的補丁只能是ASCII或者UTF-8編碼 -方式,如果你使用UTF-8編碼方式發送郵件,那麼你將會避免一些可能發生的字符集問題。 +郵件客戶端不能改變文字的字元集編碼方式。要發送的補丁只能是ASCII或者UTF-8編 +碼方式,如果你使用UTF-8編碼方式發送郵件,那麼你將會避免一些可能發生的字元 +集問題。 -郵件客戶端應該生成並且保持“References:”或者“In-Reply-To:”郵件頭,這樣郵件會話 +郵件客戶端應該產生並且保持“References:”或者“In-Reply-To:”郵件頭,這樣郵件會話 就不會中斷。 -複製粘帖(或者剪貼粘帖)通常不能用於補丁,因爲製表符會轉換爲空格。使用xclipboard, -xclip或者xcutsel也許可以,但是最好測試一下或者避免使用複製粘帖。 +複製貼上(或者剪貼貼上)通常不能用於補丁,因為製表符會轉換為空格。使用 +xclipboard,xclip或者xcutsel也許可以,但是最好測試一下或者避免使用複製貼上。 不要在使用PGP/GPG簽名的郵件中包含補丁。這樣會使得很多腳本不能讀取和適用於你的 補丁。(這個問題應該是可以修復的) -在給內核郵件列表發送補丁之前,給自己發送一個補丁是個不錯的主意,保存接收到的 -郵件,將補丁用'patch'命令打上,如果成功了,再給內核郵件列表發送。 +在給核心郵件列表發送補丁之前,給自己發送一個補丁是個不錯的主意,保存接收到的 +郵件,將補丁用'patch'命令打上,如果成功了,再給核心郵件列表發送。 一些郵件客戶端提示 ------------------ -這裏給出一些詳細的MUA配置提示,可以用於給Linux內核發送補丁。這些並不意味是 -所有的軟件包配置總結。 +這裡給出一些詳細的MUA設定提示,可以用於給Linux核心發送補丁。這些並不意味是 +所有的軟體套件設定總結。 說明: -- TUI = 以文本爲基礎的用戶接口 -- GUI = 圖形界面用戶接口 +- TUI = 以文字為基礎的使用者介面 +- GUI = 圖形介面使用者介面 Alpine (TUI) ************ -配置選項: +設定選項: -在 :menuselection:`Sending Preferences` 菜單: +在 :menuselection:`Sending Preferences` 選單: -- :menuselection:`Do Not Send Flowed Text` 必須開啓 +- :menuselection:`Do Not Send Flowed Text` 必須開啟 - :menuselection:`Strip Whitespace Before Sending` 必須關閉 -當寫郵件時,光標應該放在補丁會出現的地方,然後按下 `CTRL-R` 組合鍵,使指 -定的補丁文件嵌入到郵件中。 +當寫郵件時,游標應該放在補丁會出現的地方,然後按下 `CTRL-R` 組合鍵,使指 +定的補丁檔案嵌入到郵件中。 Claws Mail (GUI) **************** 可以用,有人用它成功地發過補丁。 -用 :menuselection:`Message-->Insert File` (`CTRL-I`) 或外置編輯器插入補丁。 +用 :menuselection:`Message-->Insert File` (`CTRL-I`) 或外部編輯器插入補丁。 -若要在Claws編輯窗口重修改插入的補丁,需關閉 +若要在Claws編輯視窗重修改插入的補丁,需關閉 :menuselection:`Configuration-->Preferences-->Compose-->Wrapping` 的 `Auto wrapping` 。 @@ -107,49 +109,49 @@ Evolution (GUI) 撰寫郵件時: 從 :menuselection:`格式-->段落樣式-->預格式化` (`CTRL-7`) -或工具欄選擇 :menuselection:`預格式化` ; +或工具列選擇 :menuselection:`預格式化` ; 然後使用: -:menuselection:`插入-->文本文件...` (`ALT-N x`) 插入補丁文件。 +:menuselection:`插入-->文字檔...` (`ALT-N x`) 插入補丁檔案。 你還可以 ``diff -Nru old.c new.c | xclip`` ,選擇 :menuselection:`預格式化` , -然後使用鼠標中鍵進行粘帖。 +然後使用滑鼠中鍵進行貼上。 Kmail (GUI) *********** 一些開發者成功的使用它發送補丁。 -默認撰寫設置禁用HTML格式是合適的;不要啓用它。 +預設撰寫設定禁用HTML格式是合適的;不要啟用它。 當書寫一封郵件的時候,在選項下面不要選擇自動換行。唯一的缺點就是你在郵件中輸 -入的任何文本都不會被自動換行,因此你必須在發送補丁之前手動換行。最簡單的方法 -就是啓用自動換行來書寫郵件,然後把它保存爲草稿。一旦你在草稿中再次打開它,它 +入的任何文字都不會被自動換行,因此你必須在發送補丁之前手動換行。最簡單的方法 +就是啟用自動換行來書寫郵件,然後把它保存為草稿。一旦你在草稿中再次開啟它,它 已經全部自動換行了,那麼你的郵件雖然沒有選擇自動換行,但是還不會失去已有的自 動換行。 -在郵件的底部,插入補丁之前,放上常用的補丁定界符:三個連字符(``---``)。 +在郵件的底部,插入補丁之前,放上常用的補丁定界符:三個連字元(``---``)。 -然後在 :menuselection:`信件` 菜單,選擇 :menuselection:`插入文本文件` ,接 -着選取你的補丁文件。還有一個額外的選項,你可以通過它配置你的創建新郵件工具欄, -加上 :menuselection:`插入文本文件` 圖標。 +然後在 :menuselection:`信件` 選單,選擇 :menuselection:`插入文字檔` ,接 +著選取你的補丁檔案。還有一個額外的選項,你可以透過它設定你的建立新郵件工具列, +加上 :menuselection:`插入文字檔` 圖示。 -將編輯器窗口拉到足夠寬避免折行。對於KMail 1.13.5 (KDE 4.5.4),它會在發送郵件 -時對編輯器窗口中顯示折行的地方自動換行。在選項菜單中取消自動換行仍不能解決。 -因此,如果你的補丁中有非常長的行,必須在發送之前把編輯器窗口拉得非常寬。 +將編輯器視窗拉到足夠寬避免折行。對於KMail 1.13.5 (KDE 4.5.4),它會在發送郵件 +時對編輯器視窗中顯示折行的地方自動換行。在選項選單中取消自動換行仍不能解決。 +因此,如果你的補丁中有非常長的行,必須在發送之前把編輯器視窗拉得非常寬。 參見:https://bugs.kde.org/show_bug.cgi?id=174034 -你可以安全地用GPG簽名附件,但是內嵌補丁最好不要使用GPG簽名它們。作爲內嵌文本 +你可以安全地用GPG簽名附件,但是內嵌補丁最好不要使用GPG簽名它們。作為內嵌文字 插入的簽名補丁將使其難以從7-bit編碼中提取。 如果你非要以附件的形式發送補丁,那麼就右鍵點擊附件,然後選擇 -:menuselection:`屬性` ,打開 :menuselection:`建議自動顯示` ,使附件內聯更容 +:menuselection:`屬性` ,開啟 :menuselection:`建議自動顯示` ,使附件內聯更容 易讓讀者看到。 -當你要保存將要發送的內嵌文本補丁,你可以從消息列表窗格選擇包含補丁的郵件,然 -後右鍵選擇 :menuselection:`另存爲` 。如果整個電子郵件的組成正確,您可直接將 -其作爲補丁使用。電子郵件以當前用戶可讀寫權限保存,因此您必須 ``chmod`` ,以 -使其在複製到別處時用戶組和其他人可讀。 +當你要保存將要發送的內嵌文字補丁,你可以從訊息列表窗格選擇包含補丁的郵件,然 +後右鍵選擇 :menuselection:`另存新檔` 。如果整個電子郵件的組成正確,您可直接將 +其作為補丁使用。電子郵件以當前使用者可讀寫權限保存,因此您必須 ``chmod`` ,以 +使其在複製到別處時使用者組和其他人可讀。 Lotus Notes (GUI) ***************** @@ -167,9 +169,9 @@ Mutt (TUI) 很多Linux開發人員使用mutt客戶端,這證明它肯定工作得非常漂亮。 Mutt不自帶編輯器,所以不管你使用什麼編輯器,不自動斷行就行。大多數編輯器都有 -:menuselection:`插入文件` 選項,它可以在不改變文件內容的情況下插入文件。 +:menuselection:`插入檔案` 選項,它可以在不改變檔案內容的情況下插入檔案。 -用 ``vim`` 作爲mutt的編輯器:: +用 ``vim`` 作為mutt的編輯器:: set editor="vi" @@ -181,21 +183,21 @@ Mutt不自帶編輯器,所以不管你使用什麼編輯器,不自動斷行 :r filename -把補丁插入爲內嵌文本。 -在未設置 ``set paste`` 時(a)ttach工作的很好。 +把補丁插入為內嵌文字。 +在未設定 ``set paste`` 時(a)ttach工作的很好。 -你可以通過 ``git format-patch`` 生成補丁,然後用 Mutt發送它們:: +你可以透過 ``git format-patch`` 產生補丁,然後用 Mutt發送它們:: $ mutt -H 0001-some-bug-fix.patch -配置選項: +設定選項: -它應該以默認設置的形式工作。 -然而,把 ``send_charset`` 設置一下也是一個不錯的主意:: +它應該以預設設定的形式工作。 +然而,把 ``send_charset`` 設定一下也是一個不錯的主意:: set send_charset="us-ascii:utf-8" -Mutt 是高度可配置的。 這裏是個使用mutt通過 Gmail 發送的補丁的最小配置:: +Mutt 是高度可設定的。 這裡是個使用mutt透過 Gmail 發送的補丁的最小設定:: # .muttrc # ================ IMAP ==================== @@ -222,7 +224,7 @@ Mutt 是高度可配置的。 這裏是個使用mutt通過 Gmail 發送的補丁 set from = "username@gmail.com" set use_from = yes -Mutt文檔含有更多信息: +Mutt文件含有更多資訊: https://gitlab.com/muttmua/mutt/-/wikis/UseCases/Gmail @@ -235,7 +237,7 @@ Pine過去有一些空格刪減問題,但是這些現在應該都被修復了 如果可以,請使用alpine(pine的繼承者)。 -配置選項: +設定選項: - 最近的版本需要 ``quell-flowed-text`` - ``no-strip-whitespace-before-send`` 選項也是需要的。 @@ -244,23 +246,23 @@ Pine過去有一些空格刪減問題,但是這些現在應該都被修復了 Sylpheed (GUI) ************** -- 內嵌文本可以很好的工作(或者使用附件)。 +- 內嵌文字可以很好的工作(或者使用附件)。 - 允許使用外部的編輯器。 - 收件箱較多時非常慢。 -- 如果通過non-SSL連接,無法使用TLS SMTP授權。 -- 撰寫窗口的標尺很有用。 +- 如果透過non-SSL連接,無法使用TLS SMTP授權。 +- 撰寫視窗的標尺很有用。 - 將地址添加到通訊簿時無法正確理解顯示的名稱。 Thunderbird (GUI) ***************** -Thunderbird是Outlook的克隆版本,它很容易損壞文本,但也有一些方法強制修正。 +Thunderbird是Outlook的翻版,它很容易損壞文字,但也有一些方法強制修正。 -在完成修改後(包括安裝擴展),您需要重新啓動Thunderbird。 +在完成修改後(包括安裝擴展),您需要重新啟動Thunderbird。 - 允許使用外部編輯器: - 使用Thunderbird發補丁最簡單的方法是使用擴展來打開您最喜歡的外部編輯器。 + 使用Thunderbird發補丁最簡單的方法是使用擴展來開啟您最喜歡的外部編輯器。 下面是一些能夠做到這一點的擴展樣例。 @@ -270,43 +272,50 @@ Thunderbird是Outlook的克隆版本,它很容易損壞文本,但也有一 https://addons.thunderbird.net/en-GB/thunderbird/addon/external-editor-revived/ - 它需要安裝“本地消息主機(native messaging host)”。 - 參見以下文檔: + 它需要安裝“本地訊息主機(native messaging host)”。 + 參見以下文件: https://github.com/Frederick888/external-editor-revived/wiki - “External Editor” https://github.com/exteditor/exteditor - 下載並安裝此擴展,然後打開 :menuselection:`新建消息` 窗口, 用 - :menuselection:`查看-->工具欄-->自定義...` 給它增加一個按鈕,直接點擊此 - 按鈕即可使用外置編輯器。 + 下載並安裝此擴展,然後開啟 :menuselection:`新增訊息` 視窗, 用 + :menuselection:`查看-->工具列-->自訂...` 給它增加一個按鈕,直接點擊此 + 按鈕即可使用外部編輯器。 請注意,“External Editor”要求你的編輯器不能fork,換句話說,編輯器必須在 - 關閉前不返回。你可能需要傳遞額外的參數或修改編輯器設置。最值得注意的是, - 如果您使用的是gvim,那麼您必須將 :menuselection:`external editor` 設置的 - 編輯器字段設置爲 ``/usr/bin/gvim --nofork"`` (假設可執行文件在 + 關閉前不返回。你可能需要傳遞額外的參數或修改編輯器設定。最值得注意的是, + 如果您使用的是gvim,那麼您必須將 :menuselection:`external editor` 設定的 + 編輯器欄位設定為 ``/usr/bin/gvim --nofork"`` (假設可執行檔在 ``/usr/bin`` ),以傳遞 ``-f`` 參數。如果您正在使用其他編輯器,請閱讀其 手冊瞭解如何處理。 若要修正內部編輯器,請執行以下操作: -- 修改你的Thunderbird設置,不要使用 ``format=flowed`` ! - 回到主窗口,按照 - :menuselection:`主菜單-->首選項-->常規-->配置編輯器...` - 打開Thunderbird的配置編輯器。 +- 修改你的Thunderbird設定,不要使用 ``format=flowed`` ! + 回到主視窗,按照 + :menuselection:`主選單-->偏好設定-->常規-->設定編輯器...` + 開啟Thunderbird的設定編輯器。 - - 將 ``mailnews.send_plaintext_flowed`` 設爲 ``false`` + - 將 ``mailnews.send_plaintext_flowed`` 設為 ``false`` - - 將 ``mailnews.wraplength`` 從 ``72`` 改爲 ``0`` + - 將 ``mailnews.wraplength`` 從 ``72`` 改為 ``0`` ,**或者** 安裝 + “Toggle Line Wrap”擴展 + + https://github.com/jan-kiszka/togglelinewrap + + https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap + + 以便即時控制此設定項。 - 不要寫HTML郵件! - 回到主窗口,打開 - :menuselection:`主菜單-->賬戶設置-->你的@郵件.地址-->通訊錄/編寫&地址簿` , - 關掉 ``以HTML格式編寫消息`` 。 + 回到主視窗,開啟 + :menuselection:`主選單-->帳戶設定-->你的@郵件.地址-->通訊錄/編寫&地址簿` , + 關掉 ``以HTML格式編寫訊息`` 。 -- 只用純文本格式查看郵件! - 回到主窗口, :menuselection:`主菜單-->查看-->消息體爲-->純文本` ! +- 只用純文字格式查看郵件! + 回到主視窗, :menuselection:`主選單-->查看-->訊息體為-->純文字` ! TkRat (GUI) *********** @@ -318,12 +327,12 @@ Gmail (Web GUI) 不要使用它發送補丁。 -Gmail網頁客戶端自動地把製表符轉換爲空格。 +Gmail網頁客戶端自動地把製表符轉換為空格。 -雖然製表符轉換爲空格問題可以被外部編輯器解決,但它同時還會使用回車換行把每行 -拆分爲78個字符。 +雖然製表符轉換為空格問題可以被外部編輯器解決,但它同時還會使用回車換行把每行 +拆分為78個字元。 -另一個問題是Gmail還會把任何含有非ASCII的字符的消息改用base64編碼,如歐洲人的 +另一個問題是Gmail還會把任何含有非ASCII的字元的訊息改用base64編碼,如歐洲人的 名字。 From 948224c58c73f27490d789c1ddfc69f98b3ade7b Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:39 +0800 Subject: [PATCH 0576/2286] docs/zh_TW: process: localize terminology in programming-language.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 變量→變數, 函數→函式, ...) and sync with the English original: drop the stale icc/architecture paragraph, switch to footnote-style references as in the English text, and add the Rust section, which now states Rust is supported (no longer experimental) per commit 9fa7153c31a3 ("rust: conclude the Rust experiment"). update to commit 26a4cfaff82a ("Merge tag 'docs-7.0-2' of git://git.kernel.org/pub/scm/linux/kernel/git/docs/linux") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/programming-language.rst | 98 ++++++++----------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/Documentation/translations/zh_TW/process/programming-language.rst b/Documentation/translations/zh_TW/process/programming-language.rst index d2c64a5599e83b..a5466047b90c1e 100644 --- a/Documentation/translations/zh_TW/process/programming-language.rst +++ b/Documentation/translations/zh_TW/process/programming-language.rst @@ -5,71 +5,59 @@ :Original: :ref:`Documentation/process/programming-language.rst ` :Translator: Alex Shi Hu Haowen <2023002089@link.tyut.edu.cn> + Chen-Yu Yeh .. _tw_programming_language: -程序設計語言 -============ +程式語言 +======== -內核是用C語言 :ref:`c-language ` 編寫的。更準確地說,內核通常是用 :ref:`gcc ` -在 ``-std=gnu11`` :ref:`gcc-c-dialect-options ` 下編譯的:ISO C11的 GNU 方言 +Linux核心是用C程式語言 [zh_tw_c-language]_ 編寫的。更準確地說,核心通常使 +用``gcc`` [zh_tw_gcc]_ 編譯,並且使用 ``-std=gnu11`` +[zh_tw_gcc-c-dialect-options]_:這是 ISO C11 的 GNU 方言。``clang`` +[zh_tw_clang]_ 也得到了支援,詳見文件: +:ref:`使用 Clang/LLVM 建置 Linux `。 -這種方言包含對語言 :ref:`gnu-extensions ` 的許多擴展,當然,它們許多都在內核中使用。 - -對於一些體系結構,有一些使用 :ref:`clang ` 和 :ref:`icc ` 編譯內核 -的支持,儘管在編寫此文檔時還沒有完成,仍需要第三方補丁。 +這種方言包含對C語言的許多擴展 [zh_tw_gnu-extensions]_,當然,它們許多都在核心 +中使用。 屬性 ---- -在整個內核中使用的一個常見擴展是屬性(attributes) :ref:`gcc-attribute-syntax ` -屬性允許將實現定義的語義引入語言實體(如變量、函數或類型),而無需對語言進行 -重大的語法更改(例如添加新關鍵字) :ref:`n2049 ` - -在某些情況下,屬性是可選的(即不支持這些屬性的編譯器仍然應該生成正確的代碼, -即使其速度較慢或執行的編譯時檢查/診斷次數不夠) - -內核定義了僞關鍵字(例如, ``pure`` ),而不是直接使用GNU屬性語法(例如, -``__attribute__((__pure__))`` ),以檢測可以使用哪些關鍵字和/或縮短代碼, 具體 -請參閱 ``include/linux/compiler_attributes.h`` - -.. _tw_c-language: - -c-language - http://www.open-std.org/jtc1/sc22/wg14/www/standards - -.. _tw_gcc: - -gcc - https://gcc.gnu.org - -.. _tw_clang: - -clang - https://clang.llvm.org +在整個核心中使用的一個常見擴展是屬性(attributes) +[zh_tw_gcc-attribute-syntax]_。屬性允許將實作定義的語義引入語言實體(如變 +數、函式或型別),而無需對語言進行重大的語法更改(例如添加新關鍵字) +[zh_tw_n2049]_。 -.. _tw_icc: +在某些情況下,屬性是可選的(即不支援這些屬性的編譯器仍然應該產生正確的程式碼, +即使其速度較慢或執行的編譯時檢查/診斷次數不夠)。 -icc - https://software.intel.com/en-us/c-compilers +核心定義了偽關鍵字(例如, ``__pure`` ),而不是直接使用GNU屬性語法(例如, +``__attribute__((__pure__))`` ),以檢測可以使用哪些關鍵字和/或縮短程式碼, +具體請參閱 ``include/linux/compiler_attributes.h`` -.. _tw_gcc-c-dialect-options: - -c-dialect-options - https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html - -.. _tw_gnu-extensions: - -gnu-extensions - https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html - -.. _tw_gcc-attribute-syntax: - -gcc-attribute-syntax - https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html - -.. _tw_n2049: - -n2049 - http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2049.pdf +Rust +---- +核心支援 Rust 程式語言 [zh_tw_rust-language]_,並可以透過設定選項 +``CONFIG_RUST`` 來啟用。Rust 程式碼使用 ``rustc`` [zh_tw_rustc]_ 編譯器在 +``--edition=2021`` [zh_tw_rust-editions]_ 選項下進行編譯。版本(Editions)是 +一種在語言中引入非後向相容的小型變更的方式。 + +除此之外,核心中還使用了一些不穩定的特性 [zh_tw_rust-unstable-features]_。 +這些不穩定的特性將來可能會發生變化,因此,一個重要的目標是達到僅使用穩定特性 +的程度。 + +具體請參閱 Documentation/rust/index.rst + +.. [zh_tw_c-language] http://www.open-std.org/jtc1/sc22/wg14/www/standards +.. [zh_tw_gcc] https://gcc.gnu.org +.. [zh_tw_clang] https://clang.llvm.org +.. [zh_tw_gcc-c-dialect-options] https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html +.. [zh_tw_gnu-extensions] https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html +.. [zh_tw_gcc-attribute-syntax] https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html +.. [zh_tw_n2049] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2049.pdf +.. [zh_tw_rust-language] https://www.rust-lang.org +.. [zh_tw_rustc] https://doc.rust-lang.org/rustc/ +.. [zh_tw_rust-editions] https://doc.rust-lang.org/edition-guide/editions/ +.. [zh_tw_rust-unstable-features] https://github.com/Rust-for-Linux/linux/issues/2 From eeae7034fd13a97f4c57a6bd102ae33a54de5405 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:40 +0800 Subject: [PATCH 0577/2286] docs/zh_TW: process: localize terminology in coding-style.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 函數→函式, 宏→巨集, 枚舉→列舉, 聲明→宣告, 寄存器→暫存器, ...) and sync with the English original: update the memory allocator list and array allocation examples to the type-aware kmalloc_objs()/kzalloc_objs() forms. update to commit 323fa4b9608b ("Documentation: Fix syntax of kmalloc_objs example in coding style doc") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/coding-style.rst | 614 +++++++++--------- 1 file changed, 311 insertions(+), 303 deletions(-) diff --git a/Documentation/translations/zh_TW/process/coding-style.rst b/Documentation/translations/zh_TW/process/coding-style.rst index 63c78982a1afcd..ef7a4cdbff375e 100644 --- a/Documentation/translations/zh_TW/process/coding-style.rst +++ b/Documentation/translations/zh_TW/process/coding-style.rst @@ -18,39 +18,40 @@ - Li Zefan - Wang Chen - Hu Haowen <2023002089@link.tyut.edu.cn> + - 葉宸佑 Chen-Yu Yeh -Linux 內核代碼風格 -================== +Linux 核心程式碼風格 +==================== -這是一個簡短的文檔,描述了 linux 內核的首選代碼風格。代碼風格是因人而異的, -而且我不願意把自己的觀點強加給任何人,但這就像我去做任何事情都必須遵循的原則 -那樣,我也希望在絕大多數事上保持這種的態度。請 (在寫代碼時) 至少考慮一下這裏 -的代碼風格。 +這是一個簡短的文件,描述了 linux 核心的首選程式碼風格。程式碼風格是因人而 +異的,而且我不願意把自己的觀點強加給任何人,但這就像我去做任何事情都必須遵 +循的原則那樣,我也希望在絕大多數事上保持這種的態度。請 (在寫程式碼時) 至少 +考慮一下這裡的程式碼風格。 -首先,我建議你打印一份 GNU 代碼規範,然後不要讀。燒了它,這是一個具有重大象徵 -性意義的動作。 +首先,我建議你列印一份 GNU 程式碼規範,然後不要讀。燒了它,這是一個具有重 +大象徵性意義的動作。 不管怎樣,現在我們開始: -1) 縮進 +1) 縮排 ------- -製表符是 8 個字符,所以縮進也是 8 個字符。有些異端運動試圖將縮進變爲 4 (甚至 -2!) 字符深,這幾乎相當於嘗試將圓周率的值定義爲 3。 +製表符是 8 個字元,所以縮排也是 8 個字元。有些異端運動試圖將縮排變為 4 (甚至 +2!) 字元深,這幾乎相當於嘗試將圓周率的值定義為 3。 -理由:縮進的全部意義就在於清楚的定義一個控制塊起止於何處。尤其是當你盯着你的 -屏幕連續看了 20 小時之後,你將會發現大一點的縮進會使你更容易分辨縮進。 +理由:縮排的全部意義就在於清楚的定義一個控制塊起止於何處。尤其是當你盯著你的 +屏幕連續看了 20 小時之後,你將會發現大一點的縮排會使你更容易分辨縮排。 -現在,有些人會抱怨 8 個字符的縮進會使代碼向右邊移動的太遠,在 80 個字符的終端 -屏幕上就很難讀這樣的代碼。這個問題的答案是,如果你需要 3 級以上的縮進,不管用 -何種方式你的代碼已經有問題了,應該修正你的程序。 +現在,有些人會抱怨 8 個字元的縮排會使程式碼向右邊移動的太遠,在 80 個字元 +的終端屏幕上就很難讀這樣的程式碼。這個問題的答案是,如果你需要 3 級以上的 +縮排,不管用何種方式你的程式碼已經有問題了,應該修正你的程式。 -簡而言之,8 個字符的縮進可以讓代碼更容易閱讀,還有一個好處是當你的函數嵌套太 +簡而言之,8 個字元的縮排可以讓程式碼更容易閱讀,還有一個好處是當你的函式嵌套太 深的時候可以給你警告。留心這個警告。 -在 switch 語句中消除多級縮進的首選的方式是讓 ``switch`` 和從屬於它的 ``case`` -標籤對齊於同一列,而不要 ``兩次縮進`` ``case`` 標籤。比如: +在 switch 語句中消除多級縮排的首選的方式是讓 ``switch`` 和從屬於它的 ``case`` +標籤對齊於同一列,而不要 ``兩次縮排`` ``case`` 標籤。比如: .. code-block:: c @@ -71,7 +72,7 @@ Linux 內核代碼風格 break; } -不要把多個語句放在一行裏,除非你有什麼東西要隱藏: +不要把多個語句放在一行裡,除非你有什麼東西要隱藏: .. code-block:: c @@ -94,37 +95,38 @@ Linux 內核代碼風格 do_that(); } -也不要在一行裏放多個賦值語句。內核代碼風格超級簡單。就是避免可能導致別人誤讀 +也不要在一行裡放多個賦值語句。核心程式碼風格超級簡單。就是避免可能導致別人誤讀 的表達式。 -除了註釋、文檔和 Kconfig 之外,不要使用空格來縮進,前面的例子是例外,是有意爲 +除了註解、文件和 Kconfig 之外,不要使用空格來縮排,前面的例子是例外,是有意為 之。 選用一個好的編輯器,不要在行尾留空格。 -2) 把長的行和字符串打散 +2) 把長的行和字串打散 ----------------------- -代碼風格的意義就在於使用平常使用的工具來維持代碼的可讀性和可維護性。 +程式碼風格的意義就在於使用平常使用的工具來維持程式碼的可讀性和可維護性。 每一行的長度的限制是 80 列,我們強烈建議您遵守這個慣例。 長於 80 列的語句要打散成有意義的片段。除非超過 80 列能顯著增加可讀性,並且不 -會隱藏信息。 +會隱藏資訊。 -子片段要明顯短於母片段,並明顯靠右。一種非常常用的樣式是將子體與函數左括號對齊。 +子片段要明顯短於母片段,並明顯靠右。一種非常常用的樣式是將子體與函式左括號 +對齊。 -這同樣適用於有着很長參數列表的函數頭。 +這同樣適用於有著很長參數列表的函式頭。 -然而,絕對不要打散對用戶可見的字符串,例如 printk 信息,因爲這樣就 +然而,絕對不要打散對使用者可見的字串,例如 printk 資訊,因為這樣就 很難對它們 grep。 3) 大括號和空格的放置 --------------------- -C 語言風格中另外一個常見問題是大括號的放置。和縮進大小不同,選擇或棄用某種放 +C 語言風格中另外一個常見問題是大括號的放置。和縮排大小不同,選擇或棄用某種放 置策略並沒有多少技術上的原因,不過首選的方式,就像 Kernighan 和 Ritchie 展示 給我們的,是把起始大括號放在行尾,而把結束大括號放在行首,所以: @@ -134,7 +136,7 @@ C 語言風格中另外一個常見問題是大括號的放置。和縮進大小 we do y } -這適用於所有的非函數語句塊 (if, switch, for, while, do)。比如: +這適用於所有的非函式語句塊 (if, switch, for, while, do)。比如: .. code-block:: c @@ -149,7 +151,7 @@ C 語言風格中另外一個常見問題是大括號的放置。和縮進大小 return NULL; } -不過,有一個例外,那就是函數:函數的起始大括號放置於下一行的開頭,所以: +不過,有一個例外,那就是函式:函式的起始大括號放置於下一行的開頭,所以: .. code-block:: c @@ -159,10 +161,10 @@ C 語言風格中另外一個常見問題是大括號的放置。和縮進大小 } 全世界的異端可能會抱怨這個不一致性是……呃……不一致,不過所有思維健全的人 -都知道 (a) K&R 是 **正確的** 並且 (b) K&R 是正確的。此外,不管怎樣函數都是特 -殊的 (C 函數是不能嵌套的)。 +都知道 (a) K&R 是 **正確的** 並且 (b) K&R 是正確的。此外,不管怎樣函式都是特 +殊的 (C 函式是不能嵌套的)。 -注意結束大括號獨自佔據一行,除非它後面跟着同一個語句的剩餘部分,也就是 do 語 +注意結束大括號獨自佔據一行,除非它後面跟著同一個語句的剩餘部分,也就是 do 語 句中的 ``while`` 或者 if 語句中的 ``else`` ,像這樣: .. code-block:: c @@ -187,7 +189,7 @@ C 語言風格中另外一個常見問題是大括號的放置。和縮進大小 也請注意這種大括號的放置方式也能使空 (或者差不多空的) 行的數量最小化,同時不 失可讀性。因此,由於你的屏幕上的新行是不可再生資源 (想想 25 行的終端屏幕),你 -將會有更多的空行來放置註釋。 +將會有更多的空行來放置註解。 當只有一個單獨的語句的時候,不用加不必要的大括號。 @@ -219,10 +221,10 @@ C 語言風格中另外一個常見問題是大括號的放置。和縮進大小 3.1) 空格 ********* -Linux 內核的空格使用方式 (主要) 取決於它是用於函數還是關鍵字。(大多數) 關鍵字 +Linux 核心的空格使用方式 (主要) 取決於它是用於函式還是關鍵字。(大多數) 關鍵字 後要加一個空格。值得注意的例外是 sizeof, typeof, alignof 和 __attribute__,這 -些關鍵字某些程度上看起來更像函數 (它們在 Linux 裏也常常伴隨小括號而使用,儘管 -在 C 裏這樣的小括號不是必需的,就像 ``struct fileinfo info;`` 聲明過後的 +些關鍵字某些程度上看起來更像函式 (它們在 Linux 裡也常常伴隨小括號而使用,儘管 +在 C 裡這樣的小括號不是必需的,就像 ``struct fileinfo info;`` 宣告過後的 ``sizeof info``)。 所以在這些關鍵字之後放一個空格:: @@ -236,14 +238,14 @@ Linux 內核的空格使用方式 (主要) 取決於它是用於函數還是關 s = sizeof(struct file); -不要在小括號裏的表達式兩側加空格。這是一個 **反例** : +不要在小括號裡的表達式兩側加空格。這是一個 **反例** : .. code-block:: c s = sizeof( struct file ); -當聲明指針類型或者返回指針類型的函數時, ``*`` 的首選使用方式是使之靠近變量名 -或者函數名,而不是靠近類型名。例子: +當宣告指標類型或者返回指標類型的函式時, ``*`` 的首選使用方式是使之靠近變數名 +或者函式名,而不是靠近類型名。例子: .. code-block:: c @@ -269,67 +271,67 @@ Linux 內核的空格使用方式 (主要) 取決於它是用於函數還是關 ``.`` 和 ``->`` 結構體成員操作符前後不加空格。 -不要在行尾留空白。有些可以自動縮進的編輯器會在新行的行首加入適量的空白,然後 -你就可以直接在那一行輸入代碼。不過假如你最後沒有在那一行輸入代碼,有些編輯器 -就不會移除已經加入的空白,就像你故意留下一個只有空白的行。包含行尾空白的行就 -這樣產生了。 +不要在行尾留空白。有些可以自動縮排的編輯器會在新行的行首加入適量的空白,然 +後你就可以直接在那一行輸入程式碼。不過假如你最後沒有在那一行輸入程式碼,有 +些編輯器就不會移除已經加入的空白,就像你故意留下一個只有空白的行。包含行尾 +空白的行就這樣產生了。 當 git 發現補丁包含了行尾空白的時候會警告你,並且可以應你的要求去掉行尾空白; -不過如果你是正在打一系列補丁,這樣做會導致後面的補丁失敗,因爲你改變了補丁的 +不過如果你是正在打一系列補丁,這樣做會導致後面的補丁失敗,因為你改變了補丁的 上下文。 4) 命名 ------- -C 是一個簡樸的語言,你的命名也應該這樣。和 Modula-2 和 Pascal 程序員不同, -C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字。C 程序員會 -稱那個變量爲 ``tmp`` ,這樣寫起來會更容易,而且至少不會令其難於理解。 +C 是一個簡樸的語言,你的命名也應該這樣。和 Modula-2 和 Pascal 程式員不同, +C 程式員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字。C 程式員會 +稱那個變數為 ``tmp`` ,這樣寫起來會更容易,而且至少不會令其難於理解。 -不過,雖然混用大小寫的名字是不提倡使用的,但是全局變量還是需要一個具描述性的 -名字。稱一個全局函數爲 ``foo`` 是一個難以饒恕的錯誤。 +不過,雖然混用大小寫的名字是不提倡使用的,但是全域變數還是需要一個具描述性的 +名字。稱一個全域函式為 ``foo`` 是一個難以饒恕的錯誤。 -全局變量 (只有當你 **真正** 需要它們的時候再用它) 需要有一個具描述性的名字,就 -像全局函數。如果你有一個可以計算活動用戶數量的函數,你應該叫它 +全域變數 (只有當你 **真正** 需要它們的時候再用它) 需要有一個具描述性的名字,就 +像全域函式。如果你有一個可以計算活動使用者數量的函式,你應該叫它 ``count_active_users()`` 或者類似的名字,你不應該叫它 ``cntuser()`` 。 -在函數名中包含函數類型 (所謂的匈牙利命名法) 是腦子出了問題——編譯器知道那些類 -型而且能夠檢查那些類型,這樣做只能把程序員弄糊塗了。 +在函式名中包含函式類型 (所謂的匈牙利命名法) 是腦子出了問題——編譯器知道那些類 +型而且能夠檢查那些類型,這樣做只能把程式員弄糊塗了。 -本地變量名應該簡短,而且能夠表達相關的含義。如果你有一些隨機的整數型的循環計 -數器,它應該被稱爲 ``i`` 。叫它 ``loop_counter`` 並無益處,如果它沒有被誤解的 -可能的話。類似的, ``tmp`` 可以用來稱呼任意類型的臨時變量。 +本地變數名應該簡短,而且能夠表達相關的含義。如果你有一些隨機的整數型的迴圈計 +數器,它應該被稱為 ``i`` 。叫它 ``loop_counter`` 並無益處,如果它沒有被誤解的 +可能的話。類似的, ``tmp`` 可以用來稱呼任意類型的臨時變數。 -如果你怕混淆了你的本地變量名,你就遇到另一個問題了,叫做函數增長荷爾蒙失衡綜 -合徵。請看第六章 (函數)。 +如果你怕混淆了你的本地變數名,你就遇到另一個問題了,叫做函式增長荷爾蒙失衡綜 +合徵。請看第六章 (函式)。 -對於符號名稱和文檔,避免引入新的“master/slave”(或獨立於“master”的“slave”) +對於符號名稱和文件,避免引入新的“master/slave”(或獨立於“master”的“slave”) 和“blacklist/whitelist”。 -“master/slave”推薦替換爲: +“master/slave”推薦替換為: '{primary,main} / {secondary,replica,subordinate}' '{initiator,requester} / {target,responder}' '{controller,host} / {device,worker,proxy}' 'leader/follower' 'director/performer' -“blacklist/whitelist”推薦替換爲: +“blacklist/whitelist”推薦替換為: 'denylist/allowlist' 'blocklist/passlist' -引入新用法的例外情況是:維護用戶空間ABI/API,或更新現有(截至2020年)硬件或 -協議規範的代碼時要求這些術語。對於新規範,儘可能將術語的規範用法轉換爲內核 +引入新用法的例外情況是:維護使用者空間ABI/API,或更新現有(截至2020年)硬體或 +協議規範的程式碼時要求這些術語。對於新規範,儘可能將術語的規範用法轉換為核心 編碼標準。 .. warning:: - 以上主從、黑白名單規則不適用於中文文檔,請勿更改中文術語! + 以上主從、黑白名單規則不適用於中文文件,請勿更改中文術語! 5) Typedef ---------- 不要使用類似 ``vps_t`` 之類的東西。 -對結構體和指針使用 typedef 是一個 **錯誤** 。當你在代碼裏看到: +對結構體和指標使用 typedef 是一個 **錯誤** 。當你在程式碼裡看到: .. code-block:: c @@ -345,34 +347,34 @@ C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字 你就知道 ``a`` 是什麼了。 -很多人認爲 typedef ``能提高可讀性`` 。實際不是這樣的。它們只在下列情況下有用: +很多人認為 typedef ``能提高可讀性`` 。實際不是這樣的。它們只在下列情況下有用: - (a) 完全不透明的對象 (這種情況下要主動使用 typedef 來 **隱藏** 這個對象實際上 + (a) 完全不透明的物件 (這種情況下要主動使用 typedef 來 **隱藏** 這個物件實際上 是什麼)。 - 例如: ``pte_t`` 等不透明對象,你只能用合適的訪問函數來訪問它們。 + 例如: ``pte_t`` 等不透明物件,你只能用合適的存取函式來存取它們。 .. note:: - 不透明性和“訪問函數”本身是不好的。我們使用 pte_t 等類型的原因在於真 - 的是完全沒有任何共用的可訪問信息。 + 不透明性和“存取函式”本身是不好的。我們使用 pte_t 等類型的原因在於真 + 的是完全沒有任何共用的可存取資訊。 (b) 清楚的整數類型,如此,這層抽象就可以 **幫助** 消除到底是 ``int`` 還是 ``long`` 的混淆。 - u8/u16/u32 是完全沒有問題的 typedef,不過它們更符合類別 (d) 而不是這裏。 + u8/u16/u32 是完全沒有問題的 typedef,不過它們更符合類別 (d) 而不是這裡。 .. note:: - 要這樣做,必須事出有因。如果某個變量是 ``unsigned long`` ,那麼沒有必要 + 要這樣做,必須事出有因。如果某個變數是 ``unsigned long`` ,那麼沒有必要 typedef unsigned long myflags_t; 不過如果有一個明確的原因,比如它在某種情況下可能會是一個 ``unsigned int`` - 而在其他情況下可能爲 ``unsigned long`` ,那麼就不要猶豫,請務必使用 + 而在其他情況下可能為 ``unsigned long`` ,那麼就不要猶豫,請務必使用 typedef。 - (c) 當你使用 sparse 按字面的創建一個 **新** 類型來做類型檢查的時候。 + (c) 當你使用 sparse 按字面的建立一個 **新** 類型來做類型檢查的時候。 (d) 和標準 C99 類型相同的類型,在某些例外的情況下。 @@ -380,45 +382,45 @@ C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字 是有些人仍然拒絕使用它們。 因此,Linux 特有的等同於標準類型的 ``u8/u16/u32/u64`` 類型和它們的有符號 - 類型是被允許的——儘管在你自己的新代碼中,它們不是強制要求要使用的。 + 類型是被允許的——儘管在你自己的新程式碼中,它們不是強制要求要使用的。 - 當編輯已經使用了某個類型集的已有代碼時,你應該遵循那些代碼中已經做出的選 + 當編輯已經使用了某個類型集的已有程式碼時,你應該遵循那些程式碼中已經做出的選 擇。 - (e) 可以在用戶空間安全使用的類型。 + (e) 可以在使用者空間安全使用的類型。 - 在某些用戶空間可見的結構體裏,我們不能要求 C99 類型而且不能用上面提到的 - ``u32`` 類型。因此,我們在與用戶空間共享的所有結構體中使用 __u32 和類似 + 在某些使用者空間可見的結構體裡,我們不能要求 C99 類型而且不能用上面提到的 + ``u32`` 類型。因此,我們在與使用者空間共享的所有結構體中使用 __u32 和類似 的類型。 可能還有其他的情況,不過基本的規則是 **永遠不要** 使用 typedef,除非你可以明 確的應用上述某個規則中的一個。 -總的來說,如果一個指針或者一個結構體裏的元素可以合理的被直接訪問到,那麼它們 +總的來說,如果一個指標或者一個結構體裡的元素可以合理的被直接存取到,那麼它們 就不應該是一個 typedef。 -6) 函數 +6) 函式 ------- -函數應該簡短而漂亮,並且只完成一件事情。函數應該可以一屏或者兩屏顯示完 (我們 +函式應該簡短而漂亮,並且只完成一件事情。函式應該可以一屏或者兩屏顯示完 (我們 都知道 ISO/ANSI 屏幕大小是 80x24),只做一件事情,而且把它做好。 -一個函數的最大長度是和該函數的複雜度和縮進級數成反比的。所以,如果你有一個理 -論上很簡單的只有一個很長 (但是簡單) 的 case 語句的函數,而且你需要在每個 case -裏做很多很小的事情,這樣的函數儘管很長,但也是可以的。 +一個函式的最大長度是和該函式的複雜度和縮排級數成反比的。所以,如果你有一個理 +論上很簡單的只有一個很長 (但是簡單) 的 case 語句的函式,而且你需要在每個 case +裡做很多很小的事情,這樣的函式儘管很長,但也是可以的。 -不過,如果你有一個複雜的函數,而且你懷疑一個天分不是很高的高中一年級學生可能 -甚至搞不清楚這個函數的目的,你應該嚴格遵守前面提到的長度限制。使用輔助函數, -併爲之取個具描述性的名字 (如果你覺得它們的性能很重要的話,可以讓編譯器內聯它 -們,這樣的效果往往會比你寫一個複雜函數的效果要好。) +不過,如果你有一個複雜的函式,而且你懷疑一個天分不是很高的高中一年級學生可能 +甚至搞不清楚這個函式的目的,你應該嚴格遵守前面提到的長度限制。使用輔助函式, +併為之取個具描述性的名字 (如果你覺得它們的效能很重要的話,可以讓編譯器行內它 +們,這樣的效果往往會比你寫一個複雜函式的效果要好。) -函數的另外一個衡量標準是本地變量的數量。此數量不應超過 5-10 個,否則你的函數 -就有問題了。重新考慮一下你的函數,把它分拆成更小的函數。人的大腦一般可以輕鬆 -的同時跟蹤 7 個不同的事物,如果再增多的話,就會糊塗了。即便你聰穎過人,你也可 +函式的另外一個衡量標準是本地變數的數量。此數量不應超過 5-10 個,否則你的函式 +就有問題了。重新考慮一下你的函式,把它分拆成更小的函式。人的大腦一般可以輕鬆 +的同時追蹤 7 個不同的事物,如果再增多的話,就會糊塗了。即便你聰穎過人,你也可 能會記不清你 2 個星期前做過的事情。 -在源文件裏,使用空行隔開不同的函數。如果該函數需要被導出,它的 **EXPORT** 宏 +在原始檔裡,使用空行隔開不同的函式。如果該函式需要被匯出,它的 **EXPORT** 巨集 應該緊貼在它的結束大括號之下。比如: .. code-block:: c @@ -429,37 +431,38 @@ C 程序員不使用類似 ThisVariableIsATemporaryCounter 這樣華麗的名字 } EXPORT_SYMBOL(system_is_up); -6.1) 函數原型 +6.1) 函式原型 ************* -在函數原型中包含參數名和它們的數據類型。雖然 C 語言裏沒有這樣的要求,但在 -Linux 裏這是提倡的做法,因爲這樣可以很簡單的給讀者提供更多的有價值的信息。 +在函式原型中包含參數名和它們的資料類型。雖然 C 語言裡沒有這樣的要求,但在 +Linux 裡這是提倡的做法,因為這樣可以很簡單的給讀者提供更多的有價值的資訊。 -不要在函數聲明裏使用 ``extern`` 關鍵字,因爲這會導致代碼行變長,並且不是嚴格 +不要在函式宣告裡使用 ``extern`` 關鍵字,因為這會導致程式碼行變長,並且不是嚴格 必需的。 -寫函數原型時,請保持 `元素順序規則 `_ 。 -例如下列函數聲明:: +寫函式原型時,請保持 `元素順序規則 `_ 。 +例如下列函式宣告:: __init void * __must_check action(enum magic value, size_t size, u8 count, char *fmt, ...) __printf(4, 5) __malloc; -推薦的函數原型元素順序是: +推薦的函式原型元素順序是: - 儲存類型(下方的 ``static __always_inline`` ,注意 ``__always_inline`` 技術上來講是個屬性但被當做 ``inline`` ) -- 儲存類型屬性(上方的 ``__init`` ——即節聲明,但也像 ``__cold`` ) +- 儲存類型屬性(上方的 ``__init`` ——即節宣告,但也像 ``__cold`` ) - 返回類型(上方的 ``void *`` ) - 返回類型屬性(上方的 ``__must_check`` ) -- 函數名(上方的 ``action`` ) -- 函數參數(上方的 ``(enum magic value, size_t size, u8 count, char *fmt, ...)`` , - 注意必須寫上參數名) -- 函數參數屬性(上方的 ``__printf(4, 5)`` ) -- 函數行爲屬性(上方的 ``__malloc`` ) +- 函式名(上方的 ``action`` ) +- 函式參數(上方的 + ``(enum magic value, size_t size, u8 count, char *fmt, ...)`` ,注意必須 + 寫上參數名) +- 函式參數屬性(上方的 ``__printf(4, 5)`` ) +- 函式行為屬性(上方的 ``__malloc`` ) -請注意,對於函數 **定義** (即實際函數體),編譯器不允許在函數參數之後添加函 -數參數屬性。在這種情況下,它們應該跟隨存儲類型屬性(例如,與上面的 **聲明** -示例相比,請注意下面的 ``__printf(4, 5)`` 的位置發生了變化):: +請注意,對於函式 **定義** (即實際函式體),編譯器不允許在函式參數之後添加函 +數參數屬性。在這種情況下,它們應該跟隨儲存類型屬性(例如,與上面的 **宣告** +範例相比,請注意下面的 ``__printf(4, 5)`` 的位置發生了變化):: static __always_inline __init __printf(4, 5) void * __must_check action(enum magic value, size_t size, u8 count, char *fmt, ...) __malloc @@ -467,26 +470,26 @@ Linux 裏這是提倡的做法,因爲這樣可以很簡單的給讀者提供 ... } -7) 集中的函數退出途徑 +7) 集中的函式退出途徑 --------------------- 雖然被某些人聲稱已經過時,但是 goto 語句的等價物還是經常被編譯器所使用,具體 形式是無條件跳轉指令。 -當一個函數從多個位置退出,並且需要做一些類似清理的常見操作時,goto 語句就很方 +當一個函式從多個位置退出,並且需要做一些類似清理的常見操作時,goto 語句就很方 便了。如果並不需要清理操作,那麼直接 return 即可。 -選擇一個能夠說明 goto 行爲或它爲何存在的標籤名。如果 goto 要釋放 ``buffer``, +選擇一個能夠說明 goto 行為或它為何存在的標籤名。如果 goto 要釋放 ``buffer``, 一個不錯的名字可以是 ``out_free_buffer:`` 。別去使用像 ``err1:`` 和 ``err2:`` -這樣的GW_BASIC 名稱,因爲一旦你添加或刪除了 (函數的) 退出路徑,你就必須對它們 +這樣的GW_BASIC 名稱,因為一旦你添加或刪除了 (函式的) 退出路徑,你就必須對它們 重新編號,這樣會難以去檢驗正確性。 使用 goto 的理由是: -- 無條件語句容易理解和跟蹤 +- 無條件語句容易理解和追蹤 - 嵌套程度減小 - 可以避免由於修改時忘記更新個別的退出點而導致錯誤 -- 讓編譯器省去刪除冗餘代碼的工作 ;) +- 讓編譯器省去刪除冗餘程式碼的工作 ;) .. code-block:: c @@ -521,7 +524,7 @@ Linux 裏這是提倡的做法,因爲這樣可以很簡單的給讀者提供 kfree(foo); return ret; -這段代碼的錯誤是,在某些退出路徑上 ``foo`` 是 NULL。通常情況下,通過把它分離 +這段程式碼的錯誤是,在某些退出路徑上 ``foo`` 是 NULL。通常情況下,透過把它分離 成兩個錯誤標籤 ``err_free_bar:`` 和 ``err_free_foo:`` 來修復這個錯誤: .. code-block:: c @@ -535,22 +538,23 @@ Linux 裏這是提倡的做法,因爲這樣可以很簡單的給讀者提供 理想情況下,你應該模擬錯誤來測試所有退出路徑。 -8) 註釋 +8) 註解 ------- -註釋是好的,不過有過度註釋的危險。永遠不要在註釋裏解釋你的代碼是如何運作的: -更好的做法是讓別人一看你的代碼就可以明白,解釋寫的很差的代碼是浪費時間。 +註解是好的,不過有過度註解的危險。永遠不要在註解裡解釋你的程式碼是如何運作的: +更好的做法是讓別人一看你的程式碼就可以明白,解釋寫的很差的程式碼是浪費時間。 -一般來說你用註釋告訴別人你的代碼做了什麼,而不是怎麼做的。也請你不要把 -註釋放在一個函數體內部:如果函數複雜到你需要獨立的註釋其中的一部分,你很可能 -需要回到第六章看一看。你可以做一些小注釋來註明或警告某些很聰明 (或者槽糕) 的 -做法,但不要加太多。你應該做的,是把註釋放在函數的頭部,告訴人們它做了什麼, +一般來說你用註解告訴別人你的程式碼做了什麼,而不是怎麼做的。也請你不要把 +註解放在一個函式體內部:如果函式複雜到你需要獨立的註解其中的一部分,你很可能 +需要回到第六章看一看。你可以做一些小註解來註明或警告某些很聰明 (或者槽糕) 的 +做法,但不要加太多。你應該做的,是把註解放在函式的頭部,告訴人們它做了什麼, 也可以加上它做這些事情的原因。 -當註釋內核 API 函數時,請使用 kernel-doc 格式。詳見 -Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc 。 +當註解核心 API 函式時,請使用 kernel-doc 格式。詳見 +Documentation/translations/zh_CN/doc-guide/index.rst 和 +tools/docs/kernel-doc 。 -長 (多行) 註釋的首選風格是: +長 (多行) 註解的首選風格是: .. code-block:: c @@ -563,7 +567,7 @@ Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc * with beginning and ending almost-blank lines. */ -對於在 net/ 和 drivers/net/ 的文件,首選的長 (多行) 註釋風格有些不同。 +對於在 net/ 和 drivers/net/ 的檔案,首選的長 (多行) 註解風格有些不同。 .. code-block:: c @@ -574,22 +578,22 @@ Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc * but there is no initial almost-blank line. */ -註釋數據也是很重要的,不管是基本類型還是衍生類型。爲了方便實現這一點,每一行 -應只聲明一個數據 (不要使用逗號來一次聲明多個數據)。這樣你就有空間來爲每個數據 -寫一段小注釋來解釋它們的用途了。 +註解資料也是很重要的,不管是基本類型還是衍生類型。為了方便實作這一點,每一行 +應只宣告一個資料 (不要使用逗號來一次宣告多個資料)。這樣你就有空間來為每個資料 +寫一段小註解來解釋它們的用途了。 9) 你已經把事情弄糟了 --------------------- -這沒什麼,我們都是這樣。可能你長期使用 Unix 的朋友已經告訴你 -``GNU emacs`` 能自動幫你格式化 C 源代碼,而且你也注意到了,確實是這樣,不過它 -所使用的默認值和我們想要的相去甚遠 (實際上,甚至比隨機打的還要差——無數個猴子 -在 GNU emacs 裏打字永遠不會創造出一個好程序) +這沒什麼,我們都是這樣。可能你長期使用 Unix 的朋友已經告訴你``GNU emacs`` +能自動幫你格式化 C 原始程式碼,而且你也注意到了,確實是這樣,不過它所使用 +的預設值和我們想要的相去甚遠 (實際上,甚至比隨機打的還要差——無數個猴子在 +GNU emacs 裡打字永遠不會創造出一個好程式) *(譯註:Infinite Monkey Theorem)* 所以你要麼放棄 GNU emacs,要麼改變它讓它使用更合理的設定。要採用後一個方案, -你可以把下面這段粘貼到你的 .emacs 文件裏。 +你可以把下面這段貼上到你的 .emacs 檔案裡。 .. code-block:: elisp @@ -640,31 +644,31 @@ Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc (expand-file-name "~/src/linux-trees") 'linux-kernel) -這會讓 emacs 在 ``~/src/linux-trees`` 下的 C 源文件獲得更好的內核代碼風格。 +這會讓 emacs 在 ``~/src/linux-trees`` 下的 C 原始檔獲得更好的核心程式碼風格。 -不過就算你嘗試讓 emacs 正確的格式化代碼失敗了,也並不意味着你失去了一切:還可 -以用 ``indent`` 。 +不過就算你嘗試讓 emacs 正確的格式化程式碼失敗了,也並不意味著你失去了一切: +還可以用 ``indent`` 。 不過,GNU indent 也有和 GNU emacs 一樣有問題的設定,所以你需要給它一些命令選 -項。不過,這還不算太糟糕,因爲就算是 GNU indent 的作者也認同 K&R 的權威性 +項。不過,這還不算太糟糕,因為就算是 GNU indent 的作者也認同 K&R 的權威性 (GNU 的人並不是壞人,他們只是在這個問題上被嚴重的誤導了),所以你只要給 indent -指定選項 ``-kr -i8`` (代表 ``K&R,8 字符縮進``),或使用 ``scripts/Lindent`` -這樣就可以以最時髦的方式縮進源代碼。 +指定選項 ``-kr -i8`` (代表 ``K&R,8 字元縮排``),或使用 ``scripts/Lindent`` +這樣就可以以最時髦的方式縮排原始程式碼。 -``indent`` 有很多選項,特別是重新格式化註釋的時候,你可能需要看一下它的手冊。 -不過記住: ``indent`` 不能修正壞的編程習慣。 +``indent`` 有很多選項,特別是重新格式化註解的時候,你可能需要看一下它的手冊。 +不過記住: ``indent`` 不能修正壞的程式設計習慣。 -請注意,您還可以使用 ``clang-format`` 工具幫助您處理這些規則,快速自動重新格 -式化部分代碼,並審閱整個文件以發現代碼風格錯誤、打字錯誤和可能的改進。它還可 -以方便地排序 ``#include`` ,對齊變量/宏,重排文本和其他類似任務。 +請注意,您還可以使用 ``clang-format`` 工具幫助您處理這些規則,快速自動重新 +格式化部分程式碼,並審閱整個檔案以發現程式碼風格錯誤、打字錯誤和可能的改進。 +它還可以方便地排序 ``#include`` ,對齊變數/巨集,重排文字和其他類似任務。 詳見 Documentation/dev-tools/clang-format.rst 。 -10) Kconfig 配置文件 +10) Kconfig 設定檔 -------------------- -對於遍佈源碼樹的所有 Kconfig* 配置文件來說,它們縮進方式有所不同。緊挨着 -``config`` 定義的行,用一個製表符縮進,然而 help 信息的縮進則額外增加 2 個空 +對於遍佈源碼樹的所有 Kconfig* 設定文件來說,它們縮排方式有所不同。緊挨著 +``config`` 定義的行,用一個製表符縮排,然而 help 資訊的縮排則額外增加 2 個空 格。舉個例子:: config AUDIT @@ -676,7 +680,7 @@ Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc logging of avc messages output). Does not do system-call auditing without CONFIG_AUDITSYSCALL. -而那些危險的功能 (比如某些文件系統的寫支持) 應該在它們的提示字符串裏顯著的聲 +而那些危險的功能 (比如某些檔案系統的寫支援) 應該在它們的提示字串裡顯著的聲 明這一點:: config ADFS_FS_RW @@ -684,49 +688,50 @@ Documentation/translations/zh_CN/doc-guide/index.rst 和 tools/docs/kernel-doc depends on ADFS_FS ... -要查看配置文件的完整文檔,請看 Documentation/kbuild/kconfig-language.rst 。 +要查看設定文件的完整文件,請看 Documentation/kbuild/kconfig-language.rst 。 -11) 數據結構 +11) 資料結構 ------------ -如果一個數據結構,在創建和銷燬它的單線執行環境之外可見,那麼它必須要有一個引 -用計數器。內核裏沒有垃圾收集 (並且內核之外的垃圾收集慢且效率低下),這意味着你 -絕對需要記錄你對這種數據結構的使用情況。 +如果一個資料結構,在建立和銷燬它的單線執行環境之外可見,那麼它必須要有一個引 +用計數器。核心裡沒有垃圾收集 (並且核心之外的垃圾收集慢且效率低下),這意味著你 +絕對需要記錄你對這種資料結構的使用情況。 -引用計數意味着你能夠避免上鎖,並且允許多個用戶並行訪問這個數據結構——而不需要 -擔心這個數據結構僅僅因爲暫時不被使用就消失了,那些用戶可能不過是沉睡了一陣或 +引用計數意味著你能夠避免上鎖,並且允許多個使用者並行存取這個資料結構——而不需要 +擔心這個資料結構僅僅因為暫時不被使用就消失了,那些使用者可能不過是沉睡了一陣或 者做了一些其他事情而已。 -注意上鎖 **不能** 取代引用計數。上鎖是爲了保持數據結構的一致性,而引用計數是一 -個內存管理技巧。通常二者都需要,不要把兩個搞混了。 +注意上鎖 **不能** 取代引用計數。上鎖是為了保持資料結構的一致性,而引用計數是一 +個記憶體管理技巧。通常二者都需要,不要把兩個搞混了。 -很多數據結構實際上有 2 級引用計數,它們通常有不同 ``類`` 的用戶。子類計數器統 -計子類用戶的數量,每當子類計數器減至零時,全局計數器減一。 +很多資料結構實際上有 2 級引用計數,它們通常有不同 ``類`` 的使用者。子類計 +數器統計子類使用者的數量,每當子類計數器減至零時,全域計數器減一。 -這種 ``多級引用計數`` 的例子可以在內存管理 (``struct mm_struct``: mm_users 和 -mm_count),和文件系統 (``struct super_block``: s_count 和 s_active) 中找到。 +這種 ``多級引用計數`` 的例子可以在記憶體管理 (``struct mm_struct``: +mm_users 和mm_count),和檔案系統 (``struct super_block``: s_count 和 +s_active) 中找到。 -記住:如果另一個執行線索可以找到你的數據結構,但這個數據結構沒有引用計數器, -這裏幾乎肯定是一個 bug。 +記住:如果另一個執行線索可以找到你的資料結構,但這個資料結構沒有引用計數器, +這裡幾乎肯定是一個 bug。 -12) 宏,枚舉和RTL ------------------ +12) 巨集,列舉和RTL +------------------- -用於定義常量的宏的名字及枚舉裏的標籤需要大寫。 +用於定義常數的巨集的名字及列舉裡的標籤需要大寫。 .. code-block:: c #define CONSTANT 0x12345 -在定義幾個相關的常量時,最好用枚舉。 +在定義幾個相關的常數時,最好用列舉。 -宏的名字請用大寫字母,不過形如函數的宏的名字可以用小寫字母。 +巨集的名字請用大寫字母,不過形如函式的巨集的名字可以用小寫字母。 -通常如果能寫成內聯函數就不要寫成像函數的宏。 +通常如果能寫成行內函式就不要寫成像函式的巨集。 -含有多個語句的宏應該被包含在一個 do-while 代碼塊裏: +含有多個語句的巨集應該被包含在一個 do-while 程式碼塊裡: .. code-block:: c @@ -736,9 +741,9 @@ mm_count),和文件系統 (``struct super_block``: s_count 和 s_active) 中 do_this(b, c); \ } while (0) -使用宏的時候應避免的事情: +使用巨集的時候應避免的事情: -1) 影響控制流程的宏: +1) 影響控制流程的巨集: .. code-block:: c @@ -748,30 +753,30 @@ mm_count),和文件系統 (``struct super_block``: s_count 和 s_active) 中 return -EBUGGERED; \ } while (0) -**非常** 不好。它看起來像一個函數,不過卻能導致 ``調用`` 它的函數退出;不要打 -亂讀者大腦裏的語法分析器。 +**非常** 不好。它看起來像一個函式,不過卻能導致 ``呼叫`` 它的函式退出;不要打 +亂讀者大腦裡的語法分析器。 -2) 依賴於一個固定名字的本地變量的宏: +2) 依賴於一個固定名字的本地變數的巨集: .. code-block:: c #define FOO(val) bar(index, val) -可能看起來像是個不錯的東西,不過它非常容易把讀代碼的人搞糊塗,而且容易導致看起 -來不相關的改動帶來錯誤。 +可能看起來像是個不錯的東西,不過它非常容易把讀程式碼的人搞糊塗,而且容易導 +致看起來不相關的改動帶來錯誤。 -3) 作爲左值的帶參數的宏: FOO(x) = y;如果有人把 FOO 變成一個內聯函數的話,這 +3) 作為左值的帶參數的巨集: FOO(x) = y;如果有人把 FOO 變成一個行內函式的話,這 種用法就會出錯了。 -4) 忘記了優先級:使用表達式定義常量的宏必須將表達式置於一對小括號之內。帶參數 - 的宏也要注意此類問題。 +4) 忘記了優先級:使用表達式定義常數的巨集必須將表達式置於一對小括號之內。帶參數 + 的巨集也要注意此類問題。 .. code-block:: c #define CONSTANT 0x4000 #define CONSTEXP (CONSTANT | 3) -5) 在宏裏定義類似函數的本地變量時命名衝突: +5) 在巨集裡定義類似函式的本地變數時命名衝突: .. code-block:: c @@ -782,45 +787,45 @@ mm_count),和文件系統 (``struct super_block``: s_count 和 s_active) 中 (ret); \ }) -ret 是本地變量的通用名字—— __foo_ret 更不容易與一個已存在的變量衝突。 +ret 是本地變數的通用名字—— __foo_ret 更不容易與一個已存在的變數衝突。 -cpp 手冊對宏的講解很詳細。gcc internals 手冊也詳細講解了 RTL,內核裏的彙編語 +cpp 手冊對巨集的講解很詳細。gcc internals 手冊也詳細講解了 RTL,核心裡的組譯語 言經常用到它。 -13) 打印內核消息 +13) 列印核心訊息 ---------------- -內核開發者應該看起來有文化。請一定注意內核信息的拼寫,以給人良好的印象。 +核心開發者應該看起來有文化。請一定注意核心資訊的拼寫,以給人良好的印象。 不要用不規範的單詞比如 ``dont``,而要用 ``do not`` 或者 ``don't`` 。保證這些信 息簡單明瞭、無歧義。 -內核信息不必以英文句號結束。 +核心資訊不必以英文句號結束。 -在小括號裏打印數字 (%d) 沒有任何價值,應該避免這樣做。 +在小括號裡列印數字 (%d) 沒有任何價值,應該避免這樣做。 - 裏有一些驅動模型診斷宏,你應該使用它們,以確保信息對應於正確 -的設備和驅動,並且被標記了正確的消息級別。這些宏有:dev_err(), dev_warn(), -dev_info() 等等。對於那些不和某個特定設備相關連的信息, 定義 -了 pr_notice(), pr_info(), pr_warn(), pr_err() 和其他。 + 裡有一些驅動模型診斷巨集,你應該使用它們,以確保資訊對應 +於正確的設備和驅動,並且被標記了正確的訊息級別。這些巨集有:dev_err(), +dev_warn(),dev_info() 等等。對於那些不和某個特定設備相關連的資訊, + 定義了 pr_notice(), pr_info(), pr_warn(), pr_err() 和其他。 -寫出好的調試信息可以是一個很大的挑戰;一旦你寫出後,這些信息在遠程除錯時能提 -供極大的幫助。然而打印調試信息的處理方式同打印非調試信息不同。其他 pr_XXX() -函數能無條件地打印,pr_debug() 卻不;默認情況下它不會被編譯,除非定義了 DEBUG -或設定了 CONFIG_DYNAMIC_DEBUG。實際這同樣是爲了 dev_dbg(),一個相關約定是在一 -個已經開啓了 DEBUG 時,使用 VERBOSE_DEBUG 來添加 dev_vdbg()。 +寫出好的除錯資訊可以是一個很大的挑戰;一旦你寫出後,這些資訊在遠端除錯時能提 +供極大的幫助。然而列印除錯資訊的處理方式同列印非除錯資訊不同。其他 pr_XXX() +函式能無條件地列印,pr_debug() 卻不;預設情況下它不會被編譯,除非定義了 DEBUG +或設定了 CONFIG_DYNAMIC_DEBUG。實際這同樣是為了 dev_dbg(),一個相關約定是在一 +個已經開啟了 DEBUG 時,使用 VERBOSE_DEBUG 來添加 dev_vdbg()。 -許多子系統擁有 Kconfig 調試選項來開啓對應 Makefile 裏面的 -DDEBUG;在其他 -情況下,特殊文件使用 #define DEBUG。當一條調試信息需要被無條件打印時,例如, -如果已經包含一個調試相關的 #ifdef 條件,printk(KERN_DEBUG ...) 就可被使用。 +許多子系統擁有 Kconfig 除錯選項來開啟對應 Makefile 裡面的 -DDEBUG;在其他 +情況下,特定檔案使用 #define DEBUG。當一條除錯資訊需要被無條件列印時,例如, +如果已經包含一個除錯相關的 #ifdef 條件,printk(KERN_DEBUG ...) 就可被使用。 -14) 分配內存 ------------- +14) 分配記憶體 +-------------- -內核提供了下面的一般用途的內存分配函數: -kmalloc(), kzalloc(), kmalloc_array(), kcalloc(), vmalloc() 和 vzalloc()。 -請參考 API 文檔以獲取有關它們的詳細信息: +核心提供了下面的一般用途的記憶體分配函式: +kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc() 和 vzalloc()。 +請參考 API 文件以獲取有關它們的詳細資訊: Documentation/translations/zh_CN/core-api/memory-allocation.rst 。 傳遞結構體大小的首選形式是這樣的: @@ -830,103 +835,106 @@ Documentation/translations/zh_CN/core-api/memory-allocation.rst 。 p = kmalloc_obj(*p, ...); 另外一種傳遞方式中,sizeof 的操作數是結構體的名字,這樣會降低可讀性,並且可能 -會引入 bug。有可能指針變量類型被改變時,而對應的傳遞給內存分配函數的 sizeof +會引入 bug。有可能指標變數類型被改變時,而對應的傳遞給記憶體分配函式的 sizeof 的結果不變。 -強制轉換一個 void 指針返回值是多餘的。C 語言本身保證了從 void 指針到其他任何 -指針類型的轉換是沒有問題的。 +強制轉換一個 void 指標回傳值是多餘的。C 語言本身保證了從 void 指標到其他任何 +指標類型的轉換是沒有問題的。 -分配一個數組的首選形式是這樣的: +分配一個陣列的首選形式是這樣的: .. code-block:: c - p = kmalloc_array(n, sizeof(...), ...); + p = kmalloc_objs(*p, n, ...); -分配一個零長數組的首選形式是這樣的: +分配一個零長陣列的首選形式是這樣的: .. code-block:: c - p = kcalloc(n, sizeof(...), ...); + p = kzalloc_objs(*p, n, ...); + +這兩種形式都會檢查分配大小 n * sizeof(...) 是否溢位,如果發生溢位則回傳 +NULL。 -兩種形式都會檢查分配 n * sizeof(...) 大小時內存的溢出,如果溢出返回 NULL。 +兩種形式都會檢查分配 n * sizeof(...) 大小時記憶體的溢出,如果溢出返回 NULL。 -在沒有 __GFP_NOWARN 的情況下使用時,這些通用分配函數都會在失敗時發起堆棧轉儲, -因此當返回NULL時,沒有必要發出額外的失敗消息。 +在沒有 __GFP_NOWARN 的情況下使用時,這些通用分配函式都會在失敗時發起堆疊轉儲, +因此當返回NULL時,沒有必要發出額外的失敗訊息。 -15) 內聯弊病 +15) 行內弊病 ------------ -有一個常見的誤解是 ``內聯`` 是 gcc 提供的可以讓代碼運行更快的一個選項。雖然使 -用內聯函數有時候是恰當的 (比如作爲一種替代宏的方式,請看第十二章),不過很多情 -況下不是這樣。inline 的過度使用會使內核變大,從而使整個系統運行速度變慢。 -因爲體積大內核會佔用更多的指令高速緩存,而且會導致 pagecache 的可用內存減少。 -想象一下,一次 pagecache 未命中就會導致一次磁盤尋址,將耗時 5 毫秒。5 毫秒的 -時間內 CPU 能執行很多很多指令。 - -一個基本的原則是如果一個函數有 3 行以上,就不要把它變成內聯函數。這個原則的一 -個例外是,如果你知道某個參數是一個編譯時常量,而且因爲這個常量你確定編譯器在 -編譯時能優化掉你的函數的大部分代碼,那仍然可以給它加上 inline 關鍵字。 -kmalloc() 內聯函數就是一個很好的例子。 - -人們經常主張給 static 的而且只用了一次的函數加上 inline,如此不會有任何損失, -因爲沒有什麼好權衡的。雖然從技術上說這是正確的,但是實際上這種情況下即使不加 -inline gcc 也可以自動使其內聯。而且其他用戶可能會要求移除 inline,由此而來的 +有一個常見的誤解是 ``行內`` 是 gcc 提供的可以讓程式碼執行更快的一個選項。 +雖然使用行內函式有時候是恰當的 (比如作為一種替代巨集的方式,請看第十二章), +不過很多情況下不是這樣。inline 的過度使用會使核心變大,從而使整個系統執行 +速度變慢。因為體積大核心會佔用更多的指令高速快取,而且會導致 pagecache 的 +可用記憶體減少。想象一下,一次 pagecache 未命中就會導致一次磁碟尋址,將耗 +時 5 毫秒。5 毫秒的時間內 CPU 能執行很多很多指令。 + +一個基本的原則是如果一個函式有 3 行以上,就不要把它變成行內函式。這個原則的一 +個例外是,如果你知道某個參數是一個編譯時常數,而且因為這個常數你確定編譯器在 +編譯時能最佳化掉你的函式的大部分程式碼,那仍然可以給它加上 inline 關鍵字。 +kmalloc() 行內函式就是一個很好的例子。 + +人們經常主張給 static 的而且只用了一次的函式加上 inline,如此不會有任何損失, +因為沒有什麼好權衡的。雖然從技術上說這是正確的,但是實際上這種情況下即使不加 +inline gcc 也可以自動使其行內。而且其他使用者可能會要求移除 inline,由此而來的 爭論會抵消 inline 自身的潛在價值,得不償失。 -16) 函數返回值及命名 +16) 函式回傳值及命名 -------------------- -函數可以返回多種不同類型的值,最常見的一種是表明函數執行成功或者失敗的值。這樣 -的一個值可以表示爲一個錯誤代碼整數 (-Exxx=失敗,0=成功) 或者一個 ``成功`` -布爾值 (0=失敗,非0=成功)。 +函式可以返回多種不同類型的值,最常見的一種是表明函式執行成功或者失敗的值。這樣 +的一個值可以表示為一個錯誤程式碼整數 (-Exxx=失敗,0=成功) 或者一個 ``成功`` +布林值 (0=失敗,非0=成功)。 混合使用這兩種表達方式是難於發現的 bug 的來源。如果 C 語言本身嚴格區分整形和 -布爾型變量,那麼編譯器就能夠幫我們發現這些錯誤... 不過 C 語言不區分。爲了避免 +布林型變數,那麼編譯器就能夠幫我們發現這些錯誤... 不過 C 語言不區分。為了避免 產生這種 bug,請遵循下面的慣例:: - 如果函數的名字是一個動作或者強制性的命令,那麼這個函數應該返回錯誤代 - 碼整數。如果是一個判斷,那麼函數應該返回一個“成功”布爾值。 + 如果函式的名字是一個動作或者強制性的命令,那麼這個函式應該返回錯誤代 + 碼整數。如果是一個判斷,那麼函式應該返回一個“成功”布林值。 比如, ``add work`` 是一個命令,所以 add_work() 在成功時返回 0,在失敗時返回 --EBUSY。類似的,因爲 ``PCI device present`` 是一個判斷,所以 pci_dev_present() +-EBUSY。類似的,因為 ``PCI device present`` 是一個判斷,所以 pci_dev_present() 在成功找到一個匹配的設備時應該返回 1,如果找不到時應該返回 0。 -所有 EXPORTed 函數都必須遵守這個慣例,所有的公共函數也都應該如此。私有 -(static) 函數不需要如此,但是我們也推薦這樣做。 +所有 EXPORTed 函式都必須遵守這個慣例,所有的公共函式也都應該如此。私有 +(static) 函式不需要如此,但是我們也推薦這樣做。 -返回值是實際計算結果而不是計算是否成功的標誌的函數不受此慣例的限制。通常 -他們通過返回一些正常值範圍之外的結果來表示出錯。典型的例子是返回指針的函數, +回傳值是實際計算結果而不是計算是否成功的標誌的函式不受此慣例的限制。通常 +他們透過返回一些正常值範圍之外的結果來表示出錯。典型的例子是返回指標的函式, 他們使用 NULL 或者 ERR_PTR 機制來報告錯誤。 -17) 使用布爾類型 +17) 使用布林類型 ---------------- -Linux內核布爾(bool)類型是C99 _Bool類型的別名。布爾值只能爲0或1,而對布爾的 -隱式或顯式轉換將自動將值轉換爲true或false。在使用布爾類型時 **不需要** 構造, +Linux核心布林(bool)類型是C99 _Bool類型的別名。布林值只能為0或1,而對布林的 +隱式或顯式轉換將自動將值轉換為true或false。在使用布林類型時 **不需要** 構造, 它會消除一類錯誤。 -使用布爾值時,應使用true和false定義,而不是1和0。 +使用布林值時,應使用true和false定義,而不是1和0。 -布爾函數返回類型和堆棧變量總是可以在適當的時候使用。鼓勵使用布爾來提高可讀性, -並且布爾值在存儲時通常比“int”更好。 +布林函式返回類型和堆疊變數總是可以在適當的時候使用。鼓勵使用布林來提高可讀性, +並且布林值在儲存時通常比“int”更好。 -如果緩存行佈局或值的大小很重要,請不要使用布爾,因爲其大小和對齊方式根據編譯 -的體系結構而不同。針對對齊和大小進行優化的結構體不應使用布爾。 +如果快取行佈局或值的大小很重要,請不要使用布林,因為其大小和對齊方式根據編譯 +的體系結構而不同。針對對齊和大小進行最佳化的結構體不應使用布林。 -如果一個結構體有多個true/false值,請考慮將它們合併爲具有1比特成員的位域,或使 +如果一個結構體有多個true/false值,請考慮將它們合併為具有1比特成員的位域,或使 用適當的固定寬度類型,如u8。 -類似地,對於函數參數,多個true/false值可以合併爲單個按位的“標誌”參數,如果調 -用點具有裸true/false常量,“標誌”參數通常是更具可讀性的替代方法。 +類似地,對於函式參數,多個true/false值可以合併為單個按位的“標誌”參數,如果調 +用點具有裸true/false常數,“標誌”參數通常是更具可讀性的替代方法。 -總之,在結構體和參數中有限地使用布爾可以提高可讀性。 +總之,在結構體和參數中有限地使用布林可以提高可讀性。 -18) 不要重新發明內核宏 ----------------------- +18) 不要重新發明核心巨集 +------------------------ -頭文件 include/linux/kernel.h 包含了一些宏,你應該使用它們,而不要自己寫一些 -它們的變種。比如,如果你需要計算一個數組的長度,使用這個宏 +標頭檔 include/linux/kernel.h 包含了一些巨集,你應該使用它們,而不要自己寫一些 +它們的變種。比如,如果你需要計算一個陣列的長度,使用這個巨集 .. code-block:: c @@ -938,15 +946,15 @@ Linux內核布爾(bool)類型是C99 _Bool類型的別名。布爾值只能 #define sizeof_field(t, f) (sizeof(((t*)0)->f)) -還有可以做嚴格的類型檢查的 min() 和 max() 宏,如果你需要可以使用它們。你可以 -自己看看那個頭文件裏還定義了什麼你可以拿來用的東西,如果有定義的話,你就不應 -在你的代碼裏自己重新定義。 +還有可以做嚴格的類型檢查的 min() 和 max() 巨集,如果你需要可以使用它們。你可以 +自己看看那個標頭檔裡還定義了什麼你可以拿來用的東西,如果有定義的話,你就不應 +在你的程式碼裡自己重新定義。 19) 編輯器模式行和其他需要羅嗦的事情 ------------------------------------ -有一些編輯器可以解釋嵌入在源文件裏的由一些特殊標記標明的配置信息。比如,emacs +有一些編輯器可以解釋嵌入在原始檔裡的由一些特殊標記標明的設定資訊。比如,emacs 能夠解析被標記成這樣的行: .. code-block:: c @@ -969,29 +977,29 @@ Vim 能夠解析這樣的標記: /* vim:set sw=8 noet */ -不要在源代碼中包含任何這樣的內容。每個人都有他自己的編輯器配置,你的源文件不 -應該覆蓋別人的配置。這包括有關縮進和模式配置的標記。人們可以使用他們自己定製 -的模式,或者使用其他可以產生正確的縮進的巧妙方法。 +不要在原始程式碼中包含任何這樣的內容。每個人都有他自己的編輯器設定,你的原 +始檔不應該覆蓋別人的設定。這包括有關縮排和模式設定的標記。人們可以使用他們 +自己定製的模式,或者使用其他可以產生正確的縮排的巧妙方法。 -20) 內聯彙編 +20) 行內組譯 ------------ -在特定架構的代碼中,你可能需要內聯彙編與 CPU 和平臺相關功能連接。需要這麼做時 -就不要猶豫。然而,當 C 可以完成工作時,不要平白無故地使用內聯彙編。在可能的情 -況下,你可以並且應該用 C 和硬件溝通。 +在特定架構的程式碼中,你可能需要行內組譯與 CPU 和平臺相關功能連接。需要這 +麼做時就不要猶豫。然而,當 C 可以完成工作時,不要平白無故地使用行內組譯。 +在可能的情況下,你可以並且應該用 C 和硬體溝通。 -請考慮去寫捆綁通用位元 (wrap common bits) 的內聯彙編的簡單輔助函數,別去重複 -地寫下只有細微差異內聯彙編。記住內聯彙編可以使用 C 參數。 +請考慮去寫捆綁通用位元 (wrap common bits) 的行內組譯的簡單輔助函式,別去重複 +地寫下只有細微差異行內組譯。記住行內組譯可以使用 C 參數。 -大型,有一定複雜度的彙編函數應該放在 .S 文件內,用相應的 C 原型定義在 C 頭文 -件中。彙編函數的 C 原型應該使用 ``asmlinkage`` 。 +大型,有一定複雜度的組譯函式應該放在 .S 檔案內,用相應的 C 原型定義在 C 標頭 +檔中。組譯函式的 C 原型應該使用 ``asmlinkage`` 。 -你可能需要把彙編語句標記爲 volatile,用來阻止 GCC 在沒發現任何副作用後就把它 -移除了。你不必總是這樣做,儘管,這不必要的舉動會限制優化。 +你可能需要把組譯語句標記為 volatile,用來阻止 GCC 在沒發現任何副作用後就把它 +移除了。你不必總是這樣做,儘管,這不必要的舉動會限制最佳化。 -在寫一個包含多條指令的單個內聯彙編語句時,把每條指令用引號分割而且各佔一行, -除了最後一條指令外,在每個指令結尾加上 ``\n\t`` ,讓彙編輸出時可以正確地縮進 +在寫一個包含多條指令的單個行內組譯語句時,把每條指令用引號分割而且各佔一行, +除了最後一條指令外,在每個指令結尾加上 ``\n\t`` ,讓組譯輸出時可以正確地縮排 下一條指令: .. code-block:: c @@ -1004,21 +1012,21 @@ Vim 能夠解析這樣的標記: 21) 條件編譯 ------------ -只要可能,就不要在 .c 文件裏面使用預處理條件 (#if, #ifdef);這樣做會讓代碼更難 -閱讀並且更難去跟蹤邏輯。替代方案是,在頭文件中用預處理條件提供給那些 .c 文件 -使用,再給 #else 提供一個空樁 (no-op stub) 版本,然後在 .c 文件內無條件地調用 -那些 (定義在頭文件內的) 函數。這樣做,編譯器會避免爲樁函數 (stub) 的調用生成 -任何代碼,產生的結果是相同的,但邏輯將更加清晰。 +只要可能,就不要在 .c 檔案裡面使用預處理條件 (#if, #ifdef);這樣做會讓程式 +碼更難閱讀並且更難去追蹤邏輯。替代方案是,在標頭檔中用預處理條件提供給那些 +.c 檔案使用,再給 #else 提供一個空樁 (no-op stub) 版本,然後在 .c 檔案內無 +條件地呼叫那些 (定義在標頭檔內的) 函式。這樣做,編譯器會避免為樁函式 (stub +) 的呼叫產生任何程式碼,產生的結果是相同的,但邏輯將更加清晰。 -最好傾向於編譯整個函數,而不是函數的一部分或表達式的一部分。與其放一個 ifdef -在表達式內,不如分解出部分或全部表達式,放進一個單獨的輔助函數,並應用預處理 -條件到這個輔助函數內。 +最好傾向於編譯整個函式,而不是函式的一部分或表達式的一部分。與其放一個 ifdef +在表達式內,不如分解出部分或全部表達式,放進一個單獨的輔助函式,並應用預處理 +條件到這個輔助函式內。 -如果你有一個在特定配置中,可能變成未使用的函數或變量,編譯器會警告它定義了但 -未使用,請把它標記爲 __maybe_unused 而不是將它包含在一個預處理條件中。(然而, -如果一個函數或變量總是未使用,就直接刪除它。) +如果你有一個在特定設定中,可能變成未使用的函式或變數,編譯器會警告它定義了但 +未使用,請把它標記為 __maybe_unused 而不是將它包含在一個預處理條件中。(然而, +如果一個函式或變數總是未使用,就直接刪除它。) -在代碼中,儘可能地使用 IS_ENABLED 宏來轉化某個 Kconfig 標記爲 C 的布爾 +在程式碼中,儘可能地使用 IS_ENABLED 巨集來轉化某個 Kconfig 標記為 C 的布林 表達式,並在一般的 C 條件中使用它: .. code-block:: c @@ -1027,13 +1035,13 @@ Vim 能夠解析這樣的標記: ... } -編譯器會做常量摺疊,然後就像使用 #ifdef 那樣去包含或排除代碼塊,所以這不會帶 -來任何運行時開銷。然而,這種方法依舊允許 C 編譯器查看塊內的代碼,並檢查它的正 -確性 (語法,類型,符號引用,等等)。因此,如果條件不滿足,代碼塊內的引用符號就 -不存在時,你還是必須去用 #ifdef。 +編譯器會做常數摺疊,然後就像使用 #ifdef 那樣去包含或排除程式碼塊,所以這不 +會帶來任何執行時開銷。然而,這種方法依舊允許 C 編譯器查看塊內的程式碼,並 +檢查它的正確性 (語法,類型,符號引用,等等)。因此,如果條件不滿足,程式碼 +塊內的引用符號就不存在時,你還是必須去用 #ifdef。 在任何有意義的 #if 或 #ifdef 塊的末尾 (超過幾行的),在 #endif 同一行的後面寫下 -註解,註釋這個條件表達式。例如: +註解,註解這個條件表達式。例如: .. code-block:: c @@ -1052,7 +1060,7 @@ ISBN 0-13-110362-8 (平裝), 0-13-110370-9 (精裝). .. note:: - 《C程序設計語言(第2版)》 + 《C程式設計語言(第2版)》 作者:[美] Brian W. Kernighan / [美] Dennis M. Ritchie 譯者:徐寶文 / 李志 / 尤晉元(審校) 出版社:機械工業出版社,2019 @@ -1065,23 +1073,23 @@ ISBN 0-201-61586-X. .. note:: - 《程序設計實踐》 + 《程式設計實踐》 作者:[美] Brian W. Kernighan / [美] Rob Pike 出版社:機械工業出版社,2005 ISBN:9787111091578 - 《程序設計實踐》 + 《程式設計實踐》 作者:[美] Brian W. Kernighan / Rob Pike 譯者:裘宗燕 出版社:機械工業出版社,2000 ISBN:9787111075738 -GNU 手冊 - 遵循 K&R 標準和此文本 - cpp, gcc, gcc internals and indent, +GNU 手冊 - 遵循 K&R 標準和此文字 - cpp, gcc, gcc internals and indent, 都可以從 https://www.gnu.org/manual/ 找到 WG14 是 C 語言的國際標準化工作組,URL: http://www.open-std.org/JTC1/SC22/WG14/ -內核文檔 Documentation/process/coding-style.rst, +核心文件 Documentation/process/coding-style.rst, 作者 greg@kroah.com 發表於 OLS 2002: http://www.kroah.com/linux/talks/ols_2002_kernel_codingstyle_talk/html/ From 41f9ea6798398ff7330b9b7f09dc242d0c5fd67e Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:41 +0800 Subject: [PATCH 0578/2286] docs/zh_TW: process: localize terminology in stable-kernel-rules.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retranslate from the current English text, which was heavily restructured upstream (three submission options, stable tag variants, tree list), and localize mainland terms to Taiwanese Mandarin (內核→核心, 隊列→佇列, 數據→資料, ...). update to commit 10466b17af65 ("docs: stable-kernel-rules: fix typo sent->send") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/stable-kernel-rules.rst | 256 ++++++++++++++---- 1 file changed, 208 insertions(+), 48 deletions(-) diff --git a/Documentation/translations/zh_TW/process/stable-kernel-rules.rst b/Documentation/translations/zh_TW/process/stable-kernel-rules.rst index 2f8f064f86294b..7d286ad54f2eae 100644 --- a/Documentation/translations/zh_TW/process/stable-kernel-rules.rst +++ b/Documentation/translations/zh_TW/process/stable-kernel-rules.rst @@ -6,7 +6,7 @@ :Original: :ref:`Documentation/process/stable-kernel-rules.rst ` -如果想評論或更新本文的內容,請直接聯繫原文檔的維護者。如果你使用英文 +如果想評論或更新本文的內容,請直接聯繫原文件的維護者。如果你使用英文 交流有困難的話,也可以向中文版維護者求助。如果本翻譯更新不及時或者翻 譯存在問題,請聯繫中文版維護者:: @@ -16,53 +16,213 @@ - 李陽 Li Yang - Kangkai Yin - 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + - 葉宸佑 Chen-Yu Yeh -所有你想知道的事情 - 關於linux穩定版發佈 -======================================== - -關於Linux 2.6穩定版發佈,所有你想知道的事情。 - -關於哪些類型的補丁可以被接收進入穩定版代碼樹,哪些不可以的規則: ----------------------------------------------------------------- - - - 必須是顯而易見的正確,並且經過測試的。 - - 連同上下文,不能大於100行。 - - 必須只修正一件事情。 - - 必須修正了一個給大家帶來麻煩的真正的bug(不是“這也許是一個問題...” - 那樣的東西)。 - - 必須修正帶來如下後果的問題:編譯錯誤(對被標記爲CONFIG_BROKEN的例外), - 內核崩潰,掛起,數據損壞,真正的安全問題,或者一些類似“哦,這不 - 好”的問題。簡短的說,就是一些致命的問題。 - - 沒有“理論上的競爭條件”,除非能給出競爭條件如何被利用的解釋。 - - 不能存在任何的“瑣碎的”修正(拼寫修正,去掉多餘空格之類的)。 - - 必須被相關子系統的維護者接受。 - - 必須遵循Documentation/translations/zh_CN/process/submitting-patches.rst裏的規則。 - -向穩定版代碼樹提交補丁的過程: ------------------------------- - - - 在確認了補丁符合以上的規則後,將補丁發送到stable@vger.kernel.org。 - - 如果補丁被接受到隊列裏,發送者會收到一個ACK回覆,如果沒有被接受,收 - 到的是NAK回覆。回覆需要幾天的時間,這取決於開發者的時間安排。 - - 被接受的補丁會被加到穩定版本隊列裏,等待其他開發者的審查。 - - 安全方面的補丁不要發到這個列表,應該發送到security@kernel.org。 - -審查週期: ----------- +所有你想知道的事情 - 關於Linux -stable 版本發布 +=============================================== + +關於哪些類型的補丁會被接收進入 "-stable" 樹、哪些不會被接收的規則: + +- 該補丁或一個等效的修復必須已經存在於Linux主線(上游)。 +- 它必須是顯而易見正確的,並且經過測試的。 +- 連同上下文,它不能大於100行。 +- 它必須遵循 + :ref:`Documentation/process/submitting-patches.rst ` + 裡的規則。 +- 它必須要麼修復一個困擾人們的真實的缺陷,要麼只是添加一個裝置ID。 + 對於前者,詳細來說: + + - 它修復的問題,像是oops、當機、資料損壞、真實的安全問題、硬體怪癖 + (hardware quirk)、建置錯誤(但不包括標記為CONFIG_BROKEN的東西), + 或者一些“喔,這可不好”之類的問題。 + - 發行版核心的使用者所報告的嚴重問題,如果修復的是顯著的效能或互動性 + 問題,也可以被考慮。由於這些修復不那麼顯而易見,並且有較高的風險引入 + 不易察覺的迴歸,它們應該只由發行版核心的維護者提交,並附上補充說明, + 給出指向bugzilla條目(如果存在)的連結,以及關於使用者可見影響的額外 + 資訊。 + - 不接受“這可能是一個問題...”之類的東西,比如“理論上的競爭條件”,除非 + 同時提供了缺陷如何被利用的解釋。 + - 不接受對使用者沒有好處的“瑣碎”修復(拼寫更改、空白清理等)。 + + +向 -stable 樹提交補丁的流程 +--------------------------- + +.. note:: + + 安全補丁不應(只)由 -stable 審查流程處理,而應遵循 + :ref:`Documentation/process/security-bugs.rst ` + 的流程。 + +要向 -stable 樹提交更改,有三個選項: + +1. 在你隨後提交到主線的補丁的描述中,加上一個“stable標籤”。 +2. 請求穩定版團隊撿取一個已經合併到主線的補丁。 +3. 向穩定版團隊提交一個與已合併到主線的更改等效的補丁。 + +以下小節更詳細地描述每個選項。 + +:ref:`tw_option_1` 是 **強烈** 推薦的做法,它最簡單也最常見。 +:ref:`tw_option_2` 主要用於提交時沒有考慮向後移植的更改。 :ref:`tw_option_3` +是前兩個選項之外的替代方案,用於已合併到主線的補丁需要調整才能套用到較舊系列 +的情況(例如由於API變化)。 + +使用選項2或3時,可以要求將你的更改包含到特定的穩定版系列中。這麼做時,要確保 +該修復或等效修復適用於、已提交到、或已經存在於所有仍在維護的較新穩定版樹中。 +這是為了防止使用者日後更新時可能遇到的迴歸,例如一個合併於5.19-rc1的修復被 +向後移植到5.10.y,卻沒有移植到5.15.y。 + +.. _tw_option_1: + +選項1 +***** + +要讓你提交到主線的補丁之後被自動撿取到穩定版樹,請在簽署(sign-off)區加上 +這個標籤:: + + Cc: stable@vger.kernel.org + +當修復未公開的漏洞時,請改用 ``Cc: stable@kernel.org``:它可以降低透過 +'git send-email' 意外將修復公開的機會,因為發送到該地址的郵件不會被投遞到 +任何地方。 + +補丁合併到主線後,它將被套用到穩定版樹,而無需作者或子系統維護者再做任何 +事情。 + +要向穩定版團隊發送額外的指示,可使用shell風格的行內註解來傳遞任意的或預定義 +的備註: + +* 指明揀選(cherry pick)所需的額外補丁前置條件:: + + Cc: # 3.3.x: a1f84a3: sched: Check for idle + Cc: # 3.3.x: 1b9508f: sched: Rate-limit newidle + Cc: # 3.3.x: fd21073: sched: Fix affinity logic + Cc: # 3.3.x + Signed-off-by: Ingo Molnar + + 上面標籤序列的含義為:: + + git cherry-pick a1f84a3 + git cherry-pick 1b9508f + git cherry-pick fd21073 + git cherry-pick + + 注意,對於一個補丁系列,你不必把系列中已有的補丁列為前置條件。例如,如果 + 你有如下補丁系列:: + + patch1 + patch2 + + 其中patch2依賴patch1,如果你已經把patch1標記為穩定版收錄,就不必再把它列 + 為patch2的前置條件。 + +* 指出核心版本的前置條件:: + + Cc: # 3.3.x + + 該標籤的含義為:: + + git cherry-pick + + 對每個從指定版本開始的“-stable”樹執行。 + + 注意,如果穩定版團隊可以從Fixes:標籤推導出適當的版本,則無需這樣標記。 + +* 延遲補丁的撿取:: - - 當穩定版的維護者決定開始一個審查週期,補丁將被髮送到審查委員會,以 - 及被補丁影響的領域的維護者(除非提交者就是該領域的維護者)並且抄送 - 到linux-kernel郵件列表。 - - 審查委員會有48小時的時間,用來決定給該補丁回覆ACK還是NAK。 - - 如果委員會中有成員拒絕這個補丁,或者linux-kernel列表上有人反對這個 - 補丁,並提出維護者和審查委員會之前沒有意識到的問題,補丁會從隊列中 - 丟棄。 - - 在審查週期結束的時候,那些得到ACK回應的補丁將會被加入到最新的穩定版 - 發佈中,一個新的穩定版發佈就此產生。 - - 安全性補丁將從內核安全小組那裏直接接收到穩定版代碼樹中,而不是通過 - 通常的審查週期。請聯繫內核安全小組以獲得關於這個過程的更多細節。 - -審查委員會: ------------- - - 由一些自願承擔這項任務的內核開發者,和幾個非志願的組成。 + Cc: # after -rc3 + +* 指出已知的問題:: + + Cc: # see patch description, needs adjustments for <= 6.3 + +此外,stable標籤還有一種變體,可以讓穩定版團隊的向後移植工具(例如AUTOSEL +或尋找含有'Fixes:'標籤的提交的腳本)忽略一個更改:: + + Cc: # reason goes here, and must be present + +.. _tw_option_2: + +選項2 +***** + +如果補丁已經合併到主線,請發送一封電子郵件到stable@vger.kernel.org,內容 +包含補丁的標題、提交ID、你認為它應該被套用的原因,以及你希望它被套用到哪些 +核心版本。 + +.. _tw_option_3: + +選項3 +***** + +在確認補丁符合上述規則後,將補丁發送到stable@vger.kernel.org,並註明你希望 +它被套用到的核心版本。這麼做時,你必須在你所提交補丁的更改日誌中註明上游的 +提交ID,並在提交說明文字上方以單獨一行標註,像這樣:: + + commit upstream. + +或者:: + + [ Upstream commit ] + +如果提交的補丁與原始的上游補丁有出入(例如因為需要為較舊的API調整),則必須 +在補丁描述中非常清楚地記錄並說明理由。 + + +提交之後 +-------- + +當補丁被接受進入佇列後,發送者會收到一個ACK;如果補丁被拒絕,則會收到NAK。 +這個回覆可能需要幾天時間,取決於穩定版團隊成員的日程安排。 + +如果被接受,補丁將被加入 -stable 佇列,供其他開發人員和相關子系統維護者 +審查。 + + +審查週期 +-------- + +- 當 -stable 維護者決定進行審查週期時,補丁將被發送到審查委員會,以及補丁 + 影響領域的維護者(除非提交者就是該領域的維護者),並抄送到linux-kernel + 郵件列表。 +- 審查委員會有48小時的時間對補丁作出ACK或NAK。 +- 如果補丁被委員會成員拒絕,或者linux-kernel列表上的成員反對這個補丁並提出 + 了維護者和委員會成員沒有意識到的問題,補丁將從佇列中移除。 +- 通過ACK的補丁將作為釋出候選(-rc)版本的一部分再次發布,以供開發人員和 + 測試人員測試。 +- 通常只會產生一個 -rc 版本,然而如果存在未解決的問題,某些補丁可能會被修改 + 或移除,或者有額外的補丁進入佇列。此後會發布更多的 -rc 版本並加以測試, + 直到不再發現問題為止。 +- 可以在郵件列表上發送帶有任何所需測試資訊的“Tested-by:”郵件來回覆 -rc + 版本。“Tested-by:”標籤將被收集並加入到發布提交中。 +- 在審查週期結束時,新的 -stable 版本將被發布,其中包含所有排隊的、經過測試 + 的補丁。 +- 安全補丁將由核心安全團隊直接接受進入 -stable 樹,而不經過正常的審查週期。 + 關於這一流程的更多細節,請聯繫核心安全團隊。 + + +樹 +-- + +- 已完成版本和進行中版本的補丁佇列可以在以下位置找到: + + https://git.kernel.org/pub/scm/linux/kernel/git/stable/stable-queue.git + +- 所有穩定版核心的最終定版並打上標籤的版本,可以在以下位置的每個版本各自的 + 分支中找到: + + https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git + +- 所有穩定版核心版本的釋出候選版本可以在以下位置找到: + + https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable-rc.git/ + + .. warning:: + -stable-rc 樹是stable-queue樹在某個時間點的快照,會頻繁變動,因此會經常 + 被rebase。它只應被用於測試目的(例如供CI系統使用)。 + + +審查委員會 +---------- +- 審查委員會由一些自願承擔這項任務的核心開發人員組成,還有幾位不是自願的。 From 3ab588fbcd1e44af68e004ecb0f4478ab2438379 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:42 +0800 Subject: [PATCH 0579/2286] docs/zh_TW: process: localize terminology in 5.Posting.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 代碼→程式碼, 通過→透過, ...) and sync with the English original: add the testing/ KUnit advice, the Fixes:/Link:/Closes: tag paragraphs, the Suggested-by: tag, and the updated rules about tagging other people. Also point cross-references at the zh_TW translations instead of zh_CN. update to commit 0a83293322fd ("doc: development-process: add notice on testing") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../translations/zh_TW/process/5.Posting.rst | 235 ++++++++++-------- 1 file changed, 136 insertions(+), 99 deletions(-) diff --git a/Documentation/translations/zh_TW/process/5.Posting.rst b/Documentation/translations/zh_TW/process/5.Posting.rst index 38f3a6d618ebfc..698cde5cd06de9 100644 --- a/Documentation/translations/zh_TW/process/5.Posting.rst +++ b/Documentation/translations/zh_TW/process/5.Posting.rst @@ -12,73 +12,78 @@ 吳想成 Wu XiangCheng 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh .. _tw_development_posting: -發佈補丁 +發布補丁 ======== -您的工作遲早會準備好提交給社區進行審查,並最終包含到主線內核中。毫不稀奇, -內核開發社區已經發展出一套用於發佈補丁的約定和過程;遵循這些約定和過程將使 -參與其中的每個人的生活更加輕鬆。本文檔試圖描述這些約定的部分細節;更多信息 -也可在以下文檔中找到 -:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` -和 :ref:`Documentation/translations/zh_CN/process/submit-checklist.rst `。 +您的工作遲早會準備好提交給社群進行審查,並最終包含到主線核心中。毫不稀奇, +核心開發社群已經發展出一套用於發布補丁的約定和過程;遵循這些約定和過程將使 +參與其中的每個人的生活更加輕鬆。本文件試圖描述這些約定的部分細節;更多資訊 +也可在以下文件中找到 +:ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` +和 +:ref:`Documentation/translations/zh_TW/process/submit-checklist.rst `。 何時寄送 -------- -在補丁完全“準備好”之前,避免發佈補丁是一種持續的誘惑。對於簡單的補丁,這 -不是問題。但是如果正在完成的工作很複雜,那麼在工作完成之前從社區獲得反饋就 -可以獲得很多好處。因此,您應該考慮發佈正在進行的工作,甚至維護一個可用的Git +在補丁完全“準備好”之前,避免發布補丁是一種持續的誘惑。對於簡單的補丁,這 +不是問題。但是如果正在完成的工作很複雜,那麼在工作完成之前從社群獲得反饋就 +可以獲得很多好處。因此,您應該考慮發布正在進行的工作,甚至維護一個可用的Git 樹,以便感興趣的開發人員可以隨時趕上您的工作。 -當發佈中有尚未準備好被包含的代碼,最好在發佈中說明。還應提及任何有待完成的 -主要工作和任何已知問題。很少有人會願意看那些被認爲是半生不熟的補丁,但是 -那些願意的人會帶着他們的點子來一起幫助你把工作推向正確的方向。 +當發布中有尚未準備好被包含的程式碼,最好在發布中說明。還應提及任何有待完成的 +主要工作和任何已知問題。很少有人會願意看那些被認為是半生不熟的補丁,但是 +那些願意的人會帶著他們的點子來一起幫助你把工作推向正確的方向。 -創建補丁之前 +建立補丁之前 ------------ -在考慮將補丁發送到開發社區之前,有許多事情應該做。包括: +在考慮將補丁發送到開發社群之前,有許多事情應該做。包括: - - 儘可能地測試代碼。利用內核的調試工具,確保內核使用了所有可能的配置選項組合 - 進行構建,使用交叉編譯器爲不同的體系結構進行構建等。 + - 儘可能地測試程式碼。利用核心的除錯工具,確保核心使用了所有可能的設定選 + 項組合進行建置,使用交叉編譯器為不同的架構進行建置等。添加測試,最好使 + 用現有的測試框架(如KUnit),並將其作為補丁系列中單獨的一員(關於補丁系 + 列,見下一節)。注意,對某些子系統而言這可能是強制性的。例如,函式庫函 + 式(位於lib/下)幾乎在所有地方被廣泛使用,應當有適當的測試。 - - 確保您的代碼符合內核代碼風格指南。 + - 確保您的程式碼符合核心程式碼風格指南。 - - 您的更改是否具有性能影響?如果是這樣,您應該運行基準測試來顯示您的變更的 + - 您的更改是否具有效能影響?如果是這樣,您應該執行基準測試來顯示您的變更的 影響(或好處);結果的摘要應該包含在補丁中。 - - 確保您有權發佈代碼。如果這項工作是爲僱主完成的,僱主對這項工作具有所有權, - 並且必須同意根據GPL對其進行發佈。 + - 確保您有權發布程式碼。如果這項工作是為僱主完成的,僱主對這項工作具有所 + 有權,並且必須同意根據GPL對其進行發布。 -一般來說,在發佈代碼之前進行一些額外的思考,幾乎總是能在短時間內得到回報。 +一般來說,在發布程式碼之前進行一些額外的思考,幾乎總是能在短時間內得到回報。 補丁準備 -------- -準備補丁發佈的工作量可能很驚人,但在此嘗試節省時間通常是不明智的,即使在短期 +準備補丁發布的工作量可能很驚人,但在此嘗試節省時間通常是不明智的,即使在短期 內亦然。 -必須針對內核的特定版本準備補丁。一般來說,補丁應該基於Linus的Git樹中的當前 -主線。當以主線爲基礎時,請從一個衆所周知的發佈點開始——如穩定版本或 -rc -版本發佈點——而不是在一個任意的主線分支點。 +必須針對核心的特定版本準備補丁。一般來說,補丁應該基於Linus的Git樹中的當前 +主線。當以主線為基礎時,請從一個衆所周知的發布點開始——如穩定版本或 -rc +版本發布點——而不是在一個任意的主線分支點。 -也可能需要針對-mm、linux-next或子系統樹生成版本,以便於更廣泛的測試和審查。 +也可能需要針對-mm、linux-next或子系統樹產生版本,以便於更廣泛的測試和審查。 根據補丁的區域以及其他地方的情況,針對其他樹建立的補丁可能需要大量的工作來 解決衝突和處理API更改。 -只有最簡單的更改才應格式化爲單個補丁;其他所有更改都應作爲一系列邏輯更改進行。 -分割補丁是一門藝術;一些開發人員花了很長時間來弄清楚如何按照社區期望的方式來 +只有最簡單的更改才應格式化為單個補丁;其他所有更改都應作為一系列邏輯更改進行。 +分割補丁是一門藝術;一些開發人員花了很長時間來弄清楚如何按照社群期望的方式來 分割。不過,這些經驗法則也許有幫助: - - 您發佈的補丁系列幾乎肯定不會是開發過程中版本控制系統中的一系列更改。相反, + - 您發布的補丁系列幾乎肯定不會是開發過程中版本控制系統中的一系列更改。相反, 需要對您所做更改的最終形式加以考慮,然後以有意義的方式進行拆分。開發人員對 離散的、自包含的更改感興趣,而不是您創造這些更改的原始路徑。 - - 每個邏輯上獨立的變更都應該格式化爲單獨的補丁。這些更改可以是小的(如“向 - 此結構體添加字段”)或大的(如添加一個重要的新驅動程序),但它們在概念上 + - 每個邏輯上獨立的變更都應該格式化為單獨的補丁。這些更改可以是小的(如“向 + 此結構體添加欄位”)或大的(如添加一個重要的新驅動程式),但它們在概念上 應該是小的,並且可以在一行內簡述。每個補丁都應該做一個特定的、可以單獨 檢查並驗證它所做的事情的更改。 @@ -86,132 +91,164 @@ 一個補丁修復了一個關鍵的安全漏洞,又重新排列了一些結構,還重新格式化了代 碼,那麼它很有可能會被忽略,從而導致重要的修復丟失。 - - 每個補丁都應該能創建一個可以正確地構建和運行的內核;如果補丁系列在中間被 - 斷開,那麼結果仍應是一個正常工作的內核。部分應用一系列補丁是使用 - “git bisct”工具查找回歸的一個常見場景;如果結果是一個損壞的內核,那麼將使 - 那些從事追蹤問題的高尚工作的開發人員和用戶的生活更加艱難。 + - 每個補丁都應該能建立一個可以正確地建置和執行的核心;如果補丁系列在中間被 + 斷開,那麼結果仍應是一個正常工作的核心。部分應用一系列補丁是使用 + “git bisct”工具查找回歸的一個常見場景;如果結果是一個損壞的核心,那麼將使 + 那些從事追蹤問題的高尚工作的開發人員和使用者的生活更加艱難。 - - 不要過分分割。一位開發人員曾經將一組針對單個文件的編輯分成500個單獨的補丁 - 發佈,這並沒有使他成爲內核郵件列表中最受歡迎的人。一個補丁可以相當大, + - 不要過分分割。一位開發人員曾經將一組針對單一檔案的編輯分成500個單獨的補丁 + 發布,這並沒有使他成為核心郵件列表中最受歡迎的人。一個補丁可以相當大, 只要它仍然包含一個單一的 *邏輯* 變更。 - - 用一系列補丁添加一個全新的基礎設施,但是該設施在系列中的最後一個補丁啓用 + - 用一系列補丁添加一個全新的基礎設施,但是該設施在系列中的最後一個補丁啟用 整個變更之前不能使用,這看起來很誘人。如果可能的話,應該避免這種誘惑; 如果這個系列增加了迴歸,那麼二分法將指出最後一個補丁是導致問題的補丁, - 即使真正的bug在其他地方。只要有可能,添加新代碼的補丁程序應該立即激活該 - 代碼。 + 即使真正的bug在其他地方。只要有可能,添加新程式碼的補丁程式應該立即激活該 + 程式碼。 -創建完美補丁系列的工作可能是一個令人沮喪的過程,在完成“真正的工作”之後需要 +建立完美補丁系列的工作可能是一個令人沮喪的過程,在完成“真正的工作”之後需要 花費大量的時間和思考。但是如果做得好,花費的時間就是值得的。 補丁格式和更改日誌 ------------------ -所以現在你有了一系列完美的補丁可以發佈,但是這項工作還沒有完成。每個補丁都 -需要被格式化成一條消息,以快速而清晰地將其目的傳達到世界其他地方。爲此, +所以現在你有了一系列完美的補丁可以發布,但是這項工作還沒有完成。每個補丁都 +需要被格式化成一條訊息,以快速而清晰地將其目的傳達到世界其他地方。為此, 每個補丁將由以下部分組成: - - 可選的“From”行,表明補丁作者。只有當你通過電子郵件發送別人的補丁時,這一行 - 纔是必須的,但是爲防止疑問加上它也不會有什麼壞處。 + - 可選的“From”行,表明補丁作者。只有當你透過電子郵件發送別人的補丁時,這一行 + 才是必須的,但是為防止疑問加上它也不會有什麼壞處。 - - 一行描述,說明補丁的作用。對於在沒有其他上下文的情況下看到該消息的讀者來說, - 該消息應足以確定修補程序的範圍;此行將顯示在“short form(簡短格式)”變更 - 日誌中。此消息通常需要先加上子系統名稱前綴,然後是補丁的目的。例如: + - 一行描述,說明補丁的作用。對於在沒有其他上下文的情況下看到該訊息的讀者 + 來說,該訊息應足以確定修補程式的範圍;此行將顯示在“short form(簡短格式)” + 變更日誌中。此訊息通常需要先加上子系統名稱前綴,然後是補丁的目的。例如: :: gpio: fix build on CONFIG_GPIO_SYSFS=n - - 一行空白,後接補丁內容的詳細描述。此描述可以是任意需要的長度;它應該說明補丁 - 的作用以及爲什麼它應該應用於內核。 + - 一行空白,後接補丁內容的詳細描述。此描述可以是任意需要的長度;它應該說 + 明補丁的作用以及為什麼它應該應用於核心。 - 一個或多個標記行,至少有一個由補丁作者的 Signed-off-by 簽名。標記將在下面 詳細描述。 -上面的項目一起構成補丁的變更日誌。寫一則好的變更日誌是一門至關重要但常常被 +上面的專案一起構成補丁的變更日誌。寫一則好的變更日誌是一門至關重要但常常被 忽視的藝術;值得花一點時間來討論這個問題。當你編寫變更日誌時,你應該記住有 很多不同的人會讀你的話。其中包括子系統維護人員和審查人員,他們需要決定是否 -應該合併補丁,分銷商和其他維護人員試圖決定是否應該將補丁反向移植到其他內核, -缺陷搜尋人員想知道補丁是否導致他們正在追查的問題,以及想知道內核如何變化的 -用戶等等。一個好的變更日誌以最直接和最簡潔的方式向所有這些人傳達所需的信息。 +應該合併補丁,分銷商和其他維護人員試圖決定是否應該將補丁反向移植到其他核心, +缺陷搜尋人員想知道補丁是否導致他們正在追查的問題,以及想知道核心如何變化的 +使用者等等。一個好的變更日誌以最直接和最簡潔的方式向所有這些人傳達所需的資訊。 在結尾,總結行應該描述變更的影響和動機,以及在一行約束條件下可能發生的變化。 -然後,詳細的描述可以詳述這些主題,並提供任何需要的附加信息。如果補丁修復了 +然後,詳細的描述可以詳述這些主題,並提供任何需要的附加資訊。如果補丁修復了 一個缺陷,請引用引入該缺陷的提交(如果可能,請在引用提交時同時提供其 id 和 標題)。如果某個問題與特定的日誌或編譯器輸出相關聯,請包含該輸出以幫助其他 -人搜索同一問題的解決方案。如果更改是爲了支持以後補丁中的其他更改,那麼應當 +人搜索同一問題的解決方案。如果更改是為了支援以後補丁中的其他更改,那麼應當 說明。如果更改了內部API,請詳細說明這些更改以及其他開發人員應該如何響應。 -一般來說,你越把自己放在每個閱讀你變更日誌的人的位置上,變更日誌(和內核 -作爲一個整體)就越好。 +一般來說,你越把自己放在每個閱讀你變更日誌的人的位置上,變更日誌(和核心 +作為一個整體)就越好。 -不需要說,變更日誌是將變更提交到版本控制系統時使用的文本。接下來將是: +不需要說,變更日誌是將變更提交到版本控制系統時使用的文字。接下來將是: - - 補丁本身,採用統一的(“-u”)補丁格式。使用“-p”選項來diff將使函數名與 + - 補丁本身,採用統一的(“-u”)補丁格式。使用“-p”選項來diff將使函式名與 更改相關聯,從而使結果補丁更容易被其他人讀取。 -上面提到的標籤(tag)用於描述各種開發人員如何與這個補丁的開發相關聯。 -:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` -文檔中對它們進行了詳細描述;下面是一個簡短的總結。每一行的格式如下: +前面已簡要提到的標籤(tag)用於提供補丁如何產生的線索。 +:ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` +文件中對它們進行了詳細描述;下面是一個簡短的總結。 -:: +有一種標籤用於引用引入了本補丁所修復問題的較早提交:: + + Fixes: 1f2e3d4c5b6a ("The first line of the commit specified by the first 12 characters of its SHA-1 ID") + +另一種標籤用於連結帶有額外背景或細節的網頁,例如導致此補丁的較早討論,或 +一份由此補丁實作的規格文件:: + + Link: https://example.com/somewhere.html optional-other-stuff + +依照企鵝老大(Chief Penguin)的指導,只有當Link:標籤指向的有用資訊無法在 +提交本身中找到時,才應該把它加到提交中。 + +如果URL指向此補丁所修復的公開缺陷報告,請改用“Closes:”標籤:: + + Closes: https://example.com/issues/1234 optional-other-stuff + +一些缺陷追蹤系統能夠在帶有此類標籤的提交被套用時自動關閉問題。一些監控 +郵件列表的機器人也會追蹤這類標籤並採取相應動作。私有缺陷追蹤系統和無效 +的URL是被禁止的。 + +另一種標籤用於記錄誰參與了補丁的開發。它們每個都使用如下格式:: tag: Full Name optional-other-stuff 常用的標籤有: - - Signed-off-by: 這是一個開發人員的證明,證明他或她有權提交補丁以包含到內核 - 中。這表明同意開發者來源認證協議,其全文見 - :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` + - Signed-off-by: 這是一個開發人員的證明,證明他或她有權提交補丁以包含到核 + 心中。這表明同意開發者來源認證協議,其全文見 + :ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` 如果沒有合適的簽字,則不能合併到主線中。 - - Co-developed-by: 聲明補丁是由多個開發人員共同創建的;當幾個人在一個補丁上 - 工作時,它用於給出共同作者(除了 From: 所給出的作者之外)。由於 - Co-developed-by: 表示作者身份,所以每個共同開發人,必須緊跟在相關合作作者 - 的Signed-off-by之後。具體內容和示例見以下文件 - :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` + - Co-developed-by: 聲明補丁是由多個開發人員共同建立的;當幾個人在一個補丁 + 上工作時,它用於給出共同作者(除了 From: 所給出的作者之外)。由於 + Co-developed-by: 表示作者身份,所以每個共同開發人,必須緊跟在相關合作作 + 者的Signed-off-by之後。具體內容和範例見以下文件 + :ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` - - Acked-by: 表示另一個開發人員(通常是相關代碼的維護人員)同意補丁適合包含 - 在內核中。 + - Acked-by: 表示另一個開發人員(通常是相關程式碼的維護人員)同意補丁適合包含 + 在核心中。 - Tested-by: 聲明某人已經測試了補丁並確認它可以工作。 - - Reviewed-by: 表示某開發人員已經審查了補丁的正確性;有關詳細信息,請參閱 - :ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` + - Reviewed-by: 表示某開發人員已經審查了補丁的正確性;有關詳細資訊,請參閱 + :ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` + + - Reported-by: 指定報告此補丁修復的問題的使用者;此標籤用於向測試我們的 + 程式碼並在發現問題時告知我們的人們(他們常常沒有得到應有的重視)表示 + 感謝。注意,此標籤後面應跟隨指向該報告的Closes:標籤,除非該報告無法在 + 網路上取得。如果補丁只修復了所報告問題的一部分,可以用Link:標籤代替 + Closes:。 - - Reported-by: 指定報告此補丁修復的問題的用戶;此標記用於表示感謝。 + - Suggested-by: 標籤表明補丁的想法是由被指名者建議的,確保其想法獲得 + 讚譽。希望這能激勵他們在未來繼續幫助我們。 - Cc:指定某人收到了補丁的副本,並有機會對此發表評論。 -在補丁中添加標籤時要小心:只有Cc:才適合在沒有指定人員明確許可的情況下添加。 +在補丁中添加上述標籤時要小心:除了Cc:、Reported-by:和Suggested-by:之外, +所有標籤都需要被指名者的明確許可。對於這三個標籤,如果根據lore存檔或提交 +歷史,該人曾以該名字和電子郵件地址對Linux核心做出過貢獻,那麼隱含的許可 +就足夠了——並且對於Reported-by:和Suggested-by:,報告或建議必須是公開作出 +的。注意,就此而言bugzilla.kernel.org是公開場所,但其中使用的電子郵件地址 +是私密的;因此不要在標籤中暴露它們,除非該人在先前的貢獻中使用過。 寄送補丁 -------- 在寄送補丁之前,您還需要注意以下幾點: - - 您確定您的郵件發送程序不會損壞補丁嗎?被郵件客戶端更改空白或修飾了行的補丁 + - 您確定您的郵件發送程式不會損壞補丁嗎?被郵件客戶端更改空白或修飾了行的補丁 無法被另一端接受,並且通常不會進行任何詳細檢查。如果有任何疑問,先把補丁寄 給你自己,讓你自己確定它是完好無損的。 - :ref:`Documentation/translations/zh_CN/process/email-clients.rst ` + :ref:`Documentation/translations/zh_TW/process/email-clients.rst ` 提供了一些有用的提示,可以讓特定的郵件客戶端正常發送補丁。 - - 你確定你的補丁沒有荒唐的錯誤嗎?您應該始終通過scripts/checkpatch.pl檢查 - 補丁程序,並解決它提出的問題。請記住,checkpatch.pl,雖然體現了對內核補丁 + - 你確定你的補丁沒有荒唐的錯誤嗎?您應該始終透過scripts/checkpatch.pl檢查 + 補丁程式,並解決它提出的問題。請記住,checkpatch.pl,雖然體現了對核心補丁 應該是什麼樣的大量思考,但它並不比您聰明。如果修復checkpatch.pl給的問題會 - 使代碼變得更糟,請不要這樣做。 + 使程式碼變得更糟,請不要這樣做。 -補丁應始終以純文本形式發送。請不要將它們作爲附件發送;這使得審閱者在答覆中更難 -引用補丁的部分。相反,只需將補丁直接放到您的消息中。 +補丁應始終以純文字形式發送。請不要將它們作為附件發送;這使得審閱者在答覆中更難 +引用補丁的部分。相反,只需將補丁直接放到您的訊息中。 -寄出補丁時,重要的是將副本發送給任何可能感興趣的人。與其他一些項目不同,內核 -鼓勵人們甚至錯誤地發送過多的副本;不要假定相關人員會看到您在郵件列表中的發佈。 +寄出補丁時,重要的是將副本發送給任何可能感興趣的人。與其他一些專案不同,核心 +鼓勵人們甚至錯誤地發送過多的副本;不要假定相關人員會看到您在郵件列表中的發布。 尤其是,副本應發送至: - - 受影響子系統的維護人員。如前所述,維護人員文件是查找這些人員的首選地方。 + - 受影響子系統的維護人員。如前所述,MAINTAINERS檔案是查找這些人員的首選地方。 - - 其他在同一領域工作的開發人員,尤其是那些現在可能在那裏工作的開發人員。使用 - git查看還有誰修改了您正在處理的文件,這很有幫助。 + - 其他在同一領域工作的開發人員,尤其是那些現在可能在那裡工作的開發人員。使用 + git查看還有誰修改了您正在處理的檔案,這很有幫助。 - 如果您對某錯誤報告或功能請求做出響應,也可以抄送原始發送人。 @@ -221,9 +258,9 @@ 補丁副本也應發到stable@vger.kernel.org 。另外,在補丁本身的標籤中添加一個 “Cc: stable@vger.kernel.org”;這將使穩定版團隊在修復進入主線時收到通知。 -當爲一個補丁選擇接收者時,最好清楚你認爲誰最終會接受這個補丁並將其合併。雖然 +當為一個補丁選擇接收者時,最好清楚你認為誰最終會接受這個補丁並將其合併。雖然 可以將補丁直接發給Linus Torvalds並讓他合併,但通常情況下不會這樣做。Linus很 -忙,並且有子系統維護人員負責監視內核的特定部分。通常您會希望維護人員合併您的 +忙,並且有子系統維護人員負責監視核心的特定部分。通常您會希望維護人員合併您的 補丁。如果沒有明顯的維護人員,Andrew Morton通常是最後的補丁接收者。 補丁需要好的主題行。補丁主題行的規範格式如下: @@ -235,12 +272,12 @@ 其中“nn”是補丁的序號,“mm”是系列中補丁的總數,“subsys”是受影響子系統的 名稱。當然,一個單獨的補丁可以省略nn/mm。 -如果您有一系列重要的補丁,那麼通常發送一個簡介作爲第〇部分。不過,這個約定 -並沒有得到普遍遵循;如果您使用它,請記住簡介中的信息不會進入內核變更日誌。 -因此,請確保補丁本身具有完整的變更日誌信息。 +如果您有一系列重要的補丁,那麼通常發送一個簡介作為第〇部分。不過,這個約定 +並沒有得到普遍遵循;如果您使用它,請記住簡介中的資訊不會進入核心變更日誌。 +因此,請確保補丁本身具有完整的變更日誌資訊。 -一般來說,多部分補丁的第二部分和後續部分應作爲對第一部分的回覆發送,以便它們 -在接收端都連接在一起。像git和coilt這樣的工具有命令,可以通過適當的線程發送 +一般來說,多部分補丁的第二部分和後續部分應作為對第一部分的回覆發送,以便它們 +在接收端都連接在一起。像git和coilt這樣的工具有命令,可以透過適當的執行緒發送 一組補丁。但是,如果您有一長串補丁,並正使用git,請不要使用–-chain-reply-to -選項,以避免創建過深的嵌套。 +選項,以避免建立過深的嵌套。 From e2876d0ca416be755a9bf4ab207251e5c5715e09 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:43 +0800 Subject: [PATCH 0580/2286] docs/zh_TW: process: localize terminology in 2.Process.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 社區→社群, 郵箱→信箱, ...) and sync with the English original: rolling-release wording with the 9.x example and Wikipedia link, 9.x-rc1 examples, stable team now Greg Kroah-Hartman and Sasha Levin, LTS list moved to kernel.org, linux-next maintained by Mark Brown, and mailing lists hosted at subspace.kernel.org. update to commit 46298375477b ("linux-next: update maintainer info.") Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Reviewed-by: Weijie Yuan Signed-off-by: Alex Shi --- .../translations/zh_TW/process/2.Process.rst | 325 +++++++++--------- 1 file changed, 158 insertions(+), 167 deletions(-) diff --git a/Documentation/translations/zh_TW/process/2.Process.rst b/Documentation/translations/zh_TW/process/2.Process.rst index f45ddba6238ff3..36b109622ea6ba 100644 --- a/Documentation/translations/zh_TW/process/2.Process.rst +++ b/Documentation/translations/zh_TW/process/2.Process.rst @@ -12,64 +12,60 @@ 吳想成 Wu XiangCheng 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh .. _tw_development_process: 開發流程如何進行 ================ -90年代早期的Linux內核開發是一件相當鬆散的事情,涉及的用戶和開發人員相對較少。 -由於擁有數以百萬計的用戶羣,且每年有大約2000名開發人員參與進來,內核因此必須 -發展出許多既定流程來保證開發的順利進行。要參與到流程中來,需要對此流程的進行 -方式有一個紮實的理解。 +90年代早期的Linux核心開發是一件相當鬆散的事情,涉及的使用者和開發人員相對 +較少。由於擁有數以百萬計的使用者羣,且每年有大約2000名開發人員參與進來,核 +心因此必須發展出許多既定流程來保證開發的順利進行。要參與到流程中來,需要對 +此流程的進行方式有一個紮實的理解。 總覽 ---- -內核開發人員使用一個鬆散的基於時間的發佈過程,每兩到三個月發佈一次新的主要 -內核版本。最近的發佈歷史記錄如下: +Linux核心使用一種鬆散的、基於時間的滾動發布開發模式。每兩到三個月就會有 +一個新的主要核心版本發布(作為範例,我們稱之為9.x) [1]_ ,它帶來新特性、 +內部API更改等等。一個典型的版本可以包含大約13000個變更集,變更了幾十萬行 +程式碼。最近的版本及其日期可以在 +`維基百科 `_ +找到。 - ====== ================= - 5.0 2019年3月3日 - 5.1 2019年5月5日 - 5.2 2019年7月7日 - 5.3 2019年9月15日 - 5.4 2019年11月24日 - 5.5 2020年1月6日 - ====== ================= - -每個5.x版本都是一個主要的內核版本,具有新特性、內部API更改等等。一個典型的5.x -版本包含大約13000個變更集,變更了幾十萬行代碼。因此,5.x是Linux內核開發的前 -沿;內核使用滾動開發模型,不斷集成重大變化。 +.. [1] 嚴格來說,Linux核心並不使用語意化版本編號方案,而是以9.x這一對 + 數字作為一個整體來標識主要發布版本。每次發布時x會遞增,只有當x被 + 認為足夠大時才會遞增9(例如,Linux 5.0是繼Linux 4.20之後發布的)。 對於每個版本的補丁合併,遵循一個相對簡單的規則。在每個開發週期的開頭,“合併 -窗口”被打開。這時,被認爲足夠穩定(並且被開發社區接受)的代碼被合併到主線內 +視窗”被開啟。這時,被認為足夠穩定(並且被開發社群接受)的程式碼被合併到主線內 核中。在這段時間內,新開發週期的大部分變更(以及所有主要變更)將以接近每天 1000次變更(“補丁”或“變更集”)的速度合併。 -(順便說一句,值得注意的是,合併窗口期間集成的更改並不是憑空產生的;它們是經 +(順便說一句,值得注意的是,合併視窗期間整合的更改並不是憑空產生的;它們是經 提前收集、測試和分級的。稍後將詳細描述該過程的工作方式。) -合併窗口持續大約兩週。在這段時間結束時,Linus Torvalds將聲明窗口已關閉,並 -釋放第一個“rc”內核。例如,對於目標爲5.6的內核,在合併窗口結束時發生的釋放 -將被稱爲5.6-rc1。-rc1 版本是一個信號,表示合併新特性的時間已經過去,穩定下一 -個內核的時間已經到來。 +合併視窗持續大約兩週。在這段時間結束時,Linus Torvalds將聲明視窗已關閉,並 +釋放第一個“rc”核心。例如,對於目標為9.x的核心,在合併視窗結束時發生的釋放 +將被稱為9.x-rc1。-rc1 版本是一個信號,表示合併新特性的時間已經過去,穩定下一 +個核心的時間已經到來。 在接下來的6到10周內,只有修復問題的補丁才應該提交給主線。有時會允許更大的 -更改,但這種情況很少發生;試圖在合併窗口外合併新功能的開發人員往往受不到 -友好的接待。一般來說,如果您錯過了給定特性的合併窗口,最好的做法是等待下一 -個開發週期。(偶爾會對未支持硬件的驅動程序進行例外;如果它們不改變已有代碼, +更改,但這種情況很少發生;試圖在合併視窗外合併新功能的開發人員往往受不到 +友好的接待。一般來說,如果您錯過了給定特性的合併視窗,最好的做法是等待下一 +個開發週期。(偶爾會對未支援硬體的驅動程式進行例外;如果它們不改變已有程式碼, 則不會導致迴歸,應該可以隨時被安全地加入)。 -隨着修復程序進入主線,補丁速度將隨着時間的推移而變慢。Linus大約每週發佈一次 -新的-rc內核;在內核被認爲足夠穩定並最終發佈前,一般會達到-rc6到-rc9之間。 +隨著修復程式進入主線,補丁速度將隨著時間的推移而變慢。Linus大約每週發布一次 +新的-rc核心;在核心被認為足夠穩定並最終發布前,一般會達到-rc6到-rc9之間。 然後,整個過程又重新開始了。 -例如,這裏是5.4的開發週期進行情況(2019年): +例如,這裡是5.4的開發週期進行情況(2019年): ============== ============================== - 九月 15 5.3 穩定版發佈 - 九月 30 5.4-rc1 合併窗口關閉 + 九月 15 5.3 穩定版發布 + 九月 30 5.4-rc1 合併視窗關閉 十月 6 5.4-rc2 十月 13 5.4-rc3 十月 20 5.4-rc4 @@ -77,26 +73,26 @@ 十一月 3 5.4-rc6 十一月 10 5.4-rc7 十一月 17 5.4-rc8 - 十一月 24 5.4 穩定版發佈 + 十一月 24 5.4 穩定版發布 ============== ============================== -開發人員如何決定何時結束開發週期並創建穩定版本?最重要的指標是以前版本的 -迴歸列表。不歡迎出現任何錯誤,但是那些破壞了以前能工作的系統的錯誤被認爲是 +開發人員如何決定何時結束開發週期並建立穩定版本?最重要的指標是以前版本的 +迴歸列表。不歡迎出現任何錯誤,但是那些破壞了以前能工作的系統的錯誤被認為是 特別嚴重的。因此,導致迴歸的補丁是不受歡迎的,很可能在穩定期內刪除。 -開發人員的目標是在穩定發佈之前修復所有已知的迴歸。在現實世界中,這種完美是 -很難實現的;在這種規模的項目中,變數太多了。需要說明的是,延遲最終版本只會 -使問題變得更糟;等待下一個合併窗口的更改將變多,導致下次出現更多的迴歸錯誤。 -因此,大多數5.x內核都有一些已知的迴歸錯誤,不過,希望沒有一個是嚴重的。 +開發人員的目標是在穩定發布之前修復所有已知的迴歸。在現實世界中,這種完美是 +很難實作的;在這種規模的專案中,變數太多了。需要說明的是,延遲最終版本只會 +使問題變得更糟;等待下一個合併視窗的更改將變多,導致下次出現更多的迴歸錯誤。 +因此,大多數核心版本都有一些已知的迴歸錯誤,不過,希望沒有一個是嚴重的。 -一旦一個穩定的版本發佈,它的持續維護工作就被移交給“穩定團隊”,目前由 -Greg Kroah-Hartman領導。穩定團隊將使用5.x.y編號方案不定期地發佈穩定版本的 -更新。要合入更新版本,補丁必須(1)修復一個重要的缺陷,且(2)已經合併到 -下一個開發版本主線中。內核通常會在其初始版本後的一個以上的開發週期內收到 -穩定版更新。例如,5.2內核的歷史如下(2019年): +一旦一個穩定的版本發布,它的持續維護工作就被移交給“穩定團隊”,目前由Greg +Kroah-Hartman和Sasha Levin組成。穩定團隊將使用9.x.y編號方案不定期地發布穩 +定版本的更新。要合入更新版本,補丁必須(1)修復一個重要的缺陷,且(2)已經 +合併到下一個開發版本主線中。核心通常會在其初始版本後的一個以上的開發週期內 +收到穩定版更新。例如,5.2核心的歷史如下(2019年): ============== =============================== - 七月 7 5.2 穩定版發佈 + 七月 7 5.2 穩定版發布 七月 13 5.2.1 七月 21 5.2.2 七月 26 5.2.3 @@ -108,36 +104,29 @@ Greg Kroah-Hartman領導。穩定團隊將使用5.x.y編號方案不定期地發 5.2.21是5.2版本的最終穩定更新。 -有些內核被指定爲“長期”內核;它們將得到更長時間的支持。在本文中,當前的長期 -內核及其維護者是: +有些核心被指定為“長期”核心;它們將得到更長時間的支援。當前的長期核心 +版本及其維護者的列表,請參考以下連結: - ====== ================================ ================ - 3.16 Ben Hutchings (長期穩定內核) - 4.4 Greg Kroah-Hartman & Sasha Levin (長期穩定內核) - 4.9 Greg Kroah-Hartman & Sasha Levin - 4.14 Greg Kroah-Hartman & Sasha Levin - 4.19 Greg Kroah-Hartman & Sasha Levin - 5.4 Greg Kroah-Hartman & Sasha Levin - ====== ================================ ================ + https://www.kernel.org/category/releases.html -長期支持內核的選擇純粹是維護人員是否有需求和時間來維護該版本的問題。 -目前還沒有爲即將發佈的任何特定版本提供長期支持的已知計劃。 +長期支援核心的選擇純粹是維護人員是否有需求和時間來維護該版本的問題。 補丁的生命週期 -------------- -補丁不會直接從開發人員的鍵盤進入主線內核。相反,有一個稍微複雜(如果有些非 -正式)的過程,旨在確保對每個補丁進行質量審查,並確保每個補丁實現了一個在主線 -中需要的更改。對於小的修復,這個過程可能會很快完成,,而對於較大或有爭議的 -變更,可能會持續數年。許多開發人員的沮喪來自於對這個過程缺乏理解或者試圖繞過它。 +補丁不會直接從開發人員的鍵盤進入主線核心。相反,有一個稍微複雜(如果有些非 +正式)的過程,旨在確保對每個補丁進行品質審查,並確保每個補丁實作了一個在主 +線中需要的更改。對於小的修復,這個過程可能會很快完成,,而對於較大或有爭議 +的變更,可能會持續數年。許多開發人員的沮喪來自於對這個過程缺乏理解或者試圖 +繞過它。 -爲了減少這種挫敗,本文將描述補丁如何進入內核。下面的介紹以一種較爲理想化的 +為了減少這種挫敗,本文將描述補丁如何進入核心。下面的介紹以一種較為理想化的 方式描述了這個過程。更詳細的過程將在後面的章節中介紹。 補丁通常要經歷以下階段: - 設計。這就是補丁的真正需求——以及滿足這些需求的方式——所在。設計工作通常 - 是在不涉及社區的情況下完成的,但是如果可能的話,最好是在公開的情況下完成 + 是在不涉及社群的情況下完成的,但是如果可能的話,最好是在公開的情況下完成 這項工作;這樣可以節省很多稍後再重新設計的時間。 - 早期評審。補丁被髮布到相關的郵件列表中,列表中的開發人員會回覆他們可能有 @@ -150,74 +139,75 @@ Greg Kroah-Hartman領導。穩定團隊將使用5.x.y編號方案不定期地發 問題。 - 請注意,大多數維護人員也有日常工作,因此合併補丁可能不是他們的最優先工作。 - 如果您的補丁得到了需要更改的反饋,那麼您應該進行這些更改,或者解釋爲何 + 如果您的補丁得到了需要更改的反饋,那麼您應該進行這些更改,或者解釋為何 不應該進行這些更改。如果您的補丁沒有評審意見,也沒有被其相應的子系統或 - 驅動程序維護者接受,那麼您應該堅持不懈地將補丁更新到當前內核使其可被正常 - 應用,並不斷地發送它以供審查和合並。 + 驅動程式維護者接受,那麼您應該堅持不懈地將補丁更新到當前核心使其可被正常 + 應用,並不斷地發送它以供審查和合併。 -- 合併到主線。最終,一個成功的補丁將被合併到由LinusTorvalds管理的主線存儲庫 +- 合併到主線。最終,一個成功的補丁將被合併到由Linus Torvalds管理的主線儲存庫 中。此時可能會出現更多的評論和/或問題;對開發人員來說應對這些問題並解決 出現的任何問題仍很重要。 -- 穩定版發佈。大量用戶可能受此補丁影響,因此可能再次出現新的問題。 +- 穩定版發布。大量使用者可能受此補丁影響,因此可能再次出現新的問題。 -- 長期維護。雖然開發人員在合併代碼後可能會忘記代碼,但這種行爲往往會給開發 - 社區留下不良印象。合併代碼消除了一些維護負擔,因爲其他人將修復由API更改 - 引起的問題。但是,如果代碼要長期保持可用,原始開發人員應該繼續爲代碼負責。 +- 長期維護。雖然開發人員在合併程式碼後可能會忘記程式碼,但這種行為往往會給 + 開發社群留下不良印象。合併程式碼消除了一些維護負擔,因為其他人將修復由 + API更改引起的問題。但是,如果程式碼要長期保持可用,原始開發人員應該繼續 + 為程式碼負責。 -內核開發人員(或他們的僱主)犯的最大錯誤之一是試圖將流程簡化爲一個“合併到 +核心開發人員(或他們的僱主)犯的最大錯誤之一是試圖將流程簡化為一個“合併到 主線”步驟。這種方法總是會讓所有相關人員感到沮喪。 -補丁如何進入內核 +補丁如何進入核心 ---------------- -只有一個人可以將補丁合併到主線內核存儲庫中:Linus Torvalds。但是,在進入 -2.6.38內核的9500多個補丁中,只有112個(大約1.3%)是由Linus自己直接選擇的。 -內核項目已經發展到一個沒有一個開發人員可以在沒有支持的情況下檢查和選擇每個 -補丁的規模。內核開發人員處理這種增長的方式是使用圍繞信任鏈構建的助理系統。 +只有一個人可以將補丁合併到主線核心儲存庫中:Linus Torvalds。但是,在進入 +2.6.38核心的9500多個補丁中,只有112個(大約1.3%)是由Linus自己直接選擇的。 +核心專案已經發展到一個沒有一個開發人員可以在沒有支援的情況下檢查和選擇每個 +補丁的規模。核心開發人員處理這種增長的方式是使用圍繞信任鏈建置的助理系統。 -內核代碼庫在邏輯上被分解爲一組子系統:網絡、特定體系結構支持、內存管理、視 -頻設備等。大多數子系統都有一個指定的維護人員,其總體負責該子系統中的代碼。 -這些子系統維護者(鬆散地)是他們所管理的內核部分的“守門員”;他們(通常) -會接受一個補丁以包含到主線內核中。 +核心程式碼庫在邏輯上被分解為一組子系統:網路、特定體系結構支援、記憶體管理、視 +頻設備等。大多數子系統都有一個指定的維護人員,其總體負責該子系統中的程式碼。 +這些子系統維護者(鬆散地)是他們所管理的核心部分的“守門員”;他們(通常) +會接受一個補丁以包含到主線核心中。 -子系統維護人員每個人都管理着自己版本的內核源代碼樹,通常(並非總是)使用Git。 -Git等工具(以及Quilt或Mercurial等相關工具)允許維護人員跟蹤補丁列表,包括作者 -信息和其他元數據。在任何給定的時間,維護人員都可以確定他或她的存儲庫中的哪 -些補丁在主線中找不到。 +子系統維護人員每個人都管理著自己版本的核心原始程式碼樹,通常(並非總是)使 +用Git。Git等工具(以及Quilt或Mercurial等相關工具)允許維護人員追蹤補丁列表, +包括作者資訊和其他元資料。在任何給定的時間,維護人員都可以確定他或她的儲存 +庫中的哪些補丁在主線中找不到。 -當合並窗口打開時,頂級維護人員將要求Linus從存儲庫中“拉出”他們爲合併選擇 -的補丁。如果Linus同意,補丁流將流向他的存儲庫,成爲主線內核的一部分。 +當合併視窗開啟時,頂級維護人員將要求Linus從儲存庫中“拉出”他們為合併選擇 +的補丁。如果Linus同意,補丁流將流向他的儲存庫,成為主線核心的一部分。 Linus對拉取中接收到的特定補丁的關注程度各不相同。很明顯,有時他看起來很 關注。但是一般來說,Linus相信子系統維護人員不會向上遊發送壞補丁。 -子系統維護人員反過來也可以從其他維護人員那裏獲取補丁。例如,網絡樹是由首先 -在專用於網絡設備驅動程序、無線網絡等的樹中積累的補丁構建的。此存儲鏈可以 -任意長,但很少超過兩個或三個鏈接。由於鏈中的每個維護者都信任那些管理較低 -級別樹的維護者,所以這個過程稱爲“信任鏈”。 +子系統維護人員反過來也可以從其他維護人員那裡獲取補丁。例如,網路樹是由首先 +在專用於網路設備驅動程式、無線網路等的樹中積累的補丁建置的。此儲存鏈可以 +任意長,但很少超過兩個或三個連結。由於鏈中的每個維護者都信任那些管理較低 +級別樹的維護者,所以這個過程稱為“信任鏈”。 -顯然,在這樣的系統中,獲取內核補丁取決於找到正確的維護者。直接向Linus發送 +顯然,在這樣的系統中,獲取核心補丁取決於找到正確的維護者。直接向Linus發送 補丁通常不是正確的方法。 Next 樹 ------- -子系統樹鏈引導補丁流到內核,但它也提出了一個有趣的問題:如果有人想查看爲 -下一個合併窗口準備的所有補丁怎麼辦?開發人員將感興趣的是,還有什麼其他的 -更改有待解決,以瞭解是否存在需要擔心的衝突;例如,更改核心內核函數原型的 -修補程序將與使用該函數舊形式的任何其他修補程序衝突。審查人員和測試人員希望 -在所有這些變更到達主線內核之前,能夠訪問它們的集成形式的變更。您可以從所有 +子系統樹鏈引導補丁流到核心,但它也提出了一個有趣的問題:如果有人想查看為 +下一個合併視窗準備的所有補丁怎麼辦?開發人員將感興趣的是,還有什麼其他的 +更改有待解決,以瞭解是否存在需要擔心的衝突;例如,更改核心核心函式原型的 +修補程式將與使用該函式舊形式的任何其他修補程式衝突。審查人員和測試人員希望 +在所有這些變更到達主線核心之前,能夠存取它們的整合形式的變更。您可以從所有 相關的子系統樹中提取更改,但這將是一項複雜且容易出錯的工作。 -解決方案以-next樹的形式出現,在這裏子系統樹被收集以供測試和審查。這些樹中 -由Andrew Morton維護的較老的一個,被稱爲“-mm”(用於內存管理,創建時爲此)。 --mm 樹集成了一長串子系統樹中的補丁;它還包含一些旨在幫助調試的補丁。 +解決方案以-next樹的形式出現,在這裡子系統樹被收集以供測試和審查。這些樹中 +由Andrew Morton維護的較老的一個,被稱為“-mm”(用於記憶體管理,建立時為此)。 +-mm 樹整合了一長串子系統樹中的補丁;它還包含一些旨在幫助除錯的補丁。 除此之外,-mm 還包含大量由Andrew直接選擇的補丁。這些補丁可能已經發布在郵件 -列表上,或者它們可能應用於內核中未指定子系統樹的部分。同時,-mm 作爲最後 +列表上,或者它們可能應用於核心中未指定子系統樹的部分。同時,-mm 作為最後 手段的子系統樹;如果沒有其他明顯的路徑可以讓補丁進入主線,那麼它很可能最 終選擇-mm 樹。累積在-mm 中的各種補丁最終將被轉發到適當的子系統樹,或者直接 -發送到Linus。在典型的開發週期中,大約5-10%的補丁通過-mm 進入主線。 +發送到Linus。在典型的開發週期中,大約5-10%的補丁透過-mm 進入主線。 當前-mm 補丁可在“mmotm”(-mm of the moment)目錄中找到: @@ -225,102 +215,103 @@ Next 樹 然而,使用MMOTM樹可能會十分令人頭疼;它甚至可能無法編譯。 -下一個週期補丁合併的主要樹是linux-next,由Stephen Rothwell 維護。根據設計 -linux-next 是下一個合併窗口關閉後主線的快照。linux-next樹在Linux-kernel 和 -Linux-next 郵件列表中發佈,可從以下位置下載: +下一個週期補丁合併的主要樹是linux-next,由Mark Brown維護。根據設計 +linux-next 是下一個合併視窗關閉後主線的快照。linux-next樹在Linux-kernel 和 +Linux-next 郵件列表中發布,可從以下位置下載: https://www.kernel.org/pub/linux/kernel/next/ -Linux-next 已經成爲內核開發過程中不可或缺的一部分;在一個給定的合併窗口中合併 -的所有補丁都應該在合併窗口打開之前的一段時間內找到進入Linux-next 的方法。 +Linux-next 已經成為核心開發過程中不可或缺的一部分;在一個給定的合併視窗中合併 +的所有補丁都應該在合併視窗開啟之前的一段時間內找到進入Linux-next 的方法。 Staging 樹 ---------- -內核源代碼樹包含drivers/staging/目錄,其中有許多驅動程序或文件系統的子目錄 -正在被添加到內核樹中。它們在仍然需要更多的修正的時候可以保留在driver/staging/ -目錄中;一旦完成,就可以將它們移到內核中。這是一種跟蹤不符合Linux內核編碼或 -質量標準的驅動程序的方法,人們可能希望使用它們並跟蹤開發。 +核心原始程式碼樹包含drivers/staging/目錄,其中有許多驅動程式或檔案系統的子目錄 +正在被添加到核心樹中。它們在仍然需要更多的修正的時候可以保留在driver/staging/ +目錄中;一旦完成,就可以將它們移到核心中。這是一種追蹤不符合Linux核心編碼或 +品質標準的驅動程式的方法,人們可能希望使用它們並追蹤開發。 -Greg Kroah Hartman 目前負責維護staging 樹。仍需要修正的驅動程序將發送給他, -每個驅動程序在drivers/staging/中都有自己的子目錄。除了驅動程序源文件之外, -目錄中還應該有一個TODO文件。TODO文件列出了驅動程序需要接受的暫停的工作, -以及驅動程序的任何補丁都應該抄送的人員列表。當前的規則要求,staging的驅動 -程序必須至少正確編譯。 +Greg Kroah-Hartman 目前負責維護staging樹。仍需要修正的驅動程式將發送給他, +每個驅動程式在drivers/staging/中都有自己的子目錄。除了驅動程式原始檔之外, +目錄中還應該有一個TODO檔案。TODO檔案列出了驅動程式需要接受的暫停的工作, +以及驅動程式的任何補丁都應該抄送的人員列表。當前的規則要求,staging的驅動 +程式必須至少正確編譯。 -Staging 是一種讓新的驅動程序進入主線的相對容易的方法,它們會幸運地引起其他 +Staging 是一種讓新的驅動程式進入主線的相對容易的方法,它們會幸運地引起其他 開發人員的注意,並迅速改進。然而,進入staging並不是故事的結尾;staging中 -沒有看到常規進展的代碼最終將被刪除。經銷商也傾向於相對不願意使用staging驅動 -程序。因此,在成爲一個合適的主線驅動的路上,staging 僅是一箇中轉站。 +沒有看到常規進展的程式碼最終將被刪除。經銷商也傾向於相對不願意使用staging驅動 +程式。因此,在成為一個合適的主線驅動的路上,staging 僅是一箇中轉站。 工具 ---- -從上面的文本可以看出,內核開發過程在很大程度上依賴於在不同方向上聚集補丁的 +從上面的文字可以看出,核心開發過程在很大程度上依賴於在不同方向上聚集補丁的 能力。如果沒有適當強大的工具,整個系統將無法在任何地方正常工作。關於如何使用 -這些工具的教程遠遠超出了本文檔的範圍,但還是用一點篇幅介紹一些關鍵點。 +這些工具的教程遠遠超出了本文件的範圍,但還是用一點篇幅介紹一些關鍵點。 -到目前爲止,內核社區使用的主要源代碼管理系統是git。Git是在自由軟件社區中開發 -的許多分佈式版本控制系統之一。它非常適合內核開發,因爲它在處理大型存儲庫和 -大量補丁時性能非常好。它也以難以學習和使用而著稱,儘管隨着時間的推移它變得 -更好了。對於內核開發人員來說,對Git的某種熟悉幾乎是一種要求;即使他們不將它 -用於自己的工作,他們也需要Git來跟上其他開發人員(以及主線)正在做的事情。 +到目前為止,核心社群使用的主要原始程式碼管理系統是git。Git是在自由軟體社群 +中開發的許多分散式版本控制系統之一。它非常適合核心開發,因為它在處理大型儲 +存庫和大量補丁時效能非常好。它也以難以學習和使用而著稱,儘管隨著時間的推移 +它變得更好了。對於核心開發人員來說,對Git的某種熟悉幾乎是一種要求;即使他 +們不將它用於自己的工作,他們也需要Git來跟上其他開發人員(以及主線)正在做 +的事情。 現在幾乎所有的Linux發行版都打包了Git。Git主頁位於: https://git-scm.com/ -此頁面包含了文檔和教程的鏈接。 +此頁面包含了文件和教程的連結。 -在不使用git的內核開發人員中,最流行的選擇幾乎肯定是Mercurial: +在不使用git的核心開發人員中,最流行的選擇幾乎肯定是Mercurial: http://www.seleric.com/mercurial/ -Mercurial與Git共享許多特性,但它提供了一個界面,許多人覺得它更易於使用。 +Mercurial與Git共享許多特性,但它提供了一個介面,許多人覺得它更易於使用。 另一個值得了解的工具是Quilt: https://savannah.nongnu.org/projects/quilt -Quilt 是一個補丁管理系統,而不是源代碼管理系統。它不會隨着時間的推移跟蹤歷史; -相反,它面向根據不斷發展的代碼庫跟蹤一組特定的更改。一些主要的子系統維護人員 -使用Quilt來管理打算向上遊移動的補丁。對於某些樹的管理(例如-mm),quilt 是 -最好的工具。 +Quilt 是一個補丁管理系統,而不是原始程式碼管理系統。它不會隨著時間的推移追 +蹤歷史;相反,它面向根據不斷發展的程式碼庫追蹤一組特定的更改。一些主要的子 +系統維護人員使用Quilt來管理打算向上遊移動的補丁。對於某些樹的管理(例如-mm), +quilt 是最好的工具。 郵件列表 -------- -大量的Linux內核開發工作是通過郵件列表完成的。如果不加入至少一個某個列表, -就很難成爲社區中的一個“全功能”成員。但是,Linux郵件列表對開發人員來說也是 +大量的Linux核心開發工作是透過郵件列表完成的。如果不加入至少一個某個列表, +就很難成為社群中的一個“全功能”成員。但是,Linux郵件列表對開發人員來說也是 一個潛在的危險,他們可能會被一堆電子郵件淹沒、違反Linux列表上使用的約定, 或者兩者兼而有之。 -大多數內核郵件列表都在vger.kernel.org上運行;主列表位於: +大多數核心郵件列表都託管在kernel.org上;主列表位於: - http://vger.kernel.org/vger-lists.html + https://subspace.kernel.org -不過,也有一些列表託管在別處;其中一些列表位於 -redhat.com/mailman/listinfo。 +也有一些列表託管在別處;請查閱MAINTAINERS檔案以找到與特定子系統相關的 +列表。 -當然,內核開發的核心郵件列表是linux-kernel。這個列表是一個令人生畏的地方: -每天的信息量可以達到500條,噪音很高,談話技術性很強,且參與者並不總是表現出 -高度的禮貌。但是,沒有其他地方可以讓內核開發社區作爲一個整體聚集在一起; -不使用此列表的開發人員將錯過重要信息。 +當然,核心開發的核心郵件列表是linux-kernel。這個列表是一個令人生畏的地方: +每天的資訊量可以達到500條,噪音很高,談話技術性很強,且參與者並不總是表現出 +高度的禮貌。但是,沒有其他地方可以讓核心開發社群作為一個整體聚集在一起; +不使用此列表的開發人員將錯過重要資訊。 以下一些提示可以幫助在linux-kernel生存: -- 將郵件轉移到單獨的文件夾,而不是主郵箱文件夾。我們必須能夠持續地忽略洪流。 +- 將郵件轉移到單獨的資料夾,而不是主信箱資料夾。我們必須能夠持續地忽略洪流。 - 不要試圖跟上每一次談話——沒人會這樣。重要的是要篩選感興趣的主題(但請注意 長時間的對話可能會偏離原來的主題,儘管未改變電子郵件的主題)和參與的人。 - 不要回復挑事的人。如果有人試圖激起憤怒,請忽略他們。 -- 當回覆Linux內核電子郵件(或其他列表上的電子郵件)時,請爲所有相關人員保留 +- 當回覆Linux核心電子郵件(或其他列表上的電子郵件)時,請為所有相關人員保留 Cc: 抄送頭。如果沒有確實的理由(如明確的請求),則不應刪除收件人。一定要 確保你要回復的人在抄送列表中。這個慣例也使你不必在回覆郵件時明確要求被抄送。 -- 在提出問題之前,搜索列表存檔(和整個網絡)。有些開發人員可能會對那些顯然 +- 在提出問題之前,搜索列表存檔(和整個網路)。有些開發人員可能會對那些顯然 沒有完成家庭作業的人感到不耐煩。 - 避免頂部回覆(把你的答案放在你要回復的引文上面的做法)。這會讓你的回答更難 @@ -329,41 +320,41 @@ redhat.com/mailman/listinfo。 - 在正確的郵件列表發問。linux-kernel 可能是通用的討論場所,但它不是尋找所有 子系統開發人員的最佳場所。 -最後一點——找到正確的郵件列表——是開發人員常出錯的地方。在linux-kernel上 -提出與網絡相關的問題的人幾乎肯定會收到一個禮貌的建議,轉到netdev列表上提出, -因爲這是大多數網絡開發人員經常出現的列表。還有其他列表可用於scsi、video4linux、 -ide、filesystem等子系統。查找郵件列表的最佳位置是與內核源代碼一起打包的 -MAINTAINERS文件。 +最後一點——找到正確的郵件列表——是開發人員常出錯的地方。在linux-kernel上提出 +與網路相關的問題的人幾乎肯定會收到一個禮貌的建議,轉到netdev列表上提出,因 +為這是大多數網路開發人員經常出現的列表。還有其他列表可用於scsi、 +video4linux、ide、filesystem等子系統。查找郵件列表的最佳位置是與核心原始程 +式碼一起打包的MAINTAINERS檔案。 -開始內核開發 +開始核心開發 ------------ -關於如何開始內核開發過程的問題很常見——個人和公司皆然。同樣常見的是失誤,這 +關於如何開始核心開發過程的問題很常見——個人和公司皆然。同樣常見的是失誤,這 使得關係的開始比本應的更困難。 -公司通常希望聘請知名的開發人員來啓動開發團隊。實際上,這是一種有效的技術。 -但它也往往是昂貴的,而且對增加有經驗的內核開發人員的數量沒有多大幫助。考 -慮到時間投入,可以讓內部開發人員加快Linux內核的開發速度。利用這段時間可以 -讓僱主擁有一批既瞭解內核又瞭解公司的開發人員,還可以幫助培訓其他人。從中期 +公司通常希望聘請知名的開發人員來啟動開發團隊。實際上,這是一種有效的技術。 +但它也往往是昂貴的,而且對增加有經驗的核心開發人員的數量沒有多大幫助。考 +慮到時間投入,可以讓內部開發人員加快Linux核心的開發速度。利用這段時間可以 +讓僱主擁有一批既瞭解核心又瞭解公司的開發人員,還可以幫助培訓其他人。從中期 來看,這通常是更有利可圖的方法。 -可以理解的是,單個開發人員往往對起步感到茫然。從一個大型項目開始可能會很 -嚇人;人們往往想先用一些較小的東西來試試水。由此,一些開發人員開始創建修補 +可以理解的是,單個開發人員往往對起步感到茫然。從一個大型專案開始可能會很 +嚇人;人們往往想先用一些較小的東西來試試水。由此,一些開發人員開始建立修補 拼寫錯誤或輕微編碼風格問題的補丁。不幸的是,這樣的補丁會產生一定程度的噪音, -這會分散整個開發社區的注意力,因此,它們越來越被人不看重。希望向社區介紹 -自己的新開發人員將無法通過這些方式獲得他們期待的反響。 +這會分散整個開發社群的注意力,因此,它們越來越被人不看重。希望向社群介紹 +自己的新開發人員將無法透過這些方式獲得他們期待的反響。 -Andrew Morton 爲有抱負的內核開發人員提供瞭如下建議 +Andrew Morton 為有抱負的核心開發人員提供了如下建議 :: - 所有內核開發者的第一個項目肯定應該是“確保內核在您可以操作的所有 - 機器上始終完美運行”。通常的方法是和其他人一起解決問題(這可能需 - 要堅持!),但就是如此——這是內核開發的一部分。 + 所有核心開發者的第一個專案肯定應該是“確保核心在您可以操作的所有 + 機器上始終完美執行”。通常的方法是和其他人一起解決問題(這可能需 + 要堅持!),但就是如此——這是核心開發的一部分。 (http://lwn.net/Articles/283982/) 在沒有明顯問題需要解決的情況下,通常建議開發人員查看當前的迴歸和開放缺陷 -列表。從來都不缺少需要解決的問題;通過解決這些問題,開發人員將從該過程獲得 -經驗,同時與開發社區的其他成員建立相互尊重。 +列表。從來都不缺少需要解決的問題;透過解決這些問題,開發人員將從該過程獲得 +經驗,同時與開發社群的其他成員建立相互尊重。 From 0543ca8ed0a4c0fc88b9f36a2273b31fca59b524 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:44 +0800 Subject: [PATCH 0581/2286] docs/zh_TW: process: localize terminology in howto.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 軟件→軟體, 社區→社群, ...) and sync with the English original: update the man-pages maintainer address, the linux-next git URL (also dropping a stray full-width question mark), and the mailing list links now pointing at subspace.kernel.org and lore.kernel.org. update to commit 413e775efaec ("Documentation: fix links to mailing list services") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../translations/zh_TW/process/howto.rst | 344 +++++++++--------- 1 file changed, 173 insertions(+), 171 deletions(-) diff --git a/Documentation/translations/zh_TW/process/howto.rst b/Documentation/translations/zh_TW/process/howto.rst index 80c416483e7386..8466f6af96cc5e 100644 --- a/Documentation/translations/zh_TW/process/howto.rst +++ b/Documentation/translations/zh_TW/process/howto.rst @@ -17,13 +17,14 @@ 陳琦 Maggie Chen 王聰 Wang Cong 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh -如何參與Linux內核開發 +如何參與Linux核心開發 ===================== -這是一篇將如何參與Linux內核開發的相關問題一網打盡的終極祕笈。它將指導你 -成爲一名Linux內核開發者,並且學會如何同Linux內核開發社區合作。它儘可能不 -包括任何關於內核編程的技術細節,但會給你指引一條獲得這些知識的正確途徑。 +這是一篇將如何參與Linux核心開發的相關問題一網打盡的終極祕笈。它將指導你 +成為一名Linux核心開發者,並且學會如何同Linux核心開發社群合作。它儘可能不 +包括任何關於核心程式設計的技術細節,但會給你指引一條獲得這些知識的正確途徑。 如果這篇文章中的任何內容不再適用,請給文末列出的文件維護者發送補丁。 @@ -31,15 +32,15 @@ 入門 ---- -你想了解如何成爲一名Linux內核開發者?或者老闆吩咐你「給這個設備寫個Linux -驅動程序」?這篇文章的目的就是教會你達成這些目標的全部訣竅,它將描述你需 -要經過的流程以及給出如何同內核社區合作的一些提示。它還將試圖解釋內核社區 -爲何這樣運作。 +你想了解如何成為一名Linux核心開發者?或者老闆吩咐你「給這個設備寫個Linux +驅動程式」?這篇文章的目的就是教會你達成這些目標的全部訣竅,它將描述你需 +要經過的流程以及給出如何同核心社群合作的一些提示。它還將試圖解釋核心社群 +為何這樣運作。 -Linux內核大部分是由C語言寫成的,一些體系結構相關的代碼用到了彙編語言。要 -參與內核開發,你必須精通C語言。除非你想爲某個架構開發底層代碼,否則你並 -不需要了解(任何體系結構的)彙編語言。下面列舉的書籍雖然不能替代紮實的C -語言教育和多年的開發經驗,但如果需要的話,做爲參考還是不錯的: +Linux核心大部分是由C語言寫成的,一些體系結構相關的程式碼用到了組譯語言。要 +參與核心開發,你必須精通C語言。除非你想為某個架構開發底層程式碼,否則你並 +不需要了解(任何體系結構的)組譯語言。下面列舉的書籍雖然不能替代紮實的C +語言教育和多年的開發經驗,但如果需要的話,做為參考還是不錯的: - "The C Programming Language" by Kernighan and Ritchie [Prentice Hall] 《C程序設計語言(第2版·新版)》(徐寶文 李志 譯)[機械工業出版社] @@ -48,67 +49,67 @@ Linux內核大部分是由C語言寫成的,一些體系結構相關的代碼 - "C: A Reference Manual" by Harbison and Steele [Prentice Hall] 《C語言參考手冊(原書第5版)》(邱仲潘 等譯)[機械工業出版社] -Linux內核使用GNU C和GNU工具鏈開發。雖然它遵循ISO C11標準,但也用到了一些 -標準中沒有定義的擴展。內核是自給自足的C環境,不依賴於標準C庫的支持,所以 -並不支持C標準中的部分定義。比如long long類型的大數除法和浮點運算就不允許 -使用。有時候確實很難弄清楚內核對工具鏈的要求和它所使用的擴展,不幸的是目 -前還沒有明確的參考資料可以解釋它們。請查閱gcc信息頁(使用「info gcc」命令 -顯示)獲得一些這方面信息。 +Linux核心使用GNU C和GNU工具鏈開發。雖然它遵循ISO C11標準,但也用到了一些 +標準中沒有定義的擴展。核心是自給自足的C環境,不依賴於標準C庫的支援,所以 +並不支援C標準中的部分定義。比如long long類型的大數除法和浮點運算就不允許 +使用。有時候確實很難弄清楚核心對工具鏈的要求和它所使用的擴展,不幸的是目 +前還沒有明確的參考資料可以解釋它們。請查閱gcc資訊頁(使用「info gcc」命令 +顯示)獲得一些這方面資訊。 -請記住你是在學習怎麼和已經存在的開發社區打交道。它由一羣形形色色的人組成, -他們對代碼、風格和過程有著很高的標準。這些標準是在長期實踐中總結出來的, +請記住你是在學習怎麼和已經存在的開發社群打交道。它由一羣形形色色的人組成, +他們對程式碼、風格和過程有著很高的標準。這些標準是在長期實踐中總結出來的, 適應於地理上分散的大型開發團隊。它們已經被很好得整理成檔,建議你在開發 -之前儘可能多的學習這些標準,而不要期望別人來適應你或者你公司的行爲方式。 +之前儘可能多的學習這些標準,而不要期望別人來適應你或者你公司的行為方式。 法律問題 -------- -Linux內核原始碼都是在GPL(通用公共許可證)的保護下發布的。要了解這種許可 -的細節請查看原始碼主目錄下的COPYING文件。Linux內核許可準則和如何使用 +Linux核心原始碼都是在GPL(通用公共許可證)的保護下發布的。要了解這種許可 +的細節請查看原始碼主目錄下的COPYING檔案。Linux核心許可準則和如何使用 `SPDX ` 標誌符說明在這個文件中 :ref:`Documentation/translations/zh_TW/process/license-rules.rst ` -如果你對它還有更深入問題請聯繫律師,而不要在Linux內核郵件組上提問。因爲 +如果你對它還有更深入問題請聯繫律師,而不要在Linux核心郵件組上提問。因為 郵件組裡的人並不是律師,不要期望他們的話有法律效力。 -對於GPL的常見問題和解答,請訪問以下連結: +對於GPL的常見問題和解答,請造訪以下連結: https://www.gnu.org/licenses/gpl-faq.html -文檔 +文件 ---- -Linux內核代碼中包含有大量的文檔。這些文檔對於學習如何與內核社區互動有著 -不可估量的價值。當一個新的功能被加入內核,最好把解釋如何使用這個功能的文 -檔也放進內核。當內核的改動導致面向用戶空間的接口發生變化時,最好將相關信 -息或手冊頁(manpages)的補丁發到mtk.manpages@gmail.com,以向手冊頁(manpages) +Linux核心程式碼中包含有大量的文件。這些文件對於學習如何與核心社群互動有著 +不可估量的價值。當一個新的功能被加入核心,最好把解釋如何使用這個功能的文 +檔也放進核心。當核心的改動導致面向使用者空間的介面發生變化時,最好將相關信 +息或手冊頁(manpages)的補丁發到alx@kernel.org,以向手冊頁(manpages) 的維護者解釋這些變化。 -以下是內核代碼中需要閱讀的文檔: +以下是核心程式碼中需要閱讀的文件: :ref:`Documentation/admin-guide/README.rst ` - 文件簡要介紹了Linux內核的背景,並且描述了如何配置和編譯內核。內核的 - 新用戶應該從這裡開始。 + 文件簡要介紹了Linux核心的背景,並且描述了如何設定和編譯核心。核心的 + 新使用者應該從這裡開始。 :ref:`Documentation/process/changes.rst ` - 文件給出了用來編譯和使用內核所需要的最小軟體包列表。 + 文件給出了用來編譯和使用核心所需要的最小軟體套件列表。 :ref:`Documentation/translations/zh_TW/process/coding-style.rst ` - 描述Linux內核的代碼風格和理由。所有新代碼需要遵守這篇文檔中定義的規 + 描述Linux核心的程式碼風格和理由。所有新程式碼需要遵守這篇文件中定義的規 范。大多數維護者只會接收符合規定的補丁,很多人也只會幫忙檢查符合風格 - 的代碼。 + 的程式碼。 :ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` - 這兩份文檔明確描述如何創建和發送補丁,其中包括(但不僅限於): + 這兩份文件明確描述如何建立和發送補丁,其中包括(但不僅限於): - 郵件內容 - 郵件格式 - 選擇收件人 - 遵守這些規定並不能保證提交成功(因爲所有補丁需要通過嚴格的內容和風格 + 遵守這些規定並不能保證提交成功(因為所有補丁需要透過嚴格的內容和風格 審查),但是忽視他們幾乎就意味著失敗。 - 其他關於如何正確地生成補丁的優秀文檔包括: + 其他關於如何正確地產生補丁的優秀文件包括: "The Perfect Patch" https://www.ozlabs.org/~akpm/stuff/tpp.txt @@ -118,78 +119,78 @@ Linux內核代碼中包含有大量的文檔。這些文檔對於學習如何與 https://web.archive.org/web/20180829112450/http://linux.yyz.us/patch-format.html :ref:`Documentation/translations/zh_TW/process/stable-api-nonsense.rst ` - 論證內核爲什麼特意不包括穩定的內核內部API,也就是說不包括像這樣的特 + 論證核心為什麼特意不包括穩定的核心內部API,也就是說不包括像這樣的特 性: - - 子系統中間層(爲了兼容性?) - - 在不同作業系統間易於移植的驅動程序 - - 減緩(甚至阻止)內核代碼的快速變化 + - 子系統中間層(為了相容性?) + - 在不同作業系統間易於移植的驅動程式 + - 減緩(甚至阻止)核心程式碼的快速變化 - 這篇文檔對於理解Linux的開發哲學至關重要。對於將開發平台從其他操作系 + 這篇文件對於理解Linux的開發哲學至關重要。對於將開發平台從其他操作系 統轉移到Linux的人來說也很重要。 :ref:`Documentation/process/security-bugs.rst ` - 如果你認爲自己發現了Linux內核的安全性問題,請根據這篇文檔中的步驟來 - 提醒其他內核開發者並幫助解決這個問題。 + 如果你認為自己發現了Linux核心的安全性問題,請根據這篇文件中的步驟來 + 提醒其他核心開發者並幫助解決這個問題。 :ref:`Documentation/translations/zh_TW/process/management-style.rst ` - 描述內核維護者的工作方法及其共有特點。這對於剛剛接觸內核開發(或者對 - 它感到好奇)的人來說很重要,因爲它解釋了很多對於內核維護者獨特行爲的 + 描述核心維護者的工作方法及其共有特點。這對於剛剛接觸核心開發(或者對 + 它感到好奇)的人來說很重要,因為它解釋了很多對於核心維護者獨特行為的 普遍誤解與迷惑。 :ref:`Documentation/process/stable-kernel-rules.rst ` - 解釋了穩定版內核發布的規則,以及如何將改動放入這些版本的步驟。 + 解釋了穩定版核心發布的規則,以及如何將改動放入這些版本的步驟。 :ref:`Documentation/process/kernel-docs.rst ` - 有助於內核開發的外部文檔列表。如果你在內核自帶的文檔中沒有找到你想找 - 的內容,可以查看這些文檔。 + 有助於核心開發的外部文件列表。如果你在核心自帶的文件中沒有找到你想找 + 的內容,可以查看這些文件。 :ref:`Documentation/process/applying-patches.rst ` - 關於補丁是什麼以及如何將它打在不同內核開發分支上的好介紹 + 關於補丁是什麼以及如何將它打在不同核心開發分支上的好介紹 -內核還擁有大量從代碼自動生成或者從 ReStructuredText(ReST) 標記生成的文檔, -比如這個文檔,它包含內核內部API的全面介紹以及如何妥善處理加鎖的規則。所有 -這些文檔都可以通過運行以下命令從內核代碼中生成爲PDF或HTML文檔:: +核心還擁有大量從程式碼自動產生或者從 ReStructuredText(ReST) 標記產生的文件, +比如這個文件,它包含核心內部API的全面介紹以及如何妥善處理加鎖的規則。所有 +這些文件都可以透過執行以下命令從核心程式碼中產生為PDF或HTML文件:: make pdfdocs make htmldocs -ReST格式的文檔會生成在 Documentation/output. 目錄中。 -它們也可以用下列命令生成 LaTeX 和 ePub 格式文檔:: +ReST格式的文件會產生在 Documentation/output. 目錄中。 +它們也可以用下列命令產生 LaTeX 和 ePub 格式文件:: make latexdocs make epubdocs -如何成爲內核開發者 +如何成為核心開發者 ------------------ -如果你對Linux內核開發一無所知,你應該訪問「Linux內核新手」計劃: +如果你對Linux核心開發一無所知,你應該造訪「Linux核心新手」計劃: https://kernelnewbies.org -它擁有一個可以問各種最基本的內核開發問題的郵件列表(在提問之前一定要記得 +它擁有一個可以問各種最基本的核心開發問題的郵件列表(在提問之前一定要記得 查找已往的郵件,確認是否有人已經回答過相同的問題)。它還擁有一個可以獲得 -實時反饋的IRC聊天頻道,以及大量對於學習Linux內核開發相當有幫助的文檔。 +實時反饋的IRC聊天頻道,以及大量對於學習Linux核心開發相當有幫助的文件。 -網站簡要介紹了原始碼組織結構、子系統劃分以及目前正在進行的項目(包括內核 -中的和單獨維護的)。它還提供了一些基本的幫助信息,比如如何編譯內核和打補 -丁。 +網站簡要介紹了原始碼組織結構、子系統劃分以及目前正在進行的專案(包括核心 +中的和單獨維護的)。它還提供了一些基本的幫助資訊,比如如何編譯核心和打補丁 +。 -如果你想加入內核開發社區並協助完成一些任務,卻找不到從哪裡開始,可以訪問 -「Linux內核房管員」計劃: +如果你想加入核心開發社群並協助完成一些任務,卻找不到從哪裡開始,可以造訪 +「Linux核心房管員」計劃: https://kernelnewbies.org/KernelJanitors -這是極佳的起點。它提供一個相對簡單的任務列表,列出內核代碼中需要被重新 -整理或者改正的地方。通過和負責這個計劃的開發者們一同工作,你會學到將補丁 -集成進內核的基本原理。如果還沒有決定下一步要做什麼的話,你還可能會得到方 +這是極佳的起點。它提供一個相對簡單的任務列表,列出核心程式碼中需要被重新 +整理或者改正的地方。透過和負責這個計劃的開發者們一同工作,你會學到將補丁 +整合進核心的基本原理。如果還沒有決定下一步要做什麼的話,你還可能會得到方 向性的指點。 -在真正動手修改內核代碼之前,理解要修改的代碼如何運作是必需的。要達到這個 -目的,沒什麼辦法比直接讀代碼更有效了(大多數花招都會有相應的注釋),而且 -一些特製的工具還可以提供幫助。例如,「Linux代碼交叉引用」項目就是一個值得 +在真正動手修改核心程式碼之前,理解要修改的程式碼如何運作是必需的。要達到這個 +目的,沒什麼辦法比直接讀程式碼更有效了(大多數花招都會有相應的註解),而且 +一些特製的工具還可以提供幫助。例如,「Linux程式碼交叉引用」專案就是一個值得 特別推薦的幫助工具,它將原始碼顯示在有編目和索引的網頁上。其中一個更新及 -時的內核源碼庫,可以通過以下地址訪問: +時的核心源碼庫,可以透過以下地址造訪: https://elixir.bootlin.com/ @@ -197,158 +198,159 @@ ReST格式的文檔會生成在 Documentation/output. 目錄中。 開發流程 -------- -目前Linux內核開發流程包括幾個「主內核分支」和很多子系統相關的內核分支。這 +目前Linux核心開發流程包括幾個「主核心分支」和很多子系統相關的核心分支。這 些分支包括: - - Linus 的內核源碼樹 - - 多個主要版本的穩定版內核樹 - - 子系統相關的內核樹 - - linux-next 集成測試樹 + - Linus 的核心源碼樹 + - 多個主要版本的穩定版核心樹 + - 子系統相關的核心樹 + - linux-next 整合測試樹 主線樹 ------ -主線樹是由Linus Torvalds 維護的。你可以在https://kernel.org 網站或者代碼 +主線樹是由Linus Torvalds 維護的。你可以在https://kernel.org 網站或者程式碼 庫中下找到它。它的開發遵循以下步驟: - - 每當一個新版本的內核被發布,爲期兩周的集成窗口將被打開。在這段時間裡 - 維護者可以向Linus提交大段的修改,通常這些修改已經被放到-mm內核中幾個 - 星期了。提交大量修改的首選方式是使用git工具(內核的代碼版本管理工具 - ,更多的信息可以在 https://git-scm.com/ 獲取),不過使用普通補丁也是 + - 每當一個新版本的核心被發布,為期兩周的整合視窗將被開啟。在這段時間裡 + 維護者可以向Linus提交大段的修改,通常這些修改已經被放到-mm核心中幾個 + 星期了。提交大量修改的首選方式是使用git工具(核心的程式碼版本管理工具 + ,更多的資訊可以在 https://git-scm.com/ 獲取),不過使用普通補丁也是 可以的。 - - 兩個星期以後-rc1版本內核發布。之後只有不包含可能影響整個內核穩定性的 - 新功能的補丁才可能被接受。請注意一個全新的驅動程序(或者文件系統)有 - 可能在-rc1後被接受是因爲這樣的修改完全獨立,不會影響其他的代碼,所以 - 沒有造成內核退步的風險。在-rc1以後也可以用git向Linus提交補丁,不過所 + - 兩個星期以後-rc1版本核心發布。之後只有不包含可能影響整個核心穩定性的 + 新功能的補丁才可能被接受。請注意一個全新的驅動程式(或者檔案系統)有 + 可能在-rc1後被接受是因為這樣的修改完全獨立,不會影響其他的程式碼,所以 + 沒有造成核心退步的風險。在-rc1以後也可以用git向Linus提交補丁,不過所 有的補丁需要同時被發送到相應的公衆郵件列表以徵詢意見。 - - 當Linus認爲當前的git源碼樹已經達到一個合理健全的狀態足以發布供人測試 + - 當Linus認為當前的git源碼樹已經達到一個合理健全的狀態足以發布供人測試 時,一個新的-rc版本就會被發布。計劃是每周都發布新的-rc版本。 - - 這個過程一直持續下去直到內核被認爲達到足夠穩定的狀態,持續時間大概是 + - 這個過程一直持續下去直到核心被認為達到足夠穩定的狀態,持續時間大概是 6個星期。 -關於內核發布,值得一提的是Andrew Morton在linux-kernel郵件列表中如是說: - 「沒有人知道新內核何時會被發布,因爲發布是根據已知bug的情況來決定 +關於核心發布,值得一提的是Andrew Morton在linux-kernel郵件列表中如是說: + 「沒有人知道新核心何時會被發布,因為發布是根據已知bug的情況來決定 的,而不是根據一個事先制定好的時間表。」 子系統特定樹 ------------ -各種內核子系統的維護者——以及許多內核子系統開發人員——在原始碼庫中公開了他們 -當前的開發狀態。這樣,其他人就可以看到內核的不同區域發生了什麼。在開發速度 -很快的領域,可能會要求開發人員將提交的內容建立在這樣的子系統內核樹上,這樣 +各種核心子系統的維護者——以及許多核心子系統開發人員——在原始碼庫中公開了他們 +當前的開發狀態。這樣,其他人就可以看到核心的不同區域發生了什麼。在開發速度 +很快的領域,可能會要求開發人員將提交的內容建立在這樣的子系統核心樹上,這樣 就避免了提交與其他已經進行的工作之間的衝突。 -這些存儲庫中的大多數都是Git樹,但是也有其他的scm在使用,或者補丁隊列被發布 -爲Quilt系列。這些子系統存儲庫的地址列在MAINTAINERS文件中。其中許多可以在 +這些儲存庫中的大多數都是Git樹,但是也有其他的scm在使用,或者補丁佇列被發布 +為Quilt系列。這些子系統儲存庫的地址列在MAINTAINERS檔案中。其中許多可以在 https://git.kernel.org/上瀏覽。 在將一個建議的補丁提交到這樣的子系統樹之前,需要對它進行審查,審查主要發生 -在郵件列表上(請參見下面相應的部分)。對於幾個內核子系統,這個審查過程是通 -過工具補丁跟蹤的。Patchwork提供了一個Web界面,顯示補丁發布、對補丁的任何評 -論或修訂,維護人員可以將補丁標記爲正在審查、接受或拒絕。大多數補丁網站都列 +在郵件列表上(請參見下面相應的部分)。對於幾個核心子系統,這個審查過程是通 +過工具補丁追蹤的。Patchwork提供了一個Web介面,顯示補丁發布、對補丁的任何評 +論或修訂,維護人員可以將補丁標記為正在審查、接受或拒絕。大多數補丁網站都列 在 https://patchwork.kernel.org/ -Linux-next 集成測試樹 +Linux-next 整合測試樹 --------------------- -在將子系統樹的更新合併到主線樹之前,需要對它們進行集成測試。爲此,存在一個 -特殊的測試存儲庫,其中幾乎每天都會提取所有子系統樹: +在將子系統樹的更新合併到主線樹之前,需要對它們進行整合測試。為此,存在一個 +特殊的測試儲存庫,其中幾乎每天都會提取所有子系統樹: - https://git.kernel.org/?p=linux/kernel/git/next/linux-next.git + https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git -通過這種方式,Linux-next 對下一個合併階段將進入主線內核的內容給出了一個概要 -展望。非常歡冒險的測試者運行測試Linux-next。 +透過這種方式,Linux-next 對下一個合併階段將進入主線核心的內容給出了一個概要 +展望。非常歡迎冒險的測試者對linux-next進行執行時測試。 -多個主要版本的穩定版內核樹 +多個主要版本的穩定版核心樹 ----------------------------------- -由3個數字組成的內核版本號說明此內核是-stable版本。它們包含內核的相對較小且 -至關重要的修補,這些修補針對安全性問題或者嚴重的內核退步。 +由3個數字組成的核心版本號說明此核心是-stable版本。它們包含核心的相對較小且 +至關重要的修補,這些修補針對安全性問題或者嚴重的核心退步。 -這種版本的內核適用於那些期望獲得最新的穩定版內核並且不想參與測試開發版或 -者實驗版的用戶。 +這種版本的核心適用於那些期望獲得最新的穩定版核心並且不想參與測試開發版或 +者實驗版的使用者。 -穩定版內核樹版本由「穩定版」小組(郵件地址)維護,一般 +穩定版核心樹版本由「穩定版」小組(郵件地址)維護,一般 隔周發布新版本。 -內核源碼中的 :ref:`Documentation/process/stable-kernel-rules.rst ` -文件具體描述了可被穩定版內核接受的修改類型以及發布的流程。 +核心源碼中的 +:ref:`Documentation/process/stable-kernel-rules.rst ` +文件具體描述了可被穩定版核心接受的修改類型以及發布的流程。 報告bug ------- -bugzilla.kernel.org是Linux內核開發者們用來跟蹤內核Bug的網站。我們鼓勵用 -戶在這個工具中報告找到的所有bug。如何使用內核bugzilla的細節請訪問: +bugzilla.kernel.org是Linux核心開發者們用來追蹤核心Bug的網站。我們鼓勵用 +戶在這個工具中報告找到的所有bug。如何使用核心bugzilla的細節請造訪: http://test.kernel.org/bugzilla/faq.html -內核源碼主目錄中的:ref:`admin-guide/reporting-bugs.rst ` -文件里有一個很好的模板。它指導用戶如何報告可能的內核bug以及需要提供哪些信息 -來幫助內核開發者們找到問題的根源。 +核心源碼主目錄中的:ref:`admin-guide/reporting-bugs.rst ` +文件裡有一個很好的模板。它指導使用者如何報告可能的核心bug以及需要提供哪些資訊 +來幫助核心開發者們找到問題的根源。 利用bug報告 ----------- -練習內核開發技能的最好辦法就是修改其他人報告的bug。你不光可以幫助內核變 +練習核心開發技能的最好辦法就是修改其他人報告的bug。你不光可以幫助核心變 得更加穩定,還可以學會如何解決實際問題從而提高自己的技能,並且讓其他開發 -者感受到你的存在。修改bug是贏得其他開發者讚譽的最好辦法,因爲並不是很多 +者感受到你的存在。修改bug是贏得其他開發者讚譽的最好辦法,因為並不是很多 人都喜歡浪費時間去修改別人報告的bug。 -要嘗試修改已知的bug,請訪問 http://bugzilla.kernel.org 網址。 +要嘗試修改已知的bug,請造訪 http://bugzilla.kernel.org 網址。 郵件列表 -------- -正如上面的文檔所描述,大多數的骨幹內核開發者都加入了Linux Kernel郵件列 +正如上面的文件所描述,大多數的骨幹核心開發者都加入了Linux Kernel郵件列 表。如何訂閱和退訂列表的細節可以在這裡找到: - http://vger.kernel.org/vger-lists.html#linux-kernel + https://subspace.kernel.org/subscribing.html 網上很多地方都有這個郵件列表的存檔(archive)。可以使用搜尋引擎來找到這些 存檔。比如: - https://lore.kernel.org/lkml/ + https://lore.kernel.org/linux-kernel/ 在發信之前,我們強烈建議你先在存檔中搜索你想要討論的問題。很多已經被詳細 討論過的問題只在郵件列表的存檔中可以找到。 -大多數內核子系統也有自己獨立的郵件列表來協調各自的開發工作。從 -MAINTAINERS文件中可以找到不同話題對應的郵件列表。 +大多數核心子系統也有自己獨立的郵件列表來協調各自的開發工作。從 +MAINTAINERS檔案中可以找到不同話題對應的郵件列表。 -很多郵件列表架設在kernel.org伺服器上。這些列表的信息可以在這裡找到: +很多郵件列表架設在kernel.org伺服器上。這些列表的資訊可以在這裡找到: - http://vger.kernel.org/vger-lists.html + https://subspace.kernel.org -在使用這些郵件列表時,請記住保持良好的行爲習慣。下面的連結提供了與這些列 +在使用這些郵件列表時,請記住保持良好的行為習慣。下面的連結提供了與這些列 表(或任何其它郵件列表)交流的一些簡單規則,雖然內容有點濫竽充數。 - http://www.albion.com/netiquette/ + https://subspace.kernel.org/etiquette.html 當有很多人回覆你的郵件時,郵件的抄送列表會變得很長。請不要將任何人從抄送 列表中刪除,除非你有足夠的理由這麼做。也不要只回復到郵件列表。請習慣於同 -一封郵件接收兩次(一封來自發送者一封來自郵件列表),而不要試圖通過添加一 +一封郵件接收兩次(一封來自發送者一封來自郵件列表),而不要試圖透過添加一 些奇特的郵件頭來解決這個問題,人們不會喜歡的。 記住保留你所回復內容的上下文和源頭。在你回覆郵件的頂部保留「某某某說到……」 這幾行。將你的評論加在被引用的段落之間而不要放在郵件的頂部。 -如果你在郵件中附帶補丁,請確認它們是可以直接閱讀的純文本(如 +如果你在郵件中附帶補丁,請確認它們是可以直接閱讀的純文字(如 :ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` -文檔中所述)。內核開發者們不希望遇到附件或者被壓縮了的補丁。只有這樣才能 -保證他們可以直接評論你的每行代碼。請確保你使用的郵件發送程序不會修改空格 +文件中所述)。核心開發者們不希望遇到附件或者被壓縮了的補丁。只有這樣才能 +保證他們可以直接評論你的每行程式碼。請確保你使用的郵件發送程式不會修改空格 和制表符。一個防範性的測試方法是先將郵件發送給自己,然後自己嘗試是否可以 -順利地打上收到的補丁。如果測試不成功,請調整或者更換你的郵件發送程序直到 -它正確工作爲止。 +順利地打上收到的補丁。如果測試不成功,請調整或者更換你的郵件發送程式直到 +它正確工作為止。 總而言之,請尊重其他的郵件列表訂閱者。 -同內核社區合作 +同核心社群合作 ---------------- -內核社區的目標就是提供盡善盡美的內核。所以當你提交補丁期望被接受進內核的 +核心社群的目標就是提供盡善盡美的核心。所以當你提交補丁期望被接受進核心的 時候,它的技術價值以及其他方面都將被評審。那麼你可能會得到什麼呢? - 批評 @@ -357,9 +359,9 @@ MAINTAINERS文件中可以找到不同話題對應的郵件列表。 - 要求證明修改的必要性 - 沉默 -要記住,這些是把補丁放進內核的正常情況。你必須學會聽取對補丁的批評和評論, +要記住,這些是把補丁放進核心的正常情況。你必須學會聽取對補丁的批評和評論, 從技術層面評估它們,然後要麼重寫你的補丁要麼簡明扼要地論證修改是不必要 -的。如果你發的郵件沒有得到任何回應,請過幾天後再試一次,因爲有時信件會湮 +的。如果你發的郵件沒有得到任何回應,請過幾天後再試一次,因為有時信件會湮 沒在茫茫信海中。 你不應該做的事情: @@ -369,8 +371,8 @@ MAINTAINERS文件中可以找到不同話題對應的郵件列表。 - 忽略別人的評論 - 沒有按照別人的要求做任何修改就重新提交 -在一個努力追尋最好技術方案的社區里,對於一個補丁有多少好處總會有不同的見 -解。你必須要抱著合作的態度,願意改變自己的觀點來適應內核的風格。或者至少 +在一個努力追尋最好技術方案的社群裡,對於一個補丁有多少好處總會有不同的見 +解。你必須要抱著合作的態度,願意改變自己的觀點來適應核心的風格。或者至少 願意去證明你的想法是有價值的。記住,犯錯誤是允許的,只要你願意朝著正確的 方案去努力。 @@ -378,36 +380,36 @@ MAINTAINERS文件中可以找到不同話題對應的郵件列表。 不會被接受,也不意味著有人和你作對。你只需要改正所有提出的問題然後重新發 送你的補丁。 -內核社區和公司文化的差異 +核心社群和公司文化的差異 ------------------------ -內核社區的工作模式同大多數傳統公司開發隊伍的工作模式並不相同。下面這些例 +核心社群的工作模式同大多數傳統公司開發隊伍的工作模式並不相同。下面這些例 子,可以幫助你避免某些可能發生問題: 用這些話介紹你的修改提案會有好處: - 它同時解決了多個問題 - - 它刪除了2000行代碼 + - 它刪除了2000行程式碼 - 這是補丁,它已經解釋了我想要說明的 - 我在5種不同的體系結構上測試過它…… - 這是一系列小補丁用來…… - - 這個修改提高了普通機器的性能…… + - 這個修改提高了普通機器的效能…… 應該避免如下的說法: - 我們在AIX/ptx/Solaris就是這麼做的,所以這麼做肯定是好的…… - 我做這行已經20年了,所以…… - - 爲了我們公司賺錢考慮必須這麼做 + - 為了我們公司賺錢考慮必須這麼做 - 這是我們的企業產品線所需要的 - - 這裡是描述我觀點的1000頁設計文檔 + - 這裡是描述我觀點的1000頁設計文件 - 這是一個5000行的補丁用來…… - - 我重寫了現在亂七八糟的代碼,這就是…… + - 我重寫了現在亂七八糟的程式碼,這就是…… - 我被規定了最後期限,所以這個補丁需要立刻被接受 -另外一個內核社區與大部分傳統公司的軟體開發隊伍不同的地方是無法面對面地交 -流。使用電子郵件和IRC聊天工具做爲主要溝通工具的一個好處是性別和種族歧視 -將會更少。Linux內核的工作環境更能接受婦女和少數族羣,因爲每個人在別人眼 -里只是一個郵件地址。國際化也幫助了公平的實現,因爲你無法通過姓名來判斷人 -的性別。男人有可能叫李麗,女人也有可能叫王剛。大多數在Linux內核上工作過 +另外一個核心社群與大部分傳統公司的軟體開發隊伍不同的地方是無法面對面地交 +流。使用電子郵件和IRC聊天工具做為主要溝通工具的一個好處是性別和種族歧視 +將會更少。Linux核心的工作環境更能接受婦女和少數族羣,因為每個人在別人眼 +里只是一個郵件地址。國際化也幫助了公平的實作,因為你無法透過姓名來判斷人 +的性別。男人有可能叫李麗,女人也有可能叫王剛。大多數在Linux核心上工作過 並表達過看法的女性對在linux上工作的經歷都給出了正面的評價。 對於一些不習慣使用英語的人來說,語言可能是一個引起問題的障礙。在郵件列表 @@ -418,61 +420,61 @@ MAINTAINERS文件中可以找到不同話題對應的郵件列表。 拆分修改 -------- -Linux內核社區並不喜歡一下接收大段的代碼。修改需要被恰當地介紹、討論並且 +Linux核心社群並不喜歡一下接收大段的程式碼。修改需要被恰當地介紹、討論並且 拆分成獨立的小段。這幾乎完全和公司中的習慣背道而馳。你的想法應該在開發最 開始的階段就讓大家知道,這樣你就可以及時獲得對你正在進行的開發的反饋。這 -樣也會讓社區覺得你是在和他們協作,而不是僅僅把他們當作傾銷新功能的對象。 +樣也會讓社群覺得你是在和他們協作,而不是僅僅把他們當作傾銷新功能的物件。 無論如何,你不要一次性地向郵件列表發送50封信,你的補丁序列應該永遠用不到 這麼多。 將補丁拆開的原因如下: -1) 小的補丁更有可能被接受,因爲它們不需要太多的時間和精力去驗證其正確性。 +1) 小的補丁更有可能被接受,因為它們不需要太多的時間和精力去驗證其正確性。 一個5行的補丁,可能在維護者看了一眼以後就會被接受。而500行的補丁則 需要數個小時來審查其正確性(所需時間隨補丁大小增加大約呈指數級增長)。 - 當出了問題的時候,小的補丁也會讓調試變得非常容易。一個一個補丁地回溯 + 當出了問題的時候,小的補丁也會讓除錯變得非常容易。一個一個補丁地回溯 將會比仔細剖析一個被打上的大補丁(這個補丁破壞了其他東西)容易得多。 2)不光發送小的補丁很重要,在提交之前重新編排、化簡(或者僅僅重新排列) 補丁也是很重要的。 -這裡有內核開發者Al Viro打的一個比方: - 「想像一個老師正在給學生批改數學作業。老師並不希望看到學生爲了得 +這裡有核心開發者Al Viro打的一個比方: + 「想像一個老師正在給學生批改數學作業。老師並不希望看到學生為了得 到正確解法所進行的嘗試和產生的錯誤。他希望看到的是最乾淨最優雅的 解答。好學生了解這點,絕不會把最終解決之前的中間方案提交上去。」 - 內核開發也是這樣。維護者和評審者不希望看到一個人在解決問題時的思 + 核心開發也是這樣。維護者和評審者不希望看到一個人在解決問題時的思 考過程。他們只希望看到簡單和優雅的解決方案。 -直接給出一流的解決方案,和社區一起協作討論尚未完成的工作,這兩者之間似乎 +直接給出一流的解決方案,和社群一起協作討論尚未完成的工作,這兩者之間似乎 很難找到一個平衡點。所以最好儘早開始收集有利於你進行改進的反饋;同時也要 -保證修改分成很多小塊,這樣在整個項目都準備好被包含進內核之前,其中的一部 +保證修改分成很多小塊,這樣在整個專案都準備好被包含進核心之前,其中的一部 分可能會先被接收。 -必須了解這樣做是不可接受的:試圖將未完成的工作提交進內核,然後再找時間修 +必須了解這樣做是不可接受的:試圖將未完成的工作提交進核心,然後再找時間修 復。 證明修改的必要性 ---------------- -除了將補丁拆成小塊,很重要的一點是讓Linux社區了解他們爲什麼需要這樣修改。 +除了將補丁拆成小塊,很重要的一點是讓Linux社群了解他們為什麼需要這樣修改。 你必須證明新功能是有人需要的並且是有用的。 記錄修改 -------- -當你發送補丁的時候,需要特別留意郵件正文的內容。因爲這裡的信息將會做爲補 -丁的修改記錄(ChangeLog),會被一直保留以備大家查閱。它需要完全地描述補丁, +當你發送補丁的時候,需要特別留意郵件正文的內容。因為這裡的資訊將會做為補丁 +的修改記錄(ChangeLog),會被一直保留以備大家查閱。它需要完全地描述補丁, 包括: - - 爲什麼需要這個修改 + - 為什麼需要這個修改 - 補丁的總體設計 - - 實現細節 + - 實作細節 - 測試結果 -想了解它具體應該看起來像什麼,請查閱以下文檔中的「ChangeLog」章節: +想了解它具體應該看起來像什麼,請查閱以下文件中的「ChangeLog」章節: 「The Perfect Patch」 https://www.ozlabs.org/~akpm/stuff/tpp.txt @@ -490,7 +492,7 @@ Dunlap和Gerrit Huizenga完善了應該說和不該說的列表。感謝Pat Moch Linder, Randy Dunlap, Kay Sievers, Vojtech Pavlik, Jan Kara, Josh Boyer, Kees Cook, Andrew Morton, Andi Kleen, Vadim Lobanov, Jesper Juhl, Adrian Bunk, Keri Harris, Frans Pop, David A. Wheeler, Junio Hamano, Michael -Kerrisk和Alex Shepard的評審、建議和貢獻。沒有他們的幫助,這篇文檔是不可 +Kerrisk和Alex Shepard的評審、建議和貢獻。沒有他們的幫助,這篇文件是不可 能完成的。 From 5f0178db86a3836fcc29ce75a210922806bba020 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:45 +0800 Subject: [PATCH 0582/2286] docs/zh_TW: process: localize terminology in embargoed-hardware-issues.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 硬件→硬體, 信息→資訊, ...) and sync with the English original: silicon-vendor wording, real PGP/S/MIME key URLs, updated Linux Foundation IT paragraph, disclosing-party responsibilities, shared-resources rule, the new "Early access" section, and the current process ambassadors list. Also fix the broken zh_Contact cross reference. update to commit cead34ac1ce1 ("MAINTAINERS: update ndesaulniers") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../process/embargoed-hardware-issues.rst | 209 ++++++++++-------- 1 file changed, 122 insertions(+), 87 deletions(-) diff --git a/Documentation/translations/zh_TW/process/embargoed-hardware-issues.rst b/Documentation/translations/zh_TW/process/embargoed-hardware-issues.rst index 93d21fd8891003..611247c816f42b 100644 --- a/Documentation/translations/zh_TW/process/embargoed-hardware-issues.rst +++ b/Documentation/translations/zh_TW/process/embargoed-hardware-issues.rst @@ -5,115 +5,119 @@ :Original: :ref:`Documentation/process/embargoed-hardware-issues.rst ` :Translator: Alex Shi Hu Haowen <2023002089@link.tyut.edu.cn> + Chen-Yu Yeh -被限制的硬件問題 +被限制的硬體問題 ================ 範圍 ---- -導致安全問題的硬件問題與隻影響Linux內核的純軟件錯誤是不同的安全錯誤類別。 +導致安全問題的硬體問題與只影響Linux核心的純軟體錯誤是不同的安全錯誤類別。 -必須區別對待諸如熔燬(Meltdown)、Spectre、L1TF等硬件問題,因爲它們通常會影響 -所有操作系統(“OS”),因此需要在不同的OS供應商、發行版、硬件供應商和其他各方 -之間進行協調。對於某些問題,軟件緩解可能依賴於微碼或固件更新,這需要進一步的 -協調。 +必須區別對待諸如熔毀(Meltdown)、Spectre、L1TF等硬體問題,因為它們通常會影 +響所有作業系統(“OS”),因此需要在不同的OS供應商、發行版、晶片供應商、硬體 +整合商和其他各方之間進行協調。對於某些問題,軟體緩解可能依賴於微碼或韌體更 +新,這需要進一步的協調。 .. _tw_Contact: 接觸 ---- -Linux內核硬件安全小組獨立於普通的Linux內核安全小組。 +Linux核心硬體安全小組獨立於普通的Linux核心安全小組。 -該小組只負責協調被限制的硬件安全問題。Linux內核中純軟件安全漏洞的報告不由該 -小組處理,報告者將被引導至常規Linux內核安全小組(:ref:`Documentation/admin-guide/ -`)聯繫。 +該小組只負責協調被限制的硬體安全問題。Linux核心中純軟體安全漏洞的報告不由 +該小組處理,報告者將被引導至常規Linux核心安全小組( +:ref:`Documentation/admin-guide/`)聯繫。 -可以通過電子郵件 與小組聯繫。這是一份私密的安全 -官名單,他們將幫助您根據我們的文檔化流程協調問題。 +可以透過電子郵件 與小組聯繫。這是一份私密的安全 +官名單,他們將幫助您根據我們的文件化流程協調問題。 -郵件列表是加密的,發送到列表的電子郵件可以通過PGP或S/MIME加密,並且必須使用報告 -者的PGP密鑰或S/MIME證書籤名。該列表的PGP密鑰和S/MIME證書可從 -https://www.kernel.org/.... 獲得。 +郵件列表是加密的,發送到列表的電子郵件可以透過PGP或S/MIME加密,並且必須使 +用報告者的PGP密鑰或S/MIME證書籤名。該列表的PGP密鑰和S/MIME證書可從以下URL +獲得: -雖然硬件安全問題通常由受影響的硬件供應商處理,但我們歡迎發現潛在硬件缺陷的研究 + - PGP: https://www.kernel.org/static/files/hardware-security.asc + - S/MIME: https://www.kernel.org/static/files/hardware-security.crt + +雖然硬體安全問題通常由受影響的晶片供應商處理,但我們歡迎發現潛在硬體缺陷的研究 人員或個人與我們聯繫。 -硬件安全官 +硬體安全官 ^^^^^^^^^^ -目前的硬件安全官小組: +目前的硬體安全官小組: - Linus Torvalds(Linux基金會院士) - - Greg Kroah Hartman(Linux基金會院士) + - Greg Kroah-Hartman(Linux基金會院士) - Thomas Gleixner(Linux基金會院士) 郵件列表的操作 ^^^^^^^^^^^^^^ -處理流程中使用的加密郵件列表託管在Linux Foundation的IT基礎設施上。通過提供這項 -服務,Linux基金會的IT基礎設施安全總監在技術上有能力訪問被限制的信息,但根據他 -的僱傭合同,他必須保密。Linux基金會的IT基礎設施安全總監還負責 kernel.org 基礎 -設施。 +處理流程中使用的加密郵件列表託管在Linux基金會的IT基礎設施上。透過提供這項 +服務,Linux基金會IT營運人員在技術上有能力存取被限制的資訊,但根據其僱傭 +合約必須保密。Linux基金會的IT人員還負責營運和管理kernel.org的其餘基礎設施。 -Linux基金會目前的IT基礎設施安全總監是 Konstantin Ryabitsev。 +Linux基金會目前的IT專案基礎設施總監是 Konstantin Ryabitsev。 保密協議 -------- -Linux內核硬件安全小組不是正式的機構,因此無法簽訂任何保密協議。核心社區意識到 +Linux核心硬體安全小組不是正式的機構,因此無法簽訂任何保密協議。核心社群意識到 這些問題的敏感性,並提供了一份諒解備忘錄。 諒解備忘錄 ---------- -Linux內核社區深刻理解在不同操作系統供應商、發行商、硬件供應商和其他各方之間 -進行協調時,保持硬件安全問題處於限制狀態的要求。 +Linux核心社群深刻理解在不同作業系統供應商、發行商、晶片供應商和其他各方之間 +進行協調時,保持硬體安全問題處於限制狀態的要求。 -Linux內核社區在過去已經成功地處理了硬件安全問題,並且有必要的機制允許在限制 -限制下進行符合社區的開發。 +Linux核心社群在過去已經成功地處理了硬體安全問題,並且有必要的機制允許在限制 +限制下進行符合社群的開發。 -Linux內核社區有一個專門的硬件安全小組負責初始聯繫,並監督在限制規則下處理 +Linux核心社群有一個專門的硬體安全小組負責初始聯繫,並監督在限制規則下處理 此類問題的過程。 -硬件安全小組確定開發人員(領域專家),他們將組成特定問題的初始響應小組。最初 +硬體安全小組確定開發人員(領域專家),他們將組成特定問題的初始響應小組。最初 的響應小組可以引入更多的開發人員(領域專家)以最佳的技術方式解決這個問題。 -所有相關開發商承諾遵守限制規定,並對收到的信息保密。違反承諾將導致立即從當前 -問題中排除,並從所有相關郵件列表中刪除。此外,硬件安全小組還將把違反者排除在 -未來的問題之外。這一後果的影響在我們社區是一種非常有效的威懾。如果發生違規 -情況,硬件安全小組將立即通知相關方。如果您或任何人發現潛在的違規行爲,請立即 -向硬件安全人員報告。 +所有相關開發商承諾遵守限制規定,並對收到的資訊保密。違反承諾將導致立即從當前 +問題中排除,並從所有相關郵件列表中刪除。此外,硬體安全小組還將把違反者排除在 +未來的問題之外。這一後果的影響在我們社群是一種非常有效的威懾。如果發生違規 +情況,硬體安全小組將立即通知相關方。如果您或任何人發現潛在的違規行為,請立即 +向硬體安全人員報告。 流程 ^^^^ -由於Linux內核開發的全球分佈式特性,面對面的會議幾乎不可能解決硬件安全問題。 +由於Linux核心開發的全球分散式特性,面對面的會議幾乎不可能解決硬體安全問題。 由於時區和其他因素,電話會議很難協調,只能在絕對必要時使用。加密電子郵件已被 證明是解決此類問題的最有效和最安全的通信方法。 開始披露 """""""" -披露內容首先通過電子郵件聯繫Linux內核硬件安全小組。此初始聯繫人應包含問題的 -描述和任何已知受影響硬件的列表。如果您的組織製造或分發受影響的硬件,我們建議 -您也考慮哪些其他硬件可能會受到影響。 +披露首先按照上述聯繫方式一節,透過電子郵件聯繫Linux核心硬體安全小組。此 +初始聯繫應包含問題的描述和任何已知受影響晶片的列表。如果您的組織製造或分發 +受影響的硬體,我們建議您也考慮哪些其他硬體可能會受到影響。披露方有責任及時 +聯繫受影響的晶片供應商。 -硬件安全小組將提供一個特定於事件的加密郵件列表,用於與報告者進行初步討論、 +硬體安全小組將提供一個特定於事件的加密郵件列表,用於與報告者進行初步討論、 進一步披露和協調。 -硬件安全小組將向披露方提供一份開發人員(領域專家)名單,在與開發人員確認他們 -將遵守本諒解備忘錄和文件化流程後,應首先告知開發人員有關該問題的信息。這些開發 -人員組成初始響應小組,並在初始接觸後負責處理問題。硬件安全小組支持響應小組, +硬體安全小組將向披露方提供一份開發人員(領域專家)名單,在與開發人員確認他們 +將遵守本諒解備忘錄和文件化流程後,應首先告知開發人員有關該問題的資訊。這些開發 +人員組成初始響應小組,並在初始接觸後負責處理問題。硬體安全小組支援響應小組, 但不一定參與緩解開發過程。 -雖然個別開發人員可能通過其僱主受到保密協議的保護,但他們不能以Linux內核開發 -人員的身份簽訂個別保密協議。但是,他們將同意遵守這一書面程序和諒解備忘錄。 +雖然個別開發人員可能透過其僱主受到保密協議的保護,但他們不能以Linux核心開發 +人員的身份簽訂個別保密協議。但是,他們將同意遵守這一書面程式和諒解備忘錄。 披露方應提供已經或應該被告知該問題的所有其他實體的聯繫人名單。這有幾個目的: - - 披露的實體列表允許跨行業通信,例如其他操作系統供應商、硬件供應商等。 + - 披露的實體列表允許跨行業通信,例如其他作業系統供應商、硬體供應商等。 - 可聯繫已披露的實體,指定應參與緩解措施開發的專家。 @@ -123,67 +127,97 @@ Linux內核社區有一個專門的硬件安全小組負責初始聯繫,並監 披露 """" -披露方通過特定的加密郵件列表向初始響應小組提供詳細信息。 +披露方透過特定的加密郵件列表向初始響應小組提供詳細資訊。 -根據我們的經驗,這些問題的技術文檔通常是一個足夠的起點,最好通過電子郵件進行 +根據我們的經驗,這些問題的技術文件通常是一個足夠的起點,最好透過電子郵件進行 進一步的技術澄清。 緩解開發 """""""" -初始響應小組設置加密郵件列表,或在適當的情況下重新修改現有郵件列表。 +初始響應小組設定加密郵件列表,或在適當的情況下重新修改現有郵件列表。 -使用郵件列表接近於正常的Linux開發過程,並且在過去已經成功地用於爲各種硬件安全 +使用郵件列表接近於正常的Linux開發過程,並且在過去已經成功地用於為各種硬體安全 問題開發緩解措施。 -郵件列表的操作方式與正常的Linux開發相同。發佈、討論和審查修補程序,如果同意, -則應用於非公共git存儲庫,參與開發人員只能通過安全連接訪問該存儲庫。存儲庫包含 -針對主線內核的主開發分支,並根據需要爲穩定的內核版本提供向後移植分支。 +郵件列表的操作方式與正常的Linux開發相同。發布、討論和審查修補程式,如果同意, +則應用於非公共git儲存庫,參與開發人員只能透過安全連接存取該儲存庫。儲存庫包含 +針對主線核心的主開發分支,並根據需要為穩定的核心版本提供向後移植分支。 + +最初的響應小組將根據需要從Linux核心開發人員社群中確定更多的專家。任何相關 +方都可以建議增補專家,每位專家都須符合上述相同的要求。 -最初的響應小組將根據需要從Linux內核開發人員社區中確定更多的專家。引進專家可以 -在開發過程中的任何時候發生,需要及時處理。 +引進專家可以在開發過程中的任何時候發生,需要及時處理。 如果專家受僱於披露方提供的披露清單上的實體或其成員,則相關實體將要求其參與。 否則,披露方將被告知專家參與的情況。諒解備忘錄涵蓋了專家,要求披露方確認參與。 如果披露方有令人信服的理由提出異議,則必須在五個工作日內提出異議,並立即與事件 -小組解決。如果披露方在五個工作日內未作出回應,則視爲默許。 +小組解決。如果披露方在五個工作日內未作出回應,則視為默許。 在確認或解決異議後,專家由事件小組披露,並進入開發過程。 +列表參與者不得在私有郵件列表之外交流該問題。列表參與者在處理補丁時不得使用 +任何共享資源(例如僱主的建置農場、CI系統等)。 + +提前取得 +"""""""" + +在列表上討論和開發的補丁,既不能分發給任何非響應小組成員的個人,也不能分發 +給任何其他組織。 + +為了讓受影響的晶片供應商能夠與其內部團隊和產業夥伴合作進行測試、驗證和 +後勤工作,提供了以下例外: + + 受影響晶片供應商的指定代表可以在任何時候將補丁移交給該晶片供應商 + 的響應小組。該代表必須將移交一事通知核心響應小組。受影響的晶片 + 供應商必須為與其響應小組共享的任何補丁,制定並維護自己的、與本 + 政策一致的文件化安全流程。 + + 晶片供應商的響應小組可以依照該晶片供應商的文件化安全流程,將這些 + 補丁分發給其產業夥伴及其內部團隊。來自產業夥伴的反饋會回到晶片 + 供應商,並由晶片供應商轉達給核心響應小組。 + + 移交給晶片供應商的響應小組後,因晶片供應商的內部團隊或產業夥伴的 + 參與而發生的過早披露,核心響應小組不再承擔任何責任或義務。晶片 + 供應商透過同意本流程來保證這一責任免除。 + 協調發布 """""""" -有關各方將協商限制結束的日期和時間。此時,準備好的緩解措施集成到相關的內核樹中 +有關各方將協商限制結束的日期和時間。此時,準備好的緩解措施整合到相關的核心樹中 併發布。 -雖然我們理解硬件安全問題需要協調限制時間,但限制時間應限制在所有有關各方制定、 -測試和準備緩解措施所需的最短時間內。人爲地延長限制時間以滿足會議討論日期或其他 -非技術原因,會給相關的開發人員和響應小組帶來了更多的工作和負擔,因爲補丁需要 -保持最新,以便跟蹤正在進行的上游內核開發,這可能會造成衝突的更改。 +雖然我們理解硬體安全問題需要協調限制時間,但限制時間應限制在所有有關各方制定、 +測試和準備緩解措施所需的最短時間內。人為地延長限制時間以滿足會議討論日期或其他 +非技術原因,會給相關的開發人員和響應小組帶來了更多的工作和負擔,因為補丁需要 +保持最新,以便追蹤正在進行的上游核心開發,這可能會造成衝突的更改。 CVE分配 """"""" -硬件安全小組和初始響應小組都不分配CVE,開發過程也不需要CVE。如果CVE是由披露方 -提供的,則可用於文檔中。 +硬體安全小組和初始響應小組都不分配CVE,開發過程也不需要CVE。如果CVE是由披露方 +提供的,則可用於文件記錄目的。 流程專使 -------- -爲了協助這一進程,我們在各組織設立了專使,他們可以回答有關報告流程和進一步處理 +為了協助這一流程,我們在各組織設立了專使,他們可以回答有關報告流程和進一步處理 的問題或提供指導。專使不參與特定問題的披露,除非響應小組或相關披露方提出要求。 現任專使名單: ============= ======================================================== - ARM AMD Tom Lendacky - IBM + Ampere Darren Hart + ARM Catalin Marinas + IBM Power Madhavan Srinivasan + IBM Z Christian Borntraeger Intel Tony Luck Qualcomm Trilok Soni + RISC-V Palmer Dabbelt + Samsung Javier González - Microsoft Sasha Levin - VMware + Microsoft James Morris Xen Andrew Cooper Canonical John Johansen @@ -192,27 +226,28 @@ CVE分配 Red Hat Josh Poimboeuf SUSE Jiri Kosina - Amazon Google Kees Cook + + LLVM Nick Desaulniers ============= ======================================================== -如果要將您的組織添加到專使名單中,請與硬件安全小組聯繫。被提名的專使必須完全 -理解和支持我們的過程,並且在Linux內核社區中很容易聯繫。 +如果要將您的組織添加到專使名單中,請與硬體安全小組聯繫。被提名的專使必須完全 +理解和支援我們的過程,並且在Linux核心社群中很容易聯繫。 加密郵件列表 ------------ 我們使用加密郵件列表進行通信。這些列表的工作原理是,發送到列表的電子郵件使用 -列表的PGP密鑰或列表的/MIME證書進行加密。郵件列表軟件對電子郵件進行解密,並 -使用訂閱者的PGP密鑰或S/MIME證書爲每個訂閱者分別對其進行重新加密。有關郵件列表 -軟件和用於確保列表安全和數據保護的設置的詳細信息,請訪問: -https://www.kernel.org/.... +列表的PGP密鑰或列表的S/MIME證書進行加密。郵件列表軟體對電子郵件進行解密,並 +使用訂閱者的PGP密鑰或S/MIME證書為每個訂閱者分別對其進行重新加密。有關郵件列表 +軟體和用於確保列表安全和資料保護的設定的詳細資訊,請見: +https://korg.wiki.kernel.org/userdoc/remail. -關鍵點 -^^^^^^ +列表密鑰 +^^^^^^^^ -初次接觸見 :ref:`zh_Contact`. 對於特定於事件的郵件列表,密鑰和S/MIME證書通過 -特定列表發送的電子郵件傳遞給訂閱者。 +初次接觸見上面的 :ref:`tw_Contact` 一節。 對於特定於事件的郵件列表,密鑰和 +S/MIME證書透過特定列表發送的電子郵件傳遞給訂閱者。 訂閱事件特定列表 ^^^^^^^^^^^^^^^^ @@ -220,13 +255,13 @@ https://www.kernel.org/.... 訂閱由響應小組處理。希望參與通信的披露方將潛在訂戶的列表發送給響應組,以便 響應組可以驗證訂閱請求。 -每個訂戶都需要通過電子郵件向響應小組發送訂閱請求。電子郵件必須使用訂閱服務器 -的PGP密鑰或S/MIME證書籤名。如果使用PGP密鑰,則必須從公鑰服務器獲得該密鑰, -並且理想情況下該密鑰連接到Linux內核的PGP信任網。另請參見: +每個訂戶都需要透過電子郵件向響應小組發送訂閱請求。電子郵件必須使用訂閱者 +的PGP密鑰或S/MIME證書籤名。如果使用PGP密鑰,則必須從公鑰伺服器獲得該密鑰, +並且理想情況下該密鑰連接到Linux核心的PGP信任網。另請參見: https://www.kernel.org/signature.html. -響應小組驗證訂閱者,並將訂閱者添加到列表中。訂閱後,訂閱者將收到來自郵件列表 -的電子郵件,該郵件列表使用列表的PGP密鑰或列表的/MIME證書籤名。訂閱者的電子郵件 -客戶端可以從簽名中提取PGP密鑰或S/MIME證書,以便訂閱者可以向列表發送加密電子 -郵件。 +響應小組驗證訂閱者,並將訂閱者添加到列表中。訂閱後,訂閱者將收到來自郵件列 +表的電子郵件,該郵件列表使用列表的PGP密鑰或列表的S/MIME證書籤名。訂閱者的 +電子郵件客戶端可以從簽名中提取PGP密鑰或S/MIME證書,以便訂閱者可以向列表發 +送加密電子郵件。 From 320b35846fbd2595907a830636889c7b74ce5e7a Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:46 +0800 Subject: [PATCH 0583/2286] docs/zh_TW: process: localize terminology in submitting-patches.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 文件→檔案, 代碼→程式碼, ...) and sync with the English original: subspace.kernel.org list info, interleaved-reply etiquette section, reworked Acked-by semantics with "# Suffix", tagging-people permission rules, the Assisted-by tag, canonical patch format subsections with affiliation format and previous-version links, and the b4 tooling section. Cross references now point at zh_TW translations and broken zh_-prefixed ref targets are fixed to tw_. update to commit 48c3876a6a6f ("docs: submitting-patches: Clarify that "reviewer" is a person") Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/submitting-patches.rst | 556 ++++++++++-------- 1 file changed, 325 insertions(+), 231 deletions(-) diff --git a/Documentation/translations/zh_TW/process/submitting-patches.rst b/Documentation/translations/zh_TW/process/submitting-patches.rst index 64de92c07906af..b780f1539b46b2 100644 --- a/Documentation/translations/zh_TW/process/submitting-patches.rst +++ b/Documentation/translations/zh_TW/process/submitting-patches.rst @@ -15,37 +15,38 @@ - 李陽 Li Yang - 王聰 Wang Cong - 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + - 葉宸佑 Chen-Yu Yeh -提交補丁:如何讓你的改動進入內核 +提交補丁:如何讓你的改動進入核心 ================================ -對於想要將改動提交到 Linux 內核的個人或者公司來說,如果不熟悉“規矩”, -提交的流程會讓人畏懼。本文檔包含了一系列建議,可以大大提高你 +對於想要將改動提交到 Linux 核心的個人或者公司來說,如果不熟悉“規矩”, +提交的流程會讓人畏懼。本文件包含了一系列建議,可以大大提高你 的改動被接受的機會. -本文檔以較爲簡潔的行文給出了大量建議。關於內核開發流程如何進行的詳細信息, -參見: Documentation/translations/zh_CN/process/development-process.rst 。 -Documentation/translations/zh_CN/process/submit-checklist.rst 給出了一系列 +本文件以較為簡潔的行文給出了大量建議。關於核心開發流程如何進行的詳細資訊, +參見: Documentation/translations/zh_TW/process/development-process.rst 。 +Documentation/translations/zh_TW/process/submit-checklist.rst 給出了一系列 提交補丁之前要檢查的事項。設備樹相關的補丁,請參閱 Documentation/devicetree/bindings/submitting-patches.rst 。 -本文檔假設您正在使用 ``git`` 準備你的補丁。如果您不熟悉 ``git`` ,最好學習 -如何使用它,這將使您作爲內核開發人員的生活變得更加輕鬆。 +本文件假設您正在使用 ``git`` 準備你的補丁。如果您不熟悉 ``git`` ,最好學習 +如何使用它,這將使您作為核心開發人員的生活變得更加輕鬆。 -部分子系統和維護人員的樹有一些關於其工作流程和要求的額外信息,請參閱 +部分子系統和維護人員的樹有一些關於其工作流程和要求的額外資訊,請參閱 Documentation/process/maintainer-handbooks.rst 。 獲取當前源碼樹 -------------- -如果您手頭沒有當前內核源代碼的存儲庫,請使用 ``git`` 獲取一份。您需要先獲取 -主線存儲庫,它可以通過以下命令拉取:: +如果您手頭沒有當前核心原始程式碼的儲存庫,請使用 ``git`` 獲取一份。您需要先獲取 +主線儲存庫,它可以透過以下命令拉取:: git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 但是,請注意,您可能不想直接針對主線樹進行開發。大多數子系統維護人員運 -行自己的樹,並希望看到針對這些樹準備的補丁。請參見MAINTAINERS文件中子系 +行自己的樹,並希望看到針對這些樹準備的補丁。請參見MAINTAINERS檔案中子系 統的 **T:** 項以查找該樹,或者直接詢問維護者該樹是否未在其中列出。 .. _tw_describe_changes: @@ -57,35 +58,37 @@ Documentation/process/maintainer-handbooks.rst 。 的問題激勵您完成這項工作。說服審閱者相信有一個問題值得解決,讓他們讀完第一段 後就能明白這一點。 -描述用戶可見的影響。直接崩潰和鎖定是相當有說服力的,但並不是所有的錯誤都那麼 -明目張膽。即使在代碼審閱期間發現了這個問題,也要描述一下您認爲它可能對用戶產 -生的影響。請記住,大多數Linux安裝運行的內核來自二級穩定樹或特定於供應商/產品 -的樹,只從上游精選特定的補丁,因此請包含任何可以幫助您將更改定位到下游的內容: -觸發的場景、DMESG的摘錄、崩潰描述、性能迴歸、延遲尖峯、鎖定等。 +描述使用者可見的影響。直接崩潰和鎖定是相當有說服力的,但並不是所有的錯誤都 +那麼明目張膽。即使在程式碼審閱期間發現了這個問題,也要描述一下您認為它可能 +對使用者產生的影響。請記住,大多數Linux安裝執行的核心來自二級穩定樹或特定 +於供應商/產品的樹,只從上游精選特定的補丁,因此請包含任何可以幫助您將更改 +定位到下游的內容:觸發的場景、DMESG的摘錄、崩潰描述、效能迴歸、延遲尖峯、 +鎖定等。 -質量優化和權衡。如果您聲稱在性能、內存消耗、堆棧佔用空間或二進制大小方面有所 -改進,請包括支持它們的數據。但也要描述不明顯的成本。優化通常不是零成本的,而是 -在CPU、內存和可讀性之間進行權衡;或者,做探索性的工作,在不同的工作負載之間進 -行權衡。請描述優化的預期缺點,以便審閱者可以權衡成本和收益。 +品質最佳化和權衡。如果您聲稱在效能、記憶體消耗、堆疊佔用空間或二進位大小方 +面有所改進,請包括支援它們的資料。但也要描述不明顯的成本。最佳化通常不是零 +成本的,而是在CPU、記憶體和可讀性之間進行權衡;或者,做探索性的工作,在不 +同的工作負載之間進行權衡。請描述最佳化的預期缺點,以便審閱者可以權衡成本和 +收益。 提出問題之後,就要詳細地描述一下您實際在做的技術細節。對於審閱者來說,用簡練的 -英語描述代碼的變化是很重要的,以驗證代碼的行爲是否符合您的意圖。 +英語描述程式碼的變化是很重要的,以驗證程式碼的行為是否符合您的意圖。 -如果您將補丁描述寫成“標準格式”,可以很容易地作爲“提交日誌”放入Linux的源代 +如果您將補丁描述寫成“標準格式”,可以很容易地作為“提交日誌”放入Linux的源代 碼管理系統 ``git`` 中,那麼維護人員將非常感謝您。 -參見 :ref:`zh_the_canonical_patch_format` 。 +參見 :ref:`tw_the_canonical_patch_format` 。 每個補丁只解決一個問題。如果你的描述開始變長,這就表明你可能需要拆分你的補丁。 -請見 :ref:`zh_split_changes` 。 +請見 :ref:`tw_split_changes` 。 -提交或重新提交補丁或補丁系列時,請包括完整的補丁說明和理由。不要 -只說這是補丁(系列)的第幾版。不要期望子系統維護人員引用更早的補丁版本或引用 -URL來查找補丁描述並將其放入補丁中。也就是說,補丁(系列)及其描述應該是獨立的。 -這對維護人員和審閱者都有好處。一些審閱者可能甚至沒有收到補丁的早期版本。 +提交或重新提交補丁或補丁系列時,請包括完整的補丁說明和理由。不要只說這是補丁 +(系列)的第幾版。不要期望子系統維護人員引用更早的補丁版本或引用URL來查 +找補丁描述並將其放入補丁中。也就是說,補丁(系列)及其描述應該是獨立的。這 +對維護人員和審閱者都有好處。一些審閱者可能甚至沒有收到補丁的早期版本。 用祈使句描述你的變更,例如“make xyzzy do frotz”而不是“[This patch]make -xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令代碼庫改變 -它的行爲一樣。 +xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令程式碼庫改變 +它的行為一樣。 如果您想要引用一個特定的提交,不要只引用提交的SHA-1 ID。還請包括提交的一行 摘要,以便於審閱者瞭解它是關於什麼的。例如:: @@ -95,38 +98,38 @@ xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令 platform_set_drvdata(), but left the variable "dev" unused, delete it. -您還應該確保至少使用前12位SHA-1 ID。內核存儲庫包含 *許多* 對象,使較短的ID -發生衝突的可能性很大。記住,即使現在不會與您的六個字符ID發生衝突,這種情況 +您還應該確保至少使用前12位SHA-1 ID。核心儲存庫包含 *許多* 物件,使較短的ID +發生衝突的可能性很大。記住,即使現在不會與您的六個字元ID發生衝突,這種情況 也可能在五年後改變。 -如果該變更的相關討論或背景信息可以在網上查閱,請加上“Link:”標籤指向它。例如 -你的補丁修復了一個缺陷,需要添加一個帶有URL的標籤指向郵件列表存檔或缺陷跟蹤器 -的相關報告;如果該補丁是由一些早先郵件列表討論或網絡上的記錄引起的,請指向它。 +如果該變更的相關討論或背景資訊可以在網上查閱,請加上“Link:”標籤指向它。例如 +你的補丁修復了一個缺陷,需要添加一個帶有URL的標籤指向郵件列表存檔或缺陷追蹤器 +的相關報告;如果該補丁是由一些早先郵件列表討論或網路上的記錄引起的,請指向它。 -當鏈接到郵件列表存檔時,請首選lore.kernel.org郵件存檔服務。用郵件中的 -``Message-ID`` 頭(去掉尖括號)可以創建鏈接URL。例如:: +當連結到郵件列表存檔時,請首選lore.kernel.org郵件存檔服務。用郵件中的 +``Message-ID`` 頭(去掉尖括號)可以建立連結URL。例如:: - Link: https://lore.kernel.org/r/30th.anniversary.repost@klaava.Helsinki.FI/ + Link: https://lore.kernel.org/30th.anniversary.repost@klaava.Helsinki.FI -請檢查該鏈接以確保可用且指向正確的郵件。 +請檢查該連結以確保可用且指向正確的郵件。 不過,在沒有外部資源的情況下,也要儘量讓你的解釋可理解。除了提供郵件列表存檔或 缺陷的URL之外,還要需要總結該補丁的相關討論要點。 -如果補丁修復了特定提交中的錯誤,例如使用 ``git bisct`` 發現了一個問題,請使用 -帶有前12個字符SHA-1 ID的“Fixes:”標籤和單行摘要。爲了簡化解析腳本,不要將該 -標籤拆分爲多行,標籤不受“75列換行”規則的限制。例如:: +如果補丁修復了特定提交中的錯誤,例如使用 ``git bisect`` 發現了一個問題,請使用 +帶有至少前12個字元SHA-1 ID的“Fixes:”標籤和單行摘要。為了簡化解析腳本,不要將該 +標籤拆分為多行,標籤不受“75列換行”規則的限制。例如:: Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") -下列 ``git config`` 設置可以讓 ``git log``, ``git show`` 增加上述風格的顯示格式:: +下列 ``git config`` 設定可以讓 ``git log``, ``git show`` 增加上述風格的顯示格式:: [core] abbrev = 12 [pretty] fixes = Fixes: %h (\"%s\") -使用示例:: +使用範例:: $ git log -1 --pretty=fixes 54a4f0239f2e Fixes: 54a4f0239f2e ("KVM: MMU: make kvm_mmu_zap_page() return the number of pages it actually freed") @@ -138,41 +141,42 @@ xyzzy do frotz”或“[I]changed xyzzy to do frotz”,就好像你在命令 將每個 **邏輯更改** 拆分成一個單獨的補丁。 -例如,如果你的改動裏同時有bug修正和性能優化,那麼把這些改動拆分到兩個或 -者更多的補丁文件中。如果你的改動包含對API的修改,並且增加了一個使用該新API +例如,如果你的改動裡同時有bug修正和效能最佳化,那麼把這些改動拆分到兩個或 +者更多的補丁檔案中。如果你的改動包含對API的修改,並且增加了一個使用該新API 的驅動,那麼把這些修改分成兩個補丁。 -另一方面,如果你將一個單獨的改動做成多個補丁文件,那麼將它們合併成一個 -單獨的補丁文件。這樣一個邏輯上單獨的改動只被包含在一個補丁文件裏。 +另一方面,如果你將一個單獨的改動做成多個補丁檔案,那麼將它們合併成一個 +單獨的補丁檔案。這樣一個邏輯上單獨的改動只被包含在一個補丁檔案裡。 需要記住的一點是,每個補丁的更改都應易於理解,以便審閱者驗證。每個補丁都應該 對其價值進行闡述。 -如果有一個補丁依賴另外一個補丁來完成它的改動,那沒問題。直接在你的補 -丁描述裏指出 **“這個補丁依賴某補丁”** 就好了。 +如果有一個補丁依賴另外一個補丁來完成它的改動,那沒問題。直接在你的補丁 +描述裡指出 **“這個補丁依賴某補丁”** 就好了。 -在將您的更改劃分爲一系列補丁時,要特別注意確保內核在應用系列中的每個補丁之後 -都能正常構建和運行。使用 ``git bisect`` 來追蹤問題的開發者可能會在任何地方分 +在將您的更改劃分為一系列補丁時,要特別注意確保核心在應用系列中的每個補丁之後 +都能正常建置和執行。使用 ``git bisect`` 來追蹤問題的開發者可能會在任何地方分 割你的補丁系列;如果你在中間引入錯誤,他們不會感謝你。 如果你不能將補丁系列濃縮得更小,那麼每次大約發送出15個補丁,然後等待審閱 -和集成。 +和整合。 檢查你的更改風格 ---------------- -檢查您的補丁是否違反了基本樣式規定,詳細信息參見 -Documentation/translations/zh_CN/process/coding-style.rst +檢查您的補丁是否違反了基本樣式規定,詳細資訊參見 +Documentation/translations/zh_TW/process/coding-style.rst 中找到。如果不這樣做,只會浪費審閱者的時間,並且會導致你的補丁被拒絕,甚至 可能沒有被閱讀。 -一個重要的例外是在將代碼從一個文件移動到另一個文件時——在這種情況下,您不應 -該在移動代碼的同一個補丁中修改移動的代碼。這清楚地描述了移動代碼和您的更改 -的行爲。這大大有助於審閱實際差異,並允許工具更好地跟蹤代碼本身的歷史。 +一個重要的例外是在將程式碼從一個檔案移動到另一個檔案時——在這種情況下,您不 +應該在移動程式碼的同一個補丁中修改移動的程式碼。這清楚地描述了移動程式碼和 +您的更改的行為。這大大有助於審閱實際差異,並允許工具更好地追蹤程式碼本身的 +歷史。 -在提交之前,使用補丁樣式檢查程序檢查補丁(scripts/check patch.pl)。不過, -請注意,樣式檢查程序應該被視爲一個指南,而不是作爲人類判斷的替代品。如果您 -的代碼看起來更好,但有違規行爲,那麼最好別管它。 +在提交之前,使用補丁樣式檢查程式檢查補丁(scripts/checkpatch.pl)。不過, +請注意,樣式檢查程式應該被視為一個指南,而不是作為人類判斷的替代品。如果您 +的程式碼看起來更好,但有違規行為,那麼最好別管它。 檢查者報告三個級別: @@ -180,58 +184,56 @@ Documentation/translations/zh_CN/process/coding-style.rst - WARNING:需要仔細審閱的事項 - CHECK:需要思考的事情 -您應該能夠判斷您的補丁中存在的所有違規行爲。 +您應該能夠判斷您的補丁中存在的所有違規行為。 選擇補丁收件人 -------------- -您應該總是知會任何補丁相應代碼的子系統維護人員;查看 -維護人員文件和源代碼修訂歷史記錄,以瞭解這些維護人員是誰。腳本 +您應該總是知會任何補丁相應程式碼的子系統維護人員;查看 +MAINTAINERS檔案和原始程式碼修訂歷史記錄,以瞭解這些維護人員是誰。腳本 scripts/get_maintainer.pl在這個步驟中非常有用。如果您找不到正在工作的子系統 的維護人員,那麼Andrew Morton(akpm@linux-foundation.org)將充當最後的維護 人員。 -您通常還應該選擇至少一個郵件列表來接收補丁集的副本。linux-kernel@vger.kernel.org -是所有補丁的默認列表,但是這個列表的流量已經導致了許多開發人員不再看它。 -在MAINTAINERS文件中查找子系統特定的列表;您的補丁可能會在那裏得到更多的關注。 -不過,請不要發送垃圾郵件到無關的列表。 +您通常還應該選擇至少一個郵件列表來接收補丁集的副本。linux-kernel@ +vger.kernel.org是所有補丁的預設列表,但是這個列表的流量已經導致了許多開發 +人員不再看它。在MAINTAINERS檔案中查找子系統特定的列表;您的補丁可能會在那 +裡得到更多的關注。不過,請不要發送垃圾郵件到無關的列表。 -許多與內核相關的列表託管在vger.kernel.org上;您可以在 -http://vger.kernel.org/vger-lists.html 上找到它們的列表。不過,也有與內核相關 +許多與核心相關的列表託管在kernel.org上;您可以在 +https://subspace.kernel.org 上找到它們的列表。不過,也有與核心相關 的列表託管在其他地方。 -不要一次發送超過15個補丁到vger郵件列表!!!! - -Linus Torvalds是決定改動能否進入 Linux 內核的最終裁決者。他的郵件地址是 +Linus Torvalds是決定改動能否進入 Linux 核心的最終裁決者。他的郵件地址是 torvalds@linux-foundation.org 。他收到的郵件很多,所以一般來說最好 **別** 給他發郵件。 -如果您有修復可利用安全漏洞的補丁,請將該補丁發送到 security@kernel.org 。對於 -嚴重的bug,可以考慮短期禁令以允許分銷商(有時間)向用戶發佈補丁;在這種情況下, -顯然不應將補丁發送到任何公共列表。 -參見 Documentation/translations/zh_CN/process/security-bugs.rst 。 +如果您有修復可利用安全漏洞的補丁,請將該補丁發送到 security@kernel.org 。 +對於嚴重的bug,可以考慮短期禁令以允許分銷商(有時間)向使用者發布補丁;在 +這種情況下,顯然不應將補丁發送到任何公共列表。參見 +Documentation/process/security-bugs.rst 。 -修復已發佈內核中嚴重錯誤的補丁程序應該抄送給穩定版維護人員,方法是把以下列行 -放進補丁的籤準區(注意,不是電子郵件收件人):: +修復已發布核心中嚴重錯誤的補丁程式應該抄送給穩定版維護人員,方法是把以下列行 +放進補丁的簽署區(注意,不是電子郵件收件人):: Cc: stable@vger.kernel.org 除了本文件之外,您還應該閱讀 -Documentation/translations/zh_CN/process/stable-kernel-rules.rst 。 +Documentation/translations/zh_TW/process/stable-kernel-rules.rst 。 -如果更改影響到用戶側內核接口,請向手冊頁維護人員(如維護人員文件中所列)發送 -手冊頁補丁,或至少發送更改通知,以便一些信息進入手冊頁。還應將用戶空間API -更改抄送到 linux-api@vger.kernel.org 。 +如果更改影響到使用者側核心介面,請向手冊頁維護人員(如MAINTAINERS檔案中所 +列)發送手冊頁補丁,或至少發送更改通知,以便一些資訊進入手冊頁。還應將使用 +者空間API更改抄送到 linux-api@vger.kernel.org 。 -不要MIME編碼,不要鏈接,不要壓縮,不要附件,只要純文本 +不要MIME編碼,不要連結,不要壓縮,不要附件,只要純文字 ------------------------------------------------------ -Linus 和其他的內核開發者需要閱讀和評論你提交的改動。對於內核開發者來說 +Linus 和其他的核心開發者需要閱讀和評論你提交的改動。對於核心開發者來說 ,可以“引用”你的改動很重要,使用一般的郵件工具,他們就可以在你的 -代碼的任何位置添加評論。 +程式碼的任何位置添加評論。 -因爲這個原因,所有的提交的補丁都是郵件中“內嵌”的。最簡單(和推薦)的方法就 +因為這個原因,所有的提交的補丁都是郵件中“內嵌”的。最簡單(和推薦)的方法就 是使用 ``git send-email`` 。https://git-send-email.io 有 ``git send-email`` 的交互式教程。 @@ -239,30 +241,54 @@ Linus 和其他的內核開發者需要閱讀和評論你提交的改動。對 .. warning:: - 如果你使用剪切-粘貼你的補丁,小心你的編輯器的自動換行功能破壞你的補丁 + 如果你使用剪切-貼上你的補丁,小心你的編輯器的自動換行功能破壞你的補丁 -不要將補丁作爲MIME編碼的附件,不管是否壓縮。很多流行的郵件軟件不 -是任何時候都將MIME編碼的附件當作純文本發送的,這會使得別人無法在你的 -代碼中加評論。另外,MIME編碼的附件會讓Linus多花一點時間來處理,這就 +不要將補丁作為MIME編碼的附件,不管是否壓縮。很多流行的郵件軟體不 +是任何時候都將MIME編碼的附件當作純文字發送的,這會使得別人無法在你的 +程式碼中加評論。另外,MIME編碼的附件會讓Linus多花一點時間來處理,這就 降低了你的改動被接受的可能性。 例外:如果你的郵路損壞了補丁,那麼有人可能會要求你使用MIME重新發送補丁。 -請參閱 Documentation/translations/zh_CN/process/email-clients.rst -以獲取有關配置電子郵件客戶端以使其不受影響地發送補丁的提示。 +請參閱 Documentation/translations/zh_TW/process/email-clients.rst +以獲取有關設定電子郵件客戶端以使其不受影響地發送補丁的提示。 回覆審閱意見 ------------ 你的補丁幾乎肯定會得到審閱者對補丁改進方法的評論(以回覆郵件的形式)。您必須 對這些評論作出回應;讓補丁被忽略的一個好辦法就是忽略審閱者的意見。直接回復郵 -件來回應意見即可。不會導致代碼更改的意見或問題幾乎肯定會帶來註釋或變更日誌的 +件來回應意見即可。不會導致程式碼更改的意見或問題幾乎肯定會帶來註解或變更日誌的 改變,以便下一個審閱者更好地瞭解正在發生的事情。 -一定要告訴審閱者你在做什麼改變,並感謝他們的時間。代碼審閱是一個累人且耗時的 +一定要告訴審閱者你在做什麼改變,並感謝他們的時間。程式碼審閱是一個累人且耗時的 過程,審閱者有時會變得暴躁。即使在這種情況下,也要禮貌地回應並解決他們指出的 問題。當發送下一版時,在封面郵件或獨立補丁里加上 ``patch changelog`` 說明與 -前一版本的不同之處(參見 :ref:`zh_the_canonical_patch_format` )。 +前一版本的不同之處(參見 :ref:`tw_the_canonical_patch_format` )。 + +.. _tw_interleaved_replies: + +在郵件討論中使用裁剪過的交錯式回覆 +---------------------------------- + +在Linux核心開發的討論中,強烈不建議置頂回覆(top-posting)。交錯式(或 +“行內”)回覆使對話更容易理解。更多細節參見: +https://en.wikipedia.org/wiki/Posting_style#Interleaved_style + +正如郵件列表中經常被引用的那樣:: + + A: http://en.wikipedia.org/wiki/Top_post + Q: Where do I find info about this thing called top-posting? + A: Because it messes up the order in which people normally read text. + Q: Why is top-posting such a bad thing? + A: Top-posting. + Q: What is the most annoying thing in e-mail? + +同樣,請裁剪掉所有與你的回覆無關的引文。這使回覆更容易查找,並節省時間和 +空間。更多細節參見: http://daringfireball.net/2007/07/on_top :: + + A: No. + Q: Should I include quotations after my reply? .. _tw_resend_reminders: @@ -271,54 +297,54 @@ Linus 和其他的內核開發者需要閱讀和評論你提交的改動。對 提交更改後,請耐心等待。審閱者是大忙人,可能無法立即審閱您的補丁。 -曾幾何時,補丁曾在沒收到評論的情況下消失在虛空中,但現在開發過程應該更加順利了。 -您應該在一週左右的時間內收到評論;如果沒有收到評論,請確保您已將補丁發送 -到正確的位置。在重新提交或聯繫審閱者之前至少等待一週——在諸如合併窗口之類的 -繁忙時間可能更長。 +曾幾何時,補丁曾在沒收到評論的情況下消失在虛空中,但現在開發過程應該更加順 +利了。您應該在一週左右的時間內收到評論;如果沒有收到評論,請確保您已將補丁 +發送到正確的位置。在重新提交或聯繫審閱者之前至少等待一週——在諸如合併視窗之 +類的繁忙時間可能更長。 在等了幾個星期後,用帶RESEND的主題重發補丁也是可以的:: [PATCH Vx RESEND] sub/sys: Condensed patch summary -當你發佈補丁(系列)修改版的時候,不要加上“RESEND”——“RESEND”只適用於重 +當你發布補丁(系列)修改版的時候,不要加上“RESEND”——“RESEND”只適用於重 新提交之前未經修改的補丁(系列)。 主題中包含 PATCH ---------------- 由於到Linus和linux-kernel的電子郵件流量很高,通常會在主題行前面加上[PATCH] -前綴。這使Linus和其他內核開發人員更容易將補丁與其他電子郵件討論區分開。 +前綴。這使Linus和其他核心開發人員更容易將補丁與其他電子郵件討論區分開。 -``git send-email`` 會自動爲你加上。 +``git send-email`` 會自動為你加上。 簽署你的作品——開發者來源認證 ------------------------------ -爲了加強對誰做了何事的追蹤,尤其是對那些透過好幾層維護者才最終到達的補丁,我 -們在通過郵件發送的補丁上引入了“簽署(sign-off)”流程。 +為了加強對誰做了何事的追蹤,尤其是對那些透過好幾層維護者才最終到達的補丁,我 +們在透過郵件發送的補丁上引入了“簽署(sign-off)”流程。 -“簽署”是在補丁註釋最後的一行簡單文字,認證你編寫了它或者其他 -人有權力將它作爲開放源代碼的補丁傳遞。規則很簡單:如果你能認證如下信息: +“簽署”是在補丁註解最後的一行簡單文字,認證你編寫了它或者其他 +人有權力將它作為開放原始程式碼的補丁傳遞。規則很簡單:如果你能認證如下資訊: 開發者來源認證 1.1 ^^^^^^^^^^^^^^^^^^ -對於本項目的貢獻,我認證如下信息: +對於本專案的貢獻,我認證如下資訊: - (a) 這些貢獻是完全或者部分的由我創建,我有權利以文件中指出 - 的開放源代碼許可證提交它;或者 + (a) 這些貢獻是完全或者部分的由我建立,我有權利以文件中指出 + 的開放原始程式碼許可證提交它;或者 (b) 這些貢獻基於以前的工作,據我所知,這些以前的工作受恰當的開放 - 源代碼許可證保護,而且,根據文件中指出的許可證,我有權提交修改後的貢獻, - 無論是完全還是部分由我創造,這些貢獻都使用同一個開放源代碼許可證 + 原始程式碼許可證保護,而且,根據文件中指出的許可證,我有權提交修改後的貢獻, + 無論是完全還是部分由我創造,這些貢獻都使用同一個開放原始程式碼許可證 (除非我被允許用其它的許可證);或者 (c) 這些貢獻由認證(a),(b)或者(c)的人直接提供給我,而 且我沒有修改它。 - (d) 我理解並同意這個項目和貢獻是公開的,貢獻的記錄(包括我 - 一起提交的個人記錄,包括sign-off)被永久維護並且可以和這個項目 - 或者開放源代碼的許可證同步地再發行。 + (d) 我理解並同意這個專案和貢獻是公開的,貢獻的記錄(包括我 + 一起提交的個人記錄,包括sign-off)被永久維護並且可以和這個專案 + 或者開放原始程式碼的許可證同步地再發行。 那麼加入這樣一行:: @@ -339,33 +365,47 @@ Linus 和其他的內核開發者需要閱讀和評論你提交的改動。對 Signed-off-by: 標籤表示簽名者參與了補丁的開發,或者他/她在補丁的傳遞路徑中。 -如果一個人沒有直接參與補丁的準備或處理,但希望表示並記錄他們對補丁的批准/贊成, -那麼他們可以要求在補丁的變更日誌中添加一個Acked-by:。 +如果一個人沒有直接參與補丁的準備或處理,但希望表示並記錄他們對補丁的批准/ +贊成,那麼他們可以要求在補丁的變更日誌中添加一個Acked-by:。 + +Acked-by: 供以某種方式對受影響程式碼負責或與之相關的人使用。最常見的情況是, +當維護者既沒有貢獻也沒有轉發補丁時,由該維護者使用。 -Acked-by: 通常由受影響代碼的維護者使用,當該維護者既沒有貢獻也沒有轉發補丁時。 +Acked-by: 也可以由其他利益相關者使用,例如具有領域知識的人(例如被修改程式 +碼的原作者)、核心uAPI補丁的使用者空間側審閱者,或某項功能的關鍵使用者。在 +這些情況下,可以視需要加上一個“# 後綴”以澄清其含義:: + + Acked-by: The Stakeholder # As primary user Acked-by: 不像簽署那樣正式。這是一個記錄,確認人至少審閱了補丁,並表示接受。 -因此,補丁合併有時會手動將Acker的“Yep,looks good to me”轉換爲 Acked-By:(但 +因此,補丁合併有時會手動將Acker的“Yep,looks good to me”轉換為 Acked-By:(但 請注意,通常最好要求一個明確的Ack)。 -Acked-by:不一定表示對整個補丁的確認。例如,如果一個補丁影響多個子系統,並且 -有一個來自某個子系統維護者的Acked-By:,那麼這通常表示只確認影響維護者代碼的部 -分。這裏應該仔細判斷。如有疑問,應參考郵件列表存檔中的原始討論。 +Acked-by: 也不如 Reviewed-by: 正式。例如,維護者可以用它表示他們同意補丁 +合入,但可能沒有像提供Reviewed-by:那樣徹底地審閱過補丁。同樣,關鍵使用者 +可能沒有對補丁進行技術審閱,但他們可能對整體方法、功能或面向使用者的介面 +感到滿意。 + +Acked-by:不一定表示對整個補丁的確認。例如,如果一個補丁影響多個子系統,並 +且有一個來自某個子系統維護者的Acked-By:,那麼這通常表示只確認影響維護者程 +式碼的部分。這裡應該仔細判斷。如有疑問,應參考郵件列表存檔中的原始討論。在 +這種情況下也可以使用“# 後綴”來澄清。 如果某人本應有機會對補丁進行評論,但沒有提供此類評論,您可以選擇在補丁中添加 -``Cc:`` 這是唯一可以在沒有被該人明確同意的情況下添加的標籤——但它應該表明 -這個人是在補丁上抄送的。此標籤記錄了討論中包含的潛在利益相關方。 +``Cc:`` 標籤。此標籤記錄了討論中包含的潛在利益相關方。注意,這是僅有的三個 +可以在未經被指名者明確許可的情況下使用的標籤之一(詳見下面的“標記他人需要 +許可”)。 -Co-developed-by: 聲明補丁是由多個開發人員共同創建的;當幾個人在一個補丁上工 -作時,它用於給出共同作者(除了From:所給出的作者之外)。因爲Co-developed-by: +Co-developed-by: 聲明補丁是由多個開發人員共同建立的;當幾個人在一個補丁上工 +作時,它用於給出共同作者(除了From:所給出的作者之外)。因為Co-developed-by: 表示作者身份,所以每個Co-developed-by:必須緊跟在相關合作作者的簽署之後。標準 -簽署程序要求Signed-off-by:標籤的順序應儘可能反映補丁的時間歷史,無論作者是通 +簽署程式要求Signed-off-by:標籤的順序應儘可能反映補丁的時間歷史,無論作者是通 過From:還是Co-developed-by:表明。值得注意的是,最後一個Signed-off-by:必須是 提交補丁的開發人員。 注意,如果From:作者也是電子郵件標題的From:行中列出的人,則From:標籤是可選的。 -被From:作者提交的補丁示例:: +被From:作者提交的補丁範例:: @@ -375,7 +415,7 @@ Co-developed-by: 聲明補丁是由多個開發人員共同創建的;當幾個 Signed-off-by: Second Co-Author Signed-off-by: From Author -被合作開發者提交的補丁示例:: +被合作開發者提交的補丁範例:: From: From Author @@ -392,67 +432,95 @@ Co-developed-by: 聲明補丁是由多個開發人員共同創建的;當幾個 ----------------------------------------------------------------- Reported-by: 給那些發現錯誤並報告錯誤的人致謝,它希望激勵他們在將來再次幫助 -我們。請注意,如果bug是以私有方式報告的,那麼在使用Reported-by標籤之前,請 -先請求許可。此標籤是爲Bug設計的;請不要將其用於感謝功能請求。 +我們。注意,Reported-by標籤是僅有的三個可以在未經被指名者明確許可的情況下 +使用的標籤之一(詳見下面的“標記他人需要許可”)。此標籤是為Bug設計的;請不要 +將其用於感謝功能請求。 Tested-by: 標籤表示補丁已由指定的人(在某些環境中)成功測試。這個標籤通知 -維護人員已經執行了一些測試,爲將來的補丁提供了一種定位測試人員的方法,並彰顯測試人員的功勞。 +維護人員已經執行了一些測試,為將來的補丁提供了一種定位測試人員的方法,並彰 +顯測試人員的功勞。 -Reviewed-by:根據審閱者的監督聲明,表明該補丁已被審閱並被認爲是可接受的: +Reviewed-by:根據審閱者的監督聲明,表明該補丁已被審閱並被認為是可接受的: 審閱者的監督聲明 ^^^^^^^^^^^^^^^^ -通過提供我的Reviewed-by:標籤,我聲明: +透過提供我的Reviewed-by:標籤,我聲明: (a) 我已經對這個補丁進行了一次技術審閱,以評估它是否適合被包含到 - 主線內核中。 + 主線核心中。 (b) 與補丁相關的任何問題、顧慮或問題都已反饋給提交者。我對提交者對 我的評論的回應感到滿意。 - (c) 雖然這一提交可能仍可被改進,但我相信,此時,(1)對內核 + (c) 雖然這一提交可能仍可被改進,但我相信,此時,(1)對核心 進行了有價值的修改,(2)沒有包含爭論中涉及的已知問題。 - (d) 雖然我已經審閱了補丁並認爲它是健全的,但我不會(除非另有明確 - 說明)作出任何保證或擔保它會在任何給定情況下實現其規定的目的 - 或正常運行。 + (d) 雖然我已經審閱了補丁並認為它是健全的,但我不會(除非另有明確 + 說明)作出任何保證或擔保它會在任何給定情況下實作其規定的目的 + 或正常執行。 -Reviewed-by是一種觀點聲明,即補丁是對內核的適當修改,沒有任何遺留的嚴重技術 -問題。任何感興趣的審閱者(完成工作的人)都可以爲一個補丁提供一個Reviewed-by -標籤。此標籤用於向審閱者提供致謝,並通知維護者補丁的審閱進度。 -當Reviewed-by:標籤由已知了解主題區域並執行徹底檢查的審閱者提供時,通常會增加 -補丁進入內核的可能性。 +Reviewed-by是一種觀點聲明,即補丁是對核心的適當修改,沒有任何遺留的嚴重技 +術問題。任何感興趣的審閱者(完成了審閱工作且具有已知身分的人)都可以為一個 +補丁提供一個Reviewed-by標籤。此標籤用於向審閱者提供致謝,並通知維護者補丁 +的審閱進度。當Reviewed-by:標籤由已知了解主題區域並執行徹底檢查的審閱者提供 +時,通常會增加補丁進入核心的可能性。 -一旦從測試人員或審閱者的“Tested-by”和“Reviewed-by”標籤出現在郵件列表中, -作者應在發送下一個版本時將其添加到適用的補丁中。但是,如果補丁在以下版本中發 -生了實質性更改,這些標籤可能不再適用,因此應該刪除。通常,在補丁更改日誌中 -(在 ``---`` 分隔符之後)應該提到刪除某人的測試者或審閱者標籤。 +一旦從測試人員或審閱者的“Tested-by”和“Reviewed-by”標籤出現在郵件列表中,作 +者應在發送下一個版本時將其添加到適用的補丁中。但是,如果補丁在以下版本中發 +生了實質性更改,這些標籤可能不再適用,因此應該刪除。通常,刪除某人的 +Acked-by、Tested-by或Reviewed-by標籤時,應在補丁更改日誌中(在 ``---`` 分 +隔符之後)提及並附上解釋。 Suggested-by: 表示補丁的想法是由指定的人提出的,並確保將此想法歸功於指定的 -人。請注意,未經許可,不得添加此標籤,特別是如果該想法未在公共論壇上發佈。 -也就是說,如果我們勤快地致謝創意提供者,他們將受到鼓舞,很有希望在未來再次 -幫助我們。 +人:如果我們勤快地致謝創意提供者,他們將受到鼓舞,很有希望在未來再次幫助 +我們。注意,這是僅有的三個可以在未經被指名者明確許可的情況下使用的標籤之一 +(詳見下面的“標記他人需要許可”)。 -Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定錯誤的來源,這有助於 -檢查錯誤修復。這個標籤還幫助穩定內核團隊確定應該接收修復的穩定內核版本。這是 -指示補丁修復的錯誤的首選方法。請參閱 :ref:`zh_describe_changes` 瞭解更多信息。 +Fixes: 指示補丁修復了之前提交中的一個缺陷。它可以便於確定問題的來源,這有助於 +檢查錯誤修復。這個標籤還幫助穩定核心團隊確定應該接收修復的穩定核心版本。這是 +指示補丁修復的錯誤的首選方法。請參閱 :ref:`tw_describe_changes` 瞭解更多資訊。 .. note:: - 附加Fixes:標籤不會改變穩定內核規則流程,也不改變所有穩定版補丁抄送 - stable@vger.kernel.org的要求。有關更多信息,請閱讀 - Documentation/translations/zh_CN/process/stable-kernel-rules.rst 。 + 附加Fixes:標籤不會改變穩定核心規則流程,也不改變所有穩定版補丁抄送 + stable@vger.kernel.org的要求。有關更多資訊,請閱讀 + Documentation/translations/zh_TW/process/stable-kernel-rules.rst 。 + +最後,雖然提供標籤是受歡迎的且通常非常受讚賞,但請注意,簽署者(即提交者和 +維護者)可以自行斟酌是否採用所提供的標籤。 + +.. _tw_tagging_people: + +標記他人需要許可 +---------------- + +在補丁中添加上述標籤時要小心:除了Cc:、Reported-by:和Suggested-by:之外, +所有標籤都需要被指名者的明確許可。對於這三個標籤,如果根據lore存檔或提交 +歷史,該人曾以該名字和電子郵件地址對Linux核心做出過貢獻,那麼隱含的許可 +就足夠了——並且對於Reported-by:和Suggested-by:,報告或建議必須是公開作出 +的。注意,就此而言bugzilla.kernel.org是公開場所,但其中使用的電子郵件地址 +是私密的;因此不要在標籤中暴露它們,除非該人在先前的貢獻中使用過。 + +使用Assisted-by: +---------------- + +如果您在建立補丁的過程中使用了任何進階編碼工具,您需要透過添加Assisted-by +標籤來聲明這一使用。不這樣做可能會妨礙您的工作被接受。關於聲明編碼助手的 +細節,請參見 Documentation/process/coding-assistants.rst 。 .. _tw_the_canonical_patch_format: 標準補丁格式 ------------ -本節描述如何格式化補丁本身。請注意,如果您的補丁存儲在 ``Git`` 存儲庫中,則 -可以使用 ``git format-patch`` 進行正確的補丁格式化。但是,這些工具無法創建 -必要的文本,因此請務必閱讀下面的說明。 +本節描述如何格式化補丁本身。請注意,如果您的補丁儲存在 ``Git`` 儲存庫中,則 +可以使用 ``git format-patch`` 進行正確的補丁格式化。但是,這些工具無法建立 +必要的文字,因此請務必閱讀下面的說明。 + +主題行 +^^^^^^ 標準的補丁標題行是:: @@ -460,7 +528,8 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 標準補丁的信體包含如下部分: - - 一個 ``from`` 行指出補丁作者。後跟空行(僅當發送補丁的人不是作者時才需要)。 + - 一個 ``from`` 行指出補丁作者。後跟空行(僅當發送補丁的人不是作者時才需 + 要)。 - 說明文字,每行最長75列,這將被複制到永久變更日誌來描述這個補丁。 @@ -470,30 +539,30 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 - 只包含 ``---`` 的標記線。 - - 任何其他不適合放在變更日誌的註釋。 + - 任何其他不適合放在變更日誌的註解。 - 實際補丁( ``diff`` 輸出)。 標題行的格式,使得對標題行按字母序排序非常的容易——很多郵件客戶端都 -可以支持——因爲序列號是用零填充的,所以按數字排序和按字母排序是一樣的。 +可以支援——因為序列號是用零填充的,所以按數字排序和按字母排序是一樣的。 -郵件標題中的“子系統”標識哪個內核子系統將被打補丁。 +郵件標題中的“子系統”標識哪個核心子系統將被打補丁。 郵件標題中的“一句話概述”扼要的描述郵件中的補丁。“一句話概述” -不應該是一個文件名。對於一個補丁系列(“補丁系列”指一系列的多個相關補 -丁),不要對每個補丁都使用同樣的“一句話概述”。 +不應該是一個檔名。對於一個補丁系列(“補丁系列”指一系列的多個相關補丁 +),不要對每個補丁都使用同樣的“一句話概述”。 -記住郵件的“一句話概述”會成爲該補丁的全局唯一標識。它會進入 ``git`` -的改動記錄裏。然後“一句話概述”會被用在開發者的討論裏,用來指代這個補 -丁。用戶將希望通過搜索引擎搜索“一句話概述”來找到那些討論這個補丁的文 +記住郵件的“一句話概述”會成為該補丁的全域唯一標識。它會進入 ``git`` +的改動記錄裡。然後“一句話概述”會被用在開發者的討論裡,用來指代這個補丁 +。使用者將希望透過搜索引擎搜索“一句話概述”來找到那些討論這個補丁的文 章。當人們在兩三個月後使用諸如 ``gitk`` 或 ``git log --oneline`` 之類 的工具查看數千個補丁時,也會很快看到它。 -出於這些原因,概述必須不超過70-75個字符,並且必須描述補丁的更改以及爲 +出於這些原因,概述必須不超過70-75個字元,並且必須描述補丁的更改以及為 什麼需要補丁。既要簡潔又要描述性很有挑戰性,但寫得好的概述應該這樣。 概述的前綴可以用方括號括起來:“Subject: [PATCH ...] <概述>”。標記 -不被視爲概述的一部分,而是描述應該如何處理補丁。如果補丁的多個版本已發 +不被視為概述的一部分,而是描述應該如何處理補丁。如果補丁的多個版本已發 送出來以響應評審(即“v1,v2,v3”)則必須包含版本號,或包含“RFC”以指示 評審請求。如果一個補丁系列中有四個補丁,那麼各個補丁可以這樣編號:1/4、2/4、 3/4、4/4。這可以確保開發人員瞭解補丁應用的順序,且 @@ -501,42 +570,78 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 一些標題的例子:: - Subject: [patch 2/5] ext2: improve scalability of bitmap searching - Subject: [PATCHv2 001/207] x86: fix eflags tracking + Subject: [PATCH 2/5] ext2: improve scalability of bitmap searching + Subject: [PATCH v2 01/27] x86: fix eflags tracking + Subject: [PATCH v2] sub/sys: Condensed patch summary + Subject: [PATCH v2 M/N] sub/sys: Condensed patch summary + +From行 +^^^^^^ -``From`` 行是信體裏的最上面一行,具有如下格式:: +``From`` 行必須是信體裡的最上面一行,具有如下格式:: From: Patch Author -``From`` 行指明在永久改動日誌裏,誰會被確認爲作者。如果沒有 ``From`` 行,那 -麼郵件頭裏的 ``From:`` 行會被用來決定改動日誌中的作者。 +``From`` 行指明在永久改動日誌裡,誰會被確認為作者。如果沒有 ``From`` 行,那 +麼郵件頭裡的 ``From:`` 行會被用來決定改動日誌中的作者。 -說明文字將會被提交到永久的源代碼改動日誌裏,因此應針對那些早已經不記得和這 -個補丁相關的討論細節的讀者。包括補丁處理的故障症狀(內核日誌消息、oops消息 -等),這對於可能正在搜索提交日誌以查找適用補丁的人特別有用。文本應該寫得如 -此詳細,以便在數週、數月甚至數年後閱讀時,能夠爲讀者提供所需的細節信息,以 -掌握創建補丁的 **原因** 。 +作者可以透過在 ``from`` 行和 ``SoB`` 行中加上組織名稱,來表明其所屬單位 +或工作的贊助者,例如: + + From: Patch Author (Company) + +說明主體 +^^^^^^^^ + +說明文字將會被提交到永久的原始程式碼改動日誌裡,因此應針對那些早已經不記得和這 +個補丁相關的討論細節的讀者。包括補丁處理的故障症狀(核心日誌訊息、oops訊息 +等),這對於可能正在搜索提交日誌以查找適用補丁的人特別有用。文字應該寫得如 +此詳細,以便在數週、數月甚至數年後閱讀時,能夠為讀者提供所需的細節資訊,以 +掌握建立補丁的 **原因** 。 如果一個補丁修復了一個編譯失敗,那麼可能不需要包含 *所有* 編譯失敗; 只要足夠讓搜索補丁的人能夠找到它就行了。與概述一樣,既要簡潔又要描述性。 -``---`` 標記行對於補丁處理工具要找到哪裏是改動日誌信息的結束,是不可缺少 + +.. _tw_backtraces: + +提交訊息中的回溯(Backtraces) +"""""""""""""""""""""""""""""" + +回溯有助於記錄導致問題的呼叫鏈。然而,並非所有回溯都有幫助。例如,早期引導呼 +叫鏈是獨特而明顯的。而逐字複製完整的dmesg輸出則會增加時間戳、模組列表、暫存 +器和堆疊轉儲等分散注意力的資訊。 + +因此,最有用的回溯應該從轉儲中提取相關資訊,以更容易集中在真實問題上。下面是 +一個剪裁良好的回溯範例:: + + unchecked MSR access error: WRMSR to 0xd51 (tried to write 0x0000000000000064) + at rIP: 0xffffffffae059994 (native_write_msr+0x4/0x20) + Call Trace: + mba_wrmsr + update_domains + rdtgroup_mkdir + +附加註解(Commentary) +^^^^^^^^^^^^^^^^^^^^^^ + +``---`` 標記行對於補丁處理工具要找到哪裡是改動日誌資訊的結束,是不可缺少 的。 -對於 ``---`` 標記之後的額外註解,一個好的用途就是用來寫 ``diffstat`` ,用來顯 -示修改了什麼文件和每個文件都增加和刪除了多少行。 ``diffstat`` 對於比較大的補 -丁特別有用。 -使用 ``diffstat`` 的選項 ``-p 1 -w 70`` 這樣文件名就會從內核源代碼樹的目錄開始 -,不會佔用太寬的空間(很容易適合80列的寬度,也許會有一些縮進。) -( ``git`` 默認會生成合適的diffstat。) +對於 ``---`` 標記之後的額外註解,一個好的用途就是用來寫 ``diffstat`` ,用 +來顯示修改了什麼檔案和每個檔案都增加和刪除了多少行。 ``diffstat`` 對於比較 +大的補丁特別有用。使用 ``diffstat`` 的選項 ``-p 1 -w 70`` 這樣檔名就會從核 +心原始程式碼樹的目錄開始,不會佔用太寬的空間(很容易適合80列的寬度,也許會 +有一些縮排。)( ``git`` 預設會產生合適的diffstat。) -其餘那些只適用於當時或者與維護者相關的註解,不合適放到永久的改動日誌裏的,也 -應該放這裏。較好的例子就是 ``補丁更改記錄`` ,記錄了v1和v2版本補丁之間的差異。 +其餘那些只適用於當時或者與維護者相關的註解,不合適放到永久的改動日誌裡的,也 +應該放這裡。較好的例子就是 ``補丁更改記錄`` ,記錄了v1和v2版本補丁之間的差異。 -請將此信息放在將變更日誌與補丁的其餘部分分隔開的 ``---`` 行 **之後** 。版本 -信息不是提交到git樹的變更日誌的一部分。只是供審閱人員使用的附加信息。如果將 +請將此資訊放在將變更日誌與補丁的其餘部分分隔開的 ``---`` 行 **之後** 。版本 +資訊不是提交到git樹的變更日誌的一部分。只是供審閱人員使用的附加資訊。如果將 其放置在提交標記上方,則需要手動交互才能將其刪除。如果它位於分隔線以下,則在 -應用補丁時會自動剝離:: +應用補丁時會自動剝離。如果可以,建議附上指向該補丁先前版本的連結(例如 +lore.kernel.org存檔連結),以幫助審閱者:: ... @@ -545,29 +650,14 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 V2 -> V3: Removed redundant helper function V1 -> V2: Cleaned up coding style and addressed review comments + v2: https://lore.kernel.org/bar + v1: https://lore.kernel.org/foo + path/to/file | 5+++-- ... 在後面的參考資料中能看到正確補丁格式的更多細節。 -.. _tw_backtraces: - -提交消息中的回溯(Backtraces) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -回溯有助於記錄導致問題的調用鏈。然而,並非所有回溯都有幫助。例如,早期引導調 -用鏈是獨特而明顯的。而逐字複製完整的dmesg輸出則會增加時間戳、模塊列表、寄存 -器和堆棧轉儲等分散注意力的信息。 - -因此,最有用的回溯應該從轉儲中提取相關信息,以更容易集中在真實問題上。下面是 -一個剪裁良好的回溯示例:: - - unchecked MSR access error: WRMSR to 0xd51 (tried to write 0x0000000000000064) - at rIP: 0xffffffffae059994 (native_write_msr+0x4/0x20) - Call Trace: - mba_wrmsr - update_domains - rdtgroup_mkdir .. _tw_explicit_in_reply_to: @@ -575,21 +665,21 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 ----------------------------- 手動添加回復補丁的的郵件頭(In-Reply_To:)是有用的(例如,使用 ``git send-email`` ), -可以將補丁與以前的相關討論關聯起來,例如,將bug補丁鏈接到電子郵件和bug報告。 -但是,對於多補丁系列,最好避免在回覆時使用鏈接到該系列的舊版本。這樣, -補丁的多個版本就不會成爲電子郵件客戶端中無法管理的引用樹。如果鏈接有用, -可以使用 https://lore.kernel.org/ 重定向器(例如,在封面電子郵件文本中) -鏈接到補丁系列的早期版本。 +可以將補丁與以前的相關討論關聯起來,例如,將bug補丁連結到電子郵件和bug報告。 +但是,對於多補丁系列,最好避免在回覆時使用連結到該系列的舊版本。這樣, +補丁的多個版本就不會成為電子郵件客戶端中無法管理的引用樹。如果連結有用, +可以使用 https://lore.kernel.org/ 重定向器(例如,在封面電子郵件文字中) +連結到補丁系列的早期版本。 -給出基礎樹信息 +給出基礎樹資訊 -------------- -當其他開發人員收到您的補丁並開始審閱時,知道應該將您的工作放到代碼樹歷史記錄 -中的什麼位置通常很有用。這對於自動化持續集成流水(CI)特別有用,這些流水線試 -圖運行一系列測試,以便在維護人員開始審閱之前確定提交的質量。 +當其他開發人員收到您的補丁並開始審閱時,知道應該將您的工作放到程式碼樹歷史記錄 +中的什麼位置通常很有用。這對於自動化持續整合流水(CI)特別有用,這些流水線試 +圖執行一系列測試,以便在維護人員開始審閱之前確定提交的品質。 -如果您使用 ``git format-patch`` 生成補丁,則可以通過 ``--base`` 標誌在提交中 -自動包含基礎樹信息。使用此選項最簡單、最方便的方法是配合主題分支:: +如果您使用 ``git format-patch`` 產生補丁,則可以透過 ``--base`` 標誌在提交中 +自動包含基礎樹資訊。使用此選項最簡單、最方便的方法是配合主題分支:: $ git checkout -t -b my-topical-branch master Branch 'my-topical-branch' set up to track local branch 'master'. @@ -603,7 +693,7 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 outgoing/... 當你編輯 ``outgoing/0000-cover-letter.patch`` 時,您會注意到在它的最底部有一 -行 ``base-commit:`` 尾註,它爲審閱者和CI工具提供了足夠的信息以正確執行 +行 ``base-commit:`` 尾註,它為審閱者和CI工具提供了足夠的資訊以正確執行 ``git am`` 而不必擔心衝突:: $ git checkout -b patch-review [base-commit-id] @@ -612,7 +702,7 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 Applying: First Commit Applying: ... -有關此選項的更多信息,請參閱 ``man git-format-patch`` 。 +有關此選項的更多資訊,請參閱 ``man git-format-patch`` 。 .. note:: @@ -622,16 +712,23 @@ Fixes: 指示補丁修復了之前提交的一個問題。它可以便於確定 的工作所基於的樹的提交哈希。你應該在封面郵件或系列的第一個補丁中添加它,它應 該放在 ``---`` 行的下面或所有其他內容之後,即只在你的電子郵件簽名之前。 +工具 +---- + +此流程的許多技術層面都可以使用b4自動化,其說明文件見 +。它可以幫助追蹤依賴關係、執行 +checkpatch,以及格式化和發送郵件。 + 參考文獻 -------- Andrew Morton,“完美的補丁”(tpp) -Jeff Garzik,“Linux內核補丁提交格式” +Jeff Garzik,“Linux核心補丁提交格式” -Greg Kroah-Hartman,“如何惹惱內核子系統維護人員” +Greg Kroah-Hartman,“如何惹惱核心子系統維護人員” @@ -644,10 +741,7 @@ Greg Kroah-Hartman,“如何惹惱內核子系統維護人員” -不!!!別再發巨型補丁炸彈給linux-kernel@vger.kernel.org的人們了! - - -內核 Documentation/translations/zh_CN/process/coding-style.rst +核心 Documentation/translations/zh_TW/process/coding-style.rst Linus Torvalds關於標準補丁格式的郵件 From d1bd27cb3af20cdd9e99f7b81303f96d0531c6b8 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:47 +0800 Subject: [PATCH 0584/2286] docs/zh_TW: process: localize terminology in 8.Conclusion.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 代碼→程式碼, 軟件→軟體, ...), normalize character conventions (爲→為, 裏→裡, 啓→啟, 發佈→發布) to match the zh_TW glossary, fix the zh_CN cross-references to zh_TW, and rephrase a few sentences for fluency. Reviewed-by: Dongliang Mu Reviewed-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../zh_TW/process/8.Conclusion.rst | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/Documentation/translations/zh_TW/process/8.Conclusion.rst b/Documentation/translations/zh_TW/process/8.Conclusion.rst index d1634421b62ccd..8f9b4ec6845f64 100644 --- a/Documentation/translations/zh_TW/process/8.Conclusion.rst +++ b/Documentation/translations/zh_TW/process/8.Conclusion.rst @@ -11,45 +11,46 @@ 吳想成 Wu XiangCheng 胡皓文 Hu Haowen <2023002089@link.tyut.edu.cn> + 葉宸佑 Chen-Yu Yeh .. _tw_development_conclusion: -更多信息 +更多資訊 ======== -關於Linux內核開發和相關主題的信息來源很多。首先是在內核源代碼分發中找到的 -文檔目錄。頂級 -:ref:`Documentation/translations/zh_CN/process/howto.rst ` +關於Linux核心開發和相關主題的資訊來源很多。首先是在核心原始碼分發中找到的 +文件目錄。頂級 +:ref:`Documentation/translations/zh_TW/process/howto.rst ` 文件是一個重要的起點; -:ref:`Documentation/translations/zh_CN/process/submitting-patches.rst ` -也是所有內核開發人員都應該閱讀的內容。許多內部內核API都是使用kerneldoc機制 -記錄的;“make htmldocs”或“make pdfdocs”可用於以HTML或PDF格式生成這些文檔 -(儘管某些發行版提供的tex版本會遇到內部限制,無法正確處理文檔)。 +:ref:`Documentation/translations/zh_TW/process/submitting-patches.rst ` +也是所有核心開發人員都應該閱讀的內容。許多內部核心API都是使用kerneldoc機制 +記錄的;“make htmldocs”或“make pdfdocs”可用於以HTML或PDF格式產生這些文件 +(儘管某些發行版提供的tex版本會遇到內部限制,無法正確處理文件)。 -不同的網站在各個細節層次上討論內核開發。本文作者想謙虛地建議用 https://lwn.net/ -作爲來源;有關許多特定內核主題的信息可以通過以下網址的 LWN 內核索引找到: +不同的網站在各個細節層次上討論核心開發。本文作者想謙虛地建議用 https://lwn.net/ +作為來源;有關許多特定核心主題的資訊可以透過以下網址的 LWN 核心索引找到: - http://lwn.net/kernel/index/ + https://lwn.net/Kernel/Index/ -除此之外,內核開發人員的一個寶貴資源是: +除此之外,核心開發人員的一個寶貴資源是: https://kernelnewbies.org/ -當然,也不應該忘記 https://kernel.org/ ,這是內核發佈信息的最終位置。 +當然,也不應該忘記 https://kernel.org/ ,這是核心發布資訊的最終位置。 -關於內核開發有很多書: +關於核心開發有很多書: - 《Linux設備驅動程序》第三版(Jonathan Corbet、Alessandro Rubini和Greg Kroah Hartman) - 線上版本在 http://lwn.net/kernel/ldd3/ + 《Linux設備驅動程序》第三版(Jonathan Corbet、Alessandro Rubini和Greg Kroah-Hartman) + 線上版本在 https://lwn.net/Kernel/LDD3/ - 《Linux內核設計與實現》(Robert Love) + 《Linux核心設計與實現》(Robert Love) - 《深入理解Linux內核》(Daniel Bovet和Marco Cesati) + 《深入理解Linux核心》(Daniel Bovet和Marco Cesati) 然而,所有這些書都有一個共同的缺點:它們上架時就往往有些過時,而且已經上架 -一段時間了。不過,在那裏還是可以找到相當多的好信息。 +一段時間了。不過,在那裡還是可以找到相當多的好資訊。 -有關git的文檔,請訪問: +有關git的文件,請造訪: https://www.kernel.org/pub/software/scm/git/docs/ @@ -58,16 +59,17 @@ 結論 ==== -祝賀所有通過這篇冗長的文檔的人。希望它能夠幫助您理解Linux內核是如何開發的, +祝賀所有通過這篇冗長的文件的人。希望它能夠幫助您理解Linux核心是如何開發的, 以及您如何參與這個過程。 -最後,重要的是參與。任何開源軟件項目都不會超過其貢獻者投入其中的總和。Linux -內核的發展速度和以前一樣快,因爲它得到了大量開發人員的幫助,他們都在努力使它 -變得更好。內核是一個最成功的例子,說明了當成千上萬的人爲了一個共同的目標一起 +最後,重要的是參與。任何開源軟體專案都不會超過其貢獻者投入其中的總和。Linux +核心的發展速度和以前一樣快,因為它得到了大量開發人員的幫助,他們都在努力使它 +變得更好。核心是一個最成功的例子,說明了當成千上萬的人為了一個共同的目標一起 工作時,可以做出什麼。 -不過,內核總是可以從更大的開發人員基礎中獲益。總有更多的工作要做。但是同樣 -重要的是,Linux生態系統中的大多數其他參與者可以通過爲內核做出貢獻而受益。使 -代碼進入主線是提高代碼質量、降低維護和分發成本、提高對內核開發方向的影響程度 -等的關鍵。這是一種共贏的局面。啓動你的編輯器,來加入我們吧;你會非常受歡迎的。 +不過,核心總是可以從更大的開發人員基礎中獲益。總有更多的工作要做。但是同樣 +重要的是,Linux生態系統中的大多數其他參與者可以透過為核心做出貢獻而受益。 +使程式碼進入主線是提高程式碼品質、降低維護和分發成本、提高對核心開發方向的 +影響程度等的關鍵。這是一種共贏的局面。啟動你的編輯器,來加入我們吧;你會非 +常受歡迎的。 From ae325af6b01bcd12f96899cc1d969a39149410f2 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 02:19:48 +0800 Subject: [PATCH 0585/2286] docs/zh_TW: process: localize terminology in index.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize mainland terms to Taiwanese Mandarin (內核→核心, 社區→社群, 文檔→文件, ...) and sync with the reworked English process index: mirror the new section layout (introduction, tools and technical guides, policy guides, dealing with bugs, maintainer information, other material), keep only existing zh_TW translations in the toctrees, and track the still-untranslated documents in TODO lists following the zh_CN convention. update to commit a03ef333fbd6 ("Documentation: security-bugs: explain what is and is not a security bug") Acked-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- .../translations/zh_TW/process/index.rst | 99 ++++++++++++++++--- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/Documentation/translations/zh_TW/process/index.rst b/Documentation/translations/zh_TW/process/index.rst index 65922d9faa2043..3f84437a2ca911 100644 --- a/Documentation/translations/zh_TW/process/index.rst +++ b/Documentation/translations/zh_TW/process/index.rst @@ -10,53 +10,121 @@ :Original: :ref:`Documentation/process/index.rst ` :Translator: Alex Shi Hu Haowen <2023002089@link.tyut.edu.cn> + Chen-Yu Yeh .. _tw_process_index: ======================== -與Linux 內核社區一起工作 +與Linux核心社群一起工作 ======================== -你想成爲Linux內核開發人員嗎?歡迎之至!在學習許多關於內核的技術知識的同時, -瞭解我們社區的工作方式也很重要。閱讀這些文檔可以讓您以更輕鬆的、麻煩更少的 -方式將更改合併到內核。 +你想成為Linux核心開發人員嗎?歡迎之至!在學習許多關於核心的技術知識的同時, +瞭解我們社群的工作方式也很重要。閱讀這些文件可以讓您以更輕鬆的、麻煩更少的 +方式將更改合併到核心。 -以下是每位開發人員都應閱讀的基本指南: +核心開發如何運作的介紹 +---------------------- + +請先閱讀這些文件:理解這裡的內容將使你更順利地進入核心社群。 .. toctree:: :maxdepth: 1 howto - code-of-conduct - code-of-conduct-interpretation + development-process submitting-patches + submit-checklist + +核心開發者的工具與技術指南 +-------------------------- + +這是核心開發者應該熟悉的材料集合。 + +.. toctree:: + :maxdepth: 1 + programming-language coding-style - development-process email-clients - license-rules - kernel-enforcement-statement - kernel-driver-statement + volatile-considered-harmful + +TODOList: -其它大多數開發人員感興趣的社區指南: +* changes +* maintainer-pgp-guide +* applying-patches +* backporting +* adding-syscalls +* botching-up-ioctls +政策指南與開發者聲明 +-------------------- + +這些是我們在核心社群(以及更廣範圍)中努力遵循的規則。 .. toctree:: :maxdepth: 1 - submit-checklist + license-rules + code-of-conduct + code-of-conduct-interpretation + kernel-enforcement-statement + kernel-driver-statement stable-api-nonsense stable-kernel-rules management-style + +TODOList: + +* contribution-maturity-model +* researcher-guidelines +* generated-content +* coding-assistants +* conclave + +處理缺陷 +-------- + +缺陷是無法避免的;正確地處理它們非常重要。下面的文件提供了關於除錯的一般 +建議,並描述了我們處理幾類特殊缺陷——迴歸和安全問題——的政策。 + +.. toctree:: + :maxdepth: 1 + embargoed-hardware-issues -這些是一些總體性技術指南,由於不大好分類而放在這裏: +TODOList: + +* debugging/index +* handling-regressions +* security-bugs +* threat-model +* cve + +維護者資訊 +---------- + +如何找到會接受你補丁的人。 + +TODOList: + +* maintainer-handbooks +* maintainers + +其他材料 +-------- + +這裡是一些大多數開發者會感興趣的其他社群指南: .. toctree:: :maxdepth: 1 magic-number - volatile-considered-harmful + +TODOList: + +* kernel-docs +* deprecated .. only:: subproject and html @@ -64,4 +132,3 @@ ==== * :ref:`genindex` - From 29c6c2ecbac269282c3ccd0f34901f0972e5d8d0 Mon Sep 17 00:00:00 2001 From: Chen-Yu Yeh Date: Fri, 7 Aug 2026 13:53:43 +0800 Subject: [PATCH 0586/2286] docs/zh_TW: Add a glossary for Traditional Chinese translations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Documentation/translations/zh_TW/glossary.rst documenting the Taiwanese Mandarin terminology used by the zh_TW translations, with the corresponding zh_CN terms for cross reference, the character conventions (為/裡/著/才/啟/只/發布), and the convention of not putting a space between CJK and ASCII, which is what the existing zh_TW tree predominantly does. Hook it into the zh_TW index, replacing the existing TODO entry. The zh_CN column is a cross-reference only; every term in it was checked against the actual usage in Documentation/translations/zh_CN/ rather than taken from an external glossary. This is the reference used by the terminology localization of the zh_TW process/ documents in this series. Acked-by: Weijie Yuan Assisted-by: Claude:claude-opus-5 Signed-off-by: Chen-Yu Yeh Signed-off-by: Alex Shi --- Documentation/translations/zh_TW/glossary.rst | 168 ++++++++++++++++++ Documentation/translations/zh_TW/index.rst | 5 +- 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 Documentation/translations/zh_TW/glossary.rst diff --git a/Documentation/translations/zh_TW/glossary.rst b/Documentation/translations/zh_TW/glossary.rst new file mode 100644 index 00000000000000..15ed1be54416b0 --- /dev/null +++ b/Documentation/translations/zh_TW/glossary.rst @@ -0,0 +1,168 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _tw_glossary: + +術語表 +====== + +:作者: 葉宸佑 Chen-Yu Yeh + +本術語表列出Linux核心繁體中文(zh_TW)翻譯所使用的術語。zh_TW翻譯以臺灣 +慣用的資訊術語為準。新增或更新翻譯時,請依照本表選擇用詞,以維持整個 +zh_TW文件樹的一致性;若遇到本表未收錄的術語,歡迎提交補丁補充。 + +下表同時列出常見的簡體中文(zh_CN)譯法,方便從zh_CN翻譯轉換或對照時 +使用。本表的zh_CN欄位為對照參考,其用詞經核對核心文件樹zh_CN譯文中的 +實際用法,非引用外部術語表。 + +一般術語 +-------- + +======================== ========================== ==================== +英文 zh_TW zh_CN(對照) +======================== ========================== ==================== +kernel 核心 内核 +user / user space 使用者/使用者空間 用户/用户空间 +software / hardware 軟體/硬體 软件/硬件 +firmware 韌體 固件 +operating system 作業系統 操作系统 +distribution 發行版 发行版 +community 社群 社区 +project 專案 项目 +documentation 文件 文档 +file 檔案 文件 +folder / directory 資料夾/目錄 文件夹/目录 +default 預設 默认/缺省 +support 支援 支持 +information 資訊 信息 +message 訊息 消息 +data 資料 数据 +quality 品質 质量 +performance 效能 性能 +network 網路 网络 +the Internet 網際網路 因特网 +server 伺服器 服务器 +digital 數位 数字 +computer 電腦 计算机 +integrate 整合 集成 +create 建立 创建 +access 存取 访问 +via / through 透過 通过 +======================== ========================== ==================== + +程式設計術語 +------------ + +======================== ========================== ==================== +英文 zh_TW zh_CN(對照) +======================== ========================== ==================== +source code 原始程式碼/原始碼 源代码 +code 程式碼 代码 +program 程式 程序 +process 行程 进程 +thread 執行緒 线程 +scheduler / schedule 排程器/排程 调度器/调度 +queue 佇列 队列 +memory 記憶體 内存 +cache 快取 缓存 +interface 介面 接口 +port 埠 端口 +register 暫存器 寄存器 +stack 堆疊 堆栈 +function 函式 函数 +function call 呼叫 调用 +callback 回呼 回调 +return value 回傳值 返回值 +macro 巨集 宏 +enum 列舉 枚举 +variable 變數 变量 +pointer 指標 指针 +array 陣列 数组 +linked list 鏈結串列 链表 +loop 迴圈 循环 +declaration 宣告 声明 +identifier 識別字 标识符 +character / string 字元/字串 字符/字符串 +byte 位元組 字节 +boolean 布林 布尔 +binary 二進位 二进制 +object 物件 对象 +type 型別/類型 类型 +module 模組 模块 +component 元件 组件 +build 建置 构建 +compile / assembler 編譯/組譯器 编译/汇编器 +debug 除錯 调试 +optimize 最佳化 优化 +implement 實作 实现 +global / local 全域/區域 全局/局部 +constant 常數 常量 +comment (in code) 註解 注释 +indentation 縮排 缩进 +inline 行內 内联 +generate 產生 生成 +load / loader 載入/載入器 加载/加载器 +export / import 匯出/匯入 导出/导入 +link / linker 連結/連結器 链接/链接器 +repository 儲存庫 存储库/仓库 +distributed 分散式 分布式 +header file 標頭檔 头文件 +configuration file 設定檔 配置文件 +file system 檔案系統 文件系统 +settings / configuration 設定 设置/配置 +======================== ========================== ==================== + +介面與其他術語 +-------------- + +======================== ========================== ==================== +英文 zh_TW zh_CN(對照) +======================== ========================== ==================== +menu 選單 菜单 +window 視窗 窗口 +toolbar 工具列 工具栏 +field 欄位 字段 +icon 圖示 图标 +mouse / cursor 滑鼠/游標 鼠标/光标 +paste 貼上 粘贴 +print / printer 列印/印表機 打印/打印机 +disk (hard disk) 磁碟(硬碟) 磁盘(硬盘) +text / plain text 文字/純文字 文本/纯文本 +mailbox 信箱 邮箱 +account 帳戶/帳號 帐户 +tracking 追蹤 跟踪 +advanced 進階 高级 +free software 自由軟體 自由软件/免费软件 +======================== ========================== ==================== + +特定用語說明 +------------ + +- **access 與 visit**:access(資料、記憶體、資源的存取)譯為「存取」; + visit(造訪網站、頁面或連結)譯為「造訪」。兩者在 zh_CN 皆作「訪問」, + zh_TW 依語意區分。 + +- **Fellow**:現有翻譯將 Linux Foundation Fellow 譯為「院士」。「院士」在 + 臺灣多指中央研究院等學術機構的頭銜,「研究員」也不完全對等,實務上亦常 + 直接保留英文。此譯法尚在討論中,暫維持現有譯法,待社群取得共識後再統一。 + +- **mailing list**:譯為「郵件列表」(zh_CN 亦作「邮件列表」)。 + +- **-stable 與穩定版**:核心的 ``-stable`` 樹保留英文原名 ``-stable``;泛指 + 時譯為「穩定版核心」。既有翻譯中「穩定版」與 ``-stable`` 混用者不強制 + 統一,新增或更新的翻譯請套用此慣例。 + +用字慣例 +-------- + +除術語之外,zh_TW翻譯採用臺灣標準用字,例如「為」(不用「爲」)、「裡」 +(不用「裏」)、「著」(不用「着」)、「才」(不用「纔」)、「啟」(不用 +「啓」)、「只」(不用「隻」)與「發布」(不用「發佈」)。 + +標點符號沿用現有文件樹的全形標點。每行寬度沿用原文慣例(每行約75個半形 +字元寬)。 + +中文與英文、數字相鄰時不加空格,例如「Linux核心」而非「Linux 核心」;此為 +現有zh_TW文件樹的主要慣例。本規則適用於新增的翻譯與改寫過的段落,既有檔案 +不另行回頭修改。行內標記(``literal``、:ref: 等)前後仍依reStructuredText +的需要保留必要的空白。 diff --git a/Documentation/translations/zh_TW/index.rst b/Documentation/translations/zh_TW/index.rst index 660a74d2023cd0..95809012a9efc3 100644 --- a/Documentation/translations/zh_TW/index.rst +++ b/Documentation/translations/zh_TW/index.rst @@ -119,9 +119,10 @@ TODOList: 術語表 ------ -TODOList: +.. toctree:: + :maxdepth: 1 -* glossary + glossary 索引和表格 From 1f5b2ad32c9cca3af00b3f5b91a61510ce30313e Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:43 +0000 Subject: [PATCH 0587/2286] docs/zh_CN: update DAMON design translation Update the Chinese translation of .../mm/damon/design.rst. Update the translation through commit c24a7f497cb5 ("Docs/mm/damon/design: update for DAMOS_QUOTA_NODE_ELIGIBLE_MEM_BP"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../translations/zh_CN/mm/damon/design.rst | 667 +++++++++++++++++- 1 file changed, 635 insertions(+), 32 deletions(-) diff --git a/Documentation/translations/zh_CN/mm/damon/design.rst b/Documentation/translations/zh_CN/mm/damon/design.rst index 16e3db34a7dd18..b7790bcea9b903 100644 --- a/Documentation/translations/zh_CN/mm/damon/design.rst +++ b/Documentation/translations/zh_CN/mm/damon/design.rst @@ -13,35 +13,83 @@ 设计 ==== -可配置的层 -========== -DAMON提供了数据访问监控功能,同时使其准确性和开销可控。基本的访问监控需要依赖于目标地址空间 -并为之优化的基元。另一方面,作为DAMON的核心,准确性和开销的权衡机制是在纯逻辑空间中。DAMON -将这两部分分离在不同的层中,并定义了它的接口,以允许各种低层次的基元实现与核心逻辑的配置。 +.. _damon_design_execution_model_and_data_structures_zh_CN: -由于这种分离的设计和可配置的接口,用户可以通过配置核心逻辑和适当的低级基元实现来扩展DAMON的 -任何地址空间。如果没有提供合适的,用户可以自己实现基元。 +执行模型和数据结构 +================== -例如,物理内存、虚拟内存、交换空间、那些特定的进程、NUMA节点、文件和支持的内存设备将被支持。 -另外,如果某些架构或设备支持特殊的优化访问检查基元,这些基元将很容易被配置。 +与监测相关的信息,包括监测请求规格和基于 DAMON 的操作方案,都存储在名为 +DAMON ``context`` 的数据结构中。DAMON 使用名为 ``kdamond`` 的内核线程执行 +每个上下文。多个 kdamond 可以并行运行,用于不同类型的监测。 +要了解用户空间如何进行配置并启动或停止 DAMON,请参考 :ref:`DAMON sysfs +接口 ` 文档。 -特定地址空间基元的参考实现 -========================== +用户还可以请求暂停和恢复每个上下文的执行。当上下文被暂停时,kdamond 除了应用 +在线参数更新外不做任何事情。 -基本访问监测的低级基元被定义为两部分。: +要了解用户空间如何暂停或恢复每个上下文,请参考 :ref:`DAMON sysfs 上下文 +` 使用文档。 -1. 确定地址空间的监测目标地址范围 -2. 目标空间中特定地址范围的访问检查。 +总体架构 +======== -DAMON目前为物理和虚拟地址空间提供了基元的实现。下面两个小节描述了这些工作的方式。 +DAMON 子系统由三层构成,包括 +- :ref:`操作集 `:实现依赖于给定监测目标地址空间 + 和可用软硬件基元集合的 DAMON 基本操作, +- :ref:`核心 `:在操作集层之上,实现包括监测开销/准确性 + 控制和访问感知系统操作在内的核心逻辑,以及 +- :ref:`模块 `:在核心层之上,实现用于各种目的、并向用户空间 + 提供接口的内核模块。 + + +.. _damon_operations_set_zh_CN: + +操作集层 +======== + +.. _damon_design_configurable_operations_set_zh_CN: + +为了进行数据访问监测和其他低层工作,DAMON 需要一组针对给定目标地址空间、 +并依赖于和优化于该地址空间的特定操作实现。例如,下面两个用于访问监测的操作 +依赖于地址空间。 + +1. 确定该地址空间的监测目标地址范围。 +2. 检查目标空间中特定地址范围的访问。 + +DAMON 将这些实现整合到称为 DAMON 操作集的层中,并定义它和上层之间的接口。 +上层专用于 DAMON 的核心逻辑,包括控制监测准确性和开销的机制。 + +因此,DAMON 可以通过配置核心逻辑使用适当的操作集,轻松扩展到任意地址空间 +和/或可用硬件功能。如果给定目的没有可用操作集,也可以按照层间接口实现新的 +操作集。 + +例如,物理内存、虚拟内存、交换空间、特定进程、NUMA 节点、文件和后端内存设备 +都可以被支持。另外,如果某些架构或设备支持特殊的优化访问检查功能,也可以很容易 +进行配置。 + +DAMON 当前提供以下三个操作集。下面三个小节描述它们如何工作。 + + - vaddr:监测特定进程的虚拟地址空间 + - fvaddr:监测固定虚拟地址范围 + - paddr:监测系统的物理地址空间 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 进行配置, +请参考文档的 :ref:`operations ` 文件部分。 + + +.. _damon_design_vaddr_target_regions_construction_zh_CN: 基于VMA的目标地址范围构造 ------------------------- -这仅仅是针对虚拟地址空间基元的实现。对于物理地址空间,只是要求用户手动设置监控目标地址范围。 +``vaddr`` DAMON 操作集的一种机制,会自动初始化并更新监测目标地址区域, +以覆盖目标进程的整个内存映射。 + +该机制仅用于 ``vaddr`` 操作集。对于 ``fvaddr`` 和 ``paddr`` 操作集, +用户需要手动设置监测目标地址范围。 在进程的超级巨大的虚拟地址空间中,只有小部分被映射到物理内存并被访问。因此,跟踪未映射的地 址区域只是一种浪费。然而,由于DAMON可以使用自适应区域调整机制来处理一定程度的噪声,所以严 @@ -50,7 +98,7 @@ DAMON目前为物理和虚拟地址空间提供了基元的实现。下面两个 出于这个原因,这个实现将复杂的映射转换为三个不同的区域,覆盖地址空间的每个映射区域。这三个 区域之间的两个空隙是给定地址空间中两个最大的未映射区域。这两个最大的未映射区域是堆和最上面 -的mmap()区域之间的间隙,以及在大多数情况下最下面的mmap()区域和堆之间的间隙。因为这些间隙 +的mmap()区域之间的间隙,以及在大多数情况下最下面的mmap()区域和栈之间的间隙。因为这些间隙 在通常的地址空间中是异常巨大的,排除这些间隙就足以做出合理的权衡。下面详细说明了这一点:: @@ -69,23 +117,52 @@ DAMON目前为物理和虚拟地址空间提供了基元的实现。下面两个 找到相关的PTE访问位的方式。虚拟地址的实现是为该地址的目标任务查找页表,而物理地址的实现则 是查找与该地址有映射关系的每一个页表。通过这种方式,实现者找到并清除下一个采样目标地址的位, 并检查该位是否在一个采样周期后再次设置。这可能会干扰其他使用访问位的内核子系统,即空闲页跟 -踪和回收逻辑。为了避免这种干扰,DAMON使其与空闲页面跟踪相互排斥,并使用 ``PG_idle`` 和 -``PG_young`` 页面标志来解决与回收逻辑的冲突,就像空闲页面跟踪那样。 +踪和回收逻辑。DAMON 不做任何事情来避免干扰空闲页跟踪,因此处理这种干扰是系统管理员的责任。 +不过,它会像空闲页跟踪一样,使用 ``PG_idle`` 和 ``PG_young`` 页面标志解决与回收逻辑的冲突。 + + +.. _damon_design_addr_unit_zh_CN: + +地址单位 +-------- +DAMON 核心层使用 ``unsigned long`` 类型表示监测目标地址范围。在某些情况下, +给定操作集的地址空间可能过大,无法用该类型处理。带有大物理地址扩展的 +ARM(32 位)就是一个例子。对于这类情况,提供了一个称为 ``address unit`` 的 +逐操作集参数。它表示一个缩放因子,需要乘以核心层地址,才能计算给定地址 +空间中的真实地址。是否支持 ``address unit`` 参数取决于每个操作集实现。 +``paddr`` 是唯一支持该参数的操作集实现。 -独立于地址空间的核心机制 -======================== +如果该值小于 ``PAGE_SIZE``,则只能使用 2 的幂。 + + +.. _damon_core_logic_zh_CN: + +核心逻辑 +======== + +.. _damon_design_monitoring_zh_CN: + +监测 +---- 下面四个部分分别描述了DAMON的核心机制和五个监测属性,即 ``采样间隔`` 、 ``聚集间隔`` 、 ``更新间隔`` 、 ``最小区域数`` 和 ``最大区域数`` 。 +请注意,``最小区域数`` 必须为 3 或更高。这是因为虚拟地址空间监测被设计为至少处理三个区域, +以容纳普通虚拟地址空间中常见的两个大型未映射区域。虽然对 ``paddr`` 这类其他操作集来说, +这一限制可能并非严格必要,但为了保持一致性,目前会对所有 DAMON 操作强制执行。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置这些属性, +请参考文档的 :ref:`monitoring_attrs ` 部分。 + 访问频率监测 ------------- +~~~~~~~~~~~~ DAMON的输出显示了在给定的时间内哪些页面的访问频率是多少。访问频率的分辨率是通过设置 ``采样间隔`` 和 ``聚集间隔`` 来控制的。详细地说,DAMON检查每个 ``采样间隔`` 对每 -个页面的访问,并将结果汇总。换句话说,计算每个页面的访问次数。在每个 ``聚合间隔`` 过 +个页面的访问,并将结果汇总。换句话说,计算每个页面的访问次数。在每个 ``聚集间隔`` 过 去后,DAMON调用先前由用户注册的回调函数,以便用户可以阅读聚合的结果,然后再清除这些结 果。这可以用以下简单的伪代码来描述:: @@ -103,38 +180,564 @@ DAMON的输出显示了在给定的时间内哪些页面的访问频率是多少 这种机制的监测开销将随着目标工作负载规模的增长而任意增加。 +.. _damon_design_region_based_sampling_zh_CN: + 基于区域的抽样调查 ------------------- +~~~~~~~~~~~~~~~~~~ 为了避免开销的无限制增加,DAMON将假定具有相同访问频率的相邻页面归入一个区域。只要保持 这个假设(一个区域内的页面具有相同的访问频率),该区域内就只需要检查一个页面。因此,对 于每个 ``采样间隔`` ,DAMON在每个区域中随机挑选一个页面,等待一个 ``采样间隔`` ,检 -查该页面是否同时被访问,如果被访问则增加该区域的访问频率。因此,监测开销是可以通过设置 -区域的数量来控制的。DAMON允许用户设置最小和最大的区域数量来进行权衡。 +查该页面是否同时被访问,如果被访问则增加该区域的访问频率计数器。该计数器称为区域的 +``nr_accesses``。因此,监测开销是可以通过设置区域的数量来控制的。DAMON允许用户设置最小 +和最大的区域数量来进行权衡。 然而,如果假设没有得到保证,这个方案就不能保持输出的质量。 +.. _damon_design_adaptive_regions_adjustment_zh_CN: + 适应性区域调整 --------------- +~~~~~~~~~~~~~~ 即使最初的监测目标区域被很好地构建以满足假设(同一区域内的页面具有相似的访问频率),数 据访问模式也会被动态地改变。这将导致监测质量下降。为了尽可能地保持假设,DAMON根据每个 区域的访问频率自适应地进行合并和拆分。 -对于每个 ``聚集区间`` ,它比较相邻区域的访问频率,如果频率差异较小,就合并这些区域。 -然后,在它报告并清除每个区域的聚合接入频率后,如果区域总数不超过用户指定的最大区域数, -它将每个区域拆分为两个或三个区域。 +对于每个 ``聚集间隔`` ,它比较相邻区域的访问频率(``nr_accesses``)。如果差异较小, +并且两个区域的大小之和小于总区域大小除以 ``最小区域数``,DAMON 就会合并这两个区域。如果 +合并后的总区域数仍然高于 ``最大区域数``,它会增大访问频率差异阈值并重复合并,直到满足区域 +数上限,或者阈值高于可能的最大值(``聚集间隔`` 除以 ``采样间隔``)。然后,在它报告并清除 +每个区域的聚合访问频率后,如果拆分后的区域总数不超过用户指定的最大区域数,它会把每个区域 +拆分为两个或三个区域。 通过这种方式,DAMON提供了其最佳的质量和最小的开销,同时保持了用户为其权衡设定的界限。 +.. _damon_design_age_tracking_zh_CN: + +年龄跟踪 +~~~~~~~~ + +通过分析监测结果,用户还可以发现某个区域当前的访问模式已经保持了多长时间。这可用于更好地理解 +访问模式。例如,可以利用访问频率和时近性实现页面放置算法。为了让这种访问模式保持时间分析更容 +易,DAMON 在每个区域中维护另一个名为 ``age`` 的计数器。对于每个 ``聚集间隔``,DAMON 检查该 +区域的大小和访问频率(``nr_accesses``)是否发生了显著变化。如果发生了变化,该计数器会被重置为 +零。否则,该计数器会增加。 + + +.. _damon_design_data_attrs_monitoring_zh_CN: + +数据属性监测 +~~~~~~~~~~~~ + +数据访问模式只是数据属性的一种类型。在某些用例中,用户需要了解更多数据属性 +信息。例如,用户可能需要知道给定热或冷内存区域中有多少由匿名页支持,或者属于 +某个特定 cgroup。为此,提供了数据属性监测功能。 + +使用该功能时,用户可以把感兴趣的数据属性注册到 DAMON :ref:`上下文 +` 中。注册通过为每个属性 +指定一个探针完成。每个探针指定一个规则,用于判断给定内存区域是否具有相关 +属性。该规则由多个过滤器构成。除支持的过滤器类型不同外,这些过滤器的工作方式 +与 :ref:`DAMOS 过滤器 ` 相同。目前,数据属性 +监测只支持 ``anon`` 和 ``memcg`` 过滤器类型。 + +如果注册了这类探针,DAMON 在进行访问 :ref:`采样 +` 时,会对每个区域的采样内存执行这些 +探针。每个 :ref:`聚集间隔 ` 中被识别为具有数据 +属性(命中探针)的样本数量,会计入每区域、每探针的计数器。因此,用户可以在 +每个聚集间隔后读取每区域、每探针的探针命中计数器,从而了解给定 DAMON 区域 +有多少具有特定数据属性。 + +这是基于采样的机制。因此,它很轻量,但输出可能包含一些测量误差。用户应在 +充分理解统计意义的基础上使用输出。 + +另一种更高精度的方式是使用 ``stat`` :ref:`动作 +` 的 :ref:`DAMOS 过滤器 +`,并使用 ``sz_ops_filter_passed`` :ref:`统计 +`。这种方式以页级别提供数据属性信息。不过,由于 +它在页级别操作,开销与内存大小成正比。 动态目标空间更新处理 --------------------- +~~~~~~~~~~~~~~~~~~~~ 监测目标地址范围可以动态改变。例如,虚拟内存可以动态地被映射和解映射。物理内存可以被 热插拔。 由于在某些情况下变化可能相当频繁,DAMON允许监控操作检查动态变化,包括内存映射变化, 并仅在用户指定的时间间隔( ``更新间隔`` )中的每个时间段,将其应用于监控操作相关的 -数据结构,如抽象的监控目标内存区。 \ No newline at end of file +数据结构,如抽象的监控目标内存区。 + +用户空间可以通过 DAMON sysfs 接口和/或跟踪点获取监测结果。更多细节请分别参考 +:ref:`DAMOS 尝试区域 ` 和 :ref:`跟踪点 ` +文档。 + + +.. _damon_design_monitoring_params_tuning_guide_zh_CN: + +监测参数调优指南 +~~~~~~~~~~~~~~~~ + +简而言之,应设置 ``聚集间隔``,使其能够为使用目的捕获有意义数量的访问。访问数量可以用聚集后 +监测结果快照中各区域的 ``nr_accesses`` 和 ``age`` 来衡量。该间隔的默认值 ``100ms`` 在许多情况 +下被证明过短。应按 ``聚集间隔`` 的比例设置 ``采样间隔``。默认推荐比例为 ``1/20``。 + +``聚集间隔`` 应设置为工作负载可在该间隔内为监测目的产生一定数量访问的时间间隔。如果该间隔过短, +只能捕获少量访问。结果是,监测结果会看起来像所有内容都同样只是很少被访问。对许多目的而言,这 +将毫无用处。不过,如果该间隔过长,根据给定目的的时间尺度,区域通过 :ref:`区域调整机制 +` 收敛所需的时间可能过长。如果工作负载实际只产生 +很少访问,而用户却认为监测目的所需的访问数量很高,就可能发生这种情况。对于这种情况,应仔细重 +新考虑每个 ``聚集间隔`` 要捕获的目标访问数量。还要注意,捕获的访问数量不仅用 ``nr_accesses`` +表示,也用 ``age`` 表示。例如,即使监测结果中的每个区域都显示 ``nr_accesses`` 为零,仍然可以 +用 ``age`` 值作为时近性信息来区分区域。 + +因此,``聚集间隔`` 的最佳值取决于工作负载的访问密集程度。用户应根据每个聚集后的监测结果快照 +中捕获的访问数量来调优该间隔。 + +请注意,该间隔的默认值是 100 毫秒,在许多情况下都太短,尤其是在大型系统上。 + +``采样间隔`` 定义每次聚集的分辨率。如果它设置得过大,监测结果会看起来像每个区域都同样很少被 +访问,或者同样频繁地被访问。也就是说,区域将无法根据访问模式区分,因此结果在许多用例中都会无 +用。如果 ``采样间隔`` 过小,它不会降低分辨率,但会增加监测开销。如果它已经足以为给定目的提供 +足够的监测结果分辨率,就不应再不必要地降低。建议将它按 ``聚集间隔`` 的比例设置。默认比例设为 +``1/20``,并且仍然推荐该比例。 + +基于手动调优指南,DAMON 提供了更直观的、基于调节项的间隔自动调优机制。更多细节请参考 +:ref:`该功能的设计文档 `。 + +基于上述指南的示例调优,请参考英文文档 Documentation/mm/damon/monitoring_intervals_tuning_example.rst。 + + +.. _damon_design_monitoring_intervals_autotuning_zh_CN: + +监测间隔自动调优 +~~~~~~~~~~~~~~~~ + +DAMON 基于 :ref:`调优指南的思路 `,提供了 +对 ``采样间隔`` 和 ``聚集间隔`` 的自动调优机制。该调优机制允许用户设置希望 DAMON 在给定时间间隔 +内观测到的访问事件数量目标。用户可以把该目标指定为 DAMON 观测到的访问事件数量与理论最大事件数 +量之间的比例(``access_bp``),该比例在给定数量的聚集中测量(``aggrs``)。 + +DAMON 观测到的访问事件基于 DAMON :ref:`区域假设 ` +按字节粒度计算。例如,如果发现大小为 ``X`` 字节、``nr_accesses`` 为 ``Y`` 的区域,就意味着 +DAMON 观测到了 ``X * Y`` 个访问事件。该区域的理论最大访问事件也以相同方式计算,但会把 ``Y`` +替换为理论最大 ``nr_accesses``,即 ``聚集间隔 / 采样间隔``。 + +该机制会计算 ``aggrs`` 次聚集期间的访问事件比例。如果观测到的访问比例低于或高于目标值,就按 +相同比例增大或减小 ``采样间隔`` 和 ``聚集间隔``。间隔变化比例按当前采样比例与目标比例之间的 +距离决定。 + +用户还可以通过两个参数(``min_sample_us`` 和 ``max_sample_us``)进一步设置调优机制可设置的 +最小和最大 ``采样间隔``。由于调优机制总是以相同比例改变 ``采样间隔`` 和 ``聚集间隔``,所以 +每次调优变化后的最小和最大 ``聚集间隔`` 也可以自动一起设置。 + +该调优默认关闭,需要由用户显式设置。根据经验法则和帕累托原则,推荐使用 4% 的访问样本比例目标。 +请注意,这里应用了两次帕累托原则(80/20 规则)。也就是说,假设以 4%(20% 的 20%)的 DAMON +观测访问事件比例(来源),捕获 64%(80% 乘以 80%)的真实访问事件(结果)。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 使用该功能,请参考文档 +的 :ref:`intervals_goal ` 部分。 + + +.. _damon_design_damos_zh_CN: + +操作方案 +-------- + +数据访问监测的一个常见目的,是实现访问感知的系统效率优化。例如: + + 换出超过两分钟未被访问的内存区域 + +或者: + + 对大于 2 MiB 且显示高访问频率超过一分钟的内存区域使用 THP。 + +这类方案的一种直接做法是基于剖析的优化。也就是说,使用 DAMON 获取工作负载 +或系统的数据访问监测结果,通过分析监测结果找到具有特殊特征的内存区域,并针对 +这些区域进行系统操作变更。这些变更可以通过修改软件(应用程序和/或内核)或向 +其提供建议来完成,也可以通过重新配置硬件来完成。离线和在线两种方式都可以使用。 + +其中,在运行时向内核提供建议会比较灵活且有效,因此被广泛使用。不过,实现这类 +方案可能引入不必要的冗余和低效。如果关注的类型很常见,剖析可能是冗余的。在 +内核和用户空间之间交换包括监测结果和操作建议在内的信息,也可能效率较低。 + +为了让用户通过移交这些工作来减少这种冗余和低效,DAMON 提供了一个名为基于 +数据访问监测的操作方案(DAMOS)的功能。它允许用户以高层次指定期望的方案。 +对于这类规格,DAMON 会开始监测,找到具有感兴趣访问模式的区域,并在每个用户 +指定的时间间隔(称为 ``apply_interval``)把用户期望的操作动作应用于这些区域。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置 +``apply_interval``,请参考文档的 :ref:`apply_interval_us ` +部分。 + + +.. _damon_design_damos_action_zh_CN: + +操作动作 +~~~~~~~~ + +用户希望应用到其感兴趣区域的管理动作。例如,换出、为下一次回收受害者选择提高 +优先级、建议 ``khugepaged`` 折叠或拆分,或者什么也不做而只收集区域统计信息。 + +支持的动作列表在 DAMOS 中定义,但每个动作的实现位于 DAMON 操作集层,因为实现 +通常依赖于监测目标地址空间。例如,换出特定虚拟地址范围的代码会不同于换出物理 +地址范围的代码。监测操作实现集也不被要求支持列表中的所有动作。因此,特定 DAMOS +动作是否可用取决于选择一起使用的操作集。 + +支持的动作列表、含义以及支持每个动作的 DAMON 操作集如下。 + + - ``willneed``:对带有 ``MADV_WILLNEED`` 的区域调用 ``madvise()``。 + ``vaddr`` 和 ``fvaddr`` 操作集支持。 + - ``cold``:对带有 ``MADV_COLD`` 的区域调用 ``madvise()``。 + ``vaddr`` 和 ``fvaddr`` 操作集支持。 + - ``pageout``:回收该区域。 + ``vaddr``、``fvaddr`` 和 ``paddr`` 操作集支持。 + - ``hugepage``:对带有 ``MADV_HUGEPAGE`` 的区域调用 ``madvise()``。 + ``vaddr`` 和 ``fvaddr`` 操作集支持。当禁用 TRANSPARENT_HUGEPAGE 时, + 应用该动作只会失败。 + - ``nohugepage``:对带有 ``MADV_NOHUGEPAGE`` 的区域调用 ``madvise()``。 + ``vaddr`` 和 ``fvaddr`` 操作集支持。当禁用 TRANSPARENT_HUGEPAGE 时, + 应用该动作只会失败。 + - ``collapse``:对带有 ``MADV_COLLAPSE`` 的区域调用 ``madvise()``。 + ``vaddr`` 和 ``fvaddr`` 操作集支持。当禁用 TRANSPARENT_HUGEPAGE 时, + 应用该动作只会失败。 + - ``lru_prio``:提高该区域在其 LRU 链表上的优先级。 + ``paddr`` 操作集支持。 + - ``lru_deprio``:降低该区域在其 LRU 链表上的优先级。 + ``paddr`` 操作集支持。 + - ``migrate_hot``:迁移区域,并优先迁移更热的区域。 + ``vaddr``、``fvaddr`` 和 ``paddr`` 操作集支持。 + - ``migrate_cold``:迁移区域,并优先迁移更冷的区域。 + ``vaddr``、``fvaddr`` 和 ``paddr`` 操作集支持。 + - ``stat``:什么也不做,只统计信息。 + 所有操作集都支持。 + +把除 ``stat`` 外的动作应用到某个区域,会被认为改变了该区域的特征。因此, +当任何这类动作被应用到区域时,DAMOS 会重置这些区域的 age。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置 +动作,请参考文档的 :ref:`action ` 部分。 + + +.. _damon_design_damos_access_pattern_zh_CN: + +目标访问模式 +~~~~~~~~~~~~ + +方案感兴趣的访问模式。这些模式由 DAMON 监测结果提供的属性构成,具体包括 +大小、访问频率和年龄。用户可以通过设置这三个属性的最小值和最大值,描述 +自己感兴趣的访问模式。如果某个区域的这三个属性都在范围内,DAMOS 就把它 +分类为该方案感兴趣的区域之一。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置 +访问模式,请参考文档的 :ref:`access_pattern ` +部分。 + + +.. _damon_design_damos_quotas_zh_CN: + +配额 +~~~~ + +DAMOS 的开销上界控制功能。如果目标访问模式没有正确调优,DAMOS 可能带来高开销。 +例如,如果发现一个具有感兴趣访问模式的巨大内存区域,把方案动作应用到该巨大区域 +的所有页面可能消耗不可接受的大量系统资源。通过调优访问模式来防止这类问题可能 +很有挑战,特别是在工作负载的访问模式高度动态时。 + +为缓解这种情况,DAMOS 提供了一个称为配额的开销上界控制功能。它允许用户指定 +DAMOS 可用于应用动作的时间上限,和/或在用户指定的时间段内可应用动作的最大内存 +区域字节数。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置基本 +配额,请参考文档的 :ref:`quotas ` 部分。 + + +.. _damon_design_damos_quotas_prioritization_zh_CN: + +优先级 +^^^^^^ + +一种在配额限制下作出良好决策的机制。当由于配额限制而无法把动作应用到所有感兴趣 +区域时,DAMOS 会对区域排序,并只把动作应用到优先级足够高、且不会超过配额的区域。 + +每个动作的优先级机制应该不同。例如,很少被访问(更冷)的内存区域应在 page-out +方案动作中被优先处理。相反,对于大页折叠方案动作,更冷的区域应被降低优先级。 +因此,每个动作的优先级机制与动作一起,在各个 DAMON 操作集中实现。 + +虽然实现取决于 DAMON 操作集,但通常会使用区域的访问模式属性来计算优先级。有些 +用户可能希望针对自己的特定场景个性化这些机制。例如,有些用户可能希望该机制更重视 +时近性(``age``)而不是访问频率(``nr_accesses``)。DAMOS 允许用户指定每个访问模式 +属性的权重,并把这些信息传递给底层机制。不过,权重如何被尊重,甚至是否被尊重, +都取决于底层优先级机制实现。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置优先级 +权重,请参考文档的 :ref:`weights ` 部分。 + + +.. _damon_design_damos_quotas_failed_memory_charging_ratio_zh_CN: + +动作失败内存计费比率 +^^^^^^^^^^^^^^^^^^^^ + +对给定区域执行 DAMOS 动作时,区域中某些内存子集可能失败。例如,如果动作是 +``pageout``,而该区域包含一些不可回收页,则把该动作应用到这些页会失败。 +这类失败动作应用消耗的系统资源量通常不同于成功动作应用。对于这类情况,用户可以 +为失败内存设置不同的计费比率。该比率可以用 ``fail_charge_num`` 和 +``fail_charge_denom`` 参数指定。这两个参数分别表示比率的分子和分母。只有当 +``fail_charge_denom`` 不为零时,该功能才启用。 + +例如,假设某个 DAMOS 动作被应用到大小为 1,000 MiB 的区域。该动作只成功应用到 +该区域的 700 MiB。``fail_charge_num`` 和 ``fail_charge_denom`` 分别设置为 ``1`` +和 ``1024``。那么只会计费 700 MiB 和 300 KiB 的大小(``700 MiB + 300 MiB * 1 / +1024``)。 + + +.. _damon_design_damos_quotas_auto_tuning_zh_CN: + +面向目标的反馈驱动自动调优 +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +自动反馈驱动的配额调优。用户可以不设置绝对配额值,而是指定自己感兴趣的指标, +以及希望该指标达到的目标值。随后,DAMOS 会自动调优对应方案的激进程度(配额)。 +例如,如果 DAMOS 未达到目标,DAMOS 会自动增加配额。如果 DAMOS 超过目标,则会 +减少配额。 + +用户可以按需要选择两种这样的调优算法。 + +- ``consist``:基于比例反馈环的算法。尝试找到一个应持续保持的最佳配额,以便持续 + 达成目标。适用于动态和长时间运行环境中的内核内部操作。这是默认选择。如果不确定, + 请使用它。 +- ``temporal``:更直接的算法。尝试尽快达成目标,使用允许的最大配额,但只持续短暂 + 时间。当配额未达成时,该算法持续把配额调优到允许的最大值。一旦配额[超过]达成, + 它就把配额设置为零。适用于需要确定性控制的环境。 + +目标可以用五个参数指定,即 ``target_metric``、``target_value``、``current_value``、 +``nid`` 和 ``path``。自动调优机制尝试使 ``target_metric`` 的 ``current_value`` +等于 ``target_value``。 + +- ``user_input``:用户提供的值。用户可以使用任何自己感兴趣的指标作为该值。用户空间 + 主工作负载的延迟或吞吐量、空闲内存率或内存压力停滞时间(PSI)等系统指标都可以 + 作为示例。请注意,在这种情况下,用户应自己显式设置 ``current_value``。换言之, + 用户应反复提供反馈。 +- ``some_mem_psi_us``:系统范围的 ``some`` 内存压力停滞信息,单位为微秒,从上一次 + 配额重置到下一次配额重置之间测量。DAMOS 自行进行测量,所以用户只需要在初始时 + 设置 ``target_value``。换言之,DAMOS 会自反馈。 +- ``node_mem_used_bp``:特定 NUMA 节点的已用内存比率,单位为 bp(1/10,000)。 +- ``node_mem_free_bp``:特定 NUMA 节点的空闲内存比率,单位为 bp(1/10,000)。 +- ``node_memcg_used_bp``:特定 cgroup 在特定 NUMA 节点上的节点已用内存比率, + 单位为 bp(1/10,000)。 +- ``node_memcg_free_bp``:特定 cgroup 在特定 NUMA 节点上的节点未用内存比率, + 单位为 bp(1/10,000)。 +- ``active_mem_bp``:活跃内存相对活跃 + 非活跃(LRU)内存大小的比率,单位为 bp + (1/10,000)。 +- ``inactive_mem_bp``:非活跃内存相对活跃 + 非活跃(LRU)内存大小的比率,单位为 bp + (1/10,000)。 +- ``node_eligible_mem_bp``:节点中符合方案目标访问模式条件的内存比率,单位为 bp + (1/10,000)。 + +``nid`` 仅在使用 ``node_mem_used_bp``、``node_mem_free_bp``、 +``node_memcg_used_bp``、``node_memcg_free_bp`` 和 ``node_eligible_mem_bp`` 指标时 +必须提供,用于指定具体 NUMA 节点。 + +``path`` 仅在使用 ``node_memcg_used_bp`` 和 ``node_memcg_free_bp`` 指标时必须提供, +用于指定 cgroup 路径。该值应为从 cgroup 挂载点起算的内存 cgroup 路径。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置调优 +目标指标、目标值和/或当前值,请参考文档的 :ref:`quota goals +` 部分。 + + +.. _damon_design_damos_watermarks_zh_CN: + +水位 +~~~~ + +条件式 DAMOS(去)激活自动化。用户可能希望 DAMOS 只在特定情况下运行。例如, +当保证有足够空闲内存时,运行主动回收方案只会消耗不必要的系统资源。为避免这类 +消耗,用户需要手动监测一些指标(例如空闲内存率),并打开或关闭 DAMON/DAMOS。 + +DAMOS 允许用户使用三个水位移交这类工作。它允许用户配置自己感兴趣的指标,以及 +三个水位值,即高、中和低。如果指标值高于高水位或低于低水位,该方案会被停用。 +如果指标值低于中水位但高于低水位,该方案会被激活。如果所有方案都被水位停用, +监测也会被停用。在这种情况下,DAMON 工作线程只会周期性地检查水位,因此几乎 +不会产生开销。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置水位, +请参考文档的 :ref:`watermarks ` 部分。 + + +.. _damon_design_damos_filters_zh_CN: + +过滤器 +~~~~~~ + +非访问模式的目标内存区域过滤。如果用户运行自己编写的程序或拥有好的剖析工具, +他们可能比内核知道更多信息,例如未来的访问模式或针对特定内存类型的一些特殊需求。 +例如,一些用户可能知道只有匿名页会影响其程序性能。他们也可以拥有一组延迟关键进程 +列表。 + +为了让用户用这类特殊知识优化 DAMOS 方案,DAMOS 提供了一个称为 DAMOS 过滤器的功能。 +该功能允许用户为每个方案设置任意数量的过滤器。每个过滤器指定: + +- 一种内存类型(``type``), +- 它是针对该类型的内存,还是针对该类型以外的所有内存(``matching``),以及 +- 是否允许(包含)或拒绝(排除)把方案动作应用到该内存(``allow``)。 + +为了高效处理过滤器,一些类型的过滤器由核心层处理,而其他类型由操作集处理。因此, +在后一种情况下,是否支持过滤器类型取决于 DAMON 操作集。对于核心层处理的过滤器, +被过滤器排除的内存区域不会计入方案已尝试应用的区域。相反,如果内存区域被操作集层 +处理的过滤器过滤,它会计入方案已尝试。这种差异会影响统计信息。 + +安装多个过滤器时,由核心层处理的一组过滤器首先求值。之后,由操作层处理的一组过滤器 +求值。每组过滤器内部按安装顺序求值。如果某部分内存匹配某个过滤器,后续过滤器会被 +忽略。如果该部分因为没有匹配任何过滤器而通过过滤器求值阶段,则是否对其应用方案动作 +取决于最后一个过滤器的 allow 类型。如果最后一个过滤器是允许型,该部分内存会被拒绝, +反之亦然。 + +例如,假设按顺序安装了 1)一个允许匿名页的过滤器,和 2)另一个拒绝年轻页的过滤器。 +如果某个符合方案动作应用条件的区域中的页面是匿名页,不论它是否年轻,方案动作都会应用 +到该页,因为它匹配第一个允许过滤器。如果该页不是匿名页但年轻,则方案动作不会应用, +因为第二个拒绝过滤器阻止了它。如果该页既不是匿名页也不年轻,由于没有匹配过滤器, +该页会通过过滤器求值阶段,并且动作会应用到该页。 + +当前支持以下 ``type`` 的过滤器。 + +- 核心层处理 + - addr + - 应用于属于给定地址范围的页面。 + - target + - 应用于属于给定 DAMON 监测目标的页面。 +- 操作层处理,只有 ``paddr`` 操作集支持。 + - anon + - 应用于包含未存储在文件中的数据的页面。 + - active + - 应用于活跃页。 + - memcg + - 应用于属于给定 cgroup 的页面。 + - young + - 应用于自方案上次访问检查以来被访问过的页面。 + - hugepage_size + - 应用于以给定大小范围管理的页面。 + - unmapped + - 应用于未映射的页面。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 设置过滤器, +请参考文档的 :ref:`filters ` 部分。 + +.. _damon_design_damos_stat_zh_CN: + +统计信息 +~~~~~~~~ + +DAMOS 行为的统计信息,用于帮助监测、调优和调试 DAMOS。 + +DAMOS 从方案执行开始起,为每个方案统计以下信息。 + +- ``nr_tried``:方案尝试应用的区域总数。 +- ``sz_tried``:方案尝试应用的区域总大小。 +- ``sz_ops_filter_passed``:通过操作集层处理的 DAMOS 过滤器的总字节数。 +- ``nr_applied``:方案已应用的区域总数。 +- ``sz_applied``:方案已应用的区域总大小。 +- ``qt_exceeds``:方案配额被超过的总次数。 +- ``nr_snapshots``:方案尝试应用的 DAMON 快照总数。 +- ``max_nr_snapshots``:``nr_snapshots`` 的上限。 + +“方案尝试应用到某个区域”表示 DAMOS 核心逻辑判断该区域符合应用方案 +:ref:`动作 ` 的条件。核心逻辑处理的 +:ref:`访问模式 `、:ref:`配额 +`、:ref:`水位 ` +和 :ref:`过滤器 ` 可能影响这一判断。核心逻辑 +只会请求底层 :ref:`操作集 ` 对该区域应用动作,因此 +该动作是否真正应用并不明确。这就是它被称为“尝试”的原因。 + +“方案应用到某个区域”表示 :ref:`操作集 ` 已经把动作 +应用到该区域的至少一部分。操作集处理的 :ref:`过滤器 +`、:ref:`动作 ` +类型以及该区域中的页面类型都可能影响这一点。例如,如果过滤器设置为排除匿名页且 +该区域只有匿名页,或者动作是 ``pageout`` 而该区域的所有页面都不可回收,则把动作 +应用到该区域会失败。 + +不同于普通统计信息,``max_nr_snapshots`` 由用户设置。如果它设置为非零,且 +``nr_snapshots`` 等于或大于 ``max_nr_snapshots``,该方案会被停用。 + +要了解用户空间如何通过 :ref:`DAMON sysfs 接口 ` 读取统计 +信息,请参考文档的 :ref:`stats ` 部分。 + + +区域遍历 +~~~~~~~~ + +DAMOS 功能允许用户访问刚刚应用了 DAMOS 动作的每个区域。使用该功能,DAMON +:ref:`API ` 允许用户访问这些区域的完整属性,包括访问监测结果以及 +区域内部通过 DAMOS 过滤器的内存量。:ref:`DAMON sysfs 接口 ` 也允许 +用户通过特殊 :ref:`文件 ` 读取这些数据。 + +.. _damon_design_api_zh_CN: + +应用编程接口 +------------ + +用于内核空间数据访问感知应用的编程接口。DAMON 是一个框架,所以它本身什么也不做。相反, +它只帮助子系统和模块等其他内核组件使用 DAMON 的核心功能构建它们的数据访问感知应用。为此, +DAMON 通过其应用编程接口,即 ``include/linux/damon.h``,向其他内核组件暴露所有功能。接口 +细节请参考 API :doc:`文档 `。 + + +.. _damon_modules_zh_CN: + +模块 +==== + +由于 DAMON 的核心是供内核组件使用的框架,它本身不向用户空间提供任何直接接口。相反,这些接口 +应由每个使用 DAMON API 的内核组件来实现。DAMON 子系统本身实现了这类 DAMON API 用户模块, +它们分别用于通用目的的 DAMON 控制和特定目的的数据访问感知系统操作,并为用户空间提供稳定的应 +用二进制接口(ABI)。用户空间可以使用这些接口构建高效的数据访问感知应用程序。 + + +通用目的用户接口模块 +-------------------- + +DAMON 模块为运行时的通用目的 DAMON 用法提供用户空间 ABI。 + +与许多其他 ABI 一样,这些模块会在类似 ``sysfs`` 的伪文件系统上创建文件,允许用户通过写入和读 +取这些文件来向 DAMON 指定请求并获取回答。作为这类 I/O 的响应,DAMON 用户接口模块会通过 DAMON +API 按用户请求控制 DAMON 并获取结果,然后把结果返回给用户空间。 + +这些 ABI 是为用户空间应用程序开发而设计的,而不是为人工手动操作而设计的。建议人工用户使用这类 +用户空间工具。一个用 Python 编写的此类用户空间工具可在 Github +(https://github.com/damonitor/damo)、Pypi(https://pypistats.org/packages/damo)以及多个发行 +版(https://repology.org/project/damo/versions)中获取。 + +目前,该类型有一个模块可用,即 ``DAMON sysfs interface``。关于接口细节,请参考 ABI +:ref:`文档 `。 + + +.. _damon_modules_special_purpose_zh_CN: + +专用访问感知内核模块 +-------------------- + +DAMON 模块为特定目的的 DAMON 用法提供用户空间 ABI。 + +DAMON 用户接口模块用于在运行时完整控制所有 DAMON 功能。对于每种专用的、系统范围的数据访问感知 +系统操作,例如主动回收或 LRU 链表均衡,可以通过移除该特定目的不需要的调节项来简化接口,并扩展 +为启动时甚至编译时控制。用于该用途的 DAMON 控制参数默认值也需要针对该目的进行优化。 + +为支持这些场景,DAMON 还提供了更多使用 DAMON API 的内核模块,它们提供更简单且更优化的用户空间 +接口。目前提供了用于访问监测统计、主动回收和 LRU 链表操作的三个模块。更多细节请阅读这些模块的 +使用文档(:doc:`../../admin-guide/mm/damon/stat`, +:doc:`../../admin-guide/mm/damon/reclaim` 和 +:doc:`../../admin-guide/mm/damon/lru_sort`)。 + +.. _damon_design_special_purpose_modules_exclusivity_zh_CN: + +请注意,这些模块当前以互斥方式运行。如果其中一个模块已经在运行,其他模块在收到启动请求时将返回 +``-EBUSY``。 + +示例 DAMON 模块 +---------------- + +DAMON 模块提供 DAMON 内核 API 用法示例。 + +内核程序员可以使用 DAMON 内核 API 构建自己的专用或通用目的 DAMON 模块。为了帮助他们容易理解 +如何使用 DAMON 内核 API,Linux 源码树的 ``samples/damon/`` 目录下提供了一些示例模块。请注意, +这些模块不是为实际产品使用而开发的,而只是为了展示如何以简单方式使用 DAMON 内核 API。 From aad61c711655f59c38564939a29d7c97d57e815b Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:44 +0000 Subject: [PATCH 0588/2286] docs/zh_CN: add DAMON_STAT usage translation Translate .../admin-guide/mm/damon/stat.rst into Chinese. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/stat.rst | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 Documentation/translations/zh_CN/admin-guide/mm/damon/stat.rst diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/stat.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/stat.rst new file mode 100644 index 00000000000000..129748a5ea1f06 --- /dev/null +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/stat.rst @@ -0,0 +1,94 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: ../../../disclaimer-zh_CN.rst + +:Original: Documentation/admin-guide/mm/damon/stat.rst + +:翻译: + + Doehyun Baek + +==================== +数据访问监测结果统计 +==================== + +数据访问监测结果统计(DAMON_STAT)是一个静态内核模块,旨在用于简单的 +访问模式监测。它使用 DAMON 监测系统整个物理内存上的访问,并提供简化的 +访问监测结果统计,即空闲时间百分位数和估计的内存带宽。 + +.. _damon_stat_monitoring_accuracy_overhead_zh_CN: + +监测精度和开销 +============== + +DAMON_STAT 使用监测间隔 +:ref:`自动调优 ` 来提高 +精度并最小化开销。它会自动调优间隔,目标是在每个快照中捕获 4 % 的 +可观测访问事件,同时将得到的采样间隔限制在最小 5 毫秒、最大 10 秒。 +在少数生产服务器系统上,它的结果是只消耗 0.x % 的单个 CPU 时间,同时 +捕获质量合理的访问模式。调优得到的间隔可以通过 +``aggr_interval_us`` :ref:`参数 ` 获取。 + +接口:模块参数 +============== + +要使用这个功能,首先应确保你的系统运行在构建时启用了 +``CONFIG_DAMON_STAT=y`` 的内核上。通过将 +``CONFIG_DAMON_STAT_ENABLED_DEFAULT`` 设置为 true,可以在构建时默认 +启用该功能。 + +为了让系统管理员在启动时和/或运行时启用或禁用它,并读取监测结果, +DAMON_STAT 提供了模块参数。下面的章节描述这些参数。 + +enabled +------- + +启用或禁用 DAMON_STAT。 + +你可以将该参数的值设置为 ``Y`` 来启用 DAMON_STAT。设置为 ``N`` 会 +禁用 DAMON_STAT。默认值由 ``CONFIG_DAMON_STAT_ENABLED_DEFAULT`` 构建 +配置选项设置。 + +请注意,该模块(damon_stat)不能与其他基于 DAMON 的专用模块同时运行。 +更多细节请参考 :ref:`DAMON 设计文档的专用模块互斥性 `。 + +.. _damon_stat_aggr_interval_us_zh_CN: + +aggr_interval_us +---------------- + +自动调优后的聚集时间间隔,单位是微秒。 + +用户可以读取 DAMON_STAT 使用的 DAMON 实例的聚集间隔。它会被 +:ref:`自动调优 `,因此该值 +会动态变化。 + +estimated_memory_bandwidth +-------------------------- + +系统的估计内存带宽消耗(字节/秒)。 + +DAMON_STAT 读取当前 DAMON 结果快照上的观测访问事件,并将其转换为以 +字节/秒为单位的内存带宽消耗估计。得到的指标通过这个只读参数向用户 +公开。由于 DAMON 使用采样,所以这只是访问强度的估计,而不是精确的 +内存带宽。 + +memory_idle_ms_percentiles +-------------------------- + +系统的逐字节空闲时间(毫秒)百分位数。 + +DAMON_STAT 基于当前 DAMON 结果快照,计算内存中每个字节到现在为止未被 +访问的时间(空闲时间)。对于访问频率(nr_accesses)大于零的区域,当前 +访问频率水平保持的时间乘以 ``-1``,就是该区域每个字节的空闲时间。如果 +某个区域的访问频率(nr_accesses)为零,则该区域保持零访问频率的时间 +(age)就是该区域每个字节的空闲时间。然后,DAMON_STAT 通过这个只读参数 +公开空闲时间值的百分位数。读取该参数会返回 101 个以毫秒为单位、用逗号 +分隔的空闲时间值。每个值分别表示第 0、第 1、第 2、第 3、……、第 99 和 +第 100 百分位的空闲时间。 + +kdamond_pid +----------- + +DAMON 线程的 PID。 + +如果 DAMON_STAT 已启用,这将成为工作线程的 PID。否则为 -1。 From 7f551b8ccac50330b5309cffe796313686611ef5 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:45 +0000 Subject: [PATCH 0589/2286] docs/zh_CN: update DAMON index translation Update the Chinese translation of .../admin-guide/mm/damon/index.rst. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/index.rst | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst index 6f8676a50b3811..77bbfa7c25dcb2 100644 --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/index.rst @@ -9,12 +9,12 @@ :校译: -============ -监测数据访问 -============ +======================================== +DAMON:数据访问监测和访问感知系统操作 +======================================== -:doc:`DAMON ` 允许轻量级的数据访问监测。使用DAMON, -用户可以分析他们系统的内存访问模式,并优化它们。 +:doc:`DAMON <../../../mm/damon/index>` 是一个 Linux 内核子系统,用于高效的 +数据访问监测和访问感知系统操作。 .. toctree:: :maxdepth: 2 @@ -23,7 +23,4 @@ usage reclaim lru_sort - - - - + stat From 4bc546fdee6e96e8c7310805a499176db4996217 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:46 +0000 Subject: [PATCH 0590/2286] docs/zh_CN: update DAMON start translation Update the Chinese translation of .../admin-guide/mm/damon/start.rst. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/start.rst | 63 ++++++++++++++++--- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst index cff7b6f98c5941..1a7f3382dd158d 100644 --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/start.rst @@ -35,18 +35,63 @@ https://github.com/damonitor/damo找到。下面的例子假设DAMO在你的$PAT 这并不是强制性的。 因为DAMO使用了DAMON的sysfs接口(详情请参考:doc:`usage`),你应该确保 -:doc:`sysfs ` 被挂载。 +``sysfs`` 被挂载。 + + +拍摄数据访问模式快照 +==================== + +下面的命令展示程序在执行时刻的内存访问模式。:: + + $ git clone https://github.com/sjp38/masim; cd masim; make + $ sudo damo start "./masim ./configs/stairs.cfg --quiet" + $ sudo damo report access + heatmap: 641111111000000000000000000000000000000000000000000000[...]33333333333333335557984444[...]7 + # min/max temperatures: -1,840,000,000, 370,010,000, column size: 3.925 MiB + 0 addr 86.182 TiB size 8.000 KiB access 0 % age 14.900 s + 1 addr 86.182 TiB size 8.000 KiB access 60 % age 0 ns + 2 addr 86.182 TiB size 3.422 MiB access 0 % age 4.100 s + 3 addr 86.182 TiB size 2.004 MiB access 95 % age 2.200 s + 4 addr 86.182 TiB size 29.688 MiB access 0 % age 14.100 s + 5 addr 86.182 TiB size 29.516 MiB access 0 % age 16.700 s + 6 addr 86.182 TiB size 29.633 MiB access 0 % age 17.900 s + 7 addr 86.182 TiB size 117.652 MiB access 0 % age 18.400 s + 8 addr 126.990 TiB size 62.332 MiB access 0 % age 9.500 s + 9 addr 126.990 TiB size 13.980 MiB access 0 % age 5.200 s + 10 addr 126.990 TiB size 9.539 MiB access 100 % age 3.700 s + 11 addr 126.990 TiB size 16.098 MiB access 0 % age 6.400 s + 12 addr 127.987 TiB size 132.000 KiB access 0 % age 2.900 s + total size: 314.008 MiB + $ sudo damo stop + +上面示例的第一条命令下载并构建一个名为 ``masim`` 的人工内存访问 +生成程序。第二条命令要求 DAMO 用给定命令启动该程序,并让 DAMON +监测新启动的进程。第三条命令从 DAMON 取回该进程当前被监测访问 +模式的快照,并以人类可读的格式显示该模式。 + +输出的第一行以单行热图格式显示各区域的相对访问温度(热度)。热图 +中的每一列表示被监测虚拟地址空间上大小相同的区域。列在该行中的 +位置和列上的数字表示该区域的相对位置和访问温度。``[...]`` 表示 +虚拟地址空间中未映射的巨大区域。第二行显示帮助理解热图的附加信息。 + +从第三行开始,输出的每一行显示进程的哪个虚拟地址范围 +(``addr XX size XX``)被访问得有多频繁(``access XX %``),以及 +持续了多长时间(``age XX``)。例如,大小约为 9.5 MiB 的第十一个 +区域在最近 3.7 秒内被访问得最频繁。最后,第四条命令停止 DAMON。 + +请注意,DAMON 不仅能监测虚拟地址空间,还能监测包括物理地址空间在内 +的多种地址空间。 + 记录数据访问模式 ================ 下面的命令记录了一个程序的内存访问模式,并将监测结果保存到文件中。 :: - $ git clone https://github.com/sjp38/masim - $ cd masim; make; ./masim ./configs/zigzag.cfg & + $ ./masim ./configs/zigzag.cfg & $ sudo damo record -o damon.data $(pidof masim) -命令的前两行下载了一个人工内存访问生成器程序并在后台运行。生成器将重复地逐一访问两个 +第一行命令再次运行该人工内存访问生成器程序。生成器将重复地逐一访问两个 100 MiB大小的内存区域。你可以用你的真实工作负载来代替它。最后一行要求 ``damo`` 将 访问模式记录在 ``damon.data`` 文件中。 @@ -57,7 +102,7 @@ https://github.com/damonitor/damo找到。下面的例子假设DAMO在你的$PAT 你可以在heatmap中直观地看到这种模式,显示哪个内存区域(X轴)何时被访问(Y轴)以及访 问的频率(数字)。:: - $ sudo damo report heats --heatmap stdout + $ sudo damo report heatmap 22222222222222222222222222222222222222211111111111111111111111111111111111111100 44444444444444444444444444444444444444434444444444444444444444444444444444443200 44444444444444444444444444444444444444433444444444444444444444444444444444444200 @@ -117,8 +162,8 @@ https://github.com/damonitor/damo找到。下面的例子假设DAMO在你的$PAT 数据访问模式感知的内存管理 ========================== -以下三个命令使每一个大小>=4K的内存区域在你的工作负载中没有被访问>=60秒,就会被换掉。 :: +以下命令使每一个大小>=4K的内存区域在你的工作负载中没有被访问>=60秒,就会被换掉。 :: - $ echo "#min-size max-size min-acc max-acc min-age max-age action" > test_scheme - $ echo "4K max 0 0 60s max pageout" >> test_scheme - $ damo schemes -c test_scheme + $ sudo damo start --damos_access_rate 0 0 --damos_sz_region 4K max \ + --damos_age 60s max --damos_action pageout \ + --target_pid From 09a5fcefe906297512ad5ed358d831c8431fe203 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:47 +0000 Subject: [PATCH 0591/2286] docs/zh_CN: update DAMON usage translation Update the Chinese translation of .../admin-guide/mm/damon/usage.rst. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/usage.rst | 523 ++++++++++++++---- 1 file changed, 412 insertions(+), 111 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst index 9d7cb51be4931b..f2fe1c8025fae1 100644 --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/usage.rst @@ -15,22 +15,27 @@ DAMON 为不同的用户提供了下面这些接口。 +- *专用 DAMON 模块。* + :ref:`这 ` 是为那些正在构建、发布和/或管理 + 带有专用 DAMON 用法的内核的人准备的。使用它,用户可以在构建、启动或运行时, + 以简单的方式为给定目的使用 DAMON 的主要功能。 - *DAMON用户空间工具。* `这 `_ 为有这特权的人, 如系统管理员,希望有一个刚好 可以工作的人性化界面。 使用它,用户可以以人性化的方式使用DAMON的主要功能。不过,它可能不会为特殊情况进行高度调整。 - 它同时支持虚拟和物理地址空间的监测。更多细节,请参考它的 `使用文档 + 更多细节,请参考它的 `使用文档 `_。 - *sysfs接口。* - :ref:`这 ` 是为那些希望更高级的使用DAMON的特权用户空间程序员准备的。 + :ref:`这 ` 是为那些希望更高级的使用DAMON的特权用户空间程序员准备的。 使用它,用户可以通过读取和写入特殊的sysfs文件来使用DAMON的主要功能。因此,你可以编写和使 用你个性化的DAMON sysfs包装程序,代替你读/写sysfs文件。 `DAMON用户空间工具 - `_ 就是这种程序的一个例子 它同时支持虚拟和物理地址 - 空间的监测。 + `_ 就是这种程序的一个例子。 - *内核空间编程接口。* - :doc:`这 ` 这是为内核空间程序员准备的。使用它,用户可以通过为你编写内 + :doc:`这 <../../../mm/damon/api>` 这是为内核空间程序员准备的。使用它,用户可以通过为你编写内 核空间的DAMON应用程序,最灵活有效地利用DAMON的每一个功能。你甚至可以为各种地址空间扩展DAMON。 - 详细情况请参考接口 :doc:`文件 `。 + 详细情况请参考接口 :doc:`文件 <../../../mm/damon/api>`。 + +.. _sysfs_interface_zh_CN: sysfs接口 ========= @@ -51,39 +56,61 @@ DAMON的sysfs接口是在定义 ``CONFIG_DAMON_SYSFS`` 时建立的。它在其s ------------ DAMON sysfs接口的文件层次结构如下图所示。在下图中,父子关系用缩进表示,每个目录有 -``/`` 后缀,每个目录中的文件用逗号(",")分开。 :: - - /sys/kernel/mm/damon/admin - │ kdamonds/nr_kdamonds - │ │ 0/state,pid - │ │ │ contexts/nr_contexts - │ │ │ │ 0/operations - │ │ │ │ │ monitoring_attrs/ +``/`` 后缀,每个目录中的文件用逗号(",")分开。 + +.. parsed-literal:: + + :ref:`/sys/kernel/mm/damon `/admin + │ :ref:`kdamonds `/nr_kdamonds + │ │ :ref:`0 `/state,pid,refresh_ms + │ │ │ :ref:`contexts `/nr_contexts + │ │ │ │ :ref:`0 `/avail_operations,operations,addr_unit, + │ │ │ │ pause + │ │ │ │ │ :ref:`monitoring_attrs `/ │ │ │ │ │ │ intervals/sample_us,aggr_us,update_us + │ │ │ │ │ │ │ intervals_goal/access_bp,aggrs,min_sample_us,max_sample_us │ │ │ │ │ │ nr_regions/min,max - │ │ │ │ │ targets/nr_targets - │ │ │ │ │ │ 0/pid_target - │ │ │ │ │ │ │ regions/nr_regions - │ │ │ │ │ │ │ │ 0/start,end + │ │ │ │ │ │ :ref:`probes `/nr_probes + │ │ │ │ │ │ │ 0/filters/nr_filters + │ │ │ │ │ │ │ │ 0/type,matching,allow,path + │ │ │ │ │ │ │ │ ... + │ │ │ │ │ │ │ ... + │ │ │ │ │ :ref:`targets `/nr_targets + │ │ │ │ │ │ :ref:`0 `/pid_target,obsolete_target + │ │ │ │ │ │ │ :ref:`regions `/nr_regions + │ │ │ │ │ │ │ │ :ref:`0 `/start,end │ │ │ │ │ │ │ │ ... │ │ │ │ │ │ ... - │ │ │ │ │ schemes/nr_schemes - │ │ │ │ │ │ 0/action - │ │ │ │ │ │ │ access_pattern/ + │ │ │ │ │ :ref:`schemes `/nr_schemes + │ │ │ │ │ │ :ref:`0 `/action,target_nid,apply_interval_us + │ │ │ │ │ │ │ :ref:`access_pattern `/ │ │ │ │ │ │ │ │ sz/min,max │ │ │ │ │ │ │ │ nr_accesses/min,max │ │ │ │ │ │ │ │ age/min,max - │ │ │ │ │ │ │ quotas/ms,bytes,reset_interval_ms + │ │ │ │ │ │ │ :ref:`quotas `/ms,bytes,reset_interval_ms, + │ │ │ │ │ │ │ effective_bytes,goal_tuner, + │ │ │ │ │ │ │ fail_charge_num,fail_charge_denom │ │ │ │ │ │ │ │ weights/sz_permil,nr_accesses_permil,age_permil - │ │ │ │ │ │ │ watermarks/metric,interval_us,high,mid,low - │ │ │ │ │ │ │ stats/nr_tried,sz_tried,nr_applied,sz_applied,qt_exceeds - │ │ │ │ │ │ │ tried_regions/ - │ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age + │ │ │ │ │ │ │ │ :ref:`goals `/nr_goals + │ │ │ │ │ │ │ │ │ 0/target_metric,target_value,current_value,nid,path + │ │ │ │ │ │ │ :ref:`watermarks `/metric,interval_us,high,mid,low + │ │ │ │ │ │ │ :ref:`{core_,ops_,}filters `/nr_filters + │ │ │ │ │ │ │ │ 0/type,matching,allow,memcg_path,addr_start,addr_end,damon_target_idx,min,max + │ │ │ │ │ │ │ :ref:`dests `/nr_dests + │ │ │ │ │ │ │ │ 0/id,weight + │ │ │ │ │ │ │ :ref:`stats `/nr_tried,sz_tried,nr_applied,sz_applied,sz_ops_filter_passed,qt_exceeds,nr_snapshots,max_nr_snapshots + │ │ │ │ │ │ │ :ref:`tried_regions `/total_bytes + │ │ │ │ │ │ │ │ 0/start,end,nr_accesses,age,sz_filter_passed + │ │ │ │ │ │ │ │ │ probes + │ │ │ │ │ │ │ │ │ │ 0/hits + │ │ │ │ │ │ │ │ │ │ ... │ │ │ │ │ │ │ │ ... │ │ │ │ │ │ ... │ │ │ │ ... │ │ ... +.. _sysfs_root_zh_CN: + 根 -- @@ -91,58 +118,100 @@ DAMON sysfs接口的根是 ``/kernel/mm/damon/`` ,它有一个名为 `` 目录。该目录包含特权用户空间程序控制DAMON的文件。拥有根权限的用户空间工具或deamons可以 使用这个目录。 +.. _sysfs_kdamonds_zh_CN: + kdamonds/ --------- -与监测相关的信息包括请求规格和结果被称为DAMON上下文。DAMON用一个叫做kdamond的内核线程 -执行每个上下文,多个kdamonds可以并行运行。 - -在 ``admin`` 目录下,有一个目录,即``kdamonds``,它有控制kdamonds的文件存在。在开始 +在 ``admin`` 目录下,有一个目录,即``kdamonds``,它有控制kdamonds的文件存在(更多 +细节请参考 :ref:`设计 `)。在开始 时,这个目录只有一个文件,``nr_kdamonds``。向该文件写入一个数字(``N``),就会创建名为 ``0`` 到 ``N-1`` 的子目录数量。每个目录代表每个kdamond。 +.. _sysfs_kdamond_zh_CN: + kdamonds// ------------- -在每个kdamond目录中,存在两个文件(``state`` 和 ``pid`` )和一个目录( ``contexts`` )。 +在每个kdamond目录中,存在三个文件(``state`` 、 ``pid`` 和 ``refresh_ms`` )和一个目录( ``contexts`` )。 读取 ``state`` 时,如果kdamond当前正在运行,则返回 ``on`` ,如果没有运行则返回 ``off`` 。 -写入 ``on`` 或 ``off`` 使kdamond处于状态。向 ``state`` 文件写 ``update_schemes_stats`` , -更新kdamond的每个基于DAMON的操作方案的统计文件的内容。关于统计信息的细节,请参考 -:ref:`stats section `. 将 ``update_schemes_tried_regions`` 写到 -``state`` 文件,为kdamond的每个基于DAMON的操作方案,更新基于DAMON的操作方案动作的尝试区域目录。 -将`clear_schemes_tried_regions`写入`state`文件,清除kdamond的每个基于DAMON的操作方案的动作 -尝试区域目录。 关于基于DAMON的操作方案动作尝试区域目录的细节,请参考:ref:tried_regions 部分 -`。 + +用户可以向 ``state`` 文件写入以下kdamond命令。 + +- ``on``:开始运行。 +- ``off``:停止运行。 +- ``commit``:再次读取除 ``state`` 文件之外的 sysfs 文件中的用户输入。 + 如果没有指定目标区域,也会忽略监测 :ref:`目标区域 ` + 输入。 +- ``update_tuned_intervals``:用自动调优后的 ``sampling interval`` 和 + ``aggregation interval`` 更新该 kdamond 的 ``sample_us`` 和 + ``aggr_us`` 文件内容。更多细节请参考 :ref:`intervals_goal 章节 + `。 +- ``commit_schemes_quota_goals``:读取基于 DAMON 的操作方案的 + :ref:`配额目标 `。 +- ``update_schemes_stats``:更新该 kdamond 的每个基于 DAMON 的操作方案的 + 统计文件内容。关于统计信息的细节,请参考 :ref:`stats 章节 + `。 +- ``update_schemes_tried_regions``:为该 kdamond 的每个基于 DAMON 的 + 操作方案,更新基于 DAMON 的操作方案动作尝试区域目录。关于基于 DAMON + 的操作方案动作尝试区域目录的细节,请参考 :ref:`tried_regions 章节 + `。 +- ``update_schemes_tried_bytes``:只更新 ``.../tried_regions/total_bytes`` + 文件。 +- ``clear_schemes_tried_regions``:为该 kdamond 的每个基于 DAMON 的 + 操作方案,清除基于 DAMON 的操作方案动作尝试区域目录。 +- ``update_schemes_effective_quotas``:更新该 kdamond 的每个基于 DAMON + 的操作方案的 ``effective_bytes`` 文件内容。更多细节请参考 + :ref:`quotas 目录 `。 如果状态为 ``on``,读取 ``pid`` 显示kdamond线程的pid。 +用户可以要求内核周期性地更新显示自动调优参数和 DAMOS 统计信息的文件, +而不是手动向 ``state`` 文件写入 ``update_tuned_intervals`` 这类关键字。 +为此,用户应将所需的更新时间间隔(毫秒)写入 ``refresh_ms`` 文件。如果 +该间隔为零,则禁用周期性更新。读取该文件会显示当前设置的时间间隔。 + ``contexts`` 目录包含控制这个kdamond要执行的监测上下文的文件。 +.. _sysfs_contexts_zh_CN: + kdamonds//contexts/ ---------------------- 在开始时,这个目录只有一个文件,即 ``nr_contexts`` 。向该文件写入一个数字( ``N`` ),就会创 -建名为``0`` 到 ``N-1`` 的子目录数量。每个目录代表每个监测背景。目前,每个kdamond只支持 +建名为``0`` 到 ``N-1`` 的子目录数量。每个目录代表每个监测上下文(更多细节请参考 +:ref:`设计 `)。目前,每个kdamond只支持 一个上下文,所以只有 ``0`` 或 ``1`` 可以被写入文件。 +.. _sysfs_context_zh_CN: + contexts// ------------- -在每个上下文目录中,存在一个文件(``operations``)和三个目录(``monitoring_attrs``, +在每个上下文目录中,存在四个文件(``avail_operations``、``operations``、 +``addr_unit`` 和 ``pause``)和三个目录(``monitoring_attrs``, ``targets``, 和 ``schemes``)。 -DAMON支持多种类型的监测操作,包括对虚拟地址空间和物理地址空间的监测。你可以通过向文件 -中写入以下关键词之一,并从文件中读取,来设置和获取DAMON将为上下文使用何种类型的监测操作。 +DAMON支持多种 :ref:`监测操作 ` 类型, +包括对虚拟地址空间和物理地址空间的监测。你可以通过读取 ``avail_operations`` 文件, +获取当前运行内核上可用的监测操作集列表。根据内核配置,该文件会列出不同的可用 +操作集。所有可用操作集及其简要说明请参考 :ref:`设计 `。 + +你可以通过向 ``operations`` 文件写入 ``avail_operations`` 文件中列出的一个关键词, +并从 ``operations`` 文件中读取,来设置和获取DAMON将为上下文使用何种类型的监测操作。 + +``addr_unit`` 文件用于设置和获取该操作集的 :ref:`地址单位 ` 参数。 + +``pause`` 文件用于设置和获取该上下文的 :ref:`暂停请求 ` 参数。 - - vaddr: 监测特定进程的虚拟地址空间 - - paddr: 监视系统的物理地址空间 +.. _sysfs_monitoring_attrs_zh_CN: contexts//monitoring_attrs/ ------------------------------ 用于指定监测属性的文件,包括所需的监测质量和效率,都在 ``monitoring_attrs`` 目录中。 -具体来说,这个目录下有两个目录,即 ``intervals`` 和 ``nr_regions`` 。 +具体来说,这个目录下有三个目录,即 ``intervals``、``nr_regions`` 和 ``probes``。 在 ``intervals`` 目录下,存在DAMON的采样间隔(``sample_us``)、聚集间隔(``aggr_us``) 和更新间隔(``update_us``)三个文件。你可以通过写入和读出这些文件来设置和获取微秒级的值。 @@ -150,7 +219,44 @@ contexts//monitoring_attrs/ 在 ``nr_regions`` 目录下,有两个文件分别用于DAMON监测区域的下限和上限(``min`` 和 ``max`` ), 这两个文件控制着监测的开销。你可以通过向这些文件的写入和读出来设置和获取这些值。 -关于间隔和监测区域范围的更多细节,请参考设计文件 (:doc:`/mm/damon/design`)。 +关于间隔和监测区域范围的更多细节,请参考设计文件 (:ref:`设计 `)。 + +.. _damon_usage_sysfs_monitoring_intervals_goal_zh_CN: + +contexts//monitoring_attrs/intervals/intervals_goal/ +------------------------------------------------------- + +在 ``intervals`` 目录下,还存在一个用于自动调优 ``sample_us`` 和 +``aggr_us`` 的目录,即 ``intervals_goal`` 目录。在该目录下,存在四个 +用于自动调优控制的文件,即 ``access_bp``、``aggrs``、``min_sample_us`` +和 ``max_sample_us``。关于调优机制的内部实现,请参考该功能的 +:ref:`设计文档 `。读取和写入 +``intervals_goal`` 目录下的四个文件,会显示并更新 :ref:`设计文档 ` 中描述的同名调优参数。调优 +从用户设置的 ``sample_us`` 和 ``aggr_us`` 开始。向 ``state`` 文件写入 +``update_tuned_intervals`` 后,可以从 ``sample_us`` 和 ``aggr_us`` 文件 +读取应用调优后的两个间隔的当前值。 + +.. _damon_usage_sysfs_probes_zh_CN: + +contexts//monitoring_attrs/probes/ +------------------------------------- + +用于注册 :ref:`数据属性监测 ` 探针的目录。 + +开始时,该目录只有一个文件 ``nr_probes``。向该文件写入一个数字 +(``N``)会创建数量为该数字的子目录,命名为 ``0`` 到 ``N-1``。每个 +目录表示一个监测探针。 + +在每个探针目录中,存在一个目录 ``filters``。该目录包含为探针安装过滤器 +的文件,该过滤器用于确定探针的数据属性。 + +开始时,``filters`` 目录只有一个文件 ``nr_filters``。向该文件写入一个 +数字(``N``)会创建数量为该数字的子目录,命名为 ``0`` 到 ``N-1``。每个 +目录表示一个过滤器,其工作方式类似于 :ref:`DAMOS 过滤器 +`。当过滤器 ``type`` 为 ``memcg`` 时,``path`` 文件 +作为 :ref:`DAMOS 过滤器 ` 的 ``memcg_path`` 使用。 + +.. _sysfs_targets_zh_CN: contexts//targets/ --------------------- @@ -158,30 +264,43 @@ contexts//targets/ 在开始时,这个目录只有一个文件 ``nr_targets`` 。向该文件写入一个数字(``N``),就可以创建 名为 ``0`` 到 ``N-1`` 的子目录的数量。每个目录代表每个监测目标。 +.. _sysfs_target_zh_CN: + targets// ------------ -在每个目标目录中,存在一个文件(``pid_target``)和一个目录(``regions``)。 +在每个目标目录中,存在两个文件(``pid_target`` 和 ``obsolete_target``)和一个目录(``regions``)。 如果你把 ``vaddr`` 写到 ``contexts//operations`` 中,每个目标应该是一个进程。你 可以通过将进程的pid写到 ``pid_target`` 文件中来指定DAMON的进程。 +用户可以通过向 ``obsolete_target`` 文件写入非零值并提交它(向 ``state`` +文件写入 ``commit``),有选择地移除目标数组中间的目标。DAMON 会从内部 +目标数组中移除匹配的目标。用户负责重新构建目标目录,使其正确表示变更后的 +内部目标数组。 + + +.. _sysfs_regions_zh_CN: + targets//regions ------------------- -当使用 ``vaddr`` 监测操作集时( ``vaddr`` 被写入 ``contexts//operations`` 文 -件),DAMON自动设置和更新监测目标区域,这样就可以覆盖目标进程的整个内存映射。然而,用户可 -能希望将初始监测区域设置为特定的地址范围。 +在使用 ``fvaddr`` 或 ``paddr`` 监测操作集时,用户需要设置监测目标地址 +范围。在使用 ``vaddr`` 操作集时,这不是必须的,但用户可以选择将初始 +监测区域设置为特定地址范围。更多细节请参考 :ref:`设计 `。 -相反,当使用 ``paddr`` 监测操作集时,DAMON不会自动设置和更新监测目标区域( ``paddr`` -被写入 ``contexts//operations`` 中)。因此,在这种情况下,用户应该自己设置监测目标 -区域。 - -在这种情况下,用户可以按照自己的意愿明确设置初始监测目标区域,将适当的值写入该目录下的文件。 +对于这类情况,用户可以按照自己的需要,向该目录下的文件写入适当的值, +显式设置初始监测目标区域。 开始时,这个目录只有一个文件, ``nr_regions`` 。向该文件写入一个数字(``N``),就可以创 建名为 ``0`` 到 ``N-1`` 的子目录。每个目录代表每个初始监测目标区域。 +如果在线提交新的 DAMON 参数时(向 :ref:`kdamond ` 的 +``state`` 文件写入 ``commit``)``nr_regions`` 为零,则提交逻辑会忽略 +目标区域。换言之,该目标当前的监测结果会被保留。 + +.. _sysfs_region_zh_CN: + regions// ------------ @@ -190,6 +309,8 @@ regions// 每个区域不应该与其他区域重叠。 目录“N”的“结束”应等于或小于目录“N+1”的“开始”。 +.. _sysfs_schemes_zh_CN: + contexts//schemes/ --------------------- @@ -200,99 +321,249 @@ contexts//schemes/ 在开始时,这个目录只有一个文件,``nr_schemes``。向该文件写入一个数字(``N``),就可以 创建名为``0``到``N-1``的子目录的数量。每个目录代表每个基于DAMON的操作方案。 +.. _sysfs_scheme_zh_CN: + schemes// ------------ -在每个方案目录中,存在五个目录(``access_pattern``、``quotas``、``watermarks``、 -``stats`` 和 ``tried_regions``)和一个文件(``action``)。 +在每个方案目录中,存在九个目录(``access_pattern``、``quotas``、 +``watermarks``、``core_filters``、``ops_filters``、``filters``、 +``dests``、``stats`` 和 ``tried_regions``)以及三个文件(``action``、 +``target_nid`` 和 ``apply_interval_us``)。 -``action`` 文件用于设置和获取你想应用于具有特定访问模式的内存区域的动作。可以写入文件 -和从文件中读取的关键词及其含义如下。 +``action`` 文件用于设置和获取方案的 :ref:`动作 `。可写入和读取该文件的关键字及其含义,与 +:ref:`设计文档 ` 中列表的内容相同。 - - ``willneed``: 对有 ``MADV_WILLNEED`` 的区域调用 ``madvise()`` 。 - - ``cold``: 对具有 ``MADV_COLD`` 的区域调用 ``madvise()`` 。 - - ``pageout``: 为具有 ``MADV_PAGEOUT`` 的区域调用 ``madvise()`` 。 - - ``hugepage``: 为带有 ``MADV_HUGEPAGE`` 的区域调用 ``madvise()`` 。 - - ``nohugepage``: 为带有 ``MADV_NOHUGEPAGE`` 的区域调用 ``madvise()``。 - - ``lru_prio``: 在其LRU列表上对区域进行优先排序。 - - ``lru_deprio``: 对区域的LRU列表进行降低优先处理。 - - ``stat``: 什么都不做,只计算统计数据 +``target_nid`` 文件用于设置迁移目标节点,只有当 ``action`` 为 +``migrate_hot`` 或 ``migrate_cold`` 时才有意义。 + +``apply_interval_us`` 文件用于以微秒为单位设置和获取方案的 +:ref:`apply_interval `。 + +.. _sysfs_access_pattern_zh_CN: schemes//access_pattern/ --------------------------- -每个基于DAMON的操作方案的目标访问模式由三个范围构成,包括以字节为单位的区域大小、每个 -聚合区间的监测访问次数和区域年龄的聚合区间数。 +用于给定基于 DAMON 的操作方案的目标访问 :ref:`模式 +` 的目录。 在 ``access_pattern`` 目录下,存在三个目录( ``sz``, ``nr_accesses``, 和 ``age`` ), 每个目录有两个文件(``min`` 和 ``max`` )。你可以通过向 ``sz``, ``nr_accesses``, 和 ``age`` 目录下的 ``min`` 和 ``max`` 文件分别写入和读取来设置和获取给定方案的访问模式。 +请注意,``min`` 和 ``max`` 形成闭区间。 + +.. _sysfs_quotas_zh_CN: schemes//quotas/ ------------------- -每个 ``动作`` 的最佳 ``目标访问模式`` 取决于工作负载,所以不容易找到。更糟糕的是,将某些动作 -的方案设置得过于激进会造成严重的开销。为了避免这种开销,用户可以为每个方案限制时间和大小配额。 -具体来说,用户可以要求DAMON尽量只使用特定的时间(``时间配额``)来应用动作,并且在给定的时间间 -隔(``重置间隔``)内,只对具有目标访问模式的内存区域应用动作,而不使用特定数量(``大小配额``)。 +用于给定基于 DAMON 的操作方案的 :ref:`配额 ` 的目录。 + +在 ``quotas`` 目录下,存在七个文件(``ms``、``bytes``、 +``reset_interval_ms``、``effective_bytes``、``goal_tuner``、 +``fail_charge_num`` 和 ``fail_charge_denom``)以及两个目录(``weights`` +和 ``goals``)。 -当预计超过配额限制时,DAMON会根据 ``目标访问模式`` 的大小、访问频率和年龄,对找到的内存区域 -进行优先排序。为了进行个性化的优先排序,用户可以为这三个属性设置权重。 +你可以分别向这三个文件写入值,设置以毫秒为单位的 ``time quota``、以字节 +为单位的 ``size quota`` 以及以毫秒为单位的 ``reset interval``。随后, +DAMON 会尝试在 ``reset_interval_ms`` 内,只使用最多 ``time quota`` +毫秒把 ``action`` 应用于符合 ``access_pattern`` 的内存区域,并且只将该 +动作应用于最多 ``bytes`` 字节的内存区域。将 ``ms`` 和 ``bytes`` 都设置 +为零会禁用配额限制,除非至少设置了一个 :ref:`目标 +`。 -在 ``quotas`` 目录下,存在三个文件(``ms``, ``bytes``, ``reset_interval_ms``)和一个 -目录(``weights``),其中有三个文件(``sz_permil``, ``nr_accesses_permil``, 和 -``age_permil``)。 +你可以向 ``goal_tuner`` 文件写入算法名称,设置要使用的基于目标的有效 +配额自动调优算法。读取该文件会返回当前选择的调优算法。关于该功能的背景 +设计以及可选择算法的名称,请参考 :ref:`自动配额调优目标 ` 的设计文档。关于目标设置,请参考 +:ref:`goals 目录 `。 -你可以设置以毫秒为单位的 ``时间配额`` ,以字节为单位的 ``大小配额`` ,以及以毫秒为单位的 ``重 -置间隔`` ,分别向这三个文件写入数值。你还可以通过向 ``weights`` 目录下的三个文件写入数值来设 -置大小、访问频率和年龄的优先权,单位为千分之一。 +你可以分别向 ``fail_charge_num`` 和 ``fail_charge_denom`` 文件写入该比率的 +分子和分母,设置动作失败内存配额计费比率。读取这些文件会返回当前设置的值。 +关于该比率功能的更多细节,请参考 :ref:`设计 `。 + +时间配额会在内部转换为大小配额。在转换后的大小配额和用户指定的大小配额之间, +会应用较小者。根据用户指定的 :ref:`目标 +`,有效大小配额会被进一步调整。读取 +``effective_bytes`` 会返回当前有效大小配额。该文件不会实时更新,所以用户 +应通过向相关 ``kdamonds//state`` 文件写入特殊关键字 +``update_schemes_effective_quotas``,要求 DAMON sysfs 接口为统计信息更新该 +文件内容。 + +在 ``weights`` 目录下,存在三个文件(``sz_permil``、 +``nr_accesses_permil`` 和 ``age_permil``)。你可以向 ``weights`` 目录下的 +三个文件写入值,设置大小、访问频率和年龄的 :ref:`优先级权重 `,单位为千分之一。 + +.. _sysfs_schemes_quota_goals_zh_CN: + +schemes//quotas/goals/ +------------------------- + +用于给定基于 DAMON 的操作方案的 :ref:`自动配额调优目标 ` 的目录。 + +开始时,该目录只有一个文件 ``nr_goals``。向该文件写入一个数字(``N``) +会创建数量为该数字的子目录,命名为 ``0`` 到 ``N-1``。每个目录表示一个 +目标和当前达成情况。在多个反馈中,会使用最好的一个。 + +每个目标目录包含五个文件,即 ``target_metric``、``target_value``、 +``current_value``、``nid`` 和 ``path``。用户可以通过写入和读取每个文件, +设置和获取 :ref:`设计文档 ` 中指定的 +配额自动调优目标的五个参数。因为内核不会更新 ``current_value``,所以只有 +当 ``target_metric`` 为 ``user_input`` 时,读取它才有意义。请注意,用户还应 +向 :ref:`kdamond 目录 ` 的 ``state`` 文件写入 +``commit_schemes_quota_goals``,以便将反馈传递给 DAMON。 + +.. _sysfs_watermarks_zh_CN: schemes//watermarks/ ----------------------- -为了便于根据系统状态激活和停用每个方案,DAMON提供了一个称为水位的功能。该功能接收五个值,称为 -``度量`` 、``间隔`` 、``高`` 、``中`` 、``低`` 。``度量值`` 是指可以测量的系统度量值,如 -自由内存比率。如果系统的度量值 ``高`` 于memoent的高值或 ``低`` 于低值,则该方案被停用。如果 -该值低于 ``中`` ,则该方案被激活。 +用于给定基于 DAMON 的操作方案的 :ref:`水位 ` 的目录。 + +在 watermarks 目录下,存在五个文件(``metric``、``interval_us``、``high``、 +``mid`` 和 ``low``),用于设置度量指标、度量指标检查之间的时间间隔以及三个 +水位。你可以分别通过写入和读取这些文件来设置和获取这五个值。 + +可写入 ``metric`` 文件的关键字及其含义如下。 + + - none:忽略水位 + - free_mem_rate:系统空闲内存率(千分之一) + +``interval_us`` 应以微秒为单位写入。 + +.. _sysfs_filters_zh_CN: + +schemes//{core\_,ops\_,}filters/ +----------------------------------- + +用于给定基于 DAMON 的操作方案的 :ref:`过滤器 ` 的目录。 + +``core_filters`` 和 ``ops_filters`` 目录分别用于由 DAMON 核心层和操作集层 +处理的过滤器。``filters`` 目录可用于安装过滤器,而不管它们由哪一层处理。 +``core_filters`` 和 ``ops_filters`` 请求的过滤器会先于 ``filters`` 中的 +过滤器安装。这三个目录具有相同的文件。 + +使用 ``filters`` 目录会使过滤器求值顺序难以预期。因此,``filters`` 目录 +已被弃用。它仍然可以工作,但计划在不久的将来移除。用户应改用 +``core_filters`` 和 ``ops_filters`` 目录。 + +开始时,该目录只有一个文件 ``nr_filters``。向该文件写入一个数字(``N``) +会创建数量为该数字的子目录,命名为 ``0`` 到 ``N-1``。每个目录表示一个 +过滤器。过滤器按数字顺序求值。 + +每个过滤器目录包含九个文件,即 ``type``、``matching``、``allow``、 +``memcg_path``、``addr_start``、``addr_end``、``min``、``max`` 和 +``damon_target_idx``。你可以向 ``type`` 文件写入过滤器类型。关于可用 +类型名称、它们的含义以及它们由哪一层处理,请参考 :ref:`设计文档 `。 -在水位目录下,存在五个文件(``metric``, ``interval_us``,``high``, ``mid``, and ``low``) -用于设置每个值。你可以通过向这些文件的写入来分别设置和获取这五个值。 +对于 ``memcg`` 类型,你可以向 ``memcg_path`` 文件写入从 cgroups 挂载点 +开始的内存 cgroup 路径,指定感兴趣的内存 cgroup。对于 ``addr`` 类型, +你可以分别向 ``addr_start`` 和 ``addr_end`` 文件写入范围(左闭右开区间) +的起始和结束地址。对于 ``hugepage_size`` 类型,你可以分别向 ``min`` 和 +``max`` 文件写入范围(闭区间)的最小和最大大小。对于 ``target`` 类型, +你可以向 ``damon_target_idx`` 文件写入 DAMON 上下文的监测目标列表中目标的 +索引。 -可以写入 ``metric`` 文件的关键词和含义如下。 +你可以向 ``matching`` 文件写入 ``Y`` 或 ``N``,指定过滤器是否用于匹配 +``type`` 的内存。你可以向 ``allow`` 文件写入 ``Y`` 或 ``N``,指定是否允许 +将动作应用于满足 ``type`` 和 ``matching`` 的内存。 - - none: 忽略水位 - - free_mem_rate: 系统的自由内存率(千分比)。 +例如,下面的操作将 DAMOS 动作限制为只应用于除 ``/having_care_already`` 之外 +所有内存 cgroup 的非匿名页。:: -``interval`` 应以微秒为单位写入。 + # cd ops_filters/0/ + # echo 2 > nr_filters + # # disallow anonymous pages + echo anon > 0/type + echo Y > 0/matching + echo N > 0/allow + # # further filter out all cgroups except one at '/having_care_already' + echo memcg > 1/type + echo /having_care_already > 1/memcg_path + echo Y > 1/matching + echo N > 1/allow + +更多细节请参考 :ref:`DAMOS 过滤器设计文档 `, +其中包括不同 ``allow`` 的多个过滤器如何工作、每个过滤器何时受支持,以及 +统计信息上的差异。 + +.. _damon_sysfs_dests_zh_CN: + +schemes//dests/ +------------------ + +用于指定给定基于 DAMON 的操作方案动作的目的地的目录。如果给定方案的动作 +不支持多个目的地,则该目录会被忽略。只有 ``DAMOS_MIGRATE_{HOT,COLD}`` 动作 +支持多个目的地。 + +开始时,该目录只有一个文件 ``nr_dests``。向该文件写入一个数字(``N``) +会创建数量为该数字的子目录,命名为 ``0`` 到 ``N-1``。每个目录表示一个 +动作目的地。 + +每个目的地目录包含两个文件,即 ``id`` 和 ``weight``。用户可以向 ``id`` +文件写入目的地标识符,并从中读取它。对于 ``DAMOS_MIGRATE_{HOT,COLD}`` 动作, +应将迁移目的节点的节点 id 写入 ``id`` 文件。用户可以向 ``weight`` 文件写入 +该目的地在给定目的地中的权重,并从中读取它。权重可以是任意整数。当 DAMOS +将动作应用于内存区域的每个实体时,它会根据目的地的相对权重选择该动作的 +目的地。 + +.. _sysfs_schemes_stats_zh_CN: schemes//stats/ ------------------ -DAMON统计每个方案被尝试应用的区域的总数量和字节数,每个方案被成功应用的区域的两个数字,以及 -超过配额限制的总数量。这些统计数据可用于在线分析或调整方案。 +DAMON 为每个方案统计信息。这些统计信息可用于方案的在线分析或调优。关于 +这些统计信息的更多细节,请参考 :ref:`设计文档 `。 + +可以分别通过读取 ``stats`` 目录下的文件(``nr_tried``、``sz_tried``、 +``nr_applied``、``sz_applied``、``sz_ops_filter_passed``、``qt_exceeds``、 +``nr_snapshots`` 和 ``max_nr_snapshots``)来获取这些统计信息。 -可以通过读取 ``stats`` 目录下的文件(``nr_tried``, ``sz_tried``, ``nr_applied``, -``sz_applied``, 和 ``qt_exceeds``))分别检索这些统计数据。这些文件不是实时更新的,所以 -你应该要求DAMON sysfs接口通过在相关的 ``kdamonds//state`` 文件中写入一个特殊的关键字 -``update_schemes_stats`` 来更新统计信息的文件内容。 +默认情况下,这些文件不会实时更新。用户应要求 DAMON sysfs 接口使用 +``refresh_ms`` 周期性地更新这些文件,或者向相关 ``kdamonds//state`` 文件 +写入特殊关键字 ``update_schemes_stats`` 来进行一次性更新。更多细节请参考 +:ref:`kdamond 目录 `。 + +.. _sysfs_schemes_tried_regions_zh_CN: schemes//tried_regions/ -------------------------- -当一个特殊的关键字 ``update_schemes_tried_regions`` 被写入相关的 ``kdamonds//state`` -文件时,DAMON会在这个目录下创建从 ``0`` 开始命名的整数目录。每个目录包含的文件暴露了关于每个 -内存区域的详细信息,在下一个 :ref:`聚集区间 `,相应的方案的 ``动作`` -已经尝试在这个目录下应用。这些信息包括地址范围、``nr_accesses`` 以及区域的 ``年龄`` 。 +该目录初始时有一个文件 ``total_bytes``。 + +当特殊关键字 ``update_schemes_tried_regions`` 被写入相关 +``kdamonds//state`` 文件时,DAMON 会更新 ``total_bytes`` 文件,使读取 +它时返回该方案尝试区域的总大小,并在该目录下创建从 ``0`` 开始以整数命名的 +目录。每个目录包含一些文件,暴露该目录下对应方案的 ``action`` 在对应方案的 +下一个 :ref:`应用间隔 ` 内尝试应用的每个内存区域的详细 +信息。这些信息包括该区域的地址范围、``nr_accesses`` 和 ``age``。 + +向相关 ``kdamonds//state`` 文件写入 ``update_schemes_tried_bytes`` 只会 +更新 ``total_bytes`` 文件,而不会创建子目录。 + +当另一个特殊关键字 ``clear_schemes_tried_regions`` 被写入相关 +``kdamonds//state`` 文件时,这些目录会被删除。 + +该目录的预期用途是调查方案行为,以及类似查询的高效数据访问监测结果获取。 +特别是对于后一种用例,用户可以将 ``action`` 设置为 ``stat``,并将 +``access pattern`` 设置为他们感兴趣、想要查询的模式。 -当另一个特殊的关键字 ``clear_schemes_tried_regions`` 被写入相关的 ``kdamonds//state`` -文件时,这些目录将被删除。 +.. _sysfs_schemes_tried_region_zh_CN: tried_regions// ------------------ -在每个区域目录中,你会发现四个文件(``start``, ``end``, ``nr_accesses``, and ``age``)。 -读取这些文件将显示相应的基于DAMON的操作方案 ``动作`` 试图应用的区域的开始和结束地址、``nr_accesses`` -和 ``年龄`` 。 +在每个区域目录中,你会发现五个文件(``start``、``end``、``nr_accesses``、 +``age`` 和 ``sz_filter_passed``)。读取这些文件将显示相应的基于DAMON的操作方案 +``action`` 试图应用的区域属性。 + +tried_regions//probes/ +------------------------- + +在每个区域目录中,还存在一个目录(``probes``)。在该目录中,存在命名为 +``0`` 到 ``N-1`` 的子目录。``N`` 是已安装探针的数量。在每个数字命名的 +目录中,存在一个文件(``hits``)。读取该文件会显示该区域的数据属性监测 +探针命中正样本的数量。 用例 ~~~~ @@ -330,16 +601,46 @@ tried_regions// 请注意,我们强烈建议使用用户空间的工具,如 `damo `_ , 而不是像上面那样手动读写文件。以上只是一个例子。 +.. _tracepoint_zh_CN: -监测结果的监测点 +监测结果的跟踪点 ================ -DAMON通过一个tracepoint ``damon:damon_aggregated`` 提供监测结果. 当监测开启时,你可 -以记录追踪点事件,并使用追踪点支持工具如perf显示结果。比如说:: +用户可以通过 :ref:`tried_regions ` 获取 +监测结果。该接口适合获取快照,但用于完整记录所有监测结果时可能效率不高。 +为此,提供了两个跟踪点,即 ``damon:damon_aggregated`` 和 +``damon:damos_before_apply``。``damon:damon_aggregated`` 提供完整的监测 +结果,而 ``damon:damos_before_apply`` 提供每个基于 DAMON 的操作方案 +(:ref:`DAMOS `)将要应用到的区域的监测结果。因此, +``damon:damos_before_apply`` 更适合记录 DAMOS 的内部行为,或者基于 DAMOS +目标访问 :ref:`模式 ` 的类似查询的高效 +监测结果记录。 - # echo on > monitor_on_DEPRECATED +监测开启时,你可以记录跟踪点事件,并使用跟踪点支持工具如 ``perf`` 显示结果。比如说:: + + # echo on > kdamonds/0/state # perf record -e damon:damon_aggregated & # sleep 5 # kill 9 $(pidof perf) - # echo off > monitor_on_DEPRECATED + # echo off > kdamonds/0/state # perf script + kdamond.0 46568 [027] 79357.842179: damon:damon_aggregated: target_id=0 nr_regions=11 122509119488-135708762112: 0 864 + [...] + +``perf script`` 输出的每一行表示一个监测区域。前五个字段与其他跟踪点输出 +一样。第六个字段 ``target_id=X`` 显示该区域的监测目标 id。第七个字段 +``nr_regions=X`` 显示该目标的监测区域总数。第八个字段 ``X-Y:`` 显示 +该区域的起始地址 ``X`` 和结束地址 ``Y``,单位为字节。第九个字段 ``X`` +显示该区域的 ``nr_accesses`` (关于该计数器的更多细节请参考 :ref:`设计 `)。最后,第十个字段 ``X`` 显示该区域 +的 ``age`` (关于该计数器的更多细节请参考 :ref:`设计 `)。 + +如果事件是 ``damon:damos_before_apply``,``perf script`` 输出大致如下:: + + kdamond.0 47293 [000] 80801.060214: damon:damos_before_apply: ctx_idx=0 scheme_idx=0 target_idx=0 nr_regions=11 121932607488-135128711168: 0 136 + [...] + +输出的每一行表示在被跟踪的时间点,每个基于 DAMON 的操作方案即将应用到的 +一个监测区域。前五个字段与通常一样。除了 ``damon_aggregated`` 跟踪点的 +输出之外,它还显示方案所属 DAMON 上下文在该上下文的 kdamond 的上下文列表 +中的索引(``ctx_idx=X``),以及该方案在该上下文的方案列表中的索引 +(``scheme_idx=X``)。 From e6f775140ac98a967738432bd6628cd50384676f Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:48 +0000 Subject: [PATCH 0592/2286] docs/zh_CN: update DAMON reclaim translation Update the Chinese translation of .../admin-guide/mm/damon/reclaim.rst. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/reclaim.rst | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst index d14ba32f77885e..1e9375947295ca 100644 --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/reclaim.rst @@ -58,6 +58,19 @@ enabled DAMON_RECLAIM。注意,由于基于水位的激活条件,DAMON_RECLAIM不能进行真正的监测和回收。 这一点请参考下面关于水位参数的描述。 +commit_inputs +------------- + +让 DAMON_RECLAIM 再次读取除 ``enabled`` 外的输入参数。 + +DAMON_RECLAIM 运行期间更新的输入参数默认不会应用。一旦该参数被设置为 +``Y``,DAMON_RECLAIM 会再次读取除 ``enabled`` 外的参数值。重新读取完成后, +该参数会被设置为 ``N``。如果重新读取时发现无效参数,DAMON_RECLAIM 会被 +禁用。 + +一旦向该参数写入 ``Y``,用户在再次读取 ``commit_inputs`` 返回 ``N`` 之前, +不得写入任何参数。如果用户违反该规则,内核可能表现出未定义行为。 + min_age ------- @@ -68,6 +81,15 @@ min_age 默认为120秒。 +autotune_monitoring_intervals +----------------------------- + +如果该参数设置为 ``Y``,DAMON_RECLAIM 会自动调优 DAMON 的采样和聚集间隔。 +自动调优的目标是在每个 DAMON 快照中捕获有意义数量的访问事件,同时将采样 +间隔限制在最小 5 毫秒、最大 10 秒。将其设置为 ``N`` 会禁用自动调优。 + +默认禁用。 + quota_ms -------- @@ -98,6 +120,29 @@ quota_reset_interval_ms 默认为1秒。 +quota_mem_pressure_us +--------------------- + +期望的内存压力停滞时间水平,单位为微秒。 + +在保持其他配额设置的上限的同时,DAMON_RECLAIM 会自动增减配额的有效水平, +目标是产生该水平的内存压力。系统范围的 ``some`` 内存 PSI 会按每个配额重置 +间隔(``quota_reset_interval_ms``)以微秒为单位收集,并与该值比较,以判断 +目标是否满足。值为零表示禁用该自动调优功能。 + +默认禁用。 + +quota_autotune_feedback +----------------------- + +用户可指定的有效配额自动调优反馈。 + +在保持其他配额设置的上限的同时,DAMON_RECLAIM 会自动增减配额的有效水平, +目标是从用户接收到值为 ``10,000`` 的反馈。DAMON_RECLAIM 假定反馈值和配额 +成正比。值为零表示禁用该自动调优功能。 + +默认禁用。 + wmarks_interval --------------- @@ -149,6 +194,8 @@ min_nr_regions DAMON用于冷内存监测的最小监测区域数。这可以用来设置监测质量的下限。但是,设 置的太高可能会导致监测开销的增加。更多细节请参考DAMON文档 (:doc:`usage`) 。 +请注意,该值必须为 3 或更高。该下限的理由请参考设计文档的 :ref:`监测 ` 章节。 + max_nr_regions -------------- @@ -163,7 +210,7 @@ monitor_region_start 目标内存区域的物理地址起点。 DAMON_RECLAIM将对其进行工作的内存区域的起始物理地址。也就是说,DAMON_RECLAIM -将在这个区域中找到冷的内存区域并进行回收。默认情况下,该区域使用最大系统内存区。 +将在这个区域中找到冷的内存区域并进行回收。默认情况下,该区域使用系统的整个物理内存。 monitor_region_end ------------------ @@ -171,7 +218,35 @@ monitor_region_end 目标内存区域的结束物理地址。 DAMON_RECLAIM将对其进行工作的内存区域的末端物理地址。也就是说,DAMON_RECLAIM将 -在这个区域内找到冷的内存区域并进行回收。默认情况下,该区域使用最大系统内存区。 +在这个区域内找到冷的内存区域并进行回收。默认情况下,该区域使用系统的整个物理内存。 + +addr_unit +--------- + +内存地址和字节数的缩放因子。 + +该参数用于设置和获取 DAMON_RECLAIM 的 DAMON 实例的 :ref:`地址单位 ` 参数。 + +``monitor_region_start`` 和 ``monitor_region_end`` 应以该单位提供。例如, +假设 ``addr_unit``、``monitor_region_start`` 和 ``monitor_region_end`` +分别设置为 ``1024``、``0`` 和 ``10``。那么 DAMON_RECLAIM 将处理从地址零 +开始、长度为 10 KiB 的物理地址范围(以字节表示为 +``[0 * 1024, 10 * 1024)``)。 + +``bytes_reclaim_tried_regions`` 和 ``bytes_reclaimed_regions`` 也使用该单位。 +例如,假设 ``addr_unit``、``bytes_reclaim_tried_regions`` 和 +``bytes_reclaimed_regions`` 的值分别为 ``1024``、``42`` 和 ``32``。那么这 +表示 DAMON_RECLAIM 总共尝试回收 42 KiB 内存,并成功回收了 32 KiB 内存。 + +如果不确定,只使用默认值(``1``)并忘记这个参数即可。 + +skip_anon +--------- + +跳过匿名页回收。 + +如果该参数设置为 ``Y``,DAMON_RECLAIM 不会回收匿名页。默认值为 ``N``。 + kdamond_pid ----------- @@ -223,6 +298,9 @@ DAMON_RECLAIM再次什么都不做,这样我们就可以退回到基于LRU列 # echo 200 > wmarks_low # echo Y > enabled +请注意,该模块(damon_reclaim)不能与其他基于 DAMON 的专用模块同时运行。 +更多细节请参考 :ref:`DAMON 设计文档的专用模块互斥性 `。 + .. [1] https://research.google/pubs/pub48551/ .. [2] https://lwn.net/Articles/787611/ .. [3] https://www.kernel.org/doc/html/latest/mm/free_page_reporting.html From c096854183fa1fbcc43f52ef1992b6eedca0f4a5 Mon Sep 17 00:00:00 2001 From: Doehyun Baek Date: Tue, 18 Aug 2026 07:03:49 +0000 Subject: [PATCH 0593/2286] docs/zh_CN: update DAMON LRU sort translation Update the Chinese translation of .../admin-guide/mm/damon/lru_sort.rst. Update the translation through commit c9380cdd949d ("Docs/{admin-guide,mm}/damon: fix DAMON documentation details"). Signed-off-by: Doehyun Baek Signed-off-by: Alex Shi --- .../zh_CN/admin-guide/mm/damon/lru_sort.rst | 68 +++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/mm/damon/lru_sort.rst b/Documentation/translations/zh_CN/admin-guide/mm/damon/lru_sort.rst index 03d33c710604c9..6af0dd59ef5db6 100644 --- a/Documentation/translations/zh_CN/admin-guide/mm/damon/lru_sort.rst +++ b/Documentation/translations/zh_CN/admin-guide/mm/damon/lru_sort.rst @@ -48,7 +48,7 @@ DAMON_LRU_SORT使用DAMON寻找热页(范围内的页面访问频率高于用 为了让系统管理员打开或者关闭并且调节指定的系统,DAMON_LRU_SORT设计了模块参数。 这意味着,你可以添加 ``damon_lru_sort.=`` 到内核的启动命令行 -参数,或者在 ``/sys/modules/damon_lru_sort/parameters/`` 写入正确的 +参数,或者在 ``/sys/module/damon_lru_sort/parameters/`` 写入正确的 值。 下边是每个参数的描述 @@ -71,6 +71,41 @@ commit_inputs ,DAMON_LRU_SORT会再次读取除了 ``enabled`` 之外的参数。读取完成后,这个参数会被 设置为 ``N`` 。如果在读取时发现有无效参数,DAMON_LRU_SORT会被关闭。 +一旦向该参数写入 ``Y``,用户在再次读取 ``commit_inputs`` 返回 ``N`` 之前, +不得写入任何参数。如果用户违反该规则,内核可能表现出未定义行为。 + +active_mem_bp +------------- + +期望的活跃内存与[非]活跃内存比率,单位为 bp(1/10,000)。 + +在保持其他配额设置的上限的同时,DAMON_LRU_SORT 会自动增减配额的有效水平, +目标是让热/冷内存的 LRU [降低]优先级处理产生该活跃内存与[非]活跃内存比率。 +值为零表示禁用该自动调优功能。 + +默认禁用。 + +autotune_monitoring_intervals +----------------------------- + +如果该参数设置为 ``Y``,DAMON_LRU_SORT 会自动调优 DAMON 的采样和聚集间隔。 +自动调优的目标是在每个 DAMON 快照中捕获有意义数量的访问事件,同时将采样 +间隔限制在最小 5 毫秒、最大 10 秒。将其设置为 ``N`` 会禁用自动调优。 + +默认禁用。 + +filter_young_pages +------------------ + +相应地为 LRU [降低]优先级处理过滤[非]年轻页。 + +如果设置该参数,则在每次 LRU [降低]优先级处理操作前再次检查页级访问 +(年轻性)。如果该页自上次检查以来未被访问(不年轻),则跳过 LRU 提高 +优先级操作。如果该页自上次检查以来已被访问(年轻),则跳过 LRU 降低优先级 +操作。当该参数分别设置为 ``Y`` 或 ``N`` 时,会启用或禁用该功能。 + +默认禁用。 + hot_thres_access_freq --------------------- @@ -163,6 +198,8 @@ min_nr_regions 对冷内存区域监测的最小数量。这个值可以作为监测质量的下限。不过,这个值设置的过 大会增加开销。更多细节请参考DAMON文档 (:doc:`usage`) 。默认值为10。 +请注意,该值必须为 3 或更高。该下限的理由请参考设计文档的 :ref:`监测 ` 章节。 + max_nr_regions -------------- @@ -176,14 +213,34 @@ monitor_region_start 目标内存区域的起始物理地址。 -DAMON_LRU_SORT要处理的目标内存区域的起始物理地址。默认,使用系统最大内存。 +DAMON_LRU_SORT要处理的目标内存区域的起始物理地址。默认,使用系统的整个物理内存。 monitor_region_end ------------------ 目标内存区域的结束物理地址。 -DAMON_LRU_SORT要处理的目标内存区域的结束物理地址。默认,使用系统最大内存。 +DAMON_LRU_SORT要处理的目标内存区域的结束物理地址。默认,使用系统的整个物理内存。 + +addr_unit +--------- + +内存地址和字节数的缩放因子。 + +该参数用于设置和获取 DAMON_RECLAIM 的 DAMON 实例的 :ref:`地址单位 ` 参数。 + +``monitor_region_start`` 和 ``monitor_region_end`` 应以该单位提供。例如, +假设 ``addr_unit``、``monitor_region_start`` 和 ``monitor_region_end`` +分别设置为 ``1024``、``0`` 和 ``10``。那么 DAMON_LRU_SORT 将处理从地址零 +开始、长度为 10 KiB 的物理地址范围(以字节表示为 +``[0 * 1024, 10 * 1024)``)。 + +带有 ``bytes_`` 前缀的统计参数也使用该单位。例如,假设 ``addr_unit``、 +``bytes_lru_sort_tried_hot_regions`` 和 ``bytes_lru_sorted_hot_regions`` 的值 +分别为 ``1024``、``42`` 和 ``32``。那么这表示 DAMON_LRU_SORT 尝试对 +42 KiB 热内存进行 LRU 排序,并总共成功对其中 32 KiB 内存进行了 LRU 排序。 + +如果不确定,只使用默认值(``1``)并忘记这个参数即可。 kdamond_pid ----------- @@ -252,7 +309,7 @@ LRU的优先级的提升,同时降低那些超过120秒无人访问的内存 进展且空闲内存低于20%,再次让DAMON_LRU_SORT停止工作,以此回退到以LRU链表为基础 以页面为单位的内存回收上。 :: - # cd /sys/modules/damon_lru_sort/parameters + # cd /sys/module/damon_lru_sort/parameters # echo 500 > hot_thres_access_freq # echo 120000000 > cold_min_age # echo 10 > quota_ms @@ -261,3 +318,6 @@ LRU的优先级的提升,同时降低那些超过120秒无人访问的内存 # echo 400 > wmarks_mid # echo 200 > wmarks_low # echo Y > enabled + +请注意,该模块(damon_lru_sort)不能与其他基于 DAMON 的专用模块同时运行。 +更多细节请参考 :ref:`DAMON 设计文档的专用模块互斥性 `。 From 9bbe7a116f3304d7b3ed50110411a08795eea694 Mon Sep 17 00:00:00 2001 From: Siwei Chen Date: Mon, 24 Aug 2026 12:38:00 +0800 Subject: [PATCH 0594/2286] docs/zh_CN: Add networking team documentation Chinese translation Translate .../networking/team.rst into Chinese. Update the translation through commit aa8a6ee3e3fc ("docs: networking: convert team.txt to ReST") Signed-off-by: Siwei Chen Reviewed-by: Dongliang Mu Reviewed-by: Weijie Yuan Signed-off-by: Alex Shi --- .../translations/zh_CN/networking/index.rst | 2 +- .../translations/zh_CN/networking/team.rst | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/networking/team.rst diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index 333e9f6cafffbf..fcb8dc8f2c72e1 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -29,6 +29,7 @@ alias mptcp-sysctl generic-hdlc + team timestamping Todolist: @@ -136,7 +137,6 @@ Todolist: * tc-queue-filters * tcp_ao * tcp-thin -* team * tipc * tproxy * tuntap diff --git a/Documentation/translations/zh_CN/networking/team.rst b/Documentation/translations/zh_CN/networking/team.rst new file mode 100644 index 00000000000000..e6cf19a245a1d3 --- /dev/null +++ b/Documentation/translations/zh_CN/networking/team.rst @@ -0,0 +1,16 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/team.rst + +:翻译: + + 陈思为 Siwei Chen + +==================== +Team(网络接口聚合) +==================== + +Team 设备由用户空间通过 libteam 库驱动,该库位于: + https://github.com/jpirko/libteam From a383af6b5409be7a0b38bc9563c4b8e866631624 Mon Sep 17 00:00:00 2001 From: Siwei Chen Date: Mon, 24 Aug 2026 12:38:01 +0800 Subject: [PATCH 0595/2286] docs/zh_CN: Add NIC SR-IOV APIs Chinese translation Translate .../networking/sriov.rst into Chinese. Update the translation through commit 4558645d139c ("docs: net: document guidance of implementing the SR-IOV NDOs") Signed-off-by: Siwei Chen Reviewed-by: Dongliang Mu Signed-off-by: Alex Shi --- .../translations/zh_CN/networking/index.rst | 2 +- .../translations/zh_CN/networking/sriov.rst | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/networking/sriov.rst diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index fcb8dc8f2c72e1..ae374c4cb14442 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -29,6 +29,7 @@ alias mptcp-sysctl generic-hdlc + sriov team timestamping @@ -128,7 +129,6 @@ Todolist: * secid * seg6-sysctl * smc-sysctl -* sriov * statistics * strparser * switchdev diff --git a/Documentation/translations/zh_CN/networking/sriov.rst b/Documentation/translations/zh_CN/networking/sriov.rst new file mode 100644 index 00000000000000..d5e16a099b070b --- /dev/null +++ b/Documentation/translations/zh_CN/networking/sriov.rst @@ -0,0 +1,34 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/sriov.rst + +:翻译: + + 陈思为 Siwei Chen + +=============== +网卡 SR-IOV API +=============== + +强烈建议现代网卡专注于实现 ``switchdev`` 模型 +(参见 `以太网交换机设备驱动模型(switchdev)`_) +来配置 SR-IOV 功能的转发与安全。 + +传统 API +======== + +旧的 SR-IOV API 在 ``rtnetlink`` Netlink 协议族中实现,是 ``RTM_GETLINK`` 和 +``RTM_SETLINK`` 命令的一部分。在驱动侧,它由若干 ``ndo_set_vf_*`` 和 +``ndo_get_vf_*`` 回调组成。 + +由于传统 API 与协议栈其余部分集成不佳,该 API 被视为已冻结;不再接受任何新功能或扩展。 +新驱动不应实现那些不常用的回调; +即以下回调禁止使用: + + - ``ndo_get_vf_port`` + - ``ndo_set_vf_port`` + - ``ndo_set_vf_rss_query_en`` + +.. _`以太网交换机设备驱动模型(switchdev)`: https://docs.kernel.org/networking/switchdev.html#switchdev From 87f8a9969fdbd278adc1f570a6f0de73eeff75a6 Mon Sep 17 00:00:00 2001 From: Siwei Chen Date: Mon, 24 Aug 2026 12:38:02 +0800 Subject: [PATCH 0596/2286] docs/zh_CN: Add Softnet Driver Issues Chinese translation Translate .../networking/driver.rst into Chinese. Update the translation through commit 96fbe161e402 ("docs: networking: add guidance on what to push via extack") Signed-off-by: Siwei Chen Reviewed-by: Dongliang Mu Reviewed-by: Weijie Yuan Signed-off-by: Alex Shi --- .../translations/zh_CN/networking/driver.rst | 138 ++++++++++++++++++ .../translations/zh_CN/networking/index.rst | 2 +- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/networking/driver.rst diff --git a/Documentation/translations/zh_CN/networking/driver.rst b/Documentation/translations/zh_CN/networking/driver.rst new file mode 100644 index 00000000000000..acb0863c6b0de0 --- /dev/null +++ b/Documentation/translations/zh_CN/networking/driver.rst @@ -0,0 +1,138 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/driver.rst + +:翻译: + + 陈思为 Siwei Chen + +================ +Softnet 驱动问题 +================ + +探测指南 +======== + +地址校验 +-------- + +为设备获取的任何硬件层地址都应进行校验。例如,以太网地址应使用 +linux/etherdevice.h 中的 is_valid_ether_addr() 进行检查。 + +关闭/停止指南 +============= + +静默 +---- + +ndo_stop 例程被调用后,硬件不得再收发任何数据。所有在途报文都必须中止。 +如有必要,应轮询或等待任何复位命令完成。 + +自动关闭 +-------- + +若设备仍处于 UP 状态,ndo_stop 例程将由 unregister_netdevice 调用。 + +发送路径指南 +============ + +提前停止队列 +------------ + +ndo_start_xmit 方法在任何正常情况下都不得返回 NETDEV_TX_BUSY。 +除非设备确实无法预先判断其发送功能何时会繁忙,否则返回该值被视为严重错误。 + +相反,驱动必须正确地维护队列。例如,对于实现分散-聚集 +(scatter-gather)的驱动,这意味着: + +.. code-block:: c + + static u32 drv_tx_avail(struct drv_ring *dr) + { + u32 used = READ_ONCE(dr->prod) - READ_ONCE(dr->cons); + + return dr->tx_ring_size - (used & dr->tx_ring_mask); + } + + static netdev_tx_t drv_hard_start_xmit(struct sk_buff *skb, + struct net_device *dev) + { + struct drv *dp = netdev_priv(dev); + struct netdev_queue *txq; + struct drv_ring *dr; + int idx; + + idx = skb_get_queue_mapping(skb); + dr = dp->tx_rings[idx]; + txq = netdev_get_tx_queue(dev, idx); + + //... + /* 这应当是极为罕见的竞争——记录下来。 */ + if (drv_tx_avail(dr) <= skb_shinfo(skb)->nr_frags + 1) { + netif_tx_stop_queue(txq); + netdev_warn(dev, "Tx Ring full when queue awake!\n"); + return NETDEV_TX_BUSY; + } + + //... 将报文排队到网卡 ... + + netdev_tx_sent_queue(txq, skb->len); + + //... 使用 WRITE_ONCE() 更新 tx 生产者索引 ... + + if (!netif_txq_maybe_stop(txq, drv_tx_avail(dr), + MAX_SKB_FRAGS + 1, 2 * MAX_SKB_FRAGS)) + dr->stats.stopped++; + + //... + return NETDEV_TX_OK; + } + +然后在 TX 回收事件处理的末尾: + +.. code-block:: c + + //... 使用 WRITE_ONCE() 更新 tx 消费者索引 ... + + netif_txq_completed_wake(txq, cmpl_pkts, cmpl_bytes, + drv_tx_avail(dr), 2 * MAX_SKB_FRAGS); + +无锁队列停止/唤醒辅助宏 +~~~~~~~~~~~~~~~~~~~~~~~ + +.. kernel-doc:: include/net/netdev_queues.h + :doc: Lockless queue stopping / waking helpers. + +netif_txq_maybe_stop()、netif_txq_try_stop() 等标准宏已经过充分测试, +请优先使用它们,而非本地同步方案。 + +无独占所有权 +------------ + +ndo_start_xmit 方法不得修改被克隆 SKB 的共享部分。 + +及时完成 +-------- + +请谨记:一旦 ndo_start_xmit 方法返回 NETDEV_TX_OK, +驱动就有责任在有限的时间内释放该 SKB。 + +例如,这意味着不允许你的 TX 缓解方案在没有新 TX 报文发送时, +让 TX 报文永远“滞留”在 TX 环中而不被回收。 +这种错误会使等待发送缓冲区空间释放的套接字发生死锁。 + +若 ndo_start_xmit 方法返回 NETDEV_TX_BUSY, +则不得保留对该 SKB 的任何引用,也不得尝试释放它。 + +错误消息报告 +============ + +许多驱动配置接口会向驱动传递一个 Netlink 扩展 ACK(``extack``)对象 +(直接作为参数,或作为参数结构体的成员)。驱动应尽量通过 ``extack`` 对象 +报告大多数错误。表示系统或设备行为异常、处于不良状态的系统级异常,则应继续 +报告到系统日志。 + +消息应 **要么** 通过 ``extack`` 传递, **要么** 写入系统日志。驱动不应 +试图将同一信息同时报告到两处。 diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index ae374c4cb14442..4675d23a7f2854 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -21,6 +21,7 @@ :maxdepth: 1 msg_zerocopy + driver napi vxlan netif-msg @@ -76,7 +77,6 @@ Todolist: * dctcp * devmem * dns_resolver -* driver * eql * fib_trie * filter From f90b482060a08d12c3b04630cdf55597bd62d8d6 Mon Sep 17 00:00:00 2001 From: Siwei Chen Date: Mon, 24 Aug 2026 12:38:03 +0800 Subject: [PATCH 0597/2286] docs/zh_CN: Add ipv6 Chinese translation Translate .../networking/ipv6.rst into Chinese. Update the translation through commit 19093313cb04 ("docs: networking: convert ipv6.txt to ReST") Signed-off-by: Siwei Chen Reviewed-by: Dongliang Mu Reviewed-by: Weijie Yuan Signed-off-by: Alex Shi --- .../translations/zh_CN/networking/index.rst | 2 +- .../translations/zh_CN/networking/ipv6.rst | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/networking/ipv6.rst diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index 4675d23a7f2854..cd768d0a2837cf 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -22,6 +22,7 @@ msg_zerocopy driver + ipv6 napi vxlan netif-msg @@ -89,7 +90,6 @@ Todolist: * ip_dynaddr * ipsec * ip-sysctl -* ipv6 * ipvlan * ipvs-sysctl * kcm diff --git a/Documentation/translations/zh_CN/networking/ipv6.rst b/Documentation/translations/zh_CN/networking/ipv6.rst new file mode 100644 index 00000000000000..3726b6fdc460f5 --- /dev/null +++ b/Documentation/translations/zh_CN/networking/ipv6.rst @@ -0,0 +1,76 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/ipv6.rst + +:翻译: + + 陈思为 Siwei Chen + +==== +IPv6 +==== + + +ipv6 模块的选项在加载时以参数形式提供。 + +模块选项可作为 insmod 或 modprobe 命令的命令行参数给出,但通常在 +``/etc/modules.d/*.conf`` 配置文件中,或在发行版特定的配置文件中指定。 + +可用的 ipv6 模块参数如下所列。若未指定某参数,则使用其默认值。 + +参数如下: + +disable + + 指定是否在加载 IPv6 模块的同时禁用其全部功能。当另一模块依赖于 IPv6 + 模块已加载,但又不需要任何 IPv6 地址或操作时,可使用此选项。 + + 可选值及其效果如下: + + 0 + 启用 IPv6。 + + 这是默认值。 + + 1 + 禁用 IPv6。 + + 接口不会添加 IPv6 地址,也无法打开 IPv6 套接字。 + + 需要重启才能启用 IPv6。 + +autoconf + + 指定是否在所有接口上启用 IPv6 地址自动配置。当不希望根据路由器通告 + (Router Advertisement)中收到的前缀自动生成地址时,可使用此选项。 + + 可选值及其效果如下: + + 0 + 所有接口均禁用 IPv6 地址自动配置。 + + 仅添加 IPv6 回环地址(::1)与链路本地地址。 + + 1 + 所有接口均启用 IPv6 地址自动配置。 + + 这是默认值。 + +disable_ipv6 + + 指定是否在所有接口上禁用 IPv6。 + 当不需要任何 IPv6 地址时,可使用此选项。 + + 可选值及其效果如下: + + 0 + 在所有接口上启用 IPv6。 + + 这是默认值。 + + 1 + 在所有接口上禁用 IPv6。 + + 接口不会添加 IPv6 地址。 From 45a89a3d4fe195f843cf1533f4057d081bd757de Mon Sep 17 00:00:00 2001 From: Siwei Chen Date: Mon, 24 Aug 2026 12:38:04 +0800 Subject: [PATCH 0598/2286] docs/zh_CN: Add LSM/SELinux secid Chinese translation Translate .../networking/secid.rst into Chinese. Update the translation through commit de1fd4a7b0f2 ("docs: networking: convert secid.txt to ReST") Signed-off-by: Siwei Chen Reviewed-by: Dongliang Mu Signed-off-by: Alex Shi --- .../translations/zh_CN/networking/index.rst | 2 +- .../translations/zh_CN/networking/secid.rst | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/networking/secid.rst diff --git a/Documentation/translations/zh_CN/networking/index.rst b/Documentation/translations/zh_CN/networking/index.rst index cd768d0a2837cf..bf22d22b64db81 100644 --- a/Documentation/translations/zh_CN/networking/index.rst +++ b/Documentation/translations/zh_CN/networking/index.rst @@ -24,6 +24,7 @@ driver ipv6 napi + secid vxlan netif-msg xfrm_proc @@ -126,7 +127,6 @@ Todolist: * representors * rxrpc * sctp -* secid * seg6-sysctl * smc-sysctl * statistics diff --git a/Documentation/translations/zh_CN/networking/secid.rst b/Documentation/translations/zh_CN/networking/secid.rst new file mode 100644 index 00000000000000..94d9e7fb5d1336 --- /dev/null +++ b/Documentation/translations/zh_CN/networking/secid.rst @@ -0,0 +1,24 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/networking/secid.rst + +:翻译: + + 陈思为 Siwei Chen + +================= +LSM/SeLinux secid +================= + +flowi 结构体: + +flowi 结构体中的 secid 成员在 LSM(如 SELinux)中用于表示该流的标签。 +该流的标签目前用于选择相匹配的带标签 xfrm。 + +若为出向流,标签派生自套接字(若有);如果该流是为响应某个入站报文而生成的, +则标签派生自相应的入站报文(例如 TCP 复位、timewait ACK 等)。 +在特殊情况下,也可酌情让标签派生自其他来源,如进程上下文、设备等。 + +若为入向流,标签派生自报文所使用的 IPSec 安全关联(若存在)。 From 2d24f71414c2bd5588df43a10689c9525d29956a Mon Sep 17 00:00:00 2001 From: Weijie Yuan Date: Mon, 17 Aug 2026 01:47:45 +0800 Subject: [PATCH 0599/2286] docs/zh_CN: add process/applying-patches Chinese translation Translate process/applying-patches.rst into Chinese, and update the toctree. Update the translation through commit fa04150b8ef7 ("Documentation: describe how to apply incremental stable patches") Signed-off-by: Weijie Yuan Signed-off-by: Alex Shi --- .../zh_CN/process/applying-patches.rst | 390 ++++++++++++++++++ .../translations/zh_CN/process/index.rst | 2 +- 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/zh_CN/process/applying-patches.rst diff --git a/Documentation/translations/zh_CN/process/applying-patches.rst b/Documentation/translations/zh_CN/process/applying-patches.rst new file mode 100644 index 00000000000000..c056b241b0cdad --- /dev/null +++ b/Documentation/translations/zh_CN/process/applying-patches.rst @@ -0,0 +1,390 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: ../disclaimer-zh_CN.rst + +:Original: Documentation/process/applying-patches.rst + +:翻译: + + 袁维杰 Weijie Yuan + +将补丁应用到 Linux 内核 ++++++++++++++++++++++++ + +原文作者: + Jesper Juhl,2005 年 8 月 + +.. note:: + + 本文档已经过时。绝大多数情况下,你几乎肯定应该考虑使用 Git,而不是 + 手工运行 ``patch``。 + +Linux 内核邮件列表中经常有人询问如何给内核打补丁;更确切地说,为众多 +内核树或分支之一制作的补丁,应当以哪个版本的内核为基础来应用。希望本文 +能够解答这些问题。 + +除了说明如何应用和撤销补丁,本文还会简要介绍不同的内核树,并举例说明 +如何应用各个内核树特有的补丁。 + + +什么是补丁? +============ + +补丁是一份短小的文本文档,其中记录了源码树两个不同版本之间的改动。 +补丁由 ``diff`` 程序生成。 + +要正确应用补丁,你必须知道它是基于哪个版本生成的,又会把源码树更新成 +哪个版本。这两项信息应当记录在补丁文件的元数据中,或者能够根据文件名 +推断出来。 + + +如何应用或撤销补丁? +==================== + +使用 ``patch`` 程序应用补丁。patch 程序读取 diff 文件(也称补丁 +文件),再按照文件中的描述修改源码树。 + +Linux 内核补丁以存放内核源码目录的父目录为基准生成。 + +因此,补丁文件中的路径会带有生成补丁时所用的内核源码目录名(也可能是 +“a/”和“b/”之类的其他目录名)。 + +这个目录名一般不会恰好与本机的内核源码目录名相同(不过,它往往有助于 +判断未注明版本的补丁是基于哪个版本生成的)。应用补丁时,应先进入内核 +源码目录,再去掉补丁文件所列路径中的第一个组成部分(``patch`` 的 +``-p1`` 参数正是用于此目的)。 + +要撤销先前应用的补丁,请给 patch 加上 -R 参数。也就是说,如果应用 +补丁时执行的是:: + + patch -p1 < ../patch-x.y.z + +那么可以这样撤销它:: + + patch -R -p1 < ../patch-x.y.z + + +如何把补丁或 diff 文件交给 ``patch``? +====================================== + +和 Linux 及其他类 UNIX 操作系统中的许多操作一样,这件事也有好几种 +做法。 + +下文的所有示例都使用如下语法,通过标准输入(stdin)把未压缩的文件 +交给 patch:: + + patch -p1 < path/to/patch-x.y.z + +如果你只想照着下文的示例操作,无意了解 patch 的其他用法,那么读到 +这里就可以跳过本节余下的内容。 + +patch 也能通过 -i 参数取得待处理文件的名称,例如:: + + patch -p1 -i path/to/patch-x.y.z + +如果补丁文件以 gzip 或 xz 格式压缩,而你不想先解压再应用,可以这样 +把它交给 patch:: + + xzcat path/to/patch-x.y.z.xz | patch -p1 + bzcat path/to/patch-x.y.z.gz | patch -p1 + +如果想在应用前手工解压补丁文件(下文的示例均假定你已经这样做了), +只需对文件运行 gunzip 或 xz,例如:: + + gunzip patch-x.y.z.gz + xz -d patch-x.y.z.xz + +执行后会留下一个纯文本的 patch-x.y.z 文件。你可以按自己的喜好, +通过标准输入或 ``-i`` 参数把它交给 patch。 + +patch 还有几个很实用的参数:``-s`` 会让 patch 除错误外不输出任何 +信息,以免错误信息很快滚出屏幕;``--dry-run`` 只列出将会发生的事情, +并不实际修改文件;最后,``--verbose`` 会让 patch 输出更详细的处理 +信息。 + + +应用补丁时的常见错误 +==================== + +应用补丁文件时,patch 会用多种方式检查文件是否合理。 + +例如,patch 会检查文件是否像一个有效的补丁文件,也会检查待修改代码 +周围的内容是否与补丁提供的上下文相符。这只是它所做的两项基本检查。 + +如果遇到不太对劲的地方,patch 有两种选择:拒绝应用改动并中止,或者 +稍作调整,设法把补丁应用上去。 + +patch 会尝试调整的一种情况是:上下文和待修改的行全都匹配,只有行号 +不同。例如,补丁要修改文件中部的内容,但文件开头附近由于某种原因增删 +了几行,就会出现这种情况。此时所有内容其实都能对上,只是整体向前或 +向后移动了一些,patch 通常会相应调整行号并应用补丁。 + +每当 patch 必须略作调整才能匹配时,它会提示补丁是在经过模糊匹配 +(**fuzz**)后应用的。你应当谨慎对待这样的改动:patch 很可能处理 +正确,但它并不保证每次都能正确处理,有时会得到错误的结果。 + +如果某项改动无法通过模糊匹配调整,patch 会直接拒绝这项改动,并留下 +一个扩展名为 ``.rej`` 的文件(拒绝文件)。你可以阅读这个文件,准确 +了解哪项改动未能应用,再视需要手工处理。 + +假如内核源码中没有第三方补丁,只有来自 kernel.org 的补丁,而且补丁 +应用顺序正确,源码文件也没有经过自行修改,那么 patch 绝不应报告模糊 +匹配或拒绝应用。如果还是看到这类信息,你的本地源码树或补丁文件很可能 +已经损坏。此时应先尝试重新下载补丁;如果问题依旧,建议从 kernel.org +重新下载完整的全新源码树,从头再来。 + +下面进一步看看 patch 可能输出的几种信息。 + +如果 patch 停下来并显示 ``File to patch:`` 提示,说明它找不到待修改 +的文件。最可能的原因是你忘记指定 -p1,或者当前目录不对。偶尔也会 +遇到必须用 ``-p0`` 而不是 ``-p1`` 应用的补丁(查看补丁文件便能判断 +是否如此;如果确实如此,是补丁制作者犯了错,但并不致命)。 + +如果看到 ``Hunk #2 succeeded at 1887 with fuzz 2 (offset 7 lines).`` +或类似信息,说明 patch 必须调整改动的位置才能应用;在这个例子中, +实际改动位置必须与预期位置相差 7 行才能匹配。 + +最终得到的文件可能正确,也可能不正确,取决于文件为何与预期不符。 + +尝试应用一个基于其他内核版本生成的补丁时,经常会出现这种情况。 + +如果看到 ``Hunk #3 FAILED at 2387.`` 之类的信息,说明补丁未能正确 +应用,patch 程序也无法通过模糊匹配完成应用。此时会生成一个 ``.rej`` +文件,其中包含导致应用失败的改动;还会生成一个 ``.orig`` 文件,其中 +是未能修改的原始内容。 + +如果看到 ``Reversed (or previously applied) patch detected! Assume +-R? [n]``,说明 patch 检测到补丁中的改动似乎已经存在。 + +如果你确实已经应用过这个补丁,只是不慎又应用了一次,请回答 [n]o 并 +中止应用。如果先前应用过补丁,现在本来就想撤销它,只是忘了指定 -R, +则可以在这里回答 [**y**]es,让 patch 替你撤销。 + +如果补丁制作者在生成补丁时颠倒了源目录和目标目录,也会出现这条信息; +在这种情况下,撤销补丁实际上才是在应用补丁。 + +``patch: **** unexpected end of file in patch`` 或 ``patch unexpectedly +ends in middle of line`` 之类的信息,表示 patch 无法理解收到的文件。 +可能是文件下载不完整,可能是你没有先解压便把压缩补丁交给了 patch, +也可能是补丁文件在途经某个邮件客户端或邮件传输代理时遭到破坏,例如 +一行过长的内容被拆成了两行。这类警告通常很容易处理,只需把被拆开的 +两行重新连接(拼接)起来即可。 + +如前所述,如果把 kernel.org 提供的补丁应用到版本正确、未经修改的 +源码树上,这些错误绝不应出现。因此,如果应用 kernel.org 的补丁时 +遇到这些错误,应当认为补丁文件或源码树已经损坏;建议重新下载完整的 +内核源码树和要应用的补丁,从头再来。 + + +除了 ``patch``,还有其他选择吗? +================================ + +有。 + +可以使用 ``interdiff`` 程序(http://cyberelk.net/tim/patchutils/), +为两个补丁之间的差异生成一个补丁,然后应用生成的补丁。 + +这样便能一步从 5.7.2 更新到 5.7.3。interdiff 的 -z 参数甚至允许 +直接传入以 gzip 或 bzip2 格式压缩的补丁,无需使用 zcat、bzcat,也 +无需手工解压。 + +下面是一步从 5.7.2 更新到 5.7.3 的方法:: + + interdiff -z ../patch-5.7.2.gz ../patch-5.7.3.gz | patch -p1 + +尽管 interdiff 能省去一两个步骤,一般仍建议按常规方法多执行这些步骤, +因为 interdiff 在某些情况下可能产生错误结果。 + +另一个选择是 ``ketchup``。它是一个自动下载并应用补丁的 Python 脚本 +(https://www.selenic.com/ketchup/)。 + +其他好用的工具还有:diffstat,用于显示补丁改动的摘要;lsdiff,用于 +简要列出补丁文件影响的文件,还可以选择同时显示各段补丁的起始行号; +grepdiff,用于在补丁中查找与指定正则表达式匹配的内容,并列出这些内容 +所在的文件。 + + +可以从哪里下载补丁? +==================== + +补丁可从 https://kernel.org/ 获取。网站首页提供了最新补丁的链接, +各类补丁也有各自固定的存放位置。 + +5.x.y(-stable)补丁和 5.x 补丁位于 + + https://www.kernel.org/pub/linux/kernel/v5.x/ + +5.x.y 增量补丁位于 + + https://www.kernel.org/pub/linux/kernel/v5.x/incr/ + +-rc 补丁并不存放在 Web 服务器上,而是根据下面这样的 git 标签按需 +生成 + + https://git.kernel.org/torvalds/p/v5.1-rc1/v5.0 + +稳定版的 -rc 补丁位于 + + https://www.kernel.org/pub/linux/kernel/v5.x/stable-review/ + + +5.x 内核 +======== + +这些是 Linus 发布的基础稳定版本,其中版本号最大者最新。 + +如果发现回归或其他严重缺陷,会在这个基础版本之上发布 -stable 修复 +补丁(见下文)。每当新的 5.x 基础内核发布时,还会提供一个补丁,用来 +表示上一个 5.x 内核与新内核之间的差异。 + +要应用从 5.6 更新到 5.7 的补丁,可以按下面的步骤操作。请注意,这类 +补丁**不能**应用到 5.x.y 内核之上,只能应用到基础 5.x 内核之上; +如果要从 5.x.y 更新到 5.x+1,必须先撤销 5.x.y 补丁。 + +下面是两个示例:: + + # 从 5.6 更新到 5.7 + + $ cd ~/linux-5.6 # 进入内核源码目录 + $ patch -p1 < ../patch-5.7 # 应用 5.7 补丁 + $ cd .. + $ mv linux-5.6 linux-5.7 # 重命名源码目录 + + # 从 5.6.1 更新到 5.7 + + $ cd ~/linux-5.6.1 # 进入内核源码目录 + $ patch -p1 -R < ../patch-5.6.1 # 撤销 5.6.1 补丁 + # 源码目录现在是 5.6 + $ patch -p1 < ../patch-5.7 # 应用新的 5.7 补丁 + $ cd .. + $ mv linux-5.6.1 linux-5.7 # 重命名源码目录 + + +5.x.y 内核 +========== + +版本号由三段数字组成的是 -stable 内核。它们包含规模较小但至关重要的 +修复,用于解决某个 5.x 内核中发现的安全问题或严重回归。 + +对于想使用最新的稳定内核,又无意帮助测试开发版或实验版的用户,推荐 +选择这个分支。 + +如果没有 5.x.y 内核可用,则版本号最大的 5.x 内核就是当前稳定内核。 + +-stable 团队既提供普通补丁,也提供增量补丁。下面说明如何应用这两类 +补丁。 + +普通补丁 +~~~~~~~~ + +这类补丁不是增量补丁。例如,5.7.3 补丁不能应用到 5.7.2 内核源码 +之上,而应应用到基础 5.7 内核源码之上。 + +因此,要把 5.7.3 补丁应用到现有的 5.7.2 内核源码,必须先撤销 +5.7.2 补丁(回到基础 5.7 内核源码),再应用新的 5.7.3 补丁。 + +下面是一个简单的示例:: + + $ cd ~/linux-5.7.2 # 进入内核源码目录 + $ patch -p1 -R < ../patch-5.7.2 # 撤销 5.7.2 补丁 + $ patch -p1 < ../patch-5.7.3 # 应用新的 5.7.3 补丁 + $ cd .. + $ mv linux-5.7.2 linux-5.7.3 # 重命名内核源码目录 + +增量补丁 +~~~~~~~~ + +增量补丁则不同:它们不应用到基础 5.x 内核之上,而应用到前一个稳定版 +内核(5.x.y-1)之上。 + +下面是应用增量补丁的示例:: + + $ cd ~/linux-5.7.2 # 进入内核源码目录 + $ patch -p1 < ../patch-5.7.2-3 # 应用新的 5.7.3 补丁 + $ cd .. + $ mv linux-5.7.2 linux-5.7.3 # 重命名内核源码目录 + + +-rc 内核 +======== + +这些是候选发布版内核。每当 Linus 认为当前的 git 树(git 是内核的 +源码管理工具)处于相当合理、足以测试的状态时,就会发布这样的开发版内核。 + +这些内核并不稳定;如果打算运行,就要预料到它们偶尔会出故障。不过, +在几个主要开发分支中,这是最稳定的一个,而且它最终会成为下一个稳定版 +内核,所以让尽可能多的人参与测试十分重要。 + +如果你想帮助测试开发版内核,但不想运行真正具有实验性的内容,这个分支 +很合适(有关真正实验性的内容,请参阅下文介绍 -next 和 -mm 内核的 +章节)。 + +-rc 补丁不是增量补丁;与上文介绍的 5.x.y 补丁一样,它们应用到基础 +5.x 内核之上。-rcN 后缀前面的内核版本号,表示这个 -rc 内核最终会 +成为哪个版本。 + +因此,5.8-rc5 表示它是 5.8 内核的第五个候选发布版,这个补丁应当 +应用到 5.7 内核源码之上。 + +下面是三个应用这类补丁的示例:: + + # 第一个示例:从 5.7 更新到 5.8-rc3 + + $ cd ~/linux-5.7 # 进入 5.7 源码目录 + $ patch -p1 < ../patch-5.8-rc3 # 应用 5.8-rc3 补丁 + $ cd .. + $ mv linux-5.7 linux-5.8-rc3 # 重命名源码目录 + + # 接着从 5.8-rc3 更新到 5.8-rc5 + + $ cd ~/linux-5.8-rc3 # 进入 5.8-rc3 目录 + $ patch -p1 -R < ../patch-5.8-rc3 # 撤销 5.8-rc3 补丁 + $ patch -p1 < ../patch-5.8-rc5 # 应用新的 5.8-rc5 补丁 + $ cd .. + $ mv linux-5.8-rc3 linux-5.8-rc5 # 重命名源码目录 + + # 最后尝试从 5.7.3 更新到 5.8-rc5 + + $ cd ~/linux-5.7.3 # 进入内核源码目录 + $ patch -p1 -R < ../patch-5.7.3 # 撤销 5.7.3 补丁 + $ patch -p1 < ../patch-5.8-rc5 # 应用新的 5.8-rc5 补丁 + $ cd .. + $ mv linux-5.7.3 linux-5.8-rc5 # 重命名源码目录 + + +-mm 补丁与 linux-next 树 +======================== + +-mm 补丁是 Andrew Morton 发布的实验性补丁。 + +过去,-mm 树还用于测试子系统补丁;现在这项工作由 +`linux-next` (https://www.kernel.org/doc/man-pages/linux-next.html) +树承担。子系统维护者先把补丁推送到 linux-next,再在合并窗口期间直接 +发送给 Linus。 + +-mm 补丁是新功能和其他实验性补丁的试验场,这些补丁并未通过子系统树 +合并。一旦这类补丁在 -mm 中经受一段时间的检验并证明自身价值,Andrew +就会将其提交给 Linus,以纳入主线。 + +linux-next 树每天更新,其中包括 -mm 补丁。二者始终处于变化之中, +含有许多实验性功能、大量不适合主线的调试补丁等,是本文所述各分支中 +实验性最强的。 + +这些补丁不适合用在必须保持稳定的系统上,运行它们的风险高于其他任何 +分支(务必备有最新的备份;运行任何实验性内核都应如此,运行 -mm 补丁 +或 linux-next 树中的内核时尤其如此)。 + +我们非常欢迎大家测试 -mm 补丁和 linux-next,因为测试的意义就是在改动 +合入更稳定的 Linus 主线树之前,排查并消除回归、崩溃、数据损坏缺陷、 +构建失败以及其他各种缺陷。 + +不过,-mm 和 linux-next 的测试者必须明白,这些树发生故障的频率高于 +其他任何内核树。 + + +至此,各种内核树已经介绍完毕。希望你现在已经清楚如何应用各类补丁, +并能帮助测试内核。 + +感谢 Randy Dunlap、Rolf Eike Beer、Linus Torvalds、Bodo Eggert、 +Johannes Stezenbach、Grant Coady、Pavel Machek,以及其他可能被我 +遗漏的人,感谢他们对本文档的审阅和贡献。 diff --git a/Documentation/translations/zh_CN/process/index.rst b/Documentation/translations/zh_CN/process/index.rst index 3bcb3bdaf5332d..3a36ca7d9333b7 100644 --- a/Documentation/translations/zh_CN/process/index.rst +++ b/Documentation/translations/zh_CN/process/index.rst @@ -82,13 +82,13 @@ TODOLIST: :maxdepth: 1 magic-number + applying-patches volatile-considered-harmful ../arch/riscv/patch-acceptance ../core-api/unaligned-memory-access TODOLIST: -* applying-patches * backporting * adding-syscalls * botching-up-ioctls From 2dc934836234303798c818467c4fe8d254ffc4ab Mon Sep 17 00:00:00 2001 From: Weijie Yuan Date: Mon, 17 Aug 2026 01:47:46 +0800 Subject: [PATCH 0600/2286] docs/zh_CN: link to Chinese applying-patches translation Now that process/applying-patches.rst has been translated into Chinese, update the references in zh_CN howto.rst and admin-guide/README.rst to point to the Chinese translation instead of the English original. Signed-off-by: Weijie Yuan Signed-off-by: Alex Shi --- Documentation/translations/zh_CN/admin-guide/README.rst | 2 +- Documentation/translations/zh_CN/process/howto.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/translations/zh_CN/admin-guide/README.rst b/Documentation/translations/zh_CN/admin-guide/README.rst index 7c2ffe7e87c7e6..ee6dd3782f4a33 100644 --- a/Documentation/translations/zh_CN/admin-guide/README.rst +++ b/Documentation/translations/zh_CN/admin-guide/README.rst @@ -83,7 +83,7 @@ Linux内核6.x版本 补丁,则不应先应用6.0.1和6.0.2的补丁。类似地,如果您运行的是6.0.2内核, 并且希望跳转到6.0.3,那么在应用6.0.3补丁之前,必须首先撤销6.0.2补丁 (即patch -R)。更多关于这方面的内容,请阅读 - :ref:`Documentation/process/applying-patches.rst ` 。 + Documentation/translations/zh_CN/process/applying-patches.rst 。 或者,脚本 patch-kernel 可以用来自动化这个过程。它能确定当前内核版本并 应用找到的所有补丁:: diff --git a/Documentation/translations/zh_CN/process/howto.rst b/Documentation/translations/zh_CN/process/howto.rst index cc47be356dd323..89af3f8f6e2b2b 100644 --- a/Documentation/translations/zh_CN/process/howto.rst +++ b/Documentation/translations/zh_CN/process/howto.rst @@ -142,7 +142,7 @@ Linux内核代码中包含有大量的文档。这些文档对于学习如何与 有助于内核开发的外部文档列表。如果你在内核自带的文档中没有找到你想找 的内容,可以查看这些文档。 - :ref:`Documentation/process/applying-patches.rst ` + :doc:`Documentation/translations/zh_CN/process/applying-patches.rst ` 关于补丁是什么以及如何将它打在不同内核开发分支上的好介绍 内核还拥有大量从代码自动生成或者从 ReStructuredText(ReST) 标记生成的文档, From cff27d90769da941f6897a9d9152670af7b71f95 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:42:54 -0700 Subject: [PATCH 0601/2286] objtool: Add test harness for the klp subcommands "objtool klp checksum" and "objtool klp diff" work on object files alone, with no kernel, vmlinux or configuration involved, so they can be tested directly. That is worth doing: most klp generation bugs so far have been in symbol correlation, special section extraction and relocation conversion, and several of them failed silently, producing a livepatch which built cleanly but was missing data. A test compiles a fixture twice, as the original and (with -DPATCHED) the patched object, runs both through klp checksum, diffs them and asserts on the result. Fixtures are compiled at test time rather than committed as binaries: codegen varies between compilers and architectures, and that variation is where a good number of these bugs come from. Assertions check properties rather than compare against recorded output. Golden files would need re-recording for every compiler change and would report churn instead of regressions. klp diff resolves symbols against Module.symvers, so the harness writes one. Whether a symbol is listed there decides between an ordinary relocation and a klp relocation, which makes it the main knob tests use. Run with: make -C tools/objtool tests Tests skip when objtool was built without klp support or when a fixture does not build for the target architecture. A missing objtool binary fails instead of skipping, since that means a broken invocation rather than an environment which cannot run the test. Signed-off-by: Puranjay Mohan Co-developed-by: Joe Lawrence Signed-off-by: Joe Lawrence Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-2-song@kernel.org --- tools/objtool/Makefile | 5 +- tools/objtool/tests/fixtures/basic.c | 20 +++ tools/objtool/tests/lib.sh | 179 +++++++++++++++++++++++++++ tools/objtool/tests/run-tests.sh | 20 +++ tools/objtool/tests/test-basic.sh | 17 +++ 5 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tools/objtool/tests/fixtures/basic.c create mode 100644 tools/objtool/tests/lib.sh create mode 100755 tools/objtool/tests/run-tests.sh create mode 100755 tools/objtool/tests/test-basic.sh diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 4cc2e756af840b..0f34abb14c8a8f 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -153,6 +153,9 @@ clean: $(LIBSUBCMD)-clean mrproper: clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) +tests: $(OBJTOOL) + $(Q)OBJTOOL=$(abspath $(OBJTOOL)) $(srctree)/tools/objtool/tests/run-tests.sh + FORCE: -.PHONY: clean mrproper FORCE +.PHONY: clean mrproper tests FORCE diff --git a/tools/objtool/tests/fixtures/basic.c b/tools/objtool/tests/fixtures/basic.c new file mode 100644 index 00000000000000..811529e7bfb1d9 --- /dev/null +++ b/tools/objtool/tests/fixtures/basic.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* One changed function and one unchanged function. */ + +/* klp diff takes the object's module name from .modinfo */ +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int untouched(int x) +{ + return x * 3; +} + +int changed(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh new file mode 100644 index 00000000000000..7b29db76aa5e91 --- /dev/null +++ b/tools/objtool/tests/lib.sh @@ -0,0 +1,179 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Helpers for the objtool klp tests. A test builds a fixture twice, as the +# original and (with -DPATCHED) the patched object, runs both through +# "klp checksum" and diffs them, then asserts on the result. +# +# Assertions check properties rather than compare against recorded output: +# codegen varies between compilers and golden files would report churn instead +# of regressions. + +TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FIXTURES_DIR="$TESTS_DIR/fixtures" + +OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" +CC="${CC:-gcc}" + +# klp-build compiles the kernel this way; klp diff needs per-symbol sections to +# extract individual functions. +FIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" + +test_name="$(basename "$0" .sh)" +workdir= + +pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } +fail() { echo "not ok - $test_name: $1" >&2; exit 1; } +skip() { echo "ok - $test_name # SKIP $1"; exit 0; } + +cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } + +# setup [exported symbol...] +setup() +{ + # A relative $OBJTOOL is relative to the objtool directory, not to the + # tests which run from tests/. + [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || + OBJTOOL="$TESTS_DIR/../$OBJTOOL" + + # Not finding objtool is a broken invocation, not an environment which + # cannot run the test. Skipping here would read as a pass. + [ -x "$OBJTOOL" ] || fail "objtool not found at '$OBJTOOL', build it first" + + # run_diff() runs objtool from inside the test's working directory, so + # a relative path would resolve against that instead. + OBJTOOL="$(realpath "$OBJTOOL")" + + "$OBJTOOL" klp 2>&1 | grep -q checksum || + skip "objtool built without klp support (needs libxxhash)" + command -v "${CC%% *}" >/dev/null || skip "no compiler ($CC)" + + workdir="$(mktemp -d)" || fail "mktemp failed" + trap cleanup EXIT + + export_syms "$@" +} + +# export_syms [symbol...] +# +# Rewrite Module.symvers so exactly these symbols are exported by vmlinux. +# Whether a symbol is listed decides between an ordinary relocation and a klp +# relocation, so tests flip it to cover both. +export_syms() +{ + : > "$workdir/Module.symvers" + for sym in "$@"; do + printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \ + "$sym" >> "$workdir/Module.symvers" + done +} + +# build_pair [cflags...] +build_pair() +{ + local fixture="$FIXTURES_DIR/$1"; shift + + [ -f "$fixture" ] || fail "missing fixture $fixture" + + $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || + skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || + skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" +} + +# run_diff [expected exit status] +run_diff() +{ + local expect="${1:-0}" rc=0 + + # Checksums live in the objects, so only generate them once even when a + # test diffs the same pair again with a different Module.symvers. + if [ ! -e "$workdir/.checksummed" ]; then + "$OBJTOOL" klp checksum "$workdir/orig.o" || + fail "klp checksum orig.o failed" + "$OBJTOOL" klp checksum "$workdir/patched.o" || + fail "klp checksum patched.o failed" + touch "$workdir/.checksummed" + fi + + # klp diff looks for Module.symvers relative to the working directory. + ( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + > "$workdir/diff.log" 2>&1 || rc=$? + + [ "$rc" = "$expect" ] || + fail "klp diff exited $rc, expected $expect: $(tail -2 "$workdir/diff.log")" +} + +cc_supports() +{ + echo 'int f(void) { return 0; }' > "$workdir/flagtest.c" + $CC $1 -c "$workdir/flagtest.c" -o "$workdir/flagtest.o" 2>/dev/null +} + +# partial_link +# +# "ld -r" through the compiler driver so the link targets the same +# architecture as the objects. +partial_link() +{ + local out="$1"; shift + + $CC -r -nostdlib -o "$out" "$@" 2>/dev/null || + $CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null +} + +# find_thinlto_toolchain +# +# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A +# mismatched pair fails with "Invalid summary version", which reads like a +# broken test rather than a broken environment. +find_thinlto_toolchain() +{ + local cc ld ver + + for cc in "${THIN_CC:-}" "$CC" clang; do + [ -n "$cc" ] || continue + command -v "${cc%% *}" >/dev/null 2>&1 || continue + + ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1) + + for ld in "${THIN_LD:-}" "ld.lld-$ver" ld.lld; do + [ -n "$ld" ] || continue + command -v "$ld" >/dev/null 2>&1 || continue + + echo 'int probe(void) { return 0; }' > "$workdir/probe.c" + $cc -flto=thin -O2 -c "$workdir/probe.c" \ + -o "$workdir/probe.o" 2>/dev/null || continue + "$ld" -r "$workdir/probe.o" -o "$workdir/probe.elf" \ + 2>/dev/null || continue + + THIN_CC="$cc" + THIN_LD="$ld" + return 0 + done + done + + return 1 +} + +out_sections() { readelf -S -W "$workdir/out.o" 2>/dev/null; } +out_relocs() { readelf -r -W "$workdir/out.o" 2>/dev/null; } +out_symbols() { readelf -s -W "$workdir/out.o" 2>/dev/null; } +diff_log() { cat "$workdir/diff.log"; } + +assert_section() +{ + out_sections | grep -q "[[:space:]]$1[[:space:]]" || + fail "expected section '$1' in output" +} + +assert_patched() +{ + assert_section ".text.$1" +} + +assert_not_patched() +{ + out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && + fail "function '$1' should not have been cloned" + return 0 +} diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh new file mode 100755 index 00000000000000..ab1dea58811e22 --- /dev/null +++ b/tools/objtool/tests/run-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Run the objtool klp tests. Each test-*.sh prints one TAP result line. + +set -u + +cd "$(dirname "$0")" || exit 1 + +tests=( test-*.sh ) +[ "${tests[0]}" = "test-*.sh" ] && { echo "1..0 # SKIP no tests found"; exit 0; } + +echo "1..${#tests[@]}" + +rc=0 +for t in "${tests[@]}"; do + ./"$t" || rc=1 +done + +exit $rc diff --git a/tools/objtool/tests/test-basic.sh b/tools/objtool/tests/test-basic.sh new file mode 100755 index 00000000000000..6b769962399a22 --- /dev/null +++ b/tools/objtool/tests/test-basic.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Only functions whose code changed get cloned into the patch. + +. "$(dirname "$0")/lib.sh" + +setup +build_pair basic.c +run_diff + +assert_patched changed +assert_not_patched untouched +assert_section ".init.klp_funcs" +assert_section ".init.klp_objects" + +pass "changed function cloned, unchanged function left alone" From c009fabc865200029173eb40db62f407225043d4 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:42:55 -0700 Subject: [PATCH 0602/2286] objtool/klp: Check the klp test environment once, before any test Every test checked for itself that objtool exists, was built with klp support, and that $CC runs. Three problems with that: it is the same work done 40 times, a missing objtool reads as a per-test skip rather than as a suite which cannot run, and a run in which everything skipped still exits 0. Do it once, before any test, in klp_preflight() in lib.sh, and export the answers where the tests can read them. If the suite cannot run the whole run fails and says why; a test which gets as far as running can assume its environment. Nothing is written down, so nothing can go stale. Sourcing lib.sh runs the checks, which means a test run by hand establishes its own answers rather than inheriting those of some earlier run with a different CC, and a test is never handed an empty value for where objtool is or which architecture it is on: either the exports are set or the run has already failed. preflight answers only whether the suite can run at all -- not what the compiler is capable of. A test needing a particular compiler feature probes for it and skips; that costs one compile and keeps the reason next to the test that has to justify it. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-3-song@kernel.org --- tools/objtool/Makefile | 3 +- tools/objtool/tests/lib.sh | 124 +++++++++++++++++++++++++------ tools/objtool/tests/run-tests.sh | 7 ++ 3 files changed, 112 insertions(+), 22 deletions(-) diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 0f34abb14c8a8f..e9a4cc8e787f57 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -154,7 +154,8 @@ mrproper: clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) tests: $(OBJTOOL) - $(Q)OBJTOOL=$(abspath $(OBJTOOL)) $(srctree)/tools/objtool/tests/run-tests.sh + $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ + $(srctree)/tools/objtool/tests/run-tests.sh FORCE: diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 7b29db76aa5e91..46bdb985765e03 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -11,8 +11,104 @@ TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FIXTURES_DIR="$TESTS_DIR/fixtures" +# The kernel's convention: CROSS_COMPILE is the one knob, with per-tool +# overrides for what it does not cover. objtool itself is always a host binary +# -- it is built with HOSTCC and only reads ELF -- so an arm64 machine can run +# the x86 tests against x86 objects given a compiler that emits them. +# +# readelf reads any target, so it rarely needs overriding, and either GNU +# readelf or llvm-readelf will do: the assertions match on fields rather than +# on columns, and where the two spell something differently -- "OS [0xff20]" +# against "OS[0xff20]" for SHN_LIVEPATCH -- they accept both. BFD's objcopy is +# usually built for the host's target alone, and llvm-objcopy is the +# target-agnostic replacement. +CROSS_COMPILE="${CROSS_COMPILE:-}" +CC="${CC:-${CROSS_COMPILE}gcc}" +LD="${LD:-${CROSS_COMPILE}ld}" +READELF="${READELF:-${CROSS_COMPILE}readelf}" +OBJCOPY="${OBJCOPY:-${CROSS_COMPILE}objcopy}" + OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" -CC="${CC:-gcc}" + +# klp_preflight +# +# Check the environment once, before any test runs, and report what was found. +# +klp_preflight() +{ + local tmp tool cc_version host cc_arch + + bail() { echo "Bail out! $*" >&2; exit 1; } + + # A relative $OBJTOOL is relative to the objtool directory, not tests/. + [ -x "$OBJTOOL" ] || [ ! -x "$TESTS_DIR/../$OBJTOOL" ] || + OBJTOOL="$TESTS_DIR/../$OBJTOOL" + + [ -x "$OBJTOOL" ] || + bail "objtool not found at '$OBJTOOL' -- build it first" + + # run_diff() runs objtool from inside the test's working directory, so + # a relative path would resolve against that instead. + OBJTOOL="$(realpath "$OBJTOOL")" + + "$OBJTOOL" klp 2>&1 | grep -q checksum || + bail "objtool was built without klp support; install libxxhash (>= 0.8) and rebuild" + + command -v "${CC%% *}" >/dev/null || bail "compiler not found: $CC" + + for tool in "$READELF" "$OBJCOPY" "$LD"; do + command -v "${tool%% *}" >/dev/null || bail "$tool not found" + done + + tmp="$(mktemp -d)" || bail "mktemp failed" + echo 'int probe(void) { return 0; }' > "$tmp/probe.c" + $CC -c -o "$tmp/probe.o" "$tmp/probe.c" 2>/dev/null || + { rm -rf "$tmp"; bail "$CC cannot compile a trivial object"; } + + # $CC, $ARCH and objtool have to agree about the target, and cross runs + # are where they stop agreeing: plain "CC=clang ARCH=x86_64" on an arm64 + # box selects the x86 tests and then builds arm64 objects, because clang + # needs --target= to emit anything but the host's. + # + # Ask objtool rather than comparing machine names. It rejects an object + # it was not built for -- "unexpected ELF machine type" -- so one check + # covers every way the three can disagree, and says so once instead of + # failing every test for the same reason. + "$OBJTOOL" klp checksum "$tmp/probe.o" >/dev/null 2>&1 || + { rm -rf "$tmp" + bail "objtool rejects an object built by '$CC'; they target" \ + "different architectures (set CROSS_COMPILE, or" \ + "--target= for clang)"; } + + # BFD objcopy is usually built for the host's target alone, and + # checksum_of() needs it to read the object under test. + $OBJCOPY -O binary --only-section=.text "$tmp/probe.o" "$tmp/probe.bin" 2>/dev/null || + { rm -rf "$tmp" + bail "$OBJCOPY cannot read objects built by '$CC'; install" \ + "binutils-multiarch or set OBJCOPY=llvm-objcopy"; } + # $ARCH only chooses which directory of tests runs, so it can disagree + # with what $CC builds without objtool noticing -- and the result is the + # wrong set of tests, quietly. + case "$($READELF -hW "$tmp/probe.o" | sed -n 's/.*Machine: *//p')" in + *X86-64*|*Intel*80386*) cc_arch=x86 ;; + *AArch64*) cc_arch=arm64 ;; + *) cc_arch= ;; + esac + rm -rf "$tmp" + + KLP_TEST_PREFLIGHT=done + export OBJTOOL CC KLP_TEST_PREFLIGHT + + cc_version="$($CC --version 2>/dev/null | head -1)" + cat <&1 | grep -q checksum || - skip "objtool built without klp support (needs libxxhash)" - command -v "${CC%% *}" >/dev/null || skip "no compiler ($CC)" - + # The environment was checked once when this file was sourced, so there + # is nothing to verify here: objtool exists at the resolved path, has + # klp support, and $CC works. workdir="$(mktemp -d)" || fail "mktemp failed" trap cleanup EXIT @@ -155,9 +237,9 @@ find_thinlto_toolchain() return 1 } -out_sections() { readelf -S -W "$workdir/out.o" 2>/dev/null; } -out_relocs() { readelf -r -W "$workdir/out.o" 2>/dev/null; } -out_symbols() { readelf -s -W "$workdir/out.o" 2>/dev/null; } +out_sections() { $READELF -S -W "$workdir/out.o" 2>/dev/null; } +out_relocs() { $READELF -r -W "$workdir/out.o" 2>/dev/null; } +out_symbols() { $READELF -s -W "$workdir/out.o" 2>/dev/null; } diff_log() { cat "$workdir/diff.log"; } assert_section() diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index ab1dea58811e22..f6a3e1b7677e21 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -2,6 +2,9 @@ # SPDX-License-Identifier: GPL-2.0 # # Run the objtool klp tests. Each test-*.sh prints one TAP result line. +# +# The harness checks the environment once up front and fails the run if the +# suite cannot execute, rather than letting every test skip and exit 0. set -u @@ -10,6 +13,10 @@ cd "$(dirname "$0")" || exit 1 tests=( test-*.sh ) [ "${tests[0]}" = "test-*.sh" ] && { echo "1..0 # SKIP no tests found"; exit 0; } +# Sourcing the harness runs its preflight, and exports what it found so the +# tests inherit it rather than working it out again. +. ./lib.sh + echo "1..${#tests[@]}" rc=0 From d76312b8a1f69be71ec38d04d69fbcdd0520ff89 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:42:56 -0700 Subject: [PATCH 0603/2286] objtool/klp: Group the klp tests by architecture Which architecture a test is for is expressed by where it lives: tests are in generic/ or in a directory named for their architecture, each carrying its own fixtures, and the runner executes generic/ plus the one that matches. A test which cannot apply here is then not run at all, rather than running in order to announce that it did not. Layout does this better than a declaration would. There is no x86_only(), and no lookup letting a fixtures// file shadow a generic one of the same name -- an arch-specific test simply carries its own fixtures. Compilers cannot work the same way: CI varies CC over the same tree, so a compiler requirement stays a declaration in the test. What a run leaves out is reported once: # not run: 5 tests in x86/ (this run is arm64) Silence would have been cheaper and wrong. A run covering less than the tree holds must not look like a run that covered all of it. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-4-song@kernel.org --- .../tests/{ => generic}/fixtures/basic.c | 0 .../objtool/tests/{ => generic}/test-basic.sh | 2 +- tools/objtool/tests/lib.sh | 27 ++++++++++++-- tools/objtool/tests/run-tests.sh | 36 ++++++++++++++++--- 4 files changed, 56 insertions(+), 9 deletions(-) rename tools/objtool/tests/{ => generic}/fixtures/basic.c (100%) rename tools/objtool/tests/{ => generic}/test-basic.sh (91%) diff --git a/tools/objtool/tests/fixtures/basic.c b/tools/objtool/tests/generic/fixtures/basic.c similarity index 100% rename from tools/objtool/tests/fixtures/basic.c rename to tools/objtool/tests/generic/fixtures/basic.c diff --git a/tools/objtool/tests/test-basic.sh b/tools/objtool/tests/generic/test-basic.sh similarity index 91% rename from tools/objtool/tests/test-basic.sh rename to tools/objtool/tests/generic/test-basic.sh index 6b769962399a22..562edfbc8646f6 100755 --- a/tools/objtool/tests/test-basic.sh +++ b/tools/objtool/tests/generic/test-basic.sh @@ -3,7 +3,7 @@ # # Only functions whose code changed get cloned into the patch. -. "$(dirname "$0")/lib.sh" +. "$(dirname "$0")/../lib.sh" setup build_pair basic.c diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 46bdb985765e03..fdea9b790c4d23 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -9,7 +9,10 @@ # of regressions. TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -FIXTURES_DIR="$TESTS_DIR/fixtures" + +# Tests live in generic/ or in an architecture directory beside it, and each +# carries its own fixtures. +FIXTURES_DIR="$(cd "$(dirname "$0")/fixtures" 2>/dev/null && pwd)" # The kernel's convention: CROSS_COMPILE is the one knob, with per-tool # overrides for what it does not cover. objtool itself is always a host binary @@ -36,7 +39,7 @@ OBJTOOL="${OBJTOOL:-$TESTS_DIR/../objtool}" # klp_preflight() { - local tmp tool cc_version host cc_arch + local tmp tool cc_version arch host cc_arch bail() { echo "Bail out! $*" >&2; exit 1; } @@ -96,8 +99,26 @@ klp_preflight() esac rm -rf "$tmp" + # Normalize to the kernel's SRCARCH. + case "${ARCH:-$(uname -m)}" in + x86_64|i?86) arch=x86 ;; + aarch64*) arch=arm64 ;; + *) arch="${ARCH:-$(uname -m)}" ;; + esac + + case "$(uname -m)" in + x86_64|i?86) host=x86 ;; + aarch64*) host=arm64 ;; + *) host="$(uname -m)" ;; + esac + + [ -z "$cc_arch" ] || [ "$cc_arch" = "$arch" ] || + bail "ARCH says $arch but '$CC' builds $cc_arch objects;" \ + "the $arch tests would run against the wrong architecture" + + KLP_TEST_ARCH="$arch" KLP_TEST_PREFLIGHT=done - export OBJTOOL CC KLP_TEST_PREFLIGHT + export OBJTOOL CC KLP_TEST_ARCH KLP_TEST_PREFLIGHT cc_version="$($CC --version 2>/dev/null | head -1)" cat </dev/null | wc -l) + [ "$n" -gt 0 ] || continue + echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ + "(this run is $KLP_TEST_ARCH)" +done + echo "1..${#tests[@]}" rc=0 From 317cec905af82e9ccca93cec661e88b1d3968a43 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:42:57 -0700 Subject: [PATCH 0604/2286] objtool/klp: Classify klp test outcomes A test could pass, fail, or skip, and the runner reported only the exit status of the last one to fail. Skips are the interesting case: they are how a suite quietly stops testing anything. Three kinds, and the runner counts them separately: declared the test said in advance it does not apply here, e.g. gcc_only on a clang run. Expected indefinitely. probe the construct did not turn up in the built object this time. Weaker: one which becomes permanent is a fixture that stopped testing anything. undeclared counted as a failure. A test which gives up for a reason it never declared is a hole, not an outcome. xfail and xpass come with them, so a known failure is reported rather than commented out and forgotten, and one which starts passing says so instead of going quietly green. The result line is what gets classified, not the whole of a test's output: objtool warns on stderr and the runner captures it, and a stray line ahead of the result would otherwise leave the exit status to decide -- counting an expected failure, which exits 0, as a pass. Every test owes the plan exactly one result line, so a substituted verdict replaces the test's own rather than being printed beside it, and a test which dies before reporting gets a result of its own instead of being inferred from its exit status. Also: a TAP version line, LC_ALL=C so the order tests run in and the behaviour of grep's character ranges inside them do not depend on the invoking shell's locale, and fail() on stdout with every other verdict. Co-developed-by: Puranjay Mohan Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-5-song@kernel.org --- tools/objtool/tests/lib.sh | 67 ++++++++++++-- tools/objtool/tests/run-tests.sh | 148 ++++++++++++++++++++++++++----- 2 files changed, 186 insertions(+), 29 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index fdea9b790c4d23..1531caab462789 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -139,8 +139,38 @@ test_name="$(basename "$0" .sh)" workdir= pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } -fail() { echo "not ok - $test_name: $1" >&2; exit 1; } -skip() { echo "ok - $test_name # SKIP $1"; exit 0; } +fail() { echo "not ok - $test_name: $1"; exit 1; } + +# Two kinds of skip, and the runner tells them apart. +# +# declared_skip the test said in advance it does not apply here, e.g. +# gcc_only on a clang run. Expected indefinitely. +# probe_skip the construct did not turn up in the built object this +# time. Weaker: it may appear on another compiler version, +# and one which becomes permanent is a fixture that quietly +# stopped testing anything. +# +# A bare skip() is neither, and the runner counts it as a failure: a test which +# gives up for a reason it never declared is a hole, not an outcome. +declared_skip() { echo "ok - $test_name # SKIP (declared) $*"; exit 0; } +probe_skip() { echo "ok - $test_name # SKIP (probe) $*"; exit 0; } +skip() { echo "ok - $test_name # SKIP $*"; exit 0; } + +# TAP directives. A test which is known to fail reports it rather than being +# commented out and forgotten, and one which starts passing again says so +# instead of quietly going green: the expectation has to be removed by hand, +# which is the point. +xfail() +{ + echo "not ok - $test_name${1:+: $1} # TODO known failure" + exit 0 +} + +xpass() +{ + echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed" + exit 1 +} cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } @@ -170,6 +200,23 @@ export_syms() done } +# gcc_only / clang_only +gcc_only() +{ + case "$($CC --version 2>/dev/null | head -1)" in + *[Gg][Cc][Cc]*) return 0 ;; + esac + declared_skip "gcc only${1:+: $1}" +} + +clang_only() +{ + case "$($CC --version 2>/dev/null | head -1)" in + *clang*) return 0 ;; + esac + declared_skip "clang only${1:+: $1}" +} + # build_pair [cflags...] build_pair() { @@ -178,9 +225,9 @@ build_pair() [ -f "$fixture" ] || fail "missing fixture $fixture" $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || - skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || - skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" + probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" } # run_diff [expected exit status] @@ -226,16 +273,20 @@ partial_link() # find_thinlto_toolchain # -# Set $THIN_CC and $THIN_LD to a clang and lld from the same LLVM release. A +# Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC). A # mismatched pair fails with "Invalid summary version", which reads like a # broken test rather than a broken environment. +# +# ThinLTO is clang-only; callers must use clang_only before calling this. +# Only $CC (or an explicit THIN_CC override) is consulted -- the harness does +# not search for a second compiler beside a gcc $CC. find_thinlto_toolchain() { - local cc ld ver + local cc ver ld - for cc in "${THIN_CC:-}" "$CC" clang; do + for cc in "${THIN_CC:-}" "$CC"; do [ -n "$cc" ] || continue - command -v "${cc%% *}" >/dev/null 2>&1 || continue + command -v "${cc%% *}" >/dev/null 2>&1 || return 1 ver=$($cc -dumpversion 2>/dev/null | cut -d. -f1) diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index 48728a98d9950a..6b937fc7f5bdac 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -8,13 +8,51 @@ # apply here is not run rather than reporting a skip; what was left out is # reported once, as a comment, so differing coverage is still visible. # +# A run covers one compiler and one architecture; CI runs the combinations. # The harness checks the environment once up front and fails the run if the # suite cannot execute, rather than letting every test skip and exit 0. set -u +# Determinism: the order test-*.sh expands in, and how grep's character ranges +# and sort's collation behave inside the tests, are all locale-dependent. A +# suite whose results depend on the invoking shell's locale is a suite whose +# failures cannot be reproduced. +export LC_ALL=C + +usage() +{ + cat <&2; usage 1 ;; + *) break ;; + esac +done + +echo "TAP version 13" + # Sourcing the harness runs its preflight, which decides which architecture # this run is for -- so the test list cannot be built before it has, and the # tests inherit the answers rather than working them out again. @@ -23,31 +61,99 @@ cd "$(dirname "$0")" || exit 1 dirs=( generic ) [ -d "$KLP_TEST_ARCH" ] && dirs+=( "$KLP_TEST_ARCH" ) -tests=() -for d in "${dirs[@]}"; do - for t in "$d"/test-*.sh; do - [ -f "$t" ] && tests+=( "$t" ) +if [ $# -gt 0 ]; then + tests=() + for arg in "$@"; do + name="test-${arg#test-}"; name="${name%.sh}.sh" + found= + for d in "${dirs[@]}"; do + [ -f "$d/$name" ] || continue + [ -x "$d/$name" ] || + { echo "not executable: $d/$name" >&2; exit 1; } + tests+=( "$d/$name" ); found=y + done + [ -n "$found" ] || + { echo "no such test for $KLP_TEST_ARCH: $arg" >&2; exit 1; } done -done -[ "${#tests[@]}" -gt 0 ] || { echo "1..0 # SKIP no tests found"; exit 0; } - -# Tests for another architecture are absent from this run entirely. Say how -# many, so a run which covers less than the tree holds does not look like one -# that covers all of it. -for d in */; do - d="${d%/}" - case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac - n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l) - [ "$n" -gt 0 ] || continue - echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ - "(this run is $KLP_TEST_ARCH)" -done +else + tests=() + for d in "${dirs[@]}"; do + for t in "$d"/test-*.sh; do + [ -f "$t" ] && tests+=( "$t" ) + done + done + [ "${#tests[@]}" -gt 0 ] || + { echo "1..0 # SKIP no tests found"; exit 0; } + + # Tests for another architecture are absent from this run entirely. Say + # how many, so a run which covers less than the tree holds does not look + # like one that covers all of it. + for d in */; do + d="${d%/}" + case "$d" in generic|"$KLP_TEST_ARCH") continue ;; esac + n=$(ls "$d"/test-*.sh 2>/dev/null | wc -l) + [ "$n" -gt 0 ] || continue + echo "# not run: $n test$( [ "$n" = 1 ] || echo s ) in $d/" \ + "(this run is $KLP_TEST_ARCH)" + done +fi echo "1..${#tests[@]}" -rc=0 +pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 + for t in "${tests[@]}"; do - ./"$t" || rc=1 + out="$(./"$t" 2>&1)" + rc=$? + + # A test prints one result line, but it is not necessarily the only + # thing it prints: objtool warns on stderr, and the runner captures + # that. Classify the result line itself rather than the whole of the + # output, or a stray line ahead of it makes every pattern below miss and + # the exit status decide -- which would count an expected failure, which + # exits 0, as a pass. + result="$(printf '%s\n' "$out" | grep -E '^(ok|not ok)' | tail -1)" + rest="$(printf '%s\n' "$out" | grep -Ev '^(ok|not ok)')" + + # Classify from the result line, not the exit status: a skip and a pass + # both exit 0, and telling them apart is the point of counting. + # + # The two skip kinds differ in what they promise. A static skip was + # declared before the test ran ("clang does not do this"), so it is + # expected indefinitely. A probe skip means the construct did not turn + # up this time, which is weaker and worth watching: one that becomes + # permanent is a fixture that quietly stopped testing anything. + case "$result" in + *"# SKIP (declared)"*) static_skip=$((static_skip + 1)) ;; + *"# SKIP (probe)"*) probe_skip=$((probe_skip + 1)) ;; + *"# SKIP"*) + # An undeclared skip: the test gave up for a reason it never + # said it might. That is a hole, not an expected outcome. + # + # Replace the line rather than adding one. Every test owes the + # plan exactly one result, and a consumer counting them is + # entitled to say so when the totals disagree. + rest="$rest${rest:+$'\n'}was: $result" + result="not ok - $(basename "$t" .sh): undeclared skip" + result="$result (use gcc_only/clang_only or require_input_*)" + fail=$((fail + 1)) ;; + "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;; + "ok"*"# TODO"*) xpass=$((xpass + 1)) ;; + "not ok"*) fail=$((fail + 1)) ;; + "ok"*) pass=$((pass + 1)) ;; + *) + # No result line at all: the test died before reporting. + rest="$rest${rest:+$'\n'}exited $rc without a result line" + result="not ok - $(basename "$t" .sh): no TAP result" + fail=$((fail + 1)) ;; + esac + + echo "$result" + [ -n "$rest" ] && printf '%s\n' "$rest" | sed 's/^[^#]/# &/' + done -exit $rc +echo "# pass:$pass fail:$fail static-skip:$static_skip" \ + "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" + +[ "$fail" = 0 ] && [ "$xpass" = 0 ] From ae04b6d0f9bf308d1e80cd8d7fb0f917ae52c2ef Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:42:58 -0700 Subject: [PATCH 0605/2286] objtool/klp: Build klp test fixtures through the harness Compiling a fixture is the one thing every test does, and it has more invariants than it looks. - The flags describe the kernel a fixture stands in for, and each is written down with its reason. That substitution is the whole mechanism by which these tests cover configurations without building a kernel -- objtool reads no .config, so a configuration reaches it only as compiler flags and the bytes they produce -- and a flag with no stated motive is indistinguishable from a mistake. - -c is not one of them. Producing an object rather than a program is the helper's contract, not something a test may reconsider, so it lives at the compile and an override cannot drop it. - A fixture which will not compile is a failure, not a skip. It is far more often a mistake in the fixture than a compiler which cannot express the construct, and the two are indistinguishable once reported as a skip. - A newly produced object has no checksums in it, so producing one invalidates the record that checksumming has already been done. Left to itself that invariant ends up in each test which rebuilds. build_one handles a single object for the tests which need more than two or different names; build_pair and build_module_pair are expressed in terms of it, so none of the above can be lost by going around them. run_checksum comes out of run_diff for the same reason: a test which wants checksums without a diff should not have to run one. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-6-song@kernel.org --- tools/objtool/tests/lib.sh | 205 +++++++++++++++++++++++++++++++++---- 1 file changed, 186 insertions(+), 19 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 1531caab462789..a4513ae8dfcec8 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -131,13 +131,35 @@ EOF [ -n "${KLP_TEST_PREFLIGHT:-}" ] || klp_preflight -# klp-build compiles the kernel this way; klp diff needs per-symbol sections to -# extract individual functions. -FIXTURE_CFLAGS="-c -O2 -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" +# What every fixture is built with. These describe the kernel a fixture stands +# in for; -c is build_one's contract rather than a property of that kernel, so +# it lives at the compile where an override cannot drop it. +# +# -O2 the kernel's default +# -ffunction-sections -fdata-sections klp-build passes these itself, through +# KCFLAGS, whatever the configuration +# -fno-asynchronous-unwind-tables arch/x86/Makefile sets this always, so +# kernel objects carry no .eh_frame +# -fno-common the kernel's Makefile sets it, so an +# uninitialised global there lands in +# .bss rather than being SHN_COMMON, +# which has no section and so no +# checksum +# +# A test overrides it; see tools/objtool/Documentation/klp-write-tests.txt. +FIXTURE_CFLAGS="-O2 -ffunction-sections -fdata-sections -fno-common \ + -fno-asynchronous-unwind-tables" test_name="$(basename "$0" .sh)" workdir= +# The pair run_diff() and the checksum helpers work on. build_pair() names +# them again and make_vmlinux_pair() repoints orig_obj at the image it links, +# but a test which builds its objects itself with build_one() sets neither, so +# the default belongs here. +orig_obj=orig.o +patched_obj=patched.o + pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } fail() { echo "not ok - $test_name: $1"; exit 1; } @@ -217,17 +239,78 @@ clang_only() declared_skip "clang only${1:+: $1}" } +# build_one [cflags...] +build_one() +{ + local fixture out + fixture="$FIXTURES_DIR/$1" + out="$workdir/$2" + shift 2 + + [ -f "$fixture" ] || fail "missing fixture $fixture" + + # run_checksum only runs once per workdir. A fresh object has no + # checksums in it, so anything built now needs that to happen again. + rm -f "$workdir/.checksummed" + + $CC -c $FIXTURE_CFLAGS "$@" -o "$out" "$fixture" 2>"$workdir/cc.log" || + fail "$(basename "$fixture") does not build: $(tail -1 "$workdir/cc.log")" +} + # build_pair [cflags...] build_pair() { - local fixture="$FIXTURES_DIR/$1"; shift + local fixture="$1"; shift - [ -f "$fixture" ] || fail "missing fixture $fixture" + # Name what this builds. A test may run several segments, and + # make_vmlinux_pair() repoints orig_obj at the image it links, so + # without this the next run_diff() would still be reading that. + orig_obj=orig.o + patched_obj=patched.o + + build_one "$fixture" orig.o "$@" + build_one "$fixture" patched.o "$@" -DPATCHED +} - $CC $FIXTURE_CFLAGS "$@" -o "$workdir/orig.o" "$fixture" 2>"$workdir/cc.log" || - probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" - $CC $FIXTURE_CFLAGS "$@" -DPATCHED -o "$workdir/patched.o" "$fixture" 2>"$workdir/cc.log" || - probe_skip "fixture does not build here: $(tail -1 "$workdir/cc.log")" +# run_objtool_check +# +# Run objtool's ordinary check pass over the pair, as the kernel build does. +# +# Some of what klp diff consumes is produced by this pass rather than by the +# compiler: .static_call_sites, .mcount_loc, .ibt_endbr_seal, ORC. +# +# Only module objects see it before klp-build -- with CONFIG_KLP_BUILD the +# per-object pass is deferred, so built-in objects reach klp diff exactly as +# the compiler left them. +run_objtool_check() +{ + local obj + + # This rewrites both objects, so checksums taken before it describe + # something that no longer exists. As in build_one(), drop the marker + # so run_checksum() takes them again. + rm -f "$workdir/.checksummed" + + for obj in "$orig_obj" "$patched_obj"; do + "$OBJTOOL" "$@" "$workdir/$obj" || + fail "objtool $* failed on $obj" + done +} + +run_checksum() +{ + # Checksums live in the objects, and a test may ask for them more than + # once -- diffing the same pair again with a different Module.symvers, + # say. objtool does the right thing when asked twice, leaving the + # object alone, but it says so, and that warning would be most of what + # a passing run prints. Remember instead, and keep quiet. + [ -e "$workdir/.checksummed" ] && return 0 + + "$OBJTOOL" klp checksum "$workdir/$orig_obj" || + fail "klp checksum $orig_obj failed" + "$OBJTOOL" klp checksum "$workdir/$patched_obj" || + fail "klp checksum $patched_obj failed" + touch "$workdir/.checksummed" } # run_diff [expected exit status] @@ -235,18 +318,10 @@ run_diff() { local expect="${1:-0}" rc=0 - # Checksums live in the objects, so only generate them once even when a - # test diffs the same pair again with a different Module.symvers. - if [ ! -e "$workdir/.checksummed" ]; then - "$OBJTOOL" klp checksum "$workdir/orig.o" || - fail "klp checksum orig.o failed" - "$OBJTOOL" klp checksum "$workdir/patched.o" || - fail "klp checksum patched.o failed" - touch "$workdir/.checksummed" - fi + run_checksum # klp diff looks for Module.symvers relative to the working directory. - ( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + ( cd "$workdir" && "$OBJTOOL" klp diff "$orig_obj" "$patched_obj" out.o ) \ > "$workdir/diff.log" 2>&1 || rc=$? [ "$rc" = "$expect" ] || @@ -267,10 +342,102 @@ partial_link() { local out="$1"; shift + rm -f "$workdir/.checksummed" + $CC -r -nostdlib -o "$out" "$@" 2>/dev/null || $CC -r -nostdlib -fuse-ld=lld -o "$out" "$@" 2>/dev/null } +# link_vmlinux +# +# Link objects into an executable, the way the kernel's final link produces +# vmlinux from vmlinux.o. Entry point 0 and no libc: nothing runs it, it only +# has to be a linked image with resolved addresses. +# +# The sub-sections have to come out in name order rather than object order, +# the way the kernel's linker script gathers .text.unlikely and .data.. apart +# from the rest. That reordering is the entire reason .klp.symid exists: a +# link which preserves order cannot tell a correct sympos from one that merely +# counted, and the caller checks the two orders really did diverge. +# +# A linker script rather than --sort-section=name, because lld accepts that +# option and ignores it -- so on a host where only lld can link the target, the +# test would quietly stop testing the thing it is named for. +# +# Three attempts because a cross run has neither $LD nor the compiler's default +# linker able to touch the target: on an arm64 host linking x86 objects, only +# lld will do it. +link_vmlinux() +{ + local out="$1" lds="$workdir/sort.lds"; shift + + echo 'SECTIONS { .data : { *(SORT_BY_NAME(.data.*)) } }' > "$lds" + + $LD -e 0 -T "$lds" -o "$out" "$@" 2>/dev/null || + $CC -nostdlib -Wl,-e,0 -Wl,-T,"$lds" \ + -o "$out" "$@" 2>/dev/null || + $CC -nostdlib -fuse-ld=lld -Wl,-e,0 -Wl,-T,"$lds" \ + -o "$out" "$@" 2>/dev/null +} + +# make_vmlinux_pair -- +# +# Build the vmlinux.o / vmlinux pair klp diff needs to resolve sympos the way +# it does for built-in code, and point the diff at it. +# +# For a module, sympos is a count in symbol table order, which klp diff can do +# from the object alone. vmlinux is different: the final link reorders +# sub-sections, so the position comes from the linked image, bridged by +# .klp.symid. klp diff only looks for that when the object it was handed is +# called vmlinux.o and a vmlinux sits beside it -- so both the name and the +# linked image matter. +make_vmlinux_pair() +{ + local orig=() patched=() seen= arg + + for arg in "$@"; do + if [ "$arg" = -- ]; then seen=y; continue; fi + if [ -n "$seen" ]; then patched+=( "$arg" ); else orig+=( "$arg" ); fi + done + + # Both sides have to have been named. Without this, forgetting the -- + # leaves one list empty, the link of nothing fails, and the test skips + # saying the toolchain cannot link -- which is a test bug wearing the + # costume of an environment one. + [ "${#orig[@]}" -gt 0 ] && [ "${#patched[@]}" -gt 0 ] || + fail "make_vmlinux_pair needs objects either side of --" + + partial_link "$workdir/vmlinux.o" "${orig[@]}" || + probe_skip "partial link unavailable" + partial_link "$workdir/patched.o" "${patched[@]}" || + probe_skip "partial link unavailable" + + "$OBJTOOL" --klp-symids --link "$workdir/vmlinux.o" || + fail "objtool --klp-symids failed" + + link_vmlinux "$workdir/vmlinux" "$workdir/vmlinux.o" || + probe_skip "cannot link a vmlinux here" + + orig_obj=vmlinux.o +} + +# build_module_pair [cflags...] +# +# Build the pair as objects belonging to a module rather than to vmlinux. klp +# diff reads the object's module name from .modinfo, and that decides which +# object a relocation is attributed to and whether a reference counts as +# cross-module, so a good deal of the code has a module path the vmlinux +# fixtures never reach. +# +# The fixture defines its .modinfo name from MODNAME. Passing that through +# -D needs two levels of quoting, which is easy to get wrong at the call site. +build_module_pair() +{ + local fixture="$1" modname="$2"; shift 2 + + build_pair "$fixture" -DMODNAME="\"$modname\"" "$@" +} + # find_thinlto_toolchain # # Set $THIN_LD to an lld from the same LLVM release as $CC (or THIN_CC). A From 2ab0faeb71936be1f53a8222cd5d0a1bcada5b82 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:42:59 -0700 Subject: [PATCH 0606/2286] objtool/klp: Grow the klp test harness vocabulary Assertions for what klp diff produces, and for what went into it. The output side covers the things a livepatch is made of: cloned sections and symbols, klp symbols and their sympos, tombstones, relocations by section and by count, the converted .klp.rela sections and their SHF_RELA_LIVEPATCH flag, SHN_LIVEPATCH symbols, recorded checksums, and klp diff's own diagnostics -- a rejection for the wrong reason is not a pass. The input side matters just as much, and is easier to forget. A test which asserts only on the output passes when the compiler never emitted the construct under test, and reads as coverage it does not have. So there are two forms, and the difference between them is a statement about why the thing might be absent: require_* the compiler cannot produce it here -> skip assert_* the fixture is supposed to produce it -> fail Names are quoted before they reach grep. Nearly every name here contains dots -- .text.target, .klp.rela.vmlinux -- and an unescaped dot matches any character, so an assertion for one section could be satisfied by a different one whose name merely lines up. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-7-song@kernel.org --- tools/objtool/tests/lib.sh | 364 ++++++++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 4 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index a4513ae8dfcec8..817915e38a35c9 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -216,9 +216,42 @@ setup() export_syms() { : > "$workdir/Module.symvers" + add_exports vmlinux "$@" +} + +# add_exports [symbol...] +# +# Append exports owned by one object, without clearing what is already there, +# so a test can describe a kernel where several objects export things. +# +# Which object owns a symbol is not cosmetic: a reference to a vmlinux symbol +# is applied when the patch module loads, and a reference to a module's symbol +# when that patched module loads, so klp diff files them in different sections. +add_exports() +{ + local owner="$1"; shift + + add_exports_ns "$owner" "" "$@" +} + +# add_exports_ns [symbol...] +# +# Exports in a symbol namespace, the last field of a Module.symvers line. +# +# A "module:" namespace is EXPORT_SYMBOL_FOR_MODULES(), where the module +# loader grants access by matching the importing module's name against the +# list. A livepatch module is never on that list, so such a symbol has to be +# referenced the way an unexported one is. Ordinary namespaces are not +# special here. +add_exports_ns() +{ + local owner="$1" ns="$2"; shift 2 + + local sym + for sym in "$@"; do - printf '0x00000000\t%s\tvmlinux\tEXPORT_SYMBOL\t\n' \ - "$sym" >> "$workdir/Module.symvers" + printf '0x00000000\t%s\t%s\tEXPORT_SYMBOL\t%s\n' \ + "$sym" "$owner" "$ns" >> "$workdir/Module.symvers" done } @@ -481,12 +514,87 @@ out_relocs() { $READELF -r -W "$workdir/out.o" 2>/dev/null; } out_symbols() { $READELF -s -W "$workdir/out.o" 2>/dev/null; } diff_log() { cat "$workdir/diff.log"; } +# out_strings
+# +# The strings in one section of the output, for the names livepatch matches on. +out_strings() { $READELF -p "$1" "$workdir/out.o" 2>/dev/null; } + +# Checks on the input objects, to run before klp diff. The two forms differ in +# what an absent construct means: +# +# require_* the compiler cannot produce it here -> skip +# assert_* the fixture is supposed to produce it -> fail + +in_sections() { $READELF -S -W "$workdir/$1" 2>/dev/null; } +in_symbols() { $READELF -s -W "$workdir/$1" 2>/dev/null; } +in_relocs() { $READELF -r -W "$workdir/$1" 2>/dev/null; } + +# count_input_symbols +# +# How many object symbols of exactly that name the input has. Deliberately not +# a grep: readelf lists section symbols too, and a newer binutils prints their +# name -- ".data." -- where an older one leaves the column blank. A dot +# is not a word character, so "grep -w " counts that line as well, and +# the same object gives a different answer depending on which readelf reads it. +count_input_symbols() +{ + in_symbols "$1" | awk -v n="$2" '$4 == "OBJECT" && $8 == n' | wc -l +} + +# re_quote +# +# A string as a literal basic regular expression. Nearly every name these +# assertions match on contains a dot -- .text.target, .klp.rela.vmlinux -- and +# an unescaped dot matches any character, so an assertion for one section can be +# satisfied by a different one whose name merely lines up. +re_quote() { printf '%s' "$1" | sed 's|[].[^$*\\/]|\\&|g'; } + +has_input_section() { in_sections "$1" | grep -q "[[:space:]]$(re_quote "$2")[[:space:]]"; } +has_input_symbol() { in_symbols "$1" | awk -v n="$2" '$NF == n' | grep -q .; } + +assert_input_section() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_section "$obj" "$1" || + fail "fixture produced no section '$1' in $obj" + done +} + +assert_input_symbol() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_symbol "$obj" "$1" || + fail "fixture produced no symbol '$1' in $obj" + done +} + +require_input_section() +{ + local obj + + for obj in "$orig_obj" "$patched_obj"; do + has_input_section "$obj" "$1" || + probe_skip "compiler produced no section '$1' here" + done +} + assert_section() { - out_sections | grep -q "[[:space:]]$1[[:space:]]" || + out_sections | grep -q "[[:space:]]$(re_quote "$1")[[:space:]]" || fail "expected section '$1' in output" } +assert_no_section() +{ + out_sections | grep -q "[[:space:]]$(re_quote "$1")[[:space:]]" && + fail "unexpected section '$1' in output" + return 0 +} + assert_patched() { assert_section ".text.$1" @@ -494,7 +602,255 @@ assert_patched() assert_not_patched() { - out_sections | grep -q "[[:space:]].text.$1[[:space:]]" && + out_sections | grep -q "[[:space:]]$(re_quote ".text.$1")[[:space:]]" && fail "function '$1' should not have been cloned" return 0 } + +# section_relocs
+# +# The relocations against one section. readelf prints every relocation section +# in turn, so a test asking about ".smp_locks" has to cut its block out of the +# listing first. +section_relocs() +{ + local sec="${1//./\\.}" + + out_relocs | awk "/rela$sec'/,/^\$/" +} + +assert_reloc_sym() +{ + section_relocs "$1" | awk -v n="$2" '$5 == n' | grep -q . || + fail "expected a relocation to '$2' in '$1'" +} + +assert_no_reloc_sym() +{ + section_relocs "$1" | awk -v n="$2" '$5 == n' | grep -q . && + fail "unexpected relocation to '$2' in '$1'" + return 0 +} + +# assert_reloc_count
+# +# Counts relocation entries, not header or blank lines: whether a special +# section entry was extracted once, twice or not at all is usually the whole +# question. +# +# A count of zero is ambiguous on its own -- a section with no relocations and +# no section at all both read as zero -- so require the section to exist. A +# test expecting nothing there wants assert_no_section. +assert_reloc_count() +{ + local n + + assert_section "$1" + + n="$(section_relocs "$1" | grep -cE '^[0-9a-f]{8,}')" + [ "$n" = "$2" ] || + fail "expected $2 relocations in '$1', found $n" +} + +# assert_klp_sym [object] +# +# A klp symbol is named .klp.sym..,. The object +# defaults to any, since most tests care that the reference was converted at +# all rather than which object it resolved against. +assert_klp_sym() +{ + out_symbols | grep -q "\.klp\.sym\.${2:-[^.]*}\.$(re_quote "$1")," || + fail "expected klp symbol for '$1'" +} + +# assert_klp_sympos +# +# The number after the comma in .klp.sym.., says which +# of several same-named symbols livepatch should resolve to, counting from 1; +# 0 means the name is unique and no disambiguation is needed. Resolving to the +# wrong one is not a load failure, it is a patch quietly wired to the wrong +# object. +assert_klp_sympos() +{ + out_symbols | grep -qE "\.klp\.sym\.[^.]+\.$(re_quote "$1"),$2([[:space:]]|\$)" || + fail "expected klp symbol for '$1' with sympos $2, found:$( + out_symbols | grep -o "\.klp\.sym\.[^.]*\.$(re_quote "$1"),[0-9]*" | + sort -u | tr '\n' ' ')" +} + +assert_no_klp_sym() +{ + out_symbols | grep -q "\.klp\.sym\.${2:-[^.]*}\.$(re_quote "$1")," && + fail "unexpected klp symbol for '$1'" + return 0 +} + +assert_tombstone() +{ + out_symbols | grep -qE "\.klp\.tombstone\.$(re_quote "$1")([[:space:]]|\$)" || + fail "expected a tombstone for '$1'" +} + +assert_symbol() +{ + out_symbols | awk -v n="$1" '$NF == n' | grep -q . || + fail "expected symbol '$1' in output" +} + +assert_no_symbol() +{ + out_symbols | awk -v n="$1" '$NF == n' | grep -q . && + fail "unexpected symbol '$1' in output" + return 0 +} + +# assert_diff_log +# +# klp diff's combined output, for tests asserting on a diagnostic. Error +# messages are part of the interface when the whole point is that a construct +# gets rejected, and a rejection for the wrong reason is not a pass. +assert_diff_log() +{ + diff_log | grep -qE -- "$1" || + fail "expected '$1' in klp diff output: $(tail -2 "$workdir/diff.log")" +} + +# checksum_of +# +# The checksum "klp checksum" recorded for one symbol, as a hex string. +# +# .discard.sym_checksum is an array of { u64 addr; u64 checksum; }, where addr +# is the target of a relocation naming the symbol. Nothing in the section +# itself says which symbol an entry belongs to, so the relocation is what +# locates the entry; the checksum is the eight bytes after it. +# Callers use this in a command substitution, where fail() would only exit the +# subshell and the test would carry on with an empty checksum. So this returns +# non-zero and prints nothing, and the assertions below check for that. For +# the same reason it does not run run_checksum() itself: that one does call +# fail(), and from in here the message would be captured as the checksum +# rather than ending the test. The caller runs it first. +checksum_of() +{ + local obj="$workdir/$1" sym="$2" off + + off="$($READELF -rW "$obj" 2>/dev/null | + awk -v s="$sym" '/rela\.discard\.sym_checksum/,/^$/ { + if ($5 == s) { print $1; exit } + }')" + + [ -n "$off" ] || return 1 + + $OBJCOPY -O binary --only-section=.discard.sym_checksum \ + "$obj" "$workdir/checksums.bin" 2>/dev/null || return 1 + + dd if="$workdir/checksums.bin" bs=1 skip=$((16#$off + 8)) count=8 \ + status=none | od -An -tx1 | tr -d ' \n' +} + +# assert_checksum_differs / assert_checksum_matches +# +# Compare what klp checksum recorded for a symbol in the original against the +# patched object. This is what decides whether klp diff treats a function as +# changed, so a test asserting only that the right functions were cloned cannot +# tell a correct checksum from one which happens to differ. +checksum_pair() +{ + run_checksum + + orig_checksum="$(checksum_of "$orig_obj" "$1")" + patched_checksum="$(checksum_of "$patched_obj" "$1")" + + [ -n "$orig_checksum" ] || + fail "no checksum recorded for '$1' in $orig_obj" + [ -n "$patched_checksum" ] || + fail "no checksum recorded for '$1' in $patched_obj" +} + +assert_checksum_differs() +{ + checksum_pair "$1" + + [ "$orig_checksum" != "$patched_checksum" ] || + fail "checksum for '$1' unchanged at $orig_checksum, expected it to differ" +} + +assert_checksum_matches() +{ + checksum_pair "$1" + + [ "$orig_checksum" = "$patched_checksum" ] || + fail "checksum for '$1' changed from $orig_checksum to" \ + "$patched_checksum, expected no change" +} + +# run_post_link [expected exit status] +# +# klp post-link runs last in a livepatch build, converting the intermediate +# __klp_relocs.* sections into the .klp.rela.* form the kernel consumes. It +# needs nothing but an object containing those sections, which is what klp diff +# produces, so it runs on out.o here rather than on a built module. Rewrites +# out.o in place, so the out_* helpers show the result afterwards. +run_post_link() +{ + local expect="${1:-0}" rc=0 + + "$OBJTOOL" klp post-link "$workdir/out.o" \ + > "$workdir/post-link.log" 2>&1 || rc=$? + + [ "$rc" = "$expect" ] || + fail "klp post-link exited $rc, expected $expect:" \ + "$(tail -2 "$workdir/post-link.log")" +} + +# The flags readelf prints for a section, or nothing when it has none. The +# leading "[nn]" index is stripped first so the columns can be counted. +section_flags() +{ + out_sections | sed 's/^ *\[[ 0-9]*\] *//' | + awk -v s="$1" '$1 == s && $7 ~ /^[A-Za-z]+$/ { print $7 }' +} + +# assert_section_flag
+# +# SHF_RELA_LIVEPATCH is OS-specific, so readelf renders it as "o". A klp rela +# section which lost it is an ordinary rela section, which the linker may apply +# and the livepatch code will not. +assert_section_flag() +{ + local flags; flags="$(section_flags "$1")" + + [ -n "$flags" ] || + fail "section '$1' has no flags, expected '$2'" + case "$flags" in + *"$2"*) ;; + *) fail "section '$1' has flags '$flags', expected '$2'" ;; + esac +} + +# assert_klp_rela
+# +# post-link names the converted sections .klp.rela..
, one per +# base section. Also checks SHF_RELA_LIVEPATCH, since the name alone is not +# what makes the kernel process it. +assert_klp_rela() +{ + local name=".klp.rela.$1.$2" + + out_sections | grep -q "[[:space:]]$(re_quote "$name")[[:space:]]" || + fail "expected section '$name' in output" + + assert_section_flag "$name" o +} + +# assert_livepatch_sym +# +# Symbols a klp relocation resolves against live in SHN_LIVEPATCH, which +# readelf prints as "OS [0xff20]" -- llvm-readelf without the space, so match +# either. The kernel resolves these itself at patch load; anything else is a +# symbol the module loader will try, and fail, to resolve normally. +assert_livepatch_sym() +{ + out_symbols | grep -E 'OS ?\[0xff20\]' | + grep -qE "\.klp\.sym\.[^.]+\.$(re_quote "$1")," || + fail "expected a klp symbol for '$1' in SHN_LIVEPATCH" +} From 8593055b7a4015761b3a4bbc3a64371bc42d1d12 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:00 -0700 Subject: [PATCH 0607/2286] objtool/klp: Give each run one working directory, one per test inside it Each test made its own mktemp directory, so a run scattered them through /tmp among everything else using the same names, and there was no way to inspect what a failing test built -- the objects are gone the moment it exits, which is right every time but the one that matters. Give the run one directory and each test a subdirectory of it, named for the test and nested the way the source is: /tmp/klp-tests.3zC6oyfd/ generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} x86/test-kcfi/... --keep then reports one path instead of forty, and removing it is one command. A test run by hand still falls back to a temp directory of its own, and still says where that went. Detecting a leak comes free. Cleanup is now "remove each test's directory, then rmdir the run's", and the rmdir fails if anything is left -- so a test which dies without running its own cleanup is reported rather than quietly leaving something behind. preflight names the directory the tests build under, since mktemp honours TMPDIR and a run in a container is otherwise silent about where its work went. The runner says what to do only when it is relevant: on a failure without --keep, that the option exists; with it, how many directories are waiting. Usage text is not read while something is broken. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-8-song@kernel.org --- tools/objtool/tests/lib.sh | 23 ++++++++++++++++++----- tools/objtool/tests/run-tests.sh | 26 ++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 817915e38a35c9..82e6b0d4c67042 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -126,6 +126,7 @@ klp_preflight() # objtool $OBJTOOL (klp: yes) # compiler $cc_version # arch $KLP_TEST_ARCH$([ "$arch" = "$host" ] || echo " (host $host, cross)") +# tmpdir ${TMPDIR:-/tmp} (each test builds in a fresh directory here) EOF } @@ -194,15 +195,27 @@ xpass() exit 1 } -cleanup() { [ -n "$workdir" ] && rm -rf "$workdir"; } +cleanup() +{ + [ -n "$workdir" ] || return 0 + + if [ -n "${KLP_TEST_KEEP:-}" ]; then + [ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir" + return 0 + fi + + rm -rf "$workdir" +} # setup [exported symbol...] setup() { - # The environment was checked once when this file was sourced, so there - # is nothing to verify here: objtool exists at the resolved path, has - # klp support, and $CC works. - workdir="$(mktemp -d)" || fail "mktemp failed" + if [ -n "${KLP_TEST_WORKDIR:-}" ]; then + workdir="$KLP_TEST_WORKDIR" + mkdir -p "$workdir" || fail "cannot create $workdir" + else + workdir="$(mktemp -d)" || fail "mktemp failed" + fi trap cleanup EXIT export_syms "$@" diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index 6b937fc7f5bdac..e6f1ac1b5d0da9 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -23,13 +23,17 @@ export LC_ALL=C usage() { cat <&2; usage 1 ;; *) break ;; @@ -98,12 +103,18 @@ else done fi +# One directory for the whole run, one per test inside it, mirroring the +# source layout. A run then leaves a single thing behind instead of 39 +# scattered among everything else using mktemp. +rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" || + { echo "Bail out! cannot create a working directory" >&2; exit 1; } + echo "1..${#tests[@]}" pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 for t in "${tests[@]}"; do - out="$(./"$t" 2>&1)" + out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)" rc=$? # A test prints one result line, but it is not necessarily the only @@ -156,4 +167,15 @@ done echo "# pass:$pass fail:$fail static-skip:$static_skip" \ "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" +# A failure is the one time the objects matter, and by default they are +# already gone. Say so then rather than in the usage text nobody reads while +# something is broken. +if [ -n "${KLP_TEST_KEEP:-}" ]; then + echo "# working directories kept in $rundir -- inspect, then rm -rf it" +elif ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then + echo "# $rundir was not empty; a test did not clean up after itself" +elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then + echo "# re-run with --keep to hold on to what a failing test built" +fi + [ "$fail" = 0 ] && [ "$xpass" = 0 ] From 48dafeead17e96ee5a63816580a0b92732ea32f6 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:01 -0700 Subject: [PATCH 0608/2286] objtool/klp: Run the klp tests under set -u Most of these assertions end in a grep, and an empty pattern matches anything. So a mistyped variable name in a test does not fail: it expands to nothing, the grep succeeds, and the test reports a pass having checked nothing. assert_section "$sectoin_nmae" becomes a search for two adjacent spaces, which readelf's column padding satisfies on every line. Refuse unset variables. lib.sh guards the few genuinely optional expansions it has with ${x:-}. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-9-song@kernel.org --- tools/objtool/tests/lib.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 82e6b0d4c67042..294b99ced3579c 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -8,6 +8,8 @@ # codegen varies between compilers and golden files would report churn instead # of regressions. +set -u + TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Tests live in generic/ or in an architecture directory beside it, and each From 2ab23a580d00fcf2a3dbb93b54b45d295aba835d Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:02 -0700 Subject: [PATCH 0609/2286] objtool/klp: Document the klp test harness Two documents, for two different readers. klp-test-design.txt is for someone deciding whether to trust the suite or change it: what makes unit tests possible for a pipeline that normally needs two kernel builds (objtool has no configuration-dependent logic, so a test reproduces a configuration by reproducing its input), how tests are selected per architecture, and what the outcome classification is for. klp-write-tests.txt is the procedure for adding one, and is written to be handed to someone -- or something -- with the instruction "follow this and write a test for commit ", or "port the case at from another harness". It leads with the rule that matters: a test is not finished until you have watched it fail with the code broken. Then the traps, each of which this suite has already fallen into -- a fixture the compiler optimised away, a named char[] which never reaches the string path because that keys on SHF_STRINGS, per-function sections hiding the movement a test was about. Some of this was previously written as comments in lib.sh. That was the wrong place: guidance on how to write a test is read once by an author, while every comment in the harness is read by everyone who opens it. Explanations of why the code is as it is stay where they were. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-10-song@kernel.org --- .../objtool/Documentation/klp-test-design.txt | 272 ++++++++++++++++++ .../objtool/Documentation/klp-write-tests.txt | 266 +++++++++++++++++ 2 files changed, 538 insertions(+) create mode 100644 tools/objtool/Documentation/klp-test-design.txt create mode 100644 tools/objtool/Documentation/klp-write-tests.txt diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt new file mode 100644 index 00000000000000..ec56bfbc2c930f --- /dev/null +++ b/tools/objtool/Documentation/klp-test-design.txt @@ -0,0 +1,272 @@ +.. SPDX-License-Identifier: GPL-2.0 + +====================================== +Design of the objtool klp test harness +====================================== + +tools/objtool/tests/ holds unit tests for the klp subcommands of +objtool -- ``klp checksum``, ``klp diff``, ``klp post-link`` and +``--klp-symids`` -- which together turn two builds of the kernel into a +livepatch module. + +This document explains how the harness is built and why. For the rules to +follow when adding a test, see klp-write-tests.txt. + + +TL;DR +===== + +One run covers one compiler and one architecture; CI runs the combinations. +Build objtool first -- it needs libelf and libxxhash -- and the same ARCH is +used for both steps. + +Natively, with gcc:: + + make -C tools/objtool + make -C tools/objtool tests + +Natively, with clang -- LLVM=1 additionally selects the LLVM binutils:: + + CC=clang make -C tools/objtool tests + LLVM=1 make -C tools/objtool tests + +Cross, with gcc -- an arm64 host running the x86 tests:: + + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool tests + +Cross, with clang. It defaults to the host triple however it is invoked, so +--target= is what makes it emit x86; OBJCOPY is needed because BFD's is +usually built for the host's target alone:: + + ARCH=x86_64 make -C tools/objtool + ARCH=x86_64 CC="clang --target=x86_64-linux-gnu" OBJCOPY=llvm-objcopy \ + make -C tools/objtool tests + +A run ends with a totals line; anything other than fail:0 is a real result:: + + # pass:48 fail:0 static-skip:1 probe-skip:0 xfail:0 xpass:0 + +Useful extras:: + + tools/objtool/tests/run-tests.sh basic # one test, by name + tools/objtool/tests/run-tests.sh --keep basic # and keep what it built + + +Why unit tests are possible at all +================================== + +klp-build is a pipeline: build the kernel twice, checksum both, diff them, +link the result. Testing that end to end means two kernel builds per case, +which is too slow to run often and too heavy to keep in the tree. + +Three properties make a much cheaper test possible. + +**objtool has no configuration-dependent logic.** It never reads ``.config``. +Every ``CONFIG_`` string in its source is a comment or one error message, and +its only build-time conditionals are driven by host libraries and the target +architecture. Configuration reaches objtool through exactly two channels: the +``objtool-args-$(CONFIG_*)`` lines in scripts/Makefile.lib, and the +contents of the object handed to it. + +**The klp subcommands use none of the first channel.** Of objtool's options +they consult three -- ``checksum``, ``debug_checksum``, ``dryrun`` -- all from +their own command line. So klp behaviour varies with configuration *only* +through the input object. + +**Therefore a test can reproduce any configuration's behaviour by reproducing +its input.** Compile a small freestanding fixture with the flags that +configuration would have used, and objtool cannot tell the difference. No +kernel, no ``.config``, no object cache. + +The whole suite runs in a few seconds. + + +Shape of a test +=============== + +Each test compiles one fixture twice -- once plain, once with ``-DPATCHED`` -- +runs ``klp checksum`` over both, diffs them, and asserts on properties of the +output object:: + + . "$(dirname "$0")/../lib.sh" + + setup + build_pair basic.c + + assert_input_symbol changed + run_diff + + assert_patched changed + assert_not_patched untouched + + pass "changed function cloned, unchanged function left alone" + +Assertions check properties, never recorded output. Codegen varies between +compilers and versions, so a golden file would report churn rather than +regressions. + + +Layout +====== + +:: + + tools/objtool/tests/ + lib.sh the harness: everything a test may call + run-tests.sh selects, runs and classifies + generic/ + test-*.sh + fixtures/*.c + x86/ + test-*.sh + fixtures/*.c + +Which architecture a test is for is expressed by where it lives. The runner +executes ``generic/`` plus the directory matching this architecture, so a test +which cannot apply is not run rather than running in order to report that it +did not. There is no ``x86_only`` helper, and no lookup letting an +architecture fixture shadow a generic one: an architecture-specific test +carries its own fixtures. + +Compilers cannot be expressed the same way, because CI varies ``CC`` over the +same tree. A compiler requirement stays a declaration inside the test +(``gcc_only``, ``clang_only``). + + +The environment is established once +=================================== + +Sourcing lib.sh runs ``klp_preflight``, which checks that objtool +exists and has klp support, that ``$CC`` works, that the binutils are present, +and which architecture this is. The answers are exported, so: + +* ``run-tests.sh`` sources lib.sh too, and therefore knows the + architecture before it chooses which tests to run; +* each test inherits the answers rather than repeating the work; +* a test run on its own establishes them for itself. + +Preflight answers only whether the suite can run at all. A suite which cannot +run must not exit 0 looking like one which passed, so a missing objtool fails +the whole run with a TAP ``Bail out!`` rather than skipping each test in turn. +What a *particular* compiler can do is a different question, left to the test +which cares. + + +Outcomes +======== + +Output is TAP. The distinction the harness cares most about is between kinds +of skip, because a skip is how a suite quietly stops testing anything: + +``declared`` + The test said in advance it does not apply -- ``gcc_only`` on a clang run. + Expected indefinitely. + +``probe`` + The construct did not turn up in the built object this time. Weaker: one + which becomes permanent is a fixture that has stopped testing anything. + +``undeclared`` + Counted as a **failure**. A test which gives up for a reason it never + declared is a hole, not an outcome. + +``xfail``/``xpass`` come with them, so a known failure is reported rather than +commented out, and one which starts passing says so instead of going quietly +green. + +The runner classifies the TAP result line, not everything a test printed: +objtool warns on stderr and that output is captured, so a stray line ahead of +the result would otherwise leave the exit status to decide -- and an expected +failure exits 0. + +A run ends with a totals line:: + + # pass:48 fail:0 static-skip:1 probe-skip:0 xfail:0 xpass:0 + +and reports what it left out:: + + # not run: 5 tests in x86/ (this run is arm64) + + +Working directories +=================== + +A run gets one directory; each test gets a subdirectory of it, mirroring the +source layout:: + + /tmp/klp-tests.XXXXXXXX/ + generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} + x86/test-kcfi/... + +``--keep`` leaves it and reports the one path. Otherwise each test removes its +own directory and the runner ``rmdir``s the run's -- which fails if anything +was left behind, so a test which dies without cleaning up is reported rather +than silently leaking. + + +Running +======= + +:: + + make -C tools/objtool # needs libelf and libxxhash + make -C tools/objtool tests + + CC=clang make -C tools/objtool tests # the other toolchain + LLVM=1 make -C tools/objtool tests # and its binutils too + + tools/objtool/tests/run-tests.sh --keep basic # one test, keep its objects + +A run covers one compiler and one architecture; CI runs the combinations. + +Cross-compiled runs +------------------- + +objtool klp is built only where ARCH_HAS_KLP is set, which today means x86 -- +so an arm64 machine cannot run any of this natively. It can run all of it +cross, because objtool is a host tool that only reads and rewrites ELF, and +the tests only compile fixtures and inspect the objects. Nothing has to +execute target code. + +:: + + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- make -C tools/objtool tests + +objtool itself stays a native binary: it is built with HOSTCC, not CC, so +setting a cross compiler cannot produce one the host is unable to run. ARCH +selects both the objtool target and the directory of tests to run. + +clang needs telling, since it defaults to the host triple however it is +invoked. The fixtures include no kernel headers, so no sysroot is needed: + +:: + + ARCH=x86_64 CC="clang --target=x86_64-linux-gnu" \ + OBJCOPY=llvm-objcopy make -C tools/objtool tests + +CROSS_COMPILE picks the binutils, and each can be overridden on its own. +readelf reads any target and rarely needs overriding; BFD's objcopy is usually +built for the host's alone, hence OBJCOPY=llvm-objcopy above, or install +binutils-multiarch. + +Either readelf will do. The assertions read readelf's output, and the two +spell some of it differently -- GNU prints "OS [0xff20]" for SHN_LIVEPATCH +where llvm-readelf prints "OS[0xff20]" -- so they accept both. + +Getting this wrong is easy and the harness refuses rather than producing a +misleading result. "CC=clang ARCH=x86_64" alone selects the x86 tests and +then builds arm64 objects; preflight compiles a probe object, hands it to +objtool, and stops the run if they disagree about the architecture, or if +ARCH does not match what the compiler emits. + + +What this does not cover +======================== + +These are unit tests for objtool's klp subcommands. They do not build a +kernel, do not run scripts/livepatch/klp-build, and do not load a +livepatch. Behaviour which only appears when the kernel applies a patch -- +the module loader refusing a relocation, late module patching ordering -- has +to be tested by booting, and is out of scope here. diff --git a/tools/objtool/Documentation/klp-write-tests.txt b/tools/objtool/Documentation/klp-write-tests.txt new file mode 100644 index 00000000000000..eb2cacaf572079 --- /dev/null +++ b/tools/objtool/Documentation/klp-write-tests.txt @@ -0,0 +1,266 @@ +.. SPDX-License-Identifier: GPL-2.0 + +===================================== +Writing a test for objtool's klp code +===================================== + +Instructions for adding a test to tools/objtool/tests/. Read +klp-test-design.txt first if you need to know how the harness works; this +document is the procedure and the rules. + +Two kinds of request bring you here: + +* *"write a test for commit "* -- a fix went in without one. +* *"port the test at , written against another harness"* -- a case + exists elsewhere and should live in tree. + +Both follow the same procedure. + + +The one rule that matters +========================= + +**A test is not finished until you have watched it fail.** + +Break the thing it guards -- revert the fix, or sabotage the exact line -- and +confirm the test fails. Then restore and confirm it passes. A test that has +never failed is not known to test anything, and this suite has produced +several that passed against deliberately broken code: + +* an alternatives fixture whose empty entry pointed at its own end label rather + than the neighbour's replacement, so the bug it guarded made no difference; +* a sympos fixture where symbol-table order and address order agreed, so + counting and reading the linked image gave the same answer; +* a string fixture using a named ``char[]``, which never reached the + contents-hashing path because that keys on ``SHF_STRINGS``; +* a static array the compiler proved constant, folded to zero, and emitted no + relocation for -- so the two builds were byte-identical. + +Every one looked correct. Say in the commit message how you verified, and if +you could not isolate the behaviour to a single line, **say that too** rather +than implying otherwise. + + +Procedure +========= + +1. **Read the fix.** What input reaches the broken line? What is observable + in the output object when it misbehaves -- a missing section, a relocation + naming the wrong symbol, an unchanged checksum, a rejected build? If + nothing is observable, stop and say so; see `When to give up`_. + +2. **Decide where it lives.** ``generic/`` unless the fixture needs + architecture-specific assembly or the behaviour is architecture-specific, + in which case ``x86/`` (or a new directory named for the architecture). + +3. **Write the fixture** in the same directory's ``fixtures/``. Reuse an + existing one if it already produces the shape; add a ``-D`` knob rather + than copying a fixture to change one line. + +4. **Write the test.** Assert the *premise* before the result -- see + `State the premise`_. + +5. **Verify by breaking the code.** Then restore. + +6. **Run the whole suite under both compilers**:: + + make -C tools/objtool tests + CC=clang make -C tools/objtool tests + +7. **Commit** the test and its fixture together, alone. One test per commit. + + +Writing the fixture +=================== + +Fixtures are freestanding C. No kernel headers -- write out the kernel +structure by hand if you need one, as the existing special-section fixtures do. + +Every fixture needs a ``.modinfo`` name, because klp diff reads the object's +module name from it:: + + static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +Use ``MODNAME`` if the test needs to vary it. Note that most fixtures hardcode +``vmlinux``: passing ``-DMODNAME`` to one that does silently does nothing and +the test quietly becomes a vmlinux test. + +The patched build is selected with ``-DPATCHED``. For a fixture with several +variants, gate each on both, so the original is always the baseline:: + + #if defined(PATCHED) && defined(WHICH_CALL) + r = callee_b(x); + #else + r = callee_a(x); + #endif + +and select one per build: ``build_pair foo.c -DWHICH_CALL``. Without the +``defined(PATCHED)`` the flag applies to *both* builds and nothing differs. + +Traps that have bitten before +----------------------------- + +* **The compiler optimises your fixture away.** A static never written is + proved constant, its reads folded, and no relocation emitted. Add a writer + the compiler cannot see through. +* **String literals versus named arrays.** The contents-hashing path keys on + ``SHF_STRINGS``, which the compiler sets on the mergeable section a *literal* + lands in, not on a ``char[]`` given a section of its own. +* **Per-function sections hide movement.** With the default + ``-ffunction-sections`` every function sits at offset 0 of its own section, + so nothing ever moves. A test about position needs + ``build_pair foo.c -fno-function-sections``. +* **Special sections need boundaries.** Either an entsize on the section or an + ``ANNOTATE_DATA_SPECIAL`` annotation, or klp diff reports "missing special + section entsize or annotations". Their targets need real (global) symbols, + or it reports "failed to convert reloc sym". +* **Prefer letting objtool generate what objtool generates.** + ``.static_call_sites``, ``.mcount_loc``, ``.ibt_endbr_seal`` and ORC come + from its check pass. Call ``run_objtool_check --mcount`` and let it build + them; a hand-written copy tests your reading of the format, not the format. + + Write one by hand only when the test needs a shape objtool will not produce, + and say so in the fixture. generic/fixtures/static_call.c does: objtool + emits the site but not the ``ANNOTATE_DATA_SPECIAL`` that describes its + boundaries -- those come from the kernel's macros -- so a fixture which has + to vary whether the annotation is there writes both itself. + + +Writing the test +================ + +Start from the shortest existing test, generic/test-basic.sh. + +State the premise +----------------- + +A test which asserts only on the output passes when the compiler never emitted +the construct in the first place, and reads as coverage it does not have. Say +what the input must contain:: + + assert_input_section __jump_table # the fixture must produce it -> fail + require_input_section .kcfi_traps # this compiler may not -> skip + +Prefer ``assert_*``. Reach for ``require_*`` only where absence genuinely +depends on compiler version or flags, and follow it with something +unconditional so the test can never be entirely vacuous. + +Where a test would otherwise duplicate a sibling, assert what makes it +different. ``test-jump-label-module-static-key`` checks that the key really is +reached through its section symbol -- without that it is a second copy of +``test-jump-label-module-key``. + +Assert both directions +---------------------- + +Check that the right thing happened *and* that the wrong thing did not. A klp +diff which clones everything is as wrong as one which clones nothing:: + + assert_patched changed + assert_not_patched untouched + +Skips +----- + +* ``gcc_only``/``clang_only`` -- a settled fact about the compiler. Declared, + so it reads as expected forever. +* ``probe_skip`` -- this toolchain did not produce the construct. Include what + to do about it if there is anything:: + + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_CC and THIN_LD to one" + +* A bare ``skip`` is **counted as a failure**. Never use it. + +Standing in for a kernel configuration +-------------------------------------- + +``FIXTURE_CFLAGS`` is what a fixture is built with. Since objtool reads no +``.config``, changing these flags is how a test covers a configuration without +building a kernel. Two ways: + +* trailing arguments to ``build_pair``/``build_one``, which win, and cover + anything expressible as a negation:: + + build_pair foo.c -fno-function-sections + +* otherwise assign ``FIXTURE_CFLAGS`` before building. + +Either way **say in a comment which kernel configuration the change stands in +for**. A flag with no stated motive is indistinguishable from a mistake. + + +What the test's comment must say +================================ + +The comment at the top is the test's justification. It should let a reader +decide, without archaeology, whether a skip or a failure matters. Include: + +* **what breaks** in the running kernel if the behaviour regresses -- not the + mechanism, the consequence; +* **why it is not caught otherwise**, which is usually "nothing fails at build + time"; +* **the fix commit** it guards, if there is one; +* **anything load-bearing about the fixture** that is not obvious, especially + anything you got wrong first. + +That last point is the one people skip. If the fixture has to be built without +per-function sections, or the static must not be named ``__warned``, or the key +must be file-local -- write it down, or the next person will simplify it away. + + +Porting a test from another harness +=================================== + +Read the original's *case*, not its code. The other harness probably builds a +real kernel module; here you write freestanding C. A transliteration will +usually test something else. + +* Work out which objtool behaviour the case exercises, then produce that shape + the cheapest way here. +* Verify by breaking the code, exactly as for a new test -- a port is not + correct because the original was. +* If the original names a fix commit, cite it. +* Credit the source in the commit message with the trailers the original + carried, followed by your own. + +Sometimes the port shows the case is already covered, and sometimes it shows +the case cannot be reproduced here. Both are results; report them rather than +committing something that passes vacuously. + + +When to give up +=============== + +Some behaviour cannot be reached from a compiled fixture. Say so, with what +you tried, instead of committing a test that passes either way. Examples that +were genuinely abandoned: + +* a memory leak -- needs valgrind, not an assertion on ELF; +* ``mkstemp`` with long paths, and other I/O edge cases; +* a NULL dereference reachable only through a debug path; +* changes made redundant by a fallback: removing the code changes no output + because something else already handles the case; +* a fix whose code has since been rewritten, so there is nothing left to + revert. + +Also stop when the behaviour depends on something outside the fixture's +control -- an ELF library's handling of empty sections, or a compiler version's +naming of anonymous data. A test which passes for you and skips for everyone +else is worse than none. + + +Checklist +========= + +Before committing: + +* the test fails with the code broken, and passes with it fixed +* the whole suite passes under **both** gcc and clang +* the premise is asserted, not assumed +* both directions are asserted where that applies +* no bare ``skip`` +* the fixture is in the same directory as the test +* the comment names the consequence, the fix commit, and anything load-bearing +* the commit contains one test and its fixtures, and nothing else +* the commit message says how you verified it From 9b11acaad1a63eddf4a9bf50dc9b90183bf037e1 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Wed, 16 Sep 2026 11:43:03 -0700 Subject: [PATCH 0610/2286] objtool: Keep failing test workdirs by default The klp test runner deletes each test's working directory when the test finishes. On failure that removes diff.log, out.o and the fixture objects before the developer can look at them, and the only hint was to re-run (something) with --keep: $ make -C tools/objtool tests ... not ok - test-special-section: expected section '.kcfi_traps' in output ... # pass:49 fail:1 static-skip:1 probe-skip:0 xfail:0 xpass:0 # re-run with --keep to hold on to what a failing test built Change the default behavior to save failing test working directories. This is friendlier to the drive-by tester. Provide the power-user with a KEEP Makefile variable that can optionally retain 'all' or 'none' of those directories. run-tests.sh --keep remains an alias for KEEP=all. Now that a run can be holding objects worth looking at, say where they are when it is cut short. A Ctrl-C left the run directory behind -- the tests clean up after themselves as they exit, but the two levels the runner made are its own to answer for, and nothing removed them. Take them away from a trap if the tests left them empty, and print the path if they did not: under the new default the test that was running keeps what it had built, and that is the run most worth being told about. The trap only ever rmdirs, so it cannot overrule what a test decided to keep; the same helper now serves the end of a normal run. Signed-off-by: Joe Lawrence Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-11-song@kernel.org --- .../objtool/Documentation/klp-test-design.txt | 14 ++- tools/objtool/Makefile | 2 +- tools/objtool/tests/lib.sh | 62 ++++++++++--- tools/objtool/tests/run-tests.sh | 92 +++++++++++++++---- 4 files changed, 136 insertions(+), 34 deletions(-) diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt index ec56bfbc2c930f..67d20bf0dc10f2 100644 --- a/tools/objtool/Documentation/klp-test-design.txt +++ b/tools/objtool/Documentation/klp-test-design.txt @@ -199,10 +199,11 @@ source layout:: generic/test-basic/{orig.o,patched.o,out.o,Module.symvers,...} x86/test-kcfi/... -``--keep`` leaves it and reports the one path. Otherwise each test removes its -own directory and the runner ``rmdir``s the run's -- which fails if anything -was left behind, so a test which dies without cleaning up is reported rather -than silently leaking. +By default (``KEEP=failed``) only failing tests keep their directories; the +runner reports where they are. ``KEEP=all`` keeps every test's directory; +``KEEP=none`` removes them all. The runner ``rmdir``s the run directory when +it is empty -- which fails if anything was left behind unexpectedly, so a test +which dies without cleaning up is reported rather than silently leaking. Running @@ -216,7 +217,10 @@ Running CC=clang make -C tools/objtool tests # the other toolchain LLVM=1 make -C tools/objtool tests # and its binutils too - tools/objtool/tests/run-tests.sh --keep basic # one test, keep its objects + make -C tools/objtool tests KEEP=all # keep every test's workdir + make -C tools/objtool tests KEEP=none # remove all workdirs + + tools/objtool/tests/run-tests.sh basic # one test; failures kept by default A run covers one compiler and one architecture; CI runs the combinations. diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index e9a4cc8e787f57..d3db9663ae1bc2 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -155,7 +155,7 @@ mrproper: clean tests: $(OBJTOOL) $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ - $(srctree)/tools/objtool/tests/run-tests.sh + KEEP=$(KEEP) $(srctree)/tools/objtool/tests/run-tests.sh FORCE: diff --git a/tools/objtool/tests/lib.sh b/tools/objtool/tests/lib.sh index 294b99ced3579c..4da168d0ddca31 100644 --- a/tools/objtool/tests/lib.sh +++ b/tools/objtool/tests/lib.sh @@ -163,8 +163,8 @@ workdir= orig_obj=orig.o patched_obj=patched.o -pass() { echo "ok - $test_name${1:+: $1}"; exit 0; } -fail() { echo "not ok - $test_name: $1"; exit 1; } +pass() { KLP_TEST_REPORTED=1; echo "ok - $test_name${1:+: $1}"; exit 0; } +fail() { KLP_TEST_FAILED=1; echo "not ok - $test_name: $*"; exit 1; } # Two kinds of skip, and the runner tells them apart. # @@ -177,8 +177,19 @@ fail() { echo "not ok - $test_name: $1"; exit 1; } # # A bare skip() is neither, and the runner counts it as a failure: a test which # gives up for a reason it never declared is a hole, not an outcome. -declared_skip() { echo "ok - $test_name # SKIP (declared) $*"; exit 0; } -probe_skip() { echo "ok - $test_name # SKIP (probe) $*"; exit 0; } +declared_skip() +{ + KLP_TEST_REPORTED=1 + echo "ok - $test_name # SKIP (declared) $*" + exit 0 +} + +probe_skip() +{ + KLP_TEST_REPORTED=1 + echo "ok - $test_name # SKIP (probe) $*" + exit 0 +} skip() { echo "ok - $test_name # SKIP $*"; exit 0; } # TAP directives. A test which is known to fail reports it rather than being @@ -187,26 +198,55 @@ skip() { echo "ok - $test_name # SKIP $*"; exit 0; } # which is the point. xfail() { + KLP_TEST_REPORTED=1 echo "not ok - $test_name${1:+: $1} # TODO known failure" exit 0 } xpass() { + KLP_TEST_FAILED=1 echo "ok - $test_name${1:+: $1} # TODO expected failure, but passed" exit 1 } +# cleanup [exit] +# +# Called with "exit" from the trap, when the test is over and what it built may +# be worth keeping. Called bare by a test which has finished with one segment +# and is about to setup() another: that one is done with, whatever the outcome +# of the segments still to come, so it goes. cleanup() { [ -n "$workdir" ] || return 0 - if [ -n "${KLP_TEST_KEEP:-}" ]; then - [ -n "${KLP_TEST_WORKDIR:-}" ] || echo "# kept $workdir" - return 0 - fi - - rm -rf "$workdir" + # run-tests.sh exports KLP_TEST_KEEP, having validated it; a test run on + # its own reads KEEP itself, so the same setting means the same thing + # either way. + case "${KLP_TEST_KEEP:-${KEEP:-failed}}" in + all) return 0 ;; + none) rm -rf "$workdir" ;; + failed|*) + [ "${1:-}" = exit ] || { + rm -rf "$workdir" + return 0 + } + # Keep what the runner is going to point at. It counts as a + # failure anything which did not report an expected outcome -- + # including a test which died before printing one, and an + # undeclared skip -- and none of those set KLP_TEST_FAILED, so + # the question to ask is whether a result was reported at all. + # An exit status cannot answer it: a test killed by a signal + # runs this trap with the status of whatever ran last. + [ -n "${KLP_TEST_REPORTED:-}" ] && [ -z "${KLP_TEST_FAILED:-}" ] && { + rm -rf "$workdir" + return 0 + } + # run on its own there is no runner to say where it was kept + [ -n "${KLP_TEST_WORKDIR:-}" ] || + echo "# kept $workdir" + ;; + esac } # setup [exported symbol...] @@ -218,7 +258,7 @@ setup() else workdir="$(mktemp -d)" || fail "mktemp failed" fi - trap cleanup EXIT + trap 'cleanup exit' EXIT export_syms "$@" } diff --git a/tools/objtool/tests/run-tests.sh b/tools/objtool/tests/run-tests.sh index e6f1ac1b5d0da9..c0762f532d2a3c 100755 --- a/tools/objtool/tests/run-tests.sh +++ b/tools/objtool/tests/run-tests.sh @@ -31,13 +31,15 @@ A test may be named with or without its "test-" prefix and ".sh" suffix, and is looked for in both directories. Options: - -k, --keep do not delete each test's working directory; print its path, - so the objects a failing test built can be looked at + -k, --keep same as KEEP=all (see below) Environment: OBJTOOL objtool binary to test (default ../objtool) CC compiler used to build fixtures (default gcc) ARCH architecture the tests are for (default: uname -m) + KEEP failed keep only failing tests (default) + all keep every test's working directory + none remove all working directories A test which needs something of its own says so in its skip message. EOF @@ -46,16 +48,28 @@ EOF cd "$(dirname "$0")" || exit 1 +keep_from_args= while [ $# -gt 0 ]; do case "$1" in -h|--help) usage ;; - -k|--keep) export KLP_TEST_KEEP=1; shift ;; + -k|--keep) keep_from_args=all; shift ;; --) shift; break ;; -*) echo "unknown option: $1" >&2; usage 1 ;; *) break ;; esac done +KLP_TEST_KEEP="${KEEP:-failed}" +[ -n "$keep_from_args" ] && KLP_TEST_KEEP="$keep_from_args" +case "$KLP_TEST_KEEP" in +all|none|failed) ;; +*) + echo "invalid KEEP=$KLP_TEST_KEEP (want failed, all, or none)" >&2 + exit 1 + ;; +esac +export KLP_TEST_KEEP + echo "TAP version 13" # Sourcing the harness runs its preflight, which decides which architecture @@ -109,9 +123,33 @@ fi rundir="$(mktemp -d "${TMPDIR:-/tmp}/klp-tests.XXXXXXXX")" || { echo "Bail out! cannot create a working directory" >&2; exit 1; } +# The run's own two levels: each test's directory, and the one per source +# directory holding them. Take them away if the tests left them empty, and +# say so if they did not. Either rmdir may fail -- the glob stays unexpanded +# when nothing was created -- so ask the directory itself rather than trusting +# the status. Never rm -rf: what to keep is the tests' decision, made in +# cleanup() as each one exits, and this must not overrule it. +reap_rundir() +{ + rmdir "$rundir"/*/ 2>/dev/null + rmdir "$rundir" 2>/dev/null + [ -d "$rundir" ] +} + +# An interrupted run has the same directory to answer for, and the tests it +# never reached will not clean up on their way out. The one it was running +# has, and under the default it kept what it had built, so say where. +interrupted() +{ + reap_rundir && echo "# interrupted; what was built is in $rundir" + exit 130 +} +trap interrupted INT TERM HUP + echo "1..${#tests[@]}" pass=0 fail=0 static_skip=0 probe_skip=0 xfail=0 xpass=0 +failed_dirs=() for t in "${tests[@]}"; do out="$(KLP_TEST_WORKDIR="$rundir/${t%.sh}" ./"$t" 2>&1)" @@ -147,16 +185,16 @@ for t in "${tests[@]}"; do rest="$rest${rest:+$'\n'}was: $result" result="not ok - $(basename "$t" .sh): undeclared skip" result="$result (use gcc_only/clang_only or require_input_*)" - fail=$((fail + 1)) ;; + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; "not ok"*"# TODO"*) xfail=$((xfail + 1)) ;; - "ok"*"# TODO"*) xpass=$((xpass + 1)) ;; - "not ok"*) fail=$((fail + 1)) ;; + "ok"*"# TODO"*) xpass=$((xpass + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; + "not ok"*) fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; "ok"*) pass=$((pass + 1)) ;; *) # No result line at all: the test died before reporting. rest="$rest${rest:+$'\n'}exited $rc without a result line" result="not ok - $(basename "$t" .sh): no TAP result" - fail=$((fail + 1)) ;; + fail=$((fail + 1)); failed_dirs+=( "$rundir/${t%.sh}" ) ;; esac echo "$result" @@ -167,15 +205,35 @@ done echo "# pass:$pass fail:$fail static-skip:$static_skip" \ "probe-skip:$probe_skip xfail:$xfail xpass:$xpass" -# A failure is the one time the objects matter, and by default they are -# already gone. Say so then rather than in the usage text nobody reads while -# something is broken. -if [ -n "${KLP_TEST_KEEP:-}" ]; then - echo "# working directories kept in $rundir -- inspect, then rm -rf it" -elif ! rmdir "$rundir"/*/ "$rundir" 2>/dev/null; then - echo "# $rundir was not empty; a test did not clean up after itself" -elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then - echo "# re-run with --keep to hold on to what a failing test built" -fi +case "$KLP_TEST_KEEP" in +all) + echo "# keep=all: workdirs kept in $rundir" + echo "# inspect: diff.log, readelf -S out.o under each test-* subdirectory" + echo "# cleanup: rm -rf $rundir" + ;; +failed) + if [ "${#failed_dirs[@]}" -gt 0 ]; then + echo "# keep=failed: ${#failed_dirs[@]} failing test(s) kept under $rundir:" + for d in "${failed_dirs[@]}"; do + echo "# ${d#"$rundir"/}/" + done + echo "# inspect: diff.log readelf -S out.o" + echo "# one test: $PWD/run-tests.sh " + echo "# cleanup: rm -rf $rundir" + elif reap_rundir; then + echo "# $rundir was not empty;" \ + "a test did not clean up after itself" + fi + ;; +none) + if reap_rundir; then + echo "# $rundir was not empty;" \ + "a test did not clean up after itself" + elif [ "$fail" != 0 ] || [ "$xpass" != 0 ]; then + echo "# keep=none: artifacts were removed" \ + "(re-run with KEEP=failed or KEEP=all)" + fi + ;; +esac [ "$fail" = 0 ] && [ "$xpass" = 0 ] From 913bf39ff461bc65cfc4e1e05c3d738ffc6ffc75 Mon Sep 17 00:00:00 2001 From: Joe Lawrence Date: Wed, 16 Sep 2026 11:43:04 -0700 Subject: [PATCH 0611/2286] objtool: Forward toolchain variables to the klp test runner The tests target already passes ARCH and CROSS_COMPILE into run-tests.sh. lib.sh uses those to pick a default compiler and binutils: CC=${CC:-${CROSS_COMPILE}gcc} LD=${LD:-${CROSS_COMPILE}ld} READELF=${READELF:-${CROSS_COMPILE}readelf} OBJCOPY=${OBJCOPY:-${CROSS_COMPILE}objcopy} Those defaults only apply when the variables reach the shell that sources lib.sh. The recipe passed OBJTOOL, ARCH and CROSS_COMPILE explicitly, but not CC or the binutils. Makefile-assigned values (e.g. from LLVM=1) are not visible in the recipe environment unless forwarded: $ LLVM=1 make -C tools/objtool tests ... # preflight # compiler gcc (GCC) ... Pass CC, LD, READELF and OBJCOPY the same way as ARCH and CROSS_COMPILE, so fixtures are built and inspected with the toolchain make selected: $ LLVM=1 make -C tools/objtool tests ... # preflight # compiler clang version 21.1.8 ... Signed-off-by: Joe Lawrence Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-12-song@kernel.org --- tools/objtool/Documentation/klp-test-design.txt | 12 +++++++++++- tools/objtool/Makefile | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/objtool/Documentation/klp-test-design.txt b/tools/objtool/Documentation/klp-test-design.txt index 67d20bf0dc10f2..2082c277197f4e 100644 --- a/tools/objtool/Documentation/klp-test-design.txt +++ b/tools/objtool/Documentation/klp-test-design.txt @@ -243,7 +243,17 @@ setting a cross compiler cannot produce one the host is unable to run. ARCH selects both the objtool target and the directory of tests to run. clang needs telling, since it defaults to the host triple however it is -invoked. The fixtures include no kernel headers, so no sysroot is needed: +invoked. LLVM=1 with CROSS_COMPILE does that for you -- the --target= it +derives is forwarded to the tests -- and the fixtures include no kernel +headers, so no sysroot is needed: + +:: + + ARCH=x86_64 CROSS_COMPILE=x86_64-linux-gnu- LLVM=1 \ + make -C tools/objtool tests + +Naming the compiler by hand works too, and is what to reach for when the +target triple is not the one CROSS_COMPILE implies: :: diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index d3db9663ae1bc2..db99c4759b2c3b 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -153,8 +153,17 @@ clean: $(LIBSUBCMD)-clean mrproper: clean $(call QUIET_CLEAN, objtool) $(RM) $(OBJTOOL) +# The kernel's own Makefile sets OBJCOPY, to llvm-objcopy under LLVM and to +# $(CROSS_COMPILE)objcopy otherwise; tools/scripts/Makefile.include names only +# LLVM_OBJCOPY and leaves OBJCOPY unset. Forwarding it unset would hand the +# tests an empty string, and they would fall back to GNU objcopy even when +# asked for an LLVM toolchain. +OBJCOPY ?= $(if $(LLVM),$(LLVM_OBJCOPY),$(CROSS_COMPILE)objcopy) + tests: $(OBJTOOL) $(Q)OBJTOOL=$(abspath $(OBJTOOL)) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) \ + CC='$(CC) $(CLANG_CROSS_FLAGS)' LD='$(LD)' \ + READELF='$(READELF)' OBJCOPY='$(OBJCOPY)' \ KEEP=$(KEEP) $(srctree)/tools/objtool/tests/run-tests.sh FORCE: From b229562c2f44cdec4ce4b8b78428514bccff9dc0 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:05 -0700 Subject: [PATCH 0612/2286] objtool/klp: Add test for rejecting changed data Livepatching replaces functions. Nothing can swap a variable which live code already refers to, so a patch which changes one has to be refused rather than applied with the old value left in place. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-13-song@kernel.org --- .../tests/generic/fixtures/changed_data.c | 16 ++++++++++++++++ .../objtool/tests/generic/test-changed-data.sh | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/changed_data.c create mode 100755 tools/objtool/tests/generic/test-changed-data.sh diff --git a/tools/objtool/tests/generic/fixtures/changed_data.c b/tools/objtool/tests/generic/fixtures/changed_data.c new file mode 100644 index 00000000000000..b52461835444ff --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/changed_data.c @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data whose value differs between the two builds. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +int klp_test_data = 2; +#else +int klp_test_data = 1; +#endif + +int target(int x) +{ + return x + klp_test_data; +} diff --git a/tools/objtool/tests/generic/test-changed-data.sh b/tools/objtool/tests/generic/test-changed-data.sh new file mode 100755 index 00000000000000..c5c7381bb4091b --- /dev/null +++ b/tools/objtool/tests/generic/test-changed-data.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Livepatching replaces functions, not data. A changed data symbol must be +# rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair changed_data.c +run_diff 255 + +diff_log | grep -q 'changed data: klp_test_data' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "changed data symbol rejected" From f4b8bfb1285992a6ab6f3d7cd25186d64f0773ee Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:06 -0700 Subject: [PATCH 0613/2286] objtool/klp: Add test for newly introduced data Adding data differs from changing it: nothing in the running kernel refers to a new variable, so it is safe and has to travel into the livepatch with the function using it. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-14-song@kernel.org --- .../objtool/tests/generic/fixtures/new_data.c | 23 ++++++++++++++++ tools/objtool/tests/generic/test-new-data.sh | 27 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_data.c create mode 100755 tools/objtool/tests/generic/test-new-data.sh diff --git a/tools/objtool/tests/generic/fixtures/new_data.c b/tools/objtool/tests/generic/fixtures/new_data.c new file mode 100644 index 00000000000000..ac364622f12a99 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_data.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Data introduced by the patch. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +/* + * Not an arithmetic progression: { 1, 2, 3, 4 } indexed by x & 3 is something + * a compiler can compute instead of load, and then target() has no reference + * to the array and there is nothing for klp diff to carry. + */ +static const int klp_new_data[4] __attribute__((used)) = { 7, 3, 11, 5 }; +#endif + +int target(int x) +{ +#ifdef PATCHED + return x + klp_new_data[x & 3]; +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-data.sh b/tools/objtool/tests/generic/test-new-data.sh new file mode 100755 index 00000000000000..acb68ff19c998f --- /dev/null +++ b/tools/objtool/tests/generic/test-new-data.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Data added by the patch has no counterpart in the running kernel and must be +# carried into the livepatch. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_data.c + +# State the premise on both sides. The array is new in the patched build and +# absent from the original; if the compiler folded it into the code instead of +# emitting it, the assertion below would fail without saying why. +has_input_symbol "$orig_obj" klp_new_data && + fail "fixture put klp_new_data in the original; nothing new to carry" +has_input_symbol "$patched_obj" klp_new_data || + fail "compiler did not emit klp_new_data; the fixture tests nothing" +in_relocs "$patched_obj" | grep -q 'klp_new_data' || + fail "target() does not reference klp_new_data; the fixture tests nothing" + +run_diff + +assert_patched target +assert_symbol klp_new_data + +pass "new data carried into the patch" From 960c3e352bac749c35041d683f32bdde467a2960 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:07 -0700 Subject: [PATCH 0614/2286] objtool/klp: Add test for newly introduced functions A function added by the patch has no original to correlate against, and still has to be carried into the livepatch or the changed caller ends up referencing something which does not exist. The helper is noinline so the case survives the optimiser; otherwise the compiler folds it into its only caller and the test covers nothing. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-15-song@kernel.org --- .../tests/generic/fixtures/new_function.c | 21 +++++++++++++++++++ .../tests/generic/test-new-function.sh | 16 ++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_function.c create mode 100755 tools/objtool/tests/generic/test-new-function.sh diff --git a/tools/objtool/tests/generic/fixtures/new_function.c b/tools/objtool/tests/generic/fixtures/new_function.c new file mode 100644 index 00000000000000..e7886eaaff3efb --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_function.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Function introduced by the patch. noinline keeps it from being folded. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +static __attribute__((noinline)) int klp_new_helper(int x) +{ + return x * 7; +} +#endif + +int target(int x) +{ +#ifdef PATCHED + return klp_new_helper(x); +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-function.sh b/tools/objtool/tests/generic/test-new-function.sh new file mode 100755 index 00000000000000..b0b7f644311075 --- /dev/null +++ b/tools/objtool/tests/generic/test-new-function.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A function added by the patch has no original to correlate against and must +# still be carried into the livepatch. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_function.c +run_diff + +assert_patched target +assert_symbol klp_new_helper + +pass "new function carried into the patch with its caller" From fbb79ff9a6638aeb5b7a5f7db25afd033e6edd96 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:08 -0700 Subject: [PATCH 0615/2286] objtool/klp: Add test for static local correlation A static local must be correlated with its original rather than duplicated. The replacement has to reach the existing variable through a klp relocation; a fresh definition would discard whatever state the running kernel accumulated. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-16-song@kernel.org --- .../tests/generic/fixtures/static_local.c | 17 +++++++++++++ .../tests/generic/test-static-local.sh | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_local.c create mode 100755 tools/objtool/tests/generic/test-static-local.sh diff --git a/tools/objtool/tests/generic/fixtures/static_local.c b/tools/objtool/tests/generic/fixtures/static_local.c new file mode 100644 index 00000000000000..f2f025d00a3922 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_local.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Static local in a patched function. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + static int counter; + + counter += 1; +#ifdef PATCHED + return x + counter + 1; +#else + return x + counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-local.sh b/tools/objtool/tests/generic/test-static-local.sh new file mode 100755 index 00000000000000..d57efa64dfd92a --- /dev/null +++ b/tools/objtool/tests/generic/test-static-local.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static local must be correlated with the original, not duplicated: a second +# copy would discard the state the running kernel accumulated. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +in_symbols orig.o | grep -q 'counter' || + probe_skip "compiler emitted no distinct static local symbol" + +run_diff +assert_patched target + +out_symbols | grep -q '\.klp\.sym\..*\.counter' || + fail "static local not referenced through a klp relocation" + +out_symbols | grep 'counter' | grep -qvE 'UND|\.klp\.(sym|tombstone)' && + fail "static local was given a fresh definition" + +pass "static local correlated rather than duplicated" From 76e7630d90d0d5ff402e06607662b1f9e34eff6c Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:09 -0700 Subject: [PATCH 0616/2286] objtool/klp: Add test for cold function halves The compiler splits unlikely code into a separate foo.cold symbol. Both halves are the same function and both belong in the livepatch: carrying only the hot part leaves the cold path branching into unpatched code. GCC needs -freorder-blocks-and-partition to split reliably. The flag is probed rather than assumed, and the test skips when the compiler declines to split at all. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-17-song@kernel.org --- .../tests/generic/fixtures/cold_function.c | 21 ++++++++++ .../tests/generic/test-cold-function.sh | 39 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/cold_function.c create mode 100755 tools/objtool/tests/generic/test-cold-function.sh diff --git a/tools/objtool/tests/generic/fixtures/cold_function.c b/tools/objtool/tests/generic/fixtures/cold_function.c new file mode 100644 index 00000000000000..f6d410983ce238 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/cold_function.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Function the compiler may split into a hot part and a foo.cold part. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static void __attribute__((cold, noinline)) slow_path(int x) +{ + __asm__ volatile("" :: "r"(x)); +} + +int target(int x) +{ + if (__builtin_expect(x < 0, 0)) + slow_path(x); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-cold-function.sh b/tools/objtool/tests/generic/test-cold-function.sh new file mode 100755 index 00000000000000..a02652fe534139 --- /dev/null +++ b/tools/objtool/tests/generic/test-cold-function.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Both halves of a split function belong to the patch; carrying only the hot +# part leaves the cold path branching into unpatched code. + +. "$(dirname "$0")/../lib.sh" + +# Clang does not split functions into a cold part at all, so there is nothing +# for this test to look at there. A given gcc may or may not split, which is a +# version property rather than a compiler choice -- that stays a probe below. +gcc_only "clang does not split functions into a cold part" + +setup + +split_flag=-freorder-blocks-and-partition +cc_supports "$split_flag" || split_flag= + +build_pair cold_function.c $split_flag + +# Find what the compiler called the cold half -- target.cold, target.cold.0, +# depending on version -- and name it exactly from here on. +cold_sym="$(in_symbols orig.o | awk '$NF ~ /^target\.cold/ { print $NF; exit }')" +[ -n "$cold_sym" ] || + probe_skip "compiler did not split the function into a cold part" + +run_diff + +assert_patched target +# Match the name field exactly, and require it to be defined. Had the cold +# half been left behind, the branch to it would appear as an undefined +# .klp.sym.vmlinux.target.cold,0 -- a different name, which happens to contain +# this one. Asking about a column instead of the name would not tell them +# apart: readelf prints SHN_LIVEPATCH as "OS [0xff20]" and llvm-readelf as +# "OS[0xff20]", so the fields either side of the name shift between the two. +out_symbols | awk -v n="$cold_sym" '$NF == n && $(NF - 1) != "UND"' | grep -q . || + fail "cold half ($cold_sym) was not carried into the patch" + +pass "cold half carried into the patch with its parent" From 996edd14fb2003461295c8c48e9df7ef373bc492 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:10 -0700 Subject: [PATCH 0617/2286] objtool/klp: Add test for special section extraction create_fake_symbols() gives each special section entry a symbol so entries can be extracted individually. Entries with ANNOTATE_DATA_SPECIAL are handled first; the rest have their boundaries derived from the entry or relocation size. The second pass has to key off whether the first one created symbols, not off whether the section already has something at offset 0. Clang puts an assembler-local label at the start of .kcfi_traps, and treating that as already handled means nothing is extracted: klp diff still reports the changed function and succeeds, but the special section is missing from the module. The fixture reproduces the shape without needing CFI or x86. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-18-song@kernel.org --- .../tests/generic/fixtures/special_section.c | 24 +++++++++++++++++++ .../tests/generic/test-special-section.sh | 20 ++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/special_section.c create mode 100755 tools/objtool/tests/generic/test-special-section.sh diff --git a/tools/objtool/tests/generic/fixtures/special_section.c b/tools/objtool/tests/generic/fixtures/special_section.c new file mode 100644 index 00000000000000..d28c5541e33706 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/special_section.c @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Special section entry with no ANNOTATE_DATA_SPECIAL annotation and a local + * label at offset 0, the shape Clang produces for .kcfi_traps. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + asm volatile( + "1:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + "trap_marker:\n\t" + ".long 1b - .\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-special-section.sh b/tools/objtool/tests/generic/test-special-section.sh new file mode 100755 index 00000000000000..b6a9139c0062e1 --- /dev/null +++ b/tools/objtool/tests/generic/test-special-section.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A .kcfi_traps entry belonging to a patched function must be extracted even +# without ANNOTATE_DATA_SPECIAL and with a local label already at offset 0. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair special_section.c + +in_symbols orig.o | grep -q 'trap_marker' || + probe_skip "fixture produced no .kcfi_traps on this arch" + +run_diff + +assert_patched target +assert_section ".kcfi_traps" + +pass ".kcfi_traps extracted despite a local label at offset 0" From 229ad17366de138ae8c4492d0c965d93bc848f4c Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:11 -0700 Subject: [PATCH 0618/2286] objtool/klp: Add test for selective special section extraction Two functions contribute entries to one special section but only one is patched. Cloning the neighbouring entry drags in whatever it points at, which is how a livepatch ends up holding relocations against unrelated code. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-19-song@kernel.org --- .../generic/fixtures/special_section_shared.c | 31 +++++++++++++++++++ .../generic/test-special-section-shared.sh | 26 ++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/special_section_shared.c create mode 100755 tools/objtool/tests/generic/test-special-section-shared.sh diff --git a/tools/objtool/tests/generic/fixtures/special_section_shared.c b/tools/objtool/tests/generic/fixtures/special_section_shared.c new file mode 100644 index 00000000000000..f54e24f862c60e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/special_section_shared.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Two functions contribute to one special section; only one is patched. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int other(int x) +{ + asm volatile( + "2:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + ".long 2b - .\n\t" + ".popsection\n\t"); + return x * 5; +} + +int target(int x) +{ + asm volatile( + "1:\n\t" + ".pushsection .kcfi_traps, \"a\"\n\t" + ".balign 4\n\t" + ".long 1b - .\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-special-section-shared.sh b/tools/objtool/tests/generic/test-special-section-shared.sh new file mode 100755 index 00000000000000..05c6110b6aef0d --- /dev/null +++ b/tools/objtool/tests/generic/test-special-section-shared.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Only the patched function's special section entry may be extracted. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair special_section_shared.c + +has_input_section orig.o .kcfi_traps || + probe_skip "fixture produced no .kcfi_traps on this arch" + +run_diff + +assert_patched target +assert_not_patched other +assert_section ".kcfi_traps" + +entries="$(out_relocs | awk '/rela\.kcfi_traps/,/^$/' | grep -c 'target')" +[ "$entries" = 1 ] || fail "expected one .kcfi_traps entry, found $entries" + +out_relocs | awk '/rela\.kcfi_traps/,/^$/' | grep -q 'other' && + fail "the untouched function's entry was dragged in" + +pass "only the patched function's entry extracted" From 729bf5310766614efed12b87e8b59a63d2657cb7 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:12 -0700 Subject: [PATCH 0619/2286] objtool/klp: Add test for jump table key relocations A cloned __jump_table entry must keep a relocation in its key slot. objtool parses the table again after klp diff to convert the static branch's scaffold instruction, and an empty slot leaves it unable to do so. The scaffold then stays as emitted and the module trips BUG() in __jump_label_patch() once the key's state differs from its compile-time default. Check both halves for a vmlinux-owned key: unexported gives a tombstone plus a klp relocation, exported gives an ordinary relocation and no klp machinery. The fixture defines the key as an STT_OBJECT, which is what the kernel emits. validate_special_section_klp_reloc() ignores anything else, so a fixture using an undefined extern would skip the interesting paths. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-20-song@kernel.org --- .../tests/generic/fixtures/jump_label.c | 76 +++++++++++++++++++ .../tests/generic/test-jump-label-key.sh | 44 +++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/jump_label.c create mode 100755 tools/objtool/tests/generic/test-jump-label-key.sh diff --git a/tools/objtool/tests/generic/fixtures/jump_label.c b/tools/objtool/tests/generic/fixtures/jump_label.c new file mode 100644 index 00000000000000..ecd3c06912af53 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/jump_label.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static branch in a patched function. The jump table entry is written out by + * hand, mirroring JUMP_TABLE_ENTRY(), so the fixture builds without kernel + * headers. The key is an STT_OBJECT; anything else is ignored by + * validate_special_section_klp_reloc(). + * + * MODNAME selects whether the key is taken to belong to vmlinux or a module. + * + * NEW_KEY puts the whole static branch behind PATCHED, so the patch introduces + * one where the original had none -- a different question from patching code + * that already has a key, because the __jump_table entry itself is new. + * + * KEY_NAME renames the key. Two names are special to + * validate_special_section_klp_reloc(): a __tracepoint_* key and the + * __UNIQUE_ID_ddebug_* one pr_debug() generates are both unsupported in a + * module, but are disabled with a warning rather than rejected, because the + * kernel is full of them and refusing outright would make ordinary functions + * unpatchable. + * + * STATIC_KEY makes the key file-local. That changes the shape of the + * relocation rather than the meaning of the code: a reference to a static lands + * on the section symbol plus an addend, so the key has to be resolved from the + * section before it can be recognised as a key at all. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +#ifndef KEY_NAME +#define KEY_NAME klp_test_key +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +#ifdef STATIC_KEY +static long KEY_NAME; +#else +long KEY_NAME; +#endif + +int target(int x) +{ + int r = x; + +#if defined(NEW_KEY) && !defined(PATCHED) + /* The original has no static branch at all. */ + return r + 1; +#else + asm goto( + "1: nop\n\t" + ".pushsection __jump_table, \"aw\"\n\t" + ".balign 8\n\t" + "912:\n\t" + ".pushsection .discard.annotate_data, \"M\", @progbits, 8\n\t" + ".long 912b - ., 1\n\t" + ".popsection\n\t" + ".long 1b - ., %l[l_yes] - .\n\t" + ".quad %c0 - .\n\t" + ".popsection\n\t" + : : "i" (&KEY_NAME) : : l_yes); + + r += 1; + goto out; +l_yes: + r += 2; +out: +#endif +#ifdef PATCHED + return r + 100; +#else + return r; +#endif +} diff --git a/tools/objtool/tests/generic/test-jump-label-key.sh b/tools/objtool/tests/generic/test-jump-label-key.sh new file mode 100755 index 00000000000000..142f94bd38ecf2 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-key.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A cloned __jump_table entry must keep a relocation in its key slot, whether +# the key needs a klp relocation or not. + +. "$(dirname "$0")/../lib.sh" + +key=klp_test_key + +setup +build_pair jump_label.c + +has_input_section orig.o __jump_table || + probe_skip "fixture produced no __jump_table on this arch" + +key_slot_relocs() +{ + out_relocs | awk '/rela__jump_table/,/^$/' | grep -c "^0*8[[:space:]]" +} + +# Unexported: klp relocation, key slot holds a tombstone. +export_syms +run_diff + +[ "$(key_slot_relocs)" = 1 ] || + fail "unexported key: key slot has no relocation" +out_relocs | awk '/rela__jump_table/,/^$/' | grep -q "\.klp\.tombstone\.$key" || + fail "unexported key: expected a .klp.tombstone.$key relocation" +out_symbols | grep -q "\.klp\.sym\..*\.$key," || + fail "unexported key: no .klp.sym reference for the real relocation" + +# Exported: ordinary relocation, no klp machinery. +export_syms "$key" +run_diff + +[ "$(key_slot_relocs)" = 1 ] || + fail "exported key: key slot has no relocation" +out_relocs | awk '/rela__jump_table/,/^$/' | grep -q "[[:space:]]$key[[:space:]]*+" || + fail "exported key: expected a direct relocation to $key" +out_symbols | grep -q '\.klp\.tombstone\.' && + fail "exported key: tombstone emitted for an exported symbol" + +pass "key slot populated for exported and unexported vmlinux keys" From bd5e5fc7d6456e0669f47a3e10150ad05ae9d249 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:13 -0700 Subject: [PATCH 0620/2286] objtool/klp: Add test for rejecting module-owned static branch keys A static branch key belonging to a module cannot be reached with a klp relocation: the patch may be applied after that module's static branch init has run, which corrupts the code. Emitting one anyway produces a module which loads and then BUG()s the first time the key changes state, so klp diff has to refuse it. The fixture differs from the accepted case only in the module name recorded in .modinfo. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-21-song@kernel.org --- .../generic/test-jump-label-module-key.sh | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-module-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-module-key.sh b/tools/objtool/tests/generic/test-jump-label-module-key.sh new file mode 100755 index 00000000000000..7c3f82cdd3c1a1 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-module-key.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static branch key owned by a module cannot be reached with a klp +# relocation and must be rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair jump_label.c -DMODNAME='"klp_testmod"' + +has_input_section orig.o __jump_table || + probe_skip "fixture produced no __jump_table on this arch" + +run_diff 255 + +diff_log | grep -q 'unsupported static branch key klp_test_key' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "module-owned static branch key rejected" From 93d298f097a41cf1cd0a167abd664f2e90020a44 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:14 -0700 Subject: [PATCH 0621/2286] objtool/klp: Add test for rejecting module-owned static call keys Static calls carry the same constraint as static branches. Check that a vmlinux-owned key is accepted and a module-owned one is refused. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-22-song@kernel.org --- .../tests/generic/fixtures/static_call.c | 59 +++++++++++++++++++ .../generic/test-static-call-module-key.sh | 36 +++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_call.c create mode 100755 tools/objtool/tests/generic/test-static-call-module-key.sh diff --git a/tools/objtool/tests/generic/fixtures/static_call.c b/tools/objtool/tests/generic/fixtures/static_call.c new file mode 100644 index 00000000000000..4a0c4c25321e57 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_call.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static call site in a patched function, laid out by hand as for + * jump_label.c. MODNAME selects whether the key belongs to vmlinux or a + * module. + * + * objtool's check pass would emit the site, and klp-write-tests.txt says to + * let it. Not here: it does not emit the ANNOTATE_DATA_SPECIAL describing + * the entry boundaries -- in the kernel that comes from the static_call + * macros -- and NO_ANNOTATE below has to be able to take it away. A fixture + * which varies the annotation has to write the entry that goes with it. + * + * NO_ANNOTATE drops the ANNOTATE_DATA_SPECIAL block from the patched build, + * leaving .static_call_sites with no annotation to describe its entry + * boundaries. The section carries no entsize either, so klp diff has to fall + * back on the annotations it can still see -- and when the patched object is + * the only one that lost them, the two sides disagree about how the section is + * divided up. + * + * NEW_CALL puts the call site behind PATCHED, so the patch introduces one + * where the original had none. The .static_call_sites entry is then new, with + * nothing in the original to correlate it against. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +long __SCK__klp_test_call; + +int target(int x) +{ +#if defined(NEW_CALL) && !defined(PATCHED) + /* The original has no static call at all. */ + return x + 1; +#else + __asm__ volatile( + "1: nop\n\t" + ".pushsection .static_call_sites, \"aw\"\n\t" + ".balign 8\n\t" + "912:\n\t" +#if !(defined(PATCHED) && defined(NO_ANNOTATE)) + ".pushsection .discard.annotate_data, \"M\", @progbits, 8\n\t" + ".long 912b - ., 1\n\t" + ".popsection\n\t" +#endif + ".long 1b - ., %c0 - .\n\t" + ".popsection\n\t" + :: "i" (&__SCK__klp_test_call)); +#endif +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-call-module-key.sh b/tools/objtool/tests/generic/test-static-call-module-key.sh new file mode 100755 index 00000000000000..260c4af72e051c --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-module-key.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# As for static branches, a static call key owned by a module must be rejected +# while a vmlinux-owned one is accepted. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c + +has_input_section orig.o .static_call_sites || + probe_skip "fixture produced no .static_call_sites on this arch" + +run_diff +assert_patched target + +# The accepted half has to show the entry was carried, not just that the +# function was: dropping the section silently would leave the patched call +# unregistered, and "target was cloned" cannot tell the two apart. +assert_section .static_call_sites +assert_reloc_sym .static_call_sites target + +rm -f "$workdir/out.o" +build_pair static_call.c -DMODNAME='"klp_testmod"' +run_diff 255 + +diff_log | grep -q 'unsupported static call key __SCK__klp_test_call' || + fail "expected rejection, got: $(diff_log | tail -1)" + +# A rejection has to leave nothing behind. out.o was removed above, so +# anything here was written by the run which was supposed to refuse. +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "module-owned static call key rejected, vmlinux-owned accepted" From e018004ab8fa4c68155da976125ed8645c075383 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:15 -0700 Subject: [PATCH 0622/2286] objtool/klp: Add test for symids in discarded sections .klp.symid records duplicate-named locals so klp diff can work out their sympos. Symbols in sections the vmlinux link throws away have to be left out, or the table references symbols which no longer exist and the link fails: `__exitcall_foo' referenced in section `.klp.symid' of vmlinux.o: defined in discarded section `.exitcall.exit' of vmlinux.o Two translation units are compiled from one fixture and partially linked so the result has duplicate locals, which symid_needed() requires. One duplicate is in a live section and one in .exitcall.exit. Checking the live duplicate as well keeps the test honest: it would otherwise pass just as happily if symid generation stopped working entirely. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-23-song@kernel.org --- .../tests/generic/fixtures/symid_discarded.c | 25 +++++++++++ .../tests/generic/test-symid-discarded.sh | 44 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/symid_discarded.c create mode 100755 tools/objtool/tests/generic/test-symid-discarded.sh diff --git a/tools/objtool/tests/generic/fixtures/symid_discarded.c b/tools/objtool/tests/generic/fixtures/symid_discarded.c new file mode 100644 index 00000000000000..573cc2d4474b2c --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/symid_discarded.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Compiled twice and partially linked so the result has duplicate locals, + * which is what symid_needed() requires. dup_normal is in a live section, + * dup_discarded in one the vmlinux link throws away. DISCARDED_SEC selects + * which discarded section, since there is more than one and each was its own + * bug. + */ + +#ifndef DISCARDED_SEC +#define DISCARDED_SEC ".exitcall.exit" +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int dup_normal = 1; + +static void *dup_discarded + __attribute__((section(DISCARDED_SEC), used)) = &dup_normal; + +int FUNC_NAME(void) +{ + return dup_normal + (dup_discarded != (void *)0); +} diff --git a/tools/objtool/tests/generic/test-symid-discarded.sh b/tools/objtool/tests/generic/test-symid-discarded.sh new file mode 100755 index 00000000000000..388a24984ec0a3 --- /dev/null +++ b/tools/objtool/tests/generic/test-symid-discarded.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# .klp.symid must not reference symbols in sections the vmlinux link discards. +# Each such section has been its own bug, found only when someone built a +# config where a duplicate happened to land there, so cover the whole list +# rather than whichever one was reported last. + +. "$(dirname "$0")/../lib.sh" + +setup + +# Allocated sections which vmlinux.lds.h discards unconditionally. A symid +# referencing one of these fails the vmlinux link outright: +# +# `__exitcall_hid_exit' referenced in section `.klp.symid' of vmlinux.o: +# defined in discarded section `.exitcall.exit' of vmlinux.o +for sec in .exitcall.exit .no_trim_symbol; do + build_one symid_discarded.c a.o \ + -DFUNC_NAME=use_a -DDISCARDED_SEC="\"$sec\"" + build_one symid_discarded.c b.o \ + -DFUNC_NAME=use_b -DDISCARDED_SEC="\"$sec\"" + + # --klp-symids only runs on a file named vmlinux.o + rm -f "$workdir/vmlinux.o" + partial_link "$workdir/vmlinux.o" "$workdir/a.o" "$workdir/b.o" || + probe_skip "partial link unavailable" + + "$OBJTOOL" --klp-symids --link "$workdir/vmlinux.o" || + fail "objtool --klp-symids failed" + + symids="$(in_relocs vmlinux.o | + awk '/rela.klp.symid/,/^$/')" + + # Without this the test would also pass if symid generation stopped + # entirely. + echo "$symids" | grep -q 'dup_normal' || + fail "$sec: no symid for the duplicate in a live section" + + echo "$symids" | grep -q 'dup_discarded' && + fail "symid emitted for a symbol in discarded section $sec" +done + +pass "no symids for symbols in discarded sections" From d6f2548ec3508fac9ad0f2a7d6f4783f9f9cd119 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:16 -0700 Subject: [PATCH 0623/2286] objtool/klp: Add test for rejecting references to init code/data Init code and data are freed once boot finishes, so a klp relocation against them can never resolve. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-24-song@kernel.org --- .../tests/generic/fixtures/init_reference.c | 17 +++++++++++ .../tests/generic/test-init-reference.sh | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/init_reference.c create mode 100755 tools/objtool/tests/generic/test-init-reference.sh diff --git a/tools/objtool/tests/generic/fixtures/init_reference.c b/tools/objtool/tests/generic/fixtures/init_reference.c new file mode 100644 index 00000000000000..9e59bdf708d3c1 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/init_reference.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Patched function referencing data in an .init section. */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* volatile so the read cannot be folded into a constant, leaving no reference */ +static volatile int init_only __attribute__((section(".init.data"), used)) = 5; + +int target(int x) +{ +#ifdef PATCHED + return x + init_only + 1; +#else + return x + init_only; +#endif +} diff --git a/tools/objtool/tests/generic/test-init-reference.sh b/tools/objtool/tests/generic/test-init-reference.sh new file mode 100755 index 00000000000000..921cf493cd7909 --- /dev/null +++ b/tools/objtool/tests/generic/test-init-reference.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Init code and data are freed after boot, so a klp relocation against them can +# never resolve. Such a patch must be rejected. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair init_reference.c + +# The rejection can only happen if the patched build really does reference the +# init symbol. The fixture makes init_only volatile so the read cannot be +# folded away, and this checks that it worked: without a relocation klp diff +# would succeed, and the failure below would read as a missing check rather +# than as a fixture which stopped posing the question. +# Either spelling will do. A file-local variable is reached through its +# section symbol -- .init.data -- and a global one by name; which of the two +# the compiler picks is its business, and the reference is what matters. +in_relocs "$patched_obj" | + awk '$5 == ".init.data" || $5 == "init_only"' | grep -q . || + fail "patched object has no reference into .init.data; the fixture tests nothing" + +run_diff 255 + +diff_log | grep -q "can't patch or reference init code/data" || + fail "expected rejection, got: $(diff_log | tail -1)" + +pass "reference to init data rejected" From 7fd24723f1601340ee09bca93989bd1fb4515783 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:17 -0700 Subject: [PATCH 0624/2286] objtool/klp: Add test for correlation across ThinLTO name mangling ThinLTO promotes the file-local symbols an imported function touches, renaming them name.llvm.. The hash is content derived, so it changes whenever the module does: original: counter.llvm.13663304415433785070 patched: counter.llvm.10543383937958011340 Correlating the two objects therefore requires demangling the suffix; matching raw names would see two unrelated symbols and treat the variable as new. The resulting klp relocation also has to name the original symbol, since that is the one in the running kernel's kallsyms. Naming the patched build's symbol produces a relocation which can never be resolved. ThinLTO is a clang feature, so the test declares itself clang-only. It also needs an lld from the same LLVM release as $CC; a mismatched pair fails with "Invalid summary version", which reads like a broken test rather than a broken environment, so probe for a working lld and skip if there is none. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-25-song@kernel.org --- .../tests/generic/fixtures/thinlto_local.c | 39 +++++++++++++++ .../tests/generic/test-thinlto-local.sh | 48 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_local.c create mode 100755 tools/objtool/tests/generic/test-thinlto-local.sh diff --git a/tools/objtool/tests/generic/fixtures/thinlto_local.c b/tools/objtool/tests/generic/fixtures/thinlto_local.c new file mode 100644 index 00000000000000..fe9f9e6bf44f9e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/thinlto_local.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two translation units (TU_B selects the second) linked with ThinLTO. + * Importing bump() promotes the file-local counter, renaming it + * counter.llvm.. The hash is content derived, so it differs between the + * original and patched builds. + */ + +#ifdef TU_B + +extern int bump(void); + +int other_entry(void) +{ + return bump() + bump(); +} + +#else + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int counter; + +int bump(void) +{ + return ++counter; +} + +int target(void) +{ +#ifdef PATCHED + return counter + 1; +#else + return counter; +#endif +} + +#endif diff --git a/tools/objtool/tests/generic/test-thinlto-local.sh b/tools/objtool/tests/generic/test-thinlto-local.sh new file mode 100755 index 00000000000000..a266263676ed5f --- /dev/null +++ b/tools/objtool/tests/generic/test-thinlto-local.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Correlating ThinLTO-promoted locals requires demangling the .llvm. +# suffix, and the resulting klp relocation must name the original symbol: that +# is the one in the running kernel's kallsyms. + +. "$(dirname "$0")/../lib.sh" + +setup +clang_only "ThinLTO requires clang" + +find_thinlto_toolchain || + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_LD to one" + +build_thinlto() # $1 output object, $2 extra flags +{ + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections $2 \ + -c "$FIXTURES_DIR/thinlto_local.c" -o "$workdir/tu_a.o" 2>/dev/null || return 1 + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections $2 -DTU_B \ + -c "$FIXTURES_DIR/thinlto_local.c" -o "$workdir/tu_b.o" 2>/dev/null || return 1 + "$THIN_LD" -r "$workdir/tu_a.o" "$workdir/tu_b.o" -o "$1" 2>/dev/null || return 1 +} + +build_thinlto "$workdir/orig.o" "" || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" +build_thinlto "$workdir/patched.o" -DPATCHED || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" + +orig_sym="$(in_symbols orig.o | grep -o 'counter\.llvm\.[0-9]*' | head -1)" +new_sym="$( in_symbols patched.o | grep -o 'counter\.llvm\.[0-9]*' | head -1)" + +[ -n "$orig_sym" ] && [ -n "$new_sym" ] || + probe_skip "$THIN_CC did not promote the local symbol" + +# Equal hashes would make plain name matching work, testing nothing. +[ "$orig_sym" != "$new_sym" ] || + probe_skip "$THIN_CC gave the same ThinLTO hash for both builds" + +run_diff +assert_patched target + +out_symbols | grep -q "\.klp\.sym\.vmlinux\.$orig_sym," || + fail "expected a klp relocation naming $orig_sym" +out_symbols | grep -q "\.klp\.sym\.vmlinux\.$new_sym," && + fail "klp relocation names $new_sym, which the running kernel does not have" + +pass "ThinLTO-mangled local correlated across differing hashes ($THIN_CC, $THIN_LD)" From 050a0c53a6c6009be329c7034d7a4d22ea146b12 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:18 -0700 Subject: [PATCH 0625/2286] objtool/klp: Add test for objects without .modinfo The module name recorded in .modinfo ends up in the livepatch's klp_object, and also decides whether a static branch key counts as belonging to vmlinux or to a module. An object without one cannot be diffed. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-26-song@kernel.org --- .../objtool/tests/generic/fixtures/no_modinfo.c | 11 +++++++++++ .../tests/generic/test-missing-modinfo.sh | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/no_modinfo.c create mode 100755 tools/objtool/tests/generic/test-missing-modinfo.sh diff --git a/tools/objtool/tests/generic/fixtures/no_modinfo.c b/tools/objtool/tests/generic/fixtures/no_modinfo.c new file mode 100644 index 00000000000000..e46374702b1af7 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/no_modinfo.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Deliberately has no .modinfo section. */ + +int target(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-missing-modinfo.sh b/tools/objtool/tests/generic/test-missing-modinfo.sh new file mode 100755 index 00000000000000..6f242f7eb756e3 --- /dev/null +++ b/tools/objtool/tests/generic/test-missing-modinfo.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# The module name in .modinfo ends up in the livepatch's klp_object, so an +# object without one cannot be diffed. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair no_modinfo.c +run_diff 255 + +diff_log | grep -q 'modinfo' || + fail "expected a complaint about .modinfo, got: $(diff_log | tail -1)" + +pass "object without .modinfo rejected" From 1ecf11530c16252dca835c9bc9133f73f5225ee4 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Wed, 16 Sep 2026 11:43:19 -0700 Subject: [PATCH 0626/2286] objtool/klp: Add test for unchecksummed input klp diff decides what changed by comparing the per-function checksums klp checksum records in .discard.sym_checksum. Handed an object which was never checksummed it has to say so; silently concluding that nothing changed would be the worst available answer. Signed-off-by: Puranjay Mohan Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-27-song@kernel.org --- .../tests/generic/test-missing-checksum.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100755 tools/objtool/tests/generic/test-missing-checksum.sh diff --git a/tools/objtool/tests/generic/test-missing-checksum.sh b/tools/objtool/tests/generic/test-missing-checksum.sh new file mode 100755 index 00000000000000..89a05b1869edbf --- /dev/null +++ b/tools/objtool/tests/generic/test-missing-checksum.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Without .discard.sym_checksum there is nothing to compare; concluding that +# nothing changed would be worse than failing. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +( cd "$workdir" && "$OBJTOOL" klp diff orig.o patched.o out.o ) \ + > "$workdir/diff.log" 2>&1 && fail "klp diff accepted an unchecksummed object" + +grep -q 'sym_checksum' "$workdir/diff.log" || + fail "expected a complaint about the checksum section, got: $(tail -1 "$workdir/diff.log")" + +pass "unchecksummed input rejected" From ade23d75deb3c679760c3f0f33d9b8d8ffa9f963 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:20 -0700 Subject: [PATCH 0627/2286] objtool/klp: Add klp diff and post-link regression tests Five tests covering behaviour the harness could reach but nothing exercised. Each was verified to fail against the code it guards, by reverting the fix or sabotaging the exact line; where a first attempt passed against broken code, the fixture was wrong and was rebuilt. post-link first coverage of the subcommand at all local-vs-export local symbols must not match exports symvers-parse-error Module.symvers parse error line numbers checksum-debug the --debug-checksum format klp-build reads function-removal the "no correlation" path local-vs-export tests the behavior of commit 86a697572c62 ("objtool/klp: Don't match local symbols against exports"), and symvers-parse-error that of commit 51c1de134863 ("objtool/klp: Fix line numbers in Module.symvers parse errors"). Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-28-song@kernel.org --- .../tests/generic/fixtures/function_removal.c | 25 ++++++++++ .../tests/generic/test-checksum-debug.sh | 49 +++++++++++++++++++ .../tests/generic/test-function-removal.sh | 34 +++++++++++++ .../tests/generic/test-local-vs-export.sh | 32 ++++++++++++ tools/objtool/tests/generic/test-post-link.sh | 42 ++++++++++++++++ .../tests/generic/test-symvers-parse-error.sh | 23 +++++++++ 6 files changed, 205 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/function_removal.c create mode 100755 tools/objtool/tests/generic/test-checksum-debug.sh create mode 100755 tools/objtool/tests/generic/test-function-removal.sh create mode 100755 tools/objtool/tests/generic/test-local-vs-export.sh create mode 100755 tools/objtool/tests/generic/test-post-link.sh create mode 100755 tools/objtool/tests/generic/test-symvers-parse-error.sh diff --git a/tools/objtool/tests/generic/fixtures/function_removal.c b/tools/objtool/tests/generic/fixtures/function_removal.c new file mode 100644 index 00000000000000..d65ff604c2c5c5 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/function_removal.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function the patch deletes, along with its only caller's use of it. The + * original has a symbol which the patched object simply does not, so there is + * nothing to correlate it against. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifndef PATCHED +int going_away(int x) +{ + return x + 7; +} +#endif + +int caller(int x) +{ +#ifdef PATCHED + return x + 1; +#else + return going_away(x); +#endif +} diff --git a/tools/objtool/tests/generic/test-checksum-debug.sh b/tools/objtool/tests/generic/test-checksum-debug.sh new file mode 100755 index 00000000000000..78856b1916362a --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-debug.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# "klp checksum --debug-checksum" prints a per-instruction checksum stream, +# and klp-build -f (--show-first-changed) parses it to report where a function +# first differs between the original and patched builds. +# +# It is a debugging aid, so nothing fails when it breaks: klp-build greps the +# stream, and an unmatched grep just yields no output, which reads as "no +# instruction changed". That is exactly how the format drifted out from under +# it once already. Pin the shape klp-build depends on: +# +# DEBUG: : checksum: (): +0x <16 hex digits> +# +# and that --dry-run leaves the object alone, since klp-build runs this against +# objects it is going to checksum again for real. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +before="$(md5sum < "$workdir/orig.o")" + +"$OBJTOOL" klp checksum --dry-run --debug-checksum=changed \ + "$workdir/orig.o" > "$workdir/debug.log" 2>&1 || + fail "klp checksum --debug-checksum failed" + +# --dry-run has to mean it: klp-build checksums these objects again afterwards, +# and "already has .discard.sym_checksum, skipping" would lose the real run. +[ "$(md5sum < "$workdir/orig.o")" = "$before" ] || + fail "--dry-run modified the object" +has_input_section orig.o .discard.sym_checksum && + fail "--dry-run created .discard.sym_checksum" + +grep -qE '^DEBUG: .*: checksum: changed\(\): [^ ]+\+0x[0-9a-f]+ [0-9a-f]{16}$' \ + "$workdir/debug.log" || + fail "unexpected --debug-checksum format: $(head -1 "$workdir/debug.log")" + +# This is the pattern klp-build greps with. Keep it working verbatim. +grep -qE "^DEBUG: .*checksum: changed\(\): " "$workdir/debug.log" || + fail "klp-build's --show-first-changed pattern no longer matches" + +# Only the requested function, or klp-build attributes instructions to the +# wrong one. +grep -qE 'checksum: untouched\(\)' "$workdir/debug.log" && + fail "--debug-checksum=changed also dumped untouched()" + +pass "--debug-checksum format is the one klp-build -f parses" diff --git a/tools/objtool/tests/generic/test-function-removal.sh b/tools/objtool/tests/generic/test-function-removal.sh new file mode 100755 index 00000000000000..df76e81e3936c4 --- /dev/null +++ b/tools/objtool/tests/generic/test-function-removal.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch which deletes a function leaves a symbol in the original with no +# counterpart in the patched object. klp diff cannot correlate it, and must +# say so and carry on: livepatching cannot remove code from a running kernel, +# so what matters is that the surviving caller is patched and the deleted +# function is not dragged into the patch module. +# +# Cloning it would be worse than useless -- dead code in the patch, plus +# whatever it references, resolved against a kernel where it may not exist. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair function_removal.c + +# One-sided by construction: present in the original, gone from the patched. +has_input_symbol orig.o going_away || + fail "fixture has no going_away in the original" +has_input_symbol patched.o going_away && + fail "fixture still has going_away in the patched object" + +run_diff + +assert_diff_log 'no correlation: going_away' + +# The caller changed, so it is patched ... +assert_patched caller +# ... and the deleted function comes along in no form at all. +assert_not_patched going_away +assert_no_symbol going_away + +pass "deleted function reported as uncorrelated and left out of the patch" diff --git a/tools/objtool/tests/generic/test-local-vs-export.sh b/tools/objtool/tests/generic/test-local-vs-export.sh new file mode 100755 index 00000000000000..5b91101dadd64b --- /dev/null +++ b/tools/objtool/tests/generic/test-local-vs-export.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# find_export() matched on symbol name alone, so a static function or variable +# sharing a name with an export was mistaken for a reference to that export. +# For a vmlinux export that means no klp relocation at all: the normal +# relocation left behind is resolved by the module loader to the vmlinux +# symbol, and the patched code quietly reads and writes the wrong object. +# +# Exports are always global, so a local symbol is never one. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +# The static the fixture uses. Compilers mangle statics variously -- gcc says +# counter.0, clang says target.counter -- so find what this one produced rather +# than assuming a shape. +local_sym="$(in_symbols orig.o | + awk '$4 == "OBJECT" && $5 == "LOCAL" && $8 ~ /counter/ { print $8; exit }')" +[ -n "$local_sym" ] || + fail "fixture produced no local 'counter' symbol" + +# Contrive the collision: something else exports that same name. +export_syms "$local_sym" counter +run_diff + +# Still treated as the local it is, not as the export. +assert_klp_sym "$local_sym" vmlinux + +pass "local symbol not mistaken for an export of the same name" diff --git a/tools/objtool/tests/generic/test-post-link.sh b/tools/objtool/tests/generic/test-post-link.sh new file mode 100755 index 00000000000000..a6d40ae1b18d1b --- /dev/null +++ b/tools/objtool/tests/generic/test-post-link.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# klp post-link converts the intermediate __klp_relocs.* sections into the +# .klp.rela.* form the kernel applies at patch load. Getting this wrong is +# invisible at build time: the module links and loads, and the relocations are +# simply never applied. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local.c + +# An unexported symbol is what produces a klp relocation in the first place. +# The static local is renamed by the compiler -- counter.0 with gcc -- so the +# fixture's premise is that some local of that shape exists, not that a symbol +# called "counter" does. Find it first; everything below names it. +local_sym="$(in_symbols orig.o | + awk '$4 == "OBJECT" && $5 == "LOCAL" && $8 ~ /counter/ { print $8; exit }')" +[ -n "$local_sym" ] || fail "fixture produced no local 'counter' symbol" + +run_diff +assert_section __klp_relocs.vmlinux + +# Nothing has converted them yet. +assert_no_section ".klp.rela.vmlinux..text.target" + +# The original relocation is neutralised by pointing it at a tombstone, which +# is what stops the module loader resolving it behind livepatch's back. +# +# Compilers mangle a static local differently -- gcc says counter.0, clang +# target.counter -- so find what this one produced rather than assuming. +assert_tombstone "$local_sym" + +run_post_link + +# One .klp.rela section per base section, carrying SHF_RELA_LIVEPATCH, against +# a symbol in SHN_LIVEPATCH for the kernel to resolve. +assert_klp_rela vmlinux .text.target +assert_livepatch_sym "$local_sym" + +pass "klp relocations converted to .klp.rela with SHN_LIVEPATCH symbols" diff --git a/tools/objtool/tests/generic/test-symvers-parse-error.sh b/tools/objtool/tests/generic/test-symvers-parse-error.sh new file mode 100755 index 00000000000000..d597f28e561752 --- /dev/null +++ b/tools/objtool/tests/generic/test-symvers-parse-error.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A malformed Module.symvers has to be reported against the line it is on. +# Module.symvers has tens of thousands of lines and is generated, so a wrong +# line number sends whoever has to fix it to the wrong place, and "line 1" is +# wrong in a way that looks plausible. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +# Three well-formed lines, then one with no tabs at all. +export_syms a b c +echo 'this line has no fields' >> "$workdir/Module.symvers" + +run_diff 255 + +assert_diff_log 'malformed Module.symvers' +assert_diff_log 'at line 4' + +pass "malformed Module.symvers reported against the offending line" From 2d17a35f6c9489884e6a5ff7cb0d6b150d413371 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:21 -0700 Subject: [PATCH 0628/2286] objtool/klp: Add test for klp reloc section naming in module objects klp diff names the intermediate __klp_relocs section after the object the relocation belongs to, and post-link turns that into .klp.rela... Name it after the wrong object and the kernel applies the relocation when the wrong module loads, or never. The fixture is the first here to honour MODNAME: most hardcode name=vmlinux, so passing -DMODNAME to them silently does nothing and the test quietly becomes a vmlinux test. This tests the behavior of commit 07f14d6af9d7 ("objtool/klp: Fix cross-module klp relocation section naming"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-29-song@kernel.org --- .../tests/generic/fixtures/cross_module.c | 25 +++++++++++++++ .../tests/generic/test-module-object.sh | 31 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/cross_module.c create mode 100755 tools/objtool/tests/generic/test-module-object.sh diff --git a/tools/objtool/tests/generic/fixtures/cross_module.c b/tools/objtool/tests/generic/fixtures/cross_module.c new file mode 100644 index 00000000000000..c170bde0666f40 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/cross_module.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function which calls out to another object. MODNAME selects which object + * this one is, so a test can make the caller a module and the callee's owner + * something else. + */ + +#ifndef MODNAME +#define MODNAME "vmlinux" +#endif + +/* klp diff takes the object's module name from .modinfo */ +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=" MODNAME; + +extern int other_mod_func(int x); + +int target(int x) +{ +#ifdef PATCHED + return other_mod_func(x) + 2; +#else + return other_mod_func(x) + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-module-object.sh b/tools/objtool/tests/generic/test-module-object.sh new file mode 100755 index 00000000000000..95c8402a523a21 --- /dev/null +++ b/tools/objtool/tests/generic/test-module-object.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A klp relocation section is named for the object being patched, not for the +# object which happens to own the symbol being referenced. Deriving it from +# the symbol means a cross-module reference lands in a section for an object +# the patch may not even touch, so the relocation is never applied and the call +# goes somewhere arbitrary. + +. "$(dirname "$0")/../lib.sh" + +setup +build_module_pair cross_module.c klp_testmod + +# The fixture has to have built as a module for any of this to mean anything. +in_sections orig.o | grep -q '\.modinfo' || + fail "fixture has no .modinfo" + +# other_mod_func belongs to a different module than the one being patched. +add_exports other_mod other_mod_func +run_diff + +# Named for the patched object ... +assert_section __klp_relocs.klp_testmod +# ... not for the object owning the symbol. +assert_no_section __klp_relocs.other_mod + +run_post_link +assert_klp_rela klp_testmod .text.target + +pass "klp relocation section named for the patched object" From 65b0aac4125c18280efbda38174ec14bcef887b2 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:22 -0700 Subject: [PATCH 0629/2286] objtool/klp: Add test for vmlinux relocs in a patched module The kernel refuses a module-targeted klp relocation which names a vmlinux symbol. An EXPORT_SYMBOL_FOR_MODULES() symbol needs a klp relocation, so patching a module function that references one only loads if klp diff files that relocation under vmlinux rather than under the patched module. This fails at load, not at build: klp-build produces a module and static checks of it find nothing wrong. Hence the assertion on which object the relocation is filed against. Reported by Dylan Hatch, whose mod-ns-lp branch carries the kernel-side half of this case. Co-developed-by: Dylan Hatch Signed-off-by: Dylan Hatch Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-30-song@kernel.org --- .../generic/test-module-vmlinux-reloc.sh | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100755 tools/objtool/tests/generic/test-module-vmlinux-reloc.sh diff --git a/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh b/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh new file mode 100755 index 00000000000000..635a75f6b8ba63 --- /dev/null +++ b/tools/objtool/tests/generic/test-module-vmlinux-reloc.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Patching a module, where the patched code references a vmlinux symbol which +# needs a klp relocation. +# +# The kernel does not allow a module-targeted klp relocation to reference a +# vmlinux symbol, and a symbol exported with EXPORT_SYMBOL_FOR_MODULES gets a +# klp relocation. Put together, filing that relocation under the patched +# module produces a patch the kernel refuses to apply to its target. +# +# So it goes under vmlinux instead, and is applied when the patch module loads +# rather than when the patched module does. That is the opposite of the rule +# for a reference to a module's symbol, which test-module-object covers; this +# is the other branch of the same decision. + +. "$(dirname "$0")/../lib.sh" + +setup +# The object being patched is a module ... +build_module_pair cross_module.c klp_testmod + +# ... and the symbol it references belongs to vmlinux, exported in a way that +# still requires a klp relocation. +export_syms +add_exports_ns vmlinux module:kvm other_mod_func +run_diff + +# Filed against vmlinux, applied when the patch loads. +assert_section __klp_relocs.vmlinux +assert_klp_sym other_mod_func vmlinux + +# Not against the patched module: that is the relocation the kernel rejects. +assert_no_section __klp_relocs.klp_testmod + +run_post_link +assert_klp_rela vmlinux .text.target +assert_no_section ".klp.rela.klp_testmod..text.target" + +pass "klp relocation to a vmlinux symbol filed under vmlinux, not the patched module" From 90949e312de909e2f2a4f72ff3221f79e891d19f Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:23 -0700 Subject: [PATCH 0630/2286] objtool/klp: Add test for Module.symvers path normalization Module.symvers names an export's owner by build path, and the kernel knows modules by their runtime name. Without normalizing one to the other, an export owned by a module is not recognised as owned by anything, and a reference that should become a klp relocation stays an ordinary one. This tests the behavior of commit 8668bf91e050 ("objtool/klp: Normalize Module.symvers paths to module names"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-31-song@kernel.org --- .../tests/generic/test-modname-normalize.sh | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 tools/objtool/tests/generic/test-modname-normalize.sh diff --git a/tools/objtool/tests/generic/test-modname-normalize.sh b/tools/objtool/tests/generic/test-modname-normalize.sh new file mode 100755 index 00000000000000..a8059bdbd66705 --- /dev/null +++ b/tools/objtool/tests/generic/test-modname-normalize.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Module.symvers records the build-tree path of the object that exports a +# symbol, not the name the module has at runtime: "arch/x86/kvm/kvm-intel", +# where the kernel knows the module as "kvm_intel". +# +# The klp symbol name embeds the owning object, and livepatch matches it +# against loaded modules by name. Left unnormalized it names a module that +# does not exist, and the relocation is never resolved -- at load time, with no +# build-time complaint. + +. "$(dirname "$0")/../lib.sh" + +setup +build_module_pair cross_module.c klp_testmod + +# A path with directory components, a dash, and no extension. +export_syms +add_exports "arch/x86/kvm/kvm-intel" other_mod_func +run_diff + +# Directories stripped, dash to underscore. +assert_klp_sym other_mod_func kvm_intel + +pass "Module.symvers paths normalized to runtime module names" From 35021df3834e0b51dd77e4be6ee7cbe9bcfa4825 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:24 -0700 Subject: [PATCH 0631/2286] objtool/klp: Add test for the contents of the klp_funcs list The patch list is what livepatch acts on, and asserting only that it exists does not say it is right. A function that should have been patched and is missing leaves the bug in place; one that should not be there patches code nobody changed. The fixture changes two of three functions and asserts on all three: the two by name, and the third by its absence. It checks the strings in .rodata.klp.str1.1 as well as the relocations, since the names the kernel matches on are real strings. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-32-song@kernel.org --- .../tests/generic/fixtures/klp_funcs.c | 31 +++++++++++++ .../tests/generic/test-klp-funcs-content.sh | 45 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/klp_funcs.c create mode 100755 tools/objtool/tests/generic/test-klp-funcs-content.sh diff --git a/tools/objtool/tests/generic/fixtures/klp_funcs.c b/tools/objtool/tests/generic/fixtures/klp_funcs.c new file mode 100644 index 00000000000000..3f0d3e206cef6e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/klp_funcs.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two changed functions and one untouched, so the patch's function list has a + * length worth checking and something that must not appear in it. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int first(int x) +{ +#ifdef PATCHED + return x + 11; +#else + return x + 1; +#endif +} + +int second(int x) +{ +#ifdef PATCHED + return x + 22; +#else + return x + 2; +#endif +} + +int third(int x) +{ + return x + 3; +} diff --git a/tools/objtool/tests/generic/test-klp-funcs-content.sh b/tools/objtool/tests/generic/test-klp-funcs-content.sh new file mode 100755 index 00000000000000..32fbd5f6a6fa91 --- /dev/null +++ b/tools/objtool/tests/generic/test-klp-funcs-content.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# .init.klp_funcs is the list the kernel walks to decide what to patch, and +# .init.klp_objects points at it. Existing tests assert only that the sections +# exist, which they do whether the list names the right functions, the wrong +# ones, or none at all -- and a patch module with an empty function list loads +# perfectly happily and patches nothing. +# +# Each entry pairs a name string in .rodata.klp.str1.1 with a relocation to the +# new function, so both halves are checkable. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair klp_funcs.c +run_diff + +assert_section .init.klp_funcs +assert_section .init.klp_objects + +# Two functions changed, so two entries, each contributing a name relocation +# and a function relocation. +assert_reloc_count .init.klp_funcs 4 + +# The functions that changed are named ... +assert_reloc_sym .init.klp_funcs first +assert_reloc_sym .init.klp_funcs second +# ... and the one that did not is absent, from the list and from the patch. +assert_no_reloc_sym .init.klp_funcs third +assert_not_patched third + +# The names the kernel matches on are real strings, not just relocations. +# readelf prints one per line as "[ offset] ", so compare the whole +# name: a word-boundary match would also accept ".text.first", since a dot is +# not a word character. +for name in first second; do + out_strings .rodata.klp.str1.1 | awk -v n="$name" '$NF == n' | grep -q . || + fail "no '$name' string in .rodata.klp.str1.1" +done + +# The object list has to reach the function list, or nothing is walked. +assert_reloc_sym .init.klp_objects .init.klp_funcs + +pass "klp_funcs lists exactly the changed functions, by name and relocation" From a87892ac05098d23e855e25c7afc128b1e9973fe Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:25 -0700 Subject: [PATCH 0632/2286] objtool/klp: Add test for EXPORT_SYMBOL_FOR_MODULES references A symbol exported with EXPORT_SYMBOL_FOR_MODULES() is reachable only by the modules named in its namespace, and a livepatch module is never one of them. So a reference to it cannot be an ordinary relocation resolved by the module loader; it has to be a klp relocation applied at patch time. Getting this wrong is silent. The module links, loads, and reads the wrong thing, or fails to load for a reason that does not name the cause. This tests the behavior of commit 4cd3cfb8b54f ("objtool/klp: Fix relocations for EXPORT_SYMBOL_FOR_MODULES() symbols"). Which object the resulting relocation is filed under is a separate question, and one this test cannot ask: the patched object here is vmlinux, so the vmlinux section is the one produced either way. That is covered by the module case, in "objtool/klp: Add test for vmlinux relocs in a patched module". Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-33-song@kernel.org --- .../generic/test-export-symbol-for-modules.sh | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 tools/objtool/tests/generic/test-export-symbol-for-modules.sh diff --git a/tools/objtool/tests/generic/test-export-symbol-for-modules.sh b/tools/objtool/tests/generic/test-export-symbol-for-modules.sh new file mode 100755 index 00000000000000..7e7bdde6a7aca7 --- /dev/null +++ b/tools/objtool/tests/generic/test-export-symbol-for-modules.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# EXPORT_SYMBOL_FOR_MODULES() puts a vmlinux symbol in a "module:" +# namespace, and the module loader grants access by matching the importing +# module's name against that list. A livepatch module is never on the list, so +# referencing such a symbol with a normal relocation fails modpost, and if that +# is silenced, fails to load with "Unknown symbol". It needs a klp relocation, +# the same as an unexported symbol. +# +# Ordinary namespaces are not affected: copy_import_ns() propagates the patched +# object's import tags to the patch module, so a normal relocation works. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair cross_module.c + +sym=other_mod_func + +# Plain vmlinux export: a normal relocation is what we want. +export_syms "$sym" +run_diff +assert_no_klp_sym "$sym" + +# Ordinary namespace: still a normal relocation. +export_syms +add_exports_ns vmlinux MY_NS "$sym" +run_diff +assert_no_klp_sym "$sym" + +# module: namespace: has to become a klp relocation. +export_syms +add_exports_ns vmlinux module:kvm "$sym" +run_diff +assert_klp_sym "$sym" vmlinux +assert_section __klp_relocs.vmlinux + +pass "EXPORT_SYMBOL_FOR_MODULES symbol referenced with a klp relocation" From 6549ce0dd1be2400ecdd8693e47c189c54562a4e Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:26 -0700 Subject: [PATCH 0633/2286] objtool/klp: Add test for new references to exported symbols A patch may introduce a reference the original object did not have. That is fine when the export belongs to vmlinux, and not fine when it belongs to a module: the livepatch would gain a module dependency nobody declared, and late module patching lets the patch load first. This tests the behavior of commit 72d76d0c18eb ("objtool/klp: Allow new references to module exports"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-34-song@kernel.org --- .../tests/generic/fixtures/new_export_ref.c | 35 ++++++++++++++ .../tests/generic/test-new-export-ref.sh | 46 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/new_export_ref.c create mode 100755 tools/objtool/tests/generic/test-new-export-ref.sh diff --git a/tools/objtool/tests/generic/fixtures/new_export_ref.c b/tools/objtool/tests/generic/fixtures/new_export_ref.c new file mode 100644 index 00000000000000..73210aacb4ae51 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/new_export_ref.c @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A reference which only exists in the patched build. The symbol has no twin + * in the original object, so what klp diff may do with it depends entirely on + * whether Module.symvers says it is exported, and by what. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +extern int newly_referenced(int x); + +/* + * A reference both builds have. When Module.symvers says a module exports + * this one, the original already depends on that module, which is what makes + * a new reference to it safe -- the loader will not let the patched module + * load without it. EXISTING_DEP leaves it out, for the case where there is + * no such dependency to inherit. + */ +extern int existing_dep(int x); + +int target(int x) +{ +#ifdef EXISTING_DEP + int base = existing_dep(x); +#else + int base = x; +#endif + +#ifdef PATCHED + return newly_referenced(base); +#else + return base + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-new-export-ref.sh b/tools/objtool/tests/generic/test-new-export-ref.sh new file mode 100755 index 00000000000000..f0be2cf87fe3c4 --- /dev/null +++ b/tools/objtool/tests/generic/test-new-export-ref.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A reference the patch adds has no counterpart in the original object. klp +# diff used to reject any such reference needing a klp relocation, which ruled +# out patches that call something they did not call before -- a common enough +# thing for a fix to do. +# +# Module.symvers is what makes it safe: it says the symbol exists and who owns +# it. But that is only sufficient for a vmlinux export. A new reference to a +# module's export is a dependency the patch module does not declare, and the +# relocation would resolve only if that module happened to be loaded, so it +# stays an error. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair new_export_ref.c + +# Exported by vmlinux, in a module: namespace so it needs a klp relocation +# rather than an ordinary one. Allowed. +export_syms +add_exports_ns vmlinux module:kvm newly_referenced +run_diff +assert_klp_sym newly_referenced vmlinux + +# Exported by a module the patched object does not depend on. Rejected, and +# for that reason rather than some other. +export_syms +add_exports other_mod newly_referenced +run_diff 255 +assert_diff_log 'undeclared module dependency' + +# ... unless the original already referenced something that module exports. +# The loader will not let the patched object load without other_mod, so the +# klp relocation has something to resolve against, and klp diff allows it. +# This is the other half of the rule, and it fails in the opposite direction: +# refusing here would reject a patch which is safe to apply. +rm -f "$workdir/out.o" +build_pair new_export_ref.c -DEXISTING_DEP +export_syms +add_exports other_mod newly_referenced existing_dep +run_diff +assert_klp_sym newly_referenced other_mod + +pass "new reference allowed for vmlinux and for a module already depended on" From dff07c58cae7f60e7ae08e9c005e8ec125391f98 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:27 -0700 Subject: [PATCH 0634/2286] objtool/klp: Add test for empty x86 alternative replacements ALTERNATIVE_2("orig", "repl", ft1, "", ft2) produces a second entry whose replacement is empty. Its replacement offset still carries a relocation, but the label it points at is the end of the previous replacement -- which is also where the next one begins. The value is meaningless and only ever used with a length of zero, so cloning must not follow it. The first version of this fixture passed with the fix reverted, because the empty entry pointed at its own end label rather than at the neighbour's replacement. It has to reach into another function's replacement to distinguish the behaviour. This tests the behavior of commit 636f230ce21e ("objtool/klp: Ignore replacement offset of empty x86 alternatives"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-35-song@kernel.org --- .../tests/x86/fixtures/empty_alternative.c | 77 +++++++++++++++++++ .../tests/x86/test-empty-alternative.sh | 31 ++++++++ 2 files changed, 108 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/empty_alternative.c create mode 100755 tools/objtool/tests/x86/test-empty-alternative.sh diff --git a/tools/objtool/tests/x86/fixtures/empty_alternative.c b/tools/objtool/tests/x86/fixtures/empty_alternative.c new file mode 100644 index 00000000000000..9336d74bfa9265 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/empty_alternative.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative with an empty replacement, as the second entry of + * ALTERNATIVE_2("orig", "repl", ft1, "", ft2) produces. Its replacement + * offset still gets a relocation, but the label it points at is the end of the + * previous replacement, which is also where the *next* one begins -- here, + * neighbor()'s. The value is meaningless; it is only ever used with a length + * of zero. + * + * struct alt_instr is written out by hand so the fixture builds without kernel + * headers: s32 instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, + * u8 replacementlen. The section carries an entsize because klp diff needs + * either that or an ANNOTATE_DATA_SPECIAL annotation to find entry boundaries. + * + * The replacement labels are global so the relocations name them rather than + * .altinstr_replacement plus an addend. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +extern int neighbor_only(int x); + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + " nop\n\t" + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + /* a real replacement */ + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte target_repl_end - target_repl\n\t" + /* an empty one, pointing at neighbor()'s replacement */ + ".long 661b - .\n\t" + ".long neighbor_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte 0\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} + +/* + * Unrelated, unpatched, and referencing a symbol nothing else does, so that + * dragging its replacement in is visible. + */ +int neighbor(int x) +{ + asm volatile( + "771: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl neighbor_repl\n\t" + "neighbor_repl:\n\t" + " call neighbor_only\n\t" + "neighbor_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 771b - .\n\t" + ".long neighbor_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte neighbor_repl_end - neighbor_repl\n\t" + ".popsection\n\t"); + return x; +} diff --git a/tools/objtool/tests/x86/test-empty-alternative.sh b/tools/objtool/tests/x86/test-empty-alternative.sh new file mode 100755 index 00000000000000..9d40c3a405af0c --- /dev/null +++ b/tools/objtool/tests/x86/test-empty-alternative.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An x86 alternative with an empty replacement still gets a relocation for its +# replacement offset, but the label it points at is the end of the previous +# replacement -- which is also the start of the next one. The value is +# meaningless, and get_alt_entry() already ignores it. +# +# Cloning it drags in an unrelated neighboring replacement and everything that +# replacement references. In the reported case an empty alternative in +# meminfo_proc_show() pulled in one from proc_kcore_init(), emitting a klp +# relocation against init text which is long freed by the time the patch is +# applied. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair empty_alternative.c + +assert_input_section .altinstructions +assert_input_section .altinstr_replacement + +run_diff + +# target's own replacement comes along ... +assert_symbol target_repl +# ... neighbor's does not, nor what it references. +assert_no_symbol neighbor_repl +assert_no_symbol neighbor_only + +pass "empty alternative's replacement offset ignored when cloning" From a5f001a475d157184c038f8dcab1e7690f501eab Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:28 -0700 Subject: [PATCH 0635/2286] objtool/klp: Add test for recorded checksum values Whether klp diff treats a function as changed is decided by its checksum, and until now nothing looked at one. A test asserting only that the right functions were cloned cannot tell a correct checksum from one that happens to differ. Asserts both directions -- the changed function's checksum moves, the untouched one's does not -- and that checksumming identical input twice gives the same answer, since otherwise every rebuild reports spurious changes. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-36-song@kernel.org --- .../tests/generic/test-checksum-value.sh | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100755 tools/objtool/tests/generic/test-checksum-value.sh diff --git a/tools/objtool/tests/generic/test-checksum-value.sh b/tools/objtool/tests/generic/test-checksum-value.sh new file mode 100755 index 00000000000000..feae6a12e98d8d --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-value.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# The per-function checksums klp checksum records are what klp diff uses to +# decide which functions changed. A checksum covering too little misses a real +# change and the patch silently omits the function; one covering too much, or +# unstable across identical input, clones functions nobody patched and drags +# their dependencies in with them. +# +# test-basic covers which functions got cloned, which is downstream of this and +# passes for either kind of wrong checksum as long as the two errors do not +# happen to cancel. This checks the checksums themselves. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair basic.c + +assert_input_symbol changed +assert_input_symbol untouched + +run_checksum + +# The edited function's checksum has to move, the untouched one's must not. +assert_checksum_differs changed +assert_checksum_matches untouched + +# And it has to be a function of the code, not of the build: checksumming the +# same input twice has to give the same answer, or every rebuild reports +# spurious changes. +first="$(checksum_of orig.o changed)" +build_pair basic.c +run_checksum +[ "$(checksum_of orig.o changed)" = "$first" ] || + fail "checksum for 'changed' differs between builds of identical source" + +pass "checksums track the changed function and are stable across rebuilds" From f55eae8f37ea5ff547a8ac4ddafcef21047d5874 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:29 -0700 Subject: [PATCH 0636/2286] objtool/klp: Add test for position-independent checksums A function that only moves has not changed, and its checksum must not move with it. Otherwise every patch reports as changed everything that shifted because something ahead of it grew. The fixture is built with -fno-function-sections, overriding the harness default: with per-function sections every function sits at offset 0 of its own section and nothing ever moves, so the test would prove nothing. It also calls across to another function rather than looping within itself -- a loop branch keeps the same displacement wherever the function goes, so it is not position-dependent to begin with. This tests the behavior of commit cca84cb12908 ("objtool/klp: Fix position-dependent checksums for non-relocated jumps/calls"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-37-song@kernel.org --- .../generic/fixtures/checksum_position.c | 42 +++++++++++++++ .../tests/generic/test-checksum-position.sh | 51 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_position.c create mode 100755 tools/objtool/tests/generic/test-checksum-position.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_position.c b/tools/objtool/tests/generic/fixtures/checksum_position.c new file mode 100644 index 00000000000000..4320bc22059226 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_position.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function whose position in the section changes between the two builds, + * without the function itself changing. + * + * target() calls callee() twice, and a call within the same section needs no + * relocation: the displacement is in the instruction. It is that displacement + * which moves, and hashing those bytes makes the checksum move with it. Both + * must therefore share a section, which is why the test passes + * -fno-function-sections. + * + * What has to change is the distance between the two, and PATCHED changes it + * by aligning them rather than by inserting a function between them. Where a + * compiler puts an added function is its own business: gcc emits these in + * source order, so a function written between callee() and target() separates + * them, but clang emits target() immediately before callee() whatever the + * source says, and an added function lands ahead of both. That moves target() + * without moving it relative to callee(), the displacement comes out identical + * in both builds, and the test passes without having asked anything. + * + * Alignment moves the functions apart on both, and moves neither function's + * own instructions -- which is exactly the distinction under test. + */ + +#ifdef PATCHED +#define MOVED __attribute__((aligned(64))) +#else +#define MOVED +#endif + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +__attribute__((noinline)) MOVED static int callee(int x) +{ + return x * 5 + 1; +} + +__attribute__((noinline)) MOVED int target(int x) +{ + return callee(x) + callee(x + 1); +} diff --git a/tools/objtool/tests/generic/test-checksum-position.sh b/tools/objtool/tests/generic/test-checksum-position.sh new file mode 100755 index 00000000000000..5ae759e719eb80 --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-position.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A function's checksum must not depend on where the function sits. +# +# A jump or call without a relocation encodes its target as an offset from the +# instruction. Hashing those bytes makes the checksum change whenever anything +# ahead of the function changes size -- so an unrelated edit elsewhere in the +# file reports this function as changed too, and the patch grows to include it +# and everything it references. Nothing fails; the livepatch is just larger and +# riskier than the patch it came from. +# +# Here the "patch" moves target() away from callee() and changes nothing else: +# both are aligned to 64 in the patched build, which shifts them apart without +# touching a byte of either. See the fixture for why it is done that way. + +. "$(dirname "$0")/../lib.sh" + +setup + +# -fno-function-sections, or each function is at offset 0 of its own section +# and target() never moves. +build_pair checksum_position.c -fno-function-sections + +assert_input_symbol target + +# The fixture is only meaningful if the displacement target's calls encode +# actually changed, and that is the distance to callee() -- not target's own +# offset. A compiler which shifted the two by the same amount would move +# target and leave the distance alone, and then the bytes are identical and +# the checksum matches for the uninteresting reason. Ask about the distance. +sym_off() # $1 object, $2 symbol +{ + in_symbols "$1" | awk -v n="$2" '$NF == n { print $2; exit }' +} + +orig_t="$(sym_off orig.o target)"; orig_c="$(sym_off orig.o callee)" +new_t="$(sym_off patched.o target)"; new_c="$(sym_off patched.o callee)" + +[ -n "$orig_t" ] && [ -n "$orig_c" ] && [ -n "$new_t" ] && [ -n "$new_c" ] || + fail "target or callee missing from one of the objects" + +orig_gap=$(( 16#$orig_t - 16#$orig_c )) +new_gap=$(( 16#$new_t - 16#$new_c )) +[ "$orig_gap" != "$new_gap" ] || + probe_skip "this compiler kept target() and callee() the same distance" \ + "apart; the call displacement did not change" + +assert_checksum_matches target + +pass "checksum unchanged when the function only moves" From 638df22cc3f894d79a2d332c34739575ba50770b Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:30 -0700 Subject: [PATCH 0637/2286] objtool/klp: Add test for sympos in module objects klp-sympos.c had no coverage at all. sympos disambiguates same-named symbols for livepatch, counting from 1, with 0 meaning the name is unique. Resolving to the wrong one is not a load failure -- it is a patch quietly wired to the wrong object. Covers the module path, where the position is a count in symbol table order and klp diff can work it out from the object alone. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-38-song@kernel.org --- .../tests/generic/fixtures/sympos_dup.c | 32 ++++++++++++ tools/objtool/tests/generic/test-sympos.sh | 51 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/sympos_dup.c create mode 100755 tools/objtool/tests/generic/test-sympos.sh diff --git a/tools/objtool/tests/generic/fixtures/sympos_dup.c b/tools/objtool/tests/generic/fixtures/sympos_dup.c new file mode 100644 index 00000000000000..7eded9b12cfc80 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/sympos_dup.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A static whose name recurs in every translation unit that includes it. + * Compiled once for a single-copy object and twice, partially linked, for one + * with duplicates -- which is the only case where sympos is non-zero. + * + * FUNC_NAME keeps the referencing functions distinct so both get patched. + * Only the first copy carries .modinfo; two would be a second thing to + * disambiguate and is not what this fixture is about. + */ + +#ifndef FUNC_NAME +#define FUNC_NAME use_a +#endif + +#ifndef NO_MODINFO +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif + +/* volatile so it survives as an STT_OBJECT rather than being folded away */ +static volatile int dup_counter = 1; + +int FUNC_NAME(int x) +{ + dup_counter += x; +#ifdef PATCHED + return dup_counter + 1; +#else + return dup_counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-sympos.sh b/tools/objtool/tests/generic/test-sympos.sh new file mode 100755 index 00000000000000..b71d4930a22af6 --- /dev/null +++ b/tools/objtool/tests/generic/test-sympos.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# sympos is what livepatch uses to tell duplicate symbol names apart in the +# patched object: which "dup_counter" of several the relocation means. Get it +# wrong and the patch resolves to the wrong object at load time, silently. +# +# klp_find_sympos() reports 0 when a name is unique and a 1-based position when +# it is not, so both need checking -- always reporting a position, or never, +# each looks right in one of the two cases. +# +# This is the module path, counting symbol table order. vmlinux is reordered +# by the final link and goes through .klp.symid instead; that needs a linked +# vmlinux next to vmlinux.o and is not covered here. + +. "$(dirname "$0")/../lib.sh" + +setup + +# One copy: the name is unique, so there is nothing to disambiguate. +build_one sympos_dup.c orig.o -DFUNC_NAME=use_a +build_one sympos_dup.c patched.o -DFUNC_NAME=use_a -DPATCHED +run_diff + +assert_klp_sympos dup_counter 0 + +# Two copies: positions, in symbol table order. +for p in "" "-DPATCHED"; do + # shellcheck disable=SC2086 + build_one sympos_dup.c "a$p.o" -DFUNC_NAME=use_a $p + # shellcheck disable=SC2086 + build_one sympos_dup.c "b$p.o" -DFUNC_NAME=use_b -DNO_MODINFO $p +done +partial_link "$workdir/orig.o" "$workdir/a.o" "$workdir/b.o" || + probe_skip "partial link unavailable" +partial_link "$workdir/patched.o" "$workdir/a-DPATCHED.o" "$workdir/b-DPATCHED.o" || + probe_skip "partial link unavailable" + +# Without duplicates in the input there is nothing for sympos to number. +[ "$(count_input_symbols orig.o dup_counter)" = 2 ] || + fail "fixture did not produce two dup_counter symbols" + +run_diff + +assert_klp_sympos dup_counter 1 +assert_klp_sympos dup_counter 2 +# ... and nothing still claiming the name is unique +out_symbols | grep -qE '\.klp\.sym\.[^.]+\.dup_counter,0([[:space:]]|$)' && + fail "sympos 0 emitted for a duplicated symbol" + +pass "sympos numbers duplicate symbols and stays 0 for unique ones" From 1a50e18ba3427ce9d22c5ff28499cca242fa7f73 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:31 -0700 Subject: [PATCH 0638/2286] objtool/klp: Add test for sympos resolved against a linked vmlinux vmlinux is not like a module: the final link reorders sub-sections, so a symbol's position has to come from the linked image rather than from symbol table order. klp diff bridges that with .klp.symid, and looks for it only when the object it was handed is called vmlinux.o with a vmlinux beside it. This was assigned to an end-to-end test on the assumption that it needs a real kernel build. It needs "ld -r" and "ld -e 0", and takes a fraction of a second. The fixture places the static appearing first in the symbol table at the higher address, and the link passes --sort-section=name to force the reordering the kernel's linker script performs. Without that the two ways of computing sympos agree, and a first version passed with the vmlinux path disabled. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-39-song@kernel.org --- .../tests/generic/fixtures/sympos_vmlinux.c | 40 +++++++++++++ .../tests/generic/test-sympos-vmlinux.sh | 57 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/sympos_vmlinux.c create mode 100755 tools/objtool/tests/generic/test-sympos-vmlinux.sh diff --git a/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c b/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c new file mode 100644 index 00000000000000..d5e70994c582d1 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/sympos_vmlinux.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two translation units with a same-named static, placed so that the linker + * puts them in the opposite order to the one they appear in the symbol table. + * + * VARSEC selects the section the static lands in. Linking with + * --sort-section=name then orders them alphabetically rather than by object + * order, so the first symbol in the symbol table ends up at the *higher* + * address. That is the whole point: counting symbol table order and reading + * the linked image's addresses now give different answers, which is what makes + * it possible to tell which one klp diff used. + * + * Only use_a is patched, so exactly one sympos is emitted and there is nothing + * to attribute. + */ + +#ifndef FUNC_NAME +#define FUNC_NAME use_a +#endif +#ifndef VARSEC +#define VARSEC ".data.mmm" +#endif + +#ifndef NO_MODINFO +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif + +/* volatile so it survives as an STT_OBJECT rather than being folded away */ +static volatile int dup_counter __attribute__((section(VARSEC))) = 1; + +int FUNC_NAME(int x) +{ + dup_counter += x; +#ifdef PATCHED + return dup_counter + 1; +#else + return dup_counter; +#endif +} diff --git a/tools/objtool/tests/generic/test-sympos-vmlinux.sh b/tools/objtool/tests/generic/test-sympos-vmlinux.sh new file mode 100755 index 00000000000000..b2b44001446f8a --- /dev/null +++ b/tools/objtool/tests/generic/test-sympos-vmlinux.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# sympos for vmlinux, which is resolved differently from sympos for a module. +# +# A module's .ko preserves symbol table order, so klp diff can count -- that is +# what test-sympos covers. vmlinux cannot be counted: the final link reorders +# sub-sections, so the order in vmlinux.o is not the order the running kernel +# has. klp diff bridges that with .klp.symid, a table of { id, address } +# emitted into vmlinux.o whose addresses the linker resolves, read back out of +# the linked vmlinux. +# +# Getting it wrong points the relocation at a different symbol of the same +# name. Nothing fails to build or load; the patched code uses the wrong +# object. +# +# The fixture is arranged so the two answers differ: the static that comes +# first in the symbol table is placed at the *higher* address, so counting +# gives 1 and reading the linked image gives 2. Without that, both paths agree +# and the test cannot tell them apart. + +. "$(dirname "$0")/../lib.sh" + +setup + +# use_a's static sorts last by section name, use_b's first. Only use_a is +# patched, so exactly one sympos comes out. +build_one sympos_vmlinux.c orig_a.o -DFUNC_NAME=use_a -DVARSEC='".data.zzz"' +build_one sympos_vmlinux.c patched_a.o -DFUNC_NAME=use_a -DVARSEC='".data.zzz"' -DPATCHED +build_one sympos_vmlinux.c b.o -DFUNC_NAME=use_b -DVARSEC='".data.aaa"' -DNO_MODINFO + +make_vmlinux_pair "$workdir/orig_a.o" "$workdir/b.o" \ + -- "$workdir/patched_a.o" "$workdir/b.o" + +[ "$(count_input_symbols vmlinux.o dup_counter)" = 2 ] || + fail "fixture did not produce two dup_counter symbols" +has_input_section vmlinux.o .klp.symid || + fail "objtool --klp-symids emitted no .klp.symid table" +has_input_section vmlinux .klp.symid || + fail ".klp.symid did not survive the link" + +# The premise: symbol table order and address order must disagree, or the test +# proves nothing. +first_addr="$(in_symbols vmlinux | awk '$8 == "dup_counter" { print $2; exit }')" +low_addr="$(in_symbols vmlinux | awk '$8 == "dup_counter" { print $2 }' | sort | head -1)" +[ "$first_addr" != "$low_addr" ] || + probe_skip "linker did not reorder the two statics" + +assert_input_symbol dup_counter +run_diff + +# Address order says 2. Counting symbol table order would say 1. +assert_klp_sympos dup_counter 2 +out_symbols | grep -q 'dup_counter,1' && + fail "sympos 1 emitted: counted symbol table order instead of reading the linked vmlinux" + +pass "vmlinux sympos taken from the linked image, not from symbol table order" From 2e49f7a8391ac75f25fc56f63747b86dcd320baf Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:32 -0700 Subject: [PATCH 0639/2286] objtool/klp: Add test for static locals which must not be correlated Most static locals have to be correlated so the patched code keeps using the running kernel's copy. Two kinds must not: anything in .data..once, the flag behind WARN_ONCE and friends, and the well-known names the kernel generates for per-instance things (__warned, __key, __func__). Sharing a .data..once flag means a patch inherits "already warned" from before it was applied, and the warning it was meant to surface never fires. The fixture deliberately does not name its .data..once variable __warned: the name rule would then catch it and the section rule would go untested. gcc spells these . and Clang ., so both are covered. This tests the behavior of commit ff529864e738 ("objtool/klp: Fix .data..once static local non-correlation") and commit 84c304a534b8 ("objtool/klp: Fix is_uncorrelated_static_local() for Clang"). Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-40-song@kernel.org --- .../fixtures/static_local_uncorrelated.c | 41 +++++++++++++++++++ .../generic/test-static-local-uncorrelated.sh | 41 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c create mode 100755 tools/objtool/tests/generic/test-static-local-uncorrelated.sh diff --git a/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c b/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c new file mode 100644 index 00000000000000..cb4cdd7a496e3b --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/static_local_uncorrelated.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Static locals of three kinds, in one patched function. + * + * Most static locals must be correlated, so the patched code keeps using the + * running kernel's copy. Two kinds must not: + * + * - anything in .data..once, the flag behind WARN_ONCE and friends. Sharing + * it would mean a patch inherits "already warned" from before the patch. + * - the well-known names the kernel generates for such things (__warned, + * __key, __func__, ...), which are per-instance by nature. gcc names them + * . and Clang ., so both spellings have to be caught. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + /* + * A .data..once variable whose name is *not* on the list below, so + * only the section can disqualify it. Naming it __warned would let + * the name rule catch it and the section rule go untested. + */ + static int once_flag __attribute__((section(".data..once"))); + /* a never-correlate name, in an ordinary section */ + static int __key; + /* and one that must be correlated */ + static int ordinary; + + if (!once_flag) + once_flag = 1; + __key += x; + ordinary += x; + +#ifdef PATCHED + return __key + ordinary + once_flag + 2; +#else + return __key + ordinary + once_flag + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-static-local-uncorrelated.sh b/tools/objtool/tests/generic/test-static-local-uncorrelated.sh new file mode 100755 index 00000000000000..d7711587d2d4a6 --- /dev/null +++ b/tools/objtool/tests/generic/test-static-local-uncorrelated.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Some static locals must not be correlated with their counterparts in the +# running kernel; the patched code has to use a fresh copy instead. +# +# .data..once holds the "have we warned yet" flags behind WARN_ONCE. Correlate +# one and the patched function inherits the flag from before the patch, so the +# warning the patch was written to produce never fires. The same goes for the +# names the kernel generates for per-instance state -- __warned, __key, +# __func__ and friends. +# +# Both directions matter, so an ordinary static local is here too: a rule that +# refuses to correlate anything would pass a test that only checks the +# refusals. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_local_uncorrelated.c +run_diff + +# Compilers mangle static locals differently -- gcc gives __key.1, Clang +# target.__key -- so match on the base name. + +# Correlated: referenced through a klp symbol, pointing at the kernel's copy. +out_symbols | grep -q '\.klp\.sym\..*ordinary' || + fail "ordinary static local was not correlated" + +# Not correlated: no klp symbol, and a copy cloned into the patch instead. +out_symbols | grep -q '\.klp\.sym\..*__key' && + fail "__key was correlated; it must use a fresh copy" +# .sbss/.sdata on the architectures with a small-data area. +out_sections | grep -qE '\.s?(bss|data)[^ ]*__key' || + fail "__key was neither correlated nor cloned" + +out_symbols | grep -q '\.klp\.sym\..*once_flag' && + fail ".data..once variable was correlated; it must use a fresh copy" +assert_section '.data..once' + +pass "per-instance static locals cloned, ordinary ones correlated" From ce30479ff1d69654e455cf06b043839d43b63628 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:33 -0700 Subject: [PATCH 0640/2286] objtool/klp: Add test for __bug_table, __ex_table and __mcount_loc extraction Three special sections with no coverage. An entry that is not carried into the patch is not a build failure: the patched function simply loses its WARN_ON location, its exception fixup, or its ftrace callsite, and nobody finds out until one of them is needed. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-41-song@kernel.org --- .../tests/x86/fixtures/special_sections.c | 77 +++++++++++++++++++ .../tests/x86/test-special-sections.sh | 42 ++++++++++ 2 files changed, 119 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/special_sections.c create mode 100755 tools/objtool/tests/x86/test-special-sections.sh diff --git a/tools/objtool/tests/x86/fixtures/special_sections.c b/tools/objtool/tests/x86/fixtures/special_sections.c new file mode 100644 index 00000000000000..d42798848fda04 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/special_sections.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A special section entry belonging to a patched function. SPECIAL_SEC picks + * which section, since klp diff treats eight of them alike and each needs + * extracting for the patched function and no other. + * + * The entry is written out by hand so the fixture builds without kernel + * headers. Only the leading relocation matters to klp diff; the rest is + * padded to the section's real entry size, because the entries have to be the + * right length for the boundaries between them to fall in the right places. + * + * SPECIAL_RELOCS covers __ex_table, whose entries relocate both the faulting + * instruction and its fixup; objtool rejects one with only the first. + */ + +#ifndef SPECIAL_SEC +#define SPECIAL_SEC "__bug_table" +#endif +#ifndef SPECIAL_ENTSIZE +#define SPECIAL_ENTSIZE 12 +#endif +#ifndef SPECIAL_RELOCS +#define SPECIAL_RELOCS 1 +#endif + +#define STR_(x) #x +#define STR(x) STR_(x) + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* + * other() gets an entry of its own, so that "the untouched function's entry + * was not dragged in" is a question the section can actually answer. With + * only target() contributing, there is nothing for klp diff to leave behind + * and the negative assertion holds however the extraction behaves. + */ +int other(int x) +{ + asm volatile( + "3: nop\n\t" + "4:\n\t" + ".pushsection " SPECIAL_SEC ", \"aM\", @progbits, " + STR(SPECIAL_ENTSIZE) "\n\t" + ".long 3b - .\n\t" +#if SPECIAL_RELOCS > 1 + ".long 4b - .\n\t" + ".fill " STR(SPECIAL_ENTSIZE) " - 8, 1, 0\n\t" +#else + ".fill " STR(SPECIAL_ENTSIZE) " - 4, 1, 0\n\t" +#endif + ".popsection\n\t"); + + return x + 9; +} + +int target(int x) +{ + asm volatile( + "1: nop\n\t" + "2:\n\t" + ".pushsection " SPECIAL_SEC ", \"aM\", @progbits, " + STR(SPECIAL_ENTSIZE) "\n\t" + ".long 1b - .\n\t" +#if SPECIAL_RELOCS > 1 + ".long 2b - .\n\t" + ".fill " STR(SPECIAL_ENTSIZE) " - 8, 1, 0\n\t" +#else + ".fill " STR(SPECIAL_ENTSIZE) " - 4, 1, 0\n\t" +#endif + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-special-sections.sh b/tools/objtool/tests/x86/test-special-sections.sh new file mode 100755 index 00000000000000..8dfaa4fc9a360f --- /dev/null +++ b/tools/objtool/tests/x86/test-special-sections.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# klp diff extracts entries from eight special sections. Between them the +# existing tests reach .kcfi_traps, __jump_table, .static_call_sites and +# .altinstructions; __bug_table, __ex_table and __mcount_loc are covered by +# nothing, though the same extraction code serves all of them. +# +# Losing an entry is quiet in every case and wrong in a different way for each: +# a WARN() in patched code that no longer reports where it came from, an +# exception fixup that is simply not there when the faulting instruction traps, +# a function ftrace can no longer see. + +. "$(dirname "$0")/../lib.sh" + + +# section, entry size, relocations per entry +for spec in "__bug_table 12 1" "__ex_table 12 2" "__mcount_loc 8 1"; do + set -- $spec + sec=$1 + + # A fresh workdir per section: run_diff caches its checksums. + setup + build_pair special_sections.c \ + -DSPECIAL_SEC="\"$1\"" -DSPECIAL_ENTSIZE="$2" -DSPECIAL_RELOCS="$3" + + assert_input_section "$sec" + run_diff + + # Extracted, and pointing at the function that was patched. + assert_section "$sec" + assert_reloc_sym "$sec" target + assert_patched target + + # Nothing belonging to the function that was not. + assert_not_patched other + assert_no_reloc_sym "$sec" other + + cleanup +done + +pass "__bug_table, __ex_table and __mcount_loc entries extracted" From 62ef20415b92885574e093f2cbd3a8202a696365 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:34 -0700 Subject: [PATCH 0641/2286] objtool/klp: Add test for kCFI prefix symbols and traps Two properties, asserted separately because they fail differently: - the __cfi_ prefix symbol has to be carried with the patched function, or the function has no type identity and indirect calls to it trap; - its .kcfi_traps entry has to be extracted, or the trap is not recognised as a CFI failure and a clean report becomes an oops. test-special-section hand-assembles a .kcfi_traps entry, which is what makes it catch commit 7df1638df97b ("objtool/klp: Fix .kcfi_traps special section extraction"). Nothing until now built real kCFI code. This tests the behavior of commit da4326573ae8 ("objtool/klp: Fix kCFI trap handling"). Commit f7ceffd21a8a ("objtool/klp: Fix kCFI prefix finding/cloning") cannot be tested as such, its code having been rewritten by commit fe6a87e0abac ("objtool: Improve and simplify prefix symbol detection"); this guards the current implementation's behaviour instead. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-42-song@kernel.org --- tools/objtool/tests/x86/fixtures/kcfi.c | 39 +++++++++++++++++++++++++ tools/objtool/tests/x86/test-kcfi.sh | 39 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/kcfi.c create mode 100755 tools/objtool/tests/x86/test-kcfi.sh diff --git a/tools/objtool/tests/x86/fixtures/kcfi.c b/tools/objtool/tests/x86/fixtures/kcfi.c new file mode 100644 index 00000000000000..b62fc60634cf22 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/kcfi.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An indirect call, which under kCFI is preceded by a type check and a trap. + * + * Clang emits a __cfi_ prefix symbol carrying the type hash ahead of + * every address-taken function, and records the trap site in .kcfi_traps. + * Both belong to the function and both have to come with it into a patch. + * + * Needs -fsanitize=kcfi, which only Clang has. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +static int impl_a(int x) +{ + return x + 1; +} + +static int impl_b(int x) +{ + return x * 2; +} + +__attribute__((noinline)) int (*pick(int x))(int) +{ + return (x & 1) ? impl_a : impl_b; +} + +int target(int x) +{ + int (*fn)(int arg) = pick(x); + +#ifdef PATCHED + return fn(x) + 2; +#else + return fn(x) + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-kcfi.sh b/tools/objtool/tests/x86/test-kcfi.sh new file mode 100755 index 00000000000000..b583ef6087472f --- /dev/null +++ b/tools/objtool/tests/x86/test-kcfi.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Under kCFI an indirect call checks a type hash before jumping, and traps on a +# mismatch. Two things belong to the calling function and must come with it +# into a patch: +# +# - the __cfi_ prefix symbol holding the hash. Lose it and the patched +# function has no type identity, so indirect calls to it trap. +# - its .kcfi_traps entry. Lose that and the trap is not recognised as a +# CFI failure, so what should be a clean report becomes an oops. +# +# Neither shows up at build time. + +. "$(dirname "$0")/../lib.sh" + +clang_only "kCFI is a Clang feature" + +setup + +# Declared above that this is Clang's; a given Clang may still be too old. +cc_supports -fsanitize=kcfi || + probe_skip "this clang does not support -fsanitize=kcfi" + +build_pair kcfi.c -fsanitize=kcfi + +assert_input_section .kcfi_traps +assert_input_symbol __cfi_target + +run_diff + +assert_patched target + +# The prefix symbol comes with its function ... +assert_symbol __cfi_target +# ... and so does the trap entry. +assert_section .kcfi_traps + +pass "kCFI prefix symbol and trap entry carried with the patched function" From 2bbf68f4cfc3518d51e5202b065bf032e9a92938 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:35 -0700 Subject: [PATCH 0642/2286] objtool/klp: Add test for symbols whose linkage the patch changes A patch can move a symbol between static and global without renaming it: dropping "static" from a helper so something else can call it, or adding it to one that is no longer shared. Correlation keys off more than the name, so a symbol whose binding moved has to still pair with itself. Failing to is not a build failure. The symbol looks new, and a new data symbol is either rejected or cloned as a second copy -- at which point the patched code updates its own private variable while the rest of the kernel keeps reading the original. The test covers both directions in one fixture, a function going global and a variable going static, and asserts the outcome rather than the absence of a warning: each symbol resolves back to the kernel's copy through a klp symbol, and neither is cloned into the patch. An earlier version asserted only that no "no correlation" or "changed data" message appeared, and passed with correlation deliberately broken. What the messages say and what the patch contains are not the same question. Verified to fail with correlation made to require matching symbol bindings. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-43-song@kernel.org --- .../tests/generic/fixtures/local_to_global.c | 34 ++++++++++ .../generic/test-local-to-global-flip.sh | 63 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/local_to_global.c create mode 100755 tools/objtool/tests/generic/test-local-to-global-flip.sh diff --git a/tools/objtool/tests/generic/fixtures/local_to_global.c b/tools/objtool/tests/generic/fixtures/local_to_global.c new file mode 100644 index 00000000000000..3c9eb9200ce54f --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/local_to_global.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A function which the patch changes from static to non-static, and a variable + * that goes the other way. The names are unchanged; only the binding moves. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* noinline, or the static one is folded into its caller and has no symbol */ +#ifdef PATCHED +__attribute__((noinline)) int flipped_up(int x) /* was static */ +#else +__attribute__((noinline)) static int flipped_up(int x) +#endif +{ + return x + 1; +} + +#ifdef PATCHED +static volatile int flipped_down = 5; /* was global */ +#else +volatile int flipped_down = 5; +#endif + +int caller(int x) +{ + flipped_down += x; +#ifdef PATCHED + return flipped_up(x) + flipped_down + 2; +#else + return flipped_up(x) + flipped_down + 1; +#endif +} diff --git a/tools/objtool/tests/generic/test-local-to-global-flip.sh b/tools/objtool/tests/generic/test-local-to-global-flip.sh new file mode 100755 index 00000000000000..45e92797190b61 --- /dev/null +++ b/tools/objtool/tests/generic/test-local-to-global-flip.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch can change a symbol's linkage without renaming it: dropping "static" +# from a helper so something else can call it, or adding it to one that is no +# longer shared. Correlation keys off more than the name, so a symbol whose +# binding moved can fail to pair with itself. +# +# Failing to correlate is not a build failure. The symbol looks new, and a +# "new" data symbol is either rejected or cloned as a second copy -- at which +# point the patched code updates its own private variable and the rest of the +# kernel keeps reading the original. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair local_to_global.c + +# Confirm the fixture really moved the bindings, in both directions. +# +# Match the binding and the name as fields, not as substrings of the line. +# -ffunction-sections and -fdata-sections give these symbols sections of their +# own, and the section symbols -- .text.flipped_up, .data.flipped_down -- are +# always LOCAL, so "does a LOCAL line mention flipped_up" is answered by the +# wrong symbol. GNU readelf 2.35 happens to leave section symbol names blank, +# but llvm-readelf prints them, and a premise that holds on one readelf and +# not the other is no premise at all. +has_binding() # $1 object, $2 binding, $3 symbol +{ + in_symbols "$1" | awk -v b="$2" -v n="$3" '$5 == b && $NF == n' | grep -q . +} + +has_binding orig.o LOCAL flipped_up || + fail "flipped_up is not local in the original" +has_binding patched.o GLOBAL flipped_up || + fail "flipped_up is not global in the patched object" +has_binding orig.o GLOBAL flipped_down || + fail "flipped_down is not global in the original" +has_binding patched.o LOCAL flipped_down || + fail "flipped_down is not local in the patched object" + +run_diff + +assert_diff_log 'changed function: caller' + +# Correlated means each pairs with its own counterpart in the original, so the +# patch refers back to the kernel's copy ... +assert_klp_sym flipped_up vmlinux +assert_klp_sym flipped_down vmlinux + +# ... rather than carrying its own. A second copy of flipped_down is the bad +# outcome: patched code would update its private one while the rest of the +# kernel keeps reading the original. +assert_not_patched flipped_up +assert_no_section .data.flipped_down +assert_no_section .bss.flipped_down + +diff_log | grep -q 'no correlation' && + fail "linkage change reported as an uncorrelated symbol" +diff_log | grep -q 'changed data' && + fail "linkage change reported as changed data" + +pass "symbols correlated across a change of linkage" From 58b0d66387b36fbf3df9e554ff4968f4df65ccbb Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:36 -0700 Subject: [PATCH 0643/2286] objtool/klp: Test rejection of a file-local static branch key A static branch key owned by a module cannot be reached with a klp reloc: late module patching allows the livepatch module to load first, leaving the __jump_table entry unresolved for jump_label_add_module() to dereference. validate_special_section_klp_reloc() rejects it at build time. test-jump-label-module-key covers that for a global key. A file-local one takes a different route to the same check: the compiler references a static through its section symbol plus an addend, so the key has to be resolved from the section before it can be recognised as STT_OBJECT at all. Until commit f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static branch/call entries") it was not, and the reference was silently emitted. Give the fixture a STATIC_KEY knob and cover it. The test checks that the input really does reference the key through its section, since without that it is only a second copy of the existing test. Verified by reverting commit f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static branch/call entries"): klp diff accepts the input and the test fails, under both gcc and clang. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-44-song@kernel.org --- .../test-jump-label-module-static-key.sh | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-module-static-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-module-static-key.sh b/tools/objtool/tests/generic/test-jump-label-module-static-key.sh new file mode 100755 index 00000000000000..7bdcbaf2fa60ad --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-module-static-key.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A static branch key owned by a module is rejected whether the key is global +# or file-local. +# +# The rejection matters because late module patching allows the livepatch +# module to load before the module it depends on: the __jump_table klp reloc is +# then unresolved, and jump_label_add_module() dereferences an uninitialized +# pointer. Catching it at build time is the only defence. +# +# test-jump-label-module-key covers the global key. A file-local one reaches +# the same check by a different route: the compiler emits the reference against +# the section symbol plus an addend, so validate_special_section_klp_reloc() +# has to resolve it to the underlying object before it can see a key at all. +# Until it did, a static key was passed over as "not STT_OBJECT" and the +# unsupported reference was emitted with nothing said. +# +# Fixed by f9fb44b0ecef ("objtool/klp: Fix detection of corrupt static +# branch/call entries"). + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair jump_label.c -DSTATIC_KEY -DMODNAME='"klp_testmod"' + +require_input_section __jump_table + +# The premise: the key is reached through its section symbol, not by name. +# Without that this is just a second copy of test-jump-label-module-key. +input_jump_relocs="$(in_relocs orig.o | awk '/rela__jump_table/,/^$/')" + +echo "$input_jump_relocs" | grep -q klp_test_key || + fail "fixture produced no __jump_table reference to the key" +echo "$input_jump_relocs" | grep -qE '\.(bss|data)\.klp_test_key' || + probe_skip "compiler referenced the static key by name, not through its section" + +run_diff 255 + +diff_log | grep -q 'unsupported static branch key klp_test_key' || + fail "expected rejection, got: $(diff_log | tail -1)" +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "module-owned file-local static branch key rejected" From 841c8c575765e00bc55f15924e47a43044012a2c Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:37 -0700 Subject: [PATCH 0644/2286] objtool/klp: Test a hand-built livepatch module's static call keys __SCK__* static call keys are not exported; modules are given read-only access at load time. Livepatch modules built by klp-build do have full access to theirs, and commit 164c9201e1da ("objtool: Add base objtool support for livepatch modules") added a check on that basis -- but a livepatch module can also be written by hand, as everything under samples/livepatch is, and such a module hits an unexported key as soon as it does anything expanding to a static call. With CONFIG_MEM_ALLOC_PROFILING_DEBUG that includes allocating memory, which is how livepatch-shadow-fix1 came to fail to build. Cover it, with the plain module as a control: it takes the same path and has always been accepted, so a test that built only the livepatch variant could not tell this fix from the check being deleted. This is objtool's ordinary check pass rather than a klp subcommand, which is the first test here to exercise it -- and is the point, since that pass is what runs over a hand-built livepatch module during a normal kernel build. Verified by reverting commit f495054bd12e ("objtool/klp: Fix unexported static call key access for manually built livepatch modules"): objtool reports "can't find static_call_key symbol: __SCK__klp_test_call" and the test fails, under both gcc and clang. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-45-song@kernel.org --- .../tests/x86/fixtures/static_call_no_key.c | 32 +++++++++++++++ .../tests/x86/test-manual-klp-static-call.sh | 40 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/static_call_no_key.c create mode 100755 tools/objtool/tests/x86/test-manual-klp-static-call.sh diff --git a/tools/objtool/tests/x86/fixtures/static_call_no_key.c b/tools/objtool/tests/x86/fixtures/static_call_no_key.c new file mode 100644 index 00000000000000..748ba7c0f86dff --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/static_call_no_key.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A static call to a trampoline whose key symbol this object cannot see. + * + * That is the normal situation for a module: __SCK__* keys are not exported, + * and read-only access is granted at load time instead. objtool's static call + * handling has to accept it for any module, including a livepatch module built + * by hand rather than by klp-build. + * + * LIVEPATCH adds the .modinfo tag which makes objtool treat this as a + * livepatch module. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = +#ifdef LIVEPATCH + "\0livepatch=Y" +#endif + "\0name=klp_testmod"; + +/* + * The trampoline is undefined here, exactly as it is for a module calling a + * static call defined in vmlinux. No __SCK__klp_test_call accompanies it. + */ +extern void __SCT__klp_test_call(void); + +int target(int x) +{ + __asm__ volatile("call __SCT__klp_test_call\n\t" ::: "memory"); + + return x + 1; +} diff --git a/tools/objtool/tests/x86/test-manual-klp-static-call.sh b/tools/objtool/tests/x86/test-manual-klp-static-call.sh new file mode 100755 index 00000000000000..6c4d275e35491f --- /dev/null +++ b/tools/objtool/tests/x86/test-manual-klp-static-call.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# objtool's static call handling must accept a livepatch module which cannot +# see a static call's key symbol. +# +# __SCK__* keys are not exported; modules get read-only access at load time +# instead. Livepatch modules built by klp-build do have full access to their +# keys, and a check was added on the strength of that -- but a livepatch module +# can also be written by hand, and samples/livepatch is full of them. One of +# those needs a key it cannot see as soon as it does anything that expands to a +# static call, which with CONFIG_MEM_ALLOC_PROFILING_DEBUG includes allocating +# memory: +# +# samples/livepatch/livepatch-shadow-fix1.o: error: objtool: static_call: +# can't find static_call_key symbol: __SCK__WARN_trap +# +# The module built without the livepatch tag is the control: it takes the same +# path and has always been accepted, so a test which only built the livepatch +# one could not tell this fix from the check being removed altogether. +# +# Fixed by f495054bd12e ("objtool/klp: Fix unexported static call key access +# for manually built livepatch modules"). + +. "$(dirname "$0")/../lib.sh" + +setup + +# Not a klp subcommand: this is objtool's ordinary check pass, which is what +# runs over a hand-built livepatch module during a normal kernel build. +for tag in "" -DLIVEPATCH; do + build_one static_call_no_key.c mod.o $tag + + "$OBJTOOL" --module --static-call "$workdir/mod.o" \ + > "$workdir/objtool.log" 2>&1 || + fail "objtool rejected a ${tag:+livepatch }module which cannot" \ + "see its static call key: $(tail -1 "$workdir/objtool.log")" +done + +pass "livepatch module accepted without access to its static call key" From c4717a1628dda5b48e68544d82bddf2fe3465615 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:38 -0700 Subject: [PATCH 0645/2286] objtool/klp: Test text annotations on alternative replacements The kernel annotates instructions inside an ALTERNATIVE's replacement wherever objtool has to be told something about them -- a retpoline-safe indirect branch, a deliberately absent ENDBR. klp diff dropped those annotations, for two reasons: replacement code has no real symbol, so the NOTYPE fake symbol objtool invents for it was not recognised as worth keeping a reference to, and .discard.annotate_insn was processed before .altinstructions, so the replacement it named had no clone yet. Nothing fails at build time when the annotation goes missing. It surfaces later as objtool warning about, or rejecting, the very code the annotation was there to explain. The fixture keeps the replacement label global, as empty_alternative.c does, so the relocations name it instead of .altinstr_replacement plus an addend -- which klp diff rejects outright. The label is still NOTYPE, which is the property under test. The test asserts the relocation as well as the section, since an entry whose relocation was dropped would otherwise pass. Verified by reverting commit 62a7a01fde87 ("objtool/klp: Fix extraction of text annotations for alternatives"): the output has no .discard.annotate_insn at all and the test fails, under both gcc and clang. Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Assisted-by: Claude:claude-opus-5 Link: https://patch.msgid.link/20260916184351.2720310-46-song@kernel.org --- .../objtool/tests/x86/fixtures/alt_annotate.c | 57 +++++++++++++++++++ .../objtool/tests/x86/test-alt-annotation.sh | 38 +++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/alt_annotate.c create mode 100755 tools/objtool/tests/x86/test-alt-annotation.sh diff --git a/tools/objtool/tests/x86/fixtures/alt_annotate.c b/tools/objtool/tests/x86/fixtures/alt_annotate.c new file mode 100644 index 00000000000000..af44d320dcb05b --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/alt_annotate.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative whose replacement instruction carries a text annotation. + * + * The kernel does this wherever an ALTERNATIVE contains something objtool has + * to be told about -- a retpoline-safe indirect branch, an intentionally + * missing ENDBR -- so the .discard.annotate_insn entry references an address + * inside .altinstr_replacement rather than inside a function. + * + * Two things make that awkward for klp diff, and both are why this fixture + * exists. Replacement code has no real symbol: objtool invents a NOTYPE fake + * symbol for it, so an annotation pointing there does not reference a FUNC. + * And .discard.annotate_insn has to be cloned after .altinstructions, or the + * replacement it names has no clone to point at yet. + * + * struct alt_instr is written out by hand as in empty_alternative.c: s32 + * instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, u8 replacementlen, + * with an entsize so klp diff can find the entry boundaries. + * .discard.annotate_insn entries are s32 offset, s32 type; type 2 is + * ANNOTYPE_RETPOLINE_SAFE. + * + * The replacement label is global so the relocations name it rather than + * .altinstr_replacement plus an addend, which klp diff cannot convert. It is + * still NOTYPE, which is the shape that matters here. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + " nop\n\t" + /* The annotation lands inside the replacement. */ + ".pushsection .discard.annotate_insn, \"M\", @progbits, 8\n\t" + ".long target_repl - .\n\t" + ".long 2\n\t" + ".popsection\n\t" + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long 0\n\t" + ".byte 1\n\t" + ".byte target_repl_end - target_repl\n\t" + ".popsection\n\t"); +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} diff --git a/tools/objtool/tests/x86/test-alt-annotation.sh b/tools/objtool/tests/x86/test-alt-annotation.sh new file mode 100755 index 00000000000000..96760e6df9e5f5 --- /dev/null +++ b/tools/objtool/tests/x86/test-alt-annotation.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A text annotation on an instruction inside an alternative's replacement must +# be carried into the patch. +# +# The kernel annotates replacement code wherever objtool has to be told +# something about it -- a retpoline-safe indirect branch, a deliberately absent +# ENDBR. Two things made klp diff drop those annotations: +# +# - replacement code has no real symbol, so objtool invents a NOTYPE fake +# one, and the extraction only kept references to FUNC symbols; +# - .discard.annotate_insn was processed before .altinstructions, so the +# replacement it referenced had no clone to point at yet. +# +# Nothing fails at build time when the annotation goes missing. It surfaces +# later as objtool warning about, or rejecting, the patched code it was there +# to explain. +# +# Fixed by 62a7a01fde87 ("objtool/klp: Fix extraction of text annotations for +# alternatives"). + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair alt_annotate.c + +assert_input_section .altinstructions +assert_input_section .discard.annotate_insn + +run_diff + +# The annotation has to survive, and to still name the replacement. Checking +# only the section would pass on an entry whose relocation was dropped. +assert_section .discard.annotate_insn +assert_reloc_sym .discard.annotate_insn target_repl + +pass "text annotation on an alternative replacement carried into the patch" From ac324798c6897a11b89667ba58a4757a57aa1505 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:39 -0700 Subject: [PATCH 0646/2286] objtool/klp: Add test for data object checksums klp checksum hashes a data symbol's length, its bytes, and every relocation it carries -- as the target's name and adjusted addend, except a reference into a string section, which contributes the string's contents instead. Nothing covered any of it. Each is load-bearing, and the failure is always the same shape: a checksum which ignores one calls a changed object unchanged, klp diff leaves it out of the patch, and the patched code goes on reading the kernel's old copy. The string case cannot be caught by hashing bytes: the pointer is identical, same section and same offset, and only the text it refers to moved. One fixture, six variants applied to the patched build alone. Each was verified by sabotaging the line it covers and watching the test fail: raw bytes initialiser change length a .bss object grows; its bytes are never hashed string contents literal edited in place, pointer untouched reloc target name pointer moved to another function reloc addend same array, different index section-symbol path the same, via a static's section symbol Two of those needed the fixture rebuilding. An initialised array does not isolate the length, because growing one changes the hashed bytes too -- hence .bss, where there are none. And a named char[] does not reach the contents-hashing path at all: that keys on SHF_STRINGS, which the compiler sets on the mergeable section a literal lands in and not on an array given a section of its own. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-47-song@kernel.org --- .../tests/generic/fixtures/checksum_data.c | 116 ++++++++++++++++++ .../tests/generic/test-checksum-data.sh | 61 +++++++++ 2 files changed, 177 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_data.c create mode 100755 tools/objtool/tests/generic/test-checksum-data.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_data.c b/tools/objtool/tests/generic/fixtures/checksum_data.c new file mode 100644 index 00000000000000..6310af5c02d348 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_data.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Data objects whose checksums must move for reasons the raw bytes do not + * show. + * + * checksum_update_object() hashes a data symbol's length and its bytes, and + * then walks its relocations: a reference into a string section contributes + * the string's *contents*, and any other reference contributes the target + * symbol's name and the adjusted addend. So three changes that leave the + * object's own bytes identical still have to change its checksum: + * + * Each variant is selected by a -D on the patched build only, so the original + * is always the baseline: + * + * WHICH_FUNC the function pointer points somewhere else + * WHICH_STR the string pointer points at a different literal + * STR_CONTENT the string it points at is edited in place + * WHICH_SLOT the same array, at a different index: addend only + * WHICH_PRIV likewise, but a static, reached through its section symbol + * + * The last is the interesting one. Nothing in the pointer changes -- same + * section, same offset -- so a checksum that hashed only the relocation and + * not what it referred to would call the object unchanged, and the patched + * kernel would keep the old string. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int callee_a(int x); +int callee_b(int x); +int callee_a(int x) { return x + 1; } +int callee_b(int x) { return x + 2; } + +/* + * String *literals*, not named arrays. The contents-hashing path keys on + * SHF_STRINGS, which the compiler sets on the mergeable .rodata.str1.1 a + * literal lands in and not on a named char[] given a section of its own. A + * fixture using the latter exercises the ordinary name-and-addend path and + * reports nothing when the text changes. + */ +#if defined(PATCHED) && defined(STR_CONTENT) +#define MESSAGE "edited" +#else +#define MESSAGE "original" +#endif + +/* A plain data object: only its own bytes decide the checksum. */ +#if defined(PATCHED) && defined(PLAIN_VALUE) +int plain = 43; +#else +int plain = 42; +#endif + +/* + * A .bss object, where length is the only thing there is to hash: the section + * has no data, so the bytes are skipped and only sym->len distinguishes this + * from an object of another size. An initialised array would not isolate it + * -- growing one changes the hashed bytes as well. + */ +#if defined(PATCHED) && defined(LONGER) +char sized[4]; +#else +char sized[2]; +#endif + +/* + * A reference into the middle of an array: same target symbol, different + * addend. Nothing else in the object changes, so this is the only way to see + * whether the addend is hashed at all. + */ +int slots[4]; + +/* + * A file-local array. A reference to a static lands on its section symbol + * plus an offset, so the hash has to resolve that back to the underlying + * object before it has a name to hash at all -- a different code path from the + * global above, and one that silently contributes nothing when it fails. + */ +static int priv_slots[4]; + +struct desc { + int (*fn)(int arg); + const char *str; + int *slot; + int *priv; +}; + +const struct desc descriptor = { +#if defined(PATCHED) && defined(WHICH_FUNC) + .fn = callee_b, +#else + .fn = callee_a, +#endif +#if defined(PATCHED) && defined(WHICH_STR) + .str = "a different literal", +#else + .str = MESSAGE, +#endif +#if defined(PATCHED) && defined(WHICH_SLOT) + .slot = &slots[2], +#else + .slot = &slots[1], +#endif +#if defined(PATCHED) && defined(WHICH_PRIV) + .priv = &priv_slots[3], +#else + .priv = &priv_slots[1], +#endif +}; + +int target(int x) +{ + return descriptor.fn(x) + plain + sized[0] + (int)descriptor.str[0] + + *descriptor.slot + *descriptor.priv; +} diff --git a/tools/objtool/tests/generic/test-checksum-data.sh b/tools/objtool/tests/generic/test-checksum-data.sh new file mode 100755 index 00000000000000..e915026b79a73f --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-data.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# What a data object's checksum has to cover. +# +# checksum_update_object() hashes the symbol's length, its bytes (when the +# section has any -- .bss does not), and then +# every relocation it carries -- as the target's name plus the adjusted addend, +# except for a reference into a string section, which contributes the string's +# contents instead. +# +# Each of those is load-bearing, and the failure is always the same shape: a +# checksum that ignores one of them calls a changed object unchanged, klp diff +# leaves it out of the patch, and the patched code goes on reading the +# kernel's old copy. Nothing says so at build time. +# +# The string case is the one that cannot be caught by hashing bytes alone. The +# pointer is identical -- same section, same offset -- and only the text it +# refers to moved. +# +# Covers the same ground as corpus/x86_64/checksum-data-basic, +# checksum-data-func-ptr, checksum-data-string-ptr and checksum-string-reloc in +# Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check +# +# Build the pair with one difference and require that symbol's checksum to move. +check() +{ + build_pair checksum_data.c "-D$1" + run_checksum + + assert_checksum_differs "$2" +} + +# The object's own bytes. +check PLAIN_VALUE plain +# Its length, for a .bss object whose bytes are not hashed at all. +check LONGER sized +# A relocation's target: same bytes in the object, different symbol named. +check WHICH_FUNC descriptor +check WHICH_STR descriptor +# The contents of a string the object points at, with the pointer untouched. +check STR_CONTENT descriptor +# A relocation's addend: same target symbol, different offset into it. +check WHICH_SLOT descriptor +# The same, for a static reached through its section symbol: the reference has +# to be resolved back to the object before there is a name or offset to hash. +check WHICH_PRIV descriptor + +# Having shown five things that must change it, show one that must not: an +# unrelated edit elsewhere in the file leaves this object alone. +build_pair checksum_data.c -DPLAIN_VALUE +run_checksum +assert_checksum_matches descriptor + +pass "data checksums cover length, bytes, reloc targets and string contents" From c8304204100b28d274c381cf76b603affe457934 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:40 -0700 Subject: [PATCH 0647/2286] objtool/klp: Add test for symbols with no checksum entry of their own .discard.sym_checksum is an array of { address, checksum } looked up by the address a relocation points at, so the invariant is one entry per address. calculate_checksums() skips zero-length symbols, aliases and cold parts to keep it, and nothing checked that it does. A duplicate entry is not a build failure. It makes the lookup ambiguous, and whichever checksum loses is never consulted again -- so a function whose code changed can be read as unchanged and dropped from the patch. The alias skip is verified the usual way: remove it and the test fails. The other two are not isolated, and the test says so rather than implying otherwise. A zero-length symbol is excluded by several of the guards at once -- its section has no data either -- so removing any one of them changes nothing observable; the assertion stands as a check on the behaviour, not on the line that produces it. The cold-part skip needs a compiler that splits functions and is not reached here at all. Which of an aliased pair keeps the entry falls out of symbol table order, so the test requires exactly one of the two rather than naming a winner -- as written first it named real_function and failed, because gcc kept the alias. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-48-song@kernel.org --- .../tests/generic/fixtures/checksum_skip.c | 47 +++++++++++ .../tests/generic/test-checksum-skip.sh | 81 +++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_skip.c create mode 100755 tools/objtool/tests/generic/test-checksum-skip.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_skip.c b/tools/objtool/tests/generic/fixtures/checksum_skip.c new file mode 100644 index 00000000000000..973bdc10295df3 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_skip.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Symbols calculate_checksums() must not give an entry of their own. + * + * Three kinds are skipped, for two different reasons: + * + * zero-length there is nothing to hash, and an entry keyed on the + * symbol's address would collide with whatever really lives + * there. + * alias a second name for an address already checksummed. + * cold part hashed as part of its parent, which func_for_each_insn() + * walks into, so a separate entry would double-count it. + * + * An entry per address is the invariant: .discard.sym_checksum is looked up by + * the address a relocation points at, so two entries for one address make the + * lookup ambiguous and one of the two checksums unreachable. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* Zero-length: an object symbol of size 0, in a section of its own. */ +extern char empty_marker[]; +__asm__(".pushsection .data.empty_marker,\"aw\",@progbits\n" + ".globl empty_marker\n" + ".type empty_marker, @object\n" + "empty_marker:\n" + ".size empty_marker, 0\n" + ".popsection\n"); + +int real_function(int x); +int real_function(int x) +{ +#ifdef PATCHED + return x + 2; +#else + return x + 1; +#endif +} + +/* Alias: a second name for real_function's address. */ +int alias_function(int x) __attribute__((alias("real_function"))); + +int target(int x) +{ + return real_function(x) + alias_function(x) + (int)(long)empty_marker; +} diff --git a/tools/objtool/tests/generic/test-checksum-skip.sh b/tools/objtool/tests/generic/test-checksum-skip.sh new file mode 100755 index 00000000000000..f245535a0f363f --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-skip.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Symbols which must not get a checksum entry of their own. +# +# .discard.sym_checksum is an array of { address, checksum } looked up by the +# address a relocation points at, so the invariant is one entry per address. +# calculate_checksums() skips three kinds of symbol to keep it: +# +# zero-length nothing to hash, and its address belongs to whatever really +# lives there +# alias a second name for an address already covered +# cold part hashed into its parent, which func_for_each_insn() walks +# into, so its own entry would double-count +# +# A duplicate entry is not a build failure. It makes the lookup ambiguous, and +# whichever checksum loses is simply never consulted again -- so a function +# whose code changed can be read as unchanged and dropped from the patch. +# +# Of the three, only the alias skip is isolated here: removing it makes this +# test fail. A zero-length symbol is excluded by more than one of the guards +# at once -- its section has no data either -- so no single change makes that +# assertion fail, and it stands as a check on the behaviour rather than on the +# line which produces it. Nothing here reaches the cold-part skip, which +# wants a compiler that splits functions; test-cold-function covers that +# symbol surviving into the patch, not its checksum. +# +# Covers the same ground as corpus/x86_64/checksum-zero-len-sym, +# checksum-alias-skip and checksum-cold-skip in Joe Lawrence's klp-build unit +# test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair checksum_skip.c + +assert_input_symbol empty_marker +assert_input_symbol alias_function +run_checksum + +# entries_for [-t] +# +# The symbol names .discard.sym_checksum has an entry for, one per line. With +# -t, what each entry points at instead: the name and its addend, which is the +# address the kernel looks the entry up by. A name alone is not that address, +# since a relocation against a section symbol names the section and puts the +# offset in the addend, and several entries can then share a name honestly. +entries_for() +{ + in_relocs "$1" | awk -v target="${2:-}" '/rela\.discard\.sym_checksum/,/^$/ { + if ($1 !~ /^[0-9a-f]{8,}/) + next + if (target == "-t") + print $5, $6, $7 + else + print $5 + }' +} + +entries="$(entries_for orig.o)" + +# The control: something real did get an entry, so an empty listing cannot +# make the rest of this pass by default. +echo "$entries" | grep -qx target || + fail "no checksum entry for target" + +echo "$entries" | grep -qx empty_marker && + fail "zero-length symbol got a checksum entry" + +# One of the two names for that address is kept and the other skipped; which +# one falls out of symbol table order and is not the point. Two would be. +n="$(echo "$entries" | grep -cxE 'real_function|alias_function')" +[ "$n" = 1 ] || + fail "expected 1 checksum entry across real_function and its alias, found $n" + +# One entry per address, which is what the skipping is for. +dupes="$(entries_for orig.o -t | sort | uniq -d)" +[ -z "$dupes" ] || + fail "two checksum entries for one address: $dupes" + +pass "zero-length symbols and aliases get no checksum entry of their own" From 8fc5ddd4f91087c13a947273fefef89a8de48342 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:41 -0700 Subject: [PATCH 0648/2286] objtool/klp: Add test for a static branch introduced by the patch Patching a function which already has a static branch and adding one to a function which had none are different cases. In the second the __jump_table entry is itself new, so there is nothing in the original to correlate it against: klp diff has to carry the entry into the patch from scratch and reach the key the way it reaches any other vmlinux symbol. Dropping it is silent. The patched function keeps a static branch the kernel never patches, so it takes the same arm forever whatever the key is set to. Give the fixture a NEW_KEY knob which puts the whole branch behind PATCHED, and assert the premise -- that the original really has no __jump_table -- before asserting the result, since otherwise this is just a second copy of test-jump-label-key. Verified by making klp diff skip __jump_table when cloning special sections: the test fails. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-49-song@kernel.org --- .../tests/generic/test-jump-label-new-key.sh | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-new-key.sh diff --git a/tools/objtool/tests/generic/test-jump-label-new-key.sh b/tools/objtool/tests/generic/test-jump-label-new-key.sh new file mode 100755 index 00000000000000..3bc6005fc776c8 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-new-key.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may introduce a static branch where the original function had none. +# +# That is not the same case as patching a function which already has one. The +# __jump_table entry is itself new, so there is no counterpart in the original +# to correlate it against: klp diff has to carry the entry and the key into the +# patch from scratch, and the key has to be reached the way any other reference +# to a vmlinux symbol is. +# +# Get it wrong and the entry is dropped, leaving a static branch the kernel +# never patches -- the code takes the wrong arm forever, silently. +# +# Where the key lives still decides whether that is allowed, exactly as it does +# for a key the original already had: a module-owned one cannot be reached, so +# introducing one has to stop the build rather than emit an entry nothing will +# resolve. +# +# Covers the same ground as corpus/x86_64/static-branch-vmlinux-new and +# static-branch-module-new in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup klp_test_key +build_pair jump_label.c -DNEW_KEY + +# The premise: the original really has no jump table, and the patched one does. +has_input_section orig.o __jump_table && + fail "fixture put a __jump_table in the original; nothing new to add" +has_input_section patched.o __jump_table || + probe_skip "compiler produced no __jump_table on this arch" + +run_diff + +assert_patched target +assert_section __jump_table +assert_reloc_sym __jump_table target + +# The same new branch, with the key owned by a module. Drop the vmlinux export +# first: while it is exported the key is reachable and being new changes +# nothing, which is what the first version of this got wrong. +export_syms +rm -f "$workdir/out.o" +build_pair jump_label.c -DNEW_KEY -DMODNAME='"klp_testmod"' +run_diff 255 +assert_diff_log 'unsupported static branch key klp_test_key' +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "static branch introduced by the patch carried in, or rejected for a module key" From 2f4bbb1cedbb67a903896a566c135c3c6f253baa Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:42 -0700 Subject: [PATCH 0649/2286] objtool/klp: Add test for tracepoint and pr_debug static branch keys A module-owned static branch key is normally fatal, because late module patching lets the livepatch load before the module it depends on and jump_label_add_module() then dereferences an unresolved entry. Tracepoints and pr_debug() generate such keys everywhere, so refusing them outright would make any function containing a trace_*() call or a pr_debug() unpatchable. klp diff drops the entry, warns, and carries on: the patched code works with that one tracepoint or debug print permanently off. Both halves matter, and the test asserts both. A build which fails is a function nobody can patch; an entry left in place is the corruption the rejection exists to prevent. Give the fixture a KEY_NAME knob so the same static branch can be built with either special name. Verified by removing each exemption in turn -- the test fails for both. That the entry is then dropped is asserted but not isolated, and the test says so. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-50-song@kernel.org --- .../generic/test-jump-label-exempt-keys.sh | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100755 tools/objtool/tests/generic/test-jump-label-exempt-keys.sh diff --git a/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh b/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh new file mode 100755 index 00000000000000..fa2f913d860b36 --- /dev/null +++ b/tools/objtool/tests/generic/test-jump-label-exempt-keys.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Two kinds of module-owned static branch key are disabled with a warning +# instead of rejected. +# +# A module-local key is normally fatal: late module patching lets the livepatch +# load before the module it depends on, so the unresolved __jump_table entry is +# dereferenced by jump_label_add_module(). test-jump-label-module-key covers +# that rejection. +# +# Tracepoints and pr_debug() generate such keys everywhere, though, and +# refusing them outright would make any function containing a trace_*() call or +# a pr_debug() unpatchable. So klp diff drops the entry, says so, and carries +# on: the patched code keeps working with that one tracepoint or debug print +# permanently off. +# +# Both halves matter. A build that fails is a function nobody can patch; an +# entry left in place is the memory corruption the rejection exists to prevent. +# +# The two exemptions are isolated: remove either and this fails. That the +# entry is then dropped is asserted but not isolated -- making the caller keep +# it anyway produces no output difference here, so that assertion stands as a +# check on the behaviour rather than on the line which produces it. +# +# Covers the same ground as corpus/x86_64/static-call-module-tracepoint and +# pr-debug-unsupported in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check +check() +{ + build_pair jump_label.c -DKEY_NAME="$1" -DMODNAME='"klp_testmod"' + require_input_section __jump_table + + # Accepted, not rejected: this is the whole point. + run_diff + assert_diff_log "$2" + + # And the entry is gone, not merely complained about. + assert_patched target + assert_no_section __jump_table +} + +check __tracepoint_klp_test 'disabling unsupported tracepoint klp_test' +check __UNIQUE_ID_ddebug_klp_test 'disabling unsupported pr_debug' + +pass "tracepoint and pr_debug keys disabled with a warning, not rejected" From 938816b7d15644a486ebcea5d58994664b8cc04c Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:43 -0700 Subject: [PATCH 0650/2286] objtool/klp: Add test for a static call introduced by the patch The counterpart to the static branch case: a patch may add a static call to a function which had none, so the .static_call_sites entry is new and there is nothing in the original to correlate it against. Where the key lives still decides whether that is allowed. A vmlinux key is reachable; a module-owned one is not, for the same reason an existing module key is not -- late module patching lets the livepatch load first and the unresolved entry is dereferenced when the module arrives. Both halves are here because they fail in opposite directions: dropping the new entry leaves a static call the kernel never patches, and accepting a new module-owned one is the corruption the check exists to prevent. Both halves are one test over one fixture: built with -DNEW_CALL the key is vmlinux's and the new entry has to be carried in; built with -DMODNAME as well the key belongs to a module, klp diff has to reject it, and no output object may be left behind. The premise is asserted first -- the original must have no .static_call_sites at all -- since otherwise this is a second copy of test-static-call-module-key. Covers the same ground as corpus/x86_64/static-call-vmlinux-new and corpus/x86_64/static-call-module-new in Joe Lawrence's klp-build unit test corpus. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-51-song@kernel.org --- .../tests/generic/test-static-call-new.sh | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 tools/objtool/tests/generic/test-static-call-new.sh diff --git a/tools/objtool/tests/generic/test-static-call-new.sh b/tools/objtool/tests/generic/test-static-call-new.sh new file mode 100755 index 00000000000000..f7d2b39c0fec2c --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-new.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may introduce a static call where the original function had none. +# +# The .static_call_sites entry is then new, with nothing in the original to +# correlate it against, so klp diff has to carry it into the patch from +# scratch. Where the key lives still decides whether that is allowed: a +# vmlinux key is reachable, and a module-owned one is not, for the same reason +# an existing module key is not -- late module patching lets the livepatch load +# first, and the unresolved entry is dereferenced when the module arrives. +# +# Both halves are here because they fail in opposite directions. Dropping the +# new entry leaves a static call the kernel never patches; accepting a new +# module-owned one is the corruption the check exists to prevent. +# +# Covers the same ground as corpus/x86_64/static-call-vmlinux-new and +# static-call-module-new in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c -DNEW_CALL + +# The premise: the original really has no static call, the patched one does. +has_input_section orig.o .static_call_sites && + fail "fixture put a .static_call_sites in the original; nothing new to add" +has_input_section patched.o .static_call_sites || + probe_skip "compiler produced no .static_call_sites on this arch" + +run_diff +assert_patched target +assert_section .static_call_sites +assert_reloc_sym .static_call_sites target + +# The same new call, with the key owned by a module: not reachable, so the +# build has to stop rather than emit a relocation nothing will resolve. +rm -f "$workdir/out.o" +build_pair static_call.c -DNEW_CALL -DMODNAME='"klp_testmod"' +run_diff 255 +assert_diff_log 'unsupported static call key __SCK__klp_test_call' +[ -e "$workdir/out.o" ] && + fail "output object produced for a rejected input" + +pass "static call introduced by the patch carried in, or rejected for a module key" From 065f09c8332f9d5cf6ee5cd2494b9af179ee749b Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:44 -0700 Subject: [PATCH 0651/2286] objtool/klp: Add test for instruction operand checksums checksum_update_insn() hashes an instruction's bytes and then what any relocation on it refers to: a string section contributes the string's contents, anything else the target symbol's name and adjusted addend, with a reference to a static resolved through its section symbol first. None of that shows up in the bytes. A rel32 operand is zero in the object and supplied by the relocation, so calling a different function, editing a literal the code passes, or reading a different index of an array all leave the encoded instruction byte-identical. A checksum stopping at the bytes reports the function unchanged and the patch silently does not contain the fix. test-checksum-position is the other half: what must *not* change the checksum when a function merely moves. Each of the four is verified by sabotaging the line it covers. The static case needed a writer the compiler cannot see through -- without one it proves the array is never written, folds every read to zero, and emits no relocation at all, so the reference under test does not exist and the variant passes having compared two identical objects. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-52-song@kernel.org --- .../tests/generic/fixtures/checksum_insn.c | 78 +++++++++++++++++++ .../tests/generic/test-checksum-insn.sh | 49 ++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/checksum_insn.c create mode 100755 tools/objtool/tests/generic/test-checksum-insn.sh diff --git a/tools/objtool/tests/generic/fixtures/checksum_insn.c b/tools/objtool/tests/generic/fixtures/checksum_insn.c new file mode 100644 index 00000000000000..10f70a74a97639 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/checksum_insn.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Instruction operands whose change must move a function's checksum even + * though the instruction bytes themselves do not. + * + * checksum_update_insn() hashes the raw bytes and then, when the instruction + * carries a relocation, what that relocation refers to: a string section + * contributes the string's contents, anything else the target symbol's name + * and the adjusted addend. A reference to a static arrives as a section + * symbol and has to be resolved back to the object first. + * + * The bytes are identical in every case below -- a rel32 operand is zero in + * the object and supplied by the relocation -- so a checksum that stopped at + * the bytes would call all of these unchanged. + * + * Each variant applies to the patched build only: + * + * WHICH_CALL calls a different function + * STR_CONTENT passes a literal whose text was edited + * WHICH_SLOT reads a different index of a global array: addend only + * WHICH_PRIV the same, for a static, reached through its section symbol + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int callee_a(int x); +int callee_b(int x); +int sink(const char *s); + +int slots[4]; + +/* + * A file-local array, plus a writer the compiler cannot see through. Without + * one it can prove the array is never written, folds every read to zero, and + * emits no relocation at all -- so the reference this is here to exercise does + * not exist. + */ +static int priv_slots[4]; + +void set_priv(int i, int v); +void set_priv(int i, int v) +{ + priv_slots[i] = v; +} + +#if defined(PATCHED) && defined(STR_CONTENT) +#define MESSAGE "edited" +#else +#define MESSAGE "original" +#endif + +int target(int x) +{ + int r; + +#if defined(PATCHED) && defined(WHICH_CALL) + r = callee_b(x); +#else + r = callee_a(x); +#endif + + r += sink(MESSAGE); + +#if defined(PATCHED) && defined(WHICH_SLOT) + r += slots[2]; +#else + r += slots[1]; +#endif + +#if defined(PATCHED) && defined(WHICH_PRIV) + r += priv_slots[3]; +#else + r += priv_slots[1]; +#endif + + return r; +} diff --git a/tools/objtool/tests/generic/test-checksum-insn.sh b/tools/objtool/tests/generic/test-checksum-insn.sh new file mode 100755 index 00000000000000..e1c04a1f518a4b --- /dev/null +++ b/tools/objtool/tests/generic/test-checksum-insn.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# What a function's checksum has to cover beyond its instruction bytes. +# +# checksum_update_insn() hashes the raw bytes, and then what any relocation on +# the instruction refers to: a string section contributes the string's +# contents, anything else the target symbol's name and the adjusted addend, +# with a reference to a static resolved back through its section symbol first. +# +# None of these show up in the bytes. A rel32 operand is zero in the object +# and supplied by the relocation, so every change below leaves the encoded +# instruction byte-identical. A checksum stopping at the bytes reports the +# function unchanged, klp diff omits it, and the patch silently does not +# contain the fix. +# +# test-checksum-position is the other half of this: it covers what must *not* +# change the checksum when a function merely moves. +# +# Covers the same ground as corpus/x86_64/checksum-reloc-sym, +# checksum-pc-relative-addend, checksum-string-reloc and +# checksum-sec-sym-resolve in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +# check +check() +{ + build_pair checksum_insn.c "-D$1" + run_checksum + + # The premise for all of them: the operand is a relocation, not bytes. + assert_checksum_differs target +} + +check WHICH_CALL # relocation target name +check STR_CONTENT # contents of a string the code passes +check WHICH_SLOT # addend, same target symbol +check WHICH_PRIV # addend via a static's section symbol + +# The converse: rebuilding identical source leaves it alone, so the above is +# not just "any rebuild moves the checksum". +build_pair checksum_insn.c +run_checksum +assert_checksum_matches target + +pass "instruction checksums cover reloc targets, addends and string contents" From 32b2cf11305d6c355e66b7f9fb827d7e97c2a5c3 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:45 -0700 Subject: [PATCH 0652/2286] objtool/klp: Add test for alternative replacement code in checksums checksum_update_insn() walks insn->alts after hashing the instruction itself: the alternative's type, and where the replacement forms a group, its feature number and every instruction in it. A patch which edits only the replacement -- code that runs on some CPUs and not others -- still has to move the function's checksum. When it does not, klp diff calls the function unchanged and leaves it out. The patch ships the old replacement, and the bug is fixed only on machines whose CPU takes the other arm. Which machines those are depends on the feature bit, so it presents as a machine-specific bug rather than a missing patch. insn->alts is built by objtool's check pass, not by the compiler, so the pair goes through that first. --mcount is the action used: it is the cheapest one that does not also need --link. Two of the three paths are isolated. Skipping the alts walk and dropping the feature hash both make this fail, and the second of those only exists inside the alt_group branch, so reaching it proves the grouped path is taken. The alternative's type is hashed but not varied here -- the fixture emits one kind of alternative -- so that line is covered without being isolated, as is the in_alt recursion guard, which wants nested alternatives. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-53-song@kernel.org --- .../objtool/tests/x86/fixtures/checksum_alt.c | 66 +++++++++++++++++++ tools/objtool/tests/x86/test-checksum-alt.sh | 45 +++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 tools/objtool/tests/x86/fixtures/checksum_alt.c create mode 100755 tools/objtool/tests/x86/test-checksum-alt.sh diff --git a/tools/objtool/tests/x86/fixtures/checksum_alt.c b/tools/objtool/tests/x86/fixtures/checksum_alt.c new file mode 100644 index 00000000000000..ed342d94eb9ed3 --- /dev/null +++ b/tools/objtool/tests/x86/fixtures/checksum_alt.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * An x86 alternative whose replacement code is part of the patched function's + * checksum. + * + * checksum_update_insn() walks insn->alts after hashing the instruction + * itself, hashing the alternative's type and, when the replacement forms a + * group, its feature number and every instruction in it. So editing only the + * replacement -- code the CPU may or may not ever run -- has to move the + * function's checksum. + * + * It is reached through objtool's own alternative handling, so the object has + * to go through the check pass first: insn->alts is built there, not by the + * compiler. + * + * struct alt_instr is written out by hand as in empty_alternative.c: s32 + * instr_offset, s32 repl_offset, u32 ft_flags, u8 instrlen, u8 replacementlen. + * + * Variants, applied to the patched build only: + * + * ALT_REPL the replacement instruction changes; the original does not + * ALT_FEATURE the feature number changes; no code changes at all + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* + * Both spellings are two bytes, because a replacement may not be longer than + * the instruction it replaces: "xchg %ax, %ax" is 66 90 and two nops are + * 90 90. The original below is padded to match. + */ +#if defined(PATCHED) && defined(ALT_REPL) +#define REPL_INSN " nop\n\t nop\n\t" +#else +#define REPL_INSN " xchg %ax, %ax\n\t" +#endif + +#if defined(PATCHED) && defined(ALT_FEATURE) +#define FEATURE "7" +#else +#define FEATURE "3" +#endif + +int target(int x) +{ + asm volatile( + "661: nop\n\t" + " nop\n\t" + "662:\n\t" + ".pushsection .altinstr_replacement, \"ax\"\n\t" + ".globl target_repl\n\t" + "target_repl:\n\t" + REPL_INSN + "target_repl_end:\n\t" + ".popsection\n\t" + ".pushsection .altinstructions, \"aM\", @progbits, 14\n\t" + ".long 661b - .\n\t" + ".long target_repl - .\n\t" + ".long " FEATURE "\n\t" + ".byte 662b - 661b\n\t" + ".byte target_repl_end - target_repl\n\t" + ".popsection\n\t"); + + return x + 1; +} diff --git a/tools/objtool/tests/x86/test-checksum-alt.sh b/tools/objtool/tests/x86/test-checksum-alt.sh new file mode 100755 index 00000000000000..74ebfca2b3ff3b --- /dev/null +++ b/tools/objtool/tests/x86/test-checksum-alt.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An alternative's replacement code counts towards the checksum of the function +# it belongs to. +# +# checksum_update_insn() walks insn->alts after hashing the instruction itself: +# the alternative's type, and where the replacement forms a group, its feature +# number and every instruction in it. So a patch which edits only the +# replacement -- code that runs on some CPUs and not others -- still has to +# move the function's checksum. +# +# If it does not, klp diff decides the function is unchanged and leaves it out. +# The patch then ships the old replacement, and the bug is fixed only on +# machines whose CPU takes the other arm. Which machines those are depends on +# the feature bit, so the failure looks like a machine-specific bug rather than +# a missing patch. +# +# insn->alts exists only after objtool's check pass, so the pair goes through +# that first -- the compiler emits none of this structure itself. +# +# Covers the same ground as corpus/x86_64/checksum-alt-group, +# checksum-alt-no-group and checksum-alt-recursion-guard in Joe Lawrence's +# klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup + +check() +{ + build_pair checksum_alt.c "-D$1" + assert_input_section .altinstructions + run_objtool_check --mcount + run_checksum + + assert_checksum_differs target +} + +# The replacement instruction itself. +check ALT_REPL +# The feature number, with no instruction anywhere changed. +check ALT_FEATURE + +pass "alternative replacement code counts towards the checksum" From 8abe5c94bfeb9bb5cf02fa17a9337d2d7bbade75 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:46 -0700 Subject: [PATCH 0653/2286] objtool/klp: Add test for the alignment of cloned data sections A cloned data section has to keep its sh_addralign. Plenty of kernel data is aligned for correctness rather than speed -- per-CPU variables, anything touched by an aligned vector move, structures padded to own a cacheline -- and a clone that lands under-aligned either faults on first use or silently shares a line it was laid out to avoid. Neither shows up until the patch is loaded on hardware that cares. The fixture's data is new in the patched build, so klp diff has to clone it rather than reference the kernel's copy, and it asserts that premise before asserting the result. Commit 2f2600decb30 ("objtool/klp: fix data alignment in __clone_symbol()") cannot be reverted to check this -- the revert is a no-op against the current code, which has been rewritten since. Verified instead by forcing the clone's alignment to 1, which the test reports as "alignment 1, expected 64". Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-54-song@kernel.org --- .../tests/generic/fixtures/data_alignment.c | 29 ++++++++++++++ .../tests/generic/test-data-alignment.sh | 40 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/data_alignment.c create mode 100755 tools/objtool/tests/generic/test-data-alignment.sh diff --git a/tools/objtool/tests/generic/fixtures/data_alignment.c b/tools/objtool/tests/generic/fixtures/data_alignment.c new file mode 100644 index 00000000000000..900253dfb2cbb8 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/data_alignment.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Data with an alignment stricter than its size. + * + * A cloned data section has to keep its sh_addralign. The kernel has plenty + * of data whose alignment is a correctness property rather than an + * optimisation -- per-CPU variables, anything touched by an aligned SSE move, + * cacheline-aligned locks -- and a clone that lands under-aligned faults or + * silently shares a cacheline it was written to avoid. + * + * The object is new in the patched build, so klp diff has to clone it rather + * than reference the kernel's copy. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +#ifdef PATCHED +int aligned_data[2] __attribute__((aligned(64))) = { 1, 2 }; +#endif + +int target(int x) +{ +#ifdef PATCHED + return x + aligned_data[0]; +#else + return x; +#endif +} diff --git a/tools/objtool/tests/generic/test-data-alignment.sh b/tools/objtool/tests/generic/test-data-alignment.sh new file mode 100755 index 00000000000000..8e389544a3b16c --- /dev/null +++ b/tools/objtool/tests/generic/test-data-alignment.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A cloned data section keeps its alignment. +# +# Plenty of kernel data is aligned for correctness rather than speed: per-CPU +# variables, anything touched by an aligned vector move, structures padded to +# own a cacheline. A clone that lands under-aligned either faults on first use +# or silently shares a line it was laid out to avoid, and neither shows up +# until the patch is loaded on hardware that cares. +# +# Fixed by 2f2600decb30 ("objtool/klp: Fix alignment of cloned data +# sections"). +# +# Covers the same ground as corpus/x86_64/cloned-data-alignment in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair data_alignment.c + +# The premise: the compiler really did over-align it, and the object is new in +# the patch so it has to be cloned rather than referenced. +want="$(in_sections patched.o | sed 's/^ *\[[ 0-9]*\] *//' | + awk '$1 == ".data.aligned_data" { print $NF }')" +[ "$want" = 64 ] || + probe_skip "compiler gave .data.aligned_data alignment '$want', not 64" +has_input_section orig.o .data.aligned_data && + fail "fixture put aligned_data in the original; nothing to clone" + +run_diff +assert_section .data.aligned_data + +got="$(out_sections | sed 's/^ *\[[ 0-9]*\] *//' | + awk '$1 == ".data.aligned_data" { print $NF }')" +[ "$got" = "$want" ] || + fail "cloned .data.aligned_data has alignment $got, expected $want" + +pass "cloned data section keeps its alignment" From 794b162b37c00a516eef83d37327fcc9238cca94 Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:47 -0700 Subject: [PATCH 0654/2286] objtool/klp: Add test for a patch which strips a data annotation klp diff needs entry boundaries for a special section: either an entsize, or ANNOTATE_DATA_SPECIAL annotations naming where each entry starts. .static_call_sites has no entsize, so the annotations are all there is -- and a patch can remove the last one in a translation unit while leaving the section itself in place, so that only the patched side has lost them. The section still has to be handled. Dropping it leaves the patched function's static call unregistered; misreading its boundaries attaches the entry to the wrong code. Neither is reported at build time. Give the fixture a NO_ANNOTATE knob and assert the premise -- annotation present in the original, absent in the patched object, section present in both -- before asserting the result. Fixed by commit 3de711fba73a ("objtool/klp: Fix create_fake_symbols() skipping entsize-based sections"). Verified by making klp diff skip .static_call_sites when cloning special sections: the test fails. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-55-song@kernel.org --- .../test-static-call-annotate-stripped.sh | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100755 tools/objtool/tests/generic/test-static-call-annotate-stripped.sh diff --git a/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh new file mode 100755 index 00000000000000..818b047ed4b62f --- /dev/null +++ b/tools/objtool/tests/generic/test-static-call-annotate-stripped.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A patch may remove the last ANNOTATE_DATA_SPECIAL in a translation unit while +# leaving the special section it described in place. +# +# klp diff needs entry boundaries for a special section: either an entsize, or +# annotations naming where each entry starts. .static_call_sites has no +# entsize, so the annotations are all there is -- and when the patched object +# is the only side that lost them, the two sides no longer agree on how the +# section divides up. +# +# The section must still be handled. Dropping it would leave the patched +# function's static call unregistered; misreading its boundaries would attach +# the entry to the wrong code. Either way nothing is reported at build time. +# +# Fixed by commit 3de711fba73a ("objtool/klp: Fix create_fake_symbols() +# skipping entsize-based sections"). +# +# Covers the same ground as corpus/x86_64/static-call-annotate-stripped in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair static_call.c -DNO_ANNOTATE + +# The premise: the original describes its entry, the patched one no longer +# does, and both still have the section itself. +has_input_section orig.o .discard.annotate_data || + fail "fixture produced no annotation in the original" +has_input_section patched.o .discard.annotate_data && + fail "patched object still has the annotation; nothing was stripped" +assert_input_section .static_call_sites + +run_diff + +assert_patched target +assert_section .static_call_sites +assert_reloc_sym .static_call_sites target + +pass "static call site kept when the patch strips its data annotation" From 91c6748459855f3782d445f1a6362a20240fc14e Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:48 -0700 Subject: [PATCH 0655/2286] objtool/klp: Add test for absolute and __ADDRESSABLE symbols A SHN_ABS symbol has no section, so any walk of sym->sec which does not check dereferences NULL, and the kernel has plenty of them -- from linker scripts and from .set in assembly. __ADDRESSABLE() emits a pointer into .discard.addressable purely to keep a symbol referenced; it means nothing to a livepatch and is discarded at link time, but it is a relocation like any other and gets looked at. Neither is what the patch changes. What this guards against is not a wrong answer but a crash or an error on input the kernel produces routinely, which would make every function near one unpatchable. Not isolated to a single line, and the test says so: the absolute symbol here has zero length, so it is excluded before the section check is reached and removing that check alone changes nothing observable. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-56-song@kernel.org --- .../generic/fixtures/abs_and_addressable.c | 44 ++++++++++++++++ .../tests/generic/test-abs-and-addressable.sh | 50 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/abs_and_addressable.c create mode 100755 tools/objtool/tests/generic/test-abs-and-addressable.sh diff --git a/tools/objtool/tests/generic/fixtures/abs_and_addressable.c b/tools/objtool/tests/generic/fixtures/abs_and_addressable.c new file mode 100644 index 00000000000000..6392ff99af421d --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/abs_and_addressable.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Two constructs which appear all over the kernel and must not upset klp + * checksum or klp diff. + * + * An absolute symbol (SHN_ABS) has no section, so anything walking sym->sec + * without checking dereferences NULL. The kernel makes them with linker + * scripts and with .set in asm; VDSO and the fixed-address per-cpu bases are + * the usual sources. + * + * __ADDRESSABLE() emits a pointer into .discard.addressable purely to keep a + * symbol referenced. It is discarded at link time and means nothing to a + * livepatch, but the pointer is a relocation like any other and has to survive + * being looked at. + * + * Neither is the subject of the patch; the point is that their presence does + * not disturb the function that is. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +/* SHN_ABS, referenced from code. */ +extern char abs_sym[]; +__asm__(".globl abs_sym\n" + ".set abs_sym, 0x1234\n"); + +int helper(int x); +int helper(int x) { return x + 1; } + +/* The shape of __ADDRESSABLE(helper). */ +__asm__(".pushsection .discard.addressable, \"aw\"\n" + ".balign 8\n" + ".quad helper\n" + ".popsection\n"); + +int target(int x) +{ +#ifdef PATCHED + return helper(x) + (int)(long)abs_sym + 1; +#else + return helper(x) + (int)(long)abs_sym; +#endif +} diff --git a/tools/objtool/tests/generic/test-abs-and-addressable.sh b/tools/objtool/tests/generic/test-abs-and-addressable.sh new file mode 100755 index 00000000000000..6adb23ed4b8880 --- /dev/null +++ b/tools/objtool/tests/generic/test-abs-and-addressable.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# An absolute symbol and an __ADDRESSABLE() pointer must not disturb the +# function being patched. +# +# A SHN_ABS symbol has no section, so any walk of sym->sec which does not check +# dereferences NULL -- and the kernel has plenty, from linker scripts and from +# .set in assembly. __ADDRESSABLE() emits a pointer into .discard.addressable +# to keep a symbol referenced; it means nothing to a livepatch and is discarded +# at link time, but it is a relocation like any other and gets looked at. +# +# Neither is what the patch changes. The failure this guards against is not a +# wrong answer but a crash or an error on input the kernel produces routinely, +# which would make any function near one unpatchable. +# +# Not isolated to a single guard: the absolute symbol here has zero length, so +# it is excluded before the section check is reached and removing that check +# alone changes nothing observable. This stands as a check on the behaviour +# rather than on the line which produces it. +# +# Covers the same ground as corpus/x86_64/checksum-abs-sym-skip and +# addressable-symbols in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair abs_and_addressable.c + +# The premise: the fixture really did produce both. +in_symbols orig.o | grep -q 'ABS.*abs_sym' || + probe_skip "assembler did not make abs_sym absolute here" +assert_input_section .discard.addressable + +# Checksumming has to survive them, and still see the function that changed. +run_checksum +assert_checksum_differs target +assert_checksum_matches helper + +# So does the diff. +run_diff +assert_patched target +assert_not_patched helper + +# An absolute symbol has no address to record a checksum against, so it gets +# no entry -- the reference to it is what mattered, not the symbol itself. +in_relocs orig.o | awk '/rela\.discard\.sym_checksum/,/^$/' | grep -qw abs_sym && + fail "absolute symbol got a checksum entry" + +pass "absolute and __ADDRESSABLE symbols do not disturb the patched function" From 41ca5150dd816f856c545775525013513e9a31fa Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:49 -0700 Subject: [PATCH 0656/2286] objtool/klp: Add test for UBSAN metadata in an unchanged function Every instrumented operation gets a per-callsite metadata object in an anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from GCC, .data..L__unnamed_ from Clang -- whose names are compiler-generated and mean nothing across a rebuild. is_uncorrelated_section() exists so klp diff does not try to pair them up, and nothing tested it. The failure it prevents is a false positive, which is the direction this suite has least coverage of. Metadata belonging to a function nobody touched compares as different and drags that function into the patch. That is not a build failure: it is a larger livepatch than intended, pulling in dependencies with it, and every extra function is one more that can fail to correlate or to apply. The fixture is built with -fsanitize=shift, which both compilers instrument; neither emits a bounds check for an index it can prove in range. One function changes, the other is byte-identical and carries instrumentation of its own, and the test asserts the second is left alone. Verified by removing each rule from is_uncorrelated_section() in turn, which splits neatly by toolchain: dropping the .data..Lubsan rule fails the test under gcc, dropping .data..L__unnamed_ fails it under clang. One test, two code paths, each checked by the compiler that reaches it. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-57-song@kernel.org --- .../tests/generic/fixtures/ubsan_noise.c | 49 +++++++++++++++++++ .../objtool/tests/generic/test-ubsan-noise.sh | 48 ++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/ubsan_noise.c create mode 100755 tools/objtool/tests/generic/test-ubsan-noise.sh diff --git a/tools/objtool/tests/generic/fixtures/ubsan_noise.c b/tools/objtool/tests/generic/fixtures/ubsan_noise.c new file mode 100644 index 00000000000000..bf59992541630e --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/ubsan_noise.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A translation unit built with UBSAN, where only one of two functions is + * patched. + * + * Every instrumented operation gets a per-callsite metadata object in an + * anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from + * GCC, .data..L__unnamed_ from Clang -- and a call to a __ubsan_handle_* + * routine. The names are compiler-generated and carry no meaning across a + * rebuild, so klp diff has to treat those sections as uncorrelated rather than + * pairing them up by name. + * + * untouched() is byte-identical in both builds and exists to catch the false + * positive: if the metadata were correlated by name, its shifts would look + * changed and it would be dragged into the patch. + * + * The shifts are what draw the instrumentation. A bounds check would do as + * well but neither compiler emits one for an index it can prove in range. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; + +int shift_by(int v, int n); + +int untouched(int v, int n) +{ + int s = 0; + + s += v << (n & 31); + s += v << ((n + 1) & 31); + s += shift_by(v, n); + + return s; +} + +int touched(int v, int n) +{ + int s = 0; + + s += v << (n & 31); +#ifdef PATCHED + s += v << ((n + 3) & 31); +#else + s += v << ((n + 2) & 31); +#endif + + return s; +} diff --git a/tools/objtool/tests/generic/test-ubsan-noise.sh b/tools/objtool/tests/generic/test-ubsan-noise.sh new file mode 100755 index 00000000000000..b415eb16bfd22c --- /dev/null +++ b/tools/objtool/tests/generic/test-ubsan-noise.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# UBSAN instrumentation in an unchanged function must not make it look changed. +# +# Every instrumented operation gets a per-callsite metadata object in an +# anonymous data section -- .data..Lubsan_data and .data..Lubsan_type from GCC, +# .data..L__unnamed_ from Clang -- whose names are compiler-generated and mean +# nothing across a rebuild. is_uncorrelated_section() exists so klp diff does +# not try to pair them up. +# +# Without that, the metadata belonging to a function nobody touched compares as +# different and drags the function into the patch. A livepatch which replaces +# functions the patch never changed is not a build failure: it is a larger +# patch than intended, taking its dependencies with it, and every extra +# function is one more that can fail to correlate or to apply. +# +# Covers the same ground as corpus/x86_64-ubsan/{ubsan-shift-noise, +# ubsan-metadata-data-section,gcc-ubsan-anonymous-data,ubsan-handler-cloning} +# and corpus/x86_64-llvm-ubsan/{clang-ubsan-bounds-noise, +# clang-ubsan-handler-cloning} in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +build_pair ubsan_noise.c -fsanitize=shift + +# The premise: this compiler really did instrument, and left its metadata in an +# anonymous section. Without that the test is just test-basic again. +ubsan_sec="$(in_sections orig.o | + grep -oE '\.data\.\.L(ubsan_data|__unnamed_)[A-Za-z0-9_.]*' | head -1)" +[ -n "$ubsan_sec" ] || + probe_skip "compiler emitted no anonymous UBSAN data section" +assert_input_symbol untouched + +run_diff + +# The changed function is patched, and the untouched one is left alone despite +# carrying instrumentation of its own. +assert_patched touched +assert_not_patched untouched + +# The handler the patched code calls has to come with it, or the clone calls +# nothing when its check fires. +out_symbols | grep -q '__ubsan_handle_' || + fail "no __ubsan_handle_* reference in the patched output" + +pass "UBSAN metadata in an unchanged function does not drag it into the patch" From 317cf7bbfa3de0fb61451e9b0b11d20831fb9f9a Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:50 -0700 Subject: [PATCH 0657/2286] objtool/klp: Add test for Clang switch jump tables For a dense enough switch Clang emits the targets as a table in .rodata..Lswitch.table. -- named after the function but not part of it. The patched function indexes into that table, so a clone which does not bring it along jumps through whatever the kernel's copy holds, which after a patch that changed the switch is the wrong set of targets. An indirect jump to a stale address reports nothing at build or load time. The fixture asserts its own premise twice over, since both halves depend on what this Clang chose to do: that a table was built rather than a chain of comparisons, and that the added case actually changed it. objtool has no switch-specific code -- the table is carried by the general mechanism for data a cloned function references -- so this guards that mechanism reaching an easily-mishandled shape rather than a particular line, and the test says so. Making the table uncorrelated, the nearest available sabotage, does not change the outcome. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-58-song@kernel.org --- .../tests/generic/fixtures/switch_rodata.c | 31 +++++++++++ .../tests/generic/test-switch-rodata.sh | 53 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/switch_rodata.c create mode 100755 tools/objtool/tests/generic/test-switch-rodata.sh diff --git a/tools/objtool/tests/generic/fixtures/switch_rodata.c b/tools/objtool/tests/generic/fixtures/switch_rodata.c new file mode 100644 index 00000000000000..817ddac92d817d --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/switch_rodata.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * A switch dense enough that Clang builds a jump table for it, in a section of + * its own: .rodata..Lswitch.table.. + * + * The table belongs to the function and has to travel with it. It is named + * after the function but is not part of it, so klp diff has to associate the + * two rather than treating the table as unrelated data. + * + * The patch adds a case, which changes the table's contents and length. + */ + +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +const char *status_to_string(unsigned int c) +{ + switch (c) { + case 0: return "idle"; + case 1: return "running"; + case 2: return "stopped"; + case 3: return "error"; + case 4: return "paused"; + case 5: return "waiting"; + case 6: return "starting"; + case 7: return "stopping"; +#ifdef PATCHED + case 8: return "completed"; +#endif + } + return "unknown"; +} diff --git a/tools/objtool/tests/generic/test-switch-rodata.sh b/tools/objtool/tests/generic/test-switch-rodata.sh new file mode 100755 index 00000000000000..fb27e96c65f680 --- /dev/null +++ b/tools/objtool/tests/generic/test-switch-rodata.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# A Clang switch jump table travels with the function it belongs to. +# +# For a dense enough switch Clang emits the targets as a table in +# .rodata..Lswitch.table., named after the function but not part of +# it. klp diff has to associate the two: the patched function indexes into +# that table, so a clone which does not bring it along jumps through whatever +# the kernel's copy holds -- which, when the patch changed the switch, is the +# wrong set of targets. +# +# That is an indirect jump to a stale address, not a missing symbol, so nothing +# reports it at build or load time. +# +# objtool has no switch-specific code: the table is carried by the general +# mechanism for data a cloned function references. So this is a regression +# test on that mechanism reaching a shape it is easy to get wrong, not a guard +# on a particular line -- making the table uncorrelated, the nearest sabotage, +# does not change the outcome. +# +# Covers the same ground as corpus/x86_64-llvm-switch-rodata/ +# clang-switch-rodata-assoc in Joe Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +clang_only "only Clang emits switch jump tables in their own section" + +setup +build_pair switch_rodata.c + +# The premise: this Clang really did build a table rather than a chain of +# comparisons, and the added case really did change it. +tbl=.rodata..Lswitch.table.status_to_string +has_input_section orig.o "$tbl" || + probe_skip "this clang built no jump table for the switch" +# readelf prefixes each line with "[nn]", which splits into one or two fields +# depending on the index, so strip it before counting columns. +tbl_size() +{ + in_sections "$1" | sed 's/^ *\[[ 0-9]*\] *//' | + awk -v s="$tbl" '$1 == s { print $5 }' +} +[ "$(tbl_size orig.o)" != "$(tbl_size patched.o)" ] || + fail "fixture's added case did not change the jump table" + +run_diff + +assert_patched status_to_string +assert_section "$tbl" +assert_reloc_sym .text.status_to_string "$tbl" + +pass "Clang switch jump table carried with the function it belongs to" From 945537271df1e3680be9b32af7041ac20f1184fb Mon Sep 17 00:00:00 2001 From: Song Liu Date: Wed, 16 Sep 2026 11:43:51 -0700 Subject: [PATCH 0658/2286] objtool/klp: Add test for ThinLTO symbols sharing a demangled name A file-local symbol which ThinLTO has to make visible is renamed helper.llvm.. With two such helpers in one link, the original and the patched object hold two each, all four spelled differently, and demangling gives "helper" for every one of them -- so the name alone cannot say which corresponds to which. Three translation units, two with a static helper of the same name and a third calling into both, which is what forces the promotion. Only one helper changes: paired correctly that means exactly one is cloned, and paired the wrong way round the other is, or both are. Had both bodies changed, both would be cloned either way and the test would prove nothing -- which is how the first version of this was written. The outcome is asserted, not the machinery. With the clang tested here the pairing survives disabling the .llvm. suffix map and stubbing out llvm_suffix() entirely, so no single-line sabotage distinguishes it; the tiered matcher this case was written for is not needed for this shape. The test says so rather than implying otherwise. Assisted-by: Claude:claude-opus-4 Based-on-test-by: Joe Lawrence Assisted-by: Claude:claude-opus-5 Signed-off-by: Song Liu Signed-off-by: Josh Poimboeuf Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260916184351.2720310-59-song@kernel.org --- .../generic/fixtures/thinlto_ambiguity.c | 57 ++++++++++++++ .../tests/generic/test-thinlto-ambiguity.sh | 77 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c create mode 100755 tools/objtool/tests/generic/test-thinlto-ambiguity.sh diff --git a/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c new file mode 100644 index 00000000000000..b88830f41a9228 --- /dev/null +++ b/tools/objtool/tests/generic/fixtures/thinlto_ambiguity.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Three translation units linked with ThinLTO, two of which have a file-local + * helper of the same name. + * + * TU_C calls into both of the others, so ThinLTO imports entry_a and entry_b + * and with them the static helper each one calls. A file-local symbol which + * has to become visible is renamed helper.llvm., and the hash is content + * derived -- so the two helpers get different hashes from each other, and + * TU_A's gets a different one again after the patch changes it. Only TU_A's + * changes: were both bodies to change, both would be cloned whichever way + * they were paired, and the pairing would not be observable. + * + * That leaves klp diff with two symbols in the original and two in the patched + * object, all four named differently, which have to be paired up correctly. + * Demangling alone gives "helper" for all of them; something else has to + * decide which is which. + * + * Only TU_A's helper changes. That is what makes a wrong pairing observable: + * paired correctly, one helper is changed and the other is not, so exactly one + * is cloned. Paired the wrong way round, both look changed -- or the wrong + * one does. If both bodies changed the outcome would be the same either way + * and the test would prove nothing. + * + * BASE differs between the two so their bodies are not identical to begin + * with. + */ + +#if defined(TU_C) +extern int entry_a(int x); +extern int entry_b(int x); +int glue(int x) { return entry_a(x) + entry_b(x + 1); } +#else +#ifdef TU_B +#define ENTRY entry_b +#define BASE 5 +#else +#define ENTRY entry_a +#define BASE 10 +static const char __modinfo[] + __attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux"; +#endif +static __attribute__((noinline)) int helper(int x, int len) +{ + int sum = 0, i; + + for (i = 0; i < len; i++) +#if defined(PATCHED) && !defined(TU_B) + sum += i * 2 + BASE; /* only TU_A's helper changes */ +#else + sum += i + BASE; +#endif + return sum + x; +} + +int ENTRY(int x) { return helper(x, 4); } +#endif diff --git a/tools/objtool/tests/generic/test-thinlto-ambiguity.sh b/tools/objtool/tests/generic/test-thinlto-ambiguity.sh new file mode 100755 index 00000000000000..34f4f3a58e2013 --- /dev/null +++ b/tools/objtool/tests/generic/test-thinlto-ambiguity.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Two ThinLTO-promoted symbols sharing a demangled name must be paired up +# correctly. +# +# A file-local symbol which ThinLTO has to make visible is renamed +# helper.llvm.. With two such helpers in one link the original and the +# patched object hold two each, all four spelled differently, and demangling +# gives "helper" for all of them -- so the name is not enough to say which +# corresponds to which. +# +# Getting it wrong is silent and specific: the patch is built against the wrong +# body, so one call site gets the other helper's arithmetic. Nothing fails to +# build and nothing fails to load. +# +# test-thinlto-local covers the unambiguous case, one promoted symbol whose +# hash moved. This is the case where demangling alone is not an answer. +# +# The outcome is asserted, not the machinery: with the clang tested here the +# pairing succeeds even with the .llvm. suffix map disabled and with +# llvm_suffix() stubbed out, so no single-line sabotage distinguishes it. The +# tiered matcher this case was written for is not needed for this shape. +# +# Covers the same ground as corpus/x86_64-llvm-thinlto/ +# thin-lto-demangled-ambiguity and thin-lto-demangled-global-match in Joe +# Lawrence's klp-build unit test corpus. + +. "$(dirname "$0")/../lib.sh" + +setup +clang_only "ThinLTO requires clang" + +find_thinlto_toolchain || + probe_skip "no matching clang/lld pair for a ThinLTO link; set THIN_LD to one" + +build_thinlto() # $1 output object, $2 extra flags +{ + local t + for t in "" -DTU_B -DTU_C; do + $THIN_CC -flto=thin -O2 -ffunction-sections -fdata-sections \ + $2 $t -c "$FIXTURES_DIR/thinlto_ambiguity.c" \ + -o "$workdir/tu$t.o" 2>/dev/null || return 1 + done + "$THIN_LD" -r "$workdir/tu.o" "$workdir/tu-DTU_B.o" "$workdir/tu-DTU_C.o" \ + -o "$1" 2>/dev/null || return 1 +} + +build_thinlto "$workdir/orig.o" "" || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" +build_thinlto "$workdir/patched.o" -DPATCHED || + probe_skip "ThinLTO build failed ($THIN_CC, $THIN_LD)" + +# The premise: two promoted helpers per object, and exactly one of them kept +# its hash -- the one the patch did not touch. Without that there is nothing +# to disambiguate. +orig_syms="$(in_symbols orig.o | grep -oE 'helper\.llvm\.[0-9]+' | sort -u)" +new_syms="$( in_symbols patched.o | grep -oE 'helper\.llvm\.[0-9]+' | sort -u)" +[ "$(echo "$orig_syms" | wc -l)" = 2 ] && [ "$(echo "$new_syms" | wc -l)" = 2 ] || + probe_skip "ThinLTO did not promote two distinct helpers here" + +kept="$(comm -12 <(echo "$orig_syms") <(echo "$new_syms"))" +moved="$(comm -13 <(echo "$orig_syms") <(echo "$new_syms"))" +[ "$(echo "$kept" | wc -w)" = 1 ] && [ "$(echo "$moved" | wc -w)" = 1 ] || + probe_skip "expected one helper to keep its hash and one to move" + +run_diff + +# Exactly one helper is cloned, and it is the one whose body changed. Cloning +# the other, or both, is what a wrong pairing looks like. +assert_not_patched "$kept" + +n="$(out_sections | grep -cE '[[:space:]]\.text\.helper\.llvm\.[0-9]+[[:space:]]')" +[ "$n" = 1 ] || + fail "expected 1 cloned helper, found $n" + +pass "ThinLTO helpers sharing a demangled name paired up correctly" From 84f0e14e6261ef718e6a8583073097ad9ea70e14 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 11:59:59 +0000 Subject: [PATCH 0659/2286] drivers/thermal/renesas/rzg3e: Fix signed integer overflow in temp_to_code() The thermal core passes -INT_MAX/INT_MAX as low/high trip points, which the driver then converts to code values for programming the registers: numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); Although 'numerator' is defined as s64, the arithmetic is performed as int. Because of this, the multiplication overflows for -INT_MAX and, because temp_d_mc is negative, the subtraction overflows for INT_MAX. To fix this, cast the temperature to s64, so that the arithmetic is performed as s64. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-2-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index c44f5b8858d02a..4acee4eaff05de 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -194,7 +194,12 @@ static u16 rzg3e_thermal_temp_to_code(struct rzg3e_thermal_priv *priv, int temp_ s64 numerator, denominator; s64 code; - numerator = (temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); + /* + * Perform the arithmetic in 64 bits so that it cannot overflow for + * -INT_MAX/INT_MAX values passed from the thermal core or when + * userspace writes arbitrary trip point temperatures. + */ + numerator = ((s64)temp_mc - info->temp_d_mc) * (priv->trmval1 - priv->trmval0); denominator = info->temp_e_mc - info->temp_d_mc; code = div64_s64(numerator, denominator) + priv->trmval0; From aa1bc39e45f29a4f56d7e6be7164e100727df465 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:00 +0000 Subject: [PATCH 0660/2286] thermal/drivers/renesas/rzg3e: Fix runtime PM handling of compare irq The TSU hw is limited in the sense that lower and upper trip points can be set, but the hw cannot raise interrupts on its own. It can only signal that the thresholds were passed when the temperature is read, either during polling or on a manual read. Therefore, the interrupt enable state configured by set_trips() must remain active for the next read to trigger an event. Currently, runtime PM autosuspend puts the chip to sleep and clears the compare interrupt that was enabled during set_trips(). On power up, the compare interrupt enable is not restored, which means that it won't ever fire. Fix this by caching the contents of the SIER register during runtime power off and restore it during runtime power on. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-3-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 4acee4eaff05de..45f67b2e11310c 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -93,6 +93,7 @@ struct rzg3e_thermal_info { * @info: chip type specific information * @trmval0: calibration value 0 (b) * @trmval1: calibration value 1 (c) + * @sier: cached interrupt enable register * @lock: protects hardware access during conversions */ struct rzg3e_thermal_priv { @@ -103,6 +104,7 @@ struct rzg3e_thermal_priv { const struct rzg3e_thermal_info *info; u16 trmval0; u16 trmval1; + u32 sier; struct mutex lock; }; @@ -148,12 +150,16 @@ static int rzg3e_thermal_power_on(struct rzg3e_thermal_priv *priv) return ret; } + /* Restore interrupt enable state */ + writel(priv->sier, priv->base + TSU_SIER); + return 0; } static void rzg3e_thermal_power_off(struct rzg3e_thermal_priv *priv) { - /* Disable all interrupts */ + /* Save and disable all interrupts */ + priv->sier = readl(priv->base + TSU_SIER); writel(0, priv->base + TSU_SIER); /* Clear pending interrupts */ From acd9bf8f9047b03f023514bceab6a00e037977ca Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:01 +0000 Subject: [PATCH 0661/2286] thermal/drivers/renesas/rzg3e: Fix race between set_trips() and get_temp() Datasheet section 7.11.9.2 "Conversion Start Trigger" states that a conversion must only be started while no other conversion is in progress: "Before making the setting to start conversion by software or an ELC trigger, check that the CONV bit of the sensor status register is 0b." set_trips() starts a conversion at the end of the function and returns without waiting for it to finish. A get_temp() call arriving right after that starts a conversion while the CONV bit may still be set, violating the requirement above. Fix this by: - polling for CONV == 0 in get_temp() before starting a conversion - not starting a conversion at the end of set_trips() anymore The conversion started at the end of set_trips() was supposed to check whether the thresholds have been passed, since the hw cannot raise interrupts on its own, but only when the temperature is read. However, the thermal core reads the temperature and computes the trip window from it just before calling into set_trips(), so reading the temperature again straight away is useless. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-4-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 45f67b2e11310c..58fdcc66456a0b 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -225,6 +225,16 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp) guard(mutex)(&priv->lock); + /* Make sure a previous conversion is not in progress */ + ret = readl_poll_timeout(priv->base + TSU_SSR, status, + !(status & TSU_SSR_CONV), + TSU_POLL_DELAY_US, + USEC_PER_MSEC); + if (ret) { + dev_err(priv->dev, "Timeout waiting for conversion\n"); + goto out; + } + /* Clear any previous conversion status */ writel(TSU_SICR_ADCLR, priv->base + TSU_SICR); @@ -304,9 +314,8 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, /* Enable comparison with "out of range" mode (CMPCOND=0) */ writel(TSU_CMSR_CMPEN, priv->base + TSU_CMSR); - /* Unmask compare IRQ and start a conversion to evaluate window */ + /* Unmask compare IRQ */ writel(TSU_SIER_CMPIE, priv->base + TSU_SIER); - writel(TSU_STRGR_ADST, priv->base + TSU_STRGR); pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); From a5f1de6d789484f308ff9b571835b3e2e7385444 Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:02 +0000 Subject: [PATCH 0662/2286] thermal/drivers/renesas/rzg3e: Remove unneeded low/high check in set_trips() The hardware manual doesn't specify that the trip settings must be strictly low < high, just that when the CMPCOND bit is 0, an interrupt will be raised when the acquired temperature data is > ULIM or < LLIM. Also, the thermal core already guarantees low < high. Remove the redundant check. Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-5-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index 58fdcc66456a0b..ffe2b872503149 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -275,10 +275,6 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, u32 val; int ret; - /* Hardware requires low < high */ - if (low >= high) - return -EINVAL; - ret = pm_runtime_resume_and_get(priv->dev); if (ret < 0) return ret; From 1c88ceb09d5e5506456a625043bc9b35ce86fc41 Mon Sep 17 00:00:00 2001 From: Yury Norov Date: Mon, 14 Sep 2026 16:49:29 -0400 Subject: [PATCH 0663/2286] bitfield: get rid of __MAKE_OP machinery The __MAKE_OP machinery hides the fixed-width bitfield helper definitions from source searches and makes the end result highly obscured and largely uncontrolled. This follows earlier discussions about making these helpers easier to find: [1], [2]. Move the explicit helpers and their shared checks into linux/bitfield-fix-width.h in both the kernel and tools headers, and include it from bitfield.h to preserve existing users. The repeated overflow check is factored into __assert_field(), preserving the original condition and compile-time diagnostics. With GCC 15.2.0 and x86-64 defconfig plus the bitfield KUnit tests, the before/after kernel builds are binary identical. The __MAKE_OP generates the following 40 functions (including the direct ____MAKE_OP(u8,u8,,) invocation): u8_encode_bits() u8_replace_bits() u8p_replace_bits() u8_get_bits() le16_encode_bits() le16_replace_bits() [dead code] le16p_replace_bits() le16_get_bits() be16_encode_bits() be16_replace_bits() [dead code] be16p_replace_bits() [dead code] be16_get_bits() u16_encode_bits() u16_replace_bits() u16p_replace_bits() u16_get_bits() le32_encode_bits() le32_replace_bits() [dead code] le32p_replace_bits() le32_get_bits() be32_encode_bits() be32_replace_bits() [dead code] be32p_replace_bits() be32_get_bits() u32_encode_bits() u32_replace_bits() u32p_replace_bits() u32_get_bits() le64_encode_bits() le64_replace_bits() [dead code] le64p_replace_bits() [dead code] le64_get_bits() be64_encode_bits() be64_replace_bits() [dead code] be64p_replace_bits() [dead code] be64_get_bits() u64_encode_bits() u64_replace_bits() u64p_replace_bits() u64_get_bits() Functions marked with [dead code] have no in-tree callers and are removed by this change. Link: https://lore.kernel.org/all/20250214073402.0129e259@kernel.org/ [1] Link: https://lore.kernel.org/all/aeub59FBHbCy-KKP@yury/ [2] Assisted-by: OpenAI Codex Reviewed-by: Akihiko Odaki Acked-by: Jakub Kicinski Signed-off-by: Yury Norov --- MAINTAINERS | 2 + include/linux/bitfield-fix-width.h | 235 ++++++++++++++++++++++ include/linux/bitfield.h | 49 +---- tools/include/linux/bitfield-fix-width.h | 236 +++++++++++++++++++++++ tools/include/linux/bitfield.h | 49 +---- 5 files changed, 475 insertions(+), 96 deletions(-) create mode 100644 include/linux/bitfield-fix-width.h create mode 100644 tools/include/linux/bitfield-fix-width.h diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b34..9abdfef63d7467 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4638,6 +4638,7 @@ BITMAP API M: Yury Norov R: Rasmus Villemoes S: Maintained +F: include/linux/bitfield-fix-width.h F: include/linux/bitfield.h F: include/linux/bitmap-str.h F: include/linux/bitmap.h @@ -4658,6 +4659,7 @@ F: lib/find_bit_benchmark.c F: lib/region_alloc_benchmark.c F: lib/test_bitmap.c F: lib/tests/cpumask_kunit.c +F: tools/include/linux/bitfield-fix-width.h F: tools/include/linux/bitfield.h F: tools/include/linux/bitmap.h F: tools/include/linux/bits.h diff --git a/include/linux/bitfield-fix-width.h b/include/linux/bitfield-fix-width.h new file mode 100644 index 00000000000000..fba86bb76999f1 --- /dev/null +++ b/include/linux/bitfield-fix-width.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_BITFIELD_FIX_WIDTH_H +#define _LINUX_BITFIELD_FIX_WIDTH_H + +#include +#include +#include + +extern void __compiletime_error("value doesn't fit into mask") +__field_overflow(void); + +extern void __compiletime_error("bad bitfield mask") +__bad_mask(void); + +static __always_inline +u64 field_multiplier(u64 field) +{ + if ((field | (field - 1)) & ((field | (field - 1)) + 1)) + __bad_mask(); + return field & -field; +} + +static __always_inline +u64 field_mask(u64 field) +{ + return field / field_multiplier(field); +} + +#define field_max(field) ((typeof(field))field_mask(field)) + +#define __assert_field(v, field) \ + do { \ + if (__builtin_constant_p(v) && \ + ((v) & ~field_mask(field))) \ + __field_overflow(); \ + } while (0) + +static __always_inline __must_check +__u8 u8_encode_bits(u8 v, u8 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline __must_check +__u8 u8_replace_bits(__u8 old, u8 val, u8 field) +{ + return (old & ~field) | u8_encode_bits(val, field); +} + +static __always_inline +void u8p_replace_bits(__u8 *p, u8 val, u8 field) +{ + *p = (*p & ~field) | u8_encode_bits(val, field); +} + +static __always_inline __must_check +u8 u8_get_bits(__u8 v, u8 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline __must_check +__le16 le16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return cpu_to_le16((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void le16p_replace_bits(__le16 *p, u16 val, u16 field) +{ + *p = (*p & ~cpu_to_le16(field)) | le16_encode_bits(val, field); +} + +static __always_inline __must_check +u16 le16_get_bits(__le16 v, u16 field) +{ + return (le16_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__be16 be16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return cpu_to_be16((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline __must_check +u16 be16_get_bits(__be16 v, u16 field) +{ + return (be16_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__u16 u16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline __must_check +__u16 u16_replace_bits(__u16 old, u16 val, u16 field) +{ + return (old & ~field) | u16_encode_bits(val, field); +} + +static __always_inline +void u16p_replace_bits(__u16 *p, u16 val, u16 field) +{ + *p = (*p & ~field) | u16_encode_bits(val, field); +} + +static __always_inline __must_check +u16 u16_get_bits(__u16 v, u16 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline __must_check +__le32 le32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return cpu_to_le32((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void le32p_replace_bits(__le32 *p, u32 val, u32 field) +{ + *p = (*p & ~cpu_to_le32(field)) | le32_encode_bits(val, field); +} + +static __always_inline __must_check +u32 le32_get_bits(__le32 v, u32 field) +{ + return (le32_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__be32 be32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return cpu_to_be32((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void be32p_replace_bits(__be32 *p, u32 val, u32 field) +{ + *p = (*p & ~cpu_to_be32(field)) | be32_encode_bits(val, field); +} + +static __always_inline __must_check +u32 be32_get_bits(__be32 v, u32 field) +{ + return (be32_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__u32 u32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline __must_check +__u32 u32_replace_bits(__u32 old, u32 val, u32 field) +{ + return (old & ~field) | u32_encode_bits(val, field); +} + +static __always_inline +void u32p_replace_bits(__u32 *p, u32 val, u32 field) +{ + *p = (*p & ~field) | u32_encode_bits(val, field); +} + +static __always_inline __must_check +u32 u32_get_bits(__u32 v, u32 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline __must_check +__le64 le64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return cpu_to_le64((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline __must_check +u64 le64_get_bits(__le64 v, u64 field) +{ + return (le64_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__be64 be64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return cpu_to_be64((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline __must_check +u64 be64_get_bits(__be64 v, u64 field) +{ + return (be64_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline __must_check +__u64 u64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline __must_check +__u64 u64_replace_bits(__u64 old, u64 val, u64 field) +{ + return (old & ~field) | u64_encode_bits(val, field); +} + +static __always_inline +void u64p_replace_bits(__u64 *p, u64 val, u64 field) +{ + *p = (*p & ~field) | u64_encode_bits(val, field); +} + +static __always_inline __must_check +u64 u64_get_bits(__u64 v, u64 field) +{ + return (v & field) / field_multiplier(field); +} + +#endif /* _LINUX_BITFIELD_FIX_WIDTH_H */ diff --git a/include/linux/bitfield.h b/include/linux/bitfield.h index 14f86e455a678b..e3f7315c1d539b 100644 --- a/include/linux/bitfield.h +++ b/include/linux/bitfield.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include /* * Bitfield access macros @@ -211,53 +211,6 @@ *(_reg_p) |= (((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask)); \ }) -extern void __compiletime_error("value doesn't fit into mask") -__field_overflow(void); -extern void __compiletime_error("bad bitfield mask") -__bad_mask(void); -static __always_inline u64 field_multiplier(u64 field) -{ - if ((field | (field - 1)) & ((field | (field - 1)) + 1)) - __bad_mask(); - return field & -field; -} -static __always_inline u64 field_mask(u64 field) -{ - return field / field_multiplier(field); -} -#define field_max(field) ((typeof(field))field_mask(field)) -#define ____MAKE_OP(type,base,to,from) \ -static __always_inline __##type __must_check type##_encode_bits(base v, base field) \ -{ \ - if (__builtin_constant_p(v) && (v & ~field_mask(field))) \ - __field_overflow(); \ - return to((v & field_mask(field)) * field_multiplier(field)); \ -} \ -static __always_inline __##type __must_check type##_replace_bits(__##type old, \ - base val, base field) \ -{ \ - return (old & ~to(field)) | type##_encode_bits(val, field); \ -} \ -static __always_inline void type##p_replace_bits(__##type *p, \ - base val, base field) \ -{ \ - *p = (*p & ~to(field)) | type##_encode_bits(val, field); \ -} \ -static __always_inline base __must_check type##_get_bits(__##type v, base field) \ -{ \ - return (from(v) & field)/field_multiplier(field); \ -} -#define __MAKE_OP(size) \ - ____MAKE_OP(le##size,u##size,cpu_to_le##size,le##size##_to_cpu) \ - ____MAKE_OP(be##size,u##size,cpu_to_be##size,be##size##_to_cpu) \ - ____MAKE_OP(u##size,u##size,,) -____MAKE_OP(u8,u8,,) -__MAKE_OP(16) -__MAKE_OP(32) -__MAKE_OP(64) -#undef __MAKE_OP -#undef ____MAKE_OP - #define __field_prep(mask, val) \ ({ \ auto __mask = (mask); \ diff --git a/tools/include/linux/bitfield-fix-width.h b/tools/include/linux/bitfield-fix-width.h new file mode 100644 index 00000000000000..8c2a0fa224e997 --- /dev/null +++ b/tools/include/linux/bitfield-fix-width.h @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_BITFIELD_FIX_WIDTH_H +#define _LINUX_BITFIELD_FIX_WIDTH_H + +#include +#include +#include +#include + +extern void __compiletime_error("value doesn't fit into mask") +__field_overflow(void); + +extern void __compiletime_error("bad bitfield mask") +__bad_mask(void); + +static __always_inline +u64 field_multiplier(u64 field) +{ + if ((field | (field - 1)) & ((field | (field - 1)) + 1)) + __bad_mask(); + return field & -field; +} + +static __always_inline +u64 field_mask(u64 field) +{ + return field / field_multiplier(field); +} + +#define field_max(field) ((typeof(field))field_mask(field)) + +#define __assert_field(v, field) \ + do { \ + if (__builtin_constant_p(v) && \ + ((v) & ~field_mask(field))) \ + __field_overflow(); \ + } while (0) + +static __always_inline +__u8 u8_encode_bits(u8 v, u8 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline +__u8 u8_replace_bits(__u8 old, u8 val, u8 field) +{ + return (old & ~field) | u8_encode_bits(val, field); +} + +static __always_inline +void u8p_replace_bits(__u8 *p, u8 val, u8 field) +{ + *p = (*p & ~field) | u8_encode_bits(val, field); +} + +static __always_inline +u8 u8_get_bits(__u8 v, u8 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline +__le16 le16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return cpu_to_le16((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void le16p_replace_bits(__le16 *p, u16 val, u16 field) +{ + *p = (*p & ~cpu_to_le16(field)) | le16_encode_bits(val, field); +} + +static __always_inline +u16 le16_get_bits(__le16 v, u16 field) +{ + return (le16_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__be16 be16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return cpu_to_be16((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +u16 be16_get_bits(__be16 v, u16 field) +{ + return (be16_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__u16 u16_encode_bits(u16 v, u16 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline +__u16 u16_replace_bits(__u16 old, u16 val, u16 field) +{ + return (old & ~field) | u16_encode_bits(val, field); +} + +static __always_inline +void u16p_replace_bits(__u16 *p, u16 val, u16 field) +{ + *p = (*p & ~field) | u16_encode_bits(val, field); +} + +static __always_inline +u16 u16_get_bits(__u16 v, u16 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline +__le32 le32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return cpu_to_le32((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void le32p_replace_bits(__le32 *p, u32 val, u32 field) +{ + *p = (*p & ~cpu_to_le32(field)) | le32_encode_bits(val, field); +} + +static __always_inline +u32 le32_get_bits(__le32 v, u32 field) +{ + return (le32_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__be32 be32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return cpu_to_be32((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +void be32p_replace_bits(__be32 *p, u32 val, u32 field) +{ + *p = (*p & ~cpu_to_be32(field)) | be32_encode_bits(val, field); +} + +static __always_inline +u32 be32_get_bits(__be32 v, u32 field) +{ + return (be32_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__u32 u32_encode_bits(u32 v, u32 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline +__u32 u32_replace_bits(__u32 old, u32 val, u32 field) +{ + return (old & ~field) | u32_encode_bits(val, field); +} + +static __always_inline +void u32p_replace_bits(__u32 *p, u32 val, u32 field) +{ + *p = (*p & ~field) | u32_encode_bits(val, field); +} + +static __always_inline +u32 u32_get_bits(__u32 v, u32 field) +{ + return (v & field) / field_multiplier(field); +} + +static __always_inline +__le64 le64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return cpu_to_le64((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +u64 le64_get_bits(__le64 v, u64 field) +{ + return (le64_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__be64 be64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return cpu_to_be64((v & field_mask(field)) * field_multiplier(field)); +} + +static __always_inline +u64 be64_get_bits(__be64 v, u64 field) +{ + return (be64_to_cpu(v) & field) / field_multiplier(field); +} + +static __always_inline +__u64 u64_encode_bits(u64 v, u64 field) +{ + __assert_field(v, field); + return (v & field_mask(field)) * field_multiplier(field); +} + +static __always_inline +__u64 u64_replace_bits(__u64 old, u64 val, u64 field) +{ + return (old & ~field) | u64_encode_bits(val, field); +} + +static __always_inline +void u64p_replace_bits(__u64 *p, u64 val, u64 field) +{ + *p = (*p & ~field) | u64_encode_bits(val, field); +} + +static __always_inline +u64 u64_get_bits(__u64 v, u64 field) +{ + return (v & field) / field_multiplier(field); +} + +#endif /* _LINUX_BITFIELD_FIX_WIDTH_H */ diff --git a/tools/include/linux/bitfield.h b/tools/include/linux/bitfield.h index ddf81f24956ba0..4508cefabec92d 100644 --- a/tools/include/linux/bitfield.h +++ b/tools/include/linux/bitfield.h @@ -9,7 +9,7 @@ #include #include -#include +#include /* * Bitfield access macros @@ -127,51 +127,4 @@ (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ }) -extern void __compiletime_error("value doesn't fit into mask") -__field_overflow(void); -extern void __compiletime_error("bad bitfield mask") -__bad_mask(void); -static __always_inline u64 field_multiplier(u64 field) -{ - if ((field | (field - 1)) & ((field | (field - 1)) + 1)) - __bad_mask(); - return field & -field; -} -static __always_inline u64 field_mask(u64 field) -{ - return field / field_multiplier(field); -} -#define field_max(field) ((typeof(field))field_mask(field)) -#define ____MAKE_OP(type,base,to,from) \ -static __always_inline __##type type##_encode_bits(base v, base field) \ -{ \ - if (__builtin_constant_p(v) && (v & ~field_mask(field))) \ - __field_overflow(); \ - return to((v & field_mask(field)) * field_multiplier(field)); \ -} \ -static __always_inline __##type type##_replace_bits(__##type old, \ - base val, base field) \ -{ \ - return (old & ~to(field)) | type##_encode_bits(val, field); \ -} \ -static __always_inline void type##p_replace_bits(__##type *p, \ - base val, base field) \ -{ \ - *p = (*p & ~to(field)) | type##_encode_bits(val, field); \ -} \ -static __always_inline base type##_get_bits(__##type v, base field) \ -{ \ - return (from(v) & field)/field_multiplier(field); \ -} -#define __MAKE_OP(size) \ - ____MAKE_OP(le##size,u##size,cpu_to_le##size,le##size##_to_cpu) \ - ____MAKE_OP(be##size,u##size,cpu_to_be##size,be##size##_to_cpu) \ - ____MAKE_OP(u##size,u##size,,) -____MAKE_OP(u8,u8,,) -__MAKE_OP(16) -__MAKE_OP(32) -__MAKE_OP(64) -#undef __MAKE_OP -#undef ____MAKE_OP - #endif From 89eebc9b5f476f47a03a2313f8f21b222e0208fe Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Wed, 12 Aug 2026 15:11:53 -0700 Subject: [PATCH 0664/2286] nilfs2: add iomap operations for direct I/O The patch adds iomap.h with declaration of iomaps operations and iomap.c with implementation of nilfs_iomap_begin(). This method is based on calling the nilfs_bmap_lookup_contig() method. It resolves the file's block-mapping to find the sequence of physically-contiguous blocks starting at starting block. Offsets at or beyond i_size are reported as a hole. For offsets within i_size, the requested length is first clamped to the block-aligned end of the file. Both the hole and mapped cases then report iomap->offset aligned to the containing block (blkoff << i_blkbits) rather than the raw request offset. Co-developed-by: Linmao Li Signed-off-by: Linmao Li cc: Christoph Hellwig cc: Ryusuke Konishi cc: linux-nilfs@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/Makefile | 2 +- fs/nilfs2/iomap.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ fs/nilfs2/iomap.h | 13 +++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 fs/nilfs2/iomap.c create mode 100644 fs/nilfs2/iomap.h diff --git a/fs/nilfs2/Makefile b/fs/nilfs2/Makefile index 43b60b8a4d0783..516e6b85a03cbf 100644 --- a/fs/nilfs2/Makefile +++ b/fs/nilfs2/Makefile @@ -3,4 +3,4 @@ obj-$(CONFIG_NILFS2_FS) += nilfs2.o nilfs2-y := inode.o file.o dir.o super.o namei.o page.o mdt.o \ btnode.o bmap.o btree.o direct.o dat.o recovery.o \ the_nilfs.o segbuf.o segment.o cpfile.o sufile.o \ - ifile.o alloc.o gcinode.o ioctl.o sysfs.o + ifile.o alloc.o gcinode.o ioctl.o sysfs.o iomap.o diff --git a/fs/nilfs2/iomap.c b/fs/nilfs2/iomap.c new file mode 100644 index 00000000000000..e130ed63abd9ac --- /dev/null +++ b/fs/nilfs2/iomap.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NILFS iomap support implementation. + * + * Written by Viacheslav Dubeyko. + */ + +#include +#include +#include "nilfs.h" +#include "mdt.h" +#include "iomap.h" + +static int nilfs_iomap_begin(struct inode *inode, loff_t offset, + loff_t length, unsigned int flags, + struct iomap *iomap, struct iomap *srcmap) +{ + struct the_nilfs *nilfs = inode->i_sb->s_fs_info; + struct nilfs_inode_info *ii = NILFS_I(inode); + sector_t blkoff = offset >> inode->i_blkbits; + loff_t iomap_offset = (loff_t)blkoff << inode->i_blkbits; + unsigned int maxblocks; + __u64 blknum = 0; + int ret; + + /* Completely beyond EOF. Treat as hole */ + if (i_size_read(inode) <= offset) { + iomap->type = IOMAP_HOLE; + iomap->addr = IOMAP_NULL_ADDR; + iomap->offset = offset; + iomap->length = length; + return 0; + } + + /* Clamp length if the requested range goes beyond i_size */ + if (offset + length > i_size_read(inode)) { + loff_t i_size = i_size_read(inode); + unsigned int blocksize = i_blocksize(inode); + + length = round_up(i_size, blocksize) - offset; + } + + maxblocks = min_t(loff_t, length >> inode->i_blkbits, INT_MAX); + if (maxblocks == 0) + maxblocks = 1; + + down_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem); + ret = nilfs_bmap_lookup_contig(ii->i_bmap, blkoff, &blknum, maxblocks); + up_read(&NILFS_MDT(nilfs->ns_dat)->mi_sem); + + if (ret == -ENOENT) { + iomap->type = IOMAP_HOLE; + iomap->addr = IOMAP_NULL_ADDR; + iomap->offset = iomap_offset; + iomap->length = i_blocksize(inode); + return 0; + } else if (ret < 0) + return ret; + + iomap->bdev = inode->i_sb->s_bdev; + iomap->offset = iomap_offset; + iomap->length = (loff_t)ret << inode->i_blkbits; + iomap->addr = (loff_t)blknum << inode->i_blkbits; + iomap->type = IOMAP_MAPPED; + iomap->flags = IOMAP_F_MERGED; + + return 0; +} + +const struct iomap_ops nilfs_iomap_ops = { + .iomap_begin = nilfs_iomap_begin, +}; diff --git a/fs/nilfs2/iomap.h b/fs/nilfs2/iomap.h new file mode 100644 index 00000000000000..adef3e22346df0 --- /dev/null +++ b/fs/nilfs2/iomap.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * NILFS iomap support declarations. + * + * Written by Viacheslav Dubeyko. + */ + +#ifndef _NILFS_IOMAP_H +#define _NILFS_IOMAP_H + +extern const struct iomap_ops nilfs_iomap_ops; + +#endif /* _NILFS_IOMAP_H */ From 081653dbead3aa92f9f3bf303ed9c71b35484c85 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Wed, 12 Aug 2026 15:11:54 -0700 Subject: [PATCH 0665/2286] nilfs2: switch O_DIRECT to iomap based operations The patch eliminates blockdev_direct_IO() from nilfs2 entirely: - nilfs_file_open() now sets FMODE_CAN_ODIRECT explicitly, since permission to open the file O_DIRECT was previously implied by aops->direct_IO being non-NULL. - nilfs_file_read_iter() dispatches O_DIRECT reads to iomap_dio_rw() using nilfs_iomap_ops; everything else still goes through generic_file_read_iter() as before. - nilfs_file_write_iter() strips IOCB_DIRECT and falls through to generic_file_write_iter()'s ordinary buffered path. NILFS2 cannot perform true direct I/O writes: new blocks are delay-allocated and only given a real disk address by the segment constructor, which works on buffer_head lists, not iomap. This reproduces today's actual behavior: the old nilfs_direct_IO() already just returned 0 for WRITE. - nilfs_direct_IO() and the .direct_IO callback on nilfs_aops are removed. - drop the unnecessary "select LEGACY_DIRECT_IO" from Kconfig in favor of "select FS_IOMAP". cc: Christoph Hellwig cc: Ryusuke Konishi cc: linux-nilfs@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/Kconfig | 2 +- fs/nilfs2/file.c | 42 +++++++++++++++++++++++++++++++++++++++--- fs/nilfs2/inode.c | 13 ------------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/fs/nilfs2/Kconfig b/fs/nilfs2/Kconfig index 7dae168e346e30..0a5ace60e6abaa 100644 --- a/fs/nilfs2/Kconfig +++ b/fs/nilfs2/Kconfig @@ -3,7 +3,7 @@ config NILFS2_FS tristate "NILFS2 file system support" select BUFFER_HEAD select CRC32 - select LEGACY_DIRECT_IO + select FS_IOMAP help NILFS2 is a log-structured file system (LFS) supporting continuous snapshotting. In addition to versioning capability of the entire diff --git a/fs/nilfs2/file.c b/fs/nilfs2/file.c index f93b68c4877c5e..30dd8e5ebe5b88 100644 --- a/fs/nilfs2/file.c +++ b/fs/nilfs2/file.c @@ -10,9 +10,12 @@ #include #include #include +#include +#include #include #include "nilfs.h" #include "segment.h" +#include "iomap.h" int nilfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) { @@ -133,20 +136,53 @@ static int nilfs_file_mmap_prepare(struct vm_area_desc *desc) return 0; } +static int nilfs_file_open(struct inode *inode, struct file *file) +{ + file->f_mode |= FMODE_CAN_ODIRECT; + return generic_file_open(inode, file); +} + +static ssize_t nilfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct inode *inode = file_inode(iocb->ki_filp); + ssize_t ret; + + if (iocb->ki_flags & IOCB_DIRECT) { + inode_lock_shared(inode); + ret = iomap_dio_rw(iocb, to, &nilfs_iomap_ops, + NULL, 0, NULL, 0); + inode_unlock_shared(inode); + } else + ret = generic_file_read_iter(iocb, to); + + return ret; +} + +static ssize_t nilfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + /* + * NILFS2 lacks direct I/O write support: fall back to buffered writes. + */ + if (iocb->ki_flags & IOCB_DIRECT) + iocb->ki_flags &= ~IOCB_DIRECT; + + return generic_file_write_iter(iocb, from); +} + /* * We have mostly NULL's here: the current defaults are ok for * the nilfs filesystem. */ const struct file_operations nilfs_file_operations = { .llseek = generic_file_llseek, - .read_iter = generic_file_read_iter, - .write_iter = generic_file_write_iter, + .read_iter = nilfs_file_read_iter, + .write_iter = nilfs_file_write_iter, .unlocked_ioctl = nilfs_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = nilfs_compat_ioctl, #endif /* CONFIG_COMPAT */ .mmap_prepare = nilfs_file_mmap_prepare, - .open = generic_file_open, + .open = nilfs_file_open, /* .release = nilfs_release_file, */ .fsync = nilfs_sync_file, .splice_read = filemap_splice_read, diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index cc2f67eca0562a..05e0389a5fa37a 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -257,18 +257,6 @@ static int nilfs_write_end(const struct kiocb *iocb, return err ? : copied; } -static ssize_t -nilfs_direct_IO(struct kiocb *iocb, struct iov_iter *iter) -{ - struct inode *inode = file_inode(iocb->ki_filp); - - if (iov_iter_rw(iter) == WRITE) - return 0; - - /* Needs synchronization with the cleaner */ - return blockdev_direct_IO(iocb, inode, iter, nilfs_get_block); -} - const struct address_space_operations nilfs_aops = { .read_folio = nilfs_read_folio, .writepages = nilfs_writepages, @@ -277,7 +265,6 @@ const struct address_space_operations nilfs_aops = { .write_begin = nilfs_write_begin, .write_end = nilfs_write_end, .invalidate_folio = block_invalidate_folio, - .direct_IO = nilfs_direct_IO, .migrate_folio = buffer_migrate_folio_norefs, .is_partially_uptodate = block_is_partially_uptodate, }; From ea6fd393cb9e6811a748c7551de0dd3090f9dcde Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Sun, 13 Sep 2026 10:37:27 +0300 Subject: [PATCH 0666/2286] x86/mm/pat: Use pr_warn() for early W^X warnings Nathan Chancellor reports the following warning: CPA detected W^X violation: 8000000000000123 -> 0000000000000123 range: 0xffffffffc0400000 - 0xffffffffc0400fff PFN 100e00 from its_alloc() doing set_memory_x() during early retpoline setup. The warning is not factually wrong but it is unproductive. First, the RWX permission is temporary and fixed up by execmem_restore_rox(). Second, at the time of the warning, all kernel text is mapped RWX. So there is not even a transient actual security issue. The right way to fix this up is to relax RWX detection during boot but make a final verification pass over all kernel page tables before running userspace. There is code to do that today (debug_checkwx()) but it must be enabled explicitly in Kconfig. For now, retain the warning even if it's unproductive, but make it scream less loudly: use pr_warn_once() rather than WARN_ONCE(). Remove the warning once debug_checkwx() behavior becomes unconditional. Closes: https://lore.kernel.org/all/20260905044253.GA3816371@ax162 Reported-by: Nathan Chancellor Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: Dave Hansen Tested-by: Nathan Chancellor Tested-by: Ihor Solodrai Link: https://patch.msgid.link/20260913-fixes-verify-rwx-v3-1-5e1d6a08bd02@kernel.org --- arch/x86/mm/pat/set_memory.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c index b3060000da8d16..a799f539287e25 100644 --- a/arch/x86/mm/pat/set_memory.c +++ b/arch/x86/mm/pat/set_memory.c @@ -697,6 +697,21 @@ static inline pgprot_t verify_rwx(pgprot_t old, pgprot_t new, unsigned long star return new; end = start + npg * PAGE_SIZE - 1; + + /* + * If the kernel text is still RWX, gently complain, this could be a + * false positive. + * Once debug_checkwx() becomes mandatory for CONFIG_STRICT_KERNEL_RWX, + * the warning can be removed completely. + */ + if (!kernel_set_to_readonly) { + pr_warn_once("CPA detected W^X violation: %016llx -> %016llx range: 0x%016lx - 0x%016lx PFN %lx\n", + (unsigned long long)pgprot_val(old), + (unsigned long long)pgprot_val(new), + start, end, pfn); + return new; + } + WARN_ONCE(1, "CPA detected W^X violation: %016llx -> %016llx range: 0x%016lx - 0x%016lx PFN %lx\n", (unsigned long long)pgprot_val(old), (unsigned long long)pgprot_val(new), From c9fc85f1e44e4e933c7e6702b100b9505715f6a8 Mon Sep 17 00:00:00 2001 From: Xu Yilun Date: Mon, 21 Sep 2026 20:39:23 +0800 Subject: [PATCH 0667/2286] x86/virt/tdx: Formalize SEAMCALL leaf version encoding support The TDX architecture includes a syscall-like ABI for OSes to communicate with SEAM mode software. This ABI has the concept of a "leaf". Each leaf is roughly analogous to a Linux syscall: it has a set of register arguments and does one logical thing like adding a page of memory to a VM or running a VM. The TDX architecture refers to this interface function as a "SEAMCALL leaf". Just like syscalls, the TDX architecture wants to evolve the ABI to extend functionality while keeping compatibility. But unlike syscalls, which do this by picking a totally new syscall number, the ABI encodes the "version" number directly into the bits of a register argument. So instead of openatN being whatever the next free number is, the SEAMCALL leaf number and version number are encoded into certain bits in how the RAX register is defined in the ABI. In Linux, several seamcall*() wrappers have been introduced to invoke SEAMCALL leafs. They all take a u64 "fn" argument for the leaf number which eventually gets set in the register. As above, the version number lives in the same register as the leaf number. So callers that want to select a specific version of the leaf, can jam it in the right place in the register by passing it in the "fn" argument. Today only the caller of TDH.VP.INIT does this hack, but future kernel changes will need to select versions for more SEAMCALL leafs. So a less hacky solution is needed. Explicitly define the arguments for seamcall*() wrappers: - Add a build-time assertion to ensure "fn" only contains valid SEAMCALL leaf number bits. Note the P-SEAMLDR selector bit (bit 63) is part of the leaf number for SEAM loader calls, so it is allowed. This also means the "fn" can't be a narrower type, such as u16, to exclude unrelated bits. - Add a "version" field in struct tdx_module_args [1], so most existing callers get a default "version == 0" behavior without code churn. Update the TDH.VP.INIT caller to specify the version descriptively. Encode the leaf number and tdx_module_args.version into RAX in assembly, because this is the place where the "fn" and struct tdx_module_args fields are marshaled into registers. AI was used under supervision to review code and workshop logs. In particular, it helped evaluate the assembly changes. Signed-off-by: Xu Yilun Signed-off-by: Dave Hansen Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Nikolay Borisov Reviewed-by: Tony Lindgren Reviewed-by: Rick Edgecombe Reviewed-by: Kishen Maloor Link: https://lore.kernel.org/kvm/4f4b0f29-424b-45ed-8cfd-c77da2ea390f@intel.com/ # [1] Link: https://patch.msgid.link/20260921-seamcall-version-v7-1-cf05fe76b467@linux.intel.com --- arch/x86/include/asm/shared/tdx.h | 5 +++++ arch/x86/kernel/asm-offsets.c | 1 + arch/x86/virt/vmx/tdx/seamcall_internal.h | 19 +++++++++++++++++++ arch/x86/virt/vmx/tdx/tdx.c | 5 +++-- arch/x86/virt/vmx/tdx/tdx.h | 8 -------- arch/x86/virt/vmx/tdx/tdxcall.S | 10 ++++++++-- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h index f20e91d7ac35be..f2cfa71cf0eab0 100644 --- a/arch/x86/include/asm/shared/tdx.h +++ b/arch/x86/include/asm/shared/tdx.h @@ -143,6 +143,11 @@ struct tdx_module_args { u64 rbx; u64 rdi; u64 rsi; + /* + * Leaf ABI version. Note that it gets encoded into RAX along with the + * leaf number. + */ + u8 version; }; /* Used to communicate with the TDX module */ diff --git a/arch/x86/kernel/asm-offsets.c b/arch/x86/kernel/asm-offsets.c index 081816888f7a05..b3c00ff4d81919 100644 --- a/arch/x86/kernel/asm-offsets.c +++ b/arch/x86/kernel/asm-offsets.c @@ -95,6 +95,7 @@ static void __used common(void) OFFSET(TDX_MODULE_rbx, tdx_module_args, rbx); OFFSET(TDX_MODULE_rdi, tdx_module_args, rdi); OFFSET(TDX_MODULE_rsi, tdx_module_args, rsi); + OFFSET(TDX_MODULE_version, tdx_module_args, version); BLANK(); OFFSET(BP_scratch, boot_params, scratch); diff --git a/arch/x86/virt/vmx/tdx/seamcall_internal.h b/arch/x86/virt/vmx/tdx/seamcall_internal.h index be5f446467dfad..051ad2d45cab77 100644 --- a/arch/x86/virt/vmx/tdx/seamcall_internal.h +++ b/arch/x86/virt/vmx/tdx/seamcall_internal.h @@ -11,6 +11,7 @@ #ifndef _X86_VIRT_SEAMCALL_INTERNAL_H #define _X86_VIRT_SEAMCALL_INTERNAL_H +#include #include #include #include @@ -23,9 +24,27 @@ u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args); typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args); +/* + * SEAMCALL leaf: + * + * Bit 15:0 Leaf number + * Bit 23:16 Leaf ABI version number + * Bit 24 Pending interrupts detection mode + * Bit 63 1 for P-SEAMLDR leaf, 0 for TDX module leaf + */ +#define SEAMCALL_LEAF_MASK GENMASK_U64(15, 0) +#define SEAMCALL_SEAMLDR_MASK BIT_U64(63) + static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn, struct tdx_module_args *args) { + /* + * fn contains leaf number for TDX module calls and P-SEAMLDR calls. + * Other fields in SEAMCALL leaf like leaf ABI version number are in + * struct tdx_module_args. + */ + BUILD_BUG_ON(fn & ~(SEAMCALL_LEAF_MASK | SEAMCALL_SEAMLDR_MASK)); + lockdep_assert_preemption_disabled(); /* diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 063574ed862544..96ced0494b687a 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1959,10 +1959,11 @@ u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid) .rcx = vp->tdvpr_pa, .rdx = initial_rcx, .r8 = x2apicid, + /* apicid requires version == 1. */ + .version = 1, }; - /* apicid requires version == 1. */ - return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args); + return seamcall(TDH_VP_INIT, &args); } EXPORT_SYMBOL_FOR_KVM(tdh_vp_init); diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index a886c54decaadc..db209541d3cdfb 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -52,14 +52,6 @@ #define TDH_PHYMEM_PAMT_REMOVE 59 #define TDH_SYS_DISABLE 69 -/* - * SEAMCALL leaf: - * - * Bit 15:0 Leaf number - * Bit 23:16 Version number - */ -#define TDX_VERSION_SHIFT 16 - /* TDX page types */ #define PT_NDA 0x0 #define PT_RSVD 0x1 diff --git a/arch/x86/virt/vmx/tdx/tdxcall.S b/arch/x86/virt/vmx/tdx/tdxcall.S index 016a2a1ec1d691..a194e83613e7bf 100644 --- a/arch/x86/virt/vmx/tdx/tdxcall.S +++ b/arch/x86/virt/vmx/tdx/tdxcall.S @@ -45,8 +45,14 @@ .macro TDX_MODULE_CALL host:req ret=0 saved=0 FRAME_BEGIN - /* Move Leaf ID to RAX */ - mov %rdi, %rax + /* Leaf ABI version -> RAX[23:16]. Zero rest of RAX. */ + movzbl TDX_MODULE_version(%rsi), %eax + shl $16, %eax + /* + * Combine leaf number arg and leaf ABI version into RAX, they don't + * overlap. + */ + or %rdi, %rax /* Move other input regs from 'struct tdx_module_args' */ movq TDX_MODULE_rcx(%rsi), %rcx From 21668258995bdc99b48f372c2d7d52539c2f2567 Mon Sep 17 00:00:00 2001 From: Davy Felipe Date: Wed, 16 Sep 2026 19:40:16 -0300 Subject: [PATCH 0668/2286] hfs: return -EPERM for unsupported setattr changes hfs_inode_setattr() rejects unsupported UID, GID and mode changes, but returns the value stored in error when the filesystem is not mounted with the quiet option. At this point error is zero, as execution has already passed a successful setattr_prepare() call. As a result, unsupported attribute changes are incorrectly reported to userspace as successful even though the inode remains unchanged. Return -EPERM for these operations while preserving the existing behavior for quiet mounts. A chown test on an HFS image reproduces the problem. Without the fix, chown 1234:1234 returns success while ownership remains 0:0. With the fix, the same operation fails with EPERM and ownership remains unchanged. Signed-off-by: Davy Felipe Reviewed-by: Viacheslav Dubeyko Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260916224016.266800-1-davyfelipe34@gmail.com Signed-off-by: Viacheslav Dubeyko --- fs/hfs/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 2aef3c36a150a2..cce3a7bb95e9ac 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -663,7 +663,7 @@ int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, ((S_ISDIR(inode->i_mode) && (attr->ia_mode != inode->i_mode)) || (attr->ia_mode & ~HFS_VALID_MODE_BITS)))) { - return hsb->s_quiet ? 0 : error; + return hsb->s_quiet ? 0 : -EPERM; } /* map file permissions to the closest allowable permissions in HFS */ From 250ee734115bca1c1f7a38d838909aa83cce87ae Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Wed, 9 Sep 2026 20:35:46 -0700 Subject: [PATCH 0669/2286] x86/sev: Report MSR_AMD64_SEV in sysfs Add SEV_STATUS to the cpu/sev/ sysfs hierarchy and report it for all SEV guest types. [ bp: Merge into a single patch, simplify. ] Signed-off-by: Joerg Roedel Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Tom Lendacky Link: https://lore.kernel.org/all/20260803125827.718024-1-joro@8bytes.org --- .../ABI/testing/sysfs-devices-system-cpu | 10 +++++-- arch/x86/coco/sev/core.c | 30 +++++++++++++++---- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 82d10d556cc89f..7b0aef8b4dd301 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -689,15 +689,21 @@ Description: Umwait control Low order two bits must be zero. What: /sys/devices/system/cpu/sev + /sys/devices/system/cpu/sev/sev_status /sys/devices/system/cpu/sev/vmpl Date: May 2024 Contact: Linux kernel mailing list Description: Secure Encrypted Virtualization (SEV) information - This directory is only present when running as an SEV-SNP guest. + This directory is only present when running as an SEV guest. + + sev_status: Reports the value of the SEV_STATUS MSR which + enumerates the enabled features of an SEV + environment. vmpl: Reports the Virtual Machine Privilege Level (VMPL) at which - the SEV-SNP guest is running. + the SEV-SNP guest is running. This file is only present + when running as an SEV-SNP guest. What: /sys/devices/system/cpu/svm diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c index cc292d7c6fd1e0..eb2e853e950aaa 100644 --- a/arch/x86/coco/sev/core.c +++ b/arch/x86/coco/sev/core.c @@ -1431,15 +1431,22 @@ static ssize_t vmpl_show(struct kobject *kobj, return sysfs_emit(buf, "%d\n", snp_vmpl); } +static ssize_t sev_status_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0x%llx\n", sev_status); +} + static struct kobj_attribute vmpl_attr = __ATTR_RO(vmpl); +static struct kobj_attribute sev_status_attr = __ATTR_RO(sev_status); -static struct attribute *vmpl_attrs[] = { - &vmpl_attr.attr, +static struct attribute *sev_status_attrs[] = { + &sev_status_attr.attr, NULL }; static struct attribute_group sev_attr_group = { - .attrs = vmpl_attrs, + .attrs = sev_status_attrs, }; static int __init sev_sysfs_init(void) @@ -1448,7 +1455,7 @@ static int __init sev_sysfs_init(void) struct device *dev_root; int ret; - if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) + if (!(sev_status & MSR_AMD64_SEV_ENABLED)) return -ENODEV; dev_root = bus_get_dev_root(&cpu_subsys); @@ -1463,7 +1470,20 @@ static int __init sev_sysfs_init(void) ret = sysfs_create_group(sev_kobj, &sev_attr_group); if (ret) - kobject_put(sev_kobj); + goto drop_kobj; + + if (sev_status & MSR_AMD64_SEV_SNP_ENABLED) { + ret = sysfs_add_file_to_group(sev_kobj, &vmpl_attr.attr, NULL); + if (ret) + goto drop_sysfs; + } + + return 0; + +drop_sysfs: + sysfs_remove_group(sev_kobj, &sev_attr_group); +drop_kobj: + kobject_put(sev_kobj); return ret; } From a9b3c7570564de40acb0998ab85c89d546b2122f Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Tue, 22 Sep 2026 08:46:41 +0200 Subject: [PATCH 0670/2286] sched/headers: Replace __ASSEMBLY__ with __ASSEMBLER__ in the header While the GCC and Clang compilers already define __ASSEMBLER__ auto- matically when compiling assembly code, __ASSEMBLY__ is a macro that only gets defined by the Makefiles in the kernel. This can be very confusing when switching between userspace and kernelspace coding, or when dealing with uapi headers that rather should use __ASSEMBLER__ instead. Let's standardize now on the __ASSEMBLER__ macro to avoid this confusion. Signed-off-by: Thomas Huth Signed-off-by: Ingo Molnar Cc: Peter Zijlstra Link: https://patch.msgid.link/20260922064641.61120-1-thuth@redhat.com --- include/uapi/linux/sched.h | 2 +- tools/perf/trace/beauty/include/uapi/linux/sched.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h index 33a4624285cd41..19ffeba894283e 100644 --- a/include/uapi/linux/sched.h +++ b/include/uapi/linux/sched.h @@ -53,7 +53,7 @@ */ #define UNSHARE_EMPTY_MNTNS 0x00100000 /* Unshare an empty mount namespace. */ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /** * struct clone_args - arguments for the clone3 syscall * @flags: Flags for the new process as listed above. diff --git a/tools/perf/trace/beauty/include/uapi/linux/sched.h b/tools/perf/trace/beauty/include/uapi/linux/sched.h index 33a4624285cd41..19ffeba894283e 100644 --- a/tools/perf/trace/beauty/include/uapi/linux/sched.h +++ b/tools/perf/trace/beauty/include/uapi/linux/sched.h @@ -53,7 +53,7 @@ */ #define UNSHARE_EMPTY_MNTNS 0x00100000 /* Unshare an empty mount namespace. */ -#ifndef __ASSEMBLY__ +#ifndef __ASSEMBLER__ /** * struct clone_args - arguments for the clone3 syscall * @flags: Flags for the new process as listed above. From d0c4bce31a579216780b990a09250f15e00f38ff Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Tue, 22 Sep 2026 11:28:59 +0200 Subject: [PATCH 0671/2286] gfs2: gfs2_create_inode excl cleanup Function gfs2_create_inode() is used for creating different types of inodes. Regular inodes can be created exclusively or non-exclusively, while all other inode types are always created exclusively. Right now, gfs2_create_inode() checks the inode type to determine when creates should be exclusive; the excl argument is only set for regular files. Change that to set the excl argument correctly for all inode types; this saves the inode type check in gfs2_create_inode(). Suggested-by: NeilBrown Signed-off-by: Andreas Gruenbacher --- fs/gfs2/inode.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 2ec848b993d030..f748f8b3c73a0e 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -740,7 +740,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, if (error) goto fail_gunlock; - inode = gfs2_dir_search(dir, &dentry->d_name, !S_ISREG(mode) || excl); + inode = gfs2_dir_search(dir, &dentry->d_name, excl); error = PTR_ERR(inode); if (!IS_ERR(inode)) { if (file && (file->f_flags & __O_REGULAR) && @@ -1338,7 +1338,7 @@ static int gfs2_symlink(struct mnt_idmap *idmap, struct inode *dir, if (size >= gfs2_max_stuffed_size(GFS2_I(dir))) return -ENAMETOOLONG; - return gfs2_create_inode(dir, dentry, NULL, S_IFLNK | S_IRWXUGO, 0, symname, size, 0); + return gfs2_create_inode(dir, dentry, NULL, S_IFLNK | S_IRWXUGO, 0, symname, size, 1); } /** @@ -1356,7 +1356,7 @@ static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, { unsigned dsize = gfs2_max_stuffed_size(GFS2_I(dir)); - return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, mode, 0, NULL, dsize, 0)); + return ERR_PTR(gfs2_create_inode(dir, dentry, NULL, mode, 0, NULL, dsize, 1)); } /** @@ -1372,7 +1372,7 @@ static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, static int gfs2_mknod(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { - return gfs2_create_inode(dir, dentry, NULL, mode, dev, NULL, 0, 0); + return gfs2_create_inode(dir, dentry, NULL, mode, dev, NULL, 0, 1); } /** From 1206e6e850c43ec9306a2ec0be04532fd4cf32cb Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 17 Sep 2026 14:12:40 -0700 Subject: [PATCH 0672/2286] s390/keyboard: Allocate key_maps and func_table with ARRAY_SIZE() In preparation for converting the kmalloc family of allocators to the type-aware kmalloc_obj family, we need to make sure that the returned type from the allocation matches the type of the variable being assigned. (The kmalloc family returns "void *", which can be implicitly cast to any pointer type.) These allocate as many pointers as ebc_key_maps and ebc_func_table hold, but the sizes were taken from the whole arrays, which would make the allocation types pointers to those arrays rather than the "u_short **" and "char **" being assigned. Allocate ARRAY_SIZE-many entries instead. The resulting allocation sizes are the same. Build tested ARCH=s390 allmodconfig with GCC s390x-linux-gnu 16.1.0: drivers/s390/char/keyboard.o Assisted-by: LLM coccinelle Signed-off-by: Kees Cook Signed-off-by: Heiko Carstens --- drivers/s390/char/keyboard.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/s390/char/keyboard.c b/drivers/s390/char/keyboard.c index ecd9ace2b3aa8d..63bcb52d6e87c8 100644 --- a/drivers/s390/char/keyboard.c +++ b/drivers/s390/char/keyboard.c @@ -81,7 +81,8 @@ kbd_alloc(void) { kbd = kzalloc_obj(struct kbd_data); if (!kbd) goto out; - kbd->key_maps = kzalloc(sizeof(ebc_key_maps), GFP_KERNEL); + kbd->key_maps = kcalloc(ARRAY_SIZE(ebc_key_maps), + sizeof(*kbd->key_maps), GFP_KERNEL); if (!kbd->key_maps) goto out_kbd; for (i = 0; i < ARRAY_SIZE(ebc_key_maps); i++) { @@ -93,7 +94,8 @@ kbd_alloc(void) { goto out_maps; } } - kbd->func_table = kzalloc(sizeof(ebc_func_table), GFP_KERNEL); + kbd->func_table = kcalloc(ARRAY_SIZE(ebc_func_table), + sizeof(*kbd->func_table), GFP_KERNEL); if (!kbd->func_table) goto out_maps; for (i = 0; i < ARRAY_SIZE(ebc_func_table); i++) { From c25f69edca8c77a197c8d55a2f2ea18ca6976c4c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:08 +0300 Subject: [PATCH 0673/2286] s390/chsc: Use kzalloc() for CUBs cub_alloc() allocates the channel measurement unit blocks and their extended counterparts. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/chsc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index 9689f722c863c1..60a4a7e6086cee 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -931,12 +931,12 @@ static int cub_alloc(struct channel_subsystem *css) int i; for (i = 0; i < CSS_NUM_CUB_PAGES; i++) { - css->cub[i] = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + css->cub[i] = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!css->cub[i]) return -ENOMEM; } for (i = 0; i < CSS_NUM_ECUB_PAGES; i++) { - css->ecub[i] = (void *)get_zeroed_page(GFP_KERNEL); + css->ecub[i] = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!css->ecub[i]) return -ENOMEM; } @@ -949,11 +949,11 @@ static void cub_free(struct channel_subsystem *css) int i; for (i = 0; i < CSS_NUM_CUB_PAGES; i++) { - free_page((unsigned long)css->cub[i]); + kfree(css->cub[i]); css->cub[i] = NULL; } for (i = 0; i < CSS_NUM_ECUB_PAGES; i++) { - free_page((unsigned long)css->ecub[i]); + kfree(css->ecub[i]); css->ecub[i] = NULL; } } From b5555485912a3a81e4038d9be5718c267e82807c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:09 +0300 Subject: [PATCH 0674/2286] s390/chsc: Use kzalloc() for the SEI work area chsc_init() allocates the work area for store event information data. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/chsc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index 60a4a7e6086cee..c3186c0a372b43 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -1142,7 +1142,7 @@ int __init chsc_init(void) { int ret; - sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + sei_page = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); if (!sei_page || !chsc_page) { ret = -ENOMEM; @@ -1154,7 +1154,7 @@ int __init chsc_init(void) return ret; out_err: free_page((unsigned long)chsc_page); - free_page((unsigned long)sei_page); + kfree(sei_page); return ret; } @@ -1162,7 +1162,7 @@ void __init chsc_init_cleanup(void) { crw_unregister_handler(CRW_RSC_CSS); free_page((unsigned long)chsc_page); - free_page((unsigned long)sei_page); + kfree(sei_page); } int __chsc_enable_facility(struct chsc_sda_area *sda_area, int operation_code) From a68e07158f50926e8c6b2bdac0204cbce9605abf Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:10 +0300 Subject: [PATCH 0675/2286] s390/chsc_sch: Use kzalloc() for CHSC requests The CHSC ioctl handlers allocate request and response areas for CHSC commands issued on behalf of userspace. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/chsc_sch.c | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c index b6cb8bb8bcc4b2..c8d6871d1841e8 100644 --- a/drivers/s390/cio/chsc_sch.c +++ b/drivers/s390/cio/chsc_sch.c @@ -292,7 +292,7 @@ static int chsc_ioctl_start(void __user *user_area) if (!css_general_characteristics.dynio) /* It makes no sense to try. */ return -EOPNOTSUPP; - chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); + chsc_area = kzalloc(PAGE_SIZE, GFP_DMA | GFP_KERNEL); if (!chsc_area) return -ENOMEM; request = kzalloc_obj(*request); @@ -321,7 +321,7 @@ static int chsc_ioctl_start(void __user *user_area) snprintf(dbf, sizeof(dbf), "ret:%d", ret); CHSC_LOG(0, dbf); kfree(request); - free_page((unsigned long)chsc_area); + kfree(chsc_area); return ret; } @@ -340,7 +340,7 @@ static int chsc_ioctl_on_close_set(void __user *user_area) ret = -ENOMEM; goto out_unlock; } - on_close_chsc_area = (void *)get_zeroed_page(GFP_DMA | GFP_KERNEL); + on_close_chsc_area = kzalloc(PAGE_SIZE, GFP_DMA | GFP_KERNEL); if (!on_close_chsc_area) { ret = -ENOMEM; goto out_free_request; @@ -353,7 +353,7 @@ static int chsc_ioctl_on_close_set(void __user *user_area) goto out_unlock; out_free_chsc: - free_page((unsigned long)on_close_chsc_area); + kfree(on_close_chsc_area); on_close_chsc_area = NULL; out_free_request: kfree(on_close_request); @@ -375,7 +375,7 @@ static int chsc_ioctl_on_close_remove(void) ret = -ENOENT; goto out_unlock; } - free_page((unsigned long)on_close_chsc_area); + kfree(on_close_chsc_area); on_close_chsc_area = NULL; kfree(on_close_request); on_close_request = NULL; @@ -392,7 +392,7 @@ static int chsc_ioctl_start_sync(void __user *user_area) struct chsc_sync_area *chsc_area; int ret, ccode; - chsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + chsc_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!chsc_area) return -ENOMEM; if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) { @@ -414,7 +414,7 @@ static int chsc_ioctl_start_sync(void __user *user_area) else ret = 0; out_free: - free_page((unsigned long)chsc_area); + kfree(chsc_area); return ret; } @@ -438,7 +438,7 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *scpcd_area; - scpcd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + scpcd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scpcd_area) return -ENOMEM; cd = kzalloc_obj(*cd); @@ -476,7 +476,7 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd) ret = 0; out_free: kfree(cd); - free_page((unsigned long)scpcd_area); + kfree(scpcd_area); return ret; } @@ -500,7 +500,7 @@ static int chsc_ioctl_info_cu(void __user *user_cd) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *scucd_area; - scucd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + scucd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scucd_area) return -ENOMEM; cd = kzalloc_obj(*cd); @@ -538,7 +538,7 @@ static int chsc_ioctl_info_cu(void __user *user_cd) ret = 0; out_free: kfree(cd); - free_page((unsigned long)scucd_area); + kfree(scucd_area); return ret; } @@ -563,7 +563,7 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *sscud_area; - sscud_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + sscud_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sscud_area) return -ENOMEM; cud = kzalloc_obj(*cud); @@ -602,7 +602,7 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud) ret = 0; out_free: kfree(cud); - free_page((unsigned long)sscud_area); + kfree(sscud_area); return ret; } @@ -625,7 +625,7 @@ static int chsc_ioctl_conf_info(void __user *user_ci) u8 data[PAGE_SIZE - 20]; } __attribute__ ((packed)) *sci_area; - sci_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + sci_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sci_area) return -ENOMEM; ci = kzalloc_obj(*ci); @@ -662,7 +662,7 @@ static int chsc_ioctl_conf_info(void __user *user_ci) ret = 0; out_free: kfree(ci); - free_page((unsigned long)sci_area); + kfree(sci_area); return ret; } @@ -696,7 +696,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) u32 res; } __attribute__ ((packed)) *cssids_parm; - sccl_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + sccl_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sccl_area) return -ENOMEM; ccl = kzalloc_obj(*ccl); @@ -745,7 +745,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) ret = 0; out_free: kfree(ccl); - free_page((unsigned long)sccl_area); + kfree(sccl_area); return ret; } @@ -756,7 +756,7 @@ static int chsc_ioctl_chpd(void __user *user_chpd) int ret; chpd = kzalloc_obj(*chpd); - scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + scpd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scpd_area || !chpd) { ret = -ENOMEM; goto out_free; @@ -775,7 +775,7 @@ static int chsc_ioctl_chpd(void __user *user_chpd) ret = -EFAULT; out_free: kfree(chpd); - free_page((unsigned long)scpd_area); + kfree(scpd_area); return ret; } @@ -796,7 +796,7 @@ static int chsc_ioctl_dcal(void __user *user_dcal) u8 data[PAGE_SIZE - 36]; } __attribute__ ((packed)) *sdcal_area; - sdcal_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + sdcal_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sdcal_area) return -ENOMEM; dcal = kzalloc_obj(*dcal); @@ -834,7 +834,7 @@ static int chsc_ioctl_dcal(void __user *user_dcal) ret = 0; out_free: kfree(dcal); - free_page((unsigned long)sdcal_area); + kfree(sdcal_area); return ret; } @@ -904,7 +904,7 @@ static int chsc_release(struct inode *inode, struct file *filp) } snprintf(dbf, sizeof(dbf), "relret:%d", ret); CHSC_LOG(0, dbf); - free_page((unsigned long)on_close_chsc_area); + kfree(on_close_chsc_area); on_close_chsc_area = NULL; kfree(on_close_request); on_close_request = NULL; From 4effd91250d211d67f7a4e2f3199994cee681d87 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:11 +0300 Subject: [PATCH 0676/2286] s390/chsc_sch: Use __free(kfree) for synchronous CHSC requests Use __free(kfree) for local buffers in the synchronous CHSC ioctl handlers and replace their cleanup labels with early returns. Keep the asynchronous and on-close handlers unchanged. Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/chsc_sch.c | 272 ++++++++++++------------------------ 1 file changed, 92 insertions(+), 180 deletions(-) diff --git a/drivers/s390/cio/chsc_sch.c b/drivers/s390/cio/chsc_sch.c index c8d6871d1841e8..225aaac7dd309f 100644 --- a/drivers/s390/cio/chsc_sch.c +++ b/drivers/s390/cio/chsc_sch.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -389,39 +390,29 @@ static int chsc_ioctl_on_close_remove(void) static int chsc_ioctl_start_sync(void __user *user_area) { - struct chsc_sync_area *chsc_area; - int ret, ccode; + struct chsc_sync_area *chsc_area __free(kfree) = NULL; + int ccode; chsc_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!chsc_area) return -ENOMEM; - if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) { - ret = -EFAULT; - goto out_free; - } - if (chsc_area->header.code & 0x4000) { - ret = -EINVAL; - goto out_free; - } + if (copy_from_user(chsc_area, user_area, PAGE_SIZE)) + return -EFAULT; + if (chsc_area->header.code & 0x4000) + return -EINVAL; chsc_log_command(chsc_area); ccode = chsc(chsc_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (copy_to_user(user_area, chsc_area, PAGE_SIZE)) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(chsc_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_info_channel_path(void __user *user_cd) { - struct chsc_chp_cd *cd; - int ret, ccode; + struct chsc_chp_cd *cd __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 : 2; @@ -436,20 +427,16 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd) u32 : 32; struct chsc_header response; u8 data[PAGE_SIZE - 20]; - } __attribute__ ((packed)) *scpcd_area; + } __attribute__ ((packed)) *scpcd_area __free(kfree) = NULL; scpcd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scpcd_area) return -ENOMEM; cd = kzalloc_obj(*cd); - if (!cd) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(cd, user_cd, sizeof(*cd))) { - ret = -EFAULT; - goto out_free; - } + if (!cd) + return -ENOMEM; + if (copy_from_user(cd, user_cd, sizeof(*cd))) + return -EFAULT; scpcd_area->request.length = 0x0010; scpcd_area->request.code = 0x0028; scpcd_area->m = cd->m; @@ -459,31 +446,23 @@ static int chsc_ioctl_info_channel_path(void __user *user_cd) scpcd_area->last_chpid = cd->chpid.id; ccode = chsc(scpcd_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (scpcd_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "scpcd: response code=%x\n", scpcd_area->response.code); - goto out_free; + return -EIO; } memcpy(&cd->cpcb, &scpcd_area->response, scpcd_area->response.length); if (copy_to_user(user_cd, cd, sizeof(*cd))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(cd); - kfree(scpcd_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_info_cu(void __user *user_cd) { - struct chsc_cu_cd *cd; - int ret, ccode; + struct chsc_cu_cd *cd __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 : 2; @@ -498,20 +477,16 @@ static int chsc_ioctl_info_cu(void __user *user_cd) u32 : 32; struct chsc_header response; u8 data[PAGE_SIZE - 20]; - } __attribute__ ((packed)) *scucd_area; + } __attribute__ ((packed)) *scucd_area __free(kfree) = NULL; scucd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scucd_area) return -ENOMEM; cd = kzalloc_obj(*cd); - if (!cd) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(cd, user_cd, sizeof(*cd))) { - ret = -EFAULT; - goto out_free; - } + if (!cd) + return -ENOMEM; + if (copy_from_user(cd, user_cd, sizeof(*cd))) + return -EFAULT; scucd_area->request.length = 0x0010; scucd_area->request.code = 0x0026; scucd_area->m = cd->m; @@ -521,31 +496,23 @@ static int chsc_ioctl_info_cu(void __user *user_cd) scucd_area->last_cun = cd->cun; ccode = chsc(scucd_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (scucd_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "scucd: response code=%x\n", scucd_area->response.code); - goto out_free; + return -EIO; } memcpy(&cd->cucb, &scucd_area->response, scucd_area->response.length); if (copy_to_user(user_cd, cd, sizeof(*cd))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(cd); - kfree(scucd_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_info_sch_cu(void __user *user_cud) { - struct chsc_sch_cud *cud; - int ret, ccode; + struct chsc_sch_cud *cud __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 : 2; @@ -561,20 +528,16 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud) u32 : 32; struct chsc_header response; u8 data[PAGE_SIZE - 20]; - } __attribute__ ((packed)) *sscud_area; + } __attribute__ ((packed)) *sscud_area __free(kfree) = NULL; sscud_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sscud_area) return -ENOMEM; cud = kzalloc_obj(*cud); - if (!cud) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(cud, user_cud, sizeof(*cud))) { - ret = -EFAULT; - goto out_free; - } + if (!cud) + return -ENOMEM; + if (copy_from_user(cud, user_cud, sizeof(*cud))) + return -EFAULT; sscud_area->request.length = 0x0010; sscud_area->request.code = 0x0006; sscud_area->m = cud->schid.m; @@ -585,31 +548,23 @@ static int chsc_ioctl_info_sch_cu(void __user *user_cud) sscud_area->last_sch = cud->schid.sch_no; ccode = chsc(sscud_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (sscud_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "sscud: response code=%x\n", sscud_area->response.code); - goto out_free; + return -EIO; } memcpy(&cud->scub, &sscud_area->response, sscud_area->response.length); if (copy_to_user(user_cud, cud, sizeof(*cud))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(cud); - kfree(sscud_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_conf_info(void __user *user_ci) { - struct chsc_conf_info *ci; - int ret, ccode; + struct chsc_conf_info *ci __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 : 2; @@ -623,20 +578,16 @@ static int chsc_ioctl_conf_info(void __user *user_ci) u64 : 64; struct chsc_header response; u8 data[PAGE_SIZE - 20]; - } __attribute__ ((packed)) *sci_area; + } __attribute__ ((packed)) *sci_area __free(kfree) = NULL; sci_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sci_area) return -ENOMEM; ci = kzalloc_obj(*ci); - if (!ci) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(ci, user_ci, sizeof(*ci))) { - ret = -EFAULT; - goto out_free; - } + if (!ci) + return -ENOMEM; + if (copy_from_user(ci, user_ci, sizeof(*ci))) + return -EFAULT; sci_area->request.length = 0x0010; sci_area->request.code = 0x0012; sci_area->m = ci->id.m; @@ -645,31 +596,23 @@ static int chsc_ioctl_conf_info(void __user *user_ci) sci_area->ssid = ci->id.ssid; ccode = chsc(sci_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (sci_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "sci: response code=%x\n", sci_area->response.code); - goto out_free; + return -EIO; } memcpy(&ci->scid, &sci_area->response, sci_area->response.length); if (copy_to_user(user_ci, ci, sizeof(*ci))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(ci); - kfree(sci_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_conf_comp_list(void __user *user_ccl) { - struct chsc_comp_list *ccl; - int ret, ccode; + struct chsc_comp_list *ccl __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 ctype : 8; @@ -681,7 +624,7 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) u64 : 64; struct chsc_header response; u8 data[PAGE_SIZE - 36]; - } __attribute__ ((packed)) *sccl_area; + } __attribute__ ((packed)) *sccl_area __free(kfree) = NULL; struct { u32 m : 1; u32 : 31; @@ -700,14 +643,10 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) if (!sccl_area) return -ENOMEM; ccl = kzalloc_obj(*ccl); - if (!ccl) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(ccl, user_ccl, sizeof(*ccl))) { - ret = -EFAULT; - goto out_free; - } + if (!ccl) + return -ENOMEM; + if (copy_from_user(ccl, user_ccl, sizeof(*ccl))) + return -EFAULT; sccl_area->request.length = 0x0020; sccl_area->request.code = 0x0030; sccl_area->fmt = ccl->req.fmt; @@ -728,61 +667,46 @@ static int chsc_ioctl_conf_comp_list(void __user *user_ccl) break; } ccode = chsc(sccl_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (sccl_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "sccl: response code=%x\n", sccl_area->response.code); - goto out_free; + return -EIO; } memcpy(&ccl->sccl, &sccl_area->response, sccl_area->response.length); if (copy_to_user(user_ccl, ccl, sizeof(*ccl))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(ccl); - kfree(sccl_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_chpd(void __user *user_chpd) { - struct chsc_scpd *scpd_area; - struct chsc_cpd_info *chpd; + struct chsc_scpd *scpd_area __free(kfree) = NULL; + struct chsc_cpd_info *chpd __free(kfree) = NULL; int ret; chpd = kzalloc_obj(*chpd); scpd_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); - if (!scpd_area || !chpd) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(chpd, user_chpd, sizeof(*chpd))) { - ret = -EFAULT; - goto out_free; - } + if (!scpd_area || !chpd) + return -ENOMEM; + if (copy_from_user(chpd, user_chpd, sizeof(*chpd))) + return -EFAULT; ret = chsc_determine_channel_path_desc(chpd->chpid, chpd->fmt, chpd->rfmt, chpd->c, chpd->m, scpd_area); if (ret) - goto out_free; + return ret; memcpy(&chpd->chpdb, &scpd_area->response, scpd_area->response.length); if (copy_to_user(user_chpd, chpd, sizeof(*chpd))) - ret = -EFAULT; -out_free: - kfree(chpd); - kfree(scpd_area); - return ret; + return -EFAULT; + return 0; } static int chsc_ioctl_dcal(void __user *user_dcal) { - struct chsc_dcal *dcal; - int ret, ccode; + struct chsc_dcal *dcal __free(kfree) = NULL; + int ccode; struct { struct chsc_header request; u32 atype : 8; @@ -794,20 +718,16 @@ static int chsc_ioctl_dcal(void __user *user_dcal) u32 res1[2]; struct chsc_header response; u8 data[PAGE_SIZE - 36]; - } __attribute__ ((packed)) *sdcal_area; + } __attribute__ ((packed)) *sdcal_area __free(kfree) = NULL; sdcal_area = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sdcal_area) return -ENOMEM; dcal = kzalloc_obj(*dcal); - if (!dcal) { - ret = -ENOMEM; - goto out_free; - } - if (copy_from_user(dcal, user_dcal, sizeof(*dcal))) { - ret = -EFAULT; - goto out_free; - } + if (!dcal) + return -ENOMEM; + if (copy_from_user(dcal, user_dcal, sizeof(*dcal))) + return -EFAULT; sdcal_area->request.length = 0x0020; sdcal_area->request.code = 0x0034; sdcal_area->atype = dcal->req.atype; @@ -816,26 +736,18 @@ static int chsc_ioctl_dcal(void __user *user_dcal) sizeof(sdcal_area->list_parm)); ccode = chsc(sdcal_area); - if (ccode != 0) { - ret = -EIO; - goto out_free; - } + if (ccode != 0) + return -EIO; if (sdcal_area->response.code != 0x0001) { - ret = -EIO; CHSC_MSG(0, "sdcal: response code=%x\n", sdcal_area->response.code); - goto out_free; + return -EIO; } memcpy(&dcal->sdcal, &sdcal_area->response, sdcal_area->response.length); if (copy_to_user(user_dcal, dcal, sizeof(*dcal))) - ret = -EFAULT; - else - ret = 0; -out_free: - kfree(dcal); - kfree(sdcal_area); - return ret; + return -EFAULT; + return 0; } static long chsc_ioctl(struct file *filp, unsigned int cmd, From a59ccfb427c60d25ca1e5039e3d4e22f65acc51e Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:12 +0300 Subject: [PATCH 0677/2286] s390/cio: Use kzalloc() for CHSC work areas chsc_init() allocates the work area for CHSC commands and qdio_allocate() allocates one for CHSC calls during qdio_establish(). This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. While on it, change qdio_irq.chsc_page to void * to get rid of the casts. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/chsc.c | 6 +++--- drivers/s390/cio/qdio.h | 2 +- drivers/s390/cio/qdio_main.c | 6 +++--- drivers/s390/cio/qdio_setup.c | 2 +- drivers/s390/cio/qdio_thinint.c | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c index c3186c0a372b43..d98f629e892957 100644 --- a/drivers/s390/cio/chsc.c +++ b/drivers/s390/cio/chsc.c @@ -1143,7 +1143,7 @@ int __init chsc_init(void) int ret; sei_page = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); - chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA); + chsc_page = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!sei_page || !chsc_page) { ret = -ENOMEM; goto out_err; @@ -1153,7 +1153,7 @@ int __init chsc_init(void) goto out_err; return ret; out_err: - free_page((unsigned long)chsc_page); + kfree(chsc_page); kfree(sei_page); return ret; } @@ -1161,7 +1161,7 @@ int __init chsc_init(void) void __init chsc_init_cleanup(void) { crw_unregister_handler(CRW_RSC_CSS); - free_page((unsigned long)chsc_page); + kfree(chsc_page); kfree(sei_page); } diff --git a/drivers/s390/cio/qdio.h b/drivers/s390/cio/qdio.h index 4bd4c00c9c0ca6..dff5f53a8795df 100644 --- a/drivers/s390/cio/qdio.h +++ b/drivers/s390/cio/qdio.h @@ -244,7 +244,7 @@ struct qdio_irq { int perf_stat_enabled; struct qdr *qdr; - unsigned long chsc_page; + void *chsc_page; struct qdio_q *input_qs[QDIO_MAX_QUEUES_PER_IRQ]; struct qdio_q *output_qs[QDIO_MAX_QUEUES_PER_IRQ]; diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index c1e09fa34e7745..d137bf8c70664c 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -934,7 +934,7 @@ int qdio_free(struct ccw_device *cdev) qdio_free_queues(irq_ptr); free_page((unsigned long) irq_ptr->qdr); - free_page(irq_ptr->chsc_page); + kfree(irq_ptr->chsc_page); kfree(irq_ptr->ccw); free_page((unsigned long) irq_ptr); return 0; @@ -986,7 +986,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, * qdio_establish. In case of low memory and swap on a zfcp disk * we may not be able to allocate memory otherwise. */ - irq_ptr->chsc_page = get_zeroed_page(GFP_KERNEL); + irq_ptr->chsc_page = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!irq_ptr->chsc_page) goto err_chsc; @@ -1006,7 +1006,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, err_queues: free_page((unsigned long) irq_ptr->qdr); err_qdr: - free_page(irq_ptr->chsc_page); + kfree(irq_ptr->chsc_page); err_chsc: err_dbf: kfree(irq_ptr->ccw); diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index ea09aadaae4eca..bd80703c174c7b 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -253,7 +253,7 @@ int qdio_setup_get_ssqd(struct qdio_irq *irq_ptr, if (!ssqd) return -ENOMEM; } else { - ssqd = (struct chsc_ssqd_area *)irq_ptr->chsc_page; + ssqd = irq_ptr->chsc_page; } rc = chsc_ssqd(*schid, ssqd); diff --git a/drivers/s390/cio/qdio_thinint.c b/drivers/s390/cio/qdio_thinint.c index e167aa75c3dff7..a2ff51887537fe 100644 --- a/drivers/s390/cio/qdio_thinint.c +++ b/drivers/s390/cio/qdio_thinint.c @@ -136,7 +136,7 @@ static struct airq_struct tiqdio_airq = { static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset) { - struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page; + struct chsc_scssc_area *scssc = irq_ptr->chsc_page; dma64_t summary_indicator_addr, subchannel_indicator_addr; int rc; From db111020cc720a39480f67b9bb8308dd7c0c7099 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:13 +0300 Subject: [PATCH 0678/2286] s390/cmf: Use kmalloc() for the CMB area alloc_cmb() allocates the channel measurement block area shared by devices using the basic channel measurement format. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. The measurement block origin must be 32-byte aligned. Each CMB is 32 bytes, so kmalloc() provides the required alignment without rounding the allocation to a power-of-two number of pages. Reject an empty area before allocating it. Replace use of __get_free_pages() with kmalloc() and free_pages() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/cmf.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/s390/cio/cmf.c b/drivers/s390/cio/cmf.c index 92ab3d546fe47c..8fd41a7dfc20b4 100644 --- a/drivers/s390/cio/cmf.c +++ b/drivers/s390/cio/cmf.c @@ -482,6 +482,9 @@ static int alloc_cmb(struct ccw_device *cdev) ssize_t size; struct cmb_data *cmb_data; + if (cmb_area.num_channels <= 0) + return -ENOMEM; + /* Allocate private cmb_data. */ cmb_data = kzalloc_obj(struct cmb_data); if (!cmb_data) @@ -501,12 +504,12 @@ static int alloc_cmb(struct ccw_device *cdev) WARN_ON(!list_empty(&cmb_area.list)); spin_unlock(&cmb_area.lock); - mem = (void *)__get_free_pages(GFP_KERNEL, get_order(size)); + mem = kmalloc(size, GFP_KERNEL); spin_lock(&cmb_area.lock); if (cmb_area.mem) { /* ok, another thread was faster */ - free_pages((unsigned long)mem, get_order(size)); + kfree(mem); } else if (!mem) { /* no luck */ ret = -ENOMEM; @@ -547,10 +550,8 @@ static void free_cmb(struct ccw_device *cdev) list_del_init(&priv->cmb_list); if (list_empty(&cmb_area.list)) { - ssize_t size; - size = sizeof(struct cmb) * cmb_area.num_channels; cmf_activate(NULL, CMF_OFF); - free_pages((unsigned long)cmb_area.mem, get_order(size)); + kfree(cmb_area.mem); cmb_area.mem = NULL; } spin_unlock_irq(cdev->ccwlock); From 91e4448b300a97422f0233d6273649d912c1e741 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:14 +0300 Subject: [PATCH 0679/2286] s390/idals: Use kmalloc() for IDAL data buffers idal_buffer_alloc() allocates the data chunks of an IDAL buffer that is used for channel I/O. These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_pages() with kmalloc() and free_pages() with kfree(). While on it, tell kmemleak to ignore the data chunks as only their DMA addresses are retained in the IDAL buffer. Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- arch/s390/include/asm/idals.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/idals.h b/arch/s390/include/asm/idals.h index 06e1ec2afd5afd..213f430a8d33a2 100644 --- a/arch/s390/include/asm/idals.h +++ b/arch/s390/include/asm/idals.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -147,9 +148,11 @@ static inline struct idal_buffer *idal_buffer_alloc(size_t size, int page_order) ib->data[i] = dma64_add(ib->data[i - 1], IDA_BLOCK_SIZE); continue; } - vaddr = (void *)__get_free_pages(GFP_KERNEL, page_order); + vaddr = kmalloc(PAGE_SIZE << page_order, GFP_KERNEL); if (!vaddr) goto error; + /* Only DMA addresses are retained in ib->data. */ + kmemleak_ignore(vaddr); ib->data[i] = virt_to_dma64(vaddr); } return ib; @@ -157,7 +160,7 @@ static inline struct idal_buffer *idal_buffer_alloc(size_t size, int page_order) while (i >= nr_chunks) { i -= nr_chunks; vaddr = dma64_to_virt(ib->data[i]); - free_pages((unsigned long)vaddr, ib->page_order); + kfree(vaddr); } kfree(ib); return ERR_PTR(-ENOMEM); @@ -175,7 +178,7 @@ static inline void idal_buffer_free(struct idal_buffer *ib) nr_chunks = (PAGE_SIZE << ib->page_order) >> IDA_SIZE_SHIFT; for (i = 0; i < nr_ptrs; i += nr_chunks) { vaddr = dma64_to_virt(ib->data[i]); - free_pages((unsigned long)vaddr, ib->page_order); + kfree(vaddr); } kfree(ib); } From c76ff72b390b553da029ed79852553e3b917a408 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:15 +0300 Subject: [PATCH 0680/2286] s390/qdio_main: Use kzalloc() for the IRQ structure qdio_allocate() allocates the QDIO irq structure. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). While on it, drop the kmemleak exemption for the CCW. The IRQ structure is now tracked, so kmemleak can find its pointer to the CCW. Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/qdio_main.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index d137bf8c70664c..28eb8602ca97e7 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -12,11 +12,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -936,7 +936,7 @@ int qdio_free(struct ccw_device *cdev) free_page((unsigned long) irq_ptr->qdr); kfree(irq_ptr->chsc_page); kfree(irq_ptr->ccw); - free_page((unsigned long) irq_ptr); + kfree(irq_ptr); return 0; } EXPORT_SYMBOL_GPL(qdio_free); @@ -961,7 +961,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, no_output_qs > QDIO_MAX_QUEUES_PER_IRQ) return -EINVAL; - irq_ptr = (void *) get_zeroed_page(GFP_KERNEL); + irq_ptr = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!irq_ptr) return -ENOMEM; @@ -969,9 +969,6 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, if (!irq_ptr->ccw) goto err_ccw; - /* kmemleak doesn't scan the page-allocated irq_ptr: */ - kmemleak_not_leak(irq_ptr->ccw); - irq_ptr->cdev = cdev; mutex_init(&irq_ptr->setup_mutex); if (qdio_allocate_dbf(irq_ptr)) @@ -1011,7 +1008,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, err_dbf: kfree(irq_ptr->ccw); err_ccw: - free_page((unsigned long) irq_ptr); + kfree(irq_ptr); return rc; } EXPORT_SYMBOL_GPL(qdio_allocate); From b03a58a24497c1b132bbded96d8b416b3a9470be Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:16 +0300 Subject: [PATCH 0681/2286] s390/qdio_main: Use kzalloc() for the QDR qdio_allocate() allocates the queue description record (QDR). This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/qdio_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/s390/cio/qdio_main.c b/drivers/s390/cio/qdio_main.c index 28eb8602ca97e7..889f63f94a4a78 100644 --- a/drivers/s390/cio/qdio_main.c +++ b/drivers/s390/cio/qdio_main.c @@ -933,7 +933,7 @@ int qdio_free(struct ccw_device *cdev) mutex_unlock(&irq_ptr->setup_mutex); qdio_free_queues(irq_ptr); - free_page((unsigned long) irq_ptr->qdr); + kfree(irq_ptr->qdr); kfree(irq_ptr->chsc_page); kfree(irq_ptr->ccw); kfree(irq_ptr); @@ -988,7 +988,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, goto err_chsc; /* qdr is used in ccw1.cda which is u32 */ - irq_ptr->qdr = (struct qdr *) get_zeroed_page(GFP_KERNEL | GFP_DMA); + irq_ptr->qdr = kzalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!irq_ptr->qdr) goto err_qdr; @@ -1001,7 +1001,7 @@ int qdio_allocate(struct ccw_device *cdev, unsigned int no_input_qs, return 0; err_queues: - free_page((unsigned long) irq_ptr->qdr); + kfree(irq_ptr->qdr); err_qdr: kfree(irq_ptr->chsc_page); err_chsc: From a0152eeabf40a0af8118cad9e0402714c15d12a2 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:17 +0300 Subject: [PATCH 0682/2286] s390/qdio_setup: Use kzalloc() for QDIO buffers qdio_alloc_buffers() allocates the QDIO buffers. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of get_zeroed_page() with kzalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/qdio_setup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index bd80703c174c7b..b6f2ff202fe64e 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -35,7 +35,7 @@ void qdio_free_buffers(struct qdio_buffer **buf, unsigned int count) int pos; for (pos = 0; pos < count; pos += QBUFF_PER_PAGE) - free_page((unsigned long) buf[pos]); + kfree(buf[pos]); } EXPORT_SYMBOL_GPL(qdio_free_buffers); @@ -49,7 +49,7 @@ int qdio_alloc_buffers(struct qdio_buffer **buf, unsigned int count) int pos; for (pos = 0; pos < count; pos += QBUFF_PER_PAGE) { - buf[pos] = (void *) get_zeroed_page(GFP_KERNEL); + buf[pos] = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!buf[pos]) { qdio_free_buffers(buf, count); return -ENOMEM; From 77e5421c112a99610f4649c3e63ccc33627b30d0 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:18 +0300 Subject: [PATCH 0683/2286] s390/qdio_setup: Use kmalloc() for the storage list __qdio_allocate_qs() allocates the storage list information block of a queue. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/qdio_setup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index b6f2ff202fe64e..d8e9c0872d15d9 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -83,7 +83,7 @@ static void __qdio_free_queues(struct qdio_q **queues, unsigned int count) for (i = 0; i < count; i++) { q = queues[i]; - free_page((unsigned long)q->sl_page); + kfree(q->sl_page); kmem_cache_free(qdio_q_cache, q); } } @@ -109,7 +109,7 @@ static int __qdio_allocate_qs(struct qdio_q **irq_ptr_qs, int nr_queues) return -ENOMEM; } - q->sl_page = (void *)__get_free_page(GFP_KERNEL); + q->sl_page = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!q->sl_page) { kmem_cache_free(qdio_q_cache, q); __qdio_free_queues(irq_ptr_qs, i); From 4380decdce1f8bed35b0d4b7b23c574f95c528b5 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:19 +0300 Subject: [PATCH 0684/2286] s390/qdio_setup: Use kmalloc() for the SSQD request qdio_setup_get_ssqd() allocates the request block for the Store Subchannel QDIO Data (SSQD) CHSC command. This memory can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/qdio_setup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/s390/cio/qdio_setup.c b/drivers/s390/cio/qdio_setup.c index d8e9c0872d15d9..e1c5c6a8b3b270 100644 --- a/drivers/s390/cio/qdio_setup.c +++ b/drivers/s390/cio/qdio_setup.c @@ -249,7 +249,7 @@ int qdio_setup_get_ssqd(struct qdio_irq *irq_ptr, DBF_EVENT("getssqd:%4x", schid->sch_no); if (!irq_ptr) { - ssqd = (struct chsc_ssqd_area *)__get_free_page(GFP_KERNEL); + ssqd = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!ssqd) return -ENOMEM; } else { @@ -270,7 +270,7 @@ int qdio_setup_get_ssqd(struct qdio_irq *irq_ptr, out: if (!irq_ptr) - free_page((unsigned long)ssqd); + kfree(ssqd); return rc; } From e95e9d86f2be30206a95bd3b8488079231e3e98a Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 16 Sep 2026 15:03:20 +0300 Subject: [PATCH 0685/2286] s390/scm: Use kmalloc() for SCM information scm_update_information() allocates the response buffer for the CHSC Store SCM Information command. This buffer can be allocated with kmalloc() as there's nothing special about it to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). While on it, use __free(kfree) for the local response buffer. Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Assisted-by: copilot:claude-opus Signed-off-by: Mike Rapoport (Microsoft) Reviewed-by: Heiko Carstens Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/cio/scm.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/s390/cio/scm.c b/drivers/s390/cio/scm.c index 171212a6d2d9cd..b79fe023ab8d18 100644 --- a/drivers/s390/cio/scm.c +++ b/drivers/s390/cio/scm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "chsc.h" @@ -224,12 +225,12 @@ static int scm_add(struct chsc_scm_info *scm_info, size_t num) int scm_update_information(void) { - struct chsc_scm_info *scm_info; + struct chsc_scm_info *scm_info __free(kfree) = NULL; u64 token = 0; size_t num; int ret; - scm_info = (void *)__get_free_page(GFP_KERNEL | GFP_DMA); + scm_info = kmalloc(PAGE_SIZE, GFP_KERNEL | GFP_DMA); if (!scm_info) return -ENOMEM; @@ -250,8 +251,6 @@ int scm_update_information(void) token = scm_info->restok; } while (token); - free_page((unsigned long)scm_info); - return ret; } From 15809d78acf4b79a8e3d77144a86abbf369f57df Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:19 +0200 Subject: [PATCH 0686/2286] coredump: refuse negative skips The dump_skip_to() helper calculates a relative skip based on the absolute positon of the coredump: cprm->to_skip = pos - cprm->pos; That's easy to mess up for callers and one already did. This risk endless zero PAGE_SIZE loops or overwriting already written coredump data thereby corrupting the dump. I don't think skipping backwards has any meaning. So warn and refuse. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-2-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/coredump.c b/fs/coredump.c index ac3cd74808c640..fce5ced9476110 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1256,6 +1256,8 @@ EXPORT_SYMBOL(dump_emit); void dump_skip_to(struct coredump_params *cprm, unsigned long pos) { + if (WARN_ON_ONCE(pos < cprm->pos)) + return; cprm->to_skip = pos - cprm->pos; } EXPORT_SYMBOL(dump_skip_to); From 37ceeb9e21cbcd4ec6fad51822d74ec722b9c38e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:20 +0200 Subject: [PATCH 0687/2286] coredump: set the minimum send buffer size The send buffer of the coredump socket is subject to the limit in net.core.wmem_default. af_unix uses sk_sndbuf / 2 - 64 bytes for a single skb. That means a send buffer below that would split a page-sized write into multiple skbs. Raise the send buffer to leave room for a page-sized write plus the header. The default value is well above that. So we really change it when it's below our minimum. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-3-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/coredump.c b/fs/coredump.c index fce5ced9476110..8c3c0bb33f24af 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -665,6 +665,9 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new) } #ifdef CONFIG_UNIX +/* af_unix halves the send buffer to size a single skb. */ +#define COREDUMP_SOCK_SNDBUF_MIN (3 * PAGE_SIZE) + static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm) { struct file *file __free(fput) = NULL; @@ -690,6 +693,10 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params * if (retval < 0) return false; + /* Don't let a page-sized write split into several skbs. */ + socket->sk->sk_sndbuf = max_t(int, socket->sk->sk_sndbuf, + COREDUMP_SOCK_SNDBUF_MIN); + file = sock_alloc_file(socket, 0, NULL); if (IS_ERR(file)) return false; From 73337f1ac1379e31cc37868e5a61e4202f095a88 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:21 +0200 Subject: [PATCH 0688/2286] selftests/coredump: discard the right amount after the coredump request read_coredump_req() gets the leftover wrong twice. It takes the absolute difference of the two sizes, so a test binary that knows a larger struct coredump_req than the kernel sends tries to discard bytes that were never sent. And it hands recv() sizeof(buffer) instead of the number of bytes it wants. So MSG_WAITALL waits for a whole page. Either one blocks until the kernel closes the socket. Which it won't because it is waiting for the coredump ack... It's benign today because struct coredump_req hasn't grown. But let's fix it for the future. Compute the leftover as what the kernel sent beyond what was consumed. Fixes: 59cd658eaf40 ("selftests/coredump: add coredump server selftests") Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-4-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/coredump/coredump_test_helpers.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 2a20faf9cb0ad3..524fa5370593a4 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -235,10 +235,10 @@ bool read_coredump_req(int fd, struct coredump_req *req) fprintf(stderr, "Read coredump request with size %u and mask 0x%llx\n", req->size, (unsigned long long)req->mask); - if (user_size > kernel_size) - remaining_size = user_size - kernel_size; - else + if (kernel_size > user_size) remaining_size = kernel_size - user_size; + else + remaining_size = 0; if (PAGE_SIZE <= remaining_size) return false; @@ -250,7 +250,7 @@ bool read_coredump_req(int fd, struct coredump_req *req) if (remaining_size) { char buffer[PAGE_SIZE]; - ret = recv(fd, buffer, sizeof(buffer), MSG_WAITALL); + ret = recv(fd, buffer, remaining_size, MSG_WAITALL); if (ret != remaining_size) return false; fprintf(stderr, "Discarded %zu bytes of data after coredump request\n", remaining_size); From 368bd3d24da9ed539d9f14e0bd605f6d5bdd55ff Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:22 +0200 Subject: [PATCH 0689/2286] selftests/coredump: collapse the expected request check into the helper All nine callers spell the expected flags. So every new feature bit the kernel learns has to be cargo culted. The callers also all pass COREDUMP_ACK_SIZE_VER0 as the minimum request size although what is being validated is coredump_req->size. And read_coredump_req() makes the same mixup twice more. Clean this all up. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-5-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 36 +++++-------------- .../selftests/coredump/coredump_test.h | 3 +- .../coredump/coredump_test_helpers.c | 35 +++++++++++------- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index d9fa6239b5a9fd..60a357e628eb40 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -151,9 +151,7 @@ TEST_F(coredump, socket_request_kernel) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_kernel: check_coredump_req failed\n"); goto out; } @@ -301,9 +299,7 @@ TEST_F(coredump, socket_request_userspace) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_userspace: check_coredump_req failed\n"); goto out; } @@ -441,9 +437,7 @@ TEST_F(coredump, socket_request_reject) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_reject: check_coredump_req failed\n"); goto out; } @@ -581,9 +575,7 @@ TEST_F(coredump, socket_request_invalid_flag_combination) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_invalid_flag_combination: check_coredump_req failed\n"); goto out; } @@ -702,9 +694,7 @@ TEST_F(coredump, socket_request_unknown_flag) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n"); goto out; } @@ -822,9 +812,7 @@ TEST_F(coredump, socket_request_invalid_size_small) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_invalid_size_small: check_coredump_req failed\n"); goto out; } @@ -944,9 +932,7 @@ TEST_F(coredump, socket_request_invalid_size_large) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n"); goto out; } @@ -1355,9 +1341,7 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps, 500) goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "check_coredump_req failed for fd %d\n", fd_coredump); goto out; } @@ -1509,9 +1493,7 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps_epoll_workers, 500) fprintf(stderr, "socket_multiple_crashing_coredumps_epoll_workers: read_coredump_req failed\n"); goto out; } - if (!check_coredump_req(&req, COREDUMP_ACK_SIZE_VER0, - COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT)) { + if (!check_coredump_req(&req)) { fprintf(stderr, "socket_multiple_crashing_coredumps_epoll_workers: check_coredump_req failed\n"); goto out; } diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h index ed47f01fa53c52..a02809145e2d6f 100644 --- a/tools/testing/selftests/coredump/coredump_test.h +++ b/tools/testing/selftests/coredump/coredump_test.h @@ -51,8 +51,7 @@ bool read_marker(int fd, enum coredump_mark mark); bool read_coredump_req(int fd, struct coredump_req *req); bool send_coredump_ack(int fd, const struct coredump_req *req, __u64 mask, size_t size_ack); -bool check_coredump_req(const struct coredump_req *req, size_t min_size, - __u64 required_mask); +bool check_coredump_req(const struct coredump_req *req); int open_coredump_tmpfile(int fd_tmpfs_detached); void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file); diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 524fa5370593a4..306711e1b24de6 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -200,7 +200,7 @@ bool read_marker(int fd, enum coredump_mark mark) bool read_coredump_req(int fd, struct coredump_req *req) { ssize_t ret; - size_t field_size, user_size, ack_size, kernel_size, remaining_size; + size_t field_size, user_size, known_size, kernel_size, remaining_size; memset(req, 0, sizeof(*req)); field_size = sizeof(req->size); @@ -214,9 +214,9 @@ bool read_coredump_req(int fd, struct coredump_req *req) } kernel_size = req->size; - if (kernel_size < COREDUMP_ACK_SIZE_VER0) { + if (kernel_size < COREDUMP_REQ_SIZE_VER0) { fprintf(stderr, "read_coredump_req: kernel_size %zu < min %d\n", - kernel_size, COREDUMP_ACK_SIZE_VER0); + kernel_size, COREDUMP_REQ_SIZE_VER0); return false; } if (kernel_size >= PAGE_SIZE) { @@ -225,11 +225,11 @@ bool read_coredump_req(int fd, struct coredump_req *req) return false; } - /* Use the minimum of user and kernel size to read the full request. */ + /* Consume as much of the request as we know about. */ user_size = sizeof(struct coredump_req); - ack_size = user_size < kernel_size ? user_size : kernel_size; - ret = recv(fd, req, ack_size, MSG_WAITALL); - if (ret != ack_size) + known_size = user_size < kernel_size ? user_size : kernel_size; + ret = recv(fd, req, known_size, MSG_WAITALL); + if (ret != known_size) return false; fprintf(stderr, "Read coredump request with size %u and mask 0x%llx\n", @@ -287,15 +287,24 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, return true; } -bool check_coredump_req(const struct coredump_req *req, size_t min_size, - __u64 required_mask) +/* Every option the kernel is expected to advertise in coredump_req->mask. */ +#define TEST_REQ_MASK_ALL \ + (COREDUMP_KERNEL | COREDUMP_USERSPACE | \ + COREDUMP_REJECT | COREDUMP_WAIT) + +bool check_coredump_req(const struct coredump_req *req) { - if (req->size < min_size) - return false; - if ((req->mask & required_mask) != required_mask) + if (req->size < COREDUMP_REQ_SIZE_VER0) { + fprintf(stderr, "%s: size %u below minimum %d\n", + __func__, req->size, COREDUMP_REQ_SIZE_VER0); return false; - if (req->mask & ~required_mask) + } + if (req->mask != TEST_REQ_MASK_ALL) { + fprintf(stderr, "%s: mask 0x%llx, expected 0x%llx\n", + __func__, (unsigned long long)req->mask, + (unsigned long long)TEST_REQ_MASK_ALL); return false; + } return true; } From eec8727ba27305f89cce20f0943fbf0e174fe0f3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:23 +0200 Subject: [PATCH 0690/2286] selftests/coredump: add a separate helper header Right now we have coredump_test.h which pulls in the test harness. So it can't be included in coredump_test_helpers.c and it hand-rolls a bunch of stuff that is not needed. Instead of this mess, split everything out into a separate coredump_test_helpers.h header and make both coredump_test.h and coredump_test_helpers.c include it. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-6-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../selftests/coredump/coredump_test.h | 30 +-------------- .../coredump/coredump_test_helpers.c | 17 +-------- .../coredump/coredump_test_helpers.h | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+), 45 deletions(-) create mode 100644 tools/testing/selftests/coredump/coredump_test_helpers.h diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h index a02809145e2d6f..8d99b5cb2f12f8 100644 --- a/tools/testing/selftests/coredump/coredump_test.h +++ b/tools/testing/selftests/coredump/coredump_test.h @@ -3,18 +3,9 @@ #ifndef __COREDUMP_TEST_H #define __COREDUMP_TEST_H -#include -#include -#include - #include "../kselftest_harness.h" -#include "../pidfd/pidfd.h" - -#ifndef PAGE_SIZE -#define PAGE_SIZE 4096 -#endif -#define NUM_THREAD_SPAWN 128 +#include "coredump_test_helpers.h" /* Coredump fixture */ FIXTURE(coredump) @@ -24,15 +15,6 @@ FIXTURE(coredump) int fd_tmpfs_detached; }; -/* Shared helper function declarations */ -void *do_nothing(void *arg); -void crashing_child(void); -int create_detached_tmpfs(void); -int create_and_listen_unix_socket(const char *path); -bool set_core_pattern(const char *pattern); -int get_peer_pidfd(int fd); -bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); - /* Inline helper that uses harness types */ static inline void wait_and_check_coredump_server(pid_t pid_coredump_server, struct __test_metadata *const _metadata, @@ -45,14 +27,4 @@ static inline void wait_and_check_coredump_server(pid_t pid_coredump_server, ASSERT_EQ(WEXITSTATUS(status), 0); } -/* Protocol helper function declarations */ -ssize_t recv_marker(int fd); -bool read_marker(int fd, enum coredump_mark mark); -bool read_coredump_req(int fd, struct coredump_req *req); -bool send_coredump_ack(int fd, const struct coredump_req *req, - __u64 mask, size_t size_ack); -bool check_coredump_req(const struct coredump_req *req); -int open_coredump_tmpfile(int fd_tmpfs_detached); -void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file); - #endif /* __COREDUMP_TEST_H */ diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 306711e1b24de6..570fc2e005c206 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -20,23 +20,8 @@ #include #include "../filesystems/wrappers.h" -#include "../pidfd/pidfd.h" -/* Forward declarations to avoid including harness header */ -struct __test_metadata; - -/* Match the fixture definition from coredump_test.h */ -struct _fixture_coredump_data { - char original_core_pattern[256]; - pid_t pid_coredump_server; - int fd_tmpfs_detached; -}; - -#ifndef PAGE_SIZE -#define PAGE_SIZE 4096 -#endif - -#define NUM_THREAD_SPAWN 128 +#include "coredump_test_helpers.h" void *do_nothing(void *arg) { diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h new file mode 100644 index 00000000000000..45904bd177b802 --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __COREDUMP_TEST_HELPERS_H +#define __COREDUMP_TEST_HELPERS_H + +#include +#include +#include + +#include "../pidfd/pidfd.h" + +#ifndef PAGE_SIZE +#define PAGE_SIZE 4096 +#endif + +#define NUM_THREAD_SPAWN 128 + +/* Shared helper function declarations */ +void *do_nothing(void *arg); +void crashing_child(void); +int create_detached_tmpfs(void); +int create_and_listen_unix_socket(const char *path); +bool set_core_pattern(const char *pattern); +int get_peer_pidfd(int fd); +bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); + +/* Protocol helper function declarations */ +ssize_t recv_marker(int fd); +bool read_marker(int fd, enum coredump_mark mark); +bool read_coredump_req(int fd, struct coredump_req *req); +bool send_coredump_ack(int fd, const struct coredump_req *req, + __u64 mask, size_t size_ack); +bool check_coredump_req(const struct coredump_req *req); +int open_coredump_tmpfile(int fd_tmpfs_detached); +void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file); + +#endif /* __COREDUMP_TEST_HELPERS_H */ From dbaf323acee19ab379537e6d9a6a68bc4eacdc76 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:24 +0200 Subject: [PATCH 0691/2286] coredump: pin the protocol struct sizes COREDUMP_REQ_SIZE_VER0 and COREDUMP_ACK_SIZE_VER0 define the initial struct sizes. Assert that both published sizes still match their structs. While at it fix some issues with docs. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-7-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 2 ++ include/uapi/linux/coredump.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 8c3c0bb33f24af..117296db821e1d 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -759,6 +759,8 @@ static inline bool coredump_sock_send(struct file *file, struct coredump_req *re return ret == sizeof(*req); } +static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER0); +static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER0); static_assert(sizeof(enum coredump_mark) == sizeof(__u32)); static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark) diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h index dc3789b78af021..662e0468da6e76 100644 --- a/include/uapi/linux/coredump.h +++ b/include/uapi/linux/coredump.h @@ -30,11 +30,11 @@ enum { * member is set to the size of struct coredump_req and provides a hint * to userspace how much data can be read. Userspace may use MSG_PEEK to * peek the size of struct coredump_req and then choose to consume it in - * one go. Userspace may also simply read a COREDUMP_ACK_SIZE_VER0 + * one go. Userspace may also simply read a COREDUMP_REQ_SIZE_VER0 * request. If the size the kernel sends is larger userspace simply * discards any remaining data. * - * The coredump_req->mask member is set to the currently know features. + * The coredump_req->mask member is set to the currently known features. * Userspace may only set coredump_ack->mask to the bits raised by the * kernel in coredump_req->mask. * From 629d7411b500df45bcb606e0cec2adc0ba29b6c4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:25 +0200 Subject: [PATCH 0692/2286] coredump: move the negotiated mask into struct coredump_params The coredump server negotiates a set of COREDUMP_* options with the kernel. The core dump path cannot see them though. Move the mask into struct coredump_params to make them available. No functional change. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-8-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 15 +++++++-------- include/linux/coredump.h | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 117296db821e1d..f8372871602b6e 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -100,7 +100,6 @@ struct core_name { unsigned int core_pipe_limit; bool core_dumped; enum coredump_type_t core_type; - u64 mask; }; static int expand_corename(struct core_name *cn, int size) @@ -245,9 +244,9 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm, int pid_in_pattern = 0; int err = 0; - cn->mask = COREDUMP_KERNEL; + cprm->mask = COREDUMP_KERNEL; if (core_pipe_limit) - cn->mask |= COREDUMP_WAIT; + cprm->mask |= COREDUMP_WAIT; cn->used = 0; cn->corename = NULL; cn->core_pipe_limit = 0; @@ -860,7 +859,7 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * return false; } - cn->mask = ack.mask; + cprm->mask = ack.mask; return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK); } @@ -1124,7 +1123,7 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, } /* Don't even generate the coredump. */ - if (cn->mask & COREDUMP_REJECT) + if (cprm->mask & COREDUMP_REJECT) return; /* get us an unshared descriptor table; almost always a no-op */ @@ -1132,13 +1131,13 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, if (unshare_files()) return; - if ((cn->mask & COREDUMP_KERNEL) && !coredump_write(cn, cprm, binfmt)) + if ((cprm->mask & COREDUMP_KERNEL) && !coredump_write(cn, cprm, binfmt)) return; coredump_sock_shutdown(cprm->file); /* Let the parent know that a coredump was generated. */ - if (cn->mask & COREDUMP_USERSPACE) + if (cprm->mask & COREDUMP_USERSPACE) cn->core_dumped = true; /* @@ -1146,7 +1145,7 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, * or usermodehelper to finish before exiting so it can e.g., * inspect /proc/. */ - if (cn->mask & COREDUMP_WAIT) { + if (cprm->mask & COREDUMP_WAIT) { switch (cn->core_type) { case COREDUMP_PIPE: wait_for_dump_helpers(cprm->file); diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 7b38ee2e7913be..dc7a05b1bb0a96 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -26,6 +26,8 @@ struct coredump_params { /* Snapshot of dumpable at dump start. */ enum task_dumpable dumpable; int cpu; + /* COREDUMP_* options negotiated with the coredump server. */ + u64 mask; loff_t written; loff_t pos; loff_t to_skip; From dc76b66c1a7e78c481bdc33fded0e9157ff39f6c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:26 +0200 Subject: [PATCH 0693/2286] coredump: deduplicate the to_skip flush dump_emit() and dump_emit_page() open-code the same flush of the accumulated cprm->to_skip. Move it into a helper. No functional change. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-9-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index f8372871602b6e..11f4963395f015 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1251,13 +1251,21 @@ static int __dump_skip(struct coredump_params *cprm, size_t nr) return __dump_emit(cprm, zeroes, nr); } -int dump_emit(struct coredump_params *cprm, const void *addr, int nr) +/* Flush the accumulated hole before writing data. */ +static int dump_flush_skip(struct coredump_params *cprm) { if (cprm->to_skip) { if (!__dump_skip(cprm, cprm->to_skip)) return 0; cprm->to_skip = 0; } + return 1; +} + +int dump_emit(struct coredump_params *cprm, const void *addr, int nr) +{ + if (!dump_flush_skip(cprm)) + return 0; return __dump_emit(cprm, addr, nr); } EXPORT_SYMBOL(dump_emit); @@ -1288,11 +1296,8 @@ static int dump_emit_page(struct coredump_params *cprm, struct page *page) if (!page) return 0; - if (cprm->to_skip) { - if (!__dump_skip(cprm, cprm->to_skip)) - return 0; - cprm->to_skip = 0; - } + if (!dump_flush_skip(cprm)) + return 0; if (cprm->written + PAGE_SIZE > cprm->limit) return 0; if (dump_interrupted()) From 302b7d6349cfb1d241de70146adfc227f4d81202 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:27 +0200 Subject: [PATCH 0694/2286] coredump: make the dump helper return bool The various dump helpers return one and zero. Every caller just does a boolean test. Convert them to return an actual bool. While at it, drop the externs. No functional changes. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-10-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 63 ++++++++++++++++++++-------------------- include/linux/coredump.h | 14 ++++----- 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 11f4963395f015..948de98263185b 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1210,41 +1210,41 @@ void vfs_coredump(const kernel_siginfo_t *siginfo) * do on a core-file: use only these functions to write out all the * necessary info. */ -static int __dump_emit(struct coredump_params *cprm, const void *addr, int nr) +static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) { struct file *file = cprm->file; loff_t pos = file->f_pos; ssize_t n; if (cprm->written + nr > cprm->limit) - return 0; + return false; if (dump_interrupted()) - return 0; + return false; n = __kernel_write(file, addr, nr, &pos); if (n != nr) - return 0; + return false; file->f_pos = pos; cprm->written += n; cprm->pos += n; - return 1; + return true; } -static int __dump_skip(struct coredump_params *cprm, size_t nr) +static bool __dump_skip(struct coredump_params *cprm, size_t nr) { static char zeroes[PAGE_SIZE]; struct file *file = cprm->file; if (file->f_mode & FMODE_LSEEK) { if (dump_interrupted() || vfs_llseek(file, nr, SEEK_CUR) < 0) - return 0; + return false; cprm->pos += nr; - return 1; + return true; } while (nr > PAGE_SIZE) { if (!__dump_emit(cprm, zeroes, PAGE_SIZE)) - return 0; + return false; nr -= PAGE_SIZE; } @@ -1252,20 +1252,20 @@ static int __dump_skip(struct coredump_params *cprm, size_t nr) } /* Flush the accumulated hole before writing data. */ -static int dump_flush_skip(struct coredump_params *cprm) +static bool dump_flush_skip(struct coredump_params *cprm) { if (cprm->to_skip) { if (!__dump_skip(cprm, cprm->to_skip)) - return 0; + return false; cprm->to_skip = 0; } - return 1; + return true; } -int dump_emit(struct coredump_params *cprm, const void *addr, int nr) +bool dump_emit(struct coredump_params *cprm, const void *addr, int nr) { if (!dump_flush_skip(cprm)) - return 0; + return false; return __dump_emit(cprm, addr, nr); } EXPORT_SYMBOL(dump_emit); @@ -1285,7 +1285,7 @@ void dump_skip(struct coredump_params *cprm, size_t nr) EXPORT_SYMBOL(dump_skip); #ifdef CONFIG_ELF_CORE -static int dump_emit_page(struct coredump_params *cprm, struct page *page) +static bool dump_emit_page(struct coredump_params *cprm, struct page *page) { struct bio_vec bvec; struct iov_iter iter; @@ -1294,25 +1294,25 @@ static int dump_emit_page(struct coredump_params *cprm, struct page *page) ssize_t n; if (!page) - return 0; + return false; if (!dump_flush_skip(cprm)) - return 0; + return false; if (cprm->written + PAGE_SIZE > cprm->limit) - return 0; + return false; if (dump_interrupted()) - return 0; + return false; pos = file->f_pos; bvec_set_page(&bvec, page, PAGE_SIZE, 0); iov_iter_bvec(&iter, ITER_SOURCE, &bvec, 1, PAGE_SIZE); n = __kernel_write_iter(cprm->file, &iter, &pos); if (n != PAGE_SIZE) - return 0; + return false; file->f_pos = pos; cprm->written += PAGE_SIZE; cprm->pos += PAGE_SIZE; - return 1; + return true; } /* @@ -1344,18 +1344,19 @@ static inline struct page *dump_page_copy(struct page *src, struct page *dst) } #endif -int dump_user_range(struct coredump_params *cprm, unsigned long start, - unsigned long len) +bool dump_user_range(struct coredump_params *cprm, unsigned long start, + unsigned long len) { unsigned long addr; struct page *dump_page; - int locked, ret; + int locked; + bool ret; dump_page = dump_page_alloc(); if (!dump_page) - return 0; + return false; - ret = 0; + ret = false; locked = 0; for (addr = start; addr < start + len; addr += PAGE_SIZE) { struct page *page; @@ -1379,7 +1380,7 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start, mmap_read_unlock(current->mm); locked = 0; } - int stop = !dump_emit_page(cprm, dump_page_copy(page, dump_page)); + bool stop = !dump_emit_page(cprm, dump_page_copy(page, dump_page)); put_page(page); if (stop) goto out; @@ -1398,7 +1399,7 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start, } cond_resched(); } - ret = 1; + ret = true; out: if (locked) mmap_read_unlock(current->mm); @@ -1408,14 +1409,14 @@ int dump_user_range(struct coredump_params *cprm, unsigned long start, } #endif -int dump_align(struct coredump_params *cprm, int align) +bool dump_align(struct coredump_params *cprm, int align) { unsigned mod = (cprm->pos + cprm->to_skip) & (align - 1); if (align & (align - 1)) - return 0; + return false; if (mod) cprm->to_skip += align - mod; - return 1; + return true; } EXPORT_SYMBOL(dump_align); diff --git a/include/linux/coredump.h b/include/linux/coredump.h index dc7a05b1bb0a96..943bddfb22bf8e 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -43,13 +43,13 @@ extern unsigned int core_file_note_size_limit; * These are the only things you should do on a core-file: use only these * functions to write out all the necessary info. */ -extern void dump_skip_to(struct coredump_params *cprm, unsigned long to); -extern void dump_skip(struct coredump_params *cprm, size_t nr); -extern int dump_emit(struct coredump_params *cprm, const void *addr, int nr); -extern int dump_align(struct coredump_params *cprm, int align); -int dump_user_range(struct coredump_params *cprm, unsigned long start, - unsigned long len); -extern void vfs_coredump(const kernel_siginfo_t *siginfo); +void dump_skip_to(struct coredump_params *cprm, unsigned long to); +void dump_skip(struct coredump_params *cprm, size_t nr); +bool dump_emit(struct coredump_params *cprm, const void *addr, int nr); +bool dump_align(struct coredump_params *cprm, int align); +bool dump_user_range(struct coredump_params *cprm, unsigned long start, + unsigned long len); +void vfs_coredump(const kernel_siginfo_t *siginfo); /* * Logging for the coredump code, ratelimited. From af7e6acf00bfc028073b4cea2e9d313703987144 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:28 +0200 Subject: [PATCH 0695/2286] coredump: always chunk writes Right now dump_emit() is the only coredump helper that writes buffers larger than a page in one call. For elf notes that can easily blow past PAGE_SIZE. That's annoying because neither pipes nor af_unix sockets take such writes in one piece. If a signal arrives while the writer is waiting they drop a short write. With the coredump records work coming up that means header and its data are desynchronized. A write that fits in one pipe buffer or one skb doesn't suffer from this. So split all writes up, including elf notes, and cap every write at a page. The coredump socket already raises sk_sndbuf far enough for a page to fit a single skb and pipes always work that way. That means dump_interrupted() is now checked once per page. So a large coredump stops earlier (good). An empty write no longer issues a zero-length write. The rlimit core check stays where it was. It continues refusing whole writes. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-11-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 948de98263185b..de3c2c72029820 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1210,19 +1210,21 @@ void vfs_coredump(const kernel_siginfo_t *siginfo) * do on a core-file: use only these functions to write out all the * necessary info. */ -static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) +/* One write, never more than a page. See __dump_emit(). */ +static bool dump_emit_chunk(struct coredump_params *cprm, const void *addr, + int nr) { struct file *file = cprm->file; loff_t pos = file->f_pos; ssize_t n; - if (cprm->written + nr > cprm->limit) - return false; if (dump_interrupted()) return false; + n = __kernel_write(file, addr, nr, &pos); if (n != nr) return false; + file->f_pos = pos; cprm->written += n; cprm->pos += n; @@ -1230,6 +1232,24 @@ static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) return true; } +static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) +{ + if (cprm->written + nr > cprm->limit) + return false; + + while (nr) { + int chunk = min_t(int, nr, PAGE_SIZE); + + if (!dump_emit_chunk(cprm, addr, chunk)) + return false; + + addr += chunk; + nr -= chunk; + } + + return true; +} + static bool __dump_skip(struct coredump_params *cprm, size_t nr) { static char zeroes[PAGE_SIZE]; @@ -1242,13 +1262,16 @@ static bool __dump_skip(struct coredump_params *cprm, size_t nr) return true; } - while (nr > PAGE_SIZE) { - if (!__dump_emit(cprm, zeroes, PAGE_SIZE)) + while (nr) { + size_t chunk = min_t(size_t, nr, PAGE_SIZE); + + if (!__dump_emit(cprm, zeroes, chunk)) return false; - nr -= PAGE_SIZE; + + nr -= chunk; } - return __dump_emit(cprm, zeroes, nr); + return true; } /* Flush the accumulated hole before writing data. */ From 98a2606e329542625a67d4d980778581148c7e0a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:29 +0200 Subject: [PATCH 0696/2286] coredump: clean up coredump state handling Right now coredump state handling is messy. The binfmt->core_dump:: methods return 1 when the coredump method did anything at all which means that a partial write counts as having dumped core. This is fine as the state is really only used to indicate that a coredump event occurred in the exit status of the task. That should obviously be indicated even if the actual writeout of the coredump failed. But it means the coredump method of the binary formats is different from all the other coredump helpers. And there's no way to communicate to userspace that a coredump was truncated. We'll add support for that in a second. For now, clean this up. Add a flag member into struct coredump_params. Let the coredump method raise COREDUMP_STATE_STARTED. This is what coredump_finish() will end up using to splice in the coredump bit into the exit status. This allows us to let the return value mean success or failure and align it with the other coredump helpers. We also start raising COREDUMP_STATE_TRUNCATED. This will be used in the next patches to communicate truncation to userspace via the coredump socket. No functional changes. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-12-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_elf.c | 12 +++++++----- fs/binfmt_elf_fdpic.c | 12 +++++++----- fs/coredump.c | 31 +++++++++++++++++-------------- include/linux/binfmts.h | 3 ++- include/linux/coredump.h | 12 ++++++++++++ 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 06d0df1053823e..78112e76eb023a 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -74,7 +74,7 @@ static int load_elf_binary(struct linux_binprm *bprm); * don't even try. */ #ifdef CONFIG_ELF_CORE -static int elf_core_dump(struct coredump_params *cprm); +static bool elf_core_dump(struct coredump_params *cprm); #else #define elf_core_dump NULL #endif @@ -1987,9 +1987,9 @@ static void fill_extnum_info(struct elfhdr *elf, struct elf_shdr *shdr4extnum, * and then they are actually written out. If we run out of core limit * we just truncate. */ -static int elf_core_dump(struct coredump_params *cprm) +static bool elf_core_dump(struct coredump_params *cprm) { - int has_dumped = 0; + bool ret = false; int segs, i; struct elfhdr elf; loff_t offset = 0, dataoff; @@ -2020,7 +2020,7 @@ static int elf_core_dump(struct coredump_params *cprm) if (!fill_note_info(&elf, e_phnum, &info, cprm)) goto end_coredump; - has_dumped = 1; + cprm->state |= COREDUMP_STATE_STARTED; offset += sizeof(elf); /* ELF header */ offset += segs * sizeof(struct elf_phdr); /* Program headers */ @@ -2115,11 +2115,13 @@ static int elf_core_dump(struct coredump_params *cprm) goto end_coredump; } + ret = true; + end_coredump: free_note_info(&info); kfree(shdr4extnum); kfree(phdr4note); - return has_dumped; + return ret; } #endif /* CONFIG_ELF_CORE */ diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 068c46875c7442..005f0a08448381 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -75,7 +75,7 @@ static int elf_fdpic_map_file_by_direct_mmap(struct elf_fdpic_params *, struct file *, struct mm_struct *); #ifdef CONFIG_ELF_CORE -static int elf_fdpic_core_dump(struct coredump_params *cprm); +static bool elf_fdpic_core_dump(struct coredump_params *cprm); #endif static struct linux_binfmt elf_fdpic_format = { @@ -1477,9 +1477,9 @@ static bool elf_fdpic_dump_segments(struct coredump_params *cprm, * and then they are actually written out. If we run out of core limit * we just truncate. */ -static int elf_fdpic_core_dump(struct coredump_params *cprm) +static bool elf_fdpic_core_dump(struct coredump_params *cprm) { - int has_dumped = 0; + bool ret = false; int segs; int i; struct elfhdr *elf = NULL; @@ -1536,7 +1536,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm) /* Set up header */ fill_elf_fdpic_header(elf, e_phnum); - has_dumped = 1; + cprm->state |= COREDUMP_STATE_STARTED; /* * Set up the notes in similar form to SVR4 core dumps made * with info from their /proc. @@ -1656,6 +1656,8 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm) cprm->file->f_pos, offset); } + ret = true; + end_coredump: while (thread_list) { tmp = thread_list; @@ -1666,7 +1668,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm) kfree(elf); kfree(psinfo); kfree(shdr4extnum); - return has_dumped; + return ret; } #endif /* CONFIG_ELF_CORE */ diff --git a/fs/coredump.c b/fs/coredump.c index de3c2c72029820..5c38d44238d2d5 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -98,7 +98,6 @@ struct core_name { char *corename __counted_by_ptr(size); int used, size; unsigned int core_pipe_limit; - bool core_dumped; enum coredump_type_t core_type; }; @@ -250,7 +249,6 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm, cn->used = 0; cn->corename = NULL; cn->core_pipe_limit = 0; - cn->core_dumped = false; if (*pat_ptr == '|') cn->core_type = COREDUMP_PIPE; else if (*pat_ptr == '@') @@ -549,13 +547,13 @@ static int coredump_wait(int exit_code, struct core_state *core_state) return core_waiters; } -static void coredump_finish(bool core_dumped) +static void coredump_finish(enum coredump_state state) { struct core_thread *curr, *next; struct task_struct *task; spin_lock_irq(¤t->sighand->siglock); - if (core_dumped && !__fatal_signal_pending(current)) + if ((state & COREDUMP_STATE_STARTED) && !__fatal_signal_pending(current)) current->signal->group_exit_code |= 0x80; next = current->signal->core_state->dumper.next; current->signal->core_state = NULL; @@ -1040,19 +1038,23 @@ static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm, return true; } -static bool coredump_write(struct core_name *cn, - struct coredump_params *cprm, - const struct linux_binfmt *binfmt) +static bool coredump_write(struct coredump_params *cprm, + const struct linux_binfmt *binfmt) { - if (dump_interrupted()) + if (dump_interrupted()) { + cprm->state |= COREDUMP_STATE_TRUNCATED; return true; + } - if (!dump_vma_snapshot(cprm)) + if (!dump_vma_snapshot(cprm)) { + cprm->state |= COREDUMP_STATE_TRUNCATED; return false; + } file_start_write(cprm->file); - cn->core_dumped = binfmt->core_dump(cprm); + if (!binfmt->core_dump(cprm)) + cprm->state |= COREDUMP_STATE_TRUNCATED; /* * Ensures that file size is big enough to contain the current * file postion. This prevents gdb from complaining about @@ -1061,7 +1063,8 @@ static bool coredump_write(struct core_name *cn, */ if (cprm->to_skip) { cprm->to_skip--; - dump_emit(cprm, "", 1); + if (!dump_emit(cprm, "", 1)) + cprm->state |= COREDUMP_STATE_TRUNCATED; } file_end_write(cprm->file); free_vma_snapshot(cprm); @@ -1077,7 +1080,7 @@ static void coredump_cleanup(struct core_name *cn, struct coredump_params *cprm) atomic_dec(&core_pipe_count); } kfree(cn->corename); - coredump_finish(cn->core_dumped); + coredump_finish(cprm->state); } static inline bool coredump_skip(const struct coredump_params *cprm, @@ -1131,14 +1134,14 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, if (unshare_files()) return; - if ((cprm->mask & COREDUMP_KERNEL) && !coredump_write(cn, cprm, binfmt)) + if ((cprm->mask & COREDUMP_KERNEL) && !coredump_write(cprm, binfmt)) return; coredump_sock_shutdown(cprm->file); /* Let the parent know that a coredump was generated. */ if (cprm->mask & COREDUMP_USERSPACE) - cn->core_dumped = true; + cprm->state |= COREDUMP_STATE_STARTED; /* * When core_pipe_limit is set we wait for the coredump server diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index f686a37f7a0a04..2e87faf9a8c285 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -128,7 +128,8 @@ struct linux_binfmt { struct module *module; int (*load_binary)(struct linux_binprm *); #ifdef CONFIG_COREDUMP - int (*core_dump)(struct coredump_params *cprm); + /* Returns true if the whole coredump was written. */ + bool (*core_dump)(struct coredump_params *cprm); unsigned long min_coredump; /* minimal dump size */ #endif } __randomize_layout; diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 943bddfb22bf8e..709388dd565914 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -9,6 +9,16 @@ #include #ifdef CONFIG_COREDUMP +/** + * enum coredump_state - what happened while the coredump was written + * @COREDUMP_STATE_STARTED: the dumper committed to writing a coredump + * @COREDUMP_STATE_TRUNCATED: the dumper stopped before it had written all of it + */ +enum coredump_state { + COREDUMP_STATE_STARTED = (1U << 0), + COREDUMP_STATE_TRUNCATED = (1U << 1), +}; + struct core_vma_metadata { unsigned long start, end; vm_flags_t flags; @@ -28,6 +38,8 @@ struct coredump_params { int cpu; /* COREDUMP_* options negotiated with the coredump server. */ u64 mask; + /* COREDUMP_STATE_* raised while the coredump is written. */ + enum coredump_state state; loff_t written; loff_t pos; loff_t to_skip; From a769e2e7114266e40a62b483360c73d83da82e11 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:30 +0200 Subject: [PATCH 0697/2286] coredump: add COREDUMP_RECORDS to the coredump socket protocol Currently a coredump sent over a socket is raw data. The kernel knows things about the data it's sending that are useful for a coredump server. For example, it knows where the unpopulated parts of a mapping are. We can't communicate this to userspace currently though. Add a COREDUMP_RECORDS feature bit and a struct coredump_record_header so userspace can negotiate that feature. Instead of a byte stream it gets a header plus data. Reassembling the records yields the same coredump that would have been sent without them. The record itself is also versioned and thus extensible with the same protocol as the ack-req sync. A record stream ends explicitly. A COREDUMP_RECORD_END record closes it, carries no data and reports the size of the coredump. It is only sent once the whole coredump has been written. If it's missing the coredump should be treated as truncated. This just adds the infrastructure. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-13-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/coredump.h | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h index 662e0468da6e76..0bd5c8662ebe3b 100644 --- a/include/uapi/linux/coredump.h +++ b/include/uapi/linux/coredump.h @@ -11,12 +11,16 @@ * @COREDUMP_USERSPACE: userspace writes coredump * @COREDUMP_REJECT: don't generate coredump * @COREDUMP_WAIT: wait for coredump server + * @COREDUMP_RECORDS: send the coredump as a sequence of records instead of + * as a plain byte stream, see struct coredump_record_header; + * requires COREDUMP_KERNEL */ enum { COREDUMP_KERNEL = (1ULL << 0), COREDUMP_USERSPACE = (1ULL << 1), COREDUMP_REJECT = (1ULL << 2), COREDUMP_WAIT = (1ULL << 3), + COREDUMP_RECORDS = (1ULL << 4), }; /** @@ -101,4 +105,66 @@ enum coredump_mark { __COREDUMP_MARK_MAX = (1U << 31), }; +/** + * enum coredump_record_type - Type of a coredump record + * + * @COREDUMP_RECORD_DATA: the header is followed by ->len bytes of data + * @COREDUMP_RECORD_END: the coredump ends here, the header is not followed + * by any data and no further record is sent + * @__COREDUMP_RECORD_TYPE_MAX: the maximum coredump record type value + */ +enum coredump_record_type { + COREDUMP_RECORD_DATA = 0U, + COREDUMP_RECORD_END = 1U, + __COREDUMP_RECORD_TYPE_MAX = (1U << 31), +}; + +/** + * struct coredump_record_header - header of a coredump record + * @size: size of struct coredump_record_header + * @type: one of enum coredump_record_type + * @flags: modifiers for this record + * @offset: offset in the coredump this record starts at + * @len: number of coredump bytes this record accounts for + * + * If the coredump server raises COREDUMP_RECORDS in coredump_ack->mask + * the kernel doesn't send the coredump as a plain byte stream. It sends + * a sequence of records instead. A COREDUMP_RECORD_DATA record is + * followed by @len bytes of actual coredump data. Records arrive in + * order and leave no gaps. So @offset is the sum of the @len of all + * records before it. + * + * The last record is a COREDUMP_RECORD_END record. It is followed by + * nothing. Its @len is zero. Its @offset is the size of the coredump. + * The kernel only sends it once it has written the whole coredump. A + * server that hits end-of-file without having seen an end record must + * treat the coredump as incomplete. + * + * The @size member is set to the size of struct coredump_record_header + * the kernel knows and lets the header grow later. It comes first so it + * can be peeked. Userspace must consume @size bytes and discard + * anything beyond what it knows. It must refuse a @size smaller than + * COREDUMP_RECORD_HEADER_SIZE_VER0. @size covers the header alone. + * @offset and @len count coredump bytes. + * + * The @flags member carries modifiers that change how the record is to + * be interpreted. No flag is defined yet. Userspace must refuse a + * record carrying a flag or a type it doesn't know. Every new record + * type is raised in coredump_req->mask as a feature of its own. A + * server only ever sees the types it asked for. + * + * COREDUMP_RECORDS must be combined with COREDUMP_KERNEL. + */ +struct coredump_record_header { + __u32 size; + __u32 type; + __u64 flags; + __u64 offset; + __u64 len; +}; + +enum { + COREDUMP_RECORD_HEADER_SIZE_VER0 = 32U, /* size of first published struct */ +}; + #endif /* _UAPI_LINUX_COREDUMP_H */ From ac3a5a750ee5aa225eab27c2964c81028e5c888a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:31 +0200 Subject: [PATCH 0698/2286] coredump: add COREDUMP_SPARSE to the coredump socket protocol A coredump with a lot of unpopulated mappings sends useless amounts of zero data to userspace. This is nonsensical. While __dump_skip() can seek over them when the target is a regular file a socket cannot do this. COREDUMP_RECORDS put the zeroes in records but it didn't get rid of them. Add a COREDUMP_SPARSE feature bit and a COREDUMP_RECORD_ZERO record type. A zero record is a bare header that tells userspace how many zero bytes were skipped. So a hole crosses the socket as one header no matter how long it is. The coredump server can recreate this sparsely. Zero records only exist inside a record stream. COREDUMP_SPARSE requires COREDUMP_RECORDS. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-14-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/coredump.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h index 0bd5c8662ebe3b..f3771861ca4857 100644 --- a/include/uapi/linux/coredump.h +++ b/include/uapi/linux/coredump.h @@ -14,6 +14,8 @@ * @COREDUMP_RECORDS: send the coredump as a sequence of records instead of * as a plain byte stream, see struct coredump_record_header; * requires COREDUMP_KERNEL + * @COREDUMP_SPARSE: describe the holes in the coredump as zero records + * instead of transferring them; requires COREDUMP_RECORDS */ enum { COREDUMP_KERNEL = (1ULL << 0), @@ -21,6 +23,7 @@ enum { COREDUMP_REJECT = (1ULL << 2), COREDUMP_WAIT = (1ULL << 3), COREDUMP_RECORDS = (1ULL << 4), + COREDUMP_SPARSE = (1ULL << 5), }; /** @@ -111,11 +114,14 @@ enum coredump_mark { * @COREDUMP_RECORD_DATA: the header is followed by ->len bytes of data * @COREDUMP_RECORD_END: the coredump ends here, the header is not followed * by any data and no further record is sent + * @COREDUMP_RECORD_ZERO: the header stands for ->len zero bytes and is not + * followed by any data * @__COREDUMP_RECORD_TYPE_MAX: the maximum coredump record type value */ enum coredump_record_type { COREDUMP_RECORD_DATA = 0U, COREDUMP_RECORD_END = 1U, + COREDUMP_RECORD_ZERO = 2U, __COREDUMP_RECORD_TYPE_MAX = (1U << 31), }; @@ -130,9 +136,11 @@ enum coredump_record_type { * If the coredump server raises COREDUMP_RECORDS in coredump_ack->mask * the kernel doesn't send the coredump as a plain byte stream. It sends * a sequence of records instead. A COREDUMP_RECORD_DATA record is - * followed by @len bytes of actual coredump data. Records arrive in - * order and leave no gaps. So @offset is the sum of the @len of all - * records before it. + * followed by @len bytes of actual coredump data. A + * COREDUMP_RECORD_ZERO record is followed by nothing and stands for + * @len zero bytes. A server that didn't raise COREDUMP_SPARSE never + * sees a zero record. Records arrive in order and leave no gaps. So + * @offset is the sum of the @len of all records before it. * * The last record is a COREDUMP_RECORD_END record. It is followed by * nothing. Its @len is zero. Its @offset is the size of the coredump. @@ -153,7 +161,8 @@ enum coredump_record_type { * type is raised in coredump_req->mask as a feature of its own. A * server only ever sees the types it asked for. * - * COREDUMP_RECORDS must be combined with COREDUMP_KERNEL. + * COREDUMP_RECORDS must be combined with COREDUMP_KERNEL, and + * COREDUMP_SPARSE with COREDUMP_RECORDS. */ struct coredump_record_header { __u32 size; From 675f4a0d7bc751600f7cdc155f440b336695b45d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:32 +0200 Subject: [PATCH 0699/2286] tools: sync coredump.h header Sync the headers for the selftests. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-15-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/include/uapi/linux/coredump.h | 79 ++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/tools/include/uapi/linux/coredump.h b/tools/include/uapi/linux/coredump.h index dc3789b78af021..f3771861ca4857 100644 --- a/tools/include/uapi/linux/coredump.h +++ b/tools/include/uapi/linux/coredump.h @@ -11,12 +11,19 @@ * @COREDUMP_USERSPACE: userspace writes coredump * @COREDUMP_REJECT: don't generate coredump * @COREDUMP_WAIT: wait for coredump server + * @COREDUMP_RECORDS: send the coredump as a sequence of records instead of + * as a plain byte stream, see struct coredump_record_header; + * requires COREDUMP_KERNEL + * @COREDUMP_SPARSE: describe the holes in the coredump as zero records + * instead of transferring them; requires COREDUMP_RECORDS */ enum { COREDUMP_KERNEL = (1ULL << 0), COREDUMP_USERSPACE = (1ULL << 1), COREDUMP_REJECT = (1ULL << 2), COREDUMP_WAIT = (1ULL << 3), + COREDUMP_RECORDS = (1ULL << 4), + COREDUMP_SPARSE = (1ULL << 5), }; /** @@ -30,11 +37,11 @@ enum { * member is set to the size of struct coredump_req and provides a hint * to userspace how much data can be read. Userspace may use MSG_PEEK to * peek the size of struct coredump_req and then choose to consume it in - * one go. Userspace may also simply read a COREDUMP_ACK_SIZE_VER0 + * one go. Userspace may also simply read a COREDUMP_REQ_SIZE_VER0 * request. If the size the kernel sends is larger userspace simply * discards any remaining data. * - * The coredump_req->mask member is set to the currently know features. + * The coredump_req->mask member is set to the currently known features. * Userspace may only set coredump_ack->mask to the bits raised by the * kernel in coredump_req->mask. * @@ -101,4 +108,72 @@ enum coredump_mark { __COREDUMP_MARK_MAX = (1U << 31), }; +/** + * enum coredump_record_type - Type of a coredump record + * + * @COREDUMP_RECORD_DATA: the header is followed by ->len bytes of data + * @COREDUMP_RECORD_END: the coredump ends here, the header is not followed + * by any data and no further record is sent + * @COREDUMP_RECORD_ZERO: the header stands for ->len zero bytes and is not + * followed by any data + * @__COREDUMP_RECORD_TYPE_MAX: the maximum coredump record type value + */ +enum coredump_record_type { + COREDUMP_RECORD_DATA = 0U, + COREDUMP_RECORD_END = 1U, + COREDUMP_RECORD_ZERO = 2U, + __COREDUMP_RECORD_TYPE_MAX = (1U << 31), +}; + +/** + * struct coredump_record_header - header of a coredump record + * @size: size of struct coredump_record_header + * @type: one of enum coredump_record_type + * @flags: modifiers for this record + * @offset: offset in the coredump this record starts at + * @len: number of coredump bytes this record accounts for + * + * If the coredump server raises COREDUMP_RECORDS in coredump_ack->mask + * the kernel doesn't send the coredump as a plain byte stream. It sends + * a sequence of records instead. A COREDUMP_RECORD_DATA record is + * followed by @len bytes of actual coredump data. A + * COREDUMP_RECORD_ZERO record is followed by nothing and stands for + * @len zero bytes. A server that didn't raise COREDUMP_SPARSE never + * sees a zero record. Records arrive in order and leave no gaps. So + * @offset is the sum of the @len of all records before it. + * + * The last record is a COREDUMP_RECORD_END record. It is followed by + * nothing. Its @len is zero. Its @offset is the size of the coredump. + * The kernel only sends it once it has written the whole coredump. A + * server that hits end-of-file without having seen an end record must + * treat the coredump as incomplete. + * + * The @size member is set to the size of struct coredump_record_header + * the kernel knows and lets the header grow later. It comes first so it + * can be peeked. Userspace must consume @size bytes and discard + * anything beyond what it knows. It must refuse a @size smaller than + * COREDUMP_RECORD_HEADER_SIZE_VER0. @size covers the header alone. + * @offset and @len count coredump bytes. + * + * The @flags member carries modifiers that change how the record is to + * be interpreted. No flag is defined yet. Userspace must refuse a + * record carrying a flag or a type it doesn't know. Every new record + * type is raised in coredump_req->mask as a feature of its own. A + * server only ever sees the types it asked for. + * + * COREDUMP_RECORDS must be combined with COREDUMP_KERNEL, and + * COREDUMP_SPARSE with COREDUMP_RECORDS. + */ +struct coredump_record_header { + __u32 size; + __u32 type; + __u64 flags; + __u64 offset; + __u64 len; +}; + +enum { + COREDUMP_RECORD_HEADER_SIZE_VER0 = 32U, /* size of first published struct */ +}; + #endif /* _UAPI_LINUX_COREDUMP_H */ From f82383160f0f51bdb8fdb1950aff4ceda53f024f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:33 +0200 Subject: [PATCH 0700/2286] coredump: send the coredump in records if requested When the coredump server raises COREDUMP_RECORDS send the coredump in records. A record consists of a struct coredump_record_header and data. A header and the bytes it describes go out in one iovec. A hole is flushed through __dump_emit() like before. So zeroes still are sent on the socket as actual data records. Making holes cheap is COREDUMP_SPARSE's job. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-16-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 150 ++++++++++++++---- include/linux/coredump.h | 5 + .../coredump/coredump_test_helpers.c | 2 +- 3 files changed, 127 insertions(+), 30 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 5c38d44238d2d5..c178b88502aa15 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -51,7 +51,6 @@ #include #include #include -#include #include #include @@ -68,6 +67,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm); static void free_vma_snapshot(struct coredump_params *cprm); +static void dump_end_record(struct coredump_params *cprm); #define CORE_FILE_NOTE_SIZE_DEFAULT (4*1024*1024) /* Define a reasonable max cap */ @@ -661,6 +661,8 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new) return 0; } +static_assert(sizeof(struct coredump_record_header) == COREDUMP_RECORD_HEADER_SIZE_VER0); + #ifdef CONFIG_UNIX /* af_unix halves the send buffer to size a single skb. */ #define COREDUMP_SOCK_SNDBUF_MIN (3 * PAGE_SIZE) @@ -803,7 +805,8 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * struct coredump_req req = { .size = sizeof(struct coredump_req), .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT, + COREDUMP_REJECT | COREDUMP_WAIT | + COREDUMP_RECORDS, .size_ack = sizeof(struct coredump_ack), }; struct coredump_ack ack = {}; @@ -857,6 +860,19 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * return false; } + /* Records only describe a coredump the kernel writes. */ + if ((ack.mask & COREDUMP_RECORDS) && !(ack.mask & COREDUMP_KERNEL)) { + coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING); + return false; + } + + /* Record header scratch; a bvec can't point at the stack. */ + if (ack.mask & COREDUMP_RECORDS) { + cprm->record_hdr = kmalloc_obj(*cprm->record_hdr); + if (!cprm->record_hdr) + return false; + } + cprm->mask = ack.mask; return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK); } @@ -1041,7 +1057,6 @@ static bool coredump_pipe(struct core_name *cn, struct coredump_params *cprm, static bool coredump_write(struct coredump_params *cprm, const struct linux_binfmt *binfmt) { - if (dump_interrupted()) { cprm->state |= COREDUMP_STATE_TRUNCATED; return true; @@ -1057,15 +1072,17 @@ static bool coredump_write(struct coredump_params *cprm, cprm->state |= COREDUMP_STATE_TRUNCATED; /* * Ensures that file size is big enough to contain the current - * file postion. This prevents gdb from complaining about + * file position. This prevents gdb from complaining about * a truncated file if the last "write" to the file was - * dump_skip. + * dump_skip. A record stream relies on it too: the flush + * emits the records that cover a trailing hole. */ if (cprm->to_skip) { cprm->to_skip--; if (!dump_emit(cprm, "", 1)) cprm->state |= COREDUMP_STATE_TRUNCATED; } + dump_end_record(cprm); file_end_write(cprm->file); free_vma_snapshot(cprm); return true; @@ -1080,6 +1097,7 @@ static void coredump_cleanup(struct core_name *cn, struct coredump_params *cprm) atomic_dec(&core_pipe_count); } kfree(cn->corename); + kfree(cprm->record_hdr); coredump_finish(cprm->state); } @@ -1213,26 +1231,74 @@ void vfs_coredump(const kernel_siginfo_t *siginfo) * do on a core-file: use only these functions to write out all the * necessary info. */ -/* One write, never more than a page. See __dump_emit(). */ -static bool dump_emit_chunk(struct coredump_params *cprm, const void *addr, - int nr) +static bool dump_records(const struct coredump_params *cprm) +{ + return cprm->mask & COREDUMP_RECORDS; +} + +/* Describe the next @len bytes of the coredump. Returns the header size. */ +static size_t dump_record_init(struct coredump_params *cprm, + enum coredump_record_type type, u64 flags, + u64 len) +{ + if (!dump_records(cprm)) + return 0; + + *cprm->record_hdr = (struct coredump_record_header) { + .size = sizeof(*cprm->record_hdr), + .type = type, + .flags = flags, + .offset = cprm->pos, + .len = len, + }; + + return sizeof(*cprm->record_hdr); +} + +/* Write @iter whole or fail. @len is what it advances the coredump by. */ +static bool dump_write_iter(struct coredump_params *cprm, struct iov_iter *iter, + size_t len) { struct file *file = cprm->file; + size_t count = iov_iter_count(iter); loff_t pos = file->f_pos; ssize_t n; - if (dump_interrupted()) + n = __kernel_write_iter(file, iter, &pos); + if (n != (ssize_t)count) return false; + file->f_pos = pos; + cprm->written += count; + cprm->pos += len; + + return true; +} + +/* One record, never more than a page. See __dump_emit(). */ +static bool dump_emit_chunk(struct coredump_params *cprm, const void *addr, + int nr) +{ + struct kvec kvec[2]; + struct iov_iter iter; + unsigned int nseg = 0; + size_t hdrlen; - n = __kernel_write(file, addr, nr, &pos); - if (n != nr) + if (dump_interrupted()) return false; - file->f_pos = pos; - cprm->written += n; - cprm->pos += n; + hdrlen = dump_record_init(cprm, COREDUMP_RECORD_DATA, 0, nr); + if (hdrlen) { + kvec[nseg].iov_base = cprm->record_hdr; + kvec[nseg].iov_len = hdrlen; + nseg++; + } + kvec[nseg].iov_base = (void *)addr; + kvec[nseg].iov_len = nr; + nseg++; - return true; + iov_iter_kvec(&iter, ITER_SOURCE, kvec, nseg, hdrlen + nr); + + return dump_write_iter(cprm, &iter, nr); } static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) @@ -1253,6 +1319,34 @@ static bool __dump_emit(struct coredump_params *cprm, const void *addr, int nr) return true; } +/* Send a record that stands on its own: a header and nothing else. */ +static bool dump_emit_record(struct coredump_params *cprm, + enum coredump_record_type type, u64 flags, u64 len) +{ + struct kvec kvec; + struct iov_iter iter; + size_t hdrlen; + + hdrlen = dump_record_init(cprm, type, flags, len); + if (!hdrlen) + return false; + + kvec.iov_base = cprm->record_hdr; + kvec.iov_len = hdrlen; + iov_iter_kvec(&iter, ITER_SOURCE, &kvec, 1, hdrlen); + + return dump_write_iter(cprm, &iter, len); +} + +/* Close the record stream. Only a whole coredump gets an end record. */ +static void dump_end_record(struct coredump_params *cprm) +{ + if (cprm->state & COREDUMP_STATE_TRUNCATED) + return; + + dump_emit_record(cprm, COREDUMP_RECORD_END, 0, 0); +} + static bool __dump_skip(struct coredump_params *cprm, size_t nr) { static char zeroes[PAGE_SIZE]; @@ -1313,11 +1407,10 @@ EXPORT_SYMBOL(dump_skip); #ifdef CONFIG_ELF_CORE static bool dump_emit_page(struct coredump_params *cprm, struct page *page) { - struct bio_vec bvec; + struct bio_vec bvec[2]; struct iov_iter iter; - struct file *file = cprm->file; - loff_t pos; - ssize_t n; + unsigned int nseg = 0; + size_t hdrlen; if (!page) return false; @@ -1328,17 +1421,16 @@ static bool dump_emit_page(struct coredump_params *cprm, struct page *page) return false; if (dump_interrupted()) return false; - pos = file->f_pos; - bvec_set_page(&bvec, page, PAGE_SIZE, 0); - iov_iter_bvec(&iter, ITER_SOURCE, &bvec, 1, PAGE_SIZE); - n = __kernel_write_iter(cprm->file, &iter, &pos); - if (n != PAGE_SIZE) - return false; - file->f_pos = pos; - cprm->written += PAGE_SIZE; - cprm->pos += PAGE_SIZE; - return true; + /* Hand the record header to the same write as the page it describes. */ + hdrlen = dump_record_init(cprm, COREDUMP_RECORD_DATA, 0, PAGE_SIZE); + if (hdrlen) + bvec_set_virt(&bvec[nseg++], cprm->record_hdr, hdrlen); + bvec_set_page(&bvec[nseg++], page, PAGE_SIZE, 0); + + iov_iter_bvec(&iter, ITER_SOURCE, bvec, nseg, hdrlen + PAGE_SIZE); + + return dump_write_iter(cprm, &iter, PAGE_SIZE); } /* diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 709388dd565914..b252bb2843b337 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #ifdef CONFIG_COREDUMP @@ -40,7 +41,11 @@ struct coredump_params { u64 mask; /* COREDUMP_STATE_* raised while the coredump is written. */ enum coredump_state state; + /* Record header scratch, NULL unless the coredump is a record stream. */ + struct coredump_record_header *record_hdr; + /* Bytes handed to the file, record headers included. */ loff_t written; + /* Offset in the coredump, record headers excluded. */ loff_t pos; loff_t to_skip; int vma_count; diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 570fc2e005c206..1c8658f35735de 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -275,7 +275,7 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, /* Every option the kernel is expected to advertise in coredump_req->mask. */ #define TEST_REQ_MASK_ALL \ (COREDUMP_KERNEL | COREDUMP_USERSPACE | \ - COREDUMP_REJECT | COREDUMP_WAIT) + COREDUMP_REJECT | COREDUMP_WAIT | COREDUMP_RECORDS) bool check_coredump_req(const struct coredump_req *req) { From 3aa5ebec7895c4398b91c07792b18aaa37858a59 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:34 +0200 Subject: [PATCH 0701/2286] coredump: describe the holes when COREDUMP_SPARSE is negotiated Make use of COREDUMP_SPARSE. Refuse it without COREDUMP_RECORDS. Actual holes are sent as a record with length indicating how much zero data there was. coredump_write() flushes a trailing hole if the coredump is done. Instead of writing the actual byte for pipes and sockets, collapse it. This stops wasting a header with coredump records for a single byte. So we now only write it when the coredump can be seeked. TL;DR a trailing hole is a zero record like any other and the records still cover the whole coredump. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-17-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 41 +++++++++++++++---- .../coredump/coredump_test_helpers.c | 3 +- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index c178b88502aa15..4ba30df014d231 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -68,6 +68,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm); static void free_vma_snapshot(struct coredump_params *cprm); static void dump_end_record(struct coredump_params *cprm); +static bool dump_flush_skip(struct coredump_params *cprm); #define CORE_FILE_NOTE_SIZE_DEFAULT (4*1024*1024) /* Define a reasonable max cap */ @@ -806,7 +807,7 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * .size = sizeof(struct coredump_req), .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | COREDUMP_REJECT | COREDUMP_WAIT | - COREDUMP_RECORDS, + COREDUMP_RECORDS | COREDUMP_SPARSE, .size_ack = sizeof(struct coredump_ack), }; struct coredump_ack ack = {}; @@ -866,6 +867,12 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * return false; } + /* Zero records only exist inside a record stream. */ + if ((ack.mask & COREDUMP_SPARSE) && !(ack.mask & COREDUMP_RECORDS)) { + coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING); + return false; + } + /* Record header scratch; a bvec can't point at the stack. */ if (ack.mask & COREDUMP_RECORDS) { cprm->record_hdr = kmalloc_obj(*cprm->record_hdr); @@ -1071,15 +1078,21 @@ static bool coredump_write(struct coredump_params *cprm, if (!binfmt->core_dump(cprm)) cprm->state |= COREDUMP_STATE_TRUNCATED; /* - * Ensures that file size is big enough to contain the current - * file position. This prevents gdb from complaining about - * a truncated file if the last "write" to the file was - * dump_skip. A record stream relies on it too: the flush - * emits the records that cover a trailing hole. + * A trailing hole still has to land in the coredump. Seeking over + * it doesn't grow the file, so the last byte of it is written + * instead and gdb doesn't see a truncated file. Everything else + * puts the hole on the wire as it flushes it. */ if (cprm->to_skip) { - cprm->to_skip--; - if (!dump_emit(cprm, "", 1)) + bool flushed; + + if (cprm->file->f_mode & FMODE_LSEEK) { + cprm->to_skip--; + flushed = dump_emit(cprm, "", 1); + } else { + flushed = dump_flush_skip(cprm); + } + if (!flushed) cprm->state |= COREDUMP_STATE_TRUNCATED; } dump_end_record(cprm); @@ -1236,6 +1249,11 @@ static bool dump_records(const struct coredump_params *cprm) return cprm->mask & COREDUMP_RECORDS; } +static bool dump_sparse(const struct coredump_params *cprm) +{ + return cprm->mask & COREDUMP_SPARSE; +} + /* Describe the next @len bytes of the coredump. Returns the header size. */ static size_t dump_record_init(struct coredump_params *cprm, enum coredump_record_type type, u64 flags, @@ -1352,6 +1370,13 @@ static bool __dump_skip(struct coredump_params *cprm, size_t nr) static char zeroes[PAGE_SIZE]; struct file *file = cprm->file; + if (dump_sparse(cprm)) { + /* Hand the server the length of the hole instead of the hole itself. */ + if (dump_interrupted()) + return false; + return dump_emit_record(cprm, COREDUMP_RECORD_ZERO, 0, nr); + } + if (file->f_mode & FMODE_LSEEK) { if (dump_interrupted() || vfs_llseek(file, nr, SEEK_CUR) < 0) return false; diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 1c8658f35735de..a5b9cde47239b6 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -275,7 +275,8 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, /* Every option the kernel is expected to advertise in coredump_req->mask. */ #define TEST_REQ_MASK_ALL \ (COREDUMP_KERNEL | COREDUMP_USERSPACE | \ - COREDUMP_REJECT | COREDUMP_WAIT | COREDUMP_RECORDS) + COREDUMP_REJECT | COREDUMP_WAIT | \ + COREDUMP_RECORDS | COREDUMP_SPARSE) bool check_coredump_req(const struct coredump_req *req) { From 4d3f266ae5841b26b006832bddbc24d8a9b85f10 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:35 +0200 Subject: [PATCH 0702/2286] selftests/coredump: test COREDUMP_RECORDS and COREDUMP_SPARSE Test the new COREDUMP_RECORDS and COREDUMP_SPARSE flags. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-18-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 407 ++++++++++++++++++ .../coredump/coredump_test_helpers.c | 236 +++++++++- .../coredump/coredump_test_helpers.h | 8 + 3 files changed, 650 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index 60a357e628eb40..abf6e2c4c35473 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -1573,4 +1573,411 @@ TEST_F_TIMEOUT(coredump, socket_multiple_crashing_coredumps_epoll_workers, 500) wait_and_check_coredump_server(pid_coredump_server, _metadata, self); } +/* + * Reassemble a record stream and check that what comes out is an ELF + * core file. The records themselves are validated by recv_coredump_records(). + */ +TEST_F(coredump, socket_request_sparse_reassemble) +{ + int fd_core_file, pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_file = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + + close(ipc_sockets[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + fd_file = creat("/tmp/coredump.file", 0644); + if (fd_file < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + if (!send_coredump_ack(fd_coredump, &req, + COREDUMP_KERNEL | COREDUMP_RECORDS | + COREDUMP_SPARSE | COREDUMP_WAIT, 0)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) + goto out; + + if (recv_coredump_records(fd_coredump, fd_file, NULL, NULL, -1) < 0) + goto out; + + exit_code = EXIT_SUCCESS; +out: + if (fd_file >= 0) + close(fd_file); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child(); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + + /* What the records reassemble into has to be an ELF core file. */ + fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd_core_file, 0); + ASSERT_TRUE(is_elf_core(fd_core_file)); + EXPECT_EQ(close(fd_core_file), 0); +} + +/* + * Crash a child with a mostly-unpopulated mapping and reassemble its + * record stream, reporting what crossed the socket and the coredump + * size the records describe. With @kill_peer the server kills the task + * once the coredump is under way so the kernel has to cut it short. + */ +static void check_record_dump(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, __u64 ack_mask, + bool kill_peer, ssize_t *received, + off_t *coredump_size) +{ + bool truncated = false; + int pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + int pipefds[2]; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_EQ(pipe(pipefds), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_file = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + bool is_truncated = false; + off_t size = 0; + ssize_t ret; + + close(ipc_sockets[0]); + close(pipefds[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + /* + * The reassembled coredump is bigger than the mapping the + * child made, so keep it on the detached tmpfs and sparse. + */ + fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached); + if (fd_file < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) + goto out; + + ret = recv_coredump_records(fd_coredump, fd_file, &size, &is_truncated, + kill_peer ? fd_peer_pidfd : -1); + if (ret < 0) + goto out; + + if (write_nointr(pipefds[1], &ret, sizeof(ret)) != sizeof(ret)) + goto out; + if (write_nointr(pipefds[1], &size, sizeof(size)) != sizeof(size)) + goto out; + if (write_nointr(pipefds[1], &is_truncated, + sizeof(is_truncated)) != sizeof(is_truncated)) + goto out; + + exit_code = EXIT_SUCCESS; +out: + close(pipefds[1]); + if (fd_file >= 0) + close(fd_file); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + EXPECT_EQ(close(pipefds[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_sparse(SPARSE_MAPPING_SIZE); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + + ASSERT_EQ(read_nointr(pipefds[0], received, sizeof(*received)), + sizeof(*received)); + ASSERT_EQ(read_nointr(pipefds[0], coredump_size, sizeof(*coredump_size)), + sizeof(*coredump_size)); + ASSERT_EQ(read_nointr(pipefds[0], &truncated, sizeof(truncated)), + sizeof(truncated)); + EXPECT_EQ(close(pipefds[0]), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + + if (kill_peer) { + /* The kernel gave up partway, so no end record closed the stream. */ + ASSERT_TRUE(truncated); + ASSERT_FALSE(WCOREDUMP(status)); + ASSERT_LT(*coredump_size, (off_t)SPARSE_MAPPING_SIZE); + return; + } + + ASSERT_FALSE(truncated); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + /* The mapping is in the coredump, holes included. */ + ASSERT_GT(*coredump_size, (off_t)SPARSE_MAPPING_SIZE); +} + +/* + * A mapping that has been written to is dumped whole, including the parts + * of it that were never faulted in. With COREDUMP_SPARSE the holes stay + * off the wire. + */ +TEST_F(coredump, socket_request_sparse_hole) +{ + off_t coredump_size = 0; + ssize_t received = 0; + + check_record_dump(_metadata, self, + COREDUMP_KERNEL | COREDUMP_RECORDS | + COREDUMP_SPARSE | COREDUMP_WAIT, + false, &received, &coredump_size); + + /* The holes didn't have to go over the socket. */ + ASSERT_LT(received, coredump_size / 8); +} + +/* + * COREDUMP_RECORDS alone splits the stream into records but elides + * nothing: the holes cross the socket as data records. + */ +TEST_F(coredump, socket_request_records_hole) +{ + off_t coredump_size = 0; + ssize_t received = 0; + + check_record_dump(_metadata, self, + COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_WAIT, + false, &received, &coredump_size); + + /* Records alone elide nothing, so everything crossed the socket. */ + ASSERT_GT(received, coredump_size); +} + +/* + * A coredump the kernel gives up on halfway still ends in an end record, + * and that record says the coredump is incomplete. COREDUMP_SPARSE is left + * out on purpose: the holes have to cross the socket so the coredump is + * far larger than the socket buffer and the kernel is still writing it + * when the kill lands. + */ +TEST_F(coredump, socket_request_records_truncated) +{ + off_t coredump_size = 0; + ssize_t received = 0; + + check_record_dump(_metadata, self, + COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_WAIT, + true, &received, &coredump_size); + + /* The end record crossed the socket even though the task was killed. */ + ASSERT_GT(received, 0); +} + +/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */ +static void check_conflicting_ack(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, __u64 ack_mask) +{ + int pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + + close(ipc_sockets[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) + goto out; + + exit_code = EXIT_SUCCESS; +out: + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child(); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_FALSE(WCOREDUMP(status)); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +} + +/* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */ +TEST_F(coredump, socket_request_records_without_kernel) +{ + check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_RECORDS); +} + +/* A zero record can't exist outside a record stream. */ +TEST_F(coredump, socket_request_sparse_without_records) +{ + check_conflicting_ack(_metadata, self, COREDUMP_KERNEL | COREDUMP_SPARSE); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index a5b9cde47239b6..5b2ffe17f7b72f 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1,9 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include #include #include +#include #include #include #include @@ -13,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +26,12 @@ #include "coredump_test_helpers.h" +#if __ELF_NATIVE_CLASS == 64 +#define COREDUMP_ELFCLASS ELFCLASS64 +#else +#define COREDUMP_ELFCLASS ELFCLASS32 +#endif + void *do_nothing(void *arg) { (void)arg; @@ -44,6 +53,228 @@ void crashing_child(void) i = *(volatile int *)NULL; } +void crashing_child_sparse(size_t size) +{ + char *p; + + /* + * Touch the first page only. The whole mapping is dumped because + * it has been written to, but all of it save that one page is a + * hole. + */ + p = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (p != MAP_FAILED) + p[0] = 'x'; + + /* crash on purpose */ + *(volatile int *)NULL = 0; +} + +/* Read @len bytes off the socket, writing them at @offset if @fd_out >= 0. */ +static ssize_t recv_record_bytes(int fd_coredump, __u64 len, int fd_out, + off_t offset) +{ + ssize_t received = 0; + + while (len) { + char buffer[PAGE_SIZE]; + size_t chunk = len < sizeof(buffer) ? len : sizeof(buffer); + ssize_t ret; + + ret = recv(fd_coredump, buffer, chunk, MSG_WAITALL); + if (ret <= 0) { + fprintf(stderr, "%s: short read %zd: %m\n", + __func__, ret); + return -1; + } + + if (fd_out >= 0 && + pwrite(fd_out, buffer, ret, offset + received) != ret) { + fprintf(stderr, "%s: pwrite failed: %m\n", __func__); + return -1; + } + + received += ret; + len -= ret; + } + + return received; +} + +/* + * Reassemble a record stream. If @fd_peer_pidfd is valid the task behind + * it is killed once a data record has arrived, so the kernel has to cut + * the coredump short with the stream already under way. + */ +ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, + off_t *coredump_size, bool *truncated, + int fd_peer_pidfd) +{ + ssize_t received = 0; + off_t size = 0; + bool is_truncated = false; + bool ended = false; + char trailing; + + while (!ended) { + struct coredump_record_header record = {}; + size_t known_size; + ssize_t ret; + + /* Peek the header size the way read_coredump_req() does. */ + ret = recv(fd_coredump, &record, sizeof(record.size), + MSG_PEEK | MSG_WAITALL); + if (ret == 0) { + /* Nothing closed the stream, so the coredump was cut short. */ + if (truncated) { + is_truncated = true; + break; + } + fprintf(stderr, "%s: stream ended without an end record\n", + __func__); + return -1; + } + if (ret != sizeof(record.size)) { + fprintf(stderr, "%s: short record peek %zd: %m\n", + __func__, ret); + return -1; + } + + if (record.size < COREDUMP_RECORD_HEADER_SIZE_VER0) { + fprintf(stderr, "%s: header size %u below minimum %u\n", + __func__, record.size, + COREDUMP_RECORD_HEADER_SIZE_VER0); + return -1; + } + + /* Consume as much of the header as we know about. */ + known_size = record.size < sizeof(record) ? record.size : sizeof(record); + ret = recv(fd_coredump, &record, known_size, MSG_WAITALL); + if (ret != (ssize_t)known_size) { + fprintf(stderr, "%s: short record read %zd: %m\n", + __func__, ret); + return -1; + } + received += ret; + + /* + * A flag changes what the record means, so refuse one we + * don't know rather than guess. + */ + if (record.flags) { + fprintf(stderr, "%s: unknown header flags 0x%llx\n", + __func__, (unsigned long long)record.flags); + return -1; + } + + /* Discard any part of the header we have no use for. */ + ret = recv_record_bytes(fd_coredump, record.size - known_size, -1, 0); + if (ret < 0) + return -1; + received += ret; + + /* Records are sent in order and they don't leave gaps. */ + if (record.offset != (__u64)size) { + fprintf(stderr, "%s: record at %llu, expected %llu\n", + __func__, (unsigned long long)record.offset, + (unsigned long long)size); + return -1; + } + + switch (record.type) { + case COREDUMP_RECORD_ZERO: + /* A hole. It comes with no data and needs none. */ + break; + case COREDUMP_RECORD_DATA: + ret = recv_record_bytes(fd_coredump, record.len, + fd_core_file, size); + if (ret < 0) + return -1; + received += ret; + if (fd_peer_pidfd >= 0) { + if (sys_pidfd_send_signal(fd_peer_pidfd, SIGKILL, + NULL, 0)) { + fprintf(stderr, "%s: kill failed: %m\n", + __func__); + return -1; + } + fd_peer_pidfd = -1; + } + break; + case COREDUMP_RECORD_END: + /* The coredump ends here and nothing follows it. */ + if (record.len) { + fprintf(stderr, "%s: end record covers %llu bytes\n", + __func__, + (unsigned long long)record.len); + return -1; + } + ended = true; + break; + default: + fprintf(stderr, "%s: unknown record type %u\n", + __func__, record.type); + return -1; + } + + size += record.len; + } + + /* The end record is the last thing on the wire. */ + if (recv(fd_coredump, &trailing, sizeof(trailing), MSG_DONTWAIT) > 0) { + fprintf(stderr, "%s: data after the end record\n", __func__); + return -1; + } + + if (truncated) + *truncated = is_truncated; + + /* + * Nothing is written for a hole, so grow the file to the size the + * records describe in case the coredump ended in one. + */ + if (ftruncate(fd_core_file, size) < 0) { + fprintf(stderr, "%s: ftruncate to %llu failed: %m\n", + __func__, (unsigned long long)size); + return -1; + } + + if (coredump_size) + *coredump_size = size; + + fprintf(stderr, "Received %zd bytes for a %s coredump of %llu bytes\n", + received, is_truncated ? "truncated" : "complete", + (unsigned long long)size); + return received; +} + +/* The ELF header of a native core file. */ +static bool is_core_ehdr(const ElfW(Ehdr) *ehdr) +{ + return !memcmp(ehdr->e_ident, ELFMAG, SELFMAG) && + ehdr->e_ident[EI_CLASS] == COREDUMP_ELFCLASS && + ehdr->e_type == ET_CORE; +} + +/* Whatever the server ends up with has to be an ELF core file. */ +bool is_elf_core(int fd) +{ + ElfW(Ehdr) ehdr; + + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) { + fprintf(stderr, "%s: short read: %m\n", __func__); + return false; + } + + if (!is_core_ehdr(&ehdr)) { + fprintf(stderr, "%s: not an ELF core file\n", __func__); + return false; + } + + return true; +} + int create_detached_tmpfs(void) { int fd_context, fd_tmpfs; @@ -86,6 +317,7 @@ int create_and_listen_unix_socket(const char *path) return fd; out: + fprintf(stderr, "%s: %s: %m\n", __func__, path); if (fd >= 0) close(fd); return -1; @@ -264,8 +496,10 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, large_ack.ack.mask = mask; large_ack.ack.size = size_ack; ret = send(fd, &large_ack, size_ack, MSG_NOSIGNAL); - if (ret != size_ack) + if (ret != size_ack) { + fprintf(stderr, "%s: short send %zd: %m\n", __func__, ret); return false; + } fprintf(stderr, "Sent coredump ack with size %zu and mask 0x%llx\n", size_ack, (unsigned long long)mask); diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index 45904bd177b802..fe0a88a71b0510 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -15,9 +15,17 @@ #define NUM_THREAD_SPAWN 128 +/* Size of the mostly unpopulated mapping the sparse coredump test maps. */ +#define SPARSE_MAPPING_SIZE (256 * 1024 * 1024) + /* Shared helper function declarations */ void *do_nothing(void *arg); void crashing_child(void); +void crashing_child_sparse(size_t size); +ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, + off_t *coredump_size, bool *truncated, + int fd_peer_pidfd); +bool is_elf_core(int fd); int create_detached_tmpfs(void); int create_and_listen_unix_socket(const char *path); bool set_core_pattern(const char *pattern); From 9fb3b3ab384d8499ac897b91c219547a18096fdc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:36 +0200 Subject: [PATCH 0703/2286] selftests/coredump: hand the record stream to a sink Currently recv_coredump_records() parses the record stream and dumps it into a file. A coredump server may want to process the data it gets. So split the parsing from the processing. No functional changes. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-19-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_test_helpers.c | 91 +++++++++++++++---- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 5b2ffe17f7b72f..45d76fa0f469aa 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -71,9 +71,19 @@ void crashing_child_sparse(size_t size) *(volatile int *)NULL = 0; } -/* Read @len bytes off the socket, writing them at @offset if @fd_out >= 0. */ -static ssize_t recv_record_bytes(int fd_coredump, __u64 len, int fd_out, - off_t offset) +/* Sink a reassembled record stream is handed to, record by record. */ +struct coredump_record_sink { + /* @len bytes of coredump data that belong at @offset. */ + int (*data)(void *ctx, const void *buf, size_t len, __u64 offset); + /* @len zero bytes that belong at @offset. */ + int (*zero)(void *ctx, __u64 offset, __u64 len); + void *ctx; +}; + +/* Read @len bytes off the socket and hand them to @sink, if there is one. */ +static ssize_t recv_record_bytes(int fd_coredump, __u64 len, + const struct coredump_record_sink *sink, + __u64 offset) { ssize_t received = 0; @@ -89,11 +99,8 @@ static ssize_t recv_record_bytes(int fd_coredump, __u64 len, int fd_out, return -1; } - if (fd_out >= 0 && - pwrite(fd_out, buffer, ret, offset + received) != ret) { - fprintf(stderr, "%s: pwrite failed: %m\n", __func__); + if (sink && sink->data(sink->ctx, buffer, ret, offset + received)) return -1; - } received += ret; len -= ret; @@ -102,14 +109,34 @@ static ssize_t recv_record_bytes(int fd_coredump, __u64 len, int fd_out, return received; } +/* Put the data where the records say it goes and leave the holes alone. */ +static int file_sink_data(void *ctx, const void *buf, size_t len, __u64 offset) +{ + int fd = *(int *)ctx; + + if (pwrite(fd, buf, len, offset) != (ssize_t)len) { + fprintf(stderr, "%s: pwrite failed: %m\n", __func__); + return -1; + } + + return 0; +} + +static int file_sink_zero(void *ctx, __u64 offset, __u64 len) +{ + /* Nothing has to be written for a hole. */ + return 0; +} + /* - * Reassemble a record stream. If @fd_peer_pidfd is valid the task behind - * it is killed once a data record has arrived, so the kernel has to cut - * the coredump short with the stream already under way. + * Read a coredump strea and funnel it into @sink. Allow to pass in a + * @fd_peer_pidfd to simulate coredump truncation by killing it after having + * received a coredump record. */ -ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, - off_t *coredump_size, bool *truncated, - int fd_peer_pidfd) +static ssize_t __recv_coredump_records(int fd_coredump, + const struct coredump_record_sink *sink, + off_t *coredump_size, bool *truncated, + int fd_peer_pidfd) { ssize_t received = 0; off_t size = 0; @@ -169,7 +196,8 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, } /* Discard any part of the header we have no use for. */ - ret = recv_record_bytes(fd_coredump, record.size - known_size, -1, 0); + ret = recv_record_bytes(fd_coredump, record.size - known_size, + NULL, 0); if (ret < 0) return -1; received += ret; @@ -185,10 +213,12 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, switch (record.type) { case COREDUMP_RECORD_ZERO: /* A hole. It comes with no data and needs none. */ + if (sink->zero(sink->ctx, record.offset, record.len)) + return -1; break; case COREDUMP_RECORD_DATA: - ret = recv_record_bytes(fd_coredump, record.len, - fd_core_file, size); + ret = recv_record_bytes(fd_coredump, record.len, sink, + record.offset); if (ret < 0) return -1; received += ret; @@ -230,6 +260,32 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, if (truncated) *truncated = is_truncated; + *coredump_size = size; + + fprintf(stderr, "Received %zd bytes for a %s coredump of %llu bytes\n", + received, is_truncated ? "truncated" : "complete", + (unsigned long long)size); + return received; +} + +/* Reassemble a record stream into the coredump it describes. */ +ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, + off_t *coredump_size, bool *truncated, + int fd_peer_pidfd) +{ + struct coredump_record_sink sink = { + .data = file_sink_data, + .zero = file_sink_zero, + .ctx = &fd_core_file, + }; + ssize_t received; + off_t size = 0; + + received = __recv_coredump_records(fd_coredump, &sink, &size, truncated, + fd_peer_pidfd); + if (received < 0) + return -1; + /* * Nothing is written for a hole, so grow the file to the size the * records describe in case the coredump ended in one. @@ -243,9 +299,6 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, if (coredump_size) *coredump_size = size; - fprintf(stderr, "Received %zd bytes for a %s coredump of %llu bytes\n", - received, is_truncated ? "truncated" : "complete", - (unsigned long long)size); return received; } From 6b7f0ceb2cabafb88c5aa1ae6795dc9b711171f5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:37 +0200 Subject: [PATCH 0704/2286] selftests/coredump: put a hole in the middle of a sparse mapping The crashing_child_sparse() helper touches the first page of the mapping. That forces everything behind it to be a trailing hole. This is easy to handle. Make the test more difficult meaningful by also touchin the last page. This causes the hole to sit between two populated pages. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-20-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/coredump/coredump_test_helpers.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 45d76fa0f469aa..89f3954c560742 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -58,14 +58,16 @@ void crashing_child_sparse(size_t size) char *p; /* - * Touch the first page only. The whole mapping is dumped because - * it has been written to, but all of it save that one page is a - * hole. + * Touch the first and the last page. This will cause the whole mapping + * to be dumped because it has been written to. Everything between + * those two pages is a hole though. */ p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); - if (p != MAP_FAILED) + if (p != MAP_FAILED) { p[0] = 'x'; + p[size - 1] = 'x'; + } /* crash on purpose */ *(volatile int *)NULL = 0; From ddab1459deded63b05b2aad495328ffee7681412 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:38 +0200 Subject: [PATCH 0705/2286] selftests/coredump: simulate a blob store A coredump server that uploads to a blob store must redescribe the coredump and fixup the phdr. A segment is split wherever a hole was left out and everything a segment covers past p_filesz is zeroes anyway. So the blob store ends up with an ordinary ELF core file that is missing nothing but holes. Nothing downstream of the server has to learn a container format. The coredump with its holes still in it is reassembled alongside the object so the two can be compared. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-21-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 156 ++++ .../coredump/coredump_test_helpers.c | 765 ++++++++++++++++++ .../coredump/coredump_test_helpers.h | 3 + 3 files changed, 924 insertions(+) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index abf6e2c4c35473..f33eaf2fa93d3e 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -1882,6 +1882,162 @@ TEST_F(coredump, socket_request_records_truncated) ASSERT_GT(received, 0); } +/* + * A coredump server that uploads to a blob store can't upload a sparse + * file. It doesn't have to: it streams the data records into the object + * as they arrive, leaves the holes out, and uploads the corrected + * program header table last. What it ends up with is an ordinary ELF + * core file that describes the same memory as the coredump the records + * came from, minus the holes. + */ +TEST_F(coredump, socket_request_sparse_blob_upload) +{ + int fd_core_file, pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + off_t coredump_size = 0; + ssize_t received = 0; + int ipc_sockets[2]; + int pipefds[2]; + struct stat st; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_EQ(pipe(pipefds), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_object = -1, fd_reference = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + off_t size = 0; + ssize_t ret; + + close(ipc_sockets[0]); + close(pipefds[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + /* The object is a plain file. It never sees a hole. */ + fd_object = open("/tmp/coredump.file", + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0600); + if (fd_object < 0) + goto out; + + /* + * The coredump with its holes still in it is bigger than + * the mapping the child made, so keep it on the detached + * tmpfs and sparse. + */ + fd_reference = open_coredump_tmpfile(self->fd_tmpfs_detached); + if (fd_reference < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + if (!send_coredump_ack(fd_coredump, &req, + COREDUMP_KERNEL | COREDUMP_RECORDS | + COREDUMP_SPARSE | COREDUMP_WAIT, 0)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) + goto out; + + ret = recv_coredump_compact(fd_coredump, fd_object, + fd_reference, &size); + if (ret < 0) + goto out; + + if (check_compact_coredump(fd_object, fd_reference)) + goto out; + + if (write_nointr(pipefds[1], &ret, sizeof(ret)) != sizeof(ret)) + goto out; + if (write_nointr(pipefds[1], &size, sizeof(size)) != sizeof(size)) + goto out; + + exit_code = EXIT_SUCCESS; +out: + close(pipefds[1]); + if (fd_reference >= 0) + close(fd_reference); + if (fd_object >= 0) + close(fd_object); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + EXPECT_EQ(close(pipefds[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_sparse(SPARSE_MAPPING_SIZE); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_EQ(read_nointr(pipefds[0], &received, sizeof(received)), + sizeof(received)); + ASSERT_EQ(read_nointr(pipefds[0], &coredump_size, sizeof(coredump_size)), + sizeof(coredump_size)); + EXPECT_EQ(close(pipefds[0]), 0); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + + /* The mapping is in the coredump, holes included. */ + ASSERT_GT(coredump_size, (off_t)SPARSE_MAPPING_SIZE); + + /* The object isn't sparse and doesn't carry them. */ + ASSERT_EQ(stat("/tmp/coredump.file", &st), 0); + ASSERT_LT(st.st_size, coredump_size / 8); + + /* And a debugger still sees an ordinary ELF core file. */ + fd_core_file = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd_core_file, 0); + ASSERT_TRUE(is_elf_core(fd_core_file)); + EXPECT_EQ(close(fd_core_file), 0); +} + /* Ack @ack_mask, expect the kernel to refuse it as conflicting. */ static void check_conflicting_ack(struct __test_metadata *const _metadata, FIXTURE_DATA(coredump) *self, __u64 ack_mask) diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 89f3954c560742..9346b8f688e260 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -330,6 +330,771 @@ bool is_elf_core(int fd) return true; } +/* + * A coredump server that uploads to a blob store can't upload a sparse + * file and can't seek in the object it is uploading. It streams the data + * records into the object as they arrive, remembers the holes it left + * out, and uploads the program header table that describes the result + * last. What comes out is an ordinary ELF core file without the holes. + */ + +/* A run of the coredump the object doesn't carry. */ +struct compact_hole { + __u64 offset; + __u64 len; +}; + +/* A program header of the object and where its bytes sat in the coredump. */ +struct compact_piece { + ElfW(Phdr) phdr; + __u64 src; +}; + +struct compact_ctx { + int fd_body; /* the object's payload, append only */ + int fd_reference; /* the coredump with its holes, for the test */ + unsigned char *head; /* everything ahead of the segment data */ + size_t head_len; + size_t head_cap; + __u64 data_offset; /* where the segment data starts, 0 while unknown */ + struct compact_hole *holes; + size_t nr_holes; + size_t holes_cap; + __u64 body_len; +}; + +/* Write @len bytes out, short writes and all. */ +static int compact_write(int fd, const void *buf, size_t len) +{ + const unsigned char *pos = buf; + + while (len) { + ssize_t ret = write(fd, pos, len); + + if (ret <= 0) { + fprintf(stderr, "%s: write failed: %m\n", __func__); + return -1; + } + + pos += ret; + len -= ret; + } + + return 0; +} + +/* Keep @len bytes of the head, or @len zeroes if @buf is NULL. */ +static int compact_head_append(struct compact_ctx *ctx, const void *buf, + size_t len) +{ + if (ctx->head_len + len > ctx->head_cap) { + size_t cap = ctx->head_cap ? ctx->head_cap : PAGE_SIZE; + unsigned char *head; + + while (cap < ctx->head_len + len) + cap *= 2; + + head = realloc(ctx->head, cap); + if (!head) { + fprintf(stderr, "%s: out of memory\n", __func__); + return -1; + } + ctx->head = head; + ctx->head_cap = cap; + } + + if (buf) + memcpy(ctx->head + ctx->head_len, buf, len); + else + memset(ctx->head + ctx->head_len, 0, len); + ctx->head_len += len; + + return 0; +} + +/* Remember a hole so the program header table can account for it later. */ +static int compact_keep_hole(struct compact_ctx *ctx, __u64 offset, __u64 len) +{ + if (ctx->nr_holes == ctx->holes_cap) { + size_t cap = ctx->holes_cap ? ctx->holes_cap * 2 : 64; + struct compact_hole *holes; + + holes = realloc(ctx->holes, cap * sizeof(*holes)); + if (!holes) { + fprintf(stderr, "%s: out of memory\n", __func__); + return -1; + } + ctx->holes = holes; + ctx->holes_cap = cap; + } + + ctx->holes[ctx->nr_holes].offset = offset; + ctx->holes[ctx->nr_holes].len = len; + ctx->nr_holes++; + + return 0; +} + +/* The segment data starts where the first PT_LOAD points. */ +static int compact_probe(struct compact_ctx *ctx) +{ + const ElfW(Ehdr) *ehdr = (const ElfW(Ehdr) *)ctx->head; + const ElfW(Phdr) *phdr; + size_t i; + + if (ctx->data_offset || ctx->head_len < sizeof(*ehdr)) + return 0; + + if (!is_core_ehdr(ehdr)) { + fprintf(stderr, "%s: not an ELF core file\n", __func__); + return -1; + } + + if (ehdr->e_phoff != sizeof(*ehdr) || + ehdr->e_phentsize != sizeof(ElfW(Phdr)) || + ehdr->e_phnum == 0 || ehdr->e_phnum == PN_XNUM) { + fprintf(stderr, "%s: unhandled program header table\n", __func__); + return -1; + } + + if (ctx->head_len < ehdr->e_phoff + + (size_t)ehdr->e_phnum * ehdr->e_phentsize) + return 0; + + phdr = (const ElfW(Phdr) *)(ctx->head + ehdr->e_phoff); + for (i = 0; i < ehdr->e_phnum; i++) { + if (phdr[i].p_type != PT_LOAD) + continue; + if (!ctx->data_offset || phdr[i].p_offset < ctx->data_offset) + ctx->data_offset = phdr[i].p_offset; + } + + if (!ctx->data_offset) { + fprintf(stderr, "%s: coredump without a single segment\n", + __func__); + return -1; + } + + return 0; +} + +/* + * Take whatever of [@offset, @offset + @len) still belongs to the head. + * @buf is NULL for a hole. Returns how much was taken. + */ +static ssize_t compact_head_take(struct compact_ctx *ctx, const void *buf, + __u64 offset, __u64 len) +{ + __u64 chunk; + + if (!len || (ctx->data_offset && offset >= ctx->data_offset)) + return 0; + + chunk = len; + if (ctx->data_offset && offset + chunk > ctx->data_offset) + chunk = ctx->data_offset - offset; + + if (offset != ctx->head_len) { + fprintf(stderr, "%s: head has a gap at %llu\n", __func__, + (unsigned long long)offset); + return -1; + } + + if (compact_head_append(ctx, buf, chunk)) + return -1; + + return chunk; +} + +static int compact_data(void *arg, const void *buf, size_t len, __u64 offset) +{ + struct compact_ctx *ctx = arg; + const unsigned char *pos = buf; + ssize_t head; + + /* Only the test needs a coredump with the holes still in it. */ + if (pwrite(ctx->fd_reference, pos, len, offset) != (ssize_t)len) { + fprintf(stderr, "%s: pwrite failed: %m\n", __func__); + return -1; + } + + /* The head has to be rewritten at the end, so hold on to it. */ + head = compact_head_take(ctx, pos, offset, len); + if (head < 0) + return -1; + if (head && compact_probe(ctx)) + return -1; + + pos += head; + len -= head; + if (!len) + return 0; + + /* Everything else goes into the object as it arrives. */ + if (compact_write(ctx->fd_body, pos, len)) + return -1; + ctx->body_len += len; + + return 0; +} + +static int compact_zero(void *arg, __u64 offset, __u64 len) +{ + struct compact_ctx *ctx = arg; + ssize_t head; + + /* A hole in the head is alignment padding. Write it out. */ + head = compact_head_take(ctx, NULL, offset, len); + if (head < 0) + return -1; + + offset += head; + len -= head; + if (!len) + return 0; + + /* This is what the object doesn't have to carry. */ + return compact_keep_hole(ctx, offset, len); +} + +/* Where @offset ends up in the object once the holes ahead of it are gone. */ +static __u64 compact_offset(const struct compact_ctx *ctx, __u64 body_start, + __u64 offset) +{ + __u64 elided = 0; + size_t i; + + for (i = 0; i < ctx->nr_holes; i++) { + __u64 len = ctx->holes[i].len; + + if (ctx->holes[i].offset >= offset) + break; + if (ctx->holes[i].offset + len > offset) + len = offset - ctx->holes[i].offset; + elided += len; + } + + return body_start + (offset - ctx->data_offset) - elided; +} + +/* A run of segment data that made it into the object. */ +static void compact_add_data(struct compact_piece *pieces, size_t *nr, + const ElfW(Phdr) *phdr, __u64 start, __u64 end) +{ + struct compact_piece *piece = &pieces[(*nr)++]; + + piece->phdr = *phdr; + piece->phdr.p_vaddr = phdr->p_vaddr + (start - phdr->p_offset); + piece->phdr.p_paddr = 0; + piece->phdr.p_filesz = end - start; + piece->phdr.p_memsz = end - start; + piece->src = start; +} + +/* + * A run of @len bytes the object doesn't carry. It grows the piece in + * front of it if this segment already has one, because everything a + * segment covers past p_filesz is zeroes anyway. + */ +static void compact_add_zero(struct compact_piece *pieces, size_t *nr, + size_t first, const ElfW(Phdr) *phdr, __u64 vaddr, + __u64 len) +{ + struct compact_piece *piece; + + if (*nr > first) { + pieces[*nr - 1].phdr.p_memsz += len; + return; + } + + piece = &pieces[(*nr)++]; + piece->phdr = *phdr; + piece->phdr.p_vaddr = vaddr; + piece->phdr.p_paddr = 0; + piece->phdr.p_filesz = 0; + piece->phdr.p_memsz = len; + piece->src = 0; +} + +/* Split the segments at the holes and write out what the object became. */ +static int compact_build(struct compact_ctx *ctx, int fd_object) +{ + __u64 note_offset = 0, note_len = 0, note_new; + __u64 align = 0, head_len, body_start, pos; + size_t nr_old, nr_new = 0, note_piece = 0, i; + struct compact_piece *pieces; + char buffer[PAGE_SIZE]; + const ElfW(Phdr) *old; + ElfW(Ehdr) ehdr; + int ret = -1; + + if (!ctx->data_offset) { + fprintf(stderr, "%s: coredump without segment data\n", __func__); + return -1; + } + + memcpy(&ehdr, ctx->head, sizeof(ehdr)); + if (ehdr.e_shoff) { + fprintf(stderr, "%s: section headers are not handled\n", + __func__); + return -1; + } + + old = (const ElfW(Phdr) *)(ctx->head + ehdr.e_phoff); + nr_old = ehdr.e_phnum; + + pieces = calloc(nr_old + 2 * ctx->nr_holes + 1, sizeof(*pieces)); + if (!pieces) { + fprintf(stderr, "%s: out of memory\n", __func__); + return -1; + } + + for (i = 0; i < nr_old; i++) { + ElfW(Phdr) phdr = old[i]; + __u64 end = phdr.p_offset + phdr.p_filesz; + __u64 cur = phdr.p_offset; + size_t first = nr_new, h; + + /* The notes move because the table in front of them grows. */ + if (phdr.p_type == PT_NOTE) { + if (note_len) { + fprintf(stderr, "%s: more than one note segment\n", + __func__); + goto out; + } + note_offset = phdr.p_offset; + note_len = phdr.p_filesz; + note_piece = nr_new; + pieces[nr_new].phdr = phdr; + pieces[nr_new++].src = 0; + continue; + } + + if (phdr.p_type != PT_LOAD) { + if (phdr.p_filesz && phdr.p_offset < ctx->data_offset) { + fprintf(stderr, "%s: segment %zu is in the head\n", + __func__, i); + goto out; + } + pieces[nr_new].phdr = phdr; + pieces[nr_new++].src = phdr.p_offset; + continue; + } + + if (!align) + align = phdr.p_align; + + for (h = 0; h < ctx->nr_holes && cur < end; h++) { + __u64 start = ctx->holes[h].offset; + __u64 stop = start + ctx->holes[h].len; + + if (stop <= cur) + continue; + if (start >= end) + break; + + /* A hole can span more than this one segment. */ + if (start < cur) + start = cur; + if (stop > end) + stop = end; + + if (start > cur) { + compact_add_data(pieces, &nr_new, &phdr, cur, + start); + cur = start; + } + compact_add_zero(pieces, &nr_new, first, &phdr, + phdr.p_vaddr + (cur - phdr.p_offset), + stop - cur); + cur = stop; + } + + if (cur < end) + compact_add_data(pieces, &nr_new, &phdr, cur, end); + + /* Whatever the kernel didn't dump of this mapping. */ + if (phdr.p_memsz > phdr.p_filesz) + compact_add_zero(pieces, &nr_new, first, &phdr, + phdr.p_vaddr + phdr.p_filesz, + phdr.p_memsz - phdr.p_filesz); + } + + if (!note_len || note_offset + note_len > ctx->head_len) { + fprintf(stderr, "%s: notes aren't where they should be\n", + __func__); + goto out; + } + + if (nr_new >= PN_XNUM) { + fprintf(stderr, "%s: %zu program headers don't fit\n", __func__, + nr_new); + goto out; + } + + if (!align || (align & (align - 1))) + align = sysconf(_SC_PAGESIZE); + + note_new = sizeof(ehdr) + (__u64)nr_new * sizeof(ElfW(Phdr)); + head_len = note_new + note_len; + body_start = (head_len + align - 1) & ~(align - 1); + + for (i = 0; i < nr_new; i++) { + struct compact_piece *piece = &pieces[i]; + + if (i == note_piece) + piece->phdr.p_offset = note_new; + else if (piece->phdr.p_filesz) + piece->phdr.p_offset = compact_offset(ctx, body_start, + piece->src); + else + piece->phdr.p_offset = 0; + } + + /* Only now is the head known. That's why it is uploaded last. */ + ehdr.e_phnum = nr_new; + if (compact_write(fd_object, &ehdr, sizeof(ehdr))) + goto out; + + for (i = 0; i < nr_new; i++) + if (compact_write(fd_object, &pieces[i].phdr, + sizeof(pieces[i].phdr))) + goto out; + + if (compact_write(fd_object, ctx->head + note_offset, note_len)) + goto out; + + /* Keep the segments aligned the way a debugger expects them. */ + memset(buffer, 0, sizeof(buffer)); + for (pos = head_len; pos < body_start; ) { + __u64 chunk = body_start - pos; + + if (chunk > sizeof(buffer)) + chunk = sizeof(buffer); + if (compact_write(fd_object, buffer, chunk)) + goto out; + pos += chunk; + } + + /* Putting the parts together is the blob store's job. Do it here. */ + for (pos = 0; pos < ctx->body_len; ) { + ssize_t chunk = pread(ctx->fd_body, buffer, sizeof(buffer), pos); + + if (chunk <= 0) { + fprintf(stderr, "%s: short read %zd: %m\n", __func__, + chunk); + goto out; + } + if (compact_write(fd_object, buffer, chunk)) + goto out; + pos += chunk; + } + + fprintf(stderr, "Object is %llu bytes in %zu program headers, %zu holes left out\n", + (unsigned long long)(body_start + ctx->body_len), nr_new, + ctx->nr_holes); + ret = 0; +out: + free(pieces); + return ret; +} + +/* + * Reassemble a record stream into an ELF core file that has no holes in + * it, the way a coredump server that uploads to a blob store has to. If + * @fd_reference is valid it gets the coredump the records describe, + * holes and all, so the test can compare the two. + */ +ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference, + off_t *coredump_size) +{ + struct compact_ctx ctx = { + .fd_body = -1, + .fd_reference = fd_reference, + }; + struct coredump_record_sink sink = { + .data = compact_data, + .zero = compact_zero, + .ctx = &ctx, + }; + ssize_t received; + off_t size = 0; + FILE *body; + + body = tmpfile(); + if (!body) { + fprintf(stderr, "%s: tmpfile failed: %m\n", __func__); + return -1; + } + ctx.fd_body = fileno(body); + + /* An upload is appended to. Make sure nothing here can seek. */ + if (fcntl(ctx.fd_body, F_SETFL, O_APPEND)) { + fprintf(stderr, "%s: F_SETFL failed: %m\n", __func__); + received = -1; + goto out; + } + + received = __recv_coredump_records(fd_coredump, &sink, &size, NULL, -1); + if (received < 0) + goto out; + + /* + * Nothing is written for a hole, so grow the reference to the size + * the records describe in case the coredump ended in one. + */ + if (ftruncate(fd_reference, size) < 0) { + fprintf(stderr, "%s: ftruncate to %llu failed: %m\n", + __func__, (unsigned long long)size); + received = -1; + goto out; + } + + if (compact_build(&ctx, fd_object)) { + received = -1; + goto out; + } + + if (coredump_size) + *coredump_size = size; +out: + fclose(body); + free(ctx.head); + free(ctx.holes); + return received; +} + +/* Read the ELF header and the program header table of @fd. */ +static ElfW(Phdr) *read_phdrs(int fd, size_t *nr) +{ + ElfW(Ehdr) ehdr; + ElfW(Phdr) *phdr; + size_t size; + + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr)) { + fprintf(stderr, "%s: no ELF header: %m\n", __func__); + return NULL; + } + + if (!is_core_ehdr(&ehdr) || !ehdr.e_phnum || + ehdr.e_phentsize != sizeof(*phdr)) { + fprintf(stderr, "%s: not an ELF core file\n", __func__); + return NULL; + } + + size = (size_t)ehdr.e_phnum * ehdr.e_phentsize; + phdr = malloc(size); + if (!phdr) { + fprintf(stderr, "%s: out of memory\n", __func__); + return NULL; + } + + if (pread(fd, phdr, size, ehdr.e_phoff) != (ssize_t)size) { + fprintf(stderr, "%s: short program header table: %m\n", __func__); + free(phdr); + return NULL; + } + + *nr = ehdr.e_phnum; + return phdr; +} + +/* The segment @vaddr falls into. */ +static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr, + __u64 vaddr) +{ + size_t i; + + for (i = 0; i < nr; i++) { + if (phdr[i].p_type != PT_LOAD) + continue; + if (vaddr >= phdr[i].p_vaddr && + vaddr < phdr[i].p_vaddr + phdr[i].p_memsz) + return &phdr[i]; + } + + return NULL; +} + +/* The next stretch of memory the segments cover, split ones merged back. */ +static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i, + __u64 *start, __u64 *end) +{ + while (*i < nr && phdr[*i].p_type != PT_LOAD) + (*i)++; + + if (*i >= nr) + return false; + + *start = phdr[*i].p_vaddr; + *end = phdr[*i].p_vaddr + phdr[*i].p_memsz; + (*i)++; + + while (*i < nr) { + if (phdr[*i].p_type != PT_LOAD) { + (*i)++; + continue; + } + if (phdr[*i].p_vaddr != *end) + break; + *end = phdr[*i].p_vaddr + phdr[*i].p_memsz; + (*i)++; + } + + return true; +} + +/* Compare @len bytes at @offset against @len bytes at @offset_ref. */ +static int compare_range(int fd, __u64 offset, int fd_ref, __u64 offset_ref, + __u64 len) +{ + char buffer[PAGE_SIZE], buffer_ref[PAGE_SIZE]; + + while (len) { + size_t chunk = len < sizeof(buffer) ? len : sizeof(buffer); + + if (pread(fd, buffer, chunk, offset) != (ssize_t)chunk || + pread(fd_ref, buffer_ref, chunk, offset_ref) != (ssize_t)chunk) { + fprintf(stderr, "%s: short read at %llu: %m\n", + __func__, (unsigned long long)offset); + return -1; + } + + if (memcmp(buffer, buffer_ref, chunk)) { + fprintf(stderr, "%s: %llu differs from %llu\n", __func__, + (unsigned long long)offset, + (unsigned long long)offset_ref); + return -1; + } + + offset += chunk; + offset_ref += chunk; + len -= chunk; + } + + return 0; +} + +/* The @len bytes at @offset the object left out have to have been zeroes. */ +static int check_zero_range(int fd, __u64 offset, __u64 len) +{ + static const char zeroes[PAGE_SIZE]; + char buffer[PAGE_SIZE]; + + while (len) { + size_t chunk = len < sizeof(buffer) ? len : sizeof(buffer); + + if (pread(fd, buffer, chunk, offset) != (ssize_t)chunk) { + fprintf(stderr, "%s: short read at %llu: %m\n", + __func__, (unsigned long long)offset); + return -1; + } + + if (memcmp(buffer, zeroes, chunk)) { + fprintf(stderr, "%s: %llu isn't a hole\n", __func__, + (unsigned long long)offset); + return -1; + } + + offset += chunk; + len -= chunk; + } + + return 0; +} + +/* + * The object has to describe the same memory as the coredump it was built + * from, and it has to describe it correctly. + */ +int check_compact_coredump(int fd_object, int fd_reference) +{ + ElfW(Phdr) *object = NULL, *reference = NULL; + size_t nr_object, nr_reference, i; + size_t io = 0, ir = 0; + int ret = -1; + + object = read_phdrs(fd_object, &nr_object); + reference = read_phdrs(fd_reference, &nr_reference); + if (!object || !reference) + goto out; + + /* Nothing may have been dropped and nothing may have been added. */ + for (;;) { + __u64 start = 0, end = 0, start_ref = 0, end_ref = 0; + bool has, has_ref; + + has = next_range(object, nr_object, &io, &start, &end); + has_ref = next_range(reference, nr_reference, &ir, &start_ref, + &end_ref); + if (!has && !has_ref) + break; + + if (has != has_ref || start != start_ref || end != end_ref) { + fprintf(stderr, "%s: object covers 0x%llx-0x%llx, coredump 0x%llx-0x%llx\n", + __func__, (unsigned long long)start, + (unsigned long long)end, + (unsigned long long)start_ref, + (unsigned long long)end_ref); + goto out; + } + } + + for (i = 0; i < nr_object; i++) { + const ElfW(Phdr) *segment; + __u64 offset, dumped; + + if (object[i].p_type != PT_LOAD || !object[i].p_memsz) + continue; + + segment = find_segment(reference, nr_reference, + object[i].p_vaddr); + if (!segment) { + fprintf(stderr, "%s: 0x%llx isn't in the coredump\n", + __func__, + (unsigned long long)object[i].p_vaddr); + goto out; + } + + offset = object[i].p_vaddr - segment->p_vaddr; + dumped = offset < segment->p_filesz ? + segment->p_filesz - offset : 0; + + /* What the object carries is what the coredump had. */ + if (object[i].p_filesz > dumped) { + fprintf(stderr, "%s: object carries %llu bytes the coredump doesn't have\n", + __func__, + (unsigned long long)(object[i].p_filesz - dumped)); + goto out; + } + + if (compare_range(fd_object, object[i].p_offset, fd_reference, + segment->p_offset + offset, + object[i].p_filesz)) + goto out; + + /* And what it left out was a hole. */ + if (object[i].p_memsz > object[i].p_filesz && + dumped > object[i].p_filesz) { + __u64 left_out = dumped - object[i].p_filesz; + + if (left_out > object[i].p_memsz - object[i].p_filesz) + left_out = object[i].p_memsz - object[i].p_filesz; + + if (check_zero_range(fd_reference, + segment->p_offset + offset + + object[i].p_filesz, left_out)) + goto out; + } + } + + ret = 0; +out: + free(object); + free(reference); + return ret; +} + int create_detached_tmpfs(void) { int fd_context, fd_tmpfs; diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index fe0a88a71b0510..00d695b67b3fc0 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -26,6 +26,9 @@ ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, off_t *coredump_size, bool *truncated, int fd_peer_pidfd); bool is_elf_core(int fd); +ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference, + off_t *coredump_size); +int check_compact_coredump(int fd_object, int fd_reference); int create_detached_tmpfs(void); int create_and_listen_unix_socket(const char *path); bool set_core_pattern(const char *pattern); From 5ffde62520c17e079a3f61c42b46c655fe0cb942 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 20 Aug 2026 01:09:39 +0200 Subject: [PATCH 0706/2286] selftests/coredump: show how to inspect the task to decide how the coredump should be sent The kernel blocks in the coredump req until the coredump ack is sent by the coredump server. This allows the coredump server to decide how the kernel is supposed to send the coredump. Let's show how that can work: - a task that has a large memory mapping gets sent as a sparse record stream - a task with a trivial memory mapping gets sent as a plain byte stream Since the threads are parked in coredump_task_exit() with their mm around we can look at /proc//statm to figure out what the task has mapped. Link: https://patch.msgid.link/20260820-work-coredump-sparse-v2-22-ba32dd718c51@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 184 ++++++++++++++++++ .../coredump/coredump_test_helpers.c | 58 ++++++ .../coredump/coredump_test_helpers.h | 7 +- 3 files changed, 248 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index f33eaf2fa93d3e..daff908232a228 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -2136,4 +2136,188 @@ TEST_F(coredump, socket_request_sparse_without_records) check_conflicting_ack(_metadata, self, COREDUMP_KERNEL | COREDUMP_SPARSE); } +/* What the server reports back about the coredump it decided to take. */ +struct stream_choice { + bool sparse; + ssize_t received; + off_t size; + ssize_t vm_size; +}; + +/* + * The kernel blocks in the coredump request until the ack arrives, so a + * coredump server gets to look at the task before it commits to a + * stream. Take the record stream only for a task whose mappings are + * worth it and the plain byte stream for everything else. + */ +static void check_stream_choice(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, bool big, + struct stream_choice *choice) +{ + int pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + int pipefds[2]; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_EQ(pipe(pipefds), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_file = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + struct stream_choice got = {}; + __u64 mask; + + close(ipc_sockets[0]); + close(pipefds[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + /* + * The reassembled coredump is bigger than the mapping the + * child made, so keep it on the detached tmpfs and sparse. + */ + fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached); + if (fd_file < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + /* + * Nothing is on the wire yet and the kernel is waiting for + * the ack, so there is all the time in the world to look at + * the task and decide what to ask it for. + */ + got.vm_size = peer_vm_size(fd_peer_pidfd); + if (got.vm_size < 0) + goto out; + got.sparse = got.vm_size >= SPARSE_STREAM_THRESHOLD; + + fprintf(stderr, "Peer maps %zd bytes, asking for %s\n", + got.vm_size, + got.sparse ? "a sparse record stream" : "a byte stream"); + + mask = COREDUMP_KERNEL | COREDUMP_WAIT; + if (got.sparse) + mask |= COREDUMP_RECORDS | COREDUMP_SPARSE; + + if (!send_coredump_ack(fd_coredump, &req, mask, 0)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) + goto out; + + if (got.sparse) { + got.received = recv_coredump_records(fd_coredump, fd_file, + &got.size, NULL, -1); + } else { + got.received = recv_coredump_bytes(fd_coredump, fd_file); + got.size = got.received; + } + if (got.received < 0) + goto out; + + /* Either way a debugger has to see an ordinary core file. */ + if (!is_elf_core(fd_file)) + goto out; + + if (write_nointr(pipefds[1], &got, sizeof(got)) != sizeof(got)) + goto out; + + exit_code = EXIT_SUCCESS; +out: + close(pipefds[1]); + if (fd_file >= 0) + close(fd_file); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + EXPECT_EQ(close(pipefds[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_sparse(big ? SPARSE_MAPPING_SIZE : PAGE_SIZE); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_EQ(read_nointr(pipefds[0], choice, sizeof(*choice)), + sizeof(*choice)); + EXPECT_EQ(close(pipefds[0]), 0); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +} + +/* A task with little mapped isn't worth a record stream. */ +TEST_F(coredump, socket_request_stream_choice_small) +{ + struct stream_choice choice = {}; + + check_stream_choice(_metadata, self, false, &choice); + + ASSERT_LT(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD); + ASSERT_FALSE(choice.sparse); + ASSERT_GT(choice.received, 0); +} + +/* A task sitting on a big mapping is. */ +TEST_F(coredump, socket_request_stream_choice_large) +{ + struct stream_choice choice = {}; + + check_stream_choice(_metadata, self, true, &choice); + + ASSERT_GE(choice.vm_size, (ssize_t)SPARSE_STREAM_THRESHOLD); + ASSERT_TRUE(choice.sparse); + ASSERT_GT(choice.size, (off_t)SPARSE_MAPPING_SIZE); + + /* The holes didn't have to go over the socket. */ + ASSERT_LT(choice.received, choice.size / 8); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 9346b8f688e260..d7cc448eeaf442 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1095,6 +1095,33 @@ int check_compact_coredump(int fd_object, int fd_reference) return ret; } +/* Read a plain coredump byte stream to end-of-file. */ +ssize_t recv_coredump_bytes(int fd_coredump, int fd_core_file) +{ + ssize_t received = 0; + + for (;;) { + char buffer[PAGE_SIZE]; + ssize_t ret = read_nointr(fd_coredump, buffer, sizeof(buffer)); + + if (ret < 0) { + fprintf(stderr, "%s: read failed: %m\n", __func__); + return -1; + } + if (ret == 0) + break; + + if (write_nointr(fd_core_file, buffer, ret) != ret) { + fprintf(stderr, "%s: write failed: %m\n", __func__); + return -1; + } + received += ret; + } + + fprintf(stderr, "Received %zd bytes of coredump\n", received); + return received; +} + int create_detached_tmpfs(void) { int fd_context, fd_tmpfs; @@ -1190,6 +1217,37 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info) return true; } +/* + * How much the peer has mapped. The task is parked in the coredump + * handshake, so its mm is still there to be looked at. + */ +ssize_t peer_vm_size(int fd_peer_pidfd) +{ + struct pidfd_info info = {}; + unsigned long pages; + char path[64]; + FILE *f; + + if (!get_pidfd_info(fd_peer_pidfd, &info)) + return -1; + + snprintf(path, sizeof(path), "/proc/%d/statm", info.pid); + f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "%s: %s: %m\n", __func__, path); + return -1; + } + + if (fscanf(f, "%lu", &pages) != 1) { + fprintf(stderr, "%s: %s: no size\n", __func__, path); + fclose(f); + return -1; + } + fclose(f); + + return (ssize_t)pages * sysconf(_SC_PAGESIZE); +} + /* Protocol helper functions */ ssize_t recv_marker(int fd) diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index 00d695b67b3fc0..97ad5cfeae928d 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -18,6 +18,9 @@ /* Size of the mostly unpopulated mapping the sparse coredump test maps. */ #define SPARSE_MAPPING_SIZE (256 * 1024 * 1024) +/* A task mapping at least this much is worth a record stream. */ +#define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2) + /* Shared helper function declarations */ void *do_nothing(void *arg); void crashing_child(void); @@ -25,9 +28,11 @@ void crashing_child_sparse(size_t size); ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, off_t *coredump_size, bool *truncated, int fd_peer_pidfd); -bool is_elf_core(int fd); ssize_t recv_coredump_compact(int fd_coredump, int fd_object, int fd_reference, off_t *coredump_size); +ssize_t recv_coredump_bytes(int fd_coredump, int fd_core_file); +ssize_t peer_vm_size(int fd_peer_pidfd); +bool is_elf_core(int fd); int check_compact_coredump(int fd_object, int fd_reference); int create_detached_tmpfs(void); int create_and_listen_unix_socket(const char *path); From 757ebc30a37250d18780de6b06ec9d3230879538 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:02 +0200 Subject: [PATCH 0707/2286] coredump: select memory types to include Currently /proc//coredump_filter determines what types of memory are included in a coredump produced by . This is fairly static. The coredump server has no easy way to configure what memory to dump even though it can figure out all the necessary details to make an informed decision. Add a new COREDUMP_MEMORY_TYPES feature bit. If the coredump server raises it the kernel will dump memory types raised in the coredump_ack->memory_types member. Zero is valid and causes the creation of a coredump that just includes the program headers and notes but no memory apart from the mappings that are always dumped. struct coredump_req gains @memory_types which is set to the default memory types that are included in the coredump. This can be overridden by raising bits in coredump_ack->memory_types. It also gains @memory_types_mask which contains a bitmask of all memory types the kernel knows about. A coredump server may only raise bits in coredump_ack->memory_types that are raised in coredump_req->memory_types_mask. struct coredump_ack grows too. If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the memory types set in the @memory_types mask. Zero is valid and dumps no memory apart from the mappings that are always dumped. A coredump server wanting to add or drop memory types instead of outright replacing it should simply copy coredump_req->memory_types and then mask off or raise types as needed. @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of at least COREDUMP_ACK_SIZE_VER1 bytes. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-1-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/proc.rst | 4 + fs/coredump.c | 114 +++++++++++++++++++++++------ include/linux/coredump.h | 4 +- include/uapi/linux/coredump.h | 70 +++++++++++++++++- 4 files changed, 165 insertions(+), 27 deletions(-) diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst index c102b62023cdc1..fc59c98acca10a 100644 --- a/Documentation/filesystems/proc.rst +++ b/Documentation/filesystems/proc.rst @@ -1963,6 +1963,10 @@ For example:: $ echo 0x7 > /proc/self/coredump_filter $ ./some_program +If the coredump socket protocol is used a coredump server can select memory +types to include dynamically. See COREDUMP_MEMORY_TYPES in +include/uapi/linux/coredump.h. + 3.5 /proc//mountinfo - Information about mounts -------------------------------------------------------- diff --git a/fs/coredump.c b/fs/coredump.c index 4ba30df014d231..320243fde9a48b 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -759,10 +759,39 @@ static inline bool coredump_sock_send(struct file *file, struct coredump_req *re return ret == sizeof(*req); } -static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER0); -static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER0); +static_assert(sizeof(struct coredump_req) == COREDUMP_REQ_SIZE_VER1); +static_assert(sizeof(struct coredump_ack) == COREDUMP_ACK_SIZE_VER1); static_assert(sizeof(enum coredump_mark) == sizeof(__u32)); +/* Every memory type this kernel knows. */ +#define COREDUMP_MEMORY_ALL \ + (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \ + COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \ + COREDUMP_MEMORY_ELF_HEADERS | \ + COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \ + COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED) + +#define COREDUMP_MEMORY_TYPE_BIT(mmf) BIT((mmf) - MMF_DUMP_FILTER_SHIFT) +static_assert(COREDUMP_MEMORY_ALL == (MMF_DUMP_FILTER_MASK >> MMF_DUMP_FILTER_SHIFT)); +static_assert(COREDUMP_MEMORY_ANON_PRIVATE == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_PRIVATE)); +static_assert(COREDUMP_MEMORY_ANON_SHARED == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ANON_SHARED)); +static_assert(COREDUMP_MEMORY_FILE_PRIVATE == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_PRIVATE)); +static_assert(COREDUMP_MEMORY_FILE_SHARED == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_MAPPED_SHARED)); +static_assert(COREDUMP_MEMORY_ELF_HEADERS == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_ELF_HEADERS)); +static_assert(COREDUMP_MEMORY_HUGETLB_PRIVATE == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_PRIVATE)); +static_assert(COREDUMP_MEMORY_HUGETLB_SHARED == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_HUGETLB_SHARED)); +static_assert(COREDUMP_MEMORY_DAX_PRIVATE == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_PRIVATE)); +static_assert(COREDUMP_MEMORY_DAX_SHARED == + COREDUMP_MEMORY_TYPE_BIT(MMF_DUMP_DAX_SHARED)); + static inline bool coredump_sock_mark(struct file *file, enum coredump_mark mark) { struct msghdr msg = { .msg_flags = MSG_NOSIGNAL }; @@ -804,11 +833,14 @@ static inline void coredump_sock_shutdown(struct file *file) static bool coredump_sock_request(struct core_name *cn, struct coredump_params *cprm) { struct coredump_req req = { - .size = sizeof(struct coredump_req), - .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | - COREDUMP_REJECT | COREDUMP_WAIT | - COREDUMP_RECORDS | COREDUMP_SPARSE, - .size_ack = sizeof(struct coredump_ack), + .size = sizeof(struct coredump_req), + .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | + COREDUMP_REJECT | COREDUMP_WAIT | + COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .size_ack = sizeof(struct coredump_ack), + .memory_types = cprm->memory_types, + .memory_types_mask = COREDUMP_MEMORY_ALL, }; struct coredump_ack ack = {}; ssize_t usize; @@ -873,6 +905,30 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * return false; } + if (ack.mask & COREDUMP_MEMORY_TYPES) { + /* The memory types need the whole field. */ + if (usize < COREDUMP_ACK_SIZE_VER1) { + coredump_sock_mark(cprm->file, COREDUMP_MARK_MINSIZE); + return false; + } + + /* The memory types only select what the kernel writes. */ + if (!(ack.mask & COREDUMP_KERNEL)) { + coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING); + return false; + } + + /* Refuse unknown memory types. */ + if (ack.memory_types & ~req.memory_types_mask) { + coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED); + return false; + } + } else if (ack.memory_types) { + /* Like @spare the field must be zero when it isn't used. */ + coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED); + return false; + } + /* Record header scratch; a bvec can't point at the stack. */ if (ack.mask & COREDUMP_RECORDS) { cprm->record_hdr = kmalloc_obj(*cprm->record_hdr); @@ -880,6 +936,10 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params * return false; } + /* The server's selection replaces the task's entirely. */ + if (ack.mask & COREDUMP_MEMORY_TYPES) + cprm->memory_types = ack.memory_types; + cprm->mask = ack.mask; return coredump_sock_mark(cprm->file, COREDUMP_MARK_REQACK); } @@ -1195,6 +1255,10 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, } } +#define COREDUMP_TASK_MEMORY_TYPES(mm) \ + ((__mm_flags_get_word((mm)) & MMF_DUMP_FILTER_MASK) >> \ + MMF_DUMP_FILTER_SHIFT) + void vfs_coredump(const kernel_siginfo_t *siginfo) { size_t *argv __free(kfree) = NULL; @@ -1206,8 +1270,8 @@ void vfs_coredump(const kernel_siginfo_t *siginfo) struct coredump_params cprm = { .siginfo = siginfo, .limit = rlimit(RLIMIT_CORE), - /* Snapshot MMF_DUMP_FILTER_* (unlocked) and dumpable for the dump. */ - .mm_flags = __mm_flags_get_word(mm), + /* Snapshot the memory types (unlocked) and dumpable for the dump. */ + .memory_types = COREDUMP_TASK_MEMORY_TYPES(mm), .dumpable = task_exec_state_get_dumpable(current), .vma_meta = NULL, .cpu = raw_smp_processor_id(), @@ -1741,15 +1805,15 @@ static bool always_dump_vma(struct vm_area_struct *vma) } #define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1 +#define COREDUMP_MEMORY_TYPE_INCLUDE(types, type) \ + ((types) & COREDUMP_MEMORY_##type) /* * Decide how much of @vma's contents should be included in a core dump. */ static unsigned long vma_dump_size(struct vm_area_struct *vma, - unsigned long mm_flags) + u64 memory_types) { -#define FILTER(type) (mm_flags & (1UL << MMF_DUMP_##type)) - /* always dump the vdso and vsyscall sections */ if (always_dump_vma(vma)) goto whole; @@ -1759,18 +1823,22 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma, /* support for DAX */ if (vma_is_dax(vma)) { - if ((vma->vm_flags & VM_SHARED) && FILTER(DAX_SHARED)) + if ((vma->vm_flags & VM_SHARED) && + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_SHARED)) goto whole; - if (!(vma->vm_flags & VM_SHARED) && FILTER(DAX_PRIVATE)) + if (!(vma->vm_flags & VM_SHARED) && + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, DAX_PRIVATE)) goto whole; return 0; } /* Hugetlb memory check */ if (is_vm_hugetlb_page(vma)) { - if ((vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_SHARED)) + if ((vma->vm_flags & VM_SHARED) && + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_SHARED)) goto whole; - if (!(vma->vm_flags & VM_SHARED) && FILTER(HUGETLB_PRIVATE)) + if (!(vma->vm_flags & VM_SHARED) && + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, HUGETLB_PRIVATE)) goto whole; return 0; } @@ -1782,25 +1850,27 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma, /* By default, dump shared memory if mapped from an anonymous file. */ if (vma->vm_flags & VM_SHARED) { if (file_inode(vma->vm_file)->i_nlink == 0 ? - FILTER(ANON_SHARED) : FILTER(MAPPED_SHARED)) + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_SHARED) : + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_SHARED)) goto whole; return 0; } /* Dump segments that have been written to. */ - if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) && FILTER(ANON_PRIVATE)) + if ((!IS_ENABLED(CONFIG_MMU) || vma->anon_vma) && + COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ANON_PRIVATE)) goto whole; if (vma->vm_file == NULL) return 0; - if (FILTER(MAPPED_PRIVATE)) + if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, FILE_PRIVATE)) goto whole; /* * If this is the beginning of an executable file mapping, * dump the first page to aid in determining what was mapped here. */ - if (FILTER(ELF_HEADERS) && + if (COREDUMP_MEMORY_TYPE_INCLUDE(memory_types, ELF_HEADERS) && vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) { if ((READ_ONCE(file_inode(vma->vm_file)->i_mode) & 0111) != 0) return PAGE_SIZE; @@ -1816,8 +1886,6 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma, return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER; } -#undef FILTER - return 0; whole: @@ -1902,7 +1970,7 @@ static bool dump_vma_snapshot(struct coredump_params *cprm) m->start = vma->vm_start; m->end = vma->vm_end; m->flags = vma->vm_flags; - m->dump_size = vma_dump_size(vma, cprm->mm_flags); + m->dump_size = vma_dump_size(vma, cprm->memory_types); m->pgoff = vma->vm_pgoff; m->file = vma->vm_file; if (m->file) diff --git a/include/linux/coredump.h b/include/linux/coredump.h index b252bb2843b337..74af57b9406b8e 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -32,8 +32,8 @@ struct coredump_params { const kernel_siginfo_t *siginfo; struct file *file; unsigned long limit; - /* MMF_DUMP_FILTER_* bits, snapshot of mm->flags at dump start. */ - unsigned long mm_flags; + /* COREDUMP_MEMORY_* types to dump, the task's or the server's. */ + u64 memory_types; /* Snapshot of dumpable at dump start. */ enum task_dumpable dumpable; int cpu; diff --git a/include/uapi/linux/coredump.h b/include/uapi/linux/coredump.h index f3771861ca4857..6d0c53b534ea8d 100644 --- a/include/uapi/linux/coredump.h +++ b/include/uapi/linux/coredump.h @@ -16,6 +16,9 @@ * requires COREDUMP_KERNEL * @COREDUMP_SPARSE: describe the holes in the coredump as zero records * instead of transferring them; requires COREDUMP_RECORDS + * @COREDUMP_MEMORY_TYPES: dump the memory types in + * coredump_ack->memory_types instead of the ones + * the task selected; requires COREDUMP_KERNEL */ enum { COREDUMP_KERNEL = (1ULL << 0), @@ -24,6 +27,37 @@ enum { COREDUMP_WAIT = (1ULL << 3), COREDUMP_RECORDS = (1ULL << 4), COREDUMP_SPARSE = (1ULL << 5), + COREDUMP_MEMORY_TYPES = (1ULL << 6), +}; + +/** + * coredump memory types + * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory + * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory + * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory + * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory + * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private + * mapping that starts an ELF file + * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory + * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory + * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory + * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory + * + * A bitmask of memory types a coredump may request to be included. New + * memory type bits must ensure that they do not steal memory from an + * existing one so a coredump server will continue to get the same + * coredumps even if a new bit is introduced. + */ +enum { + COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0), + COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1), + COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2), + COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3), + COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4), + COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5), + COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6), + COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7), + COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8), }; /** @@ -31,6 +65,8 @@ enum { * @size: size of struct coredump_req * @size_ack: known size of struct coredump_ack on this kernel * @mask: supported features + * @memory_types: the memory types the task selected + * @memory_types_mask: the memory types this kernel knows * * When a coredump happens the kernel will connect to the coredump * socket and send a coredump request to the coredump server. The @size @@ -49,15 +85,27 @@ enum { * struct coredump_ack the kernel knows. Userspace may only send up to * coredump_req->size_ack bytes to the kernel and must set * coredump_ack->size accordingly. + * + * @memory_types is set to the default memory types that are included in + * the coredump. This can be overridden by raising bits in + * coredump_ack->memory_types. + * + * @memory_types_mask contains a bitmask of all memory types the kernel + * knows about. A coredump server may only raise bits in + * coredump_ack->memory_types that are raised in + * coredump_req->memory_types_mask. */ struct coredump_req { __u32 size; __u32 size_ack; __u64 mask; + __u64 memory_types; + __u64 memory_types_mask; }; enum { COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */ + COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */ }; /** @@ -65,6 +113,8 @@ enum { * @size: size of the struct * @spare: unused * @mask: features kernel is supposed to use + * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES + * in @mask * * The @size member must be set to the size of struct coredump_ack. It * may never exceed what the kernel returned in coredump_req->size_ack @@ -74,15 +124,30 @@ enum { * The @mask member must be set to the features the coredump server * wants the kernel to use. Only bits the kernel returned in * coredump_req->mask may be set. + * + * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the + * memory types set in the @memory_types mask. Zero is valid and dumps + * no memory apart from the mappings that are always dumped. + * + * Note that memory a task excluded via MADV_DONTDUMP is always left + * out. A coredump server wanting to add or drop memory types instead of + * outright replacing it should simply copy coredump_req->memory_types + * and then mask off or raise types as needed. + * + * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't + * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of + * at least COREDUMP_ACK_SIZE_VER1 bytes. */ struct coredump_ack { __u32 size; __u32 spare; __u64 mask; + __u64 memory_types; }; enum { COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */ + COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */ }; /** @@ -90,11 +155,12 @@ enum { * * The kernel will place a single byte on the coredump socket. The * markers notify userspace whether the coredump ack succeeded or - * failed. + * failed. After any marker other than COREDUMP_MARK_REQACK the kernel + * closes the connection and no coredump is generated. * * @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small * @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big - * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid + * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid * @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options * @COREDUMP_MARK_REQACK: the coredump request and ack was successful * @__COREDUMP_MARK_MAX: the maximum coredump mark value From 27a26a0b86ceee58c92e61a67b3d50db1ef8f3ba Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:03 +0200 Subject: [PATCH 0708/2286] tools: sync coredump.h header Sync the headers for the selftests. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-2-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/include/uapi/linux/coredump.h | 70 ++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tools/include/uapi/linux/coredump.h b/tools/include/uapi/linux/coredump.h index f3771861ca4857..6d0c53b534ea8d 100644 --- a/tools/include/uapi/linux/coredump.h +++ b/tools/include/uapi/linux/coredump.h @@ -16,6 +16,9 @@ * requires COREDUMP_KERNEL * @COREDUMP_SPARSE: describe the holes in the coredump as zero records * instead of transferring them; requires COREDUMP_RECORDS + * @COREDUMP_MEMORY_TYPES: dump the memory types in + * coredump_ack->memory_types instead of the ones + * the task selected; requires COREDUMP_KERNEL */ enum { COREDUMP_KERNEL = (1ULL << 0), @@ -24,6 +27,37 @@ enum { COREDUMP_WAIT = (1ULL << 3), COREDUMP_RECORDS = (1ULL << 4), COREDUMP_SPARSE = (1ULL << 5), + COREDUMP_MEMORY_TYPES = (1ULL << 6), +}; + +/** + * coredump memory types + * @COREDUMP_MEMORY_ANON_PRIVATE: anonymous private memory + * @COREDUMP_MEMORY_ANON_SHARED: anonymous shared memory + * @COREDUMP_MEMORY_FILE_PRIVATE: file-backed private memory + * @COREDUMP_MEMORY_FILE_SHARED: file-backed shared memory + * @COREDUMP_MEMORY_ELF_HEADERS: the first page of a file-backed private + * mapping that starts an ELF file + * @COREDUMP_MEMORY_HUGETLB_PRIVATE: hugetlb private memory + * @COREDUMP_MEMORY_HUGETLB_SHARED: hugetlb shared memory + * @COREDUMP_MEMORY_DAX_PRIVATE: DAX private memory + * @COREDUMP_MEMORY_DAX_SHARED: DAX shared memory + * + * A bitmask of memory types a coredump may request to be included. New + * memory type bits must ensure that they do not steal memory from an + * existing one so a coredump server will continue to get the same + * coredumps even if a new bit is introduced. + */ +enum { + COREDUMP_MEMORY_ANON_PRIVATE = (1ULL << 0), + COREDUMP_MEMORY_ANON_SHARED = (1ULL << 1), + COREDUMP_MEMORY_FILE_PRIVATE = (1ULL << 2), + COREDUMP_MEMORY_FILE_SHARED = (1ULL << 3), + COREDUMP_MEMORY_ELF_HEADERS = (1ULL << 4), + COREDUMP_MEMORY_HUGETLB_PRIVATE = (1ULL << 5), + COREDUMP_MEMORY_HUGETLB_SHARED = (1ULL << 6), + COREDUMP_MEMORY_DAX_PRIVATE = (1ULL << 7), + COREDUMP_MEMORY_DAX_SHARED = (1ULL << 8), }; /** @@ -31,6 +65,8 @@ enum { * @size: size of struct coredump_req * @size_ack: known size of struct coredump_ack on this kernel * @mask: supported features + * @memory_types: the memory types the task selected + * @memory_types_mask: the memory types this kernel knows * * When a coredump happens the kernel will connect to the coredump * socket and send a coredump request to the coredump server. The @size @@ -49,15 +85,27 @@ enum { * struct coredump_ack the kernel knows. Userspace may only send up to * coredump_req->size_ack bytes to the kernel and must set * coredump_ack->size accordingly. + * + * @memory_types is set to the default memory types that are included in + * the coredump. This can be overridden by raising bits in + * coredump_ack->memory_types. + * + * @memory_types_mask contains a bitmask of all memory types the kernel + * knows about. A coredump server may only raise bits in + * coredump_ack->memory_types that are raised in + * coredump_req->memory_types_mask. */ struct coredump_req { __u32 size; __u32 size_ack; __u64 mask; + __u64 memory_types; + __u64 memory_types_mask; }; enum { COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */ + COREDUMP_REQ_SIZE_VER1 = 32U, /* memory_types and memory_types_mask added */ }; /** @@ -65,6 +113,8 @@ enum { * @size: size of the struct * @spare: unused * @mask: features kernel is supposed to use + * @memory_types: memory types to dump, only with COREDUMP_MEMORY_TYPES + * in @mask * * The @size member must be set to the size of struct coredump_ack. It * may never exceed what the kernel returned in coredump_req->size_ack @@ -74,15 +124,30 @@ enum { * The @mask member must be set to the features the coredump server * wants the kernel to use. Only bits the kernel returned in * coredump_req->mask may be set. + * + * If COREDUMP_MEMORY_TYPES is raised in @mask the kernel dumps the + * memory types set in the @memory_types mask. Zero is valid and dumps + * no memory apart from the mappings that are always dumped. + * + * Note that memory a task excluded via MADV_DONTDUMP is always left + * out. A coredump server wanting to add or drop memory types instead of + * outright replacing it should simply copy coredump_req->memory_types + * and then mask off or raise types as needed. + * + * Note that @memory_types must be zero if COREDUMP_MEMORY_TYPES isn't + * raised. COREDUMP_MEMORY_TYPES requires COREDUMP_KERNEL and an ack of + * at least COREDUMP_ACK_SIZE_VER1 bytes. */ struct coredump_ack { __u32 size; __u32 spare; __u64 mask; + __u64 memory_types; }; enum { COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */ + COREDUMP_ACK_SIZE_VER1 = 24U, /* memory_types added */ }; /** @@ -90,11 +155,12 @@ enum { * * The kernel will place a single byte on the coredump socket. The * markers notify userspace whether the coredump ack succeeded or - * failed. + * failed. After any marker other than COREDUMP_MARK_REQACK the kernel + * closes the connection and no coredump is generated. * * @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small * @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big - * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid + * @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask or memory types were invalid * @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options * @COREDUMP_MARK_REQACK: the coredump request and ack was successful * @__COREDUMP_MARK_MAX: the maximum coredump mark value From 43c25b5fb7dbaeb10e3e832d6d9ce5afee8a7833 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:04 +0200 Subject: [PATCH 0709/2286] selftests/coredump: simplify the refusal tests A couple of tests send a coredump_ack that the kernel refuses. They then check the marker. Make sure they all use common infrastructure. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-3-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 558 +++--------------- .../coredump/coredump_test_helpers.c | 37 +- .../coredump/coredump_test_helpers.h | 1 + 3 files changed, 111 insertions(+), 485 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index daff908232a228..a07546e796517e 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -508,91 +508,79 @@ TEST_F(coredump, socket_request_reject) wait_and_check_coredump_server(pid_coredump_server, _metadata, self); } -TEST_F(coredump, socket_request_invalid_flag_combination) +/* An ack the kernel must refuse and how. */ +struct refused_ack { + /* The ack and how many bytes of it the server sends. */ + struct coredump_ack ack; + size_t bytes; + /* The marker the kernel answers with. */ + enum coredump_mark mark; +}; + +/* Send @refused, expect the kernel to refuse it with the marker. */ +static void check_refused_ack(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, + const struct refused_ack *refused) { - int pidfd, ret, status; + int pidfd, status; pid_t pid, pid_coredump_server; struct pidfd_info info = {}; int ipc_sockets[2]; char c; + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); - ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); - ASSERT_EQ(ret, 0); - pid_coredump_server = fork(); ASSERT_GE(pid_coredump_server, 0); if (pid_coredump_server == 0) { - struct coredump_req req = {}; int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; close(ipc_sockets[0]); fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); - if (fd_server < 0) { - fprintf(stderr, "socket_request_invalid_flag_combination: create_and_listen_unix_socket failed: %m\n"); + if (fd_server < 0) goto out; - } - if (write_nointr(ipc_sockets[1], "1", 1) < 0) { - fprintf(stderr, "socket_request_invalid_flag_combination: write_nointr to ipc socket failed: %m\n"); + if (write_nointr(ipc_sockets[1], "1", 1) < 0) goto out; - } close(ipc_sockets[1]); fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); - if (fd_coredump < 0) { - fprintf(stderr, "socket_request_invalid_flag_combination: accept4 failed: %m\n"); + if (fd_coredump < 0) goto out; - } fd_peer_pidfd = get_peer_pidfd(fd_coredump); - if (fd_peer_pidfd < 0) { - fprintf(stderr, "socket_request_invalid_flag_combination: get_peer_pidfd failed\n"); + if (fd_peer_pidfd < 0) goto out; - } - if (!get_pidfd_info(fd_peer_pidfd, &info)) { - fprintf(stderr, "socket_request_invalid_flag_combination: get_pidfd_info failed\n"); + /* The task shows as dumping while it waits for the ack. */ + if (!get_pidfd_info(fd_peer_pidfd, &info)) goto out; - } - if (!(info.mask & PIDFD_INFO_COREDUMP)) { - fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_INFO_COREDUMP not set in mask\n"); + if (!(info.mask & PIDFD_INFO_COREDUMP) || + !(info.coredump_mask & PIDFD_COREDUMPED)) { + fprintf(stderr, "Peer isn't marked as dumping\n"); goto out; } - if (!(info.coredump_mask & PIDFD_COREDUMPED)) { - fprintf(stderr, "socket_request_invalid_flag_combination: PIDFD_COREDUMPED not set in coredump_mask\n"); - goto out; - } - - if (!read_coredump_req(fd_coredump, &req)) { - fprintf(stderr, "socket_request_invalid_flag_combination: read_coredump_req failed\n"); + if (!read_coredump_req(fd_coredump, &req)) goto out; - } - if (!check_coredump_req(&req)) { - fprintf(stderr, "socket_request_invalid_flag_combination: check_coredump_req failed\n"); + if (!check_coredump_req(&req)) goto out; - } - if (!send_coredump_ack(fd_coredump, &req, - COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT, 0)) { - fprintf(stderr, "socket_request_invalid_flag_combination: send_coredump_ack failed\n"); + if (!send_coredump_ack_bytes(fd_coredump, &refused->ack, + refused->bytes)) goto out; - } - if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) { - fprintf(stderr, "socket_request_invalid_flag_combination: read_marker COREDUMP_MARK_CONFLICTING failed\n"); + if (!read_marker(fd_coredump, refused->mark)) goto out; - } exit_code = EXIT_SUCCESS; - fprintf(stderr, "socket_request_invalid_flag_combination: completed successfully\n"); out: if (fd_peer_pidfd >= 0) close(fd_peer_pidfd); @@ -627,362 +615,72 @@ TEST_F(coredump, socket_request_invalid_flag_combination) wait_and_check_coredump_server(pid_coredump_server, _metadata, self); } -TEST_F(coredump, socket_request_unknown_flag) +/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */ +static void check_conflicting_ack(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, __u64 ack_mask) { - int pidfd, ret, status; - pid_t pid, pid_coredump_server; - struct pidfd_info info = {}; - int ipc_sockets[2]; - char c; - - ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); - - ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); - ASSERT_EQ(ret, 0); - - pid_coredump_server = fork(); - ASSERT_GE(pid_coredump_server, 0); - if (pid_coredump_server == 0) { - struct coredump_req req = {}; - int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; - int exit_code = EXIT_FAILURE; - - close(ipc_sockets[0]); - - fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); - if (fd_server < 0) { - fprintf(stderr, "socket_request_unknown_flag: create_and_listen_unix_socket failed: %m\n"); - goto out; - } - - if (write_nointr(ipc_sockets[1], "1", 1) < 0) { - fprintf(stderr, "socket_request_unknown_flag: write_nointr to ipc socket failed: %m\n"); - goto out; - } - - close(ipc_sockets[1]); - - fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); - if (fd_coredump < 0) { - fprintf(stderr, "socket_request_unknown_flag: accept4 failed: %m\n"); - goto out; - } - - fd_peer_pidfd = get_peer_pidfd(fd_coredump); - if (fd_peer_pidfd < 0) { - fprintf(stderr, "socket_request_unknown_flag: get_peer_pidfd failed\n"); - goto out; - } - - if (!get_pidfd_info(fd_peer_pidfd, &info)) { - fprintf(stderr, "socket_request_unknown_flag: get_pidfd_info failed\n"); - goto out; - } - - if (!(info.mask & PIDFD_INFO_COREDUMP)) { - fprintf(stderr, "socket_request_unknown_flag: PIDFD_INFO_COREDUMP not set in mask\n"); - goto out; - } - - if (!(info.coredump_mask & PIDFD_COREDUMPED)) { - fprintf(stderr, "socket_request_unknown_flag: PIDFD_COREDUMPED not set in coredump_mask\n"); - goto out; - } - - if (!read_coredump_req(fd_coredump, &req)) { - fprintf(stderr, "socket_request_unknown_flag: read_coredump_req failed\n"); - goto out; - } - - if (!check_coredump_req(&req)) { - fprintf(stderr, "socket_request_unknown_flag: check_coredump_req failed\n"); - goto out; - } - - if (!send_coredump_ack(fd_coredump, &req, (1ULL << 63), 0)) { - fprintf(stderr, "socket_request_unknown_flag: send_coredump_ack failed\n"); - goto out; - } - - if (!read_marker(fd_coredump, COREDUMP_MARK_UNSUPPORTED)) { - fprintf(stderr, "socket_request_unknown_flag: read_marker COREDUMP_MARK_UNSUPPORTED failed\n"); - goto out; - } - - exit_code = EXIT_SUCCESS; - fprintf(stderr, "socket_request_unknown_flag: completed successfully\n"); -out: - if (fd_peer_pidfd >= 0) - close(fd_peer_pidfd); - if (fd_coredump >= 0) - close(fd_coredump); - if (fd_server >= 0) - close(fd_server); - _exit(exit_code); - } - self->pid_coredump_server = pid_coredump_server; - - EXPECT_EQ(close(ipc_sockets[1]), 0); - ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); - EXPECT_EQ(close(ipc_sockets[0]), 0); - - pid = fork(); - ASSERT_GE(pid, 0); - if (pid == 0) - crashing_child(); - - pidfd = sys_pidfd_open(pid, 0); - ASSERT_GE(pidfd, 0); - - waitpid(pid, &status, 0); - ASSERT_TRUE(WIFSIGNALED(status)); - ASSERT_FALSE(WCOREDUMP(status)); + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .mask = ack_mask, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_CONFLICTING, + }; + + check_refused_ack(_metadata, self, &refused); +} - ASSERT_TRUE(get_pidfd_info(pidfd, &info)); - ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); - ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); +/* More than one of KERNEL, USERSPACE and REJECT. */ +TEST_F(coredump, socket_request_invalid_flag_combination) +{ + check_conflicting_ack(_metadata, self, + COREDUMP_KERNEL | COREDUMP_REJECT | COREDUMP_WAIT); +} - wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +/* A flag the kernel didn't advertise in coredump_req->mask. */ +TEST_F(coredump, socket_request_unknown_flag) +{ + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .mask = 1ULL << 63, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_UNSUPPORTED, + }; + + check_refused_ack(_metadata, self, &refused); } +/* An ack smaller than the first published struct. */ TEST_F(coredump, socket_request_invalid_size_small) { - int pidfd, ret, status; - pid_t pid, pid_coredump_server; - struct pidfd_info info = {}; - int ipc_sockets[2]; - char c; - - ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); - - ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); - ASSERT_EQ(ret, 0); - - pid_coredump_server = fork(); - ASSERT_GE(pid_coredump_server, 0); - if (pid_coredump_server == 0) { - struct coredump_req req = {}; - int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; - int exit_code = EXIT_FAILURE; - - close(ipc_sockets[0]); - - fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); - if (fd_server < 0) { - fprintf(stderr, "socket_request_invalid_size_small: create_and_listen_unix_socket failed: %m\n"); - goto out; - } - - if (write_nointr(ipc_sockets[1], "1", 1) < 0) { - fprintf(stderr, "socket_request_invalid_size_small: write_nointr to ipc socket failed: %m\n"); - goto out; - } - - close(ipc_sockets[1]); - - fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); - if (fd_coredump < 0) { - fprintf(stderr, "socket_request_invalid_size_small: accept4 failed: %m\n"); - goto out; - } - - fd_peer_pidfd = get_peer_pidfd(fd_coredump); - if (fd_peer_pidfd < 0) { - fprintf(stderr, "socket_request_invalid_size_small: get_peer_pidfd failed\n"); - goto out; - } - - if (!get_pidfd_info(fd_peer_pidfd, &info)) { - fprintf(stderr, "socket_request_invalid_size_small: get_pidfd_info failed\n"); - goto out; - } - - if (!(info.mask & PIDFD_INFO_COREDUMP)) { - fprintf(stderr, "socket_request_invalid_size_small: PIDFD_INFO_COREDUMP not set in mask\n"); - goto out; - } - - if (!(info.coredump_mask & PIDFD_COREDUMPED)) { - fprintf(stderr, "socket_request_invalid_size_small: PIDFD_COREDUMPED not set in coredump_mask\n"); - goto out; - } - - if (!read_coredump_req(fd_coredump, &req)) { - fprintf(stderr, "socket_request_invalid_size_small: read_coredump_req failed\n"); - goto out; - } - - if (!check_coredump_req(&req)) { - fprintf(stderr, "socket_request_invalid_size_small: check_coredump_req failed\n"); - goto out; - } - - if (!send_coredump_ack(fd_coredump, &req, - COREDUMP_REJECT | COREDUMP_WAIT, - COREDUMP_ACK_SIZE_VER0 / 2)) { - fprintf(stderr, "socket_request_invalid_size_small: send_coredump_ack failed\n"); - goto out; - } - - if (!read_marker(fd_coredump, COREDUMP_MARK_MINSIZE)) { - fprintf(stderr, "socket_request_invalid_size_small: read_marker COREDUMP_MARK_MINSIZE failed\n"); - goto out; - } - - exit_code = EXIT_SUCCESS; - fprintf(stderr, "socket_request_invalid_size_small: completed successfully\n"); -out: - if (fd_peer_pidfd >= 0) - close(fd_peer_pidfd); - if (fd_coredump >= 0) - close(fd_coredump); - if (fd_server >= 0) - close(fd_server); - _exit(exit_code); - } - self->pid_coredump_server = pid_coredump_server; - - EXPECT_EQ(close(ipc_sockets[1]), 0); - ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); - EXPECT_EQ(close(ipc_sockets[0]), 0); - - pid = fork(); - ASSERT_GE(pid, 0); - if (pid == 0) - crashing_child(); - - pidfd = sys_pidfd_open(pid, 0); - ASSERT_GE(pidfd, 0); - - waitpid(pid, &status, 0); - ASSERT_TRUE(WIFSIGNALED(status)); - ASSERT_FALSE(WCOREDUMP(status)); - - ASSERT_TRUE(get_pidfd_info(pidfd, &info)); - ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); - ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); - - wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + struct refused_ack refused = { + .ack = { + .size = COREDUMP_ACK_SIZE_VER0 / 2, + .mask = COREDUMP_REJECT | COREDUMP_WAIT, + }, + .bytes = COREDUMP_ACK_SIZE_VER0 / 2, + .mark = COREDUMP_MARK_MINSIZE, + }; + + check_refused_ack(_metadata, self, &refused); } +/* An ack bigger than the kernel said it accepts. */ TEST_F(coredump, socket_request_invalid_size_large) { - int pidfd, ret, status; - pid_t pid, pid_coredump_server; - struct pidfd_info info = {}; - int ipc_sockets[2]; - char c; - - ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); - - ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); - ASSERT_EQ(ret, 0); - - pid_coredump_server = fork(); - ASSERT_GE(pid_coredump_server, 0); - if (pid_coredump_server == 0) { - struct coredump_req req = {}; - int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; - int exit_code = EXIT_FAILURE; - - close(ipc_sockets[0]); - - fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); - if (fd_server < 0) { - fprintf(stderr, "socket_request_invalid_size_large: create_and_listen_unix_socket failed: %m\n"); - goto out; - } - - if (write_nointr(ipc_sockets[1], "1", 1) < 0) { - fprintf(stderr, "socket_request_invalid_size_large: write_nointr to ipc socket failed: %m\n"); - goto out; - } - - close(ipc_sockets[1]); - - fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); - if (fd_coredump < 0) { - fprintf(stderr, "socket_request_invalid_size_large: accept4 failed: %m\n"); - goto out; - } - - fd_peer_pidfd = get_peer_pidfd(fd_coredump); - if (fd_peer_pidfd < 0) { - fprintf(stderr, "socket_request_invalid_size_large: get_peer_pidfd failed\n"); - goto out; - } - - if (!get_pidfd_info(fd_peer_pidfd, &info)) { - fprintf(stderr, "socket_request_invalid_size_large: get_pidfd_info failed\n"); - goto out; - } - - if (!(info.mask & PIDFD_INFO_COREDUMP)) { - fprintf(stderr, "socket_request_invalid_size_large: PIDFD_INFO_COREDUMP not set in mask\n"); - goto out; - } - - if (!(info.coredump_mask & PIDFD_COREDUMPED)) { - fprintf(stderr, "socket_request_invalid_size_large: PIDFD_COREDUMPED not set in coredump_mask\n"); - goto out; - } - - if (!read_coredump_req(fd_coredump, &req)) { - fprintf(stderr, "socket_request_invalid_size_large: read_coredump_req failed\n"); - goto out; - } - - if (!check_coredump_req(&req)) { - fprintf(stderr, "socket_request_invalid_size_large: check_coredump_req failed\n"); - goto out; - } - - if (!send_coredump_ack(fd_coredump, &req, - COREDUMP_REJECT | COREDUMP_WAIT, - COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE)) { - fprintf(stderr, "socket_request_invalid_size_large: send_coredump_ack failed\n"); - goto out; - } - - if (!read_marker(fd_coredump, COREDUMP_MARK_MAXSIZE)) { - fprintf(stderr, "socket_request_invalid_size_large: read_marker COREDUMP_MARK_MAXSIZE failed\n"); - goto out; - } - - exit_code = EXIT_SUCCESS; - fprintf(stderr, "socket_request_invalid_size_large: completed successfully\n"); -out: - if (fd_peer_pidfd >= 0) - close(fd_peer_pidfd); - if (fd_coredump >= 0) - close(fd_coredump); - if (fd_server >= 0) - close(fd_server); - _exit(exit_code); - } - self->pid_coredump_server = pid_coredump_server; - - EXPECT_EQ(close(ipc_sockets[1]), 0); - ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); - EXPECT_EQ(close(ipc_sockets[0]), 0); - - pid = fork(); - ASSERT_GE(pid, 0); - if (pid == 0) - crashing_child(); - - pidfd = sys_pidfd_open(pid, 0); - ASSERT_GE(pidfd, 0); - - waitpid(pid, &status, 0); - ASSERT_TRUE(WIFSIGNALED(status)); - ASSERT_FALSE(WCOREDUMP(status)); - - ASSERT_TRUE(get_pidfd_info(pidfd, &info)); - ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); - ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); - - wait_and_check_coredump_server(pid_coredump_server, _metadata, self); + struct refused_ack refused = { + .ack = { + .size = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE, + .mask = COREDUMP_REJECT | COREDUMP_WAIT, + }, + .bytes = COREDUMP_ACK_SIZE_VER0 + PAGE_SIZE, + .mark = COREDUMP_MARK_MAXSIZE, + }; + + check_refused_ack(_metadata, self, &refused); } /* @@ -2038,92 +1736,6 @@ TEST_F(coredump, socket_request_sparse_blob_upload) EXPECT_EQ(close(fd_core_file), 0); } -/* Ack @ack_mask, expect the kernel to refuse it as conflicting. */ -static void check_conflicting_ack(struct __test_metadata *const _metadata, - FIXTURE_DATA(coredump) *self, __u64 ack_mask) -{ - int pidfd, status; - pid_t pid, pid_coredump_server; - struct pidfd_info info = {}; - int ipc_sockets[2]; - char c; - - ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); - ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); - - pid_coredump_server = fork(); - ASSERT_GE(pid_coredump_server, 0); - if (pid_coredump_server == 0) { - int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; - int exit_code = EXIT_FAILURE; - struct coredump_req req = {}; - - close(ipc_sockets[0]); - - fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); - if (fd_server < 0) - goto out; - - if (write_nointr(ipc_sockets[1], "1", 1) < 0) - goto out; - - close(ipc_sockets[1]); - - fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); - if (fd_coredump < 0) - goto out; - - fd_peer_pidfd = get_peer_pidfd(fd_coredump); - if (fd_peer_pidfd < 0) - goto out; - - if (!read_coredump_req(fd_coredump, &req)) - goto out; - - if (!check_coredump_req(&req)) - goto out; - - if (!send_coredump_ack(fd_coredump, &req, ack_mask, 0)) - goto out; - - if (!read_marker(fd_coredump, COREDUMP_MARK_CONFLICTING)) - goto out; - - exit_code = EXIT_SUCCESS; -out: - if (fd_peer_pidfd >= 0) - close(fd_peer_pidfd); - if (fd_coredump >= 0) - close(fd_coredump); - if (fd_server >= 0) - close(fd_server); - _exit(exit_code); - } - self->pid_coredump_server = pid_coredump_server; - - EXPECT_EQ(close(ipc_sockets[1]), 0); - ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); - EXPECT_EQ(close(ipc_sockets[0]), 0); - - pid = fork(); - ASSERT_GE(pid, 0); - if (pid == 0) - crashing_child(); - - pidfd = sys_pidfd_open(pid, 0); - ASSERT_GE(pidfd, 0); - - waitpid(pid, &status, 0); - ASSERT_TRUE(WIFSIGNALED(status)); - ASSERT_FALSE(WCOREDUMP(status)); - - ASSERT_TRUE(get_pidfd_info(pidfd, &info)); - ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); - ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); - - wait_and_check_coredump_server(pid_coredump_server, _metadata, self); -} - /* COREDUMP_RECORDS applies to a coredump the kernel writes, nothing else. */ TEST_F(coredump, socket_request_records_without_kernel) { diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index d7cc448eeaf442..9aa901e14f02ee 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1354,8 +1354,8 @@ bool read_coredump_req(int fd, struct coredump_req *req) return true; } -bool send_coredump_ack(int fd, const struct coredump_req *req, - __u64 mask, size_t size_ack) +/* Send @len bytes of @ack as they are, more than the struct if asked to. */ +bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len) { ssize_t ret; /* @@ -1367,23 +1367,36 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, char buffer[PAGE_SIZE]; } large_ack = {}; - if (!size_ack) - size_ack = sizeof(struct coredump_ack) < req->size_ack ? - sizeof(struct coredump_ack) : - req->size_ack; - large_ack.ack.mask = mask; - large_ack.ack.size = size_ack; - ret = send(fd, &large_ack, size_ack, MSG_NOSIGNAL); - if (ret != size_ack) { + if (len > sizeof(large_ack)) + return false; + + large_ack.ack = *ack; + ret = send(fd, &large_ack, len, MSG_NOSIGNAL); + if (ret != len) { fprintf(stderr, "%s: short send %zd: %m\n", __func__, ret); return false; } - fprintf(stderr, "Sent coredump ack with size %zu and mask 0x%llx\n", - size_ack, (unsigned long long)mask); + fprintf(stderr, "Sent %zu bytes of coredump ack: size %u, mask 0x%llx\n", + len, ack->size, (unsigned long long)ack->mask); return true; } +bool send_coredump_ack(int fd, const struct coredump_req *req, + __u64 mask, size_t size_ack) +{ + struct coredump_ack ack = { + .mask = mask, + }; + + if (!size_ack) + size_ack = sizeof(struct coredump_ack) < req->size_ack ? + sizeof(struct coredump_ack) : + req->size_ack; + ack.size = size_ack; + return send_coredump_ack_bytes(fd, &ack, size_ack); +} + /* Every option the kernel is expected to advertise in coredump_req->mask. */ #define TEST_REQ_MASK_ALL \ (COREDUMP_KERNEL | COREDUMP_USERSPACE | \ diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index 97ad5cfeae928d..0970d3550fc1db 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -46,6 +46,7 @@ bool read_marker(int fd, enum coredump_mark mark); bool read_coredump_req(int fd, struct coredump_req *req); bool send_coredump_ack(int fd, const struct coredump_req *req, __u64 mask, size_t size_ack); +bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len); bool check_coredump_req(const struct coredump_req *req); int open_coredump_tmpfile(int fd_tmpfs_detached); void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file); From b85fd05158c603ab0e591e1722c92e20ab02658a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:05 +0200 Subject: [PATCH 0710/2286] selftests/coredump: test COREDUMP_MEMORY_TYPES Test the new COREDUMP_MEMORY_TYPES flag. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-4-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 349 ++++++++++++++++++ .../coredump/coredump_test_helpers.c | 203 +++++++++- .../coredump/coredump_test_helpers.h | 23 ++ 3 files changed, 568 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index a07546e796517e..6dcd6c15a5653c 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -1932,4 +1932,353 @@ TEST_F(coredump, socket_request_stream_choice_large) ASSERT_LT(choice.received, choice.size / 8); } +/* What a memory types test asks of the kernel and what it expects back. */ +struct memory_choice { + /* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */ + __u64 task_filter; + /* The ack. */ + __u64 mask; + __u64 memory_types; + size_t size_ack; + /* The shared mapping is in the coredump with all of its memory. */ + bool shared_dumped; + /* No memory at all. Pull a page from /proc//mem instead. */ + bool skeleton; +}; + +/* A skeleton still carries the vdso and friends, nothing bigger. */ +#define SKELETON_DATA_PAGES 16 + +/* + * The crashing child maps shared anonymous memory and tells the server + * where. The server acks with @choice and checks whether that mapping's + * segment in the coredump carries its memory. + */ +static void check_memory_dump(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, + const struct memory_choice *choice) +{ + int pidfd, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + int addr_pipe[2]; + char c; + + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + ASSERT_EQ(pipe(addr_pipe), 0); + ASSERT_TRUE(set_core_pattern("@@/tmp/coredump.socket")); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_file = -1; + int exit_code = EXIT_FAILURE; + struct coredump_req req = {}; + __u64 task_filter; + ElfW(Phdr) segment; + ssize_t received; + off_t size; + char *addr; + + close(ipc_sockets[0]); + close(addr_pipe[1]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) + goto out; + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) + goto out; + + fd_file = open_coredump_tmpfile(self->fd_tmpfs_detached); + if (fd_file < 0) + goto out; + + if (!read_coredump_req(fd_coredump, &req)) + goto out; + + if (!check_coredump_req(&req)) + goto out; + + /* The request reports the memory types the task selected. */ + if (!peer_coredump_filter(fd_peer_pidfd, &task_filter)) + goto out; + + if (req.memory_types != task_filter) { + fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n", + (unsigned long long)req.memory_types, + (unsigned long long)task_filter); + goto out; + } + + if (choice->task_filter != FILTER_TASK_INHERIT && + task_filter != choice->task_filter) { + fprintf(stderr, "Task selected 0x%llx, child asked for 0x%llx\n", + (unsigned long long)task_filter, + (unsigned long long)choice->task_filter); + goto out; + } + + /* The child sent the address of its mapping before it crashed. */ + if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr)) + goto out; + + if (!send_coredump_ack_types(fd_coredump, &req, choice->mask, + choice->memory_types, + choice->size_ack)) + goto out; + + if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) + goto out; + + if (choice->mask & COREDUMP_RECORDS) + received = recv_coredump_records(fd_coredump, fd_file, + &size, NULL, -1); + else + received = recv_coredump_bytes(fd_coredump, fd_file); + if (received < 0) + goto out; + + if (!is_elf_core(fd_file)) + goto out; + + /* A dump ending in holes or empty segments must still be whole. */ + if (!check_coredump_extent(fd_file)) + goto out; + + if (!find_coredump_segment(fd_file, (__u64)(uintptr_t)addr, &segment)) + goto out; + + if (segment.p_memsz != MEMORY_MAPPING_SIZE) { + fprintf(stderr, "Segment spans %llu bytes, the mapping %u\n", + (unsigned long long)segment.p_memsz, + MEMORY_MAPPING_SIZE); + goto out; + } + + if (segment.p_filesz != (choice->shared_dumped ? segment.p_memsz : 0)) { + fprintf(stderr, "Segment carries %llu bytes, expected %s of them\n", + (unsigned long long)segment.p_filesz, + choice->shared_dumped ? "all" : "none"); + goto out; + } + + if (choice->skeleton) { + __u64 data, notes, data_max; + char buf[PAGE_SIZE]; + + if (!sum_coredump_segments(fd_file, &data, ¬es)) + goto out; + + data_max = SKELETON_DATA_PAGES * sysconf(_SC_PAGESIZE); + if (!notes || data > data_max) { + fprintf(stderr, "Skeleton has %llu note and %llu memory bytes\n", + (unsigned long long)notes, + (unsigned long long)data); + goto out; + } + + /* The task is parked in COREDUMP_WAIT with its memory. */ + if (peer_read_mem(fd_peer_pidfd, (__u64)(uintptr_t)addr, + buf, sizeof(buf)) != sizeof(buf)) + goto out; + + if (buf[0] != 'x') { + fprintf(stderr, "Pulled memory lacks the child's mark\n"); + goto out; + } + + fprintf(stderr, "Skeleton of %zd bytes, pulled %zu bytes of memory\n", + received, sizeof(buf)); + } + + exit_code = EXIT_SUCCESS; +out: + close(addr_pipe[0]); + if (fd_file >= 0) + close(fd_file); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + EXPECT_EQ(close(addr_pipe[0]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_memory(choice->task_filter, addr_pipe[1]); + EXPECT_EQ(close(addr_pipe[1]), 0); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0); + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +} + +/* Without COREDUMP_MEMORY_TYPES the task's own selection decides. */ +TEST_F(coredump, socket_request_memory_types_task_includes) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .mask = COREDUMP_KERNEL, + .shared_dumped = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +TEST_F(coredump, socket_request_memory_types_task_excludes) +{ + struct memory_choice choice = { + .task_filter = 0, + .mask = COREDUMP_KERNEL, + .shared_dumped = false, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* The server drops a memory type the task would have dumped. */ +TEST_F(coredump, socket_request_memory_types_restricts) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE, + .shared_dumped = false, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* The server adds a memory type the task had excluded. */ +TEST_F(coredump, socket_request_memory_types_widens) +{ + struct memory_choice choice = { + .task_filter = 0, + .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .shared_dumped = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* The memory types decide what goes into a record stream just the same. */ +TEST_F(coredump, socket_request_memory_types_records) +{ + struct memory_choice choice = { + .task_filter = FILTER_TASK_INHERIT, + .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE, + .shared_dumped = false, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* + * An empty selection leaves a skeleton: every program header and every note + * but no memory. A server that wants to pick the memory itself reads it + * from /proc//mem while the task waits for it to finish. + */ +TEST_F(coredump, socket_request_memory_types_skeleton) +{ + struct memory_choice choice = { + .task_filter = FILTER_TASK_INHERIT, + .mask = COREDUMP_KERNEL | COREDUMP_WAIT | COREDUMP_MEMORY_TYPES, + .memory_types = 0, + .shared_dumped = false, + .skeleton = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* A memory type the kernel didn't advertise in memory_types_mask. */ +TEST_F(coredump, socket_request_memory_types_unknown_bit) +{ + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES, + .memory_types = 1ULL << 63, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_UNSUPPORTED, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* The memory types must be zero unless COREDUMP_MEMORY_TYPES is raised. */ +TEST_F(coredump, socket_request_memory_types_stale_field) +{ + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .mask = COREDUMP_KERNEL, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_UNSUPPORTED, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* COREDUMP_MEMORY_TYPES needs an ack that has the memory types. */ +TEST_F(coredump, socket_request_memory_types_short_ack) +{ + struct refused_ack refused = { + .ack = { + .size = COREDUMP_ACK_SIZE_VER0, + .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES, + }, + .bytes = COREDUMP_ACK_SIZE_VER0, + .mark = COREDUMP_MARK_MINSIZE, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* The memory types select what the kernel writes, nothing else. */ +TEST_F(coredump, socket_request_memory_types_without_kernel) +{ + check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 9aa901e14f02ee..7ae0c6c458aa13 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,48 @@ void crashing_child_sparse(size_t size) *(volatile int *)NULL = 0; } +/* Select @types through the caller's own /proc/self/coredump_filter. */ +static bool set_coredump_filter(__u64 types) +{ + char buf[32]; + int fd, len; + bool ok; + + fd = open("/proc/self/coredump_filter", O_WRONLY | O_CLOEXEC); + if (fd < 0) + return false; + + len = snprintf(buf, sizeof(buf), "0x%llx", (unsigned long long)types); + ok = write_nointr(fd, buf, len) == len; + close(fd); + return ok; +} + +/* + * Map shared anonymous memory, touch it, tell the server where it is and + * crash. A @task_filter other than FILTER_TASK_INHERIT is selected first. + */ +void crashing_child_memory(__u64 task_filter, int fd_addr) +{ + char *p; + + if (task_filter != FILTER_TASK_INHERIT && !set_coredump_filter(task_filter)) + _exit(EXIT_FAILURE); + + p = mmap(NULL, MEMORY_MAPPING_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) + _exit(EXIT_FAILURE); + p[0] = 'x'; + + if (write_nointr(fd_addr, &p, sizeof(p)) != sizeof(p)) + _exit(EXIT_FAILURE); + close(fd_addr); + + /* crash on purpose */ + *(volatile int *)NULL = 0; +} + /* Sink a reassembled record stream is handed to, record by record. */ struct coredump_record_sink { /* @len bytes of coredump data that belong at @offset. */ @@ -916,6 +959,82 @@ static const ElfW(Phdr) *find_segment(const ElfW(Phdr) *phdr, size_t nr, return NULL; } +/* The PT_LOAD segment @vaddr falls into. */ +bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment) +{ + const ElfW(Phdr) *found; + ElfW(Phdr) *phdr; + size_t nr; + + phdr = read_phdrs(fd, &nr); + if (!phdr) + return false; + + found = find_segment(phdr, nr, vaddr); + if (found) + *segment = *found; + else + fprintf(stderr, "%s: no segment for 0x%llx\n", __func__, + (unsigned long long)vaddr); + + free(phdr); + return found; +} + +/* How many bytes the PT_LOAD and the PT_NOTE segments of @fd carry. */ +bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes) +{ + ElfW(Phdr) *phdr; + size_t nr, i; + + phdr = read_phdrs(fd, &nr); + if (!phdr) + return false; + + *data = 0; + *notes = 0; + for (i = 0; i < nr; i++) { + if (phdr[i].p_type == PT_LOAD) + *data += phdr[i].p_filesz; + else if (phdr[i].p_type == PT_NOTE) + *notes += phdr[i].p_filesz; + } + + free(phdr); + return true; +} + +/* The coredump in @fd is at least as long as every segment it declares. */ +bool check_coredump_extent(int fd) +{ + ElfW(Phdr) *phdr; + struct stat st; + size_t nr, i; + bool ok = true; + + if (fstat(fd, &st)) { + fprintf(stderr, "%s: fstat: %m\n", __func__); + return false; + } + + phdr = read_phdrs(fd, &nr); + if (!phdr) + return false; + + for (i = 0; i < nr; i++) { + if (phdr[i].p_offset + phdr[i].p_filesz <= (__u64)st.st_size) + continue; + fprintf(stderr, "%s: segment %zu ends at %llu, the coredump at %llu\n", + __func__, i, + (unsigned long long)(phdr[i].p_offset + phdr[i].p_filesz), + (unsigned long long)st.st_size); + ok = false; + } + + free(phdr); + return ok; +} + /* The next stretch of memory the segments cover, split ones merged back. */ static bool next_range(const ElfW(Phdr) *phdr, size_t nr, size_t *i, __u64 *start, __u64 *end) @@ -1250,6 +1369,62 @@ ssize_t peer_vm_size(int fd_peer_pidfd) /* Protocol helper functions */ +/* The peer's /proc//coredump_filter, which is in memory types. */ +bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types) +{ + struct pidfd_info info = {}; + unsigned long value; + char path[64]; + FILE *f; + int ret; + + if (!get_pidfd_info(fd_peer_pidfd, &info)) + return false; + + snprintf(path, sizeof(path), "/proc/%d/coredump_filter", info.pid); + f = fopen(path, "r"); + if (!f) { + fprintf(stderr, "%s: %s: %m\n", __func__, path); + return false; + } + + ret = fscanf(f, "%lx", &value); + fclose(f); + if (ret != 1) { + fprintf(stderr, "%s: %s: no value\n", __func__, path); + return false; + } + + *memory_types = value; + return true; +} + +/* Read @len bytes at @addr from the peer's /proc//mem. */ +ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len) +{ + struct pidfd_info info = {}; + char path[64]; + ssize_t ret; + int fd; + + if (!get_pidfd_info(fd_peer_pidfd, &info)) + return -1; + + snprintf(path, sizeof(path), "/proc/%d/mem", info.pid); + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "%s: %s: %m\n", __func__, path); + return -1; + } + + ret = pread(fd, buf, len, addr); + if (ret < 0) + fprintf(stderr, "%s: %s at 0x%llx: %m\n", __func__, path, + (unsigned long long)addr); + close(fd); + return ret; +} + ssize_t recv_marker(int fd) { enum coredump_mark mark = COREDUMP_MARK_REQACK; @@ -1377,16 +1552,18 @@ bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len) return false; } - fprintf(stderr, "Sent %zu bytes of coredump ack: size %u, mask 0x%llx\n", - len, ack->size, (unsigned long long)ack->mask); + fprintf(stderr, "Sent %zu bytes of coredump ack: size %u, mask 0x%llx, types 0x%llx\n", + len, ack->size, (unsigned long long)ack->mask, + (unsigned long long)ack->memory_types); return true; } -bool send_coredump_ack(int fd, const struct coredump_req *req, - __u64 mask, size_t size_ack) +bool send_coredump_ack_types(int fd, const struct coredump_req *req, + __u64 mask, __u64 memory_types, size_t size_ack) { struct coredump_ack ack = { .mask = mask, + .memory_types = memory_types, }; if (!size_ack) @@ -1397,17 +1574,23 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, return send_coredump_ack_bytes(fd, &ack, size_ack); } +bool send_coredump_ack(int fd, const struct coredump_req *req, + __u64 mask, size_t size_ack) +{ + return send_coredump_ack_types(fd, req, mask, 0, size_ack); +} + /* Every option the kernel is expected to advertise in coredump_req->mask. */ #define TEST_REQ_MASK_ALL \ (COREDUMP_KERNEL | COREDUMP_USERSPACE | \ COREDUMP_REJECT | COREDUMP_WAIT | \ - COREDUMP_RECORDS | COREDUMP_SPARSE) + COREDUMP_RECORDS | COREDUMP_SPARSE | COREDUMP_MEMORY_TYPES) bool check_coredump_req(const struct coredump_req *req) { - if (req->size < COREDUMP_REQ_SIZE_VER0) { + if (req->size < COREDUMP_REQ_SIZE_VER1) { fprintf(stderr, "%s: size %u below minimum %d\n", - __func__, req->size, COREDUMP_REQ_SIZE_VER0); + __func__, req->size, COREDUMP_REQ_SIZE_VER1); return false; } if (req->mask != TEST_REQ_MASK_ALL) { @@ -1416,6 +1599,12 @@ bool check_coredump_req(const struct coredump_req *req) (unsigned long long)TEST_REQ_MASK_ALL); return false; } + if (req->memory_types_mask != TEST_MEMORY_ALL) { + fprintf(stderr, "%s: memory_types_mask 0x%llx, expected 0x%llx\n", + __func__, (unsigned long long)req->memory_types_mask, + (unsigned long long)TEST_MEMORY_ALL); + return false; + } return true; } diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index 0970d3550fc1db..fc21b8620359ed 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -3,6 +3,7 @@ #ifndef __COREDUMP_TEST_HELPERS_H #define __COREDUMP_TEST_HELPERS_H +#include #include #include #include @@ -21,10 +22,30 @@ /* A task mapping at least this much is worth a record stream. */ #define SPARSE_STREAM_THRESHOLD (SPARSE_MAPPING_SIZE / 2) +/* Size of the shared anonymous mapping the memory types tests map. */ +#define MEMORY_MAPPING_SIZE (4 * 1024 * 1024) + +/* Leave the coredump_filter the crashing child inherited alone. */ +#define FILTER_TASK_INHERIT ((__u64)-1) + +/* Every memory type the kernel is expected to advertise. */ +#define TEST_MEMORY_ALL \ + (COREDUMP_MEMORY_ANON_PRIVATE | COREDUMP_MEMORY_ANON_SHARED | \ + COREDUMP_MEMORY_FILE_PRIVATE | COREDUMP_MEMORY_FILE_SHARED | \ + COREDUMP_MEMORY_ELF_HEADERS | \ + COREDUMP_MEMORY_HUGETLB_PRIVATE | COREDUMP_MEMORY_HUGETLB_SHARED | \ + COREDUMP_MEMORY_DAX_PRIVATE | COREDUMP_MEMORY_DAX_SHARED) + /* Shared helper function declarations */ void *do_nothing(void *arg); void crashing_child(void); void crashing_child_sparse(size_t size); +void crashing_child_memory(__u64 task_filter, int fd_addr); +bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment); +bool sum_coredump_segments(int fd, __u64 *data, __u64 *notes); +bool check_coredump_extent(int fd); +bool peer_coredump_filter(int fd_peer_pidfd, __u64 *memory_types); +ssize_t peer_read_mem(int fd_peer_pidfd, __u64 addr, void *buf, size_t len); ssize_t recv_coredump_records(int fd_coredump, int fd_core_file, off_t *coredump_size, bool *truncated, int fd_peer_pidfd); @@ -46,6 +67,8 @@ bool read_marker(int fd, enum coredump_mark mark); bool read_coredump_req(int fd, struct coredump_req *req); bool send_coredump_ack(int fd, const struct coredump_req *req, __u64 mask, size_t size_ack); +bool send_coredump_ack_types(int fd, const struct coredump_req *req, + __u64 mask, __u64 memory_types, size_t size_ack); bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len); bool check_coredump_req(const struct coredump_req *req); int open_coredump_tmpfile(int fd_tmpfs_detached); From 9375d944aa3b7ce9ca81d8f213b751d774f81774 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:06 +0200 Subject: [PATCH 0711/2286] selftests/coredump: improve coredump size negotiation tests Improve the size handling tests when negotiating a coredump through req and ack. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-5-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 158 ++++++++++++++++-- .../coredump/coredump_test_helpers.c | 17 +- .../coredump/coredump_test_helpers.h | 1 + 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index 6dcd6c15a5653c..6c7327832d444d 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -1932,11 +1932,74 @@ TEST_F(coredump, socket_request_stream_choice_large) ASSERT_LT(choice.received, choice.size / 8); } +/* What a coredump server was built with. */ +struct server_build { + /* sizeof(struct coredump_req) and sizeof(struct coredump_ack) back then. */ + size_t req_size; + size_t ack_size; + /* The features it raises if the kernel offers them. */ + __u64 wants; + /* Its policy: what it drops from and adds to the task's selection. */ + __u64 drop; + __u64 add; +}; + +/* A server from when the structs were first published: kernel-written dumps. */ +static const struct server_build server_build_ver0 = { + .req_size = COREDUMP_REQ_SIZE_VER0, + .ack_size = COREDUMP_ACK_SIZE_VER0, + .wants = COREDUMP_KERNEL, +}; + +/* A server built against this header: no shared memory, always the ELF headers. */ +static const struct server_build server_build_ver1 = { + .req_size = sizeof(struct coredump_req), + .ack_size = sizeof(struct coredump_ack), + .wants = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .drop = COREDUMP_MEMORY_ANON_SHARED | COREDUMP_MEMORY_FILE_SHARED, + .add = COREDUMP_MEMORY_ELF_HEADERS, +}; + +/* + * Build the ack the way a server does: from what the kernel offers, what + * this build implements, and what fits in the ack the kernel accepts. + * Fields the build never read are zero and never consulted. + */ +static void negotiate(const struct coredump_req *req, + const struct server_build *build, + struct coredump_ack *ack) +{ + __u64 offered = req->mask & build->wants; + + memset(ack, 0, sizeof(*ack)); + ack->size = build->ack_size < req->size_ack ? build->ack_size : req->size_ack; + /* These builds only ever have the kernel write the coredump. */ + ack->mask = COREDUMP_KERNEL; + + /* Sparse needs records, records need the kernel to write. */ + if (offered & COREDUMP_RECORDS) { + ack->mask |= COREDUMP_RECORDS; + if (offered & COREDUMP_SPARSE) + ack->mask |= COREDUMP_SPARSE; + } + + /* The memory types need an ack that carries them. */ + if ((offered & COREDUMP_MEMORY_TYPES) && ack->size >= COREDUMP_ACK_SIZE_VER1) { + ack->mask |= COREDUMP_MEMORY_TYPES; + /* Start from the task's selection; only advertised types pass. */ + ack->memory_types = (req->memory_types & ~build->drop) | build->add; + ack->memory_types &= req->memory_types_mask; + } +} + /* What a memory types test asks of the kernel and what it expects back. */ struct memory_choice { /* Memory types the crashing child selects, or FILTER_TASK_INHERIT. */ __u64 task_filter; - /* The ack. */ + /* Negotiate the ack as this server build, NULL to send it as given. */ + const struct server_build *build; + /* The ack, or what the negotiation must arrive at. */ __u64 mask; __u64 memory_types; size_t size_ack; @@ -1976,6 +2039,13 @@ static void check_memory_dump(struct __test_metadata *const _metadata, int fd_file = -1; int exit_code = EXIT_FAILURE; struct coredump_req req = {}; + struct coredump_ack ack = { + .size = choice->size_ack, + .mask = choice->mask, + .memory_types = choice->memory_types, + }; + /* How much of the request this server reads. */ + size_t req_size = choice->build ? choice->build->req_size : sizeof(req); __u64 task_filter; ElfW(Phdr) segment; ssize_t received; @@ -2006,21 +2076,24 @@ static void check_memory_dump(struct __test_metadata *const _metadata, if (fd_file < 0) goto out; - if (!read_coredump_req(fd_coredump, &req)) + if (!read_coredump_req_sized(fd_coredump, &req, req_size)) goto out; - if (!check_coredump_req(&req)) - goto out; - - /* The request reports the memory types the task selected. */ if (!peer_coredump_filter(fd_peer_pidfd, &task_filter)) goto out; - if (req.memory_types != task_filter) { - fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n", - (unsigned long long)req.memory_types, - (unsigned long long)task_filter); - goto out; + /* A build from before the memory types never read that far. */ + if (req_size >= COREDUMP_REQ_SIZE_VER1) { + if (!check_coredump_req(&req)) + goto out; + + /* The request reports the memory types the task selected. */ + if (req.memory_types != task_filter) { + fprintf(stderr, "Request reports 0x%llx, task selected 0x%llx\n", + (unsigned long long)req.memory_types, + (unsigned long long)task_filter); + goto out; + } } if (choice->task_filter != FILTER_TASK_INHERIT && @@ -2035,15 +2108,28 @@ static void check_memory_dump(struct __test_metadata *const _metadata, if (read_nointr(addr_pipe[0], &addr, sizeof(addr)) != sizeof(addr)) goto out; - if (!send_coredump_ack_types(fd_coredump, &req, choice->mask, - choice->memory_types, - choice->size_ack)) + /* A server build negotiates its ack and must arrive at the choice. */ + if (choice->build) { + negotiate(&req, choice->build, &ack); + + if (ack.size != choice->size_ack || ack.mask != choice->mask || + ack.memory_types != choice->memory_types) { + fprintf(stderr, + "Negotiated %u bytes, mask 0x%llx, types 0x%llx\n", + ack.size, (unsigned long long)ack.mask, + (unsigned long long)ack.memory_types); + goto out; + } + } + + if (!send_coredump_ack_types(fd_coredump, &req, ack.mask, + ack.memory_types, ack.size)) goto out; if (!read_marker(fd_coredump, COREDUMP_MARK_REQACK)) goto out; - if (choice->mask & COREDUMP_RECORDS) + if (ack.mask & COREDUMP_RECORDS) received = recv_coredump_records(fd_coredump, fd_file, &size, NULL, -1); else @@ -2281,4 +2367,46 @@ TEST_F(coredump, socket_request_memory_types_without_kernel) check_conflicting_ack(_metadata, self, COREDUMP_USERSPACE | COREDUMP_MEMORY_TYPES); } +/* + * A server built with the first structs reads the request it knows, + * discards the rest and acks with the ack it knows. It raises nothing + * it wasn't built for and the kernel dumps what the task selected. + */ +TEST_F(coredump, socket_request_negotiate_ver0) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .build = &server_build_ver0, + .mask = COREDUMP_KERNEL, + .memory_types = 0, + .size_ack = COREDUMP_ACK_SIZE_VER0, + .shared_dumped = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* + * A server built against this header takes every feature the kernel + * offers, drops shared memory from what the task selected and adds the + * ELF headers. + */ +TEST_F(coredump, socket_request_negotiate_ver1) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .build = &server_build_ver1, + .mask = COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ELF_HEADERS, + .size_ack = COREDUMP_ACK_SIZE_VER1, + .shared_dumped = false, + }; + + check_memory_dump(_metadata, self, &choice); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 7ae0c6c458aa13..ab94c45cd8be61 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1467,10 +1467,11 @@ bool read_marker(int fd, enum coredump_mark mark) return ret == mark; } -bool read_coredump_req(int fd, struct coredump_req *req) +/* Read the request as a server built with a @user_size byte struct does. */ +bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size) { ssize_t ret; - size_t field_size, user_size, known_size, kernel_size, remaining_size; + size_t field_size, known_size, kernel_size, remaining_size; memset(req, 0, sizeof(*req)); field_size = sizeof(req->size); @@ -1478,25 +1479,24 @@ bool read_coredump_req(int fd, struct coredump_req *req) /* Peek the size of the coredump request. */ ret = recv(fd, req, field_size, MSG_PEEK | MSG_WAITALL); if (ret != field_size) { - fprintf(stderr, "read_coredump_req: peek failed (got %zd, expected %zu): %m\n", + fprintf(stderr, "%s: peek failed (got %zd, expected %zu): %m\n", __func__, ret, field_size); return false; } kernel_size = req->size; if (kernel_size < COREDUMP_REQ_SIZE_VER0) { - fprintf(stderr, "read_coredump_req: kernel_size %zu < min %d\n", + fprintf(stderr, "%s: kernel_size %zu < min %d\n", __func__, kernel_size, COREDUMP_REQ_SIZE_VER0); return false; } if (kernel_size >= PAGE_SIZE) { - fprintf(stderr, "read_coredump_req: kernel_size %zu >= PAGE_SIZE %d\n", + fprintf(stderr, "%s: kernel_size %zu >= PAGE_SIZE %d\n", __func__, kernel_size, PAGE_SIZE); return false; } /* Consume as much of the request as we know about. */ - user_size = sizeof(struct coredump_req); known_size = user_size < kernel_size ? user_size : kernel_size; ret = recv(fd, req, known_size, MSG_WAITALL); if (ret != known_size) @@ -1529,6 +1529,11 @@ bool read_coredump_req(int fd, struct coredump_req *req) return true; } +bool read_coredump_req(int fd, struct coredump_req *req) +{ + return read_coredump_req_sized(fd, req, sizeof(*req)); +} + /* Send @len bytes of @ack as they are, more than the struct if asked to. */ bool send_coredump_ack_bytes(int fd, const struct coredump_ack *ack, size_t len) { diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index fc21b8620359ed..8e0187645c930d 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -65,6 +65,7 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); ssize_t recv_marker(int fd); bool read_marker(int fd, enum coredump_mark mark); bool read_coredump_req(int fd, struct coredump_req *req); +bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size); bool send_coredump_ack(int fd, const struct coredump_req *req, __u64 mask, size_t size_ack); bool send_coredump_ack_types(int fd, const struct coredump_req *req, From c2b24fe7939739468f30d03d1af8e38032d55310 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 21 Aug 2026 13:52:07 +0200 Subject: [PATCH 0712/2286] selftests/coredump: test failed handshakes Add more coredump refusal tests. Link: https://patch.msgid.link/20260821-work-coredump-filter-v1-6-91f9a73ef03e@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../coredump/coredump_socket_protocol_test.c | 211 +++++++++++++++++- .../coredump/coredump_test_helpers.c | 32 ++- .../coredump/coredump_test_helpers.h | 1 + 3 files changed, 238 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c index 6c7327832d444d..f5c9bad8754634 100644 --- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c @@ -510,14 +510,15 @@ TEST_F(coredump, socket_request_reject) /* An ack the kernel must refuse and how. */ struct refused_ack { - /* The ack and how many bytes of it the server sends. */ + /* The ack, and how many bytes of it the server sends before it hangs up. */ struct coredump_ack ack; size_t bytes; - /* The marker the kernel answers with. */ + /* The marker the kernel answers with, or none if @no_marker. */ enum coredump_mark mark; + bool no_marker; }; -/* Send @refused, expect the kernel to refuse it with the marker. */ +/* Send @refused, expect the kernel to refuse it and hang up. */ static void check_refused_ack(struct __test_metadata *const _metadata, FIXTURE_DATA(coredump) *self, const struct refused_ack *refused) @@ -577,7 +578,16 @@ static void check_refused_ack(struct __test_metadata *const _metadata, refused->bytes)) goto out; - if (!read_marker(fd_coredump, refused->mark)) + /* Nothing more to say. A server that died looks the same. */ + if (shutdown(fd_coredump, SHUT_WR)) + goto out; + + if (!refused->no_marker && + !read_marker(fd_coredump, refused->mark)) + goto out; + + /* The kernel hangs up after a refusal, marker or not. */ + if (!read_hangup(fd_coredump)) goto out; exit_code = EXIT_SUCCESS; @@ -2409,4 +2419,197 @@ TEST_F(coredump, socket_request_negotiate_ver1) check_memory_dump(_metadata, self, &choice); } +/* An ack that picks none of KERNEL, USERSPACE and REJECT. */ +TEST_F(coredump, socket_request_no_mode) +{ + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .mask = COREDUMP_WAIT, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_CONFLICTING, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* @spare must be zero, like every field that isn't in use. */ +TEST_F(coredump, socket_request_spare) +{ + struct refused_ack refused = { + .ack = { + .size = sizeof(struct coredump_ack), + .spare = 1, + .mask = COREDUMP_KERNEL, + }, + .bytes = sizeof(struct coredump_ack), + .mark = COREDUMP_MARK_UNSUPPORTED, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* An ack size is a byte count. One that ends inside a field is valid. */ +#define ACK_SIZE_BETWEEN (COREDUMP_ACK_SIZE_VER0 + sizeof(__u32)) + +/* Any size from VER0 up to what the kernel accepts works without memory types. */ +TEST_F(coredump, socket_request_ack_size_between) +{ + struct memory_choice choice = { + .task_filter = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .mask = COREDUMP_KERNEL, + .size_ack = ACK_SIZE_BETWEEN, + .shared_dumped = true, + }; + + check_memory_dump(_metadata, self, &choice); +} + +/* The memory types need the whole field, not the part that happens to fit. */ +TEST_F(coredump, socket_request_memory_types_ack_size_between) +{ + struct refused_ack refused = { + .ack = { + .size = ACK_SIZE_BETWEEN, + .mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES, + }, + .bytes = ACK_SIZE_BETWEEN, + .mark = COREDUMP_MARK_MINSIZE, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* A server that hangs up without acking gets no marker and no coredump. */ +TEST_F(coredump, socket_request_server_hangs_up) +{ + struct refused_ack refused = { + .bytes = 0, + .no_marker = true, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* A server that hangs up in the middle of its ack looks the same. */ +TEST_F(coredump, socket_request_ack_truncated) +{ + struct refused_ack refused = { + .ack = { + .size = COREDUMP_ACK_SIZE_VER0, + .mask = COREDUMP_KERNEL, + }, + .bytes = COREDUMP_ACK_SIZE_VER0 / 2, + .no_marker = true, + }; + + check_refused_ack(_metadata, self, &refused); +} + +/* + * The kernels a server built against this header can't meet here: + * negotiate() against their requests, no coredump involved. + */ + +/* The request of a kernel with the first structs and features. */ +static const struct coredump_req req_ver0 = { + .size = COREDUMP_REQ_SIZE_VER0, + .size_ack = COREDUMP_ACK_SIZE_VER0, + .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | + COREDUMP_REJECT | COREDUMP_WAIT, +}; + +/* The request of this kernel. */ +static const struct coredump_req req_ver1 = { + .size = COREDUMP_REQ_SIZE_VER1, + .size_ack = COREDUMP_ACK_SIZE_VER1, + .mask = COREDUMP_KERNEL | COREDUMP_USERSPACE | + COREDUMP_REJECT | COREDUMP_WAIT | + COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES, + .memory_types = COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ANON_SHARED, + .memory_types_mask = TEST_MEMORY_ALL, +}; + +/* A kernel with the first structs gets the first ack and nothing newer. */ +TEST(negotiate_ver0_kernel) +{ + struct coredump_ack ack; + + negotiate(&req_ver0, &server_build_ver1, &ack); + ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL); + ASSERT_EQ(ack.memory_types, 0); +} + +/* A kernel with records and sparse but the first structs: both, no types. */ +TEST(negotiate_sparse_kernel) +{ + struct coredump_req req = req_ver0; + struct coredump_ack ack; + + req.mask |= COREDUMP_RECORDS | COREDUMP_SPARSE; + negotiate(&req, &server_build_ver1, &ack); + ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE); + ASSERT_EQ(ack.memory_types, 0); +} + +/* Records without sparse: sparse isn't raised on its own. */ +TEST(negotiate_records_without_sparse) +{ + struct coredump_req req = req_ver0; + struct coredump_ack ack; + + req.mask |= COREDUMP_RECORDS; + negotiate(&req, &server_build_ver1, &ack); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS); +} + +/* + * A feature whose ack field lies past what the kernel accepts can't be + * raised. No kernel offers the memory types without the room for them, so a + * request that does stands in for a feature newer than this header. + */ +TEST(negotiate_types_need_room) +{ + struct coredump_req req = req_ver0; + struct coredump_ack ack; + + req.mask |= COREDUMP_MEMORY_TYPES; + negotiate(&req, &server_build_ver1, &ack); + ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL); + ASSERT_EQ(ack.memory_types, 0); +} + +/* This kernel: the policy applied to the task's selection. */ +TEST(negotiate_ver1_kernel) +{ + struct coredump_ack ack; + + negotiate(&req_ver1, &server_build_ver1, &ack); + ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER1); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES); + ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE | + COREDUMP_MEMORY_ELF_HEADERS); +} + +/* A kernel that doesn't know a type the policy adds isn't asked for it. */ +TEST(negotiate_unknown_type) +{ + struct coredump_req req = req_ver1; + struct coredump_ack ack; + + req.memory_types_mask &= ~(__u64)COREDUMP_MEMORY_ELF_HEADERS; + negotiate(&req, &server_build_ver1, &ack); + ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE | + COREDUMP_MEMORY_TYPES); + ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE); +} + TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index ab94c45cd8be61..4e36e3e4fb78f3 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -1467,6 +1467,29 @@ bool read_marker(int fd, enum coredump_mark mark) return ret == mark; } +/* + * The kernel hung up without sending anything more: end of stream, or a + * reset if it refused the ack on its peeked size and never read it. + */ +bool read_hangup(int fd) +{ + ssize_t ret; + char c; + + ret = recv(fd, &c, sizeof(c), MSG_WAITALL); + if (ret == 0) { + fprintf(stderr, "Kernel closed the connection\n"); + return true; + } + if (ret < 0 && errno == ECONNRESET) { + fprintf(stderr, "Kernel closed the connection with the ack unread\n"); + return true; + } + + fprintf(stderr, "%s: expected a hangup, got %zd: %m\n", __func__, ret); + return false; +} + /* Read the request as a server built with a @user_size byte struct does. */ bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size) { @@ -1593,11 +1616,16 @@ bool send_coredump_ack(int fd, const struct coredump_req *req, bool check_coredump_req(const struct coredump_req *req) { - if (req->size < COREDUMP_REQ_SIZE_VER1) { - fprintf(stderr, "%s: size %u below minimum %d\n", + if (req->size != COREDUMP_REQ_SIZE_VER1) { + fprintf(stderr, "%s: size %u, expected %d\n", __func__, req->size, COREDUMP_REQ_SIZE_VER1); return false; } + if (req->size_ack != COREDUMP_ACK_SIZE_VER1) { + fprintf(stderr, "%s: size_ack %u, expected %d\n", + __func__, req->size_ack, COREDUMP_ACK_SIZE_VER1); + return false; + } if (req->mask != TEST_REQ_MASK_ALL) { fprintf(stderr, "%s: mask 0x%llx, expected 0x%llx\n", __func__, (unsigned long long)req->mask, diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h index 8e0187645c930d..3f2f87837558e8 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.h +++ b/tools/testing/selftests/coredump/coredump_test_helpers.h @@ -64,6 +64,7 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); /* Protocol helper function declarations */ ssize_t recv_marker(int fd); bool read_marker(int fd, enum coredump_mark mark); +bool read_hangup(int fd); bool read_coredump_req(int fd, struct coredump_req *req); bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size); bool send_coredump_ack(int fd, const struct coredump_req *req, From 8421a62085ec2d0be97b17ef66536344f472703e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:52 +0200 Subject: [PATCH 0713/2286] coredump: parse a snapshot of core_pattern This is a long-standing problem that I discussed a while back with Jann. I didn't care enough about it to really fix it and it's from the before-fore-times. coredump_parse() reads core_pattern directly while it can concurrently be modified. So it reads the first byte, figures out what mode is wanted, then allocates the number buffer and then consumes the rest of the core_pattern string. Say the sysctl handler updates the core_pattern array byte by byte (idiotic but supported). So that can lead to all kinds of insane mixups. Say you could transform the old "|/usr/bin/helper" and the new "/tmp/core.%p" into a usermodehelper started as "tmp/core.". So copy what proc_do_uts_string() does and let the handler run proc_dostring() on a copy, validate the copy and make it visible beneath a spinlock. Then coredump_parse() can take a snapshot under the same spinlock and parse a stable copy. From now on, rejected patterns are never visible and we can drop the whole rollback logic. It has the same minor defect that utsname has, namely that two writers can race on a non-zero offset. Irrelevant imho. Reviewed-by: Oleg Nesterov Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-3-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 59 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 320243fde9a48b..a2d73242a8c6fb 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -84,6 +84,8 @@ static int core_uses_pid; static unsigned int core_pipe_limit; static unsigned int core_sort_vma; static char core_pattern[CORENAME_MAX_SIZE] = "core"; +/* Taken around every copy in and out of core_pattern. */ +static DEFINE_SPINLOCK(core_pattern_lock); static int core_name_size = CORENAME_MAX_SIZE; unsigned int core_file_note_size_limit = CORE_FILE_NOTE_SIZE_DEFAULT; static atomic_t core_pipe_count = ATOMIC_INIT(0); @@ -239,11 +241,16 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm, size_t **argv, int *argc) { const struct cred *cred = current_cred(); - const char *pat_ptr = core_pattern; + char pattern[CORENAME_MAX_SIZE]; + const char *pat_ptr = pattern; bool was_space = false; int pid_in_pattern = 0; int err = 0; + /* The sysctl handler may be publishing a new pattern. */ + scoped_guard(spinlock, &core_pattern_lock) + strscpy(pattern, core_pattern); + cprm->mask = COREDUMP_KERNEL; if (core_pipe_limit) cprm->mask |= COREDUMP_WAIT; @@ -1640,11 +1647,11 @@ void validate_coredump_safety(void) } } -static inline bool check_coredump_socket(void) +static inline bool check_coredump_socket(const char *pattern) { const char *p; - if (core_pattern[0] != '@') + if (pattern[0] != '@') return true; /* @@ -1656,16 +1663,16 @@ static inline bool check_coredump_socket(void) return false; /* Must be an absolute path... */ - if (core_pattern[1] != '/') { + if (pattern[1] != '/') { /* ... or the socket request protocol... */ - if (core_pattern[1] != '@') + if (pattern[1] != '@') return false; /* ... and if so must be an absolute path. */ - if (core_pattern[2] != '/') + if (pattern[2] != '/') return false; - p = &core_pattern[2]; + p = &pattern[2]; } else { - p = &core_pattern[1]; + p = &pattern[1]; } /* The path obviously cannot exceed UNIX_PATH_MAX. */ @@ -1673,7 +1680,7 @@ static inline bool check_coredump_socket(void) return false; /* Must not contain ".." in the path. */ - if (name_contains_dotdot(core_pattern)) + if (name_contains_dotdot(pattern)) return false; return true; @@ -1682,27 +1689,35 @@ static inline bool check_coredump_socket(void) static int proc_dostring_coredump(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { + char pattern[CORENAME_MAX_SIZE]; + const struct ctl_table tmp = { + .procname = table->procname, + .data = pattern, + .maxlen = sizeof(pattern), + }; + bool changed = false; int error; - ssize_t retval; - char old_core_pattern[CORENAME_MAX_SIZE]; - - if (!write) - return proc_dostring(table, write, buffer, lenp, ppos); - retval = strscpy(old_core_pattern, core_pattern, CORENAME_MAX_SIZE); + /* Work on a copy, proc_dostring() appends at *ppos. */ + scoped_guard(spinlock, &core_pattern_lock) + strscpy(pattern, core_pattern); - error = proc_dostring(table, write, buffer, lenp, ppos); - if (error) + error = proc_dostring(&tmp, write, buffer, lenp, ppos); + if (error || !write) return error; - if (!check_coredump_socket()) { - strscpy(core_pattern, old_core_pattern, retval + 1); + if (!check_coredump_socket(pattern)) return -EINVAL; - } - if (strncmp(old_core_pattern, core_pattern, CORENAME_MAX_SIZE)) + /* Publish the validated pattern whole. */ + scoped_guard(spinlock, &core_pattern_lock) { + changed = strncmp(pattern, core_pattern, CORENAME_MAX_SIZE); + if (changed) + strscpy(core_pattern, pattern); + } + if (changed) validate_coredump_safety(); - return error; + return 0; } static const unsigned int core_file_note_size_min = CORE_FILE_NOTE_SIZE_DEFAULT; From ca38d128b66275286da4230cb07b401c8f973fbd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 19 Sep 2026 14:31:31 +0200 Subject: [PATCH 0714/2286] =?UTF-8?q?ACPI:=20thermal:=20Fix=20acpi=5Ftherm?= =?UTF-8?q?al=5Fguess=5Foffset()=20for=20device=20with=20a=20273.0=C2=B0K?= =?UTF-8?q?=20offset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices round the 273.15° offset between Kelvin and Celsius to 273.0 instead of 273.1 or 273.2. Make acpi_thermal_guess_offset() check for this value too, fixing e.g. all zones reporting x.8°C to them reporting y.0°C instead. Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260919123131.11443-1-johannes.goede@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/thermal.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 08c5a4ba16a660..13e9a3a79d92a3 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -741,7 +741,9 @@ static void acpi_thermal_aml_dependency_fix(struct acpi_thermal *tz) */ static void acpi_thermal_guess_offset(struct acpi_thermal *tz, long crit_temp) { - if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1) + if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 0) + tz->kelvin_offset = 273000; + else if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1) tz->kelvin_offset = 273100; else tz->kelvin_offset = 273200; From 61d58591aa0188bf2463e263962256316ff8b576 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:50 +0200 Subject: [PATCH 0715/2286] coredump: hold RCU while releasing parked threads coredump_finish() releases the parked threads by clearing ->task in their core_thread entry and calling wake_up_process() on each of them. It does that without holding a reference on the task and without being inside rcu. But calling wake_up_process(task) without rcu here isn't safe. A parked thread doesn't need that wakeup to leave. A spurious wakeup or a preemption after the store is enough for the task to go away. So if the coredump client is preempted between the store and wake_up_process() the thread can exit and be freed in the meantime and try_to_wake_up() takes pi_lock in freed memory. Hold rcu across the loop. Fixes: a94e2d408eae ("coredump: kill mm->core_done") Cc: stable@vger.kernel.org Acked-by: Oleg Nesterov Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-1-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/coredump.c b/fs/coredump.c index 7c0f276c3e0d6a..d88a65e37ee15c 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -562,6 +562,8 @@ static void coredump_finish(bool core_dumped) current->signal->core_state = NULL; spin_unlock_irq(¤t->sighand->siglock); + /* A released thread may exit and be freed before it is woken. */ + guard(rcu)(); while ((curr = next) != NULL) { next = curr->next; task = curr->task; @@ -570,6 +572,7 @@ static void coredump_finish(bool core_dumped) * ->task == NULL before we read ->next. */ smp_mb(); + /* Any wakeup now lets the thread exit, rcu keeps it alive. */ curr->task = NULL; wake_up_process(task); } From 0e5663856b69219ef2f02e04bac9f5c5be19718a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:53 +0200 Subject: [PATCH 0716/2286] io-wq: order the exit bit against worker creation task work io_wq_exit_workers() cancels task work for queued worker creation with task_work_cancel_match(). That has a plain load of task->task_works. io_queue_worker_create() adds the new entry and tests IO_WQ_BIT_EXIT to cancel it if the workqueue is already on its way out. That must be ordered. task_work_add() has a full barrier via cmpxchg(). But io_wq_exit_start() sets the bit with set_bit() which doesn't have any memory ordering. So afaict, on weakly ordered architectures the exiting task may load task_works before the store of the bit is visible. The other side tests the bit before the store is visible as well. So the entry remains queued with worker_refs and the exiting task waits on worker_done indefinitely. If the task still has rings then io_uring_del_tctx_node() provides the barrier via test_and_set_bit() in io_wq_set_exit_on_idle(). When it has closed all rings though that barrier is gone. Add the barrier after the set_bit(). Fixes: 71a85387546e ("io-wq: check for wq exit after adding new worker task_work") Cc: stable@vger.kernel.org Reviewed-by: Jens Axboe Acked-by: Oleg Nesterov Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-4-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- io_uring/io-wq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 2ca223e47d41f5..2a980e86dd9405 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1324,6 +1324,8 @@ static bool io_task_work_match(struct callback_head *cb, void *data) void io_wq_exit_start(struct io_wq *wq) { set_bit(IO_WQ_BIT_EXIT, &wq->state); + /* Pairs with task_work_add() in io_queue_worker_create(). */ + smp_mb__after_atomic(); } static void io_wq_cancel_tw_create(struct io_wq *wq) From c0e659511d4f49087da095a95799edb5b775a705 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:54 +0200 Subject: [PATCH 0717/2286] signal: don't retarget shared signals in a dying thread group If the calling thread has blocked a signal then retarget_shared_pending() slingshots a shared pending signal to a sibling thread. Today it only skips PF_EXITING threads but not a coredumping thread. But if SIGNAL_GROUP_EXIT is set no thread in the thread-group can dequeue such signals anymore as get_signal() sends SIGKILL to every thread and prepare_signal() drops any other signal. exit_signals() also returns early on SIGNAL_GROUP_EXIT. But neither sigprocmask() nor the mask restore in sigtimedwait() do. Say a thread sleeps in sigtimedwait() and a sibling thread crashes. The sleeping thread gets woken by the zap call. It restores its mask on the way out and retargets a signal that was queued for the coredumping process. That means TIF_SIGPENDING is set on the coredumping task which zap_threads() had cleared. So a blocking write sees TIF_SIGPENDING and truncates the dump. Stop retargeting signals when SIGNAL_GROUP_EXIT is set and avoid needlessly truncating coredumps. Fixes: e6fa16ab9c1e ("signal: sigprocmask() should do retarget_shared_pending()") Cc: stable@vger.kernel.org Acked-by: Oleg Nesterov Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-5-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- kernel/signal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/signal.c b/kernel/signal.c index a5e15bf09d3190..51e43e17d1c63b 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3097,6 +3097,10 @@ static void retarget_shared_pending(struct task_struct *tsk, sigset_t *which) sigset_t retarget; struct task_struct *t; + /* Nobody dequeues them in a dying group, see get_signal(). */ + if (tsk->signal->flags & SIGNAL_GROUP_EXIT) + return; + sigandsets(&retarget, &tsk->signal->shared_pending.signal, which); if (sigisemptyset(&retarget)) return; From 563d606b93d7a1d23512832fc252d32c68860d87 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 22 Sep 2026 15:26:16 +0200 Subject: [PATCH 0718/2286] signal: only SIGKILL and the freezers interrupt a coredumping task For quite a while now coredump has allowed either SIGKILL or freezing to interrupt an ongoing coredump. Since we seem to like it complicated "freezing" can mean a lot of things: (1) Power Management induced freezing (2) cgroup v1 freezer controller induced freezing (3) cgroup v2 freezer controller induced freezing So (1) and (2) are handled by checking freezing() but (3) isn't. (3) uses cgroup_freeze_task() which sets JOBCTL_TRAP_FREEZE and calls signal_wake_up() on every task in the cgroup. The trap isn't handled by the coredump code. Which means the current logic is inconsistent. Power and cgroup v1 freeze and abort the coredump. With cgroup v2 it depends on where the core goes. A dump to a file completes and the cgroup v2 freeze has to wait for it to finish. When dumping to a pipe or socket the dump is interrupted at the next write because of trap's TIF_SIGPENDING. Which is it? Let's be consistent and align (1)-(3): freezing interrupts an ongoing coredump and doesn't make it wait until the dump is done. All three freezers go through signal_wake_up() and so set TIF_SIGPENDING on the dumper. And once zap_threads() has set core_state that flag can't mean anything else anymore. prepare_signal() drops every signal but SIGKILL for the group, the dying threads don't retarget shared signals to the dumper, and zap_process() cleared the pending job control traps. So the flag is the thing to check. Let dump_interrupted() test task_sigpending() and nothing else. That is also what the pipe and socket writes already fail on. TIF_NOTIFY_SIGNAL is left out on purpose, task work must not cut a dump short. A seized tracee that gets PTRACE_INTERRUPT during the dump is cut short the same way. It already was for pipes and sockets. The one freeze that check can miss is one requested before the dump started. zap_threads() clears TIF_SIGPENDING and the cgroup v2 freezer only wakes a task once while its JOBCTL_TRAP_FREEZE isn't part of JOBCTL_PENDING_MASK and survives zap_process(). So refuse to start the dump when freezing() is true or the trap is pending. The task then exits without dumping, which is what happens today when the freeze interrupts the dump at the first write. Fixes: 403bad72b67d ("coredump: only SIGKILL should interrupt the coredumping task") Fixes: 76f969e8948d ("cgroup: cgroup v2 freezer") Cc: stable@vger.kernel.org Suggested-by: Oleg Nesterov Link: https://lore.kernel.org/r/arJ6zZuI59BJnXpl@redhat.com Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-2-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index d88a65e37ee15c..af09a466034143 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -508,7 +508,9 @@ static int zap_threads(struct task_struct *tsk, int nr = -EAGAIN; spin_lock_irq(&tsk->sighand->siglock); - if (!(signal->flags & SIGNAL_GROUP_EXIT) && !signal->group_exec_task) { + /* A freeze requested before the dump would be lost with TIF_SIGPENDING. */ + if (!(signal->flags & SIGNAL_GROUP_EXIT) && !signal->group_exec_task && + !freezing(tsk) && !(tsk->jobctl & JOBCTL_TRAP_FREEZE)) { /* Allow SIGKILL, see prepare_signal() */ signal->core_state = core_state; nr = zap_process(signal, exit_code); @@ -580,13 +582,8 @@ static void coredump_finish(bool core_dumped) static bool dump_interrupted(void) { - /* - * SIGKILL or freezing() interrupt the coredumping. Perhaps we - * can do try_to_freeze() and check __fatal_signal_pending(), - * but then we need to teach dump_write() to restart and clear - * TIF_SIGPENDING. - */ - return fatal_signal_pending(current) || freezing(current); + /* Only SIGKILL and the freezers set it after zap_threads(). */ + return task_sigpending(current); } static void wait_for_dump_helpers(struct file *file) From 62badefdbab34c0dc3c4ec13c09787dc4d50b4bb Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:55 +0200 Subject: [PATCH 0719/2286] selftests/coredump: test shared signal retargeting during a dump A shared signal that is still queued when a thread crashes gets retargeted to the dumper when a zapped sibling restores its signal mask on the way out of sigtimedwait(). That sets TIF_SIGPENDING on the dumper and cuts the core short at the next full pipe or socket buffer. Cover that: - one sibling sleeps in sigtimedwait() for SIGUSR1 - a vfork() child queues SIGUSR1 for the main thread while it sleeps killably and then the SIGSEGV that is dequeued first - the coredump server holds its read back so the dumper blocks on the full socket buffer Check that the core covers every segment with and without the queued signal. Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-6-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/coredump/.gitignore | 1 + tools/testing/selftests/coredump/Makefile | 4 +- .../selftests/coredump/coredump_signal_test.c | 238 ++++++++++++++++++ 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/coredump/coredump_signal_test.c diff --git a/tools/testing/selftests/coredump/.gitignore b/tools/testing/selftests/coredump/.gitignore index 097f52db0be964..c198f2ca5872d1 100644 --- a/tools/testing/selftests/coredump/.gitignore +++ b/tools/testing/selftests/coredump/.gitignore @@ -2,3 +2,4 @@ stackdump_test coredump_socket_test coredump_socket_protocol_test +coredump_signal_test diff --git a/tools/testing/selftests/coredump/Makefile b/tools/testing/selftests/coredump/Makefile index 728f3c342eb923..6173e35cba16b5 100644 --- a/tools/testing/selftests/coredump/Makefile +++ b/tools/testing/selftests/coredump/Makefile @@ -4,7 +4,8 @@ CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) TEST_GEN_PROGS := stackdump_test \ coredump_socket_test \ coredump_socket_protocol_test \ - coredump_notify_signal_test + coredump_notify_signal_test \ + coredump_signal_test # Spawned by the kernel as the |helper, not a test of its own. TEST_GEN_FILES := coredump_notify_signal_helper TEST_FILES := stackdump @@ -16,3 +17,4 @@ $(OUTPUT)/coredump_socket_test: coredump_test_helpers.c $(OUTPUT)/coredump_socket_protocol_test: coredump_test_helpers.c $(OUTPUT)/coredump_notify_signal_test: coredump_test_helpers.c $(OUTPUT)/coredump_notify_signal_helper: coredump_test_helpers.c +$(OUTPUT)/coredump_signal_test: coredump_test_helpers.c diff --git a/tools/testing/selftests/coredump/coredump_signal_test.c b/tools/testing/selftests/coredump/coredump_signal_test.c new file mode 100644 index 00000000000000..fdf48b1447811f --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_signal_test.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coredump_test.h" + +/* Big enough to fill the socket buffer many times over. */ +#define CRASH_MAPPING_SIZE (32 * 1024 * 1024) + +FIXTURE_SETUP(coredump) +{ + FILE *file; + int ret; + + self->pid_coredump_server = -ESRCH; + self->fd_tmpfs_detached = -1; + file = fopen("/proc/sys/kernel/core_pattern", "r"); + ASSERT_NE(NULL, file); + + ret = fread(self->original_core_pattern, 1, sizeof(self->original_core_pattern), file); + ASSERT_TRUE(ret || feof(file)); + ASSERT_LT(ret, sizeof(self->original_core_pattern)); + + self->original_core_pattern[ret] = '\0'; + + ret = fclose(file); + ASSERT_EQ(0, ret); +} + +FIXTURE_TEARDOWN(coredump) +{ + const char *reason; + FILE *file; + int ret, status; + + if (self->pid_coredump_server > 0) { + kill(self->pid_coredump_server, SIGTERM); + waitpid(self->pid_coredump_server, &status, 0); + } + unlink("/tmp/coredump.file"); + unlink("/tmp/coredump.socket"); + + file = fopen("/proc/sys/kernel/core_pattern", "w"); + if (!file) { + reason = "Unable to open core_pattern"; + goto fail; + } + + ret = fprintf(file, "%s", self->original_core_pattern); + if (ret < 0) { + reason = "Unable to write to core_pattern"; + goto fail; + } + + ret = fclose(file); + if (ret) { + reason = "Unable to close core_pattern"; + goto fail; + } + + return; +fail: + /* This should never happen */ + fprintf(stderr, "Failed to cleanup coredump test: %s\n", reason); +} + +static volatile int waiter_ready; + +static void usr1_handler(int sig) +{ +} + +/* Sleeps in sigtimedwait() with SIGUSR1 unblocked only inside the kernel. */ +static void *sigwaiter(void *arg) +{ + sigset_t set; + siginfo_t info; + + sigemptyset(&set); + sigaddset(&set, SIGUSR1); + __atomic_store_n(&waiter_ready, 1, __ATOMIC_RELEASE); + for (;;) + sigtimedwait(&set, &info, NULL); + return NULL; +} + +/* + * Crash with a shared SIGUSR1 still queued for this thread. The vfork() + * child queues it while we sleep killably and then the SIGSEGV that is + * dequeued first. The zap wakes the sibling out of sigtimedwait() and its + * mask restore retargets SIGUSR1 to the dumper. + */ +static void crashing_child_retarget(bool queue_shared) +{ + sigset_t all, old; + pthread_t thread; + pid_t pid, tid; + char *p; + + p = mmap(NULL, CRASH_MAPPING_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) + _exit(EXIT_FAILURE); + memset(p, 0x5a, CRASH_MAPPING_SIZE); + + signal(SIGUSR1, usr1_handler); + sigfillset(&all); + pthread_sigmask(SIG_BLOCK, &all, &old); + /* One waiter only, retarget stops at the first thread not blocking it. */ + if (pthread_create(&thread, NULL, sigwaiter, NULL)) + _exit(EXIT_FAILURE); + pthread_sigmask(SIG_SETMASK, &old, NULL); + while (!__atomic_load_n(&waiter_ready, __ATOMIC_ACQUIRE)) + usleep(1000); + usleep(50 * 1000); + + pid = getpid(); + tid = syscall(SYS_gettid); + if (vfork() == 0) { + if (queue_shared) + syscall(SYS_kill, pid, SIGUSR1); + syscall(SYS_tgkill, pid, tid, SIGSEGV); + syscall(SYS_exit, 0); + } + + /* Not reached, the pending SIGSEGV dumps core. */ + for (;;) + pause(); +} + +/* + * Dump the crashing child into /tmp/coredump.file through a server that + * holds the read back so the dumper blocks on the full socket buffer. + */ +static void run_coredump(struct __test_metadata *const _metadata, + FIXTURE_DATA(coredump) *self, bool queue_shared) +{ + pid_t pid, pid_coredump_server; + int ipc_sockets[2]; + int status; + char c; + + unlink("/tmp/coredump.file"); + unlink("/tmp/coredump.socket"); + ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket")); + ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_core_file = -1; + int exit_code = EXIT_FAILURE; + + close(ipc_sockets[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) + goto out; + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) + goto out; + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) { + fprintf(stderr, "%s: accept4 failed: %m\n", __func__); + goto out; + } + + /* Let the dumper run into the full socket buffer first. */ + sleep(1); + + fd_core_file = creat("/tmp/coredump.file", 0644); + if (fd_core_file < 0) { + fprintf(stderr, "%s: creat failed: %m\n", __func__); + goto out; + } + + if (recv_coredump_bytes(fd_coredump, fd_core_file) < 0) + goto out; + + exit_code = EXIT_SUCCESS; +out: + if (fd_core_file >= 0) + close(fd_core_file); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_retarget(queue_shared); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + ASSERT_TRUE(WCOREDUMP(status)); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +} + +static void check_coredump_complete(struct __test_metadata *const _metadata) +{ + int fd; + + fd = open("/tmp/coredump.file", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_TRUE(check_coredump_extent(fd)); + close(fd); +} + +TEST_F(coredump, retarget_shared_pending) +{ + run_coredump(_metadata, self, false); + check_coredump_complete(_metadata); + + run_coredump(_metadata, self, true); + check_coredump_complete(_metadata); +} + +TEST_HARNESS_MAIN From cabc249e6560bee7bdfee9fbcb7ce4e951dcfd41 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:58 +0200 Subject: [PATCH 0720/2286] ptrace: refuse to change the signal mask of a user worker A user worker is created with every signal other than SIGKILL and SIGSTOP blocked. It never runs user code and that mask is what keeps get_signal() from dequeuing anything else for it. complete_signal() never picks a thread that blocks the signal, tkill() queues on the worker but nothing dequeues it. ptrace_signal() requeues an injected signal that the tracee blocks. PTRACE_SETSIGMASK is the only way to change that mask from the outside. A tracer that clears it makes the worker eligible for every signal. Whatever the worker then dequeues it can only act on by leaving. Refuse PTRACE_SETSIGMASK for a user worker with -EPERM, the way ptrace_attach() refuses a kernel thread. A worker is guaranteed to only ever dequeue SIGKILL or SIGSTOP and an injected signal stays pending on it, which is what already happens when the mask isn't touched. Taking the request and quietly leaving the mask alone was the other option, the way set_current_blocked() keeps SIGKILL and SIGSTOP unblocked whatever userspace asks for. But then a tracer gets success back with nothing changed and no way to tell that apart from a mask that took effect. Fixes: e8b33b8cfafc ("Revert "kernel: treat PF_IO_WORKER like PF_KTHREAD for ptrace/signals"") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-9-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/ptrace.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/ptrace.c b/kernel/ptrace.c index d041645d9d17da..4e9822a87aabbc 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -1227,6 +1227,12 @@ int ptrace_request(struct task_struct *child, long request, case PTRACE_SETSIGMASK: { sigset_t new_set; + /* A user worker only ever takes SIGKILL and SIGSTOP. */ + if (child->flags & PF_USER_WORKER) { + ret = -EPERM; + break; + } + if (addr != sizeof(sigset_t)) { ret = -EINVAL; break; From cbb592b083e5c5fb3ac36087878ca572690c3c62 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:59 +0200 Subject: [PATCH 0721/2286] selftests/coredump: test a user worker as the coredumping thread A tracer can clear the signal mask of an io-wq worker or an SQPOLL thread with PTRACE_SETSIGMASK and inject a coredump signal. The worker then runs vfs_coredump() itself. Cover that: - a child keeps a ring, an idle io-wq worker and with SQPOLL the SQPOLL thread alive - seize the chosen thread, stop it with SIGSTOP, clear its mask and detach with SIGSEGV - require the thread group to be gone in bounded time, by the dump or by SIGKILL The io-wq worker of an SQPOLL ring fails: the SQPOLL thread waits for its workers to exit before it parks, the dumping worker waits for the SQPOLL thread to park and the group is stuck in D state. Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-10-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/coredump/.gitignore | 1 + tools/testing/selftests/coredump/Makefile | 4 +- .../selftests/coredump/coredump_worker_test.c | 427 ++++++++++++++++++ 3 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/coredump/coredump_worker_test.c diff --git a/tools/testing/selftests/coredump/.gitignore b/tools/testing/selftests/coredump/.gitignore index c198f2ca5872d1..e32f6e9006f6e2 100644 --- a/tools/testing/selftests/coredump/.gitignore +++ b/tools/testing/selftests/coredump/.gitignore @@ -3,3 +3,4 @@ stackdump_test coredump_socket_test coredump_socket_protocol_test coredump_signal_test +coredump_worker_test diff --git a/tools/testing/selftests/coredump/Makefile b/tools/testing/selftests/coredump/Makefile index 6173e35cba16b5..c4cdab7b7d0ba8 100644 --- a/tools/testing/selftests/coredump/Makefile +++ b/tools/testing/selftests/coredump/Makefile @@ -5,7 +5,8 @@ TEST_GEN_PROGS := stackdump_test \ coredump_socket_test \ coredump_socket_protocol_test \ coredump_notify_signal_test \ - coredump_signal_test + coredump_signal_test \ + coredump_worker_test # Spawned by the kernel as the |helper, not a test of its own. TEST_GEN_FILES := coredump_notify_signal_helper TEST_FILES := stackdump @@ -18,3 +19,4 @@ $(OUTPUT)/coredump_socket_protocol_test: coredump_test_helpers.c $(OUTPUT)/coredump_notify_signal_test: coredump_test_helpers.c $(OUTPUT)/coredump_notify_signal_helper: coredump_test_helpers.c $(OUTPUT)/coredump_signal_test: coredump_test_helpers.c +$(OUTPUT)/coredump_worker_test: coredump_test_helpers.c diff --git a/tools/testing/selftests/coredump/coredump_worker_test.c b/tools/testing/selftests/coredump/coredump_worker_test.c new file mode 100644 index 00000000000000..81cadde0e7528e --- /dev/null +++ b/tools/testing/selftests/coredump/coredump_worker_test.c @@ -0,0 +1,427 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * A user worker as the coredumping thread. + * + * io-wq workers and SQPOLL threads are threads of the process that never + * return to userspace. They block every signal but SIGKILL and SIGSTOP, + * but a tracer can replace that mask with PTRACE_SETSIGMASK and inject a + * coredump signal. get_signal() then runs vfs_coredump() in the worker. + * The worker's own exit bookkeeping runs only after the dump, so a + * zapped sibling that waits for it in its exit path deadlocks with the + * dumper and the whole thread group is stuck in D state. + * + * Inject SIGSEGV into a chosen thread and require that the thread group + * is gone in bounded time, either because the dump completed or because + * SIGKILL still works. A failure leaves the stuck process behind. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coredump_test.h" + +#ifndef PTRACE_SETSIGMASK +#define PTRACE_SETSIGMASK 0x420b +#endif + +/* The dump of the tiny child takes well under a second. */ +#define EXIT_TIMEOUT_MS 5000 + +FIXTURE_SETUP(coredump) +{ + FILE *file; + int ret; + + self->pid_coredump_server = -ESRCH; + self->fd_tmpfs_detached = -1; + file = fopen("/proc/sys/kernel/core_pattern", "r"); + ASSERT_NE(NULL, file); + + ret = fread(self->original_core_pattern, 1, sizeof(self->original_core_pattern), file); + ASSERT_TRUE(ret || feof(file)); + ASSERT_LT(ret, sizeof(self->original_core_pattern)); + + self->original_core_pattern[ret] = '\0'; + + ret = fclose(file); + ASSERT_EQ(0, ret); +} + +FIXTURE_TEARDOWN(coredump) +{ + const char *reason; + FILE *file; + int ret; + + file = fopen("/proc/sys/kernel/core_pattern", "w"); + if (!file) { + reason = "Unable to open core_pattern"; + goto fail; + } + + ret = fprintf(file, "%s", self->original_core_pattern); + if (ret < 0) { + reason = "Unable to write to core_pattern"; + goto fail; + } + + ret = fclose(file); + if (ret) { + reason = "Unable to close core_pattern"; + goto fail; + } + + return; +fail: + /* This should never happen */ + fprintf(stderr, "Failed to cleanup coredump test: %s\n", reason); +} + +/* A raw ring, no liburing. */ +struct uring { + int fd; + struct io_uring_params params; + void *sq; + size_t sq_len; + struct io_uring_sqe *sqes; + size_t sqes_len; + unsigned int *sq_tail, *sq_mask, *sq_array; + unsigned int *cq_head, *cq_tail, *cq_mask; + struct io_uring_cqe *cqes; +}; + +static int uring_setup(struct uring *r, unsigned int flags) +{ + size_t cq_len; + + memset(r, 0, sizeof(*r)); + r->params.flags = flags; + if (flags & IORING_SETUP_SQPOLL) + r->params.sq_thread_idle = 2000; + r->fd = syscall(__NR_io_uring_setup, 8, &r->params); + if (r->fd < 0) + return -1; + if (!(r->params.features & IORING_FEAT_SINGLE_MMAP)) + return -1; + + r->sq_len = r->params.sq_off.array + r->params.sq_entries * sizeof(unsigned int); + cq_len = r->params.cq_off.cqes + r->params.cq_entries * sizeof(struct io_uring_cqe); + if (cq_len > r->sq_len) + r->sq_len = cq_len; + r->sq = mmap(NULL, r->sq_len, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQ_RING); + if (r->sq == MAP_FAILED) + return -1; + r->sqes_len = r->params.sq_entries * sizeof(struct io_uring_sqe); + r->sqes = mmap(NULL, r->sqes_len, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_POPULATE, r->fd, IORING_OFF_SQES); + if (r->sqes == MAP_FAILED) + return -1; + + r->sq_tail = r->sq + r->params.sq_off.tail; + r->sq_mask = r->sq + r->params.sq_off.ring_mask; + r->sq_array = r->sq + r->params.sq_off.array; + r->cq_head = r->sq + r->params.cq_off.head; + r->cq_tail = r->sq + r->params.cq_off.tail; + r->cq_mask = r->sq + r->params.cq_off.ring_mask; + r->cqes = r->sq + r->params.cq_off.cqes; + return 0; +} + +/* Submit one sqe, wait for its completion and return the result. */ +static int uring_submit_wait(struct uring *r, const struct io_uring_sqe *sqe) +{ + unsigned int tail = *r->sq_tail, idx = tail & *r->sq_mask; + unsigned int flags = IORING_ENTER_GETEVENTS; + int i; + + r->sqes[idx] = *sqe; + r->sq_array[idx] = idx; + __atomic_store_n(r->sq_tail, tail + 1, __ATOMIC_RELEASE); + + if (r->params.flags & IORING_SETUP_SQPOLL) + flags |= IORING_ENTER_SQ_WAKEUP; + + for (i = 0; i < 100; i++) { + if (syscall(__NR_io_uring_enter, r->fd, 1, 1, flags, NULL, 0) < 0 && + errno != EINTR) + return -1; + if (__atomic_load_n(r->cq_tail, __ATOMIC_ACQUIRE) != *r->cq_head) { + unsigned int head = *r->cq_head; + int res = r->cqes[head & *r->cq_mask].res; + + __atomic_store_n(r->cq_head, head + 1, __ATOMIC_RELEASE); + return res; + } + flags &= ~IORING_ENTER_SQ_WAKEUP; + usleep(10 * 1000); + } + return -1; +} + +static bool uring_available(unsigned int flags) +{ + struct io_uring_params params = { .flags = flags }; + int fd; + + fd = syscall(__NR_io_uring_setup, 2, ¶ms); + if (fd < 0) + return false; + close(fd); + return true; +} + +/* + * Keep a ring, an idle io-wq worker and with SQPOLL an SQPOLL thread + * alive. The last worker of a ring never exits on its idle timeout. + */ +static void worker_child(bool sqpoll, int fd_ipc) +{ + struct rlimit rl = { RLIM_INFINITY, RLIM_INFINITY }; + struct io_uring_sqe sqe = {}; + static char buf[64]; + struct uring ring; + int memfd; + + if (setrlimit(RLIMIT_CORE, &rl)) + _exit(EXIT_FAILURE); + + memfd = memfd_create("coredump_worker", 0); + if (memfd < 0 || write(memfd, "hello", 5) != 5) + _exit(EXIT_FAILURE); + + if (uring_setup(&ring, sqpoll ? IORING_SETUP_SQPOLL : 0)) + _exit(EXIT_FAILURE); + + /* IOSQE_ASYNC forces the read through io-wq so a worker appears. */ + sqe.opcode = IORING_OP_READ; + sqe.fd = memfd; + sqe.addr = (__u64)(uintptr_t)buf; + sqe.len = sizeof(buf); + sqe.flags = IOSQE_ASYNC; + if (uring_submit_wait(&ring, &sqe) != 5) + _exit(EXIT_FAILURE); + + if (write_nointr(fd_ipc, "1", 1) != 1) + _exit(EXIT_FAILURE); + close(fd_ipc); + + for (;;) + pause(); +} + +/* Find the thread of @pid whose comm starts with @prefix. */ +static pid_t find_thread(pid_t pid, const char *prefix) +{ + char path[64], comm[64]; + pid_t tid = -1; + struct dirent *de; + ssize_t bytes; + DIR *dir; + int fd; + + snprintf(path, sizeof(path), "/proc/%d/task", pid); + dir = opendir(path); + if (!dir) + return -1; + + while (tid < 0 && (de = readdir(dir))) { + if (!isdigit(de->d_name[0])) + continue; + snprintf(path, sizeof(path), "/proc/%d/task/%s/comm", pid, de->d_name); + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + continue; + bytes = read(fd, comm, sizeof(comm) - 1); + close(fd); + if (bytes <= 0) + continue; + comm[bytes] = '\0'; + if (!strncmp(comm, prefix, strlen(prefix))) + tid = atoi(de->d_name); + } + closedir(dir); + return tid; +} + +/* + * Attach, stop the thread with SIGSTOP, drop the signal mask that + * copy_process() gave it and resume it with SIGSEGV instead. + */ +static bool inject_coredump_signal(pid_t pid, pid_t tid) +{ + __u64 mask = 0; + int status; + + if (ptrace(PTRACE_SEIZE, tid, NULL, NULL)) + return false; + if (syscall(SYS_tgkill, pid, tid, SIGSTOP)) + return false; + if (waitpid(tid, &status, __WALL) != tid) + return false; + if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) + return false; + if (ptrace(PTRACE_SETSIGMASK, tid, sizeof(mask), &mask)) + return false; + return !ptrace(PTRACE_DETACH, tid, NULL, (void *)(long)SIGSEGV); +} + +/* Reap @pid within @timeout_ms, -1 when it is still there. */ +static int wait_exit(pid_t pid, int *status, int timeout_ms) +{ + int i; + + for (i = 0; i < timeout_ms / 10; i++) { + pid_t ret = waitpid(pid, status, WNOHANG); + + if (ret == pid) + return 0; + if (ret < 0) + return -1; + usleep(10 * 1000); + } + return -1; +} + +static void log_threads(struct __test_metadata *const _metadata, pid_t pid) +{ + char path[64], line[256], comm[64] = {}; + struct dirent *de; + DIR *dir; + FILE *f; + + snprintf(path, sizeof(path), "/proc/%d/task", pid); + dir = opendir(path); + if (!dir) + return; + while ((de = readdir(dir))) { + if (!isdigit(de->d_name[0])) + continue; + snprintf(path, sizeof(path), "/proc/%d/task/%s/status", pid, de->d_name); + f = fopen(path, "r"); + if (!f) + continue; + while (fgets(line, sizeof(line), f)) { + line[strcspn(line, "\n")] = '\0'; + if (!strncmp(line, "Name:", 5)) + snprintf(comm, sizeof(comm), "%s", line + 6); + else if (!strncmp(line, "State:", 6)) + TH_LOG("tid %s (%s) %s", de->d_name, comm, line + 7); + } + fclose(f); + } + closedir(dir); +} + +enum dumper { + DUMPER_MAIN, + DUMPER_WORKER, + DUMPER_SQPOLL, +}; + +static void run_dumper(struct __test_metadata *const _metadata, bool sqpoll, + enum dumper dumper) +{ + bool killed = false; + char path[64], c; + int ipc[2], status, fd; + pid_t pid, tid; + + ASSERT_TRUE(set_core_pattern("/tmp/coredump.file.%p")); + ASSERT_EQ(pipe2(ipc, O_CLOEXEC), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(ipc[0]); + worker_child(sqpoll, ipc[1]); + } + close(ipc[1]); + ASSERT_EQ(read_nointr(ipc[0], &c, 1), 1); + close(ipc[0]); + + switch (dumper) { + case DUMPER_MAIN: + tid = pid; + break; + case DUMPER_WORKER: + tid = find_thread(pid, "iou-wrk-"); + break; + case DUMPER_SQPOLL: + tid = find_thread(pid, "iou-sqp-"); + break; + } + ASSERT_GT(tid, 0); + ASSERT_TRUE(inject_coredump_signal(pid, tid)); + + if (wait_exit(pid, &status, EXIT_TIMEOUT_MS)) { + /* No dump. Whatever happened, SIGKILL must still work. */ + log_threads(_metadata, pid); + kill(pid, SIGKILL); + killed = true; + ASSERT_EQ(wait_exit(pid, &status, EXIT_TIMEOUT_MS), 0) { + TH_LOG("thread group %d is stuck after SIGSEGV to tid %d", + pid, tid); + } + } + + ASSERT_TRUE(WIFSIGNALED(status)); + if (killed) { + TH_LOG("tid %d did not dump, the group was killed instead", tid); + ASSERT_EQ(WTERMSIG(status), SIGKILL); + return; + } + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + ASSERT_TRUE(WCOREDUMP(status)); + + snprintf(path, sizeof(path), "/tmp/coredump.file.%d", pid); + fd = open(path, O_RDONLY | O_CLOEXEC); + unlink(path); + ASSERT_GE(fd, 0); + ASSERT_TRUE(check_coredump_extent(fd)); + close(fd); +} + +/* The mechanics: an injected SIGSEGV into a normal thread dumps core. */ +TEST_F(coredump, main_thread_dumper) +{ + if (!uring_available(0)) + SKIP(return, "io_uring is not available"); + run_dumper(_metadata, false, DUMPER_MAIN); +} + +TEST_F(coredump, plain_worker_dumper) +{ + if (!uring_available(0)) + SKIP(return, "io_uring is not available"); + run_dumper(_metadata, false, DUMPER_WORKER); +} + +TEST_F(coredump, sqpoll_thread_dumper) +{ + if (!uring_available(IORING_SETUP_SQPOLL)) + SKIP(return, "io_uring SQPOLL is not available"); + run_dumper(_metadata, true, DUMPER_SQPOLL); +} + +/* + * The SQPOLL thread leaves its loop on the zap and waits for its io-wq + * workers to exit before it parks. The dumping worker never does. + */ +TEST_F(coredump, sqpoll_worker_dumper) +{ + if (!uring_available(IORING_SETUP_SQPOLL)) + SKIP(return, "io_uring SQPOLL is not available"); + run_dumper(_metadata, true, DUMPER_WORKER); +} + +TEST_HARNESS_MAIN From 35f322efdbe24dd21a08e4283ed43010d4b6f8c6 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:00 +0200 Subject: [PATCH 0722/2286] selftests/coredump: expect PTRACE_SETSIGMASK to be refused on a user worker PTRACE_SETSIGMASK returns -EPERM for a user worker now. - inject_coredump_signal() reports whether the mask could be changed - a refused mask means the injected SIGSEGV stays pending on the worker - the worker cases check that the group is alive and kill it The main thread case still dumps. Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-11-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- .../selftests/coredump/coredump_worker_test.c | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tools/testing/selftests/coredump/coredump_worker_test.c b/tools/testing/selftests/coredump/coredump_worker_test.c index 81cadde0e7528e..9a4270b65a6e6c 100644 --- a/tools/testing/selftests/coredump/coredump_worker_test.c +++ b/tools/testing/selftests/coredump/coredump_worker_test.c @@ -16,6 +16,7 @@ * SIGKILL still works. A failure leaves the stuck process behind. */ #include +#include #include #include #include @@ -255,24 +256,31 @@ static pid_t find_thread(pid_t pid, const char *prefix) /* * Attach, stop the thread with SIGSTOP, drop the signal mask that - * copy_process() gave it and resume it with SIGSEGV instead. + * copy_process() gave it and resume it with SIGSEGV. Returns 1 when the + * mask was changed, 0 when PTRACE_SETSIGMASK was refused (a user worker + * keeps its mask and the SIGSEGV stays pending), -1 on any other failure. */ -static bool inject_coredump_signal(pid_t pid, pid_t tid) +static int inject_coredump_signal(pid_t pid, pid_t tid) { __u64 mask = 0; - int status; + int status, ret = 1; if (ptrace(PTRACE_SEIZE, tid, NULL, NULL)) - return false; + return -1; if (syscall(SYS_tgkill, pid, tid, SIGSTOP)) - return false; + return -1; if (waitpid(tid, &status, __WALL) != tid) - return false; + return -1; if (!WIFSTOPPED(status) || WSTOPSIG(status) != SIGSTOP) - return false; - if (ptrace(PTRACE_SETSIGMASK, tid, sizeof(mask), &mask)) - return false; - return !ptrace(PTRACE_DETACH, tid, NULL, (void *)(long)SIGSEGV); + return -1; + if (ptrace(PTRACE_SETSIGMASK, tid, sizeof(mask), &mask)) { + if (errno != EPERM) + return -1; + ret = 0; + } + if (ptrace(PTRACE_DETACH, tid, NULL, (void *)(long)SIGSEGV)) + return -1; + return ret; } /* Reap @pid within @timeout_ms, -1 when it is still there. */ @@ -333,7 +341,7 @@ static void run_dumper(struct __test_metadata *const _metadata, bool sqpoll, { bool killed = false; char path[64], c; - int ipc[2], status, fd; + int ipc[2], status, fd, ret; pid_t pid, tid; ASSERT_TRUE(set_core_pattern("/tmp/coredump.file.%p")); @@ -361,7 +369,19 @@ static void run_dumper(struct __test_metadata *const _metadata, bool sqpoll, break; } ASSERT_GT(tid, 0); - ASSERT_TRUE(inject_coredump_signal(pid, tid)); + ret = inject_coredump_signal(pid, tid); + ASSERT_GE(ret, 0); + if (!ret) { + /* The signal sits on the worker, the group must be untouched. */ + ASSERT_NE(dumper, DUMPER_MAIN); + TH_LOG("PTRACE_SETSIGMASK refused for tid %d, the SIGSEGV stays pending", tid); + ASSERT_EQ(wait_exit(pid, &status, 1000), -1); + kill(pid, SIGKILL); + ASSERT_EQ(wait_exit(pid, &status, EXIT_TIMEOUT_MS), 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGKILL); + return; + } if (wait_exit(pid, &status, EXIT_TIMEOUT_MS)) { /* No dump. Whatever happened, SIGKILL must still work. */ From b42c85c20d84ff3e4f0f027d6596c3b3b4a20657 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:01 +0200 Subject: [PATCH 0723/2286] exec: cancel io_uring requests before de_thread() begin_new_exec() cancels the exec'ing thread's io_uring requests after de_thread() made it the only thread in the thread-group. Now io_uring_task_cancel() runs task work after. If it runs a create_worker_cb() item then a io-wq queued on the thread before the exec creates a new io-wq worker thread. Said thread joins the thread-group which de_thread() just took down. The io-iwq sticks around until it notices that its workqueue is dying. But code after de_thread() relies on being single-threaded. Move io_uring_task_cancel() before de_thread() but behind the point of no return. Any worker created during task work run from io_uring_task_cancel() is just a sibling thread that de_thread() will take down and reap. Once io_uring_task_cancel() returned the thread has neither a task context nor a workqueue left so nothing can add a thread behind de_thread()'s back anymore. Suggested-by: Oleg Nesterov Link: https://lore.kernel.org/r/aq0_lvHDViIpX_Mu@redhat.com Fixes: 3bfe6106693b ("io-wq: fork worker threads from original task") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-12-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Reviewed-by: Jens Axboe Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 745f6eb5279e65..bf143ee3acb1a2 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1148,16 +1148,19 @@ int begin_new_exec(struct linux_binprm * bprm) */ bprm->point_of_no_return = true; + /* + * Cancel any io_uring activity across execve. This runs task work + * that may still create an io-wq worker, so do it while de_thread() + * can still zap it. + */ + io_uring_task_cancel(); + /* Make this the only thread in the thread group */ retval = de_thread(me); if (retval) goto out; /* see the comment in check_unsafe_exec() */ current->fs->in_exec = 0; - /* - * Cancel any io_uring activity across execve - */ - io_uring_task_cancel(); /* Ensure the files table is not shared. */ retval = unshare_files(); From e760200370e4de054b6b5f568a7faa911e2956bb Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:02 +0200 Subject: [PATCH 0724/2286] fork: move the coredump and exec checks into create_io_thread() Only a PF_USER_WORKER thread returns from get_signal() after a fatal signal and can go on to create a thread. get_signal() sets PF_SIGNALED before it dumps core and before it lets a PF_USER_WORKER thread return. That flags sticks. The exec'ing thread itself is never signaled. But it doesn't need the check anyway as we moved cancellation of io_uring requests before de_thread(). create_io_thread() is the only way a signaled thread creates another one. vhost_task_create() is reached from ioctls alone. io_should_retry_thread() doesn't retry -EINTR, so io-wq callers give up the same way they did when copy_process() refused. Suggested-by: Oleg Nesterov Link: https://lore.kernel.org/r/aq0VLTyxNatFmLOK@redhat.com Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-13-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/fork.c b/kernel/fork.c index 416758c8a3d431..0489d11656d9f9 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2700,6 +2700,10 @@ struct task_struct *create_io_thread(int (*fn)(void *), void *arg, int node) .user_worker = 1, }; + /* A creator past its fatal signal gets no thread. */ + if (current->flags & PF_SIGNALED) + return ERR_PTR(-EINTR); + return copy_process(NULL, 0, node, &args); } From 1f978881ce6c9ac4f4d4a7b6a6e52339eadcd350 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:03 +0200 Subject: [PATCH 0725/2286] fork: don't create io threads once PF_POSTCOREDUMP is set zap_process() skips every thread that already has PF_POSTCOREDUMP set. Such a thread is past synchronize_group_exit(), so a coredump can't catch it anymore. It isn't counted in core_state->threads_remaining and it isn't sent SIGKILL. do_exit() sets PF_POSTCOREDUMP in synchronize_group_exit() and calls io_uring_files_cancel() right after that. The cancellation runs task work and a create_worker_cb() that io-wq queued before the exit creates a new io-wq worker from there. If another thread started a coredump in the meantime that worker joins a thread-group which is already being dumped. zap_process() never saw it so it was never counted. But when it exits it sees signal->core_state in synchronize_group_exit() and decrements threads_remaining like any other thread. So the count reaches zero one thread early and the dumper leaves coredump_wait_inactive() while a thread it counted is still running. The task has no fatal signal pending because zap_process() deliberately didn't send it one, and it never went through get_signal() so it doesn't have PF_SIGNALED either. Refuse to create an io thread when the creator has PF_POSTCOREDUMP. That costs nothing. io_uring_files_cancel() raises IO_WQ_BIT_EXIT before it runs any task work, so a worker created from there only ever gets to exit again, and io_should_retry_thread() doesn't retry -EINTR. Clearing PF_POSTCOREDUMP for the new thread in copy_process() was the other option. It makes the worker a thread like any other, but it only helps while the dump hasn't started. A worker born after zap_process() has run is invisible to it whatever its flags say and still decrements threads_remaining on the way out. Refusing to create it covers both, and then no task is ever born with the flag, so there is nothing to clear. Moving io_uring_files_cancel() ahead of synchronize_group_exit() closes the same window. It runs the cancellation, and whatever that can block on, before the thread announces itself to the dumper. A thread that blocks there never reaches coredump_task_exit() at all, so the dumper ends up waiting for a thread that never parks. Refusing the creation leaves the cancellation where it is. But it's very ugly to run io_uring work even before we did all the generic exit work. Fixes: 92307383082d ("coredump: Don't perform any cleanups before dumping core") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-14-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 0489d11656d9f9..623eaead3747ef 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2700,8 +2700,8 @@ struct task_struct *create_io_thread(int (*fn)(void *), void *arg, int node) .user_worker = 1, }; - /* A creator past its fatal signal gets no thread. */ - if (current->flags & PF_SIGNALED) + /* A creator past its fatal signal or its coredump point gets no thread. */ + if (current->flags & (PF_SIGNALED | PF_POSTCOREDUMP)) return ERR_PTR(-EINTR); return copy_process(NULL, 0, node, &args); From c7de3d02e678490b3aa0796d9be7dfe5f259374a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:04 +0200 Subject: [PATCH 0726/2286] fork: use SIG_KERNEL_ONLY_MASK for the user worker signal mask For a user worker copy_process() blocks every signal except for SIGKILL and SIGSTOP. Use SIG_KERNEL_ONLY_MASK instead of open-coding it. No functional changes. Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-15-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 623eaead3747ef..1a20264e9b89c9 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -2140,12 +2140,9 @@ __latent_entropy struct task_struct *copy_process( if (args->kthread) p->flags |= PF_KTHREAD; if (args->user_worker) { - /* - * Mark us a user worker, and block any signal that isn't - * fatal or STOP - */ + /* A user worker takes only the signals nobody can block. */ p->flags |= PF_USER_WORKER; - siginitsetinv(&p->blocked, sigmask(SIGKILL)|sigmask(SIGSTOP)); + siginitsetinv(&p->blocked, SIG_KERNEL_ONLY_MASK); } if (args->io_thread) p->flags |= PF_IO_WORKER; From 2586c1ecbc749f37673f02df9ed65b6a4ad28601 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:05 +0200 Subject: [PATCH 0727/2286] signal: enforce the user worker signal mask in __set_task_blocked() User workers block every signal except for SIGKILL and SIGSTOP in copy_process(). So complete_signal() never picks a thread that blocks the signal and get_signal() never dequeues such a thread. Net effect is that user workers only exit on SIGKILL or stop on SIGSTOP. Before we fixed it a tracer could unblock a signal. For example, by unblocking SIGHUP a installing a handler complete_signal() could end up picking a user worker for a process-directed SIGHUP. Which effectively means the handler never runs. After blocking PTRACE_SETSIGMASK what remains is the in-kernel sigprocmask() and force_sig_info_to_task() changes. The in-kernel sigprocmask() users block more signals for a critical section and restore the saved mask afterwards. cifs used to do that in __smb_send_rqst() and ocfs2 in ocfs2_block_signals(). Both were reachable from an io-wq worker. Both only add SIGKILL and SIGSTOP to the user workers's and then put the fork-time mask back. force_sig_info_to_task() unblocks a signal so a target can't hide from the signal. A synchronous signal forced onto a user worker is accepted currently so let's leave that alone. Make it a rule that a user worker's signal mask can never drop below the copy_process() deafult. SIGKILL and SIGSTOP have the same rule in the other direction. rt_sigprocmask(), set_current_blocked() and PTRACE_SETSIGMASK strip them from whatever mask userspace asks for. Do the same for user worker mask in __set_task_blocked(). Add the fork-time mask back into the new set for a user worker and warn if that changed anything. Warn when a caller unblocks a signal for a user worker. No functional changes. Link: https://lore.kernel.org/r/aq_4fY6GVtK47Njq@redhat.com Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-16-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/signal.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/signal.c b/kernel/signal.c index 51e43e17d1c63b..12be8ef85d8239 100644 --- a/kernel/signal.c +++ b/kernel/signal.c @@ -3192,6 +3192,16 @@ long do_no_restart_syscall(struct restart_block *param) static void __set_task_blocked(struct task_struct *tsk, const sigset_t *newset) { + sigset_t floor, floored; + + /* A user worker never unblocks anything but SIGKILL and SIGSTOP. */ + if (unlikely(tsk->flags & PF_USER_WORKER)) { + siginitsetinv(&floor, SIG_KERNEL_ONLY_MASK); + sigorsets(&floored, newset, &floor); + WARN_ON_ONCE(!sigequalsets(&floored, newset)); + newset = &floored; + } + if (task_sigpending(tsk) && !thread_group_empty(tsk)) { sigset_t newblocked; /* A set of now blocked but previously unblocked signals. */ From 125a931f9d7f0fd93037f1c8c8154ab5b19678c8 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sun, 21 Jun 2026 19:59:07 +0900 Subject: [PATCH 0728/2286] rust: time: warn and assert out-of-range input in fsleep() and udelay() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fsleep() documents out-of-range input as a bug but does not check it, unlike udelay(). Add `debug_assert!` calls to catch it in debug builds. Also add `pr_warn_once!` calls in both fsleep() and udelay() so the clamped path is observable at runtime. Reviewed-by: Onur Özkan Reviewed-by: Andreas Hindborg Signed-off-by: Sang-Heon Jeon Link: https://msgid.link/20260621105909.4055210-1-ekffu200098@gmail.com Signed-off-by: Andreas Hindborg --- rust/kernel/time/delay.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/rust/kernel/time/delay.rs b/rust/kernel/time/delay.rs index b5b1b42797a0fe..4debe23731bf55 100644 --- a/rust/kernel/time/delay.rs +++ b/rust/kernel/time/delay.rs @@ -8,7 +8,10 @@ //! C header: [`include/linux/delay.h`](srctree/include/linux/delay.h). use super::Delta; -use crate::prelude::*; +use crate::{ + pr_warn_once, + prelude::*, // +}; /// Sleeps for a given duration at least. /// @@ -18,8 +21,7 @@ use crate::prelude::*; /// `delta` must be within `[0, i32::MAX]` microseconds; /// otherwise, it is erroneous behavior. That is, it is considered a bug /// to call this function with an out-of-range value, in which case the function -/// will sleep for at least the maximum value in the range and may warn -/// in the future. +/// will sleep for at least the maximum value in the range and warns once. /// /// The behavior above differs from the C side [`fsleep()`] for which out-of-range /// values mean "infinite timeout" instead. @@ -32,10 +34,13 @@ pub fn fsleep(delta: Delta) { // overflow inside fsleep, which could lead to unintentional infinite sleep. const MAX_DELTA: Delta = Delta::from_micros(i32::MAX as i64); + debug_assert!(delta.as_nanos() >= 0); + debug_assert!(delta <= MAX_DELTA); + let delta = if (Delta::ZERO..=MAX_DELTA).contains(&delta) { delta } else { - // TODO: Add WARN_ONCE() when it's supported. + pr_warn_once!("attempted to fsleep() with out of range delta\n"); MAX_DELTA }; @@ -54,7 +59,8 @@ pub fn fsleep(delta: Delta) { /// /// `delta` must be within `[0, MAX_UDELAY_MS]` in milliseconds; /// otherwise, it is erroneous behavior. That is, it is considered a bug to -/// call this function with an out-of-range value. +/// call this function with an out-of-range value, in which case the function +/// will delay for at least the maximum value in the range and warns once. /// /// The behavior above differs from the C side [`udelay()`] for which out-of-range /// values could lead to an overflow and unexpected behavior. @@ -69,6 +75,7 @@ pub fn udelay(delta: Delta) { let delta = if (Delta::ZERO..=MAX_UDELAY_DELTA).contains(&delta) { delta } else { + pr_warn_once!("attempted to udelay() with out of range delta\n"); MAX_UDELAY_DELTA }; From 17ca0bc06bb246d9488556f3f16ad6f37a4fc5ad Mon Sep 17 00:00:00 2001 From: Yilin Chen <1479826151@qq.com> Date: Wed, 8 Jul 2026 07:21:06 +0000 Subject: [PATCH 0729/2286] rust: configfs: fix release safety documentation ItemOperationsVTable, Data>::release() receives a pointer to a config_item, not a config_group. The old Safety docs described the pointer as a config_group embedded in Group. Update them to state that the pointer must be the cg_item field of a config_group embedded in a valid Group. Assisted-by: Codex:GPT-5 Signed-off-by: Yilin Chen <1479826151@qq.com> Link: https://msgid.link/tencent_9FAFC60382D724C608F0F6BFABFC63BB6707@qq.com Signed-off-by: Andreas Hindborg --- rust/kernel/configfs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs index cd082b83e9e745..7722035ebc020e 100644 --- a/rust/kernel/configfs.rs +++ b/rust/kernel/configfs.rs @@ -437,8 +437,8 @@ where { /// # Safety /// - /// `this` must be a pointer to a `bindings::config_group` embedded in a - /// `Group`. + /// `this` must be a pointer to the `cg_item` field of a `bindings::config_group` embedded in a + /// valid `Group`. /// /// This function will destroy the pointee of `this`. The pointee of `this` /// must not be accessed after the function returns. From 6a8e2c63f2fece3828ac09e4e434666de842acfe Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Wed, 22 Jul 2026 09:10:50 +0900 Subject: [PATCH 0730/2286] rust: time: rename ClockSource trait to ClockId The `ClockSource` trait has nothing to do with the C `struct clocksource` in `include/linux/clocksource.h`, which abstracts the hardware counter used as a source of time for the majority of the clockids. The trait instead carries a `clockid_t` `ID`, i.e. one of the IDs of "the various system clocks (for POSIX.1b interval timers)" as described in include/uapi/linux/time.h (CLOCK_MONOTONIC, CLOCK_REALTIME, ...). It thus plays the role of a `clockid_t`, and the `ClockSource` name overlaps confusingly with the C `clocksource` concept when reading across C and Rust code. Rename the trait to `ClockId` to reflect that it represents a `clockid_t`. This is a pure rename; there is no functional change. Suggested-by: John Stultz Signed-off-by: FUJITA Tomonori Link: https://lore.kernel.org/rust-for-linux/CANDhNCrKMdHCmL76LWCROVF2Ly-9NxmfmQ2T+P=iv34aVkO2uQ@mail.gmail.com/ Reviewed-by: Andreas Hindborg Link: https://msgid.link/20260722001050.4115708-1-tomo@flapping.org Signed-off-by: Andreas Hindborg --- rust/kernel/time.rs | 34 +++++++-------- rust/kernel/time/hrtimer.rs | 82 ++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs index 6c0a5e8090d00a..60717f17e9bab1 100644 --- a/rust/kernel/time.rs +++ b/rust/kernel/time.rs @@ -53,9 +53,9 @@ pub fn msecs_to_jiffies(msecs: Msecs) -> Jiffies { unsafe { bindings::__msecs_to_jiffies(msecs) } } -/// Trait for clock sources. +/// Trait for kernel clock identifiers. /// -/// Selection of the clock source depends on the use case. In some cases the usage of a +/// Selection of the clock depends on the use case. In some cases the usage of a /// particular clock is mandatory, e.g. in network protocols, filesystems. In other /// cases the user of the clock has to decide which clock is best suited for the /// purpose. In most scenarios clock [`Monotonic`] is the best choice as it @@ -66,13 +66,13 @@ pub fn msecs_to_jiffies(msecs: Msecs) -> Jiffies { /// Implementers must ensure that `ktime_get()` returns a value in the inclusive range /// `0..=KTIME_MAX` (i.e., greater than or equal to 0 and less than or equal to /// `KTIME_MAX`, where `KTIME_MAX` equals `i64::MAX`). -pub unsafe trait ClockSource { - /// The kernel clock ID associated with this clock source. +pub unsafe trait ClockId { + /// The kernel clock ID associated with this clock. /// /// This constant corresponds to the C side `clockid_t` value. const ID: bindings::clockid_t; - /// Get the current time from the clock source. + /// Get the current time from the clock. /// /// The function must return a value in the range `0..=KTIME_MAX`. fn ktime_get() -> bindings::ktime_t; @@ -93,7 +93,7 @@ pub struct Monotonic; // SAFETY: The kernel's `ktime_get()` is guaranteed to return a value // in `0..=KTIME_MAX`. -unsafe impl ClockSource for Monotonic { +unsafe impl ClockId for Monotonic { const ID: bindings::clockid_t = bindings::CLOCK_MONOTONIC as bindings::clockid_t; fn ktime_get() -> bindings::ktime_t { @@ -120,7 +120,7 @@ pub struct RealTime; // SAFETY: The kernel's `ktime_get_real()` is guaranteed to return a value // in `0..=KTIME_MAX`. -unsafe impl ClockSource for RealTime { +unsafe impl ClockId for RealTime { const ID: bindings::clockid_t = bindings::CLOCK_REALTIME as bindings::clockid_t; fn ktime_get() -> bindings::ktime_t { @@ -140,7 +140,7 @@ pub struct BootTime; // SAFETY: The kernel's `ktime_get_boottime()` is guaranteed to return a value // in `0..=KTIME_MAX`. -unsafe impl ClockSource for BootTime { +unsafe impl ClockId for BootTime { const ID: bindings::clockid_t = bindings::CLOCK_BOOTTIME as bindings::clockid_t; fn ktime_get() -> bindings::ktime_t { @@ -164,7 +164,7 @@ pub struct Tai; // SAFETY: The kernel's `ktime_get_clocktai()` is guaranteed to return a value // in `0..=KTIME_MAX`. -unsafe impl ClockSource for Tai { +unsafe impl ClockId for Tai { const ID: bindings::clockid_t = bindings::CLOCK_TAI as bindings::clockid_t; fn ktime_get() -> bindings::ktime_t { @@ -180,24 +180,24 @@ unsafe impl ClockSource for Tai { /// The `inner` value is in the range from 0 to `KTIME_MAX`. #[repr(transparent)] #[derive(PartialEq, PartialOrd, Eq, Ord)] -pub struct Instant { +pub struct Instant { inner: bindings::ktime_t, _c: PhantomData, } -impl Clone for Instant { +impl Clone for Instant { fn clone(&self) -> Self { *self } } -impl Copy for Instant {} +impl Copy for Instant {} -impl Instant { +impl Instant { /// Get the current time from the clock source. #[inline] pub fn now() -> Self { - // INVARIANT: The `ClockSource::ktime_get()` function returns a value in the range + // INVARIANT: The `ClockId::ktime_get()` function returns a value in the range // from 0 to `KTIME_MAX`. Self { inner: C::ktime_get(), @@ -239,7 +239,7 @@ impl Instant { } } -impl ops::Sub for Instant { +impl ops::Sub for Instant { type Output = Delta; // By the type invariant, it never overflows. @@ -251,7 +251,7 @@ impl ops::Sub for Instant { } } -impl ops::Add for Instant { +impl ops::Add for Instant { type Output = Self; #[inline] @@ -271,7 +271,7 @@ impl ops::Add for Instant { } } -impl ops::Sub for Instant { +impl ops::Sub for Instant { type Output = Self; #[inline] diff --git a/rust/kernel/time/hrtimer.rs b/rust/kernel/time/hrtimer.rs index 2d7f1131a8131d..87aafbee0ce958 100644 --- a/rust/kernel/time/hrtimer.rs +++ b/rust/kernel/time/hrtimer.rs @@ -403,14 +403,14 @@ //! //! [`Arc`]: kernel::sync::Arc -use super::{ClockSource, Delta, Instant}; +use super::{ClockId, Delta, Instant}; use crate::{prelude::*, types::Opaque}; use core::{marker::PhantomData, ptr::NonNull}; use pin_init::PinInit; /// A type-alias to refer to the [`Instant`] for a given `T` from [`HrTimer`]. /// -/// Where `C` is the [`ClockSource`] of the [`HrTimer`]. +/// Where `C` is the [`ClockId`] of the [`HrTimer`]. pub type HrTimerInstant = Instant<<>::TimerMode as HrTimerMode>::Clock>; /// A timer backed by a C `struct hrtimer`. @@ -855,7 +855,7 @@ pub trait HrTimerExpires { fn as_nanos(&self) -> i64; } -impl HrTimerExpires for Instant { +impl HrTimerExpires for Instant { #[inline] fn as_nanos(&self) -> i64 { Instant::::as_nanos(self) @@ -870,22 +870,22 @@ impl HrTimerExpires for Delta { } mod private { - use crate::time::ClockSource; + use crate::time::ClockId; pub trait Sealed {} - impl Sealed for super::AbsoluteMode {} - impl Sealed for super::RelativeMode {} - impl Sealed for super::AbsolutePinnedMode {} - impl Sealed for super::RelativePinnedMode {} - impl Sealed for super::AbsoluteSoftMode {} - impl Sealed for super::RelativeSoftMode {} - impl Sealed for super::AbsolutePinnedSoftMode {} - impl Sealed for super::RelativePinnedSoftMode {} - impl Sealed for super::AbsoluteHardMode {} - impl Sealed for super::RelativeHardMode {} - impl Sealed for super::AbsolutePinnedHardMode {} - impl Sealed for super::RelativePinnedHardMode {} + impl Sealed for super::AbsoluteMode {} + impl Sealed for super::RelativeMode {} + impl Sealed for super::AbsolutePinnedMode {} + impl Sealed for super::RelativePinnedMode {} + impl Sealed for super::AbsoluteSoftMode {} + impl Sealed for super::RelativeSoftMode {} + impl Sealed for super::AbsolutePinnedSoftMode {} + impl Sealed for super::RelativePinnedSoftMode {} + impl Sealed for super::AbsoluteHardMode {} + impl Sealed for super::RelativeHardMode {} + impl Sealed for super::AbsolutePinnedHardMode {} + impl Sealed for super::RelativePinnedHardMode {} } /// Operational mode of [`HrTimer`]. @@ -894,16 +894,16 @@ pub trait HrTimerMode: private::Sealed { const C_MODE: bindings::hrtimer_mode; /// Type representing the clock source. - type Clock: ClockSource; + type Clock: ClockId; /// Type representing the expiration specification (absolute or relative time). type Expires: HrTimerExpires; } /// Timer that expires at a fixed point in time. -pub struct AbsoluteMode(PhantomData); +pub struct AbsoluteMode(PhantomData); -impl HrTimerMode for AbsoluteMode { +impl HrTimerMode for AbsoluteMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS; type Clock = C; @@ -911,9 +911,9 @@ impl HrTimerMode for AbsoluteMode { } /// Timer that expires after a delay from now. -pub struct RelativeMode(PhantomData); +pub struct RelativeMode(PhantomData); -impl HrTimerMode for RelativeMode { +impl HrTimerMode for RelativeMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL; type Clock = C; @@ -921,8 +921,8 @@ impl HrTimerMode for RelativeMode { } /// Timer with absolute expiration time, pinned to its current CPU. -pub struct AbsolutePinnedMode(PhantomData); -impl HrTimerMode for AbsolutePinnedMode { +pub struct AbsolutePinnedMode(PhantomData); +impl HrTimerMode for AbsolutePinnedMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS_PINNED; type Clock = C; @@ -930,8 +930,8 @@ impl HrTimerMode for AbsolutePinnedMode { } /// Timer with relative expiration time, pinned to its current CPU. -pub struct RelativePinnedMode(PhantomData); -impl HrTimerMode for RelativePinnedMode { +pub struct RelativePinnedMode(PhantomData); +impl HrTimerMode for RelativePinnedMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL_PINNED; type Clock = C; @@ -939,8 +939,8 @@ impl HrTimerMode for RelativePinnedMode { } /// Timer with absolute expiration, handled in soft irq context. -pub struct AbsoluteSoftMode(PhantomData); -impl HrTimerMode for AbsoluteSoftMode { +pub struct AbsoluteSoftMode(PhantomData); +impl HrTimerMode for AbsoluteSoftMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS_SOFT; type Clock = C; @@ -948,8 +948,8 @@ impl HrTimerMode for AbsoluteSoftMode { } /// Timer with relative expiration, handled in soft irq context. -pub struct RelativeSoftMode(PhantomData); -impl HrTimerMode for RelativeSoftMode { +pub struct RelativeSoftMode(PhantomData); +impl HrTimerMode for RelativeSoftMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL_SOFT; type Clock = C; @@ -957,8 +957,8 @@ impl HrTimerMode for RelativeSoftMode { } /// Timer with absolute expiration, pinned to CPU and handled in soft irq context. -pub struct AbsolutePinnedSoftMode(PhantomData); -impl HrTimerMode for AbsolutePinnedSoftMode { +pub struct AbsolutePinnedSoftMode(PhantomData); +impl HrTimerMode for AbsolutePinnedSoftMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS_PINNED_SOFT; type Clock = C; @@ -966,8 +966,8 @@ impl HrTimerMode for AbsolutePinnedSoftMode { } /// Timer with absolute expiration, pinned to CPU and handled in soft irq context. -pub struct RelativePinnedSoftMode(PhantomData); -impl HrTimerMode for RelativePinnedSoftMode { +pub struct RelativePinnedSoftMode(PhantomData); +impl HrTimerMode for RelativePinnedSoftMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL_PINNED_SOFT; type Clock = C; @@ -975,8 +975,8 @@ impl HrTimerMode for RelativePinnedSoftMode { } /// Timer with absolute expiration, handled in hard irq context. -pub struct AbsoluteHardMode(PhantomData); -impl HrTimerMode for AbsoluteHardMode { +pub struct AbsoluteHardMode(PhantomData); +impl HrTimerMode for AbsoluteHardMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS_HARD; type Clock = C; @@ -984,8 +984,8 @@ impl HrTimerMode for AbsoluteHardMode { } /// Timer with relative expiration, handled in hard irq context. -pub struct RelativeHardMode(PhantomData); -impl HrTimerMode for RelativeHardMode { +pub struct RelativeHardMode(PhantomData); +impl HrTimerMode for RelativeHardMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL_HARD; type Clock = C; @@ -993,8 +993,8 @@ impl HrTimerMode for RelativeHardMode { } /// Timer with absolute expiration, pinned to CPU and handled in hard irq context. -pub struct AbsolutePinnedHardMode(PhantomData); -impl HrTimerMode for AbsolutePinnedHardMode { +pub struct AbsolutePinnedHardMode(PhantomData); +impl HrTimerMode for AbsolutePinnedHardMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_ABS_PINNED_HARD; type Clock = C; @@ -1002,8 +1002,8 @@ impl HrTimerMode for AbsolutePinnedHardMode { } /// Timer with relative expiration, pinned to CPU and handled in hard irq context. -pub struct RelativePinnedHardMode(PhantomData); -impl HrTimerMode for RelativePinnedHardMode { +pub struct RelativePinnedHardMode(PhantomData); +impl HrTimerMode for RelativePinnedHardMode { const C_MODE: bindings::hrtimer_mode = bindings::hrtimer_mode_HRTIMER_MODE_REL_PINNED_HARD; type Clock = C; From e7280ab196ba4bcef6d3f6ee860bf0d2c8e96e51 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:45:59 +0200 Subject: [PATCH 0731/2286] ACPI: bus: Use acpi/acpi_bus.h without CONFIG_ACPI Corrently acpi/acpi_bus.h is included in linux/acpi.h only if CONFIG_ACPI is set, but it contains declarations that are useful otherwise too. For consistency, modify linux/acpi.h to always include acpi/acpi_bus.h and update the latter to avoid exposing too much when CONFIG_ACPI is not set. Since acpi_has_method() is called from some code build when CONFIG_ACPI is unset, add a stub for it to the !CONFIG_ACPI block in acpi/acpi_bus.h. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3717753.iIbC2pHGDl@rafael.j.wysocki --- include/acpi/acpi_bus.h | 12 ++++++++---- include/linux/acpi.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b239..32837f507f4093 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -9,6 +9,8 @@ #ifndef __ACPI_BUS_H__ #define __ACPI_BUS_H__ +#ifdef CONFIG_ACPI + #include #include #include @@ -58,7 +60,6 @@ bool acpi_dock_match(acpi_handle handle); bool acpi_check_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 funcs); union acpi_object *acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 func, union acpi_object *argv4); -#ifdef CONFIG_ACPI bool acpi_get_physical_device_location(acpi_handle handle, struct acpi_pld_info **pld); @@ -77,7 +78,6 @@ acpi_evaluate_dsm_typed(acpi_handle handle, const guid_t *guid, u64 rev, return obj; } -#endif #define ACPI_INIT_DSM_ARGV4(cnt, eles) \ { \ @@ -90,8 +90,6 @@ bool acpi_dev_found(const char *hid); bool acpi_dev_present(const char *hid, const char *uid, s64 hrv); bool acpi_reduced_hardware(void); -#ifdef CONFIG_ACPI - struct proc_dir_entry; #define ACPI_BUS_FILE_ROOT "acpi" @@ -942,8 +940,14 @@ int acpi_wait_for_acpi_ipmi(void); int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices); u32 arch_acpi_add_auto_dep(acpi_handle handle); + #else /* CONFIG_ACPI */ +static inline bool acpi_has_method(acpi_handle handle, char *name) +{ + return false; +} + static inline struct device *acpi_bus_get_primary_device(struct acpi_device *adev) { return NULL; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac81209473..ba5234bd93a29a 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -25,6 +25,7 @@ struct irq_domain_ops; #define _LINUX #endif #include +#include #include #ifdef CONFIG_ACPI @@ -35,7 +36,6 @@ struct irq_domain_ops; #include #include -#include #include #include #include From 28bfd74601a4ac16310b0797c24e6220108c58d1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:46:37 +0200 Subject: [PATCH 0732/2286] ACPI: amba: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Hanjun Guo Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3362405.5fSG56mABF@rafael.j.wysocki --- drivers/acpi/arm64/amba.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 1350083bce5f1f..7ee431d9464ae1 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -87,11 +87,12 @@ static int amba_handler_attach(struct acpi_device *adev, * the amba device we are about to create. */ if (parent) - dev->dev.parent = acpi_get_first_physical_node(parent); + dev->dev.parent = acpi_bus_get_primary_device(parent); device_set_node(&dev->dev, acpi_fwnode_handle(adev)); ret = amba_device_add(dev, &iomem_resource); + put_device(dev->dev.parent); if (ret) { dev_err(&adev->dev, "%s(): amba_device_add() failed (%d)\n", __func__, ret); From 574799253d1598f7dec5f57a261a8773ac9179f9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:48:30 +0200 Subject: [PATCH 0733/2286] platform/x86: yogabook: Switch to use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Reviewed-by: Mark Pearson Link: https://patch.msgid.link/1973238.CQOukoFCf9@rafael.j.wysocki --- drivers/platform/x86/lenovo/yogabook.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c index 1a4b2ab1f35d66..8a91d440724b3c 100644 --- a/drivers/platform/x86/lenovo/yogabook.c +++ b/drivers/platform/x86/lenovo/yogabook.c @@ -353,13 +353,13 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context) goto error_put_devs; } - data->kbd_dev = get_device(acpi_get_first_physical_node(data->kbd_adev)); + data->kbd_dev = acpi_bus_get_primary_device(data->kbd_adev); if (!data->kbd_dev || !data->kbd_dev->driver) { r = -EPROBE_DEFER; goto error_put_devs; } - data->dig_dev = get_device(acpi_get_first_physical_node(data->dig_adev)); + data->dig_dev = acpi_bus_get_primary_device(data->dig_adev); if (!data->dig_dev || !data->dig_dev->driver) { r = -EPROBE_DEFER; goto error_put_devs; From 790b502a4b019a71f1c3e1d7904c7426ac2f2e11 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:49:07 +0200 Subject: [PATCH 0734/2286] platform/x86: serdev_helpers: Switch to use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Link: https://patch.msgid.link/2720286.Lt9SDvczpP@rafael.j.wysocki --- drivers/platform/x86/serdev_helpers.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/serdev_helpers.h b/drivers/platform/x86/serdev_helpers.h index 57eac75805e223..20267e1941f16a 100644 --- a/drivers/platform/x86/serdev_helpers.h +++ b/drivers/platform/x86/serdev_helpers.h @@ -74,8 +74,7 @@ get_serdev_controller(const char *serial_ctrl_hid, return ERR_PTR(-ENODEV); } - /* get_first_physical_node() returns a weak ref */ - parent = get_device(acpi_get_first_physical_node(adev)); + parent = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!parent) { pr_err("error could not get %s/%s serial-ctrl physical node\n", From 2058cd0f59caf475c8efad843b900a3119eb3a5c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:50:28 +0200 Subject: [PATCH 0735/2286] platform/x86: x86-android-tablets: Use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Link: https://patch.msgid.link/3912360.MHq7AAxBmi@rafael.j.wysocki --- drivers/platform/x86/x86-android-tablets/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index cfff7f5eac5d7d..2c262f117235b3 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -390,7 +390,6 @@ static int gpio_secondary_fwnode_init(struct device *parent, { const struct software_node *const *swnode; struct fwnode_handle *fwnode; - struct device *phys_dev; int ret; if (!node_group) @@ -418,7 +417,8 @@ static int gpio_secondary_fwnode_init(struct device *parent, if (WARN_ON(!fwnode)) return -ENOENT; - phys_dev = acpi_get_first_physical_node(to_acpi_device(dev)); + struct device *phys_dev __free(put_device) = + acpi_bus_get_primary_device(to_acpi_device(dev)); if (!phys_dev) return dev_err_probe(parent, -ENODEV, "No physical device for ACPI GPIO dev: %pfwP\n", From 7a4c8ddba1dcf7c63b329d81de1d494188e5d2b0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:49:49 +0200 Subject: [PATCH 0736/2286] platform/x86: apple-gmux: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Hans de Goede Link: https://patch.msgid.link/2287208.Mh6RI2rZIc@rafael.j.wysocki --- include/linux/apple-gmux.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/apple-gmux.h b/include/linux/apple-gmux.h index 206d97ffda79ae..d7939c3a08fdc4 100644 --- a/include/linux/apple-gmux.h +++ b/include/linux/apple-gmux.h @@ -109,7 +109,7 @@ static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, enum apple_gmux_ty if (!adev) return false; - dev = get_device(acpi_get_first_physical_node(adev)); + dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!dev) return false; From 37a78048d6161e13f61ab773dcbd6e7ba965e978 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:51:06 +0200 Subject: [PATCH 0737/2286] thunderbolt: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Mika Westerberg Link: https://patch.msgid.link/1941270.atdPhlSkOF@rafael.j.wysocki --- drivers/thunderbolt/acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 53546bc477a5b4..be91bc6049008d 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -17,8 +17,8 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, struct acpi_device *adev = acpi_fetch_acpi_dev(handle); struct fwnode_handle *fwnode; struct tb_nhi *nhi = data; + struct device *dev = NULL; struct pci_dev *pdev; - struct device *dev; if (!adev) return AE_OK; @@ -37,7 +37,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, * USB3 ports might not even have a physical device yet if xHCI driver * isn't bound yet. */ - dev = acpi_get_first_physical_node(adev); + dev = acpi_bus_get_primary_device(adev); if (!dev || !dev_is_pci(dev)) goto out_put; @@ -74,6 +74,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, } out_put: + put_device(dev); fwnode_handle_put(fwnode); return AE_OK; } From 6f50ab7ab1f93e874fa75d18bf98bbca4ce0204f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:53:51 +0200 Subject: [PATCH 0738/2286] ASoC: amd: acp-es8336: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. It also fixes a device reference counting issue that may lead to a premature device release if gpiod_get_optional() fails in st_es8336_late_probe(). Fixes: 02527c3f2300 ("ASoC: amd: add Machine driver for Jadeite platform") Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2063881.yKVeVyVuyW@rafael.j.wysocki --- sound/soc/amd/acp-es8336.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 9f3f11256788e2..34e953d1a3a76c 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -198,7 +198,7 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (!adev) return -ENODEV; - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_err(card->dev, "can not find codec dev\n"); From aae5bd00ced5b7e42ef3b3aa46923ad72388c60b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:54:29 +0200 Subject: [PATCH 0739/2286] ASoC: amd: acp3x-es83xx: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. It also fixes a device reference counting issue that may lead to a premature device release if devm_kzalloc() fails in acp3x_es83xx_probe(). Fixes: 54fcd9dd44b2 ("ASoC: amd: acp: Add machine driver that enables sound for systems with a ES8336 codec") Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2310746.NgBsaNRSFp@rafael.j.wysocki --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 3a640e65231420..c522b421e1ec2f 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -428,7 +428,7 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENXIO; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_warn(dev, "Error cannot find codec device, will defer probe\n"); From e1aee48abfaeed1fe1a88018066d2f06c98c065f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:55:15 +0200 Subject: [PATCH 0740/2286] ASoC: intel: boards: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Cezary Rojewski Acked-by: Mark Brown Link: https://patch.msgid.link/15844908.tv2OnDr8pf@rafael.j.wysocki --- sound/soc/intel/boards/bytcht_es8316.c | 4 ++-- sound/soc/intel/boards/bytcr_rt5640.c | 4 ++-- sound/soc/intel/boards/bytcr_rt5651.c | 4 ++-- sound/soc/intel/boards/cht_bsw_rt5645.c | 5 ++--- sound/soc/intel/boards/sof_cirrus_common.c | 2 +- sound/soc/intel/boards/sof_es8336.c | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c index ea387dc7427382..4d8d1d176a49f3 100644 --- a/sound/soc/intel/boards/bytcht_es8316.c +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -601,11 +601,11 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; /* override platform name, if required */ byt_cht_es8316_card.dev = dev; diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 40da3eea5fa72e..c84b9a0fe65a0a 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -1744,11 +1744,11 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (codec_dev) { - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; } else { /* * Special case for Android tablets where the codec i2c_client diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 62cb4856c7972d..a0ed56d285bcb0 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -943,11 +943,11 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; /* * swap SSP0 if bytcr is detected diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index 249be121be1574..4f20928a3604a8 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -529,7 +529,6 @@ static int snd_cht_mc_probe(struct platform_device *pdev) const char *platform_name; struct cht_mc_private *drv; struct acpi_device *adev; - struct device *codec_dev; bool sof_parent; bool found = false; bool is_bytcr = false; @@ -583,8 +582,8 @@ static int snd_cht_mc_probe(struct platform_device *pdev) return -ENOENT; } - /* acpi_get_first_physical_node() returns a borrowed ref, no need to deref */ - codec_dev = acpi_get_first_physical_node(adev); + struct device *codec_dev __free(put_device) = acpi_bus_get_primary_device(adev); + acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index 88fc6cb2bfd49b..a4ecb1f9a61549 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -168,7 +168,7 @@ static int cs35l41_compute_codec_conf(void) cs35l41_name_prefixes[uid]); continue; } - physdev = get_device(acpi_get_first_physical_node(adev)); + physdev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) { pr_devel("Cannot find physical node for HID %s UID %u (%s)\n", CS35L41_HID, diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index f1e62c2e79feff..d40f10c6ae54a7 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -702,11 +702,11 @@ static int sof_es8336_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; ret = snd_soc_fixup_dai_links_platform_name(&sof_es8336_card, mach->mach_params.platform); From ec6bb46cf5f70b7df2b2d87bc3a7fbe55f60abc2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:55:56 +0200 Subject: [PATCH 0741/2286] ASoC: loongson: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2796562.BddDVKsqQX@rafael.j.wysocki --- sound/soc/loongson/loongson_card.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 6422cc1703b6ad..9f390727846298 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -196,7 +196,6 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) struct snd_soc_card *card = &data->snd_card; const char *codec_dai_name; struct acpi_device *adev; - struct device *phy_dev; int i, ret; /* fixup platform name based on reference node */ @@ -204,7 +203,7 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) if (!adev) return -ENOENT; - phy_dev = acpi_get_first_physical_node(adev); + struct device *phy_dev __free(put_device) = acpi_bus_get_primary_device(adev); if (!phy_dev) return -EPROBE_DEFER; From 7df0f9ee9c5a1aa70b9b34ec719324d2961cb7a7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:52:32 +0200 Subject: [PATCH 0742/2286] ALSA: hda: aw88399: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Marco Giunta Link: https://patch.msgid.link/2568518.jE0xQCEvom@rafael.j.wysocki --- sound/hda/codecs/side-codecs/aw88399_hda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 11cef49230242d..439ad45557a4d2 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -209,8 +209,7 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) return -ENODEV; } - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) return -ENODEV; From 0fa8ad52b6e3e0838a6c865d1159b6409621d91b Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 22 Sep 2026 16:26:34 +0100 Subject: [PATCH 0743/2286] ACPI: bus: Provide !ACPI stub for acpi_device_uid() While the likes of acpi_dev_uid_to_integer() and acpi_dev_uid_match() already have stubs for !ACPI, the basic acpi_device_uid() getter does not. Although calls in portable drivers should already be conditional on a valid acpi_device in a manner which would usually lead to compile- time elimination anyway, add a stub for the sake of completeness. Signed-off-by: Robin Murphy [ rjw: Subject tweak ] Link: https://patch.msgid.link/8325277ff0484ab869fecf662a7ea356cd00bcde.1790090794.git.robin.murphy@arm.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 32837f507f4093..dca4df750c5481 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -963,6 +963,11 @@ static inline const char *acpi_device_hid(struct acpi_device *device) return ""; } +static inline char *acpi_device_uid(struct acpi_device *device) +{ + return NULL; +} + static inline bool acpi_get_physical_device_location(acpi_handle handle, struct acpi_pld_info **pld) { From 50fb9e04c9b1fe822baccba57801e2bc7795552e Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Tue, 25 Aug 2026 21:14:00 +0800 Subject: [PATCH 0744/2286] cpufreq/amd-pstate: Restore previous EPP if profile_name allocation fails amd_pstate_set_dynamic_epp() first applies the EPP value derived from the current platform profile via amd_pstate_set_epp() and only then allocates the profile name and registers the platform profile handler and the power supply notifier. All failures after the EPP has been programmed jump to the cleanup label, which restores the previously programmed EPP value. However, if the kasprintf() of the profile name fails, the function returns -ENOMEM directly, bypassing the cleanup label, so the EPP value programmed above is never rolled back even though enabling dynamic EPP has failed. Make the allocation failure path jump to the cleanup label as well, so that the previous EPP value is restored consistently on all error paths. Fixes: b7294b627598 ("cpufreq/amd-pstate: Add dynamic EPP as an "energy_performance_preference" mode") Signed-off-by: Xueqin Luo Link: https://lore.kernel.org/r/20260825131400.1509428-1-luoxueqin@kylinos.cn Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 83960a6336b9a6..8b2eb72c397323 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1381,8 +1381,10 @@ static int amd_pstate_set_dynamic_epp(struct cpufreq_policy *policy) return ret; cpudata->profile_name = kasprintf(GFP_KERNEL, "amd-pstate-epp-cpu%d", cpudata->cpu); - if (!cpudata->profile_name) - return -ENOMEM; + if (!cpudata->profile_name) { + ret = -ENOMEM; + goto cleanup; + } cpudata->ppdev = platform_profile_register(get_cpu_device(policy->cpu), cpudata->profile_name, From b400642c5e93863ec17ea15fcbd3efc81c299531 Mon Sep 17 00:00:00 2001 From: Wentao Guan Date: Fri, 18 Sep 2026 17:09:10 +0800 Subject: [PATCH 0745/2286] cpufreq/amd-pstate: Skip auto_sel write when it already matches the mode Since commit 9dfd13f80c85 ("cpufreq/amd-pstate: Toggle auto_sel in active mode on shared memory systems"), shmem_init_perf() programs AUTONOMOUS_SELECTION_ENABLE unconditionally, including in active mode. Platforms that exclusively support autonomous selection report this field as an Integer of 1, as required by the ACPI specification, instead of as a register descriptor. cppc_get_reg_val() reads such a field back successfully, but cppc_set_reg_val() rejects the write with -EOPNOTSUPP because the entry is not an ACPI_TYPE_BUFFER. shmem_init_perf() propagates that error, so every amd_pstate_epp_cpu_init() fails, cpufreq_register_driver() ends up with an empty policy list and returns -ENODEV, and amd-pstate refuses to load at all: amd_pstate: failed to set auto_sel, ret: -95 amd_pstate: Failed to initialize CPU 0: -95 ... amd_pstate: failed to register with return -19 Skip the write when the register already holds the value the requested mode needs. Active and guided mode are fixed on these platforms, which report auto_sel as 1, while passive mode still fails to load on a platform that cannot disable autonomous selection, exactly as it did before the offending commit. Fixes: 9dfd13f80c85 ("cpufreq/amd-pstate: Toggle auto_sel in active mode on shared memory systems") Cc: stable@vger.kernel.org Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Tested-by: LFRon Signed-off-by: Wentao Guan Link: https://lore.kernel.org/r/20260918090909.86544-1-guanwentao@uniontech.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 8b2eb72c397323..c93a4b765dffde 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -609,8 +609,10 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) return 0; } - ret = cppc_set_auto_sel(cpudata->cpu, - (cppc_state == AMD_PSTATE_PASSIVE) ? 0 : 1); + if ((cppc_state != AMD_PSTATE_PASSIVE) == auto_sel) + return 0; + + ret = cppc_set_auto_sel(cpudata->cpu, !auto_sel); if (ret) pr_warn("failed to set auto_sel, ret: %d\n", ret); From 2cb947450e0e5a88bb6bb5fca6dbfda1910e74b0 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Mon, 21 Sep 2026 12:19:39 -0300 Subject: [PATCH 0746/2286] nilfs2: fix checkpoint root lifetime on sysfs errors nilfs_find_or_create_root() links a new checkpoint root into the checkpoint tree and drops ns_cptree_lock before creating the root's sysfs group. If nilfs_sysfs_create_snapshot_group() then fails, the root is freed while it is still reachable from ns_cptree, so a concurrent nilfs_lookup_root() can dereference freed memory. nilfs_sysfs_create_snapshot_group() allocates memory and creates sysfs nodes and may sleep, so it cannot run under the ns_cptree_lock spinlock. Reorder the creation path so that the sysfs group is set up before the root is published: initialize the root, create its sysfs group outside the lock, and only then take ns_cptree_lock to link it into the tree. On sysfs failure the root was never visible and is freed directly; in the unlikely case a root with the same checkpoint number is already present, the sysfs group of the new root is removed before it is freed. Concurrent insertions are already serialized by ns_snapshot_mount_mutex, so only the race against nilfs_lookup_root() needs to be closed. Because kobject_put() does not guarantee that the embedded kobject's release callback has run by the time it returns (CONFIG_DEBUG_KOBJECT_RELEASE defers it), the container must not be freed until the release completes. Keep that wait_for_completion() of snapshot_kobj_unregister inside the sysfs helpers, so both the creation error path and nilfs_sysfs_delete_snapshot_group() wait for the release before their callers free the root. Fixes: dd70edbde262 ("nilfs2: integrate sysfs support into driver") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Acked-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/sysfs.c | 5 ++++- fs/nilfs2/the_nilfs.c | 34 +++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/fs/nilfs2/sysfs.c b/fs/nilfs2/sysfs.c index bc52afbfc5c739..5bb74a45ad1ed1 100644 --- a/fs/nilfs2/sysfs.c +++ b/fs/nilfs2/sysfs.c @@ -195,8 +195,10 @@ int nilfs_sysfs_create_snapshot_group(struct nilfs_root *root) "%llu", root->cno); } - if (err) + if (err) { kobject_put(&root->snapshot_kobj); + wait_for_completion(&root->snapshot_kobj_unregister); + } return err; } @@ -204,6 +206,7 @@ int nilfs_sysfs_create_snapshot_group(struct nilfs_root *root) void nilfs_sysfs_delete_snapshot_group(struct nilfs_root *root) { kobject_put(&root->snapshot_kobj); + wait_for_completion(&root->snapshot_kobj_unregister); } /************************************************************************ diff --git a/fs/nilfs2/the_nilfs.c b/fs/nilfs2/the_nilfs.c index f3805e7aabeb5e..38c6d4afefba9a 100644 --- a/fs/nilfs2/the_nilfs.c +++ b/fs/nilfs2/the_nilfs.c @@ -887,6 +887,26 @@ nilfs_find_or_create_root(struct the_nilfs *nilfs, __u64 cno) if (!new) return NULL; + new->cno = cno; + new->ifile = NULL; + new->nilfs = nilfs; + refcount_set(&new->count, 1); + atomic64_set(&new->inodes_count, 0); + atomic64_set(&new->blocks_count, 0); + + /* + * Register the sysfs group before publishing the root in the + * checkpoint tree. nilfs_sysfs_create_snapshot_group() can sleep, + * so it must run outside ns_cptree_lock; creating it first also + * ensures a concurrent nilfs_lookup_root() can never observe a root + * whose sysfs registration later fails and gets freed. + */ + err = nilfs_sysfs_create_snapshot_group(new); + if (err) { + kfree(new); + return NULL; + } + spin_lock(&nilfs->ns_cptree_lock); p = &nilfs->ns_cptree.rb_node; @@ -903,29 +923,17 @@ nilfs_find_or_create_root(struct the_nilfs *nilfs, __u64 cno) } else { refcount_inc(&root->count); spin_unlock(&nilfs->ns_cptree_lock); + nilfs_sysfs_delete_snapshot_group(new); kfree(new); return root; } } - new->cno = cno; - new->ifile = NULL; - new->nilfs = nilfs; - refcount_set(&new->count, 1); - atomic64_set(&new->inodes_count, 0); - atomic64_set(&new->blocks_count, 0); - rb_link_node(&new->rb_node, parent, p); rb_insert_color(&new->rb_node, &nilfs->ns_cptree); spin_unlock(&nilfs->ns_cptree_lock); - err = nilfs_sysfs_create_snapshot_group(new); - if (err) { - kfree(new); - new = NULL; - } - return new; } From f0d8c34b3b5e845113581d7d3b24ddaa61bce1f0 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 09:12:51 +0000 Subject: [PATCH 0747/2286] EDAC/i10nm: Fix mdev reference leak in i10nm_get_ddr_munits() In i10nm_get_ddr_munits(), get_ddr_munit() returns a PCI device with its reference count incremented. If ioremap() fails to map the MMIO space of the memory controller, the function returns -ENODEV without putting the reference held by mdev, leaking it. Put the device reference before returning on the ioremap failure path. [Tony: Dropped the Cc: stable tag] Fixes: c545f5e41225 ("EDAC/i10nm: Skip the absent memory controllers") Signed-off-by: Wentao Liang Signed-off-by: Tony Luck Reviewed-by: Qiuxu Zhuo Link: https://patch.msgid.link/20260916091251.1997522-1-vulab@iscas.ac.cn --- drivers/edac/i10nm_base.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/edac/i10nm_base.c b/drivers/edac/i10nm_base.c index f3f8e4e00148aa..87e541c3bf4bbf 100644 --- a/drivers/edac/i10nm_base.c +++ b/drivers/edac/i10nm_base.c @@ -635,6 +635,7 @@ static int i10nm_get_ddr_munits(void) mbase = ioremap(base + off, size); if (!mbase) { + pci_dev_put(mdev); i10nm_printk(KERN_ERR, "Failed to ioremap 0x%llx\n", base + off); return -ENODEV; From 94509668fccfa098e0a3d57e7093a9afd88aea9f Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Mon, 21 Sep 2026 22:36:32 -0300 Subject: [PATCH 0748/2286] x86/boot: Remove redundant "cc" clobber in memcmp() and document it The "cc" clobber remains recognized for source compatibility, but it has no meaning anymore; it is automatically generated without condition-code constraints: https://lore.kernel.org/all/57b0d188-b256-bde4-43e6-99dae4f59d60@suse.de Comment out the redundant "cc" clobber for documentation purposes. [ bp: Massage commit message. ] Fixes: a8c171c107c0 ("x86/boot: Add volatile, clobbers and zero-length test in memcmp()") Reported-by: "H. Peter Anvin" Signed-off-by: Mauricio Faria de Oliveira Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260921-pvh-kasan-inline-v10-1-08da47943d8e@igalia.com --- arch/x86/boot/string.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86/boot/string.c b/arch/x86/boot/string.c index 1632d40e1f545a..e10260ed58b69f 100644 --- a/arch/x86/boot/string.c +++ b/arch/x86/boot/string.c @@ -36,11 +36,15 @@ int memcmp(const void *s1, const void *s2, size_t len) /* * Make sure ZF is properly set in the len==0 case because in it, * RCX==0 and the REPE; CMPSB won't get executed. + * + * The "cc" clobber has no meaning anymore, just source compatibility. + * On x86 the flag status bits are automatically added to the clobber + * set when there are no =@ccXY constraints. Keep it as documentation. */ asm volatile("test %3, %3\n\t" "repe cmpsb" : "=@ccnz" (diff), "+D" (s1), "+S" (s2), "+c" (len) - : : "cc", "memory"); + : : /* "cc", */ "memory"); return diff; } From 5b6fe00406ce967c556d544a7fc80233341c67c8 Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Mon, 21 Sep 2026 22:36:33 -0300 Subject: [PATCH 0749/2286] x86/asm, x86/boot: Carve out inline memcmp() into a separate header Move the inline memcmp() function currently only available in boot/string.c into the shared string function header to be reused. This is not done through to avoid pulling unnecessary code in boot/string.c that causes build errors in boot/compressed/string.c and purgatory/purgatory.ro. No functional changes. [ bp: Massage commit message. ] Signed-off-by: Mauricio Faria de Oliveira Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260921-pvh-kasan-inline-v10-2-08da47943d8e@igalia.com --- arch/x86/boot/string.c | 17 ++------------- arch/x86/include/asm/shared/string.h | 31 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 arch/x86/include/asm/shared/string.h diff --git a/arch/x86/boot/string.c b/arch/x86/boot/string.c index e10260ed58b69f..be454a6864225f 100644 --- a/arch/x86/boot/string.c +++ b/arch/x86/boot/string.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "ctype.h" #include "string.h" @@ -31,21 +32,7 @@ int memcmp(const void *s1, const void *s2, size_t len) { - bool diff; - - /* - * Make sure ZF is properly set in the len==0 case because in it, - * RCX==0 and the REPE; CMPSB won't get executed. - * - * The "cc" clobber has no meaning anymore, just source compatibility. - * On x86 the flag status bits are automatically added to the clobber - * set when there are no =@ccXY constraints. Keep it as documentation. - */ - asm volatile("test %3, %3\n\t" - "repe cmpsb" - : "=@ccnz" (diff), "+D" (s1), "+S" (s2), "+c" (len) - : : /* "cc", */ "memory"); - return diff; + return __inline_memcmp(s1, s2, len); } /* diff --git a/arch/x86/include/asm/shared/string.h b/arch/x86/include/asm/shared/string.h new file mode 100644 index 00000000000000..6291653fe629ba --- /dev/null +++ b/arch/x86/include/asm/shared/string.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_SHARED_STRING_H +#define _ASM_X86_SHARED_STRING_H + +/* + * Returns: 0 (equal) + * 1 (not equal) + * + * In contrast, the regular memcmp() follows glibc return value semantics. + */ +static __always_inline int __inline_memcmp(const void *s1, const void *s2, size_t len) +{ + bool diff; + + /* + * Make sure ZF is properly set in the len==0 case because in it, + * RCX==0 and the REPE; CMPSB won't get executed. + * + * The "cc" clobber has no meaning anymore, just source compatibility. + * On x86 the flag status bits are automatically added to the clobber + * set when there are no =@ccXY constraints. Keep it as documentation. + */ + asm volatile("test %3, %3\n\t" + "repe cmpsb" + : "=@ccnz" (diff), "+D" (s1), "+S" (s2), "+c" (len) + : : /* "cc", */ "memory"); + + return diff; +} + +#endif /* _ASM_X86_SHARED_STRING_H */ From 85f11c17b261772cddc4606f98f6c604aded4607 Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Mon, 21 Sep 2026 22:36:34 -0300 Subject: [PATCH 0750/2286] x86/asm: Group inline string functions Group the __inline string functions in the same header. Use since __inline_memcmp() must remain there for use by arch/x86/boot/string.c. No functional changes. Signed-off-by: Mauricio Faria de Oliveira Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260921-pvh-kasan-inline-v10-3-08da47943d8e@igalia.com --- arch/x86/include/asm/shared/string.h | 21 +++++++++++++++++++++ arch/x86/include/asm/string.h | 21 +-------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/arch/x86/include/asm/shared/string.h b/arch/x86/include/asm/shared/string.h index 6291653fe629ba..6bef90d62a2113 100644 --- a/arch/x86/include/asm/shared/string.h +++ b/arch/x86/include/asm/shared/string.h @@ -2,6 +2,27 @@ #ifndef _ASM_X86_SHARED_STRING_H #define _ASM_X86_SHARED_STRING_H +static __always_inline void *__inline_memcpy(void *to, const void *from, size_t len) +{ + void *ret = to; + + asm volatile("rep movsb" + : "+D" (to), "+S" (from), "+c" (len) + : : "memory"); + return ret; +} + +static __always_inline void *__inline_memset(void *s, int v, size_t n) +{ + void *ret = s; + + asm volatile("rep stosb" + : "+D" (s), "+c" (n) + : "a" ((uint8_t)v) + : "memory"); + return ret; +} + /* * Returns: 0 (equal) * 1 (not equal) diff --git a/arch/x86/include/asm/string.h b/arch/x86/include/asm/string.h index 9cb5aae7fba9ff..dbf59f0d4cca71 100644 --- a/arch/x86/include/asm/string.h +++ b/arch/x86/include/asm/string.h @@ -8,25 +8,6 @@ # include #endif -static __always_inline void *__inline_memcpy(void *to, const void *from, size_t len) -{ - void *ret = to; - - asm volatile("rep movsb" - : "+D" (to), "+S" (from), "+c" (len) - : : "memory"); - return ret; -} - -static __always_inline void *__inline_memset(void *s, int v, size_t n) -{ - void *ret = s; - - asm volatile("rep stosb" - : "+D" (s), "+c" (n) - : "a" ((uint8_t)v) - : "memory"); - return ret; -} +#include #endif /* _ASM_X86_STRING_H */ From b24bc9dec9c89d01ccb675a55cda6831dc048ce9 Mon Sep 17 00:00:00 2001 From: Mauricio Faria de Oliveira Date: Mon, 21 Sep 2026 22:36:35 -0300 Subject: [PATCH 0751/2286] x86/pvh: Really inline memcmp() and memset() to fix unbootable VMs Even with __builtin the compiler may decide to use the out of line function instead of the inline implementation. The existing code is broken with gcc-14/15 but not gcc-12/13 (Ubuntu 25.10) and vmlinux no longer boots with CONFIG_PVH if CONFIG_KASAN_GENERIC is set. The instrumented out of line function performs a memory access to a region not yet initialized by KASAN, as it is still in the PVH kernel entry point. For testing purposes, if the size argument in cpuid_base_hypervisor() is reduced from 12 to 8 the compiler decides to use the inline implementation. In xen_prepare_pvh(), it (still) decides to use the inline implementation (at least in these compiler versions), but it is not guaranteed to remain. Switch the builtin to the inline implementation to address this. [ bp: Massage commit message. ] Fixes: 416a33c9afce ("x86/cpu: fix unbootable VMs by inlining memcmp() in hypervisor_cpuid_base()") Fixes: fbe5a6dfe492 ("xen, pvh: fix unbootable VMs by inlining memset() in xen_prepare_pvh()") Signed-off-by: Mauricio Faria de Oliveira Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260921-pvh-kasan-inline-v10-4-08da47943d8e@igalia.com --- arch/x86/include/asm/cpuid/api.h | 2 +- arch/x86/platform/pvh/enlighten.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/cpuid/api.h b/arch/x86/include/asm/cpuid/api.h index 82eddfa2347b32..2d9f3d4d63de6e 100644 --- a/arch/x86/include/asm/cpuid/api.h +++ b/arch/x86/include/asm/cpuid/api.h @@ -204,7 +204,7 @@ static inline u32 cpuid_base_hypervisor(const char *sig, u32 leaves) * from PVH early boot code before instrumentation is set up * and memcmp() itself may be instrumented. */ - if (!__builtin_memcmp(sig, signature, 12) && + if (!__inline_memcmp(sig, signature, 12) && (leaves == 0 || ((eax - base) >= leaves))) return base; } diff --git a/arch/x86/platform/pvh/enlighten.c b/arch/x86/platform/pvh/enlighten.c index f2053cbe9b0ce3..cb442cbd9d8286 100644 --- a/arch/x86/platform/pvh/enlighten.c +++ b/arch/x86/platform/pvh/enlighten.c @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -129,7 +130,7 @@ void __init xen_prepare_pvh(void) * This must not compile to "call memset" because memset() may be * instrumented. */ - __builtin_memset(&pvh_bootparams, 0, sizeof(pvh_bootparams)); + __inline_memset(&pvh_bootparams, 0, sizeof(pvh_bootparams)); hypervisor_specific_init(xen_guest); From 2163b6a8f2f2d5f6a0ae4983695f7090bb6abe2f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:26 +0200 Subject: [PATCH 0752/2286] userns: pass const uid_gid_map in lookup helpers map_id_down(), map_id_up() and map_id_range_up() search the extents of an idmapping and never modify it. Let all relevant helpers pass a const struct uid_gid_map. Callers can now pass pass struct mnt_idmap as const and pass down &idmap->uid_map and &idmap->gid_map. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-1-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- include/linux/uidgid.h | 12 ++++++------ kernel/user_namespace.c | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h index 2dc767e08f5447..02403629b49f1f 100644 --- a/include/linux/uidgid.h +++ b/include/linux/uidgid.h @@ -130,9 +130,9 @@ static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid) return from_kgid(ns, gid) != (gid_t) -1; } -u32 map_id_down(struct uid_gid_map *map, u32 id); -u32 map_id_up(struct uid_gid_map *map, u32 id); -u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count); +u32 map_id_down(const struct uid_gid_map *map, u32 id); +u32 map_id_up(const struct uid_gid_map *map, u32 id); +u32 map_id_range_up(const struct uid_gid_map *map, u32 id, u32 count); #else @@ -182,17 +182,17 @@ static inline bool kgid_has_mapping(struct user_namespace *ns, kgid_t gid) return gid_valid(gid); } -static inline u32 map_id_down(struct uid_gid_map *map, u32 id) +static inline u32 map_id_down(const struct uid_gid_map *map, u32 id) { return id; } -static inline u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count) +static inline u32 map_id_range_up(const struct uid_gid_map *map, u32 id, u32 count) { return id; } -static inline u32 map_id_up(struct uid_gid_map *map, u32 id) +static inline u32 map_id_up(const struct uid_gid_map *map, u32 id) { return id; } diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 0bed462e9b2a24..55b6bd75624bd3 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -278,8 +278,8 @@ static int cmp_map_id(const void *k, const void *e) * map_id_range_down_max - Find idmap via binary search in ordered idmap array. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_down_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_down_max(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { struct idmap_key key; @@ -296,8 +296,8 @@ map_id_range_down_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 cou * Can only be called if number of mappings is equal or less than * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_down_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_down_base(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { unsigned idx; u32 first, last, id2; @@ -315,9 +315,9 @@ map_id_range_down_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 co return NULL; } -static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) +static u32 map_id_range_down(const struct uid_gid_map *map, u32 id, u32 count) { - struct uid_gid_extent *extent; + const struct uid_gid_extent *extent; unsigned extents = map->nr_extents; smp_rmb(); @@ -335,7 +335,7 @@ static u32 map_id_range_down(struct uid_gid_map *map, u32 id, u32 count) return id; } -u32 map_id_down(struct uid_gid_map *map, u32 id) +u32 map_id_down(const struct uid_gid_map *map, u32 id) { return map_id_range_down(map, id, 1); } @@ -345,8 +345,8 @@ u32 map_id_down(struct uid_gid_map *map, u32 id) * Can only be called if number of mappings is equal or less than * UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_up_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_up_base(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { unsigned idx; u32 first, last, id2; @@ -368,8 +368,8 @@ map_id_range_up_base(unsigned extents, struct uid_gid_map *map, u32 id, u32 coun * map_id_up_max - Find idmap via binary search in ordered idmap array. * Can only be called if number of mappings exceeds UID_GID_MAP_MAX_BASE_EXTENTS. */ -static struct uid_gid_extent * -map_id_range_up_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count) +static const struct uid_gid_extent * +map_id_range_up_max(unsigned extents, const struct uid_gid_map *map, u32 id, u32 count) { struct idmap_key key; @@ -381,9 +381,9 @@ map_id_range_up_max(unsigned extents, struct uid_gid_map *map, u32 id, u32 count sizeof(struct uid_gid_extent), cmp_map_id); } -u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count) +u32 map_id_range_up(const struct uid_gid_map *map, u32 id, u32 count) { - struct uid_gid_extent *extent; + const struct uid_gid_extent *extent; unsigned extents = map->nr_extents; smp_rmb(); @@ -401,7 +401,7 @@ u32 map_id_range_up(struct uid_gid_map *map, u32 id, u32 count) return id; } -u32 map_id_up(struct uid_gid_map *map, u32 id) +u32 map_id_up(const struct uid_gid_map *map, u32 id) { return map_id_range_up(map, id, 1); } From c0d8c08327d3087a4cc0f2a3d572da6a7035120b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:27 +0200 Subject: [PATCH 0753/2286] fs: port mnt_idmap_{get,put}() to const mnt_idmap struct mnt_idmap is immutable once alloc_mnt_idmap() has set it up. The only thing that changes afterwards is the reference count. Model mnt_idmap_{get,put}() after struct cred's reference count handling and take a const pointer. This model is forced upon us because ceph stores the idmapping in mds requests and need to be able to take and put references. mnt_idmap_get() keeps returning a non-const pointer for now because struct vfsmount still stores a non-const pointer. We can only change that at the end of the series once every user of mnt_idmap() has been ported. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-2-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/internal.h | 4 ++-- fs/mnt_idmapping.c | 16 ++++++++++------ include/linux/mnt_idmapping.h | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/fs/internal.h b/fs/internal.h index c658c8a5ebd569..581aaa2d651031 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -327,8 +327,8 @@ ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *po * fs/attr.c */ struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns); -struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap); -void mnt_idmap_put(struct mnt_idmap *idmap); +struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); +void mnt_idmap_put(const struct mnt_idmap *idmap); struct stashed_operations { struct dentry *(*stash_dentry)(struct dentry **stashed, struct dentry *dentry); diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c index cb61fbdb52e906..cc6c5f1cfba881 100644 --- a/fs/mnt_idmapping.c +++ b/fs/mnt_idmapping.c @@ -312,12 +312,14 @@ struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns) * * Return: @idmap with reference count bumped if @not_mnt_idmap isn't passed. */ -struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap) +struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap) { + struct mnt_idmap *nonconst_idmap = (struct mnt_idmap *)idmap; + if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap) - refcount_inc(&idmap->count); + refcount_inc(&nonconst_idmap->count); - return idmap; + return nonconst_idmap; } EXPORT_SYMBOL_GPL(mnt_idmap_get); @@ -328,11 +330,13 @@ EXPORT_SYMBOL_GPL(mnt_idmap_get); * If this is a non-initial idmapping, put the reference count when a mount is * released and free it if we're the last user. */ -void mnt_idmap_put(struct mnt_idmap *idmap) +void mnt_idmap_put(const struct mnt_idmap *idmap) { + struct mnt_idmap *nonconst_idmap = (struct mnt_idmap *)idmap; + if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap && - refcount_dec_and_test(&idmap->count)) - free_mnt_idmap(idmap); + refcount_dec_and_test(&nonconst_idmap->count)) + free_mnt_idmap(nonconst_idmap); } EXPORT_SYMBOL_GPL(mnt_idmap_put); diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h index e71a6070a8f8c3..e5bee8b4109410 100644 --- a/include/linux/mnt_idmapping.h +++ b/include/linux/mnt_idmapping.h @@ -121,8 +121,8 @@ static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid) int vfsgid_in_group_p(vfsgid_t vfsgid); -struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap); -void mnt_idmap_put(struct mnt_idmap *idmap); +struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); +void mnt_idmap_put(const struct mnt_idmap *idmap); vfsuid_t make_vfsuid(struct mnt_idmap *idmap, struct user_namespace *fs_userns, kuid_t kuid); From 1923c1065fe94c79259bfdf6a5a7793de4b3dd08 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:28 +0200 Subject: [PATCH 0754/2286] fs: port vfs{g,u}id helpers to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-3-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/ceph/inode.c | 2 +- fs/ceph/super.h | 2 +- fs/fuse/dir.c | 12 ++++++------ fs/internal.h | 2 +- fs/mnt_idmapping.c | 15 ++++++++------- fs/overlayfs/inode.c | 4 ++-- fs/overlayfs/overlayfs.h | 2 +- fs/posix_acl.c | 2 +- fs/smb/server/smbacl.c | 14 +++++++------- fs/smb/server/smbacl.h | 8 ++++---- fs/smb/server/vfs.c | 4 ++-- fs/smb/server/vfs.h | 2 +- include/linux/mnt_idmapping.h | 12 ++++++------ security/integrity/evm/evm_main.c | 4 ++-- 14 files changed, 43 insertions(+), 42 deletions(-) diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index d52e2b389e0b62..3921b601ad5976 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2568,7 +2568,7 @@ static int fill_fscrypt_truncate(struct inode *inode, return ret; } -int __ceph_setattr(struct mnt_idmap *idmap, struct inode *inode, +int __ceph_setattr(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *attr, struct ceph_iattr *cia) { struct ceph_inode_info *ci = ceph_inode(inode); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 72d4e30304dcab..e9a9d5737c80b0 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1173,7 +1173,7 @@ struct ceph_iattr { struct ceph_fscrypt_auth *fscrypt_auth; }; -extern int __ceph_setattr(struct mnt_idmap *idmap, struct inode *inode, +extern int __ceph_setattr(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *attr, struct ceph_iattr *cia); extern int ceph_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e49b4e874b15f8..0d297f74c66379 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -751,7 +751,7 @@ static u32 fuse_ext_size(size_t size) /* * This adds just a single supplementary group that matches the parent's group. */ -static int get_create_supp_group(struct mnt_idmap *idmap, +static int get_create_supp_group(const struct mnt_idmap *idmap, struct inode *dir, struct fuse_in_arg *ext) { @@ -782,7 +782,7 @@ static int get_create_supp_group(struct mnt_idmap *idmap, return 0; } -static int get_create_ext(struct mnt_idmap *idmap, +static int get_create_ext(const struct mnt_idmap *idmap, struct fuse_args *args, struct inode *dir, struct dentry *dentry, umode_t mode) @@ -1375,7 +1375,7 @@ static int fuse_link(struct dentry *entry, struct inode *newdir, return err; } -static void fuse_fillattr(struct mnt_idmap *idmap, struct inode *inode, +static void fuse_fillattr(const struct mnt_idmap *idmap, struct inode *inode, struct fuse_attr *attr, struct kstat *stat) { unsigned int blkbits; @@ -1429,7 +1429,7 @@ static void fuse_statx_to_attr(struct fuse_statx *sx, struct fuse_attr *attr) attr->blksize = sx->blksize; } -static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, +static int fuse_do_statx(const struct mnt_idmap *idmap, struct inode *inode, struct file *file, struct kstat *stat) { int err; @@ -1490,7 +1490,7 @@ static int fuse_do_statx(struct mnt_idmap *idmap, struct inode *inode, return 0; } -static int fuse_do_getattr(struct mnt_idmap *idmap, struct inode *inode, +static int fuse_do_getattr(const struct mnt_idmap *idmap, struct inode *inode, struct kstat *stat, struct file *file) { int err; @@ -2000,7 +2000,7 @@ static bool update_mtime(unsigned ivalid, bool trust_local_mtime) return true; } -static void iattr_to_fattr(struct mnt_idmap *idmap, struct fuse_conn *fc, +static void iattr_to_fattr(const struct mnt_idmap *idmap, struct fuse_conn *fc, struct iattr *iattr, struct fuse_setattr_in *arg, bool trust_local_cmtime) { diff --git a/fs/internal.h b/fs/internal.h index 581aaa2d651031..87ac96c3e29e0f 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -354,7 +354,7 @@ static inline bool path_mounted(const struct path *path) } void file_f_owner_release(struct file *file); bool file_seek_cur_needs_f_lock(struct file *file); -int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map); +int statmount_mnt_idmap(const struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map); struct dentry *find_next_child(struct dentry *parent, struct dentry *prev); int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c index cc6c5f1cfba881..4d5fbf18560895 100644 --- a/fs/mnt_idmapping.c +++ b/fs/mnt_idmapping.c @@ -77,7 +77,7 @@ static inline bool initial_idmapping(const struct user_namespace *ns) * returned. */ -vfsuid_t make_vfsuid(struct mnt_idmap *idmap, +vfsuid_t make_vfsuid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, kuid_t kuid) { @@ -117,7 +117,7 @@ EXPORT_SYMBOL_GPL(make_vfsuid); * If @kgid has no mapping in either @idmap or @fs_userns INVALID_GID is * returned. */ -vfsgid_t make_vfsgid(struct mnt_idmap *idmap, +vfsgid_t make_vfsgid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, kgid_t kgid) { gid_t gid; @@ -147,7 +147,7 @@ EXPORT_SYMBOL_GPL(make_vfsgid); * * Return: @vfsuid mapped into the filesystem idmapping */ -kuid_t from_vfsuid(struct mnt_idmap *idmap, +kuid_t from_vfsuid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsuid_t vfsuid) { uid_t uid; @@ -176,7 +176,7 @@ EXPORT_SYMBOL_GPL(from_vfsuid); * * Return: @vfsgid mapped into the filesystem idmapping */ -kgid_t from_vfsgid(struct mnt_idmap *idmap, +kgid_t from_vfsgid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsgid_t vfsgid) { gid_t gid; @@ -340,9 +340,10 @@ void mnt_idmap_put(const struct mnt_idmap *idmap) } EXPORT_SYMBOL_GPL(mnt_idmap_put); -int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map) +int statmount_mnt_idmap(const struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map) { - struct uid_gid_map *map, *map_up; + const struct uid_gid_map *map; + struct uid_gid_map *map_up; u32 idx, nr_mappings; if (!is_valid_mnt_idmap(idmap)) @@ -362,7 +363,7 @@ int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_ for (idx = 0, nr_mappings = 0; idx < map->nr_extents; idx++) { uid_t lower; - struct uid_gid_extent *extent; + const struct uid_gid_extent *extent; if (map->nr_extents <= UID_GID_MAP_MAX_BASE_EXTENTS) extent = &map->extent[idx]; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 401cb8c75520cb..0364e7d08ad725 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -355,7 +355,7 @@ static const char *ovl_get_link(struct dentry *dentry, * alter the POSIX ACLs for the underlying filesystem. */ static void ovl_idmap_posix_acl(const struct inode *realinode, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct posix_acl *acl) { struct user_namespace *fs_userns = i_user_ns(realinode); @@ -447,7 +447,7 @@ struct posix_acl *ovl_get_acl_path(const struct path *path, * * This is obviously only relevant when idmapped layers are used. */ -struct posix_acl *do_ovl_get_acl(struct mnt_idmap *idmap, +struct posix_acl *do_ovl_get_acl(const struct mnt_idmap *idmap, struct inode *inode, int type, bool rcu, bool noperm) { diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0d8c6152e9fc5..6508f30aaaca2a 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -806,7 +806,7 @@ int ovl_permission(struct mnt_idmap *idmap, struct inode *inode, int mask); #ifdef CONFIG_FS_POSIX_ACL -struct posix_acl *do_ovl_get_acl(struct mnt_idmap *idmap, +struct posix_acl *do_ovl_get_acl(const struct mnt_idmap *idmap, struct inode *inode, int type, bool rcu, bool noperm); static inline struct posix_acl *ovl_get_inode_acl(struct inode *inode, int type, diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 18b302f941743b..f9c55a59c6c677 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -1057,7 +1057,7 @@ int simple_acl_create(struct inode *dir, struct inode *inode) return 0; } -static int vfs_set_acl_idmapped_mnt(struct mnt_idmap *idmap, +static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, struct posix_acl *acl) { diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 8ad2e5a5cca8af..915dbfab62ea0c 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -257,7 +257,7 @@ void id_to_sid(unsigned int cid, uint sidtype, struct smb_sid *ssid) ssid->num_subauth++; } -static int sid_to_id(struct mnt_idmap *idmap, +static int sid_to_id(const struct mnt_idmap *idmap, struct smb_sid *psid, uint sidtype, struct smb_fattr *fattr) { @@ -383,7 +383,7 @@ void free_acl_state(struct posix_acl_state *state) kfree(state->groups); } -static void parse_dacl(struct mnt_idmap *idmap, +static void parse_dacl(const struct mnt_idmap *idmap, struct smb_acl *pdacl, char *end_of_acl, struct smb_sid *pownersid, struct smb_sid *pgrpsid, struct smb_fattr *fattr) @@ -608,7 +608,7 @@ static void parse_dacl(struct mnt_idmap *idmap, free_acl_state(&default_acl_state); } -static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, +static void set_posix_acl_entries_dacl(const struct mnt_idmap *idmap, struct smb_ace *pndace, struct smb_fattr *fattr, u16 *num_aces, u16 *size, u16 existing_nt_aces, @@ -739,7 +739,7 @@ static void set_posix_acl_entries_dacl(struct mnt_idmap *idmap, } } -static void set_ntacl_dacl(struct mnt_idmap *idmap, +static void set_ntacl_dacl(const struct mnt_idmap *idmap, struct smb_acl *pndacl, struct smb_acl *nt_dacl, unsigned int aces_size, @@ -798,7 +798,7 @@ static void set_ntacl_dacl(struct mnt_idmap *idmap, pndacl->size = cpu_to_le16(le16_to_cpu(pndacl->size) + size); } -static void set_mode_dacl(struct mnt_idmap *idmap, +static void set_mode_dacl(const struct mnt_idmap *idmap, struct smb_acl *pndacl, struct smb_fattr *fattr) { struct smb_ace *pace, *pndace; @@ -884,7 +884,7 @@ static int parse_sid(struct smb_sid *psid, char *end_of_acl) } /* Convert CIFS ACL to POSIX form */ -int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd, +int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd, int acl_len, struct smb_fattr *fattr) { int rc = 0; @@ -1017,7 +1017,7 @@ size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr, } /* Convert permission bits from mode to equivalent CIFS ACL */ -int build_sec_desc(struct mnt_idmap *idmap, +int build_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd, struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info, __u32 *secdesclen, struct smb_fattr *fattr) diff --git a/fs/smb/server/smbacl.h b/fs/smb/server/smbacl.h index 01810c16cc0461..28d215807faae2 100644 --- a/fs/smb/server/smbacl.h +++ b/fs/smb/server/smbacl.h @@ -81,9 +81,9 @@ struct posix_acl_state { struct posix_ace_state_array *groups; }; -int parse_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd, +int parse_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd, int acl_len, struct smb_fattr *fattr); -int build_sec_desc(struct mnt_idmap *idmap, struct smb_ntsd *pntsd, +int build_sec_desc(const struct mnt_idmap *idmap, struct smb_ntsd *pntsd, struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info, __u32 *secdesclen, struct smb_fattr *fattr); int init_acl_state(struct posix_acl_state *state, u16 cnt); @@ -105,7 +105,7 @@ void ksmbd_init_domain(u32 *sub_auth); size_t smb_acl_sec_desc_scratch_len(struct smb_fattr *fattr, struct smb_ntsd *ppntsd, int ppntsd_size, int addition_info); -static inline uid_t posix_acl_uid_translate(struct mnt_idmap *idmap, +static inline uid_t posix_acl_uid_translate(const struct mnt_idmap *idmap, struct posix_acl_entry *pace) { vfsuid_t vfsuid; @@ -117,7 +117,7 @@ static inline uid_t posix_acl_uid_translate(struct mnt_idmap *idmap, return from_kuid(&init_user_ns, vfsuid_into_kuid(vfsuid)); } -static inline gid_t posix_acl_gid_translate(struct mnt_idmap *idmap, +static inline gid_t posix_acl_gid_translate(const struct mnt_idmap *idmap, struct posix_acl_entry *pace) { vfsgid_t vfsgid; diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index d2b524f79cbe6c..cb52dce113b98f 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -1540,7 +1540,7 @@ int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path) return err; } -static struct xattr_smb_acl *ksmbd_vfs_make_xattr_posix_acl(struct mnt_idmap *idmap, +static struct xattr_smb_acl *ksmbd_vfs_make_xattr_posix_acl(const struct mnt_idmap *idmap, struct inode *inode, int acl_type) { @@ -2253,7 +2253,7 @@ int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap, return rc; } -int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap, +int ksmbd_vfs_inherit_posix_acl(const struct mnt_idmap *idmap, const struct path *path, struct inode *parent_inode) { struct posix_acl *acls; diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 55d099de71f527..9f4b003166e52e 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -173,7 +173,7 @@ int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap, struct xattr_dos_attrib *da); int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap, const struct path *path); -int ksmbd_vfs_inherit_posix_acl(struct mnt_idmap *idmap, +int ksmbd_vfs_inherit_posix_acl(const struct mnt_idmap *idmap, const struct path *path, struct inode *parent_inode); void ksmbd_vfs_update_compressed_fattr(struct dentry *dentry, __le32 *fattr); diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h index e5bee8b4109410..b16f52d27ba091 100644 --- a/include/linux/mnt_idmapping.h +++ b/include/linux/mnt_idmapping.h @@ -124,16 +124,16 @@ int vfsgid_in_group_p(vfsgid_t vfsgid); struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); void mnt_idmap_put(const struct mnt_idmap *idmap); -vfsuid_t make_vfsuid(struct mnt_idmap *idmap, +vfsuid_t make_vfsuid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, kuid_t kuid); -vfsgid_t make_vfsgid(struct mnt_idmap *idmap, +vfsgid_t make_vfsgid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, kgid_t kgid); -kuid_t from_vfsuid(struct mnt_idmap *idmap, +kuid_t from_vfsuid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsuid_t vfsuid); -kgid_t from_vfsgid(struct mnt_idmap *idmap, +kgid_t from_vfsgid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsgid_t vfsgid); /** @@ -148,7 +148,7 @@ kgid_t from_vfsgid(struct mnt_idmap *idmap, * * Return: true if @vfsuid has a mapping in the filesystem, false if not. */ -static inline bool vfsuid_has_fsmapping(struct mnt_idmap *idmap, +static inline bool vfsuid_has_fsmapping(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsuid_t vfsuid) { @@ -186,7 +186,7 @@ static inline kuid_t vfsuid_into_kuid(vfsuid_t vfsuid) * * Return: true if @vfsgid has a mapping in the filesystem, false if not. */ -static inline bool vfsgid_has_fsmapping(struct mnt_idmap *idmap, +static inline bool vfsgid_has_fsmapping(const struct mnt_idmap *idmap, struct user_namespace *fs_userns, vfsgid_t vfsgid) { diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index b59e3f121b8a89..66f27653cdac29 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -481,7 +481,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) * * Returns 1 if passed xattr value differs from current value, 0 otherwise. */ -static int evm_xattr_change(struct mnt_idmap *idmap, +static int evm_xattr_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { @@ -517,7 +517,7 @@ static int evm_xattr_change(struct mnt_idmap *idmap, * For posix xattr acls only, permit security.evm, even if it currently * doesn't exist, to be updated unless the EVM signature is immutable. */ -static int evm_protect_xattr(struct mnt_idmap *idmap, +static int evm_protect_xattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { From ecc302ec6acb680d96f44c94796f0274c260b7c7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:29 +0200 Subject: [PATCH 0755/2286] fs: port fs{g,u}id helpers to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-4-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/ceph/mds_client.h | 2 +- fs/fuse/dir.c | 13 +++++++------ fs/fuse/fuse_i.h | 4 ++-- fs/fuse/req.c | 8 +++++--- include/linux/fs.h | 6 +++--- include/linux/mnt_idmapping.h | 4 ++-- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/fs/ceph/mds_client.h b/fs/ceph/mds_client.h index 3c62e3c3530b4a..1ac8a7e08e5c10 100644 --- a/fs/ceph/mds_client.h +++ b/fs/ceph/mds_client.h @@ -375,7 +375,7 @@ struct ceph_mds_request { int r_fmode; /* file mode, if expecting cap */ int r_request_release_offset; const struct cred *r_cred; - struct mnt_idmap *r_mnt_idmap; + const struct mnt_idmap *r_mnt_idmap; struct timespec64 r_stamp; /* for choosing which mds to send this request to */ diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0d297f74c66379..7a204f55ca84ba 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -820,7 +820,7 @@ static void free_ext_value(struct fuse_args *args) * If the filesystem doesn't support this, then fall back to separate * 'mknod' + 'open' requests. */ -static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir, +static int fuse_create_open(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, struct file *file, unsigned int flags, umode_t mode, u32 opcode) { @@ -980,7 +980,7 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry, /* * Code shared between mknod, mkdir, symlink and link */ -static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_mount *fm, +static struct dentry *create_new_entry(const struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args, struct inode *dir, struct dentry *entry, umode_t mode) { @@ -1053,7 +1053,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun return ERR_PTR(err); } -static int create_new_nondir(struct mnt_idmap *idmap, struct fuse_mount *fm, +static int create_new_nondir(const struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args, struct inode *dir, struct dentry *entry, umode_t mode) { @@ -1256,9 +1256,10 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry) return err; } -static int fuse_rename_common(struct mnt_idmap *idmap, struct inode *olddir, struct dentry *oldent, - struct inode *newdir, struct dentry *newent, - unsigned int flags, int opcode, size_t argsize) +static int fuse_rename_common(const struct mnt_idmap *idmap, struct inode *olddir, + struct dentry *oldent, struct inode *newdir, + struct dentry *newent, unsigned int flags, + int opcode, size_t argsize) { int err; struct fuse_rename2_in inarg; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index c8d4c5f3af7e8c..a07c75377a30af 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1003,7 +1003,7 @@ void __exit fuse_ctl_cleanup(void); /* * Simple request sending that does request allocation and freeing */ -ssize_t __fuse_simple_request(struct mnt_idmap *idmap, +ssize_t __fuse_simple_request(const struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args); @@ -1012,7 +1012,7 @@ static inline ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_arg return __fuse_simple_request(&invalid_mnt_idmap, fm, args); } -static inline ssize_t fuse_simple_idmap_request(struct mnt_idmap *idmap, +static inline ssize_t fuse_simple_idmap_request(const struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args) { diff --git a/fs/fuse/req.c b/fs/fuse/req.c index a01ee743d31e97..a133b012d04134 100644 --- a/fs/fuse/req.c +++ b/fs/fuse/req.c @@ -3,7 +3,8 @@ #include "dev.h" #include "fuse_i.h" -static int fuse_fill_creds(struct fuse_mount *fm, struct fuse_args *args, struct mnt_idmap *idmap) +static int fuse_fill_creds(struct fuse_mount *fm, struct fuse_args *args, + const struct mnt_idmap *idmap) { struct fuse_conn *fc = fm->fc; bool no_idmap = !fm->sb || (fm->sb->s_iflags & SB_I_NOIDMAP); @@ -49,7 +50,8 @@ static int fuse_fill_creds(struct fuse_mount *fm, struct fuse_args *args, struct return 0; } -static int fuse_req_prep(struct fuse_mount *fm, struct fuse_args *args, struct mnt_idmap *idmap) +static int fuse_req_prep(struct fuse_mount *fm, struct fuse_args *args, + const struct mnt_idmap *idmap) { if (!args->force && fm->fc->conn_error) return -ECONNREFUSED; @@ -57,7 +59,7 @@ static int fuse_req_prep(struct fuse_mount *fm, struct fuse_args *args, struct m return fuse_fill_creds(fm, args, idmap); } -ssize_t __fuse_simple_request(struct mnt_idmap *idmap, struct fuse_mount *fm, +ssize_t __fuse_simple_request(const struct mnt_idmap *idmap, struct fuse_mount *fm, struct fuse_args *args) { struct fuse_conn *fc = fm->fc; diff --git a/include/linux/fs.h b/include/linux/fs.h index f9d1e05e8ae68e..959c747ef75ad9 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1544,7 +1544,7 @@ static inline void i_gid_update(struct mnt_idmap *idmap, * an idmapped mount map the caller's fsuid according to @idmap. */ static inline void inode_fsuid_set(struct inode *inode, - struct mnt_idmap *idmap) + const struct mnt_idmap *idmap) { inode->i_uid = mapped_fsuid(idmap, i_user_ns(inode)); } @@ -1558,7 +1558,7 @@ static inline void inode_fsuid_set(struct inode *inode, * an idmapped mount map the caller's fsgid according to @idmap. */ static inline void inode_fsgid_set(struct inode *inode, - struct mnt_idmap *idmap) + const struct mnt_idmap *idmap) { inode->i_gid = mapped_fsgid(idmap, i_user_ns(inode)); } @@ -1575,7 +1575,7 @@ static inline void inode_fsgid_set(struct inode *inode, * Return: true if fsuid and fsgid is mapped, false if not. */ static inline bool fsuidgid_has_mapping(struct super_block *sb, - struct mnt_idmap *idmap) + const struct mnt_idmap *idmap) { struct user_namespace *fs_userns = sb->s_user_ns; kuid_t kuid; diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h index b16f52d27ba091..6656aea23be2d9 100644 --- a/include/linux/mnt_idmapping.h +++ b/include/linux/mnt_idmapping.h @@ -225,7 +225,7 @@ static inline kgid_t vfsgid_into_kgid(vfsgid_t vfsgid) * * Return: the caller's current fsuid mapped up according to @idmap. */ -static inline kuid_t mapped_fsuid(struct mnt_idmap *idmap, +static inline kuid_t mapped_fsuid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns) { return from_vfsuid(idmap, fs_userns, VFSUIDT_INIT(current_fsuid())); @@ -244,7 +244,7 @@ static inline kuid_t mapped_fsuid(struct mnt_idmap *idmap, * * Return: the caller's current fsgid mapped up according to @idmap. */ -static inline kgid_t mapped_fsgid(struct mnt_idmap *idmap, +static inline kgid_t mapped_fsgid(const struct mnt_idmap *idmap, struct user_namespace *fs_userns) { return from_vfsgid(idmap, fs_userns, VFSGIDT_INIT(current_fsgid())); From 0c18a70900eeef375290c702b0be7905533eb467 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:30 +0200 Subject: [PATCH 0756/2286] fs: port i_{g,u}id_into_vfs{g,u}id() to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-5-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/exfat/file.c | 2 +- fs/fat/file.c | 2 +- fs/internal.h | 2 +- fs/namei.c | 2 +- fs/smb/server/ndr.c | 2 +- fs/smb/server/ndr.h | 2 +- fs/smb/server/smb2pdu.c | 2 +- fs/xattr.c | 2 +- fs/xfs/xfs_itable.c | 2 +- fs/xfs/xfs_itable.h | 2 +- include/linux/fs.h | 6 +++--- security/integrity/ima/ima.h | 10 +++++----- security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_appraise.c | 2 +- security/integrity/ima/ima_main.c | 2 +- security/integrity/ima/ima_policy.c | 4 ++-- 16 files changed, 23 insertions(+), 23 deletions(-) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index a2a9ee1a20048e..0530803f71e858 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -143,7 +143,7 @@ static long exfat_fallocate(struct file *file, int mode, return err; } -static bool exfat_allow_set_time(struct mnt_idmap *idmap, +static bool exfat_allow_set_time(const struct mnt_idmap *idmap, struct exfat_sb_info *sbi, struct inode *inode) { mode_t allow_utime = sbi->options.allow_utime; diff --git a/fs/fat/file.c b/fs/fat/file.c index 1c835ca5f21a51..0b8b493d06e497 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -493,7 +493,7 @@ static int fat_sanitize_mode(const struct msdos_sb_info *sbi, return 0; } -static int fat_allow_set_time(struct mnt_idmap *idmap, +static int fat_allow_set_time(const struct mnt_idmap *idmap, struct msdos_sb_info *sbi, struct inode *inode) { umode_t allow_utime = sbi->options.allow_utime; diff --git a/fs/internal.h b/fs/internal.h index 87ac96c3e29e0f..72244e5f4e57d9 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -299,7 +299,7 @@ int filename_setxattr(int dfd, struct filename *filename, int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx); int import_xattr_name(struct xattr_name *kname, const char __user *name); -int may_write_xattr(struct mnt_idmap *idmap, struct inode *inode); +int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode); #ifdef CONFIG_FS_POSIX_ACL int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3efff..603d43e4a961cb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1407,7 +1407,7 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link) * * Returns 0 if the open is allowed, -ve on error. */ -static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd, +static int may_create_in_sticky(const struct mnt_idmap *idmap, struct nameidata *nd, struct inode *const inode) { umode_t dir_mode = nd->dir_mode; diff --git a/fs/smb/server/ndr.c b/fs/smb/server/ndr.c index 58d71560f626b4..7e546c22e284b9 100644 --- a/fs/smb/server/ndr.c +++ b/fs/smb/server/ndr.c @@ -338,7 +338,7 @@ static int ndr_encode_posix_acl_entry(struct ndr *n, struct xattr_smb_acl *acl) } int ndr_encode_posix_acl(struct ndr *n, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *inode, struct xattr_smb_acl *acl, struct xattr_smb_acl *def_acl) diff --git a/fs/smb/server/ndr.h b/fs/smb/server/ndr.h index f3c108c8cf4dbf..646568c42e4d13 100644 --- a/fs/smb/server/ndr.h +++ b/fs/smb/server/ndr.h @@ -14,7 +14,7 @@ struct ndr { int ndr_encode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da); int ndr_decode_dos_attr(struct ndr *n, struct xattr_dos_attrib *da); -int ndr_encode_posix_acl(struct ndr *n, struct mnt_idmap *idmap, +int ndr_encode_posix_acl(struct ndr *n, const struct mnt_idmap *idmap, struct inode *inode, struct xattr_smb_acl *acl, struct xattr_smb_acl *def_acl); int ndr_encode_v4_ntacl(struct ndr *n, struct xattr_ntacl *acl); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index a8046f477d54ec..0789200d602f8e 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3604,7 +3604,7 @@ static int smb2_create_sd_buffer(struct ksmbd_work *work, } static void ksmbd_acls_fattr(struct smb_fattr *fattr, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *inode) { vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode); diff --git a/fs/xattr.c b/fs/xattr.c index d5897911520075..c08f188748a9ce 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -100,7 +100,7 @@ xattr_resolve_name(struct inode *inode, const char **name) * * Return: On success zero is returned. On error a negative errno is returned. */ -int may_write_xattr(struct mnt_idmap *idmap, struct inode *inode) +int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode) { if (IS_IMMUTABLE(inode)) return -EPERM; diff --git a/fs/xfs/xfs_itable.c b/fs/xfs/xfs_itable.c index 159295c63e8f73..a4cf1effa5e613 100644 --- a/fs/xfs/xfs_itable.c +++ b/fs/xfs/xfs_itable.c @@ -63,7 +63,7 @@ want_metadir_file( STATIC int xfs_bulkstat_one_int( struct xfs_mount *mp, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct xfs_trans *tp, xfs_ino_t ino, struct xfs_bstat_chunk *bc) diff --git a/fs/xfs/xfs_itable.h b/fs/xfs/xfs_itable.h index 2d0612f14d6e12..c0567bfc30fbf8 100644 --- a/fs/xfs/xfs_itable.h +++ b/fs/xfs/xfs_itable.h @@ -8,7 +8,7 @@ /* In-memory representation of a userspace request for batch inode data. */ struct xfs_ibulk { struct xfs_mount *mp; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; void __user *ubuffer; /* user output buffer */ xfs_ino_t startino; /* start with this inode */ unsigned int icount; /* number of elements in ubuffer */ diff --git a/include/linux/fs.h b/include/linux/fs.h index 959c747ef75ad9..cec5de83d8826d 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1439,7 +1439,7 @@ static inline void i_gid_write(struct inode *inode, gid_t gid) * Return: whe inode's i_uid mapped down according to @idmap. * If the inode's i_uid has no mapping INVALID_VFSUID is returned. */ -static inline vfsuid_t i_uid_into_vfsuid(struct mnt_idmap *idmap, +static inline vfsuid_t i_uid_into_vfsuid(const struct mnt_idmap *idmap, const struct inode *inode) { return make_vfsuid(idmap, i_user_ns(inode), inode->i_uid); @@ -1491,7 +1491,7 @@ static inline void i_uid_update(struct mnt_idmap *idmap, * Return: the inode's i_gid mapped down according to @idmap. * If the inode's i_gid has no mapping INVALID_VFSGID is returned. */ -static inline vfsgid_t i_gid_into_vfsgid(struct mnt_idmap *idmap, +static inline vfsgid_t i_gid_into_vfsgid(const struct mnt_idmap *idmap, const struct inode *inode) { return make_vfsgid(idmap, i_user_ns(inode), inode->i_gid); @@ -2173,7 +2173,7 @@ extern loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos, (inode)->i_rdev == WHITEOUT_DEV) #define IS_ANON_FILE(inode) ((inode)->i_flags & S_ANON_INODE) -static inline bool HAS_UNMAPPED_ID(struct mnt_idmap *idmap, +static inline bool HAS_UNMAPPED_ID(const struct mnt_idmap *idmap, struct inode *inode) { return !vfsuid_valid(i_uid_into_vfsuid(idmap, inode)) || diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 10214f73ca1ed9..b502854f28ee71 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -423,7 +423,7 @@ static inline void ima_process_queued_keys(void) {} #endif /* CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS */ /* LIM API function definitions */ -int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, +int ima_get_action(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, @@ -437,7 +437,7 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, struct ima_template_desc *template_desc); -int process_buffer_measurement(struct mnt_idmap *idmap, +int process_buffer_measurement(const struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, @@ -454,7 +454,7 @@ void ima_free_template_entry(struct ima_template_entry *entry); const char *ima_d_path(const struct path *path, char **pathbuf, char *filename); /* IMA policy related functions */ -int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, +int ima_match_policy(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, @@ -489,7 +489,7 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, bool bprm_is_check); -int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, +int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct ima_iint_cache *iint, struct file *file); enum integrity_status ima_get_cache_status(struct ima_iint_cache *iint, @@ -519,7 +519,7 @@ static inline int ima_appraise_measurement(enum ima_hooks func, return INTEGRITY_UNKNOWN; } -static inline int ima_must_appraise(struct mnt_idmap *idmap, +static inline int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func) { diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 122d127e108dcc..3c5a23b4a2ffc0 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -188,7 +188,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, * Returns IMA_MEASURE, IMA_APPRAISE mask. * */ -int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, +int ima_get_action(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ced2e131b061bf..6b21db789ef1fa 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -71,7 +71,7 @@ bool is_ima_appraise_enabled(void) * * Return 1 to appraise or hash */ -int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, +int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func) { struct lsm_prop prop; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index ab1e53b3210d3a..8d9c92dc882538 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -1095,7 +1095,7 @@ static int ima_post_load_data(char *buf, loff_t size, * has been written to the passed location but not added to a measurement entry, * a negative value otherwise. */ -int process_buffer_measurement(struct mnt_idmap *idmap, +int process_buffer_measurement(const struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f79d07bb63c6fc..89d197d99383ef 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -575,7 +575,7 @@ static bool ima_match_rule_data(struct ima_rule_entry *rule, * Returns true on rule match, false on failure. */ static bool ima_match_rules(struct ima_rule_entry *rule, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, const char *func_data) @@ -757,7 +757,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) * list when walking it. Reads are many orders of magnitude more numerous * than writes so ima_match_policy() is classical RCU candidate. */ -int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, +int ima_match_policy(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, From b4388f171cc029f2d5bd572ac94cd6d534bbf7ed Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:31 +0200 Subject: [PATCH 0757/2286] fs: port i_{g,u}id_{needs_}update() to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-6-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- include/linux/fs.h | 8 ++++---- security/integrity/evm/evm_main.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/linux/fs.h b/include/linux/fs.h index cec5de83d8826d..154f2c976a0a65 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1456,7 +1456,7 @@ static inline vfsuid_t i_uid_into_vfsuid(const struct mnt_idmap *idmap, * * Return: true if @inode's i_uid field needs to be updated, false if not. */ -static inline bool i_uid_needs_update(struct mnt_idmap *idmap, +static inline bool i_uid_needs_update(const struct mnt_idmap *idmap, const struct iattr *attr, const struct inode *inode) { @@ -1474,7 +1474,7 @@ static inline bool i_uid_needs_update(struct mnt_idmap *idmap, * Safely update @inode's i_uid field translating the vfsuid of any idmapped * mount into the filesystem kuid. */ -static inline void i_uid_update(struct mnt_idmap *idmap, +static inline void i_uid_update(const struct mnt_idmap *idmap, const struct iattr *attr, struct inode *inode) { @@ -1508,7 +1508,7 @@ static inline vfsgid_t i_gid_into_vfsgid(const struct mnt_idmap *idmap, * * Return: true if @inode's i_gid field needs to be updated, false if not. */ -static inline bool i_gid_needs_update(struct mnt_idmap *idmap, +static inline bool i_gid_needs_update(const struct mnt_idmap *idmap, const struct iattr *attr, const struct inode *inode) { @@ -1526,7 +1526,7 @@ static inline bool i_gid_needs_update(struct mnt_idmap *idmap, * Safely update @inode's i_gid field translating the vfsgid of any idmapped * mount into the filesystem kgid. */ -static inline void i_gid_update(struct mnt_idmap *idmap, +static inline void i_gid_update(const struct mnt_idmap *idmap, const struct iattr *attr, struct inode *inode) { diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 66f27653cdac29..06e187c9912d7b 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -933,7 +933,7 @@ static inline void evm_inode_post_remove_acl(struct mnt_idmap *idmap, evm_inode_post_removexattr(dentry, acl_name); } -static int evm_attr_change(struct mnt_idmap *idmap, +static int evm_attr_change(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_backing_inode(dentry); From bc9c0b94c634c42afab7c3ce991367db59056752 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:32 +0200 Subject: [PATCH 0758/2286] quota: port to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-7-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/quota/dquot.c | 2 +- include/linux/quotaops.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 204afc5e984bcc..c5e9c4e24d685e 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -2079,7 +2079,7 @@ EXPORT_SYMBOL(__dquot_transfer); /* Wrapper for transferring ownership of an inode for uid/gid only * Called from FSXXX_setattr() */ -int dquot_transfer(struct mnt_idmap *idmap, struct inode *inode, +int dquot_transfer(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *iattr) { struct dquot *transfer_to[MAXQUOTAS] = {}; diff --git a/include/linux/quotaops.h b/include/linux/quotaops.h index f9c0f9d7c9d93a..0c64ca674e778a 100644 --- a/include/linux/quotaops.h +++ b/include/linux/quotaops.h @@ -20,7 +20,7 @@ static inline struct quota_info *sb_dqopt(struct super_block *sb) } /* i_rwsem must being held */ -static inline bool is_quota_modification(struct mnt_idmap *idmap, +static inline bool is_quota_modification(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *ia) { return ((ia->ia_valid & ATTR_SIZE) || @@ -109,7 +109,7 @@ int dquot_set_dqblk(struct super_block *sb, struct kqid id, struct qc_dqblk *di); int __dquot_transfer(struct inode *inode, struct dquot **transfer_to); -int dquot_transfer(struct mnt_idmap *idmap, struct inode *inode, +int dquot_transfer(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *iattr); static inline struct mem_dqinfo *sb_dqinfo(struct super_block *sb, int type) @@ -229,7 +229,7 @@ static inline void dquot_free_inode(struct inode *inode) { } -static inline int dquot_transfer(struct mnt_idmap *idmap, +static inline int dquot_transfer(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *iattr) { return 0; From 8e647e65a66bfed62ba3cb4bb89200b510e2f78d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:33 +0200 Subject: [PATCH 0759/2286] fs: port privilege checking helpers to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-8-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/attr.c | 10 +++++----- fs/f2fs/acl.c | 4 ++-- fs/f2fs/file.c | 2 +- fs/inode.c | 4 ++-- fs/internal.h | 4 ++-- include/linux/capability.h | 4 ++-- include/linux/fs.h | 8 ++++---- include/linux/security.h | 2 +- kernel/capability.c | 4 ++-- security/commoncap.c | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index 71888ac903c27d..23f1347ff24046 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -30,7 +30,7 @@ * * Return: ATTR_KILL_SGID if setgid bit needs to be removed, 0 otherwise. */ -int setattr_should_drop_sgid(struct mnt_idmap *idmap, +int setattr_should_drop_sgid(const struct mnt_idmap *idmap, const struct inode *inode) { umode_t mode = inode->i_mode; @@ -60,7 +60,7 @@ EXPORT_SYMBOL(setattr_should_drop_sgid); * Return: A mask of ATTR_KILL_S{G,U}ID indicating which - if any - setid bits * to remove, 0 otherwise. */ -int setattr_should_drop_suidgid(struct mnt_idmap *idmap, +int setattr_should_drop_suidgid(const struct mnt_idmap *idmap, struct inode *inode) { umode_t mode = inode->i_mode; @@ -91,7 +91,7 @@ EXPORT_SYMBOL(setattr_should_drop_suidgid); * permissions. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -static bool chown_ok(struct mnt_idmap *idmap, +static bool chown_ok(const struct mnt_idmap *idmap, const struct inode *inode, vfsuid_t ia_vfsuid) { vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode); @@ -118,7 +118,7 @@ static bool chown_ok(struct mnt_idmap *idmap, * permissions. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -static bool chgrp_ok(struct mnt_idmap *idmap, +static bool chgrp_ok(const struct mnt_idmap *idmap, const struct inode *inode, vfsgid_t ia_vfsgid) { vfsgid_t vfsgid = i_gid_into_vfsgid(idmap, inode); @@ -339,7 +339,7 @@ static void setattr_copy_mgtime(struct inode *inode, const struct iattr *attr) * that for "simple" filesystems, the struct inode is the inode storage. * The caller is free to mark the inode dirty afterwards if needed. */ -void setattr_copy(struct mnt_idmap *idmap, struct inode *inode, +void setattr_copy(const struct mnt_idmap *idmap, struct inode *inode, const struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c index d3253549173e68..e7e0084d0a5f78 100644 --- a/fs/f2fs/acl.c +++ b/fs/f2fs/acl.c @@ -219,7 +219,7 @@ struct posix_acl *f2fs_get_acl(struct inode *inode, int type, bool rcu) return __f2fs_get_acl(inode, type, NULL); } -static int f2fs_acl_update_mode(struct mnt_idmap *idmap, +static int f2fs_acl_update_mode(const struct mnt_idmap *idmap, struct inode *inode, umode_t *mode_p, struct posix_acl **acl) { @@ -240,7 +240,7 @@ static int f2fs_acl_update_mode(struct mnt_idmap *idmap, return 0; } -static int __f2fs_set_acl(struct mnt_idmap *idmap, +static int __f2fs_set_acl(const struct mnt_idmap *idmap, struct inode *inode, int type, struct posix_acl *acl, struct folio *ifolio) { diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index edc352569e87e3..03bb3a9a449f60 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1096,7 +1096,7 @@ int f2fs_getattr(struct mnt_idmap *idmap, const struct path *path, } #ifdef CONFIG_F2FS_FS_POSIX_ACL -static void __setattr_copy(struct mnt_idmap *idmap, +static void __setattr_copy(const struct mnt_idmap *idmap, struct inode *inode, const struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; diff --git a/fs/inode.c b/fs/inode.c index ba7da39be4a315..72a8bbe38d62f5 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2337,7 +2337,7 @@ EXPORT_SYMBOL(touch_atime); * response to write or truncate. Return 0 if nothing has to be changed. * Negative value on error (change should be denied). */ -int dentry_needs_remove_privs(struct mnt_idmap *idmap, +int dentry_needs_remove_privs(const struct mnt_idmap *idmap, struct dentry *dentry) { struct inode *inode = d_inode(dentry); @@ -3033,7 +3033,7 @@ EXPORT_SYMBOL(inode_set_ctime_deleg); * * Return: true if the caller is sufficiently privileged, false if not. */ -bool in_group_or_capable(struct mnt_idmap *idmap, +bool in_group_or_capable(const struct mnt_idmap *idmap, const struct inode *inode, vfsgid_t vfsgid) { if (vfsgid_in_group_p(vfsgid)) diff --git a/fs/internal.h b/fs/internal.h index 72244e5f4e57d9..161b0cafe17fbf 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -210,8 +210,8 @@ extern int vfs_open(const struct path *, struct file *); * inode.c */ extern long prune_icache_sb(struct super_block *sb, struct shrink_control *sc); -int dentry_needs_remove_privs(struct mnt_idmap *, struct dentry *dentry); -bool in_group_or_capable(struct mnt_idmap *idmap, +int dentry_needs_remove_privs(const struct mnt_idmap *, struct dentry *dentry); +bool in_group_or_capable(const struct mnt_idmap *idmap, const struct inode *inode, vfsgid_t vfsgid); /* diff --git a/include/linux/capability.h b/include/linux/capability.h index 37db92b3d6f89e..c39a2bbefc47a5 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -181,9 +181,9 @@ static inline bool ns_capable_setid(struct user_namespace *ns, int cap) } #endif /* CONFIG_MULTIUSER */ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct inode *inode); -bool capable_wrt_inode_uidgid(struct mnt_idmap *idmap, +bool capable_wrt_inode_uidgid(const struct mnt_idmap *idmap, const struct inode *inode, int cap); extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap); extern bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns); diff --git a/include/linux/fs.h b/include/linux/fs.h index 154f2c976a0a65..7c82d7aaa77578 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1835,7 +1835,7 @@ void inode_init_owner(struct mnt_idmap *idmap, struct inode *inode, extern bool may_open_dev(const struct path *path); umode_t mode_strip_sgid(struct mnt_idmap *idmap, const struct inode *dir, umode_t mode); -bool in_group_or_capable(struct mnt_idmap *idmap, +bool in_group_or_capable(const struct mnt_idmap *idmap, const struct inode *inode, vfsgid_t vfsgid); /* @@ -3045,9 +3045,9 @@ static inline struct inode *new_inode_pseudo(struct super_block *sb) } extern struct inode *new_inode(struct super_block *sb); extern void free_inode_nonrcu(struct inode *inode); -extern int setattr_should_drop_suidgid(struct mnt_idmap *, struct inode *); +extern int setattr_should_drop_suidgid(const struct mnt_idmap *, struct inode *); extern int file_remove_privs(struct file *); -int setattr_should_drop_sgid(struct mnt_idmap *idmap, +int setattr_should_drop_sgid(const struct mnt_idmap *idmap, const struct inode *inode); /* @@ -3401,7 +3401,7 @@ int may_setattr(struct mnt_idmap *idmap, struct inode *inode, unsigned int ia_valid); int setattr_prepare(struct mnt_idmap *, struct dentry *, struct iattr *); extern int inode_newsize_ok(const struct inode *, loff_t offset); -void setattr_copy(struct mnt_idmap *, struct inode *inode, +void setattr_copy(const struct mnt_idmap *, struct inode *inode, const struct iattr *attr); extern int file_update_time(struct file *file); diff --git a/include/linux/security.h b/include/linux/security.h index 153e9043058f88..4598a13657ee2c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -185,7 +185,7 @@ extern int cap_capset(struct cred *new, const struct cred *old, extern int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file); int cap_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); -int cap_inode_removexattr(struct mnt_idmap *idmap, +int cap_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name); int cap_inode_need_killpriv(struct dentry *dentry); int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry); diff --git a/kernel/capability.c b/kernel/capability.c index 829f49ae07b965..ae3b037031480b 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -453,7 +453,7 @@ EXPORT_SYMBOL(file_ns_capable); * Return true if the inode uid and gid are within the namespace. */ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct inode *inode) { return vfsuid_has_mapping(ns, i_uid_into_vfsuid(idmap, inode)) && @@ -470,7 +470,7 @@ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, * its own user namespace and that the given inode's uid and gid are * mapped into the current user namespace. */ -bool capable_wrt_inode_uidgid(struct mnt_idmap *idmap, +bool capable_wrt_inode_uidgid(const struct mnt_idmap *idmap, const struct inode *inode, int cap) { struct user_namespace *ns = current_user_ns(); diff --git a/security/commoncap.c b/security/commoncap.c index 3399535808fec7..9361393fd756fb 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -1063,7 +1063,7 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name, * This is used to make sure security xattrs don't get removed by those who * aren't privileged to remove them. */ -int cap_inode_removexattr(struct mnt_idmap *idmap, +int cap_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct user_namespace *user_ns = dentry->d_sb->s_user_ns; From 2239abe92b56ea7d524faebee6d0e946f2b47c8c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:34 +0200 Subject: [PATCH 0760/2286] fs: port inode_owner_or_capable() to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-9-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/btrfs/ioctl.c | 2 +- fs/ext4/ioctl.c | 2 +- fs/inode.c | 2 +- include/linux/fs.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 72bc9d4f77087b..b34c6a8247e423 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3886,7 +3886,7 @@ static long btrfs_ioctl_quota_rescan_wait(struct btrfs_fs_info *fs_info) } static long _btrfs_ioctl_set_received_subvol(struct file *file, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct btrfs_ioctl_received_subvol_args *sa) { struct inode *inode = file_inode(file); diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index c8387e6a2c6e90..6de77da4203cd8 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -373,7 +373,7 @@ void ext4_reset_inode_seed(struct inode *inode) * */ static long swap_inode_boot_loader(struct super_block *sb, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *inode) { handle_t *handle; diff --git a/fs/inode.c b/fs/inode.c index 72a8bbe38d62f5..4313e9735c5c41 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2746,7 +2746,7 @@ EXPORT_SYMBOL(inode_init_owner); * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -bool inode_owner_or_capable(struct mnt_idmap *idmap, +bool inode_owner_or_capable(const struct mnt_idmap *idmap, const struct inode *inode) { vfsuid_t vfsuid; diff --git a/include/linux/fs.h b/include/linux/fs.h index 7c82d7aaa77578..e2babddf755d40 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1755,7 +1755,7 @@ static inline bool file_write_not_started(const struct file *file) return sb_write_not_started(file_inode(file)->i_sb); } -bool inode_owner_or_capable(struct mnt_idmap *idmap, +bool inode_owner_or_capable(const struct mnt_idmap *idmap, const struct inode *inode); /* From 98c949b9d2f56b03902ab1b4640c37fb81798baa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:35 +0200 Subject: [PATCH 0761/2286] fs: port inode_init_owner() to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-10-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/btrfs/btrfs_inode.h | 2 +- fs/btrfs/inode.c | 6 +++--- fs/btrfs/ioctl.c | 2 +- fs/ext4/ext4.h | 2 +- fs/ext4/ialloc.c | 2 +- fs/ext4/namei.c | 4 ++-- fs/f2fs/f2fs.h | 2 +- fs/f2fs/namei.c | 10 +++++----- fs/hugetlbfs/inode.c | 2 +- fs/inode.c | 4 ++-- fs/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/xfs/libxfs/xfs_inode_util.h | 2 +- fs/xfs/xfs_inode.c | 4 ++-- fs/xfs/xfs_inode.h | 2 +- fs/xfs/xfs_iops.c | 2 +- fs/xfs/xfs_symlink.c | 2 +- fs/xfs/xfs_symlink.h | 2 +- include/linux/fs.h | 4 ++-- mm/shmem.c | 8 ++++---- 20 files changed, 33 insertions(+), 33 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 1082fa92c1457a..26ce5319c7fcf5 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -559,7 +559,7 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args, int btrfs_create_new_inode(struct btrfs_trans_handle *trans, struct btrfs_new_inode_args *args); void btrfs_new_inode_args_destroy(struct btrfs_new_inode_args *args); -struct inode *btrfs_new_subvol_inode(struct mnt_idmap *idmap, +struct inode *btrfs_new_subvol_inode(const struct mnt_idmap *idmap, struct inode *dir); void btrfs_set_delalloc_extent(struct btrfs_inode *inode, struct extent_state *state, u32 bits); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 3c10a0ef000231..d5a78d9027e202 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8022,7 +8022,7 @@ static int btrfs_truncate(struct btrfs_inode *inode, bool skip_writeback) return ret; } -struct inode *btrfs_new_subvol_inode(struct mnt_idmap *idmap, +struct inode *btrfs_new_subvol_inode(const struct mnt_idmap *idmap, struct inode *dir) { struct inode *inode; @@ -8540,7 +8540,7 @@ static int btrfs_rename_exchange(struct inode *old_dir, return ret; } -static struct inode *new_whiteout_inode(struct mnt_idmap *idmap, +static struct inode *new_whiteout_inode(const struct mnt_idmap *idmap, struct inode *dir) { struct inode *inode; @@ -8555,7 +8555,7 @@ static struct inode *new_whiteout_inode(struct mnt_idmap *idmap, return inode; } -static int btrfs_rename(struct mnt_idmap *idmap, +static int btrfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index b34c6a8247e423..8eacdc2ee4de3b 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -534,7 +534,7 @@ static unsigned int create_subvol_num_items(const struct btrfs_qgroup_inherit *i return num_items; } -static noinline int create_subvol(struct mnt_idmap *idmap, +static noinline int create_subvol(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct btrfs_qgroup_inherit *inherit) { diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 724a27e8be613e..aef86b1d10facb 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3044,7 +3044,7 @@ extern int ext4fs_dirhash(const struct inode *dir, const char *name, int len, /* ialloc.c */ extern int ext4_mark_inode_used(struct super_block *sb, int ino); -extern struct inode *__ext4_new_inode(struct mnt_idmap *, handle_t *, +extern struct inode *__ext4_new_inode(const struct mnt_idmap *, handle_t *, struct inode *, umode_t, const struct qstr *qstr, __u32 goal, uid_t *owner, __u32 i_flags, diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index a5831fc536db90..529623103ae7ac 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -930,7 +930,7 @@ static int ext4_xattr_credits_for_new_inode(struct inode *dir, mode_t mode, * For other inodes, search forward from the parent directory's block * group to find a free inode. */ -struct inode *__ext4_new_inode(struct mnt_idmap *idmap, +struct inode *__ext4_new_inode(const struct mnt_idmap *idmap, handle_t *handle, struct inode *dir, umode_t mode, const struct qstr *qstr, __u32 goal, uid_t *owner, __u32 i_flags, diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index a6386c1d237f1a..edc12599e64d88 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3753,7 +3753,7 @@ static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent) } } -static struct inode *ext4_whiteout_for_rename(struct mnt_idmap *idmap, +static struct inode *ext4_whiteout_for_rename(const struct mnt_idmap *idmap, struct ext4_renament *ent, int credits, handle_t **h) { @@ -3796,7 +3796,7 @@ static struct inode *ext4_whiteout_for_rename(struct mnt_idmap *idmap, * while new_{dentry,inode) refers to the destination dentry/inode * This comes from rename(const char *oldpath, const char *newpath) */ -static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int ext4_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 9940a6cecf1a2c..eefc886e3ffc77 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3890,7 +3890,7 @@ void f2fs_destroy_evict_inode_work(void); int f2fs_update_extension_list(struct f2fs_sb_info *sbi, const char *name, bool hot, bool set); struct dentry *f2fs_get_parent(struct dentry *child); -int f2fs_get_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +int f2fs_get_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct inode **new_inode); /* diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index ff86ee07290d18..e3378029471370 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -231,7 +231,7 @@ static void set_file_temperature(struct f2fs_sb_info *sbi, struct inode *inode, file_set_hot(inode); } -static struct inode *f2fs_new_inode(struct mnt_idmap *idmap, +static struct inode *f2fs_new_inode(const struct mnt_idmap *idmap, struct inode *dir, umode_t mode, const char *name) { @@ -857,7 +857,7 @@ static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, return err; } -static int __f2fs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int __f2fs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode, bool is_whiteout, struct inode **new_inode, struct f2fs_filename *fname) { @@ -944,7 +944,7 @@ static int f2fs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, return finish_open_simple(file, err); } -static int f2fs_create_whiteout(struct mnt_idmap *idmap, +static int f2fs_create_whiteout(const struct mnt_idmap *idmap, struct inode *dir, struct inode **whiteout, struct f2fs_filename *fname) { @@ -952,14 +952,14 @@ static int f2fs_create_whiteout(struct mnt_idmap *idmap, true, whiteout, fname); } -int f2fs_get_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +int f2fs_get_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct inode **new_inode) { return __f2fs_tmpfile(idmap, dir, NULL, S_IFREG, false, new_inode, NULL); } -static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int f2fs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 7611a8470ea265..4bd3adc20a4756 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -892,7 +892,7 @@ static struct inode *hugetlbfs_get_root(struct super_block *sb, static struct lock_class_key hugetlbfs_i_mmap_rwsem_key; static struct inode *hugetlbfs_get_inode(struct super_block *sb, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, umode_t mode, dev_t dev) { diff --git a/fs/inode.c b/fs/inode.c index 4313e9735c5c41..99f1aab89eae9e 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2716,7 +2716,7 @@ EXPORT_SYMBOL(init_special_inode); * and initializing i_uid and i_gid. On non-idmapped mounts or if permission * checking is to be performed on the raw inode simply pass @nop_mnt_idmap. */ -void inode_init_owner(struct mnt_idmap *idmap, struct inode *inode, +void inode_init_owner(const struct mnt_idmap *idmap, struct inode *inode, const struct inode *dir, umode_t mode) { inode_fsuid_set(inode, idmap); @@ -3058,7 +3058,7 @@ EXPORT_SYMBOL(in_group_or_capable); * * Return: the new mode to use for the file */ -umode_t mode_strip_sgid(struct mnt_idmap *idmap, +umode_t mode_strip_sgid(const struct mnt_idmap *idmap, const struct inode *dir, umode_t mode) { if ((mode & (S_ISGID | S_IXGRP)) != (S_ISGID | S_IXGRP)) diff --git a/fs/namei.c b/fs/namei.c index 603d43e4a961cb..db82daec364656 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4142,7 +4142,7 @@ EXPORT_SYMBOL(end_renaming); * * Returns: mode to be passed to the filesystem */ -static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap, +static inline umode_t vfs_prepare_mode(const struct mnt_idmap *idmap, const struct inode *dir, umode_t mode, umode_t mask_perms, umode_t type) { diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 7beb0af2649847..a022dc7781bfb1 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -688,7 +688,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, return err; } -static int ovl_create_object(struct mnt_idmap *idmap, struct dentry *dentry, +static int ovl_create_object(const struct mnt_idmap *idmap, struct dentry *dentry, int mode, dev_t rdev, const char *link) { int err; diff --git a/fs/xfs/libxfs/xfs_inode_util.h b/fs/xfs/libxfs/xfs_inode_util.h index 060242998a230d..e9eac35159c372 100644 --- a/fs/xfs/libxfs/xfs_inode_util.h +++ b/fs/xfs/libxfs/xfs_inode_util.h @@ -27,7 +27,7 @@ prid_t xfs_get_initial_prid(struct xfs_inode *dp); * idmap to NULL. To create a tree root, set pip to NULL. */ struct xfs_icreate_args { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct xfs_inode *pip; /* parent inode or null */ dev_t rdev; umode_t mode; diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 030a7c8f2c129a..6fcd1c5928f8ce 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -2084,7 +2084,7 @@ xfs_sort_inodes( */ static int xfs_rename_alloc_whiteout( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct xfs_name *src_name, struct xfs_inode *dp, struct xfs_inode **wip) @@ -2130,7 +2130,7 @@ xfs_rename_alloc_whiteout( */ int xfs_rename( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct xfs_inode *src_dp, struct xfs_name *src_name, struct xfs_inode *src_ip, diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 34c1038ebfcde1..12bc4573c52dab 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -568,7 +568,7 @@ int xfs_remove(struct xfs_inode *dp, struct xfs_name *name, struct xfs_inode *ip); int xfs_link(struct xfs_inode *tdp, struct xfs_inode *sip, struct xfs_name *target_name); -int xfs_rename(struct mnt_idmap *idmap, +int xfs_rename(const struct mnt_idmap *idmap, struct xfs_inode *src_dp, struct xfs_name *src_name, struct xfs_inode *src_ip, struct xfs_inode *target_dp, struct xfs_name *target_name, diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 4a3299abf774f6..df5e7c1b233484 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -169,7 +169,7 @@ xfs_create_need_xattr( STATIC int xfs_generic_create( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, diff --git a/fs/xfs/xfs_symlink.c b/fs/xfs/xfs_symlink.c index 5585ac7f4d16bf..40a9bb1703b582 100644 --- a/fs/xfs/xfs_symlink.c +++ b/fs/xfs/xfs_symlink.c @@ -82,7 +82,7 @@ xfs_readlink( int xfs_symlink( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct xfs_inode *dp, struct xfs_name *link_name, const char *target_path, diff --git a/fs/xfs/xfs_symlink.h b/fs/xfs/xfs_symlink.h index 0d29a50e66fdc8..3c5a969f9fc558 100644 --- a/fs/xfs/xfs_symlink.h +++ b/fs/xfs/xfs_symlink.h @@ -7,7 +7,7 @@ /* Kernel only symlink definitions */ -int xfs_symlink(struct mnt_idmap *idmap, struct xfs_inode *dp, +int xfs_symlink(const struct mnt_idmap *idmap, struct xfs_inode *dp, struct xfs_name *link_name, const char *target_path, umode_t mode, struct xfs_inode **ipp); int xfs_readlink(struct xfs_inode *ip, char *link); diff --git a/include/linux/fs.h b/include/linux/fs.h index e2babddf755d40..91a23c8dbbf69c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1830,10 +1830,10 @@ extern long compat_ptr_ioctl(struct file *file, unsigned int cmd, /* * VFS file helper functions. */ -void inode_init_owner(struct mnt_idmap *idmap, struct inode *inode, +void inode_init_owner(const struct mnt_idmap *idmap, struct inode *inode, const struct inode *dir, umode_t mode); extern bool may_open_dev(const struct path *path); -umode_t mode_strip_sgid(struct mnt_idmap *idmap, +umode_t mode_strip_sgid(const struct mnt_idmap *idmap, const struct inode *dir, umode_t mode); bool in_group_or_capable(const struct mnt_idmap *idmap, const struct inode *inode, vfsgid_t vfsgid); diff --git a/mm/shmem.c b/mm/shmem.c index 897fa2b61346f6..9ab5231c4dfe58 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3022,7 +3022,7 @@ static struct offset_ctx *shmem_get_offset_ctx(struct inode *inode) return &SHMEM_I(inode)->dir_offsets; } -static struct inode *__shmem_get_inode(struct mnt_idmap *idmap, +static struct inode *__shmem_get_inode(const struct mnt_idmap *idmap, struct super_block *sb, struct inode *dir, umode_t mode, dev_t dev, vma_flags_t flags) @@ -3102,7 +3102,7 @@ static struct inode *__shmem_get_inode(struct mnt_idmap *idmap, } #ifdef CONFIG_TMPFS_QUOTA -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, +static struct inode *shmem_get_inode(const struct mnt_idmap *idmap, struct super_block *sb, struct inode *dir, umode_t mode, dev_t dev, vma_flags_t flags) { @@ -3130,7 +3130,7 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, return ERR_PTR(err); } #else -static struct inode *shmem_get_inode(struct mnt_idmap *idmap, +static struct inode *shmem_get_inode(const struct mnt_idmap *idmap, struct super_block *sb, struct inode *dir, umode_t mode, dev_t dev, vma_flags_t flags) { @@ -5792,7 +5792,7 @@ static inline void shmem_unacct_size(unsigned long flags, loff_t size) { } -static inline struct inode *shmem_get_inode(struct mnt_idmap *idmap, +static inline struct inode *shmem_get_inode(const struct mnt_idmap *idmap, struct super_block *sb, struct inode *dir, umode_t mode, dev_t dev, vma_flags_t flags) { From 59ffddf0fbeb132fec8fff77d7f3cf1222747a66 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:36 +0200 Subject: [PATCH 0762/2286] fs: port acl to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-11-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/ntfs/ea.c | 4 ++-- fs/ntfs/ea.h | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/inode.c | 2 +- fs/ntfs3/ntfs_fs.h | 4 ++-- fs/ntfs3/xattr.c | 4 ++-- fs/posix_acl.c | 6 +++--- include/linux/lsm_hook_defs.h | 8 ++++---- include/linux/posix_acl.h | 4 ++-- include/linux/security.h | 16 ++++++++-------- security/integrity/evm/evm_main.c | 10 +++++----- security/integrity/ima/ima_appraise.c | 4 ++-- security/security.c | 8 ++++---- security/selinux/hooks.c | 6 +++--- security/smack/smack_lsm.c | 6 +++--- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index cdd306933d7317..744ea8c446e580 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -998,7 +998,7 @@ struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, return acl; } -static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap, +static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, struct inode *inode, struct posix_acl *acl, int type, bool init_acl) { @@ -1086,7 +1086,7 @@ int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false); } -int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, +int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir) { struct posix_acl *default_acl, *acl; diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index acb39c2a6fbc86..9d17abc2e49169 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -21,7 +21,7 @@ struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); -int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, +int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir); #else #define ntfs_get_acl NULL diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 7091b2496facb2..e2cf1ab6935a54 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -391,7 +391,7 @@ static int ntfs_sd_add_everyone(struct ntfs_inode *ni) return ret; } -static struct ntfs_inode *__ntfs_create(struct mnt_idmap *idmap, struct inode *dir, +static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct inode *dir, __le16 *name, u8 name_len, mode_t mode, dev_t dev, const char *target, int target_len) { diff --git a/fs/ntfs3/inode.c b/fs/ntfs3/inode.c index 56b4f6469a280b..a9fdd33f2886c9 100644 --- a/fs/ntfs3/inode.c +++ b/fs/ntfs3/inode.c @@ -1381,7 +1381,7 @@ ntfs_create_reparse_buffer(struct ntfs_sb_info *sbi, const char *symname, * * NOTE: if fnd != NULL (ntfs_atomic_open) then @dir is locked */ -int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, +int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const struct cpu_str *uni, umode_t mode, dev_t dev, const char *symname, u32 size, struct ntfs_fnd *fnd) diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 5811d89d67b394..a2b74f41048f58 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -803,7 +803,7 @@ int ntfs_set_size(struct inode *inode, u64 new_size); int ntfs3_write_inode(struct inode *inode, struct writeback_control *wbc); int ntfs_sync_inode(struct inode *inode); int inode_read_data(struct inode *inode, void *data, size_t bytes); -int ntfs_create_inode(struct mnt_idmap *idmap, struct inode *dir, +int ntfs_create_inode(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const struct cpu_str *uni, umode_t mode, dev_t dev, const char *symname, u32 size, struct ntfs_fnd *fnd); @@ -967,7 +967,7 @@ struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); -int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, +int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir); #else #define ntfs_get_acl NULL diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 594ef6860b93bf..d800735477c36e 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -595,7 +595,7 @@ struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, return acl; } -static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap, +static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, struct inode *inode, struct posix_acl *acl, int type, bool init_acl) { @@ -688,7 +688,7 @@ int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * * Called from ntfs_create_inode(). */ -int ntfs_init_acl(struct mnt_idmap *idmap, struct inode *inode, +int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir) { struct posix_acl *default_acl, *acl; diff --git a/fs/posix_acl.c b/fs/posix_acl.c index f9c55a59c6c677..b279804ebf5657 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -378,7 +378,7 @@ EXPORT_SYMBOL(posix_acl_from_mode); * by the acl. Returns -E... otherwise. */ int -posix_acl_permission(struct mnt_idmap *idmap, struct inode *inode, +posix_acl_permission(const struct mnt_idmap *idmap, struct inode *inode, const struct posix_acl *acl, int want) { const struct posix_acl_entry *pa, *pe, *mask_obj; @@ -709,7 +709,7 @@ EXPORT_SYMBOL_GPL(posix_acl_create); * * Called from set_acl inode operations. */ -int posix_acl_update_mode(struct mnt_idmap *idmap, +int posix_acl_update_mode(const struct mnt_idmap *idmap, struct inode *inode, umode_t *mode_p, struct posix_acl **acl) { @@ -889,7 +889,7 @@ EXPORT_SYMBOL (posix_acl_to_xattr); * Return: On success, the size of the stored uapi posix acls, on error a * negative errno. */ -static ssize_t vfs_posix_acl_to_xattr(struct mnt_idmap *idmap, +static ssize_t vfs_posix_acl_to_xattr(const struct mnt_idmap *idmap, struct inode *inode, const struct posix_acl *acl, void *buffer, size_t size) diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 65c9609ec20770..30897590258f3b 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -159,15 +159,15 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry, const char *name) LSM_HOOK(int, 0, inode_file_setattr, struct dentry *dentry, struct file_kattr *fa) LSM_HOOK(int, 0, inode_file_getattr, struct dentry *dentry, struct file_kattr *fa) -LSM_HOOK(int, 0, inode_set_acl, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_set_acl, const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) LSM_HOOK(void, LSM_RET_VOID, inode_post_set_acl, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) -LSM_HOOK(int, 0, inode_get_acl, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_get_acl, const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) -LSM_HOOK(int, 0, inode_remove_acl, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_remove_acl, const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) -LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, struct mnt_idmap *idmap, +LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry) LSM_HOOK(int, 0, inode_killpriv, struct mnt_idmap *idmap, diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index 62d497763e25a5..7c6cfcae0a9d48 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -84,7 +84,7 @@ struct posix_acl *posix_acl_clone(const struct posix_acl *acl, gfp_t flags); int posix_acl_chmod(struct mnt_idmap *, struct dentry *, umode_t); extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **, struct posix_acl **); -int posix_acl_update_mode(struct mnt_idmap *, struct inode *, umode_t *, +int posix_acl_update_mode(const struct mnt_idmap *, struct inode *, umode_t *, struct posix_acl **); int simple_set_acl(struct mnt_idmap *, struct dentry *, @@ -96,7 +96,7 @@ void set_cached_acl(struct inode *inode, int type, struct posix_acl *acl); void forget_cached_acl(struct inode *inode, int type); void forget_all_cached_acls(struct inode *inode); int posix_acl_valid(struct user_namespace *, const struct posix_acl *); -int posix_acl_permission(struct mnt_idmap *, struct inode *, +int posix_acl_permission(const struct mnt_idmap *, struct inode *, const struct posix_acl *, int); static inline void cache_no_acl(struct inode *inode) diff --git a/include/linux/security.h b/include/linux/security.h index 4598a13657ee2c..12bc12e243bf1a 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -430,16 +430,16 @@ int security_inode_getattr(const struct path *path); int security_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags); -int security_inode_set_acl(struct mnt_idmap *idmap, +int security_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl); void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name, struct posix_acl *kacl); -int security_inode_get_acl(struct mnt_idmap *idmap, +int security_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); -int security_inode_remove_acl(struct mnt_idmap *idmap, +int security_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); -void security_inode_post_remove_acl(struct mnt_idmap *idmap, +void security_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); void security_inode_post_setxattr(struct dentry *dentry, const char *name, @@ -1003,7 +1003,7 @@ static inline int security_inode_setxattr(struct mnt_idmap *idmap, return cap_inode_setxattr(dentry, name, value, size, flags); } -static inline int security_inode_set_acl(struct mnt_idmap *idmap, +static inline int security_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) @@ -1016,21 +1016,21 @@ static inline void security_inode_post_set_acl(struct dentry *dentry, struct posix_acl *kacl) { } -static inline int security_inode_get_acl(struct mnt_idmap *idmap, +static inline int security_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return 0; } -static inline int security_inode_remove_acl(struct mnt_idmap *idmap, +static inline int security_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return 0; } -static inline void security_inode_post_remove_acl(struct mnt_idmap *idmap, +static inline void security_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 06e187c9912d7b..aaf228ebed7ce4 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -652,7 +652,7 @@ static int evm_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, } #ifdef CONFIG_FS_POSIX_ACL -static int evm_inode_set_acl_change(struct mnt_idmap *idmap, +static int evm_inode_set_acl_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct posix_acl *kacl) { @@ -671,7 +671,7 @@ static int evm_inode_set_acl_change(struct mnt_idmap *idmap, return 0; } #else -static inline int evm_inode_set_acl_change(struct mnt_idmap *idmap, +static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct posix_acl *kacl) @@ -693,7 +693,7 @@ static inline int evm_inode_set_acl_change(struct mnt_idmap *idmap, * * Return: zero on success, -EPERM on failure. */ -static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { enum integrity_status evm_status; @@ -745,7 +745,7 @@ static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * * Return: zero on success, -EPERM on failure. */ -static int evm_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return evm_inode_set_acl(idmap, dentry, acl_name, NULL); @@ -926,7 +926,7 @@ static void evm_inode_post_removexattr(struct dentry *dentry, * Update the 'security.evm' xattr with the EVM HMAC re-calculated after * removing posix acls. */ -static inline void evm_inode_post_remove_acl(struct mnt_idmap *idmap, +static inline void evm_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 6b21db789ef1fa..1aed041c127a75 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -790,7 +790,7 @@ static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, return result; } -static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { if (evm_revalidate_status(acl_name)) @@ -815,7 +815,7 @@ static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, return result; } -static int ima_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return ima_inode_set_acl(idmap, dentry, acl_name, NULL); diff --git a/security/security.c b/security/security.c index 2ee276ab15c5bc..e81dcda3050b9d 100644 --- a/security/security.c +++ b/security/security.c @@ -1953,7 +1953,7 @@ int security_inode_setxattr(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_set_acl(struct mnt_idmap *idmap, +int security_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { @@ -1990,7 +1990,7 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name, * * Return: Returns 0 if permission is granted. */ -int security_inode_get_acl(struct mnt_idmap *idmap, +int security_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -2009,7 +2009,7 @@ int security_inode_get_acl(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_remove_acl(struct mnt_idmap *idmap, +int security_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -2026,7 +2026,7 @@ int security_inode_remove_acl(struct mnt_idmap *idmap, * Update inode security data after successfully removing posix acls on * @dentry in @idmap. The posix acls are identified by @acl_name. */ -void security_inode_post_remove_acl(struct mnt_idmap *idmap, +void security_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1da5..672d6374d8b059 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3453,20 +3453,20 @@ static int selinux_inode_setxattr(struct mnt_idmap *idmap, &ad); } -static int selinux_inode_set_acl(struct mnt_idmap *idmap, +static int selinux_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); } -static int selinux_inode_get_acl(struct mnt_idmap *idmap, +static int selinux_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return dentry_has_perm(current_cred(), dentry, FILE__GETATTR); } -static int selinux_inode_remove_acl(struct mnt_idmap *idmap, +static int selinux_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8e88ac65fd7fcf..244cdb49e928ff 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1543,7 +1543,7 @@ static int smack_inode_removexattr(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_set_acl(struct mnt_idmap *idmap, +static int smack_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { @@ -1566,7 +1566,7 @@ static int smack_inode_set_acl(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_get_acl(struct mnt_idmap *idmap, +static int smack_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { struct smk_audit_info ad; @@ -1588,7 +1588,7 @@ static int smack_inode_get_acl(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_remove_acl(struct mnt_idmap *idmap, +static int smack_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { struct smk_audit_info ad; From 41839e7bd92a16aed96a35ac61042b1291dac8f1 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:37 +0200 Subject: [PATCH 0763/2286] fs: port ->permission() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-12-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/afs/internal.h | 2 +- fs/afs/security.c | 2 +- fs/attr.c | 2 +- fs/autofs/root.c | 4 ++-- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/btrfs/ioctl.c | 2 +- fs/ceph/inode.c | 2 +- fs/ceph/super.h | 2 +- fs/coda/coda_linux.h | 2 +- fs/coda/dir.c | 2 +- fs/coda/pioctl.c | 4 ++-- fs/ecryptfs/inode.c | 2 +- fs/failfs.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/gfs2/inode.h | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/internal.h | 2 +- fs/kernfs/inode.c | 2 +- fs/kernfs/kernfs-internal.h | 2 +- fs/namei.c | 22 +++++++++++----------- fs/nfs/dir.c | 2 +- fs/nilfs2/inode.c | 2 +- fs/nilfs2/nilfs.h | 2 +- fs/ocfs2/file.c | 2 +- fs/ocfs2/file.h | 2 +- fs/orangefs/inode.c | 2 +- fs/orangefs/orangefs-kernel.h | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/proc/base.c | 4 ++-- fs/proc/fd.c | 4 ++-- fs/proc/fd.h | 2 +- fs/proc/proc_sysctl.c | 2 +- fs/smb/client/cifsfs.c | 2 +- fs/smb/server/vfs.c | 2 +- fs/smb/server/vfs.h | 2 +- fs/tracefs/inode.c | 2 +- include/linux/fs.h | 8 ++++---- include/linux/nfs_fs.h | 2 +- security/selinux/selinuxfs.c | 2 +- 44 files changed, 61 insertions(+), 61 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 844d65eb47a5fb..5af7d3bfcd5e35 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -74,7 +74,7 @@ prototypes:: int (*readlink) (struct dentry *, char __user *,int); const char *(*get_link) (struct dentry *, struct inode *, struct delayed_call *); void (*truncate) (struct inode *); - int (*permission) (struct mnt_idmap *, struct inode *, int, unsigned int); + int (*permission) (const struct mnt_idmap *, struct inode *, int, unsigned int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index d3a93eec3945f8..b7e476f701854f 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -428,7 +428,7 @@ As of kernel 2.6.22, the following members are defined: int (*readlink) (struct dentry *, char __user *,int); const char *(*get_link) (struct dentry *, struct inode *, struct delayed_call *); - int (*permission) (struct mnt_idmap *, struct inode *, int); + int (*permission) (const struct mnt_idmap *, struct inode *, int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 290873bac89b3f..e772da2a15f29a 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1530,7 +1530,7 @@ extern void afs_cache_permit(struct afs_vnode *, struct key *, unsigned int, extern struct key *afs_request_key(struct afs_cell *); extern struct key *afs_request_key_rcu(struct afs_cell *); extern int afs_check_permit(struct afs_vnode *, struct key *, afs_access_t *); -extern int afs_permission(struct mnt_idmap *, struct inode *, int); +extern int afs_permission(const struct mnt_idmap *, struct inode *, int); extern void __exit afs_clean_up_permit_cache(void); /* diff --git a/fs/afs/security.c b/fs/afs/security.c index 6d00d62a65edf6..fd040f7c6766c7 100644 --- a/fs/afs/security.c +++ b/fs/afs/security.c @@ -428,7 +428,7 @@ int afs_check_permit(struct afs_vnode *vnode, struct key *key, * - AFS ACLs are attached to directories only, and a file is controlled by its * parent directory's ACL */ -int afs_permission(struct mnt_idmap *idmap, struct inode *inode, +int afs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct afs_vnode *vnode = AFS_FS_I(inode); diff --git a/fs/attr.c b/fs/attr.c index 23f1347ff24046..c5033afe04bfb4 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -369,7 +369,7 @@ void setattr_copy(const struct mnt_idmap *idmap, struct inode *inode, } EXPORT_SYMBOL(setattr_copy); -int may_setattr(struct mnt_idmap *idmap, struct inode *inode, +int may_setattr(const struct mnt_idmap *idmap, struct inode *inode, unsigned int ia_valid) { int error; diff --git a/fs/autofs/root.c b/fs/autofs/root.c index b36439f4521ebe..3158df448025e9 100644 --- a/fs/autofs/root.c +++ b/fs/autofs/root.c @@ -11,7 +11,7 @@ #include "autofs_i.h" -static int autofs_dir_permission(struct mnt_idmap *, struct inode *, int); +static int autofs_dir_permission(const struct mnt_idmap *, struct inode *, int); static int autofs_dir_symlink(struct mnt_idmap *, struct inode *, struct dentry *, const char *); static int autofs_dir_unlink(struct inode *, struct dentry *); @@ -552,7 +552,7 @@ static struct dentry *autofs_lookup(struct inode *dir, return NULL; } -static int autofs_dir_permission(struct mnt_idmap *idmap, +static int autofs_dir_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { if (mask & MAY_WRITE) { diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 486c40f73e51e0..c2cfa9395bc919 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -89,7 +89,7 @@ static int bad_inode_readlink(struct dentry *dentry, char __user *buffer, return -EIO; } -static int bad_inode_permission(struct mnt_idmap *idmap, +static int bad_inode_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return -EIO; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index d5a78d9027e202..7bf38f069116fd 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9375,7 +9375,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, * we are marking them with IOP_FASTPERM_MAY_EXEC, allowing path lookup to * elide calls here. */ -static int btrfs_permission(struct mnt_idmap *idmap, +static int btrfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct btrfs_root *root = BTRFS_I(inode)->root; diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 8eacdc2ee4de3b..9d6302547b1293 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -1726,7 +1726,7 @@ static noinline int btrfs_search_path_in_tree(struct btrfs_root *root, u64 dirid return 0; } -static int btrfs_search_path_in_tree_user(struct mnt_idmap *idmap, +static int btrfs_search_path_in_tree_user(const struct mnt_idmap *idmap, struct inode *inode, struct btrfs_ioctl_ino_lookup_user_args *args) { diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 3921b601ad5976..b8527f67e3f0a5 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -3098,7 +3098,7 @@ int ceph_do_getvxattr(struct inode *inode, const char *name, void *value, * Check inode permissions. We verify we have a valid value for * the AUTH cap, then call the generic handler. */ -int ceph_permission(struct mnt_idmap *idmap, struct inode *inode, +int ceph_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int err; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index e9a9d5737c80b0..ef9861797eb267 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1166,7 +1166,7 @@ static inline int ceph_do_getattr(struct inode *inode, int mask, bool force) { return __ceph_do_getattr(inode, NULL, mask, force); } -extern int ceph_permission(struct mnt_idmap *idmap, +extern int ceph_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); struct ceph_iattr { diff --git a/fs/coda/coda_linux.h b/fs/coda/coda_linux.h index dd6277d87afb7d..5ee6cedd38650d 100644 --- a/fs/coda/coda_linux.h +++ b/fs/coda/coda_linux.h @@ -46,7 +46,7 @@ extern const struct file_operations coda_ioctl_operations; /* operations shared over more than one file */ int coda_open(struct inode *i, struct file *f); int coda_release(struct inode *i, struct file *f); -int coda_permission(struct mnt_idmap *idmap, struct inode *inode, +int coda_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int coda_revalidate_inode(struct inode *); int coda_getattr(struct mnt_idmap *, const struct path *, struct kstat *, diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 67148edfadeef4..e8db5960b61b87 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -73,7 +73,7 @@ static struct dentry *coda_lookup(struct inode *dir, struct dentry *entry, unsig } -int coda_permission(struct mnt_idmap *idmap, struct inode *inode, +int coda_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int error; diff --git a/fs/coda/pioctl.c b/fs/coda/pioctl.c index 36e35c15561a98..c457e9bab94bdc 100644 --- a/fs/coda/pioctl.c +++ b/fs/coda/pioctl.c @@ -24,7 +24,7 @@ #include "coda_linux.h" /* pioctl ops */ -static int coda_ioctl_permission(struct mnt_idmap *idmap, +static int coda_ioctl_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); static long coda_pioctl(struct file *filp, unsigned int cmd, unsigned long user_data); @@ -41,7 +41,7 @@ const struct file_operations coda_ioctl_operations = { }; /* the coda pioctl inode ops */ -static int coda_ioctl_permission(struct mnt_idmap *idmap, +static int coda_ioctl_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return (mask & MAY_EXEC) ? -EACCES : 0; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 525297c7ebd8c4..6dafca14ca78f6 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -849,7 +849,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length) } static int -ecryptfs_permission(struct mnt_idmap *idmap, struct inode *inode, +ecryptfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return inode_permission(&nop_mnt_idmap, diff --git a/fs/failfs.c b/fs/failfs.c index 66a36da3d236d6..cec6cad99eb212 100644 --- a/fs/failfs.c +++ b/fs/failfs.c @@ -22,7 +22,7 @@ bool failfs_mnt(const struct vfsmount *mnt) return mnt->mnt_sb == failfs_root_path.mnt->mnt_sb; } -static int failfs_permission(struct mnt_idmap *idmap, struct inode *inode, +static int failfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return -EOPNOTSUPP; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7a204f55ca84ba..4b87973c727d9e 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1763,7 +1763,7 @@ static int fuse_perm_getattr(struct inode *inode, int mask) * access request is sent. Execute permission is still checked * locally based on file mode. */ -static int fuse_permission(struct mnt_idmap *idmap, +static int fuse_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct fuse_conn *fc = get_fuse_conn(inode); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index f361876c558335..1b1d22abe1d47e 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1963,7 +1963,7 @@ static const char *gfs2_get_link(struct dentry *dentry, * Returns: errno */ -int gfs2_permission(struct mnt_idmap *idmap, struct inode *inode, +int gfs2_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int may_not_block = mask & MAY_NOT_BLOCK; diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h index 2fcd96dd136139..373473eb091c63 100644 --- a/fs/gfs2/inode.h +++ b/fs/gfs2/inode.h @@ -97,7 +97,7 @@ int gfs2_dinode_dealloc(struct gfs2_inode *ip); struct inode *gfs2_lookupi(struct inode *dir, const struct qstr *name, int is_root); -int gfs2_permission(struct mnt_idmap *idmap, +int gfs2_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); struct inode *gfs2_lookup_meta(struct inode *dip, const char *name); void gfs2_dinode_out(const struct gfs2_inode *ip, void *buf); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 7add056d47d8f0..0d5c5f11f91399 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -774,7 +774,7 @@ static int hostfs_rename2(struct mnt_idmap *idmap, return err; } -static int hostfs_permission(struct mnt_idmap *idmap, +static int hostfs_permission(const struct mnt_idmap *idmap, struct inode *ino, int desired) { char *name; diff --git a/fs/internal.h b/fs/internal.h index 161b0cafe17fbf..a502b1fbacd245 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -55,7 +55,7 @@ extern int filename_lookup(int dfd, struct filename *name, unsigned flags, struct path *path, const struct path *root); int filename_rmdir(int dfd, struct filename *name); int filename_unlinkat(int dfd, struct filename *name); -int may_linkat(struct mnt_idmap *idmap, const struct path *link); +int may_linkat(const struct mnt_idmap *idmap, const struct path *link); int filename_renameat2(int olddfd, struct filename *oldname, int newdfd, struct filename *newname, unsigned int flags); int filename_mkdirat(int dfd, struct filename *name, umode_t mode); diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 237dcdd73fc24a..d53d104e54d549 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -272,7 +272,7 @@ void kernfs_evict_inode(struct inode *inode) kernfs_put(kn); } -int kernfs_iop_permission(struct mnt_idmap *idmap, +int kernfs_iop_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct kernfs_node *kn; diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index aa784b540b3616..0f6f412213f6bb 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -171,7 +171,7 @@ extern struct kmem_cache *kernfs_node_cache, *kernfs_iattrs_cache; */ extern const struct xattr_handler * const kernfs_xattr_handlers[]; void kernfs_evict_inode(struct inode *inode); -int kernfs_iop_permission(struct mnt_idmap *idmap, +int kernfs_iop_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int kernfs_iop_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); diff --git a/fs/namei.c b/fs/namei.c index db82daec364656..f5e00ae41ed4a2 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -371,7 +371,7 @@ struct filename *complete_getname(struct delayed_filename *v) * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -static int check_acl(struct mnt_idmap *idmap, +static int check_acl(const struct mnt_idmap *idmap, struct inode *inode, int mask) { #ifdef CONFIG_FS_POSIX_ACL @@ -435,7 +435,7 @@ static inline bool no_acl_inode(struct inode *inode) * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -static int acl_permission_check(struct mnt_idmap *idmap, +static int acl_permission_check(const struct mnt_idmap *idmap, struct inode *inode, int mask) { unsigned int mode = inode->i_mode; @@ -518,7 +518,7 @@ static int acl_permission_check(struct mnt_idmap *idmap, * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int generic_permission(struct mnt_idmap *idmap, struct inode *inode, +int generic_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int ret; @@ -575,7 +575,7 @@ EXPORT_SYMBOL(generic_permission); * flag in inode->i_opflags, that says "this has not special * permission function, use the fast case". */ -static inline int do_inode_permission(struct mnt_idmap *idmap, +static inline int do_inode_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) { @@ -625,7 +625,7 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) * * When checking for MAY_APPEND, MAY_WRITE must also be set in @mask. */ -int inode_permission(struct mnt_idmap *idmap, +int inode_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int retval; @@ -680,7 +680,7 @@ EXPORT_SYMBOL(inode_permission); * on IOP_FASTPERM can still get the optimization if they set IOP_FASTPERM_MAY_EXEC * on their directory inodes. */ -static __always_inline int lookup_inode_permission_may_exec(struct mnt_idmap *idmap, +static __always_inline int lookup_inode_permission_may_exec(const struct mnt_idmap *idmap, struct inode *inode, int mask) { /* Lookup already checked this to return -ENOTDIR */ @@ -1314,7 +1314,7 @@ static inline int may_follow_link(struct nameidata *nd, const struct inode *inod * * Otherwise returns true. */ -static bool safe_hardlink_source(struct mnt_idmap *idmap, +static bool safe_hardlink_source(const struct mnt_idmap *idmap, struct inode *inode) { umode_t mode = inode->i_mode; @@ -1357,7 +1357,7 @@ static bool safe_hardlink_source(struct mnt_idmap *idmap, * * Returns 0 if successful, -ve on error. */ -int may_linkat(struct mnt_idmap *idmap, const struct path *link) +int may_linkat(const struct mnt_idmap *idmap, const struct path *link) { struct inode *inode = link->dentry->d_inode; @@ -1952,7 +1952,7 @@ static struct dentry *lookup_slow_killable(const struct qstr *name, return res; } -static inline int may_lookup(struct mnt_idmap *idmap, +static inline int may_lookup(const struct mnt_idmap *idmap, struct nameidata *restrict nd) { int err, mask; @@ -4228,7 +4228,7 @@ bool may_open_dev(const struct path *path) !(path->mnt->mnt_sb->s_iflags & SB_I_NODEV); } -static int may_open(struct mnt_idmap *idmap, const struct path *path, +static int may_open(const struct mnt_idmap *idmap, const struct path *path, int acc_mode, int flag) { struct dentry *dentry = path->dentry; @@ -4312,7 +4312,7 @@ static inline int open_to_namei_flags(int flag) return flag; } -static int may_o_create(struct mnt_idmap *idmap, +static int may_o_create(const struct mnt_idmap *idmap, const struct path *dir, struct dentry *dentry, umode_t mode) { diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 49394123bd0961..7574bc2fb677fa 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -3393,7 +3393,7 @@ static int nfs_execute_ok(struct inode *inode, int mask) return ret; } -int nfs_permission(struct mnt_idmap *idmap, +int nfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 34e6096069ad67..9342f651bf9b14 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -943,7 +943,7 @@ int nilfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, return err; } -int nilfs_permission(struct mnt_idmap *idmap, struct inode *inode, +int nilfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct nilfs_root *root = NILFS_I(inode)->i_root; diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h index 4fc42d3787a441..0eaa191f70b8bd 100644 --- a/fs/nilfs2/nilfs.h +++ b/fs/nilfs2/nilfs.h @@ -302,7 +302,7 @@ extern void nilfs_evict_inode(struct inode *); extern int nilfs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); extern void nilfs_write_failed(struct address_space *mapping, loff_t to); -int nilfs_permission(struct mnt_idmap *idmap, struct inode *inode, +int nilfs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int nilfs_load_inode_block(struct inode *inode, struct buffer_head **pbh); extern int nilfs_inode_dirty(struct inode *); diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index d6e977ba656568..24bbf607546cfd 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1349,7 +1349,7 @@ int ocfs2_getattr(struct mnt_idmap *idmap, const struct path *path, return err; } -int ocfs2_permission(struct mnt_idmap *idmap, struct inode *inode, +int ocfs2_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int ret, had_lock; diff --git a/fs/ocfs2/file.h b/fs/ocfs2/file.h index 41e65e45a9f3ff..08e4d679653177 100644 --- a/fs/ocfs2/file.h +++ b/fs/ocfs2/file.h @@ -54,7 +54,7 @@ int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ocfs2_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); -int ocfs2_permission(struct mnt_idmap *idmap, +int ocfs2_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index cd3273c88e030e..8e6eeec501bc65 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -872,7 +872,7 @@ int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path, return ret; } -int orangefs_permission(struct mnt_idmap *idmap, +int orangefs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { int ret; diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h index 1451fc2c1917c4..c582ceffb26e3c 100644 --- a/fs/orangefs/orangefs-kernel.h +++ b/fs/orangefs/orangefs-kernel.h @@ -357,7 +357,7 @@ int orangefs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); -int orangefs_permission(struct mnt_idmap *idmap, +int orangefs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int orangefs_update_time(struct inode *inode, enum fs_update_time type, diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 0364e7d08ad725..1166381f185288 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -303,7 +303,7 @@ int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, return err; } -int ovl_permission(struct mnt_idmap *idmap, +int ovl_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct inode *upperinode = ovl_inode_upper(inode); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 6508f30aaaca2a..513ba93c7d1b36 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -802,7 +802,7 @@ int ovl_set_nlink_lower(struct dentry *dentry); unsigned int ovl_get_nlink(struct ovl_fs *ofs, struct dentry *lowerdentry, struct dentry *upperdentry, unsigned int fallback); -int ovl_permission(struct mnt_idmap *idmap, struct inode *inode, +int ovl_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); #ifdef CONFIG_FS_POSIX_ACL diff --git a/fs/proc/base.c b/fs/proc/base.c index 6a39de424f62a1..8dc43c84629810 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -743,7 +743,7 @@ static bool has_pid_permissions(struct proc_fs_info *fs_info, } -static int proc_pid_permission(struct mnt_idmap *idmap, +static int proc_pid_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb); @@ -3607,7 +3607,7 @@ int proc_pid_readdir(struct file *file, struct dir_context *ctx) * This function makes sure that the node is always accessible for members of * same thread group. */ -static int proc_tid_comm_permission(struct mnt_idmap *idmap, +static int proc_tid_comm_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { bool is_same_tgroup; diff --git a/fs/proc/fd.c b/fs/proc/fd.c index 0f9a1556f2a347..0653bceb730db4 100644 --- a/fs/proc/fd.c +++ b/fs/proc/fd.c @@ -82,7 +82,7 @@ static int seq_fdinfo_open(struct inode *inode, struct file *file) * that the current task has PTRACE_MODE_READ in addition to the normal * POSIX-like checks. */ -static int proc_fdinfo_permission(struct mnt_idmap *idmap, struct inode *inode, +static int proc_fdinfo_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { bool allowed = false; @@ -323,7 +323,7 @@ static struct dentry *proc_lookupfd(struct inode *dir, struct dentry *dentry, * /proc/pid/fd needs a special permission handler so that a process can still * access /proc/self/fd after it has executed a setuid(). */ -int proc_fd_permission(struct mnt_idmap *idmap, +int proc_fd_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { struct task_struct *p; diff --git a/fs/proc/fd.h b/fs/proc/fd.h index 7e7265f7e06f8b..77f2e4f38592ba 100644 --- a/fs/proc/fd.h +++ b/fs/proc/fd.h @@ -10,7 +10,7 @@ extern const struct inode_operations proc_fd_inode_operations; extern const struct file_operations proc_fdinfo_operations; extern const struct inode_operations proc_fdinfo_inode_operations; -extern int proc_fd_permission(struct mnt_idmap *idmap, +extern int proc_fd_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); static inline unsigned int proc_fd(struct inode *inode) diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 04a382178c657b..5315d891f24467 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -788,7 +788,7 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx) return 0; } -static int proc_sys_permission(struct mnt_idmap *idmap, +static int proc_sys_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { /* diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 7ecd70efdfea89..b1ecbcfb154e44 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -402,7 +402,7 @@ static long cifs_fallocate(struct file *file, int mode, loff_t off, loff_t len) return rc; } -static int cifs_permission(struct mnt_idmap *idmap, +static int cifs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { unsigned int sbflags = cifs_sb_flags(CIFS_SB(inode)); diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index cb52dce113b98f..0361de56edc26b 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -115,7 +115,7 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf, return 0; } -void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap, +void ksmbd_vfs_query_maximal_access(const struct mnt_idmap *idmap, struct dentry *dentry, __le32 *daccess) { *daccess = cpu_to_le32(FILE_READ_ATTRIBUTES | READ_CONTROL); diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 9f4b003166e52e..9c8b5b28892603 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -74,7 +74,7 @@ struct ksmbd_kstat { }; int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child); -void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap, +void ksmbd_vfs_query_maximal_access(const struct mnt_idmap *idmap, struct dentry *dentry, __le32 *daccess); int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode); int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode); diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index f3d6188a3b7b24..7cf32384ad50a4 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -189,7 +189,7 @@ static void set_tracefs_inode_owner(struct inode *inode) inode->i_gid = gid; } -static int tracefs_permission(struct mnt_idmap *idmap, +static int tracefs_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask) { set_tracefs_inode_owner(inode); diff --git a/include/linux/fs.h b/include/linux/fs.h index 91a23c8dbbf69c..f815594dd8cbfc 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1994,7 +1994,7 @@ enum fs_update_time { struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *); - int (*permission) (struct mnt_idmap *, struct inode *, int); + int (*permission) (const struct mnt_idmap *, struct inode *, int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); int (*readlink) (struct dentry *, char __user *,int); @@ -2709,8 +2709,8 @@ static inline int bmap(struct inode *inode, sector_t *block) int notify_change(struct mnt_idmap *, struct dentry *, struct iattr *, struct delegated_inode *); -int inode_permission(struct mnt_idmap *, struct inode *, int); -int generic_permission(struct mnt_idmap *, struct inode *, int); +int inode_permission(const struct mnt_idmap *, struct inode *, int); +int generic_permission(const struct mnt_idmap *, struct inode *, int); static inline int file_permission(struct file *file, int mask) { return inode_permission(file_mnt_idmap(file), @@ -3397,7 +3397,7 @@ static inline bool generic_ci_validate_strict_name(struct inode *dir, } #endif -int may_setattr(struct mnt_idmap *idmap, struct inode *inode, +int may_setattr(const struct mnt_idmap *idmap, struct inode *inode, unsigned int ia_valid); int setattr_prepare(struct mnt_idmap *, struct dentry *, struct iattr *); extern int inode_newsize_ok(const struct inode *, loff_t offset); diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index b85a73ae7919fb..b2828ea03abe76 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -441,7 +441,7 @@ extern int nfs_getattr(struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void nfs_access_add_cache(struct inode *, struct nfs_access_entry *, const struct cred *); extern void nfs_access_set_mask(struct nfs_access_entry *, u32); -extern int nfs_permission(struct mnt_idmap *, struct inode *, int); +extern int nfs_permission(const struct mnt_idmap *, struct inode *, int); extern int nfs_open(struct inode *, struct file *); extern int nfs_attribute_cache_expired(struct inode *inode); extern int nfs_revalidate_inode(struct inode *inode, unsigned long flags); diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index c7d91476971cb5..a0be7f1b5993c4 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -1782,7 +1782,7 @@ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, return sel_attach(dir, name, inode); } -static int reject_all(struct mnt_idmap *idmap, struct inode *inode, int mask) +static int reject_all(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return -EPERM; // no access for anyone, root or no root. } From 16d90b57d843903fa43f44090d1d19e7a005fa3c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:38 +0200 Subject: [PATCH 0764/2286] fs: port xattr to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-13-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- fs/9p/xattr.c | 2 +- fs/afs/xattr.c | 4 ++-- fs/attr.c | 2 +- fs/btrfs/xattr.c | 6 +++--- fs/ceph/xattr.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/ext2/xattr_security.c | 2 +- fs/ext2/xattr_trusted.c | 2 +- fs/ext2/xattr_user.c | 2 +- fs/ext4/xattr_hurd.c | 2 +- fs/ext4/xattr_security.c | 2 +- fs/ext4/xattr_trusted.c | 2 +- fs/ext4/xattr_user.c | 2 +- fs/f2fs/xattr.c | 4 ++-- fs/fuse/dir.c | 2 +- fs/fuse/fuse_i.h | 2 +- fs/fuse/xattr.c | 2 +- fs/gfs2/xattr.c | 2 +- fs/hfs/attr.c | 2 +- fs/hfsplus/xattr.c | 2 +- fs/hfsplus/xattr_security.c | 2 +- fs/hfsplus/xattr_trusted.c | 2 +- fs/hfsplus/xattr_user.c | 2 +- fs/jffs2/security.c | 2 +- fs/jffs2/xattr_trusted.c | 2 +- fs/jffs2/xattr_user.c | 2 +- fs/jfs/xattr.c | 4 ++-- fs/kernfs/inode.c | 4 ++-- fs/nfs/nfs4proc.c | 10 +++++----- fs/ntfs/ea.c | 2 +- fs/ntfs3/xattr.c | 2 +- fs/ocfs2/xattr.c | 6 +++--- fs/orangefs/xattr.c | 2 +- fs/overlayfs/xattrs.c | 4 ++-- fs/pidfs.c | 2 +- fs/smb/client/xattr.c | 2 +- fs/smb/server/vfs.c | 24 ++++++++++++------------ fs/smb/server/vfs.h | 20 ++++++++++---------- fs/ubifs/xattr.c | 2 +- fs/xattr.c | 22 +++++++++++----------- fs/xfs/xfs_iops.c | 2 +- fs/xfs/xfs_xattr.c | 2 +- include/linux/capability.h | 4 ++-- include/linux/fs.h | 2 +- include/linux/lsm_hook_defs.h | 8 ++++---- include/linux/security.h | 20 ++++++++++---------- include/linux/xattr.h | 20 ++++++++++---------- kernel/bpf/inode.c | 2 +- mm/shmem.c | 2 +- net/socket.c | 4 ++-- security/commoncap.c | 8 ++++---- security/integrity/evm/evm_main.c | 4 ++-- security/integrity/ima/ima_appraise.c | 4 ++-- security/security.c | 8 ++++---- security/selinux/hooks.c | 6 +++--- security/smack/smack_lsm.c | 6 +++--- 57 files changed, 135 insertions(+), 135 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 5af7d3bfcd5e35..2a91bc9c15f599 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -148,7 +148,7 @@ prototypes:: struct inode *inode, const char *name, void *buffer, size_t size); int (*set)(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags); diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c index 8604e3377ee7ab..dac06587f67ace 100644 --- a/fs/9p/xattr.c +++ b/fs/9p/xattr.c @@ -153,7 +153,7 @@ static int v9fs_xattr_handler_get(const struct xattr_handler *handler, } static int v9fs_xattr_handler_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/afs/xattr.c b/fs/afs/xattr.c index 3770ed236f671c..bcffd7236cc9c9 100644 --- a/fs/afs/xattr.c +++ b/fs/afs/xattr.c @@ -97,7 +97,7 @@ static const struct afs_operation_ops afs_store_acl_operation = { * Set a file's AFS3 ACL. */ static int afs_xattr_set_acl(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) @@ -228,7 +228,7 @@ static const struct afs_operation_ops yfs_store_opaque_acl2_operation = { * Set a file's YFS ACL. */ static int afs_xattr_set_yfs(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/attr.c b/fs/attr.c index c5033afe04bfb4..1845d145fe4c83 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -158,7 +158,7 @@ static bool chgrp_ok(const struct mnt_idmap *idmap, * Should be called as the first thing in ->setattr implementations, * possibly after taking additional locks. */ -int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry, +int setattr_prepare(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index ab55d10bd71fdb..a06420b9c66288 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -353,7 +353,7 @@ static int btrfs_xattr_handler_get(const struct xattr_handler *handler, } static int btrfs_xattr_handler_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) @@ -395,7 +395,7 @@ static int btrfs_xattr_handler_get_security(const struct xattr_handler *handler, } static int btrfs_xattr_handler_set_security(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, @@ -413,7 +413,7 @@ static int btrfs_xattr_handler_set_security(const struct xattr_handler *handler, } static int btrfs_xattr_handler_set_prop(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c index cc4ffbbcb719a7..7d77214c76c69e 100644 --- a/fs/ceph/xattr.c +++ b/fs/ceph/xattr.c @@ -1352,7 +1352,7 @@ static int ceph_get_xattr_handler(const struct xattr_handler *handler, } static int ceph_set_xattr_handler(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 6dafca14ca78f6..691b3a24b60aa3 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1158,7 +1158,7 @@ static int ecryptfs_xattr_get(const struct xattr_handler *handler, } static int ecryptfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/xattr_security.c b/fs/ext2/xattr_security.c index db47b8ab153e7d..ade074354258ea 100644 --- a/fs/ext2/xattr_security.c +++ b/fs/ext2/xattr_security.c @@ -19,7 +19,7 @@ ext2_xattr_security_get(const struct xattr_handler *handler, static int ext2_xattr_security_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/xattr_trusted.c b/fs/ext2/xattr_trusted.c index 995f931228ce8b..0f12d634d6d08a 100644 --- a/fs/ext2/xattr_trusted.c +++ b/fs/ext2/xattr_trusted.c @@ -26,7 +26,7 @@ ext2_xattr_trusted_get(const struct xattr_handler *handler, static int ext2_xattr_trusted_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/xattr_user.c b/fs/ext2/xattr_user.c index dd15072310818b..48002c033e9c92 100644 --- a/fs/ext2/xattr_user.c +++ b/fs/ext2/xattr_user.c @@ -30,7 +30,7 @@ ext2_xattr_user_get(const struct xattr_handler *handler, static int ext2_xattr_user_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_hurd.c b/fs/ext4/xattr_hurd.c index 8a5842e4cd95c6..a3ecbff72b1015 100644 --- a/fs/ext4/xattr_hurd.c +++ b/fs/ext4/xattr_hurd.c @@ -32,7 +32,7 @@ ext4_xattr_hurd_get(const struct xattr_handler *handler, static int ext4_xattr_hurd_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c index 776cf11d24ca10..af5b8a93fed13a 100644 --- a/fs/ext4/xattr_security.c +++ b/fs/ext4/xattr_security.c @@ -23,7 +23,7 @@ ext4_xattr_security_get(const struct xattr_handler *handler, static int ext4_xattr_security_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_trusted.c b/fs/ext4/xattr_trusted.c index 9811eb0ab27623..458e1982ef83a3 100644 --- a/fs/ext4/xattr_trusted.c +++ b/fs/ext4/xattr_trusted.c @@ -30,7 +30,7 @@ ext4_xattr_trusted_get(const struct xattr_handler *handler, static int ext4_xattr_trusted_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_user.c b/fs/ext4/xattr_user.c index 4b70bf4e762633..ad35215f66104c 100644 --- a/fs/ext4/xattr_user.c +++ b/fs/ext4/xattr_user.c @@ -31,7 +31,7 @@ ext4_xattr_user_get(const struct xattr_handler *handler, static int ext4_xattr_user_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index 6728d1488cadb9..ffe4815ffa6d8a 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -67,7 +67,7 @@ static int f2fs_xattr_generic_get(const struct xattr_handler *handler, } static int f2fs_xattr_generic_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -111,7 +111,7 @@ static int f2fs_xattr_advise_get(const struct xattr_handler *handler, } static int f2fs_xattr_advise_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 4b87973c727d9e..0dbe3c6560f819 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -2143,7 +2143,7 @@ int fuse_flush_times(struct inode *inode, struct fuse_file *ff) * vmtruncate() doesn't allow for this case, so do the rlimit checking * and the actual truncation by hand. */ -int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int fuse_do_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr, struct file *file) { struct inode *inode = d_inode(dentry); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index a07c75377a30af..9ea346ea2e0160 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1189,7 +1189,7 @@ bool fuse_write_update_attr(struct inode *inode, loff_t pos, ssize_t written); int fuse_flush_times(struct inode *inode, struct fuse_file *ff); int fuse_write_inode(struct inode *inode, struct writeback_control *wbc); -int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int fuse_do_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr, struct file *file); void fuse_unlock_inode(struct inode *inode, bool locked); diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index cab2685acc6528..53e3c5e6fff0dd 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -188,7 +188,7 @@ static int fuse_xattr_get(const struct xattr_handler *handler, } static int fuse_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c index b9f48d6f10a97a..0e66e4849b571d 100644 --- a/fs/gfs2/xattr.c +++ b/fs/gfs2/xattr.c @@ -1229,7 +1229,7 @@ int __gfs2_xattr_set(struct inode *inode, const char *name, } static int gfs2_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/hfs/attr.c b/fs/hfs/attr.c index f8395cdd1adf87..6d737a085461ca 100644 --- a/fs/hfs/attr.c +++ b/fs/hfs/attr.c @@ -121,7 +121,7 @@ static int hfs_xattr_get(const struct xattr_handler *handler, } static int hfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c index 21a1c196c71f2e..71364e093fa704 100644 --- a/fs/hfsplus/xattr.c +++ b/fs/hfsplus/xattr.c @@ -1008,7 +1008,7 @@ static int hfsplus_osx_getxattr(const struct xattr_handler *handler, } static int hfsplus_osx_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_security.c b/fs/hfsplus/xattr_security.c index 90f68ec119cddd..1969919c12cb99 100644 --- a/fs/hfsplus/xattr_security.c +++ b/fs/hfsplus/xattr_security.c @@ -23,7 +23,7 @@ static int hfsplus_security_getxattr(const struct xattr_handler *handler, } static int hfsplus_security_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_trusted.c b/fs/hfsplus/xattr_trusted.c index fdbaebc1c49a01..c140a95ab3f09d 100644 --- a/fs/hfsplus/xattr_trusted.c +++ b/fs/hfsplus/xattr_trusted.c @@ -22,7 +22,7 @@ static int hfsplus_trusted_getxattr(const struct xattr_handler *handler, } static int hfsplus_trusted_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_user.c b/fs/hfsplus/xattr_user.c index 6464b6c3d58d69..7e5da15f99372a 100644 --- a/fs/hfsplus/xattr_user.c +++ b/fs/hfsplus/xattr_user.c @@ -22,7 +22,7 @@ static int hfsplus_user_getxattr(const struct xattr_handler *handler, } static int hfsplus_user_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/security.c b/fs/jffs2/security.c index 437f3a2c1b5441..67330aeb8ae8c0 100644 --- a/fs/jffs2/security.c +++ b/fs/jffs2/security.c @@ -57,7 +57,7 @@ static int jffs2_security_getxattr(const struct xattr_handler *handler, } static int jffs2_security_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/xattr_trusted.c b/fs/jffs2/xattr_trusted.c index b7c5da2d89bdf1..85133ad8b449e5 100644 --- a/fs/jffs2/xattr_trusted.c +++ b/fs/jffs2/xattr_trusted.c @@ -25,7 +25,7 @@ static int jffs2_trusted_getxattr(const struct xattr_handler *handler, } static int jffs2_trusted_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/xattr_user.c b/fs/jffs2/xattr_user.c index f64edce4927b2e..dcfd3caf1d8ba0 100644 --- a/fs/jffs2/xattr_user.c +++ b/fs/jffs2/xattr_user.c @@ -25,7 +25,7 @@ static int jffs2_user_getxattr(const struct xattr_handler *handler, } static int jffs2_user_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jfs/xattr.c b/fs/jfs/xattr.c index 11d7f74d207be0..dcc4a69d44fec9 100644 --- a/fs/jfs/xattr.c +++ b/fs/jfs/xattr.c @@ -956,7 +956,7 @@ static int jfs_xattr_get(const struct xattr_handler *handler, } static int jfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -975,7 +975,7 @@ static int jfs_xattr_get_os2(const struct xattr_handler *handler, } static int jfs_xattr_set_os2(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index d53d104e54d549..b348d4cd150bb4 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -344,7 +344,7 @@ static int kernfs_vfs_xattr_get(const struct xattr_handler *handler, } static int kernfs_vfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) @@ -356,7 +356,7 @@ static int kernfs_vfs_xattr_set(const struct xattr_handler *handler, } static int kernfs_vfs_user_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 04b1987115d5da..21e5df7e525127 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -7866,7 +7866,7 @@ int nfs4_lock_delegation_recall(struct file_lock *fl, struct nfs4_state *state, #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl" static int nfs4_xattr_set_nfs4_acl(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7889,7 +7889,7 @@ static bool nfs4_xattr_list_nfs4_acl(struct dentry *dentry) #define XATTR_NAME_NFSV4_DACL "system.nfs4_dacl" static int nfs4_xattr_set_nfs4_dacl(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7912,7 +7912,7 @@ static bool nfs4_xattr_list_nfs4_dacl(struct dentry *dentry) #define XATTR_NAME_NFSV4_SACL "system.nfs4_sacl" static int nfs4_xattr_set_nfs4_sacl(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7935,7 +7935,7 @@ static bool nfs4_xattr_list_nfs4_sacl(struct dentry *dentry) #ifdef CONFIG_NFS_V4_SECURITY_LABEL static int nfs4_xattr_set_nfs4_label(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7965,7 +7965,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_label_handler = { #ifdef CONFIG_NFS_V4_2 static int nfs4_xattr_set_nfs4_user(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 744ea8c446e580..7fd323912510ef 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -852,7 +852,7 @@ static int ntfs_validate_fattr(struct ntfs_inode *ni, __le32 fattr) } static int ntfs_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *unused, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index d800735477c36e..5644167de8639e 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -863,7 +863,7 @@ static bool ntfs_is_reserved_lxattr(const char *name) * ntfs_setxattr - inode_operations::setxattr */ static noinline int ntfs_setxattr(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *de, + const struct mnt_idmap *idmap, struct dentry *de, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 35bcbb0ff607b2..da8e19e21a91b0 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -7503,7 +7503,7 @@ static int ocfs2_xattr_security_get(const struct xattr_handler *handler, } static int ocfs2_xattr_security_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -7598,7 +7598,7 @@ static int ocfs2_xattr_trusted_get(const struct xattr_handler *handler, } static int ocfs2_xattr_trusted_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -7629,7 +7629,7 @@ static int ocfs2_xattr_user_get(const struct xattr_handler *handler, } static int ocfs2_xattr_user_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/orangefs/xattr.c b/fs/orangefs/xattr.c index 885fd3bd5a3da7..a49e64566e1eb9 100644 --- a/fs/orangefs/xattr.c +++ b/fs/orangefs/xattr.c @@ -527,7 +527,7 @@ ssize_t orangefs_listxattr(struct dentry *dentry, char *buffer, size_t size) } static int orangefs_xattr_set_default(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, diff --git a/fs/overlayfs/xattrs.c b/fs/overlayfs/xattrs.c index 5ae44b9c879031..acc54f6138ef5c 100644 --- a/fs/overlayfs/xattrs.c +++ b/fs/overlayfs/xattrs.c @@ -190,7 +190,7 @@ static int ovl_own_xattr_get(const struct xattr_handler *handler, } static int ovl_own_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -217,7 +217,7 @@ static int ovl_other_xattr_get(const struct xattr_handler *handler, } static int ovl_other_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/pidfs.c b/fs/pidfs.c index a6a643f15d08a9..5bc136de866047 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -1102,7 +1102,7 @@ static int pidfs_xattr_get(const struct xattr_handler *handler, } static int pidfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *unused, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) { diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c index 5091f6c0d7fef8..f6c9343016f715 100644 --- a/fs/smb/client/xattr.c +++ b/fs/smb/client/xattr.c @@ -91,7 +91,7 @@ static int cifs_creation_time_set(unsigned int xid, struct cifs_tcon *pTcon, } static int cifs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 0361de56edc26b..3c4d18a4e8d531 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -216,7 +216,7 @@ int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode) return err; } -static ssize_t ksmbd_vfs_getcasexattr(struct mnt_idmap *idmap, +static ssize_t ksmbd_vfs_getcasexattr(const struct mnt_idmap *idmap, struct dentry *dentry, char *attr_name, int attr_name_len, char **attr_value) { @@ -841,7 +841,7 @@ ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list) return size; } -static ssize_t ksmbd_vfs_xattr_len(struct mnt_idmap *idmap, +static ssize_t ksmbd_vfs_xattr_len(const struct mnt_idmap *idmap, struct dentry *dentry, char *xattr_name) { return vfs_getxattr(idmap, dentry, xattr_name, NULL, 0); @@ -856,7 +856,7 @@ static ssize_t ksmbd_vfs_xattr_len(struct mnt_idmap *idmap, * * Return: read xattr value length on success, otherwise error */ -ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap, +ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap, struct dentry *dentry, char *xattr_name, char **xattr_buf) { @@ -893,7 +893,7 @@ ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap, * * Return: 0 on success, otherwise error */ -int ksmbd_vfs_setxattr(struct mnt_idmap *idmap, +int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path, const char *attr_name, void *attr_value, size_t attr_size, int flags, bool get_write) @@ -1177,7 +1177,7 @@ int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start, return ret; } -int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_remove_xattr(const struct mnt_idmap *idmap, const struct path *path, char *attr_name, bool get_write) { @@ -1511,7 +1511,7 @@ int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap, return err; } -int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path) +int ksmbd_vfs_remove_sd_xattrs(const struct mnt_idmap *idmap, const struct path *path) { char *name, *xattr_list = NULL; ssize_t xattr_list_len; @@ -1606,7 +1606,7 @@ static struct xattr_smb_acl *ksmbd_vfs_make_xattr_posix_acl(const struct mnt_idm } int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct path *path, struct smb_ntsd *pntsd, int len, bool get_write) @@ -1673,7 +1673,7 @@ int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn, } int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct smb_ntsd **pntsd) { @@ -1742,7 +1742,7 @@ int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, return rc; } -int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_set_dos_attrib_xattr(const struct mnt_idmap *idmap, const struct path *path, struct xattr_dos_attrib *da, bool get_write) @@ -1764,7 +1764,7 @@ int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap, return err; } -int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_get_dos_attrib_xattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct xattr_dos_attrib *da) { @@ -1820,7 +1820,7 @@ void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat) } int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct ksmbd_kstat *ksmbd_kstat) { @@ -1895,7 +1895,7 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work, return 0; } -ssize_t ksmbd_vfs_casexattr_len(struct mnt_idmap *idmap, +ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap, struct dentry *dentry, char *attr_name, int attr_name_len) { diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 9c8b5b28892603..5e34fc61b48bcf 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -104,20 +104,20 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work, unsigned int *chunk_size_written, loff_t *total_size_written); ssize_t ksmbd_vfs_listxattr(struct dentry *dentry, char **list); -ssize_t ksmbd_vfs_getxattr(struct mnt_idmap *idmap, +ssize_t ksmbd_vfs_getxattr(const struct mnt_idmap *idmap, struct dentry *dentry, char *xattr_name, char **xattr_buf); -ssize_t ksmbd_vfs_casexattr_len(struct mnt_idmap *idmap, +ssize_t ksmbd_vfs_casexattr_len(const struct mnt_idmap *idmap, struct dentry *dentry, char *attr_name, int attr_name_len); -int ksmbd_vfs_setxattr(struct mnt_idmap *idmap, +int ksmbd_vfs_setxattr(const struct mnt_idmap *idmap, const struct path *path, const char *attr_name, void *attr_value, size_t attr_size, int flags, bool get_write); int ksmbd_vfs_xattr_stream_name(char *stream_name, char **xattr_stream_name, size_t *xattr_stream_name_size, int s_type); -int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_remove_xattr(const struct mnt_idmap *idmap, const struct path *path, char *attr_name, bool get_write); int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name, @@ -147,28 +147,28 @@ int ksmbd_vfs_query_allocated_ranges(struct ksmbd_file *fp, loff_t start, int ksmbd_vfs_unlink(struct file *filp); void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat); int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct ksmbd_kstat *ksmbd_kstat); void ksmbd_vfs_posix_lock_wait(struct file_lock *flock); void ksmbd_vfs_posix_lock_unblock(struct file_lock *flock); int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap, const struct path *path); -int ksmbd_vfs_remove_sd_xattrs(struct mnt_idmap *idmap, const struct path *path); +int ksmbd_vfs_remove_sd_xattrs(const struct mnt_idmap *idmap, const struct path *path); int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct path *path, struct smb_ntsd *pntsd, int len, bool get_write); int ksmbd_vfs_get_sd_xattr(struct ksmbd_conn *conn, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct smb_ntsd **pntsd); -int ksmbd_vfs_set_dos_attrib_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_set_dos_attrib_xattr(const struct mnt_idmap *idmap, const struct path *path, struct xattr_dos_attrib *da, bool get_write); -int ksmbd_vfs_get_dos_attrib_xattr(struct mnt_idmap *idmap, +int ksmbd_vfs_get_dos_attrib_xattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct xattr_dos_attrib *da); int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap, diff --git a/fs/ubifs/xattr.c b/fs/ubifs/xattr.c index b5a9ab9d8a10ad..3b0e8a270f5944 100644 --- a/fs/ubifs/xattr.c +++ b/fs/ubifs/xattr.c @@ -660,7 +660,7 @@ static int xattr_get(const struct xattr_handler *handler, } static int xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/xattr.c b/fs/xattr.c index c08f188748a9ce..acdaafb0c7c12c 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -123,7 +123,7 @@ static inline int xattr_permission_error(int mask) * because different namespaces have very different rules. */ static int -xattr_permission(struct mnt_idmap *idmap, struct inode *inode, +xattr_permission(const struct mnt_idmap *idmap, struct inode *inode, const char *name, int mask) { if (mask & MAY_WRITE) { @@ -204,7 +204,7 @@ xattr_supports_user_prefix(struct inode *inode) EXPORT_SYMBOL(xattr_supports_user_prefix); int -__vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +__vfs_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) { @@ -242,7 +242,7 @@ EXPORT_SYMBOL(__vfs_setxattr); * is executed. It also assumes that the caller will make the appropriate * permission checks. */ -int __vfs_setxattr_noperm(struct mnt_idmap *idmap, +int __vfs_setxattr_noperm(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -295,7 +295,7 @@ int __vfs_setxattr_noperm(struct mnt_idmap *idmap, * a delegation was broken on, NULL if none. */ int -__vfs_setxattr_locked(struct mnt_idmap *idmap, struct dentry *dentry, +__vfs_setxattr_locked(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags, struct delegated_inode *delegated_inode) { @@ -324,7 +324,7 @@ __vfs_setxattr_locked(struct mnt_idmap *idmap, struct dentry *dentry, EXPORT_SYMBOL_GPL(__vfs_setxattr_locked); int -vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +vfs_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct inode *inode = dentry->d_inode; @@ -358,7 +358,7 @@ vfs_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, EXPORT_SYMBOL_GPL(vfs_setxattr); static ssize_t -xattr_getsecurity(struct mnt_idmap *idmap, struct inode *inode, +xattr_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void *value, size_t size) { void *buffer = NULL; @@ -395,7 +395,7 @@ xattr_getsecurity(struct mnt_idmap *idmap, struct inode *inode, * Returns the result of alloc, if failed, or the getxattr operation. */ int -vfs_getxattr_alloc(struct mnt_idmap *idmap, struct dentry *dentry, +vfs_getxattr_alloc(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, char **xattr_value, size_t xattr_size, gfp_t flags) { @@ -448,7 +448,7 @@ __vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name, EXPORT_SYMBOL(__vfs_getxattr); ssize_t -vfs_getxattr(struct mnt_idmap *idmap, struct dentry *dentry, +vfs_getxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, void *value, size_t size) { struct inode *inode = dentry->d_inode; @@ -527,7 +527,7 @@ vfs_listxattr(struct dentry *dentry, char *list, size_t size) EXPORT_SYMBOL_GPL(vfs_listxattr); int -__vfs_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +__vfs_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct inode *inode = d_inode(dentry); @@ -557,7 +557,7 @@ EXPORT_SYMBOL(__vfs_removexattr); * a delegation was broken on, NULL if none. */ int -__vfs_removexattr_locked(struct mnt_idmap *idmap, +__vfs_removexattr_locked(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct delegated_inode *delegated_inode) { @@ -589,7 +589,7 @@ __vfs_removexattr_locked(struct mnt_idmap *idmap, EXPORT_SYMBOL_GPL(__vfs_removexattr_locked); int -vfs_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +vfs_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct inode *inode = dentry->d_inode; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index df5e7c1b233484..b652f129c85e10 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -754,7 +754,7 @@ xfs_vn_getattr( static int xfs_vn_change_ok( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { diff --git a/fs/xfs/xfs_xattr.c b/fs/xfs/xfs_xattr.c index 1efe6c8139b252..b059a9714d11ab 100644 --- a/fs/xfs/xfs_xattr.c +++ b/fs/xfs/xfs_xattr.c @@ -169,7 +169,7 @@ xfs_xattr_flags_to_op( static int xfs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *unused, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/include/linux/capability.h b/include/linux/capability.h index c39a2bbefc47a5..8ab509c4e1ef16 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -210,11 +210,11 @@ static inline bool checkpoint_restore_ns_capable_noaudit(struct user_namespace * } /* audit system wants to get cap info from files as well */ -int get_vfs_caps_from_disk(struct mnt_idmap *idmap, +int get_vfs_caps_from_disk(const struct mnt_idmap *idmap, const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps); -int cap_convert_nscap(struct mnt_idmap *idmap, struct dentry *dentry, +int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry, const void **ivalue, size_t size); #endif /* !_LINUX_CAPABILITY_H */ diff --git a/include/linux/fs.h b/include/linux/fs.h index f815594dd8cbfc..08166900931a83 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3399,7 +3399,7 @@ static inline bool generic_ci_validate_strict_name(struct inode *dir, int may_setattr(const struct mnt_idmap *idmap, struct inode *inode, unsigned int ia_valid); -int setattr_prepare(struct mnt_idmap *, struct dentry *, struct iattr *); +int setattr_prepare(const struct mnt_idmap *, struct dentry *, struct iattr *); extern int inode_newsize_ok(const struct inode *, loff_t offset); void setattr_copy(const struct mnt_idmap *, struct inode *inode, const struct iattr *attr); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 30897590258f3b..e8638f7d22455b 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -146,14 +146,14 @@ LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) LSM_HOOK(int, 0, inode_getattr, const struct path *path) LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name) -LSM_HOOK(int, 0, inode_setxattr, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_setxattr, const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) LSM_HOOK(void, LSM_RET_VOID, inode_post_setxattr, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) LSM_HOOK(int, 0, inode_getxattr, struct dentry *dentry, const char *name) LSM_HOOK(int, 0, inode_listxattr, struct dentry *dentry) -LSM_HOOK(int, 0, inode_removexattr, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_removexattr, const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) LSM_HOOK(void, LSM_RET_VOID, inode_post_removexattr, struct dentry *dentry, const char *name) @@ -170,9 +170,9 @@ LSM_HOOK(int, 0, inode_remove_acl, const struct mnt_idmap *idmap, LSM_HOOK(void, LSM_RET_VOID, inode_post_remove_acl, const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry) -LSM_HOOK(int, 0, inode_killpriv, struct mnt_idmap *idmap, +LSM_HOOK(int, 0, inode_killpriv, const struct mnt_idmap *idmap, struct dentry *dentry) -LSM_HOOK(int, -EOPNOTSUPP, inode_getsecurity, struct mnt_idmap *idmap, +LSM_HOOK(int, -EOPNOTSUPP, inode_getsecurity, const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) LSM_HOOK(int, -EOPNOTSUPP, inode_setsecurity, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/include/linux/security.h b/include/linux/security.h index 12bc12e243bf1a..d5832750418ac6 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -188,8 +188,8 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name, int cap_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name); int cap_inode_need_killpriv(struct dentry *dentry); -int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry); -int cap_inode_getsecurity(struct mnt_idmap *idmap, +int cap_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry); +int cap_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc); extern int cap_mmap_addr(unsigned long addr); @@ -427,7 +427,7 @@ int security_inode_setattr(struct mnt_idmap *idmap, void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid); int security_inode_getattr(const struct path *path); -int security_inode_setxattr(struct mnt_idmap *idmap, +int security_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags); int security_inode_set_acl(const struct mnt_idmap *idmap, @@ -446,7 +446,7 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); int security_inode_getxattr(struct dentry *dentry, const char *name); int security_inode_listxattr(struct dentry *dentry); -int security_inode_removexattr(struct mnt_idmap *idmap, +int security_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name); void security_inode_post_removexattr(struct dentry *dentry, const char *name); int security_inode_file_setattr(struct dentry *dentry, @@ -454,8 +454,8 @@ int security_inode_file_setattr(struct dentry *dentry, int security_inode_file_getattr(struct dentry *dentry, struct file_kattr *fa); int security_inode_need_killpriv(struct dentry *dentry); -int security_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry); -int security_inode_getsecurity(struct mnt_idmap *idmap, +int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry); +int security_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc); int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags); @@ -996,7 +996,7 @@ static inline int security_inode_getattr(const struct path *path) return 0; } -static inline int security_inode_setxattr(struct mnt_idmap *idmap, +static inline int security_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -1050,7 +1050,7 @@ static inline int security_inode_listxattr(struct dentry *dentry) return 0; } -static inline int security_inode_removexattr(struct mnt_idmap *idmap, +static inline int security_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { @@ -1078,13 +1078,13 @@ static inline int security_inode_need_killpriv(struct dentry *dentry) return cap_inode_need_killpriv(dentry); } -static inline int security_inode_killpriv(struct mnt_idmap *idmap, +static inline int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry) { return cap_inode_killpriv(idmap, dentry); } -static inline int security_inode_getsecurity(struct mnt_idmap *idmap, +static inline int security_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 54ac3cbc133f86..4cc4257de084e5 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -47,7 +47,7 @@ struct xattr_handler { struct inode *inode, const char *name, void *buffer, size_t size); int (*set)(const struct xattr_handler *, - struct mnt_idmap *idmap, struct dentry *dentry, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags); }; @@ -77,25 +77,25 @@ struct xattr { }; ssize_t __vfs_getxattr(struct dentry *, struct inode *, const char *, void *, size_t); -ssize_t vfs_getxattr(struct mnt_idmap *, struct dentry *, const char *, +ssize_t vfs_getxattr(const struct mnt_idmap *, struct dentry *, const char *, void *, size_t); ssize_t vfs_listxattr(struct dentry *d, char *list, size_t size); -int __vfs_setxattr(struct mnt_idmap *, struct dentry *, struct inode *, +int __vfs_setxattr(const struct mnt_idmap *, struct dentry *, struct inode *, const char *, const void *, size_t, int); -int __vfs_setxattr_noperm(struct mnt_idmap *, struct dentry *, +int __vfs_setxattr_noperm(const struct mnt_idmap *, struct dentry *, const char *, const void *, size_t, int); -int __vfs_setxattr_locked(struct mnt_idmap *, struct dentry *, +int __vfs_setxattr_locked(const struct mnt_idmap *, struct dentry *, const char *, const void *, size_t, int, struct delegated_inode *); -int vfs_setxattr(struct mnt_idmap *, struct dentry *, const char *, +int vfs_setxattr(const struct mnt_idmap *, struct dentry *, const char *, const void *, size_t, int); -int __vfs_removexattr(struct mnt_idmap *, struct dentry *, const char *); -int __vfs_removexattr_locked(struct mnt_idmap *, struct dentry *, +int __vfs_removexattr(const struct mnt_idmap *, struct dentry *, const char *); +int __vfs_removexattr_locked(const struct mnt_idmap *, struct dentry *, const char *, struct delegated_inode *); -int vfs_removexattr(struct mnt_idmap *, struct dentry *, const char *); +int vfs_removexattr(const struct mnt_idmap *, struct dentry *, const char *); ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size); -int vfs_getxattr_alloc(struct mnt_idmap *idmap, +int vfs_getxattr_alloc(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, char **xattr_value, size_t size, gfp_t flags); diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index 7837968c0842cd..11174037a9ca76 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -874,7 +874,7 @@ enum { }; static int bpf_fs_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, struct dentry *unused, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/mm/shmem.c b/mm/shmem.c index 9ab5231c4dfe58..ed633b3bc38d9a 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4264,7 +4264,7 @@ static int shmem_xattr_handler_get(const struct xattr_handler *handler, } static int shmem_xattr_handler_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/net/socket.c b/net/socket.c index c05d86e63abf7d..052273ad081acc 100644 --- a/net/socket.c +++ b/net/socket.c @@ -422,7 +422,7 @@ static const struct xattr_handler sockfs_xattr_handler = { }; static int sockfs_security_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) @@ -447,7 +447,7 @@ static int sockfs_user_xattr_get(const struct xattr_handler *handler, } static int sockfs_user_xattr_set(const struct xattr_handler *handler, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) diff --git a/security/commoncap.c b/security/commoncap.c index 9361393fd756fb..d47ab30223435b 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -348,7 +348,7 @@ int cap_inode_need_killpriv(struct dentry *dentry) * * Return: 0 if successful, -ve on error. */ -int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry) +int cap_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry) { int error; @@ -417,7 +417,7 @@ static bool is_v3header(int size, const struct vfs_cap_data *cap) * by the integrity subsystem, which really wants the unconverted values - * so that's good. */ -int cap_inode_getsecurity(struct mnt_idmap *idmap, +int cap_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { @@ -566,7 +566,7 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap) * * Return: On success, return the new size; on error, return < 0. */ -int cap_convert_nscap(struct mnt_idmap *idmap, struct dentry *dentry, +int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry, const void **ivalue, size_t size) { struct vfs_ns_cap_data *nscap; @@ -672,7 +672,7 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, * permissions. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -int get_vfs_caps_from_disk(struct mnt_idmap *idmap, +int get_vfs_caps_from_disk(const struct mnt_idmap *idmap, const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps) { diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index aaf228ebed7ce4..94928549bfab13 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -607,7 +607,7 @@ static int evm_protect_xattr(const struct mnt_idmap *idmap, * userspace from writing HMAC value. Writing 'security.evm' requires * requires CAP_SYS_ADMIN privileges. */ -static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len, int flags) { @@ -639,7 +639,7 @@ static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that * the current value is valid. */ -static int evm_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name) { /* Policy permits modification of the protected xattrs even though diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 1aed041c127a75..cfc20c63a81450 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -757,7 +757,7 @@ static int validate_hash_algo(struct dentry *dentry, return -EACCES; } -static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len, int flags) { @@ -799,7 +799,7 @@ static int ima_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentr return 0; } -static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name) { int result, digsig = -1; diff --git a/security/security.c b/security/security.c index e81dcda3050b9d..e3a14c958bc40d 100644 --- a/security/security.c +++ b/security/security.c @@ -1921,7 +1921,7 @@ int security_inode_getattr(const struct path *path) * * Return: Returns 0 if permission is granted. */ -int security_inode_setxattr(struct mnt_idmap *idmap, +int security_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -2108,7 +2108,7 @@ int security_inode_listxattr(struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_removexattr(struct mnt_idmap *idmap, +int security_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { int rc; @@ -2197,7 +2197,7 @@ int security_inode_need_killpriv(struct dentry *dentry) * Return: Return 0 on success. If error is returned, then the operation * causing setuid bit removal is failed. */ -int security_inode_killpriv(struct mnt_idmap *idmap, +int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry) { return call_int_hook(inode_killpriv, idmap, dentry); @@ -2219,7 +2219,7 @@ int security_inode_killpriv(struct mnt_idmap *idmap, * * Return: Returns size of buffer on success. */ -int security_inode_getsecurity(struct mnt_idmap *idmap, +int security_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 672d6374d8b059..b7c6eba08fdc7d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3367,7 +3367,7 @@ static int selinux_inode_xattr_skipcap(const char *name) return !strcmp(name, XATTR_NAME_SELINUX); } -static int selinux_inode_setxattr(struct mnt_idmap *idmap, +static int selinux_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -3526,7 +3526,7 @@ static int selinux_inode_listxattr(struct dentry *dentry) return dentry_has_perm(cred, dentry, FILE__GETATTR); } -static int selinux_inode_removexattr(struct mnt_idmap *idmap, +static int selinux_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { /* if not a selinux xattr, only check the ordinary setattr perm */ @@ -3606,7 +3606,7 @@ static int selinux_path_notify(const struct path *path, u64 mask, * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(struct mnt_idmap *idmap, +static int selinux_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 244cdb49e928ff..df4fc6ff26aa8f 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1348,7 +1348,7 @@ static int smack_inode_xattr_skipcap(const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setxattr(struct mnt_idmap *idmap, +static int smack_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -1482,7 +1482,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_removexattr(struct mnt_idmap *idmap, +static int smack_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct inode_smack *isp; @@ -1612,7 +1612,7 @@ static int smack_inode_remove_acl(const struct mnt_idmap *idmap, * * Returns the size of the attribute or an error code */ -static int smack_inode_getsecurity(struct mnt_idmap *idmap, +static int smack_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { From 51e7cfbd10512c8b7f13cdd77cc41c12c770e83e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:39 +0200 Subject: [PATCH 0765/2286] fs: port ->fileattr_set() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-14-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/btrfs/ioctl.c | 2 +- fs/btrfs/ioctl.h | 2 +- fs/ecryptfs/inode.c | 2 +- fs/efivarfs/inode.c | 2 +- fs/ext2/ext2.h | 2 +- fs/ext2/ioctl.c | 2 +- fs/ext4/ext4.h | 2 +- fs/ext4/ioctl.c | 2 +- fs/f2fs/f2fs.h | 2 +- fs/f2fs/file.c | 2 +- fs/file_attr.c | 2 +- fs/fuse/fuse_i.h | 2 +- fs/fuse/ioctl.c | 2 +- fs/gfs2/file.c | 2 +- fs/gfs2/inode.h | 2 +- fs/hfsplus/hfsplus_fs.h | 2 +- fs/hfsplus/inode.c | 2 +- fs/jfs/ioctl.c | 2 +- fs/jfs/jfs_inode.h | 2 +- fs/nilfs2/ioctl.c | 2 +- fs/nilfs2/nilfs.h | 2 +- fs/ntfs3/file.c | 2 +- fs/ntfs3/ntfs_fs.h | 2 +- fs/ocfs2/ioctl.c | 2 +- fs/ocfs2/ioctl.h | 2 +- fs/orangefs/inode.c | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/ubifs/ioctl.c | 2 +- fs/ubifs/ubifs.h | 2 +- fs/xfs/xfs_ioctl.c | 2 +- fs/xfs/xfs_ioctl.h | 2 +- include/linux/fileattr.h | 2 +- include/linux/fs.h | 2 +- mm/shmem.c | 2 +- 37 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 2a91bc9c15f599..b95cd499289680 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -88,7 +88,7 @@ prototypes:: umode_t create_mode); int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); - int (*fileattr_set)(struct mnt_idmap *idmap, + int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa); struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index b7e476f701854f..c43bad400c3079 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -441,7 +441,7 @@ As of kernel 2.6.22, the following members are defined: int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int); int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int); - int (*fileattr_set)(struct mnt_idmap *idmap, + int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa); struct offset_ctx *(*get_offset_ctx)(struct inode *inode); diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 9d6302547b1293..94c98801800fc0 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -278,7 +278,7 @@ int btrfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int btrfs_fileattr_set(struct mnt_idmap *idmap, +int btrfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct btrfs_inode *inode = BTRFS_I(d_inode(dentry)); diff --git a/fs/btrfs/ioctl.h b/fs/btrfs/ioctl.h index ccf6bed9cc24ee..55f86aeb3500ea 100644 --- a/fs/btrfs/ioctl.h +++ b/fs/btrfs/ioctl.h @@ -17,7 +17,7 @@ struct btrfs_ioctl_balance_args; long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg); long btrfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg); int btrfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int btrfs_fileattr_set(struct mnt_idmap *idmap, +int btrfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int btrfs_ioctl_get_supported_features(void __user *arg); void btrfs_sync_inode_flags_to_i_flags(struct btrfs_inode *inode); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 691b3a24b60aa3..b9c32e8f855a12 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1078,7 +1078,7 @@ static int ecryptfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return vfs_fileattr_get(ecryptfs_dentry_to_lower(dentry), fa); } -static int ecryptfs_fileattr_set(struct mnt_idmap *idmap, +static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry); diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index f0d009555fc660..f7dcd72e4a0771 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -150,7 +150,7 @@ efivarfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) } static int -efivarfs_fileattr_set(struct mnt_idmap *idmap, +efivarfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { unsigned int i_flags = 0; diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 5642451bf19173..702cb7aa1afd01 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -748,7 +748,7 @@ extern int ext2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, /* ioctl.c */ extern int ext2_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -extern int ext2_fileattr_set(struct mnt_idmap *idmap, +extern int ext2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); extern long ext2_ioctl(struct file *, unsigned int, unsigned long); extern long ext2_compat_ioctl(struct file *, unsigned int, unsigned long); diff --git a/fs/ext2/ioctl.c b/fs/ext2/ioctl.c index c3fea55b8efa4c..f2218455fa4771 100644 --- a/fs/ext2/ioctl.c +++ b/fs/ext2/ioctl.c @@ -27,7 +27,7 @@ int ext2_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int ext2_fileattr_set(struct mnt_idmap *idmap, +int ext2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index aef86b1d10facb..2c9a845ed0f83b 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3246,7 +3246,7 @@ extern int ext4_ind_remove_space(handle_t *handle, struct inode *inode, /* ioctl.c */ extern long ext4_ioctl(struct file *, unsigned int, unsigned long); extern long ext4_compat_ioctl(struct file *, unsigned int, unsigned long); -int ext4_fileattr_set(struct mnt_idmap *idmap, +int ext4_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int ext4_fileattr_get(struct dentry *dentry, struct file_kattr *fa); extern void ext4_reset_inode_seed(struct inode *inode); diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 6de77da4203cd8..2719f20493c87e 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -1008,7 +1008,7 @@ int ext4_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int ext4_fileattr_set(struct mnt_idmap *idmap, +int ext4_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index eefc886e3ffc77..245df8563256f0 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3858,7 +3858,7 @@ int f2fs_do_shutdown(struct f2fs_sb_info *sbi, unsigned int flag, bool readonly, bool need_lock); int f2fs_precache_extents(struct inode *inode); int f2fs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int f2fs_fileattr_set(struct mnt_idmap *idmap, +int f2fs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 03bb3a9a449f60..d0cc2ca1397a9a 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -3580,7 +3580,7 @@ int f2fs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int f2fs_fileattr_set(struct mnt_idmap *idmap, +int f2fs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/file_attr.c b/fs/file_attr.c index bfb00d256dd560..5393c4dfa4a86e 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -265,7 +265,7 @@ static int fileattr_set_prepare(struct inode *inode, * * Return: 0 on success, or a negative error on failure. */ -int vfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 9ea346ea2e0160..6ca56757869c7e 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1238,7 +1238,7 @@ long fuse_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg); long fuse_file_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg); int fuse_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int fuse_fileattr_set(struct mnt_idmap *idmap, +int fuse_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); /* iomode.c */ diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 3614ea60391398..3ee45b2e148ff0 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -540,7 +540,7 @@ int fuse_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return err; } -int fuse_fileattr_set(struct mnt_idmap *idmap, +int fuse_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index b8c10de113ba7a..9487625a416636 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -275,7 +275,7 @@ static int do_gfs2_set_flags(struct inode *inode, u32 reqflags, u32 mask) return error; } -int gfs2_fileattr_set(struct mnt_idmap *idmap, +int gfs2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h index 373473eb091c63..99196d3114e429 100644 --- a/fs/gfs2/inode.h +++ b/fs/gfs2/inode.h @@ -109,7 +109,7 @@ extern const struct file_operations gfs2_file_fops_nolock; extern const struct file_operations gfs2_dir_fops_nolock; int gfs2_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int gfs2_fileattr_set(struct mnt_idmap *idmap, +int gfs2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); void gfs2_set_inode_flags(struct inode *inode); diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 1e5b58e6a13fc3..86fc1c1cdfd6b1 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -465,7 +465,7 @@ int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path, int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, int datasync); int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int hfsplus_fileattr_set(struct mnt_idmap *idmap, +int hfsplus_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); /* ioctl.c */ diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 2ce6de574fa6ef..9821662bfc1d4c 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -797,7 +797,7 @@ int hfsplus_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int hfsplus_fileattr_set(struct mnt_idmap *idmap, +int hfsplus_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/jfs/ioctl.c b/fs/jfs/ioctl.c index 563f148be8affb..27d39cddaca6e7 100644 --- a/fs/jfs/ioctl.c +++ b/fs/jfs/ioctl.c @@ -70,7 +70,7 @@ int jfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int jfs_fileattr_set(struct mnt_idmap *idmap, +int jfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/jfs/jfs_inode.h b/fs/jfs/jfs_inode.h index 2c6c81c8cb9fb7..88cfc38862822e 100644 --- a/fs/jfs/jfs_inode.h +++ b/fs/jfs/jfs_inode.h @@ -10,7 +10,7 @@ struct fid; extern struct inode *ialloc(struct inode *, umode_t); extern int jfs_fsync(struct file *, loff_t, loff_t, int); extern int jfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -extern int jfs_fileattr_set(struct mnt_idmap *idmap, +extern int jfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); extern long jfs_ioctl(struct file *, unsigned int, unsigned long); extern struct inode *jfs_iget(struct super_block *, unsigned long); diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index 01a04080ef7045..f56a79767c69f9 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -135,7 +135,7 @@ int nilfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) * * Return: 0 on success, or a negative error code on failure. */ -int nilfs_fileattr_set(struct mnt_idmap *idmap, +int nilfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h index 0eaa191f70b8bd..829df7be11fe62 100644 --- a/fs/nilfs2/nilfs.h +++ b/fs/nilfs2/nilfs.h @@ -270,7 +270,7 @@ extern int nilfs_sync_file(struct file *, loff_t, loff_t, int); /* ioctl.c */ int nilfs_fileattr_get(struct dentry *dentry, struct file_kattr *m); -int nilfs_fileattr_set(struct mnt_idmap *idmap, +int nilfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); long nilfs_ioctl(struct file *, unsigned int, unsigned long); long nilfs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg); diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index 2abf334bfa0cf9..f7baa202dc28f7 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -129,7 +129,7 @@ int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) /* * ntfs_fileattr_set - inode_operations::fileattr_set */ -int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index a2b74f41048f58..e494bed87679c9 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -551,7 +551,7 @@ extern const struct file_operations ntfs_dir_operations; /* Globals from file.c */ int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, u32 flags); diff --git a/fs/ocfs2/ioctl.c b/fs/ocfs2/ioctl.c index cbe59d23166636..36c7c9ac8b5dd6 100644 --- a/fs/ocfs2/ioctl.c +++ b/fs/ocfs2/ioctl.c @@ -82,7 +82,7 @@ int ocfs2_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return status; } -int ocfs2_fileattr_set(struct mnt_idmap *idmap, +int ocfs2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/ocfs2/ioctl.h b/fs/ocfs2/ioctl.h index 4a1c2313b42903..b1cb529fc5f9ac 100644 --- a/fs/ocfs2/ioctl.h +++ b/fs/ocfs2/ioctl.h @@ -12,7 +12,7 @@ #define OCFS2_IOCTL_PROTO_H int ocfs2_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int ocfs2_fileattr_set(struct mnt_idmap *idmap, +int ocfs2_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); long ocfs2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); long ocfs2_compat_ioctl(struct file *file, unsigned cmd, unsigned long arg); diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 8e6eeec501bc65..536d5e842c55ec 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -934,7 +934,7 @@ static int orangefs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -static int orangefs_fileattr_set(struct mnt_idmap *idmap, +static int orangefs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { u64 val = 0; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 1166381f185288..5f4e8b6d972227 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -650,7 +650,7 @@ int ovl_real_fileattr_set(const struct path *realpath, struct file_kattr *fa) return vfs_fileattr_set(mnt_idmap(realpath->mnt), realpath->dentry, fa); } -int ovl_fileattr_set(struct mnt_idmap *idmap, +int ovl_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 513ba93c7d1b36..46a5cf5dae4c61 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -914,7 +914,7 @@ extern const struct file_operations ovl_file_operations; int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa); int ovl_real_fileattr_set(const struct path *realpath, struct file_kattr *fa); int ovl_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int ovl_fileattr_set(struct mnt_idmap *idmap, +int ovl_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); struct ovl_file; struct ovl_file *ovl_file_alloc(struct file *realfile); diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c index 79536b2e3d7a1a..5c34f895bd4e12 100644 --- a/fs/ubifs/ioctl.c +++ b/fs/ubifs/ioctl.c @@ -144,7 +144,7 @@ int ubifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int ubifs_fileattr_set(struct mnt_idmap *idmap, +int ubifs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index 00db0d19a85e38..af0ef3583aeb2a 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -2083,7 +2083,7 @@ void ubifs_destroy_size_tree(struct ubifs_info *c); /* ioctl.c */ int ubifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int ubifs_fileattr_set(struct mnt_idmap *idmap, +int ubifs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg); void ubifs_set_inode_flags(struct inode *inode); diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1b53701bebea1a..0df67f0d573c8b 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -748,7 +748,7 @@ xfs_ioctl_setattr_check_projid( int xfs_fileattr_set( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { diff --git a/fs/xfs/xfs_ioctl.h b/fs/xfs/xfs_ioctl.h index f5ed5cf9d3df65..70c26cb05eb653 100644 --- a/fs/xfs/xfs_ioctl.h +++ b/fs/xfs/xfs_ioctl.h @@ -21,7 +21,7 @@ xfs_fileattr_get( extern int xfs_fileattr_set( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); diff --git a/include/linux/fileattr.h b/include/linux/fileattr.h index 58044b59801620..09e32b84e02acf 100644 --- a/include/linux/fileattr.h +++ b/include/linux/fileattr.h @@ -74,7 +74,7 @@ static inline bool fileattr_has_fsx(const struct file_kattr *fa) } int vfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); -int vfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int ioctl_getflags(struct file *file, unsigned int __user *argp); int ioctl_setflags(struct file *file, unsigned int __user *argp); diff --git a/include/linux/fs.h b/include/linux/fs.h index 08166900931a83..764ef2d97a5df9 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2030,7 +2030,7 @@ struct inode_operations { int); int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int); - int (*fileattr_set)(struct mnt_idmap *idmap, + int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa); struct offset_ctx *(*get_offset_ctx)(struct inode *inode); diff --git a/mm/shmem.c b/mm/shmem.c index ed633b3bc38d9a..097699ea276206 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4159,7 +4159,7 @@ static int shmem_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -static int shmem_fileattr_set(struct mnt_idmap *idmap, +static int shmem_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa) { struct inode *inode = d_inode(dentry); From 737362c7012aff5fcc238de855c1f89ba8a79abe Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:40 +0200 Subject: [PATCH 0766/2286] fs: port ->set_acl() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-15-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/vfs.rst | 2 +- fs/9p/acl.c | 2 +- fs/9p/acl.h | 2 +- fs/bad_inode.c | 2 +- fs/btrfs/acl.c | 2 +- fs/btrfs/acl.h | 2 +- fs/ceph/acl.c | 2 +- fs/ceph/super.h | 2 +- fs/ecryptfs/inode.c | 2 +- fs/ext2/acl.c | 2 +- fs/ext2/acl.h | 2 +- fs/ext4/acl.c | 2 +- fs/ext4/acl.h | 2 +- fs/f2fs/acl.c | 2 +- fs/f2fs/acl.h | 2 +- fs/fuse/acl.c | 2 +- fs/fuse/fuse_i.h | 2 +- fs/gfs2/acl.c | 2 +- fs/gfs2/acl.h | 2 +- fs/internal.h | 4 ++-- fs/jffs2/acl.c | 2 +- fs/jffs2/acl.h | 2 +- fs/jfs/acl.c | 2 +- fs/jfs/jfs_acl.h | 2 +- fs/nfs/nfs3_fs.h | 2 +- fs/nfs/nfs3acl.c | 2 +- fs/ntfs/ea.c | 2 +- fs/ntfs/ea.h | 2 +- fs/ntfs3/ntfs_fs.h | 4 ++-- fs/ntfs3/xattr.c | 4 ++-- fs/ocfs2/acl.c | 2 +- fs/ocfs2/acl.h | 2 +- fs/orangefs/acl.c | 2 +- fs/orangefs/orangefs-kernel.h | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/posix_acl.c | 12 ++++++------ fs/smb/client/cifsacl.c | 2 +- fs/smb/client/cifsproto.h | 2 +- fs/smb/server/vfs.c | 4 ++-- fs/smb/server/vfs.h | 4 ++-- fs/xattr.c | 4 ++-- fs/xfs/xfs_acl.c | 2 +- fs/xfs/xfs_acl.h | 2 +- fs/xfs/xfs_iops.c | 4 ++-- fs/xfs/xfs_iops.h | 2 +- include/linux/fs.h | 2 +- include/linux/posix_acl.h | 16 ++++++++-------- 48 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index c43bad400c3079..d0dd2be3885bd8 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -440,7 +440,7 @@ As of kernel 2.6.22, the following members are defined: unsigned open_flag, umode_t create_mode); int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int); - int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int); + int (*set_acl)(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa); diff --git a/fs/9p/acl.c b/fs/9p/acl.c index ae7e7cf7523a16..d09a41336d0d6a 100644 --- a/fs/9p/acl.c +++ b/fs/9p/acl.c @@ -152,7 +152,7 @@ struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, return v9fs_get_cached_acl(d_inode(dentry), type); } -int v9fs_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int v9fs_iop_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int retval; diff --git a/fs/9p/acl.h b/fs/9p/acl.h index 333cfcc281da44..7de74704c46582 100644 --- a/fs/9p/acl.h +++ b/fs/9p/acl.h @@ -12,7 +12,7 @@ struct posix_acl *v9fs_iop_get_inode_acl(struct inode *inode, int type, bool rcu); struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); -int v9fs_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int v9fs_iop_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid); int v9fs_set_create_acl(struct inode *inode, struct p9_fid *fid, diff --git a/fs/bad_inode.c b/fs/bad_inode.c index c2cfa9395bc919..6e116810f66d04 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -153,7 +153,7 @@ static int bad_inode_tmpfile(struct mnt_idmap *idmap, return -EIO; } -static int bad_inode_set_acl(struct mnt_idmap *idmap, +static int bad_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c index 662cdd1cbdef6e..10a0d733bfd174 100644 --- a/fs/btrfs/acl.c +++ b/fs/btrfs/acl.c @@ -101,7 +101,7 @@ int __btrfs_set_acl(struct btrfs_trans_handle *trans, struct inode *inode, return 0; } -int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int btrfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int ret; diff --git a/fs/btrfs/acl.h b/fs/btrfs/acl.h index 0458cd51ed4892..6eae2db3654d1a 100644 --- a/fs/btrfs/acl.h +++ b/fs/btrfs/acl.h @@ -15,7 +15,7 @@ struct mnt_idmap; struct dentry; struct posix_acl *btrfs_get_acl(struct inode *inode, int type, bool rcu); -int btrfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int btrfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int __btrfs_set_acl(struct btrfs_trans_handle *trans, struct inode *inode, struct posix_acl *acl, int type); diff --git a/fs/ceph/acl.c b/fs/ceph/acl.c index 85d3dd48b167a3..124f07ae5b2db6 100644 --- a/fs/ceph/acl.c +++ b/fs/ceph/acl.c @@ -87,7 +87,7 @@ struct posix_acl *ceph_get_acl(struct inode *inode, int type, bool rcu) return acl; } -int ceph_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ceph_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int ret = 0; diff --git a/fs/ceph/super.h b/fs/ceph/super.h index ef9861797eb267..fd603be209e43c 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1252,7 +1252,7 @@ void ceph_release_acl_sec_ctx(struct ceph_acl_sec_ctx *as_ctx); #ifdef CONFIG_CEPH_FS_POSIX_ACL struct posix_acl *ceph_get_acl(struct inode *, int, bool); -int ceph_set_acl(struct mnt_idmap *idmap, +int ceph_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int ceph_pre_init_acls(struct inode *dir, umode_t *mode, struct ceph_acl_sec_ctx *as_ctx); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index b9c32e8f855a12..1e514648521401 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1097,7 +1097,7 @@ static struct posix_acl *ecryptfs_get_acl(struct mnt_idmap *idmap, posix_acl_xattr_name(type)); } -static int ecryptfs_set_acl(struct mnt_idmap *idmap, +static int ecryptfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { diff --git a/fs/ext2/acl.c b/fs/ext2/acl.c index 7e54c31589c7ca..b2746657fc533a 100644 --- a/fs/ext2/acl.c +++ b/fs/ext2/acl.c @@ -219,7 +219,7 @@ __ext2_set_acl(struct inode *inode, struct posix_acl *acl, int type) * inode->i_mutex: down */ int -ext2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +ext2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int error; diff --git a/fs/ext2/acl.h b/fs/ext2/acl.h index 4a8443a2b8ec79..e68bc354560811 100644 --- a/fs/ext2/acl.h +++ b/fs/ext2/acl.h @@ -56,7 +56,7 @@ static inline int ext2_acl_count(size_t size) /* acl.c */ extern struct posix_acl *ext2_get_acl(struct inode *inode, int type, bool rcu); -extern int ext2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +extern int ext2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int ext2_init_acl (struct inode *, struct inode *); diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c index 3bffe862f95499..59fac55a242610 100644 --- a/fs/ext4/acl.c +++ b/fs/ext4/acl.c @@ -225,7 +225,7 @@ __ext4_set_acl(handle_t *handle, struct inode *inode, int type, } int -ext4_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +ext4_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { handle_t *handle; diff --git a/fs/ext4/acl.h b/fs/ext4/acl.h index 0c5a79c3b5d480..a14838c5bc42d8 100644 --- a/fs/ext4/acl.h +++ b/fs/ext4/acl.h @@ -56,7 +56,7 @@ static inline int ext4_acl_count(size_t size) /* acl.c */ struct posix_acl *ext4_get_acl(struct inode *inode, int type, bool rcu); -int ext4_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ext4_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int ext4_init_acl(handle_t *, struct inode *, struct inode *); diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c index e7e0084d0a5f78..22ca39e0aad64a 100644 --- a/fs/f2fs/acl.c +++ b/fs/f2fs/acl.c @@ -289,7 +289,7 @@ static int __f2fs_set_acl(const struct mnt_idmap *idmap, return error; } -int f2fs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int f2fs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/f2fs/acl.h b/fs/f2fs/acl.h index 20e87e63c08958..01ec74b6daf25c 100644 --- a/fs/f2fs/acl.h +++ b/fs/f2fs/acl.h @@ -34,7 +34,7 @@ struct f2fs_acl_header { #ifdef CONFIG_F2FS_FS_POSIX_ACL struct posix_acl *f2fs_get_acl(struct inode *, int, bool); -int f2fs_set_acl(struct mnt_idmap *, struct dentry *, +int f2fs_set_acl(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); int f2fs_init_acl(struct inode *, struct inode *, struct folio *ifolio, struct folio *dfolio); diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c index 31fb50e16aed7b..c3813fff4f2719 100644 --- a/fs/fuse/acl.c +++ b/fs/fuse/acl.c @@ -90,7 +90,7 @@ struct posix_acl *fuse_get_inode_acl(struct inode *inode, int type, bool rcu) return __fuse_get_acl(fc, inode, type, rcu); } -int fuse_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int fuse_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 6ca56757869c7e..561ada21ea86d5 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1207,7 +1207,7 @@ struct posix_acl; struct posix_acl *fuse_get_inode_acl(struct inode *inode, int type, bool rcu); struct posix_acl *fuse_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); -int fuse_set_acl(struct mnt_idmap *, struct dentry *dentry, +int fuse_set_acl(const struct mnt_idmap *, struct dentry *dentry, struct posix_acl *acl, int type); /* readdir.c */ diff --git a/fs/gfs2/acl.c b/fs/gfs2/acl.c index a5b60778b91c90..7b94efbbe8e390 100644 --- a/fs/gfs2/acl.c +++ b/fs/gfs2/acl.c @@ -102,7 +102,7 @@ int __gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type) return error; } -int gfs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int gfs2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/gfs2/acl.h b/fs/gfs2/acl.h index 82f5b09c04e669..d19d417559367f 100644 --- a/fs/gfs2/acl.h +++ b/fs/gfs2/acl.h @@ -13,7 +13,7 @@ struct posix_acl *gfs2_get_acl(struct inode *inode, int type, bool rcu); int __gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type); -int gfs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int gfs2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); #endif /* __ACL_DOT_H__ */ diff --git a/fs/internal.h b/fs/internal.h index a502b1fbacd245..8ba0a1613d206f 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -302,12 +302,12 @@ int import_xattr_name(struct xattr_name *kname, const char __user *name); int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode); #ifdef CONFIG_FS_POSIX_ACL -int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, const void *kvalue, size_t size); ssize_t do_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, void *kvalue, size_t size); #else -static inline int do_set_acl(struct mnt_idmap *idmap, +static inline int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, const void *kvalue, size_t size) { diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c index f0f8a4f57add0a..7548f44bf32754 100644 --- a/fs/jffs2/acl.c +++ b/fs/jffs2/acl.c @@ -228,7 +228,7 @@ static int __jffs2_set_acl(struct inode *inode, int xprefix, struct posix_acl *a return rc; } -int jffs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int jffs2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int rc, xprefix; diff --git a/fs/jffs2/acl.h b/fs/jffs2/acl.h index e976b8cb82cf91..bc5df521633ff8 100644 --- a/fs/jffs2/acl.h +++ b/fs/jffs2/acl.h @@ -28,7 +28,7 @@ struct jffs2_acl_header { #ifdef CONFIG_JFFS2_FS_POSIX_ACL struct posix_acl *jffs2_get_acl(struct inode *inode, int type, bool rcu); -int jffs2_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int jffs2_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int jffs2_init_acl_pre(struct inode *, struct inode *, umode_t *); extern int jffs2_init_acl_post(struct inode *); diff --git a/fs/jfs/acl.c b/fs/jfs/acl.c index 16b71a23ff1e33..6e0a7feb6c8089 100644 --- a/fs/jfs/acl.c +++ b/fs/jfs/acl.c @@ -89,7 +89,7 @@ static int __jfs_set_acl(tid_t tid, struct inode *inode, int type, return rc; } -int jfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int jfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int rc; diff --git a/fs/jfs/jfs_acl.h b/fs/jfs/jfs_acl.h index f892e54d0fcd2d..bda26b33351962 100644 --- a/fs/jfs/jfs_acl.h +++ b/fs/jfs/jfs_acl.h @@ -8,7 +8,7 @@ #ifdef CONFIG_JFS_POSIX_ACL struct posix_acl *jfs_get_acl(struct inode *inode, int type, bool rcu); -int jfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int jfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int jfs_init_acl(tid_t, struct inode *, struct inode *); diff --git a/fs/nfs/nfs3_fs.h b/fs/nfs/nfs3_fs.h index b333ea119ef549..ffcabadb3546f9 100644 --- a/fs/nfs/nfs3_fs.h +++ b/fs/nfs/nfs3_fs.h @@ -12,7 +12,7 @@ */ #ifdef CONFIG_NFS_V3_ACL extern struct posix_acl *nfs3_get_acl(struct inode *inode, int type, bool rcu); -extern int nfs3_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +extern int nfs3_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl, struct posix_acl *dfacl); diff --git a/fs/nfs/nfs3acl.c b/fs/nfs/nfs3acl.c index a126eb31f62fae..2549a1985b9afc 100644 --- a/fs/nfs/nfs3acl.c +++ b/fs/nfs/nfs3acl.c @@ -254,7 +254,7 @@ int nfs3_proc_setacls(struct inode *inode, struct posix_acl *acl, } -int nfs3_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int nfs3_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct posix_acl *orig = acl, *dfacl = NULL, *alloc; diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 7fd323912510ef..063cc15f162659 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -1080,7 +1080,7 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, return err; } -int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false); diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 9d17abc2e49169..6d98e7487a2c3d 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -19,7 +19,7 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); #ifdef CONFIG_NTFS_FS_POSIX_ACL struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); -int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index e494bed87679c9..0a5e81d420f062 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -965,7 +965,7 @@ unsigned long ntfs_names_hash(const u16 *name, size_t len, const u16 *upcase, #ifdef CONFIG_NTFS3_FS_POSIX_ACL struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); -int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, struct inode *dir); @@ -974,7 +974,7 @@ int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, #define ntfs_set_acl NULL #endif -int ntfs_acl_chmod(struct mnt_idmap *idmap, struct dentry *dentry); +int ntfs_acl_chmod(const struct mnt_idmap *idmap, struct dentry *dentry); ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); extern const struct xattr_handler *const ntfs_xattr_handlers[]; diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 5644167de8639e..2d17b80999ca36 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -677,7 +677,7 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap, /* * ntfs_set_acl - inode_operations::set_acl */ -int ntfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { return ntfs_set_acl_ex(idmap, d_inode(dentry), acl, type, false); @@ -722,7 +722,7 @@ int ntfs_init_acl(const struct mnt_idmap *idmap, struct inode *inode, /* * ntfs_acl_chmod - Helper for ntfs_setattr(). */ -int ntfs_acl_chmod(struct mnt_idmap *idmap, struct dentry *dentry) +int ntfs_acl_chmod(const struct mnt_idmap *idmap, struct dentry *dentry) { struct inode *inode = d_inode(dentry); struct super_block *sb = inode->i_sb; diff --git a/fs/ocfs2/acl.c b/fs/ocfs2/acl.c index 090ec60fb576a6..801a2f56ad08c4 100644 --- a/fs/ocfs2/acl.c +++ b/fs/ocfs2/acl.c @@ -260,7 +260,7 @@ static int ocfs2_set_acl(handle_t *handle, return ret; } -int ocfs2_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ocfs2_iop_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { struct buffer_head *bh = NULL; diff --git a/fs/ocfs2/acl.h b/fs/ocfs2/acl.h index a91f9ce278d6d3..1ed05899cce13b 100644 --- a/fs/ocfs2/acl.h +++ b/fs/ocfs2/acl.h @@ -17,7 +17,7 @@ struct ocfs2_acl_entry { }; struct posix_acl *ocfs2_iop_get_acl(struct inode *inode, int type, bool rcu); -int ocfs2_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ocfs2_iop_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int ocfs2_acl_chmod(struct inode *, struct buffer_head *); struct ocfs2_acl_state { diff --git a/fs/orangefs/acl.c b/fs/orangefs/acl.c index a01ef0c1b1bf2b..f31196e4bfaae6 100644 --- a/fs/orangefs/acl.c +++ b/fs/orangefs/acl.c @@ -112,7 +112,7 @@ int __orangefs_set_acl(struct inode *inode, struct posix_acl *acl, int type) return error; } -int orangefs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int orangefs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int error; diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h index c582ceffb26e3c..a5c9e7be4005d4 100644 --- a/fs/orangefs/orangefs-kernel.h +++ b/fs/orangefs/orangefs-kernel.h @@ -98,7 +98,7 @@ enum orangefs_vfs_op_states { extern const struct xattr_handler * const orangefs_xattr_handlers[]; extern struct posix_acl *orangefs_get_acl(struct inode *inode, int type, bool rcu); -extern int orangefs_set_acl(struct mnt_idmap *idmap, +extern int orangefs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int __orangefs_set_acl(struct inode *inode, struct posix_acl *acl, int type); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 5f4e8b6d972227..18e4cbe154cf36 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -536,7 +536,7 @@ static int ovl_set_or_remove_acl(struct dentry *dentry, struct inode *inode, return err; } -int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ovl_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int err; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 46a5cf5dae4c61..8b335993bf56dd 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -819,7 +819,7 @@ static inline struct posix_acl *ovl_get_acl(struct mnt_idmap *idmap, { return do_ovl_get_acl(idmap, d_inode(dentry), type, false, false); } -int ovl_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int ovl_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); struct posix_acl *ovl_get_acl_path(const struct path *path, const char *acl_name, bool noperm); diff --git a/fs/posix_acl.c b/fs/posix_acl.c index b279804ebf5657..cec7fb557278ab 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -608,7 +608,7 @@ EXPORT_SYMBOL(__posix_acl_chmod); * performed on the raw inode simply pass @nop_mnt_idmap. */ int - posix_acl_chmod(struct mnt_idmap *idmap, struct dentry *dentry, + posix_acl_chmod(const struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode) { struct inode *inode = d_inode(dentry); @@ -937,7 +937,7 @@ static ssize_t vfs_posix_acl_to_xattr(const struct mnt_idmap *idmap, } int -set_posix_acl(struct mnt_idmap *idmap, struct dentry *dentry, +set_posix_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type, struct posix_acl *acl) { struct inode *inode = d_inode(dentry); @@ -1018,7 +1018,7 @@ const struct xattr_handler nop_posix_acl_default = { }; EXPORT_SYMBOL_GPL(nop_posix_acl_default); -int simple_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int simple_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { int error; @@ -1091,7 +1091,7 @@ static int vfs_set_acl_idmapped_mnt(const struct mnt_idmap *idmap, * * Return: On success 0, on error negative errno. */ -int vfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { int acl_type; @@ -1212,7 +1212,7 @@ EXPORT_SYMBOL_GPL(vfs_get_acl); * * Return: On success 0, on error negative errno. */ -int vfs_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { int acl_type; @@ -1265,7 +1265,7 @@ int vfs_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, } EXPORT_SYMBOL_GPL(vfs_remove_acl); -int do_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, const void *kvalue, size_t size) { int error; diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 12005f46307dea..0a05b6b0be5f69 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1931,7 +1931,7 @@ struct posix_acl *cifs_get_acl(struct mnt_idmap *idmap, #endif } -int cifs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int cifs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { #if defined(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) && defined(CONFIG_CIFS_POSIX) diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 00168839c12319..565697c233a9e1 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -214,7 +214,7 @@ struct smb_ntsd *get_cifs_acl_by_fid(struct cifs_sb_info *cifs_sb, u32 *pacllen, u32 info); struct posix_acl *cifs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); -int cifs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int cifs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); int set_cifs_acl(struct smb_ntsd *pnntsd, __u32 acllen, struct inode *inode, const char *path, int aclflag); diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 3c4d18a4e8d531..75edff8dde31ce 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -1471,7 +1471,7 @@ struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work, return dent; } -int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap, +int ksmbd_vfs_remove_acl_xattrs(const struct mnt_idmap *idmap, const struct path *path) { char *name, *xattr_list = NULL; @@ -2200,7 +2200,7 @@ void ksmbd_vfs_posix_lock_unblock(struct file_lock *flock) locks_delete_block(flock); } -int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap, +int ksmbd_vfs_set_init_posix_acl(const struct mnt_idmap *idmap, const struct path *path) { struct posix_acl_state acl_state; diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h index 5e34fc61b48bcf..67cc48e583c816 100644 --- a/fs/smb/server/vfs.h +++ b/fs/smb/server/vfs.h @@ -152,7 +152,7 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work, struct ksmbd_kstat *ksmbd_kstat); void ksmbd_vfs_posix_lock_wait(struct file_lock *flock); void ksmbd_vfs_posix_lock_unblock(struct file_lock *flock); -int ksmbd_vfs_remove_acl_xattrs(struct mnt_idmap *idmap, +int ksmbd_vfs_remove_acl_xattrs(const struct mnt_idmap *idmap, const struct path *path); int ksmbd_vfs_remove_sd_xattrs(const struct mnt_idmap *idmap, const struct path *path); int ksmbd_vfs_set_sd_xattr(struct ksmbd_conn *conn, @@ -171,7 +171,7 @@ int ksmbd_vfs_set_dos_attrib_xattr(const struct mnt_idmap *idmap, int ksmbd_vfs_get_dos_attrib_xattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct xattr_dos_attrib *da); -int ksmbd_vfs_set_init_posix_acl(struct mnt_idmap *idmap, +int ksmbd_vfs_set_init_posix_acl(const struct mnt_idmap *idmap, const struct path *path); int ksmbd_vfs_inherit_posix_acl(const struct mnt_idmap *idmap, const struct path *path, diff --git a/fs/xattr.c b/fs/xattr.c index acdaafb0c7c12c..758a7ffe3a86f4 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -652,7 +652,7 @@ int setxattr_copy(const char __user *name, struct kernel_xattr_ctx *ctx) return error; } -static int do_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int do_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct kernel_xattr_ctx *ctx) { if (is_posix_acl_xattr(ctx->kname->name)) @@ -1029,7 +1029,7 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size) * Extended attribute REMOVE operations */ static long -removexattr(struct mnt_idmap *idmap, struct dentry *d, const char *name) +removexattr(const struct mnt_idmap *idmap, struct dentry *d, const char *name) { if (is_posix_acl_xattr(name)) return vfs_remove_acl(idmap, d, name); diff --git a/fs/xfs/xfs_acl.c b/fs/xfs/xfs_acl.c index fdfca6fc75b6ea..20d87b52c4fc70 100644 --- a/fs/xfs/xfs_acl.c +++ b/fs/xfs/xfs_acl.c @@ -243,7 +243,7 @@ xfs_acl_set_mode( } int -xfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +xfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type) { umode_t mode; diff --git a/fs/xfs/xfs_acl.h b/fs/xfs/xfs_acl.h index bf7f960997d36a..183526bec32c17 100644 --- a/fs/xfs/xfs_acl.h +++ b/fs/xfs/xfs_acl.h @@ -11,7 +11,7 @@ struct posix_acl; #ifdef CONFIG_XFS_POSIX_ACL extern struct posix_acl *xfs_get_acl(struct inode *inode, int type, bool rcu); -extern int xfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +extern int xfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); extern int __xfs_set_acl(struct inode *inode, struct posix_acl *acl, int type); void xfs_forget_acl(struct inode *inode, const char *name); diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index b652f129c85e10..25d4d42fbc7ffb 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -777,7 +777,7 @@ xfs_vn_change_ok( */ static int xfs_setattr_nonsize( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct xfs_inode *ip, struct iattr *iattr) @@ -903,7 +903,7 @@ xfs_setattr_nonsize( */ int xfs_vn_setattr_size( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { diff --git a/fs/xfs/xfs_iops.h b/fs/xfs/xfs_iops.h index 0896f6b8b3b8a1..328305bba19dc7 100644 --- a/fs/xfs/xfs_iops.h +++ b/fs/xfs/xfs_iops.h @@ -10,7 +10,7 @@ struct xfs_inode; extern ssize_t xfs_vn_listxattr(struct dentry *, char *data, size_t size); -int xfs_vn_setattr_size(struct mnt_idmap *idmap, +int xfs_vn_setattr_size(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *vap); int xfs_inode_init_security(struct inode *inode, struct inode *dir, diff --git a/include/linux/fs.h b/include/linux/fs.h index 764ef2d97a5df9..9186d9bb2697d2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2028,7 +2028,7 @@ struct inode_operations { struct file *, umode_t); struct posix_acl *(*get_acl)(struct mnt_idmap *, struct dentry *, int); - int (*set_acl)(struct mnt_idmap *, struct dentry *, + int (*set_acl)(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index 7c6cfcae0a9d48..9220fe0c965dca 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -74,20 +74,20 @@ extern int __posix_acl_create(struct posix_acl **, gfp_t, umode_t *); extern int __posix_acl_chmod(struct posix_acl **, gfp_t, umode_t); extern struct posix_acl *get_posix_acl(struct inode *, int); -int set_posix_acl(struct mnt_idmap *, struct dentry *, int, +int set_posix_acl(const struct mnt_idmap *, struct dentry *, int, struct posix_acl *); struct posix_acl *get_cached_acl_rcu(struct inode *inode, int type); struct posix_acl *posix_acl_clone(const struct posix_acl *acl, gfp_t flags); #ifdef CONFIG_FS_POSIX_ACL -int posix_acl_chmod(struct mnt_idmap *, struct dentry *, umode_t); +int posix_acl_chmod(const struct mnt_idmap *, struct dentry *, umode_t); extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **, struct posix_acl **); int posix_acl_update_mode(const struct mnt_idmap *, struct inode *, umode_t *, struct posix_acl **); -int simple_set_acl(struct mnt_idmap *, struct dentry *, +int simple_set_acl(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); extern int simple_acl_create(struct inode *, struct inode *); @@ -105,16 +105,16 @@ static inline void cache_no_acl(struct inode *inode) inode->i_default_acl = NULL; } -int vfs_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl); struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); -int vfs_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); int posix_acl_listxattr(struct inode *inode, char **buffer, ssize_t *remaining_size); #else -static inline int posix_acl_chmod(struct mnt_idmap *idmap, +static inline int posix_acl_chmod(const struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode) { return 0; @@ -141,7 +141,7 @@ static inline void forget_all_cached_acls(struct inode *inode) { } -static inline int vfs_set_acl(struct mnt_idmap *idmap, +static inline int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct posix_acl *acl) { @@ -155,7 +155,7 @@ static inline struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap, return ERR_PTR(-EOPNOTSUPP); } -static inline int vfs_remove_acl(struct mnt_idmap *idmap, +static inline int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return -EOPNOTSUPP; From 03a56ee3418b8e2cc3218b1a671bf75de2e4b7ad Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:41 +0200 Subject: [PATCH 0767/2286] fs: port ->get_acl() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-16-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/acl.c | 2 +- fs/9p/acl.h | 2 +- fs/ecryptfs/inode.c | 2 +- fs/fuse/acl.c | 2 +- fs/fuse/fuse_i.h | 2 +- fs/internal.h | 4 ++-- fs/ntfs/ea.c | 2 +- fs/ntfs/ea.h | 2 +- fs/ntfs3/ntfs_fs.h | 2 +- fs/ntfs3/xattr.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/posix_acl.c | 6 +++--- fs/smb/client/cifsacl.c | 2 +- fs/smb/client/cifsproto.h | 2 +- fs/xattr.c | 2 +- include/linux/fs.h | 2 +- include/linux/posix_acl.h | 4 ++-- 19 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index b95cd499289680..167988321a8cac 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -91,7 +91,7 @@ prototypes:: int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa); - struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int); + struct posix_acl * (*get_acl)(const struct mnt_idmap *, struct dentry *, int); struct offset_ctx *(*get_offset_ctx)(struct inode *inode); locking rules: diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index d0dd2be3885bd8..6adb1061a9b64f 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -439,7 +439,7 @@ As of kernel 2.6.22, the following members are defined: int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); - struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int); + struct posix_acl * (*get_acl)(const struct mnt_idmap *, struct dentry *, int); int (*set_acl)(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); diff --git a/fs/9p/acl.c b/fs/9p/acl.c index d09a41336d0d6a..c6c7c47d32b9e8 100644 --- a/fs/9p/acl.c +++ b/fs/9p/acl.c @@ -140,7 +140,7 @@ struct posix_acl *v9fs_iop_get_inode_acl(struct inode *inode, int type, bool rcu } -struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, +struct posix_acl *v9fs_iop_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { struct v9fs_session_info *v9ses; diff --git a/fs/9p/acl.h b/fs/9p/acl.h index 7de74704c46582..2d1b24abcd3f50 100644 --- a/fs/9p/acl.h +++ b/fs/9p/acl.h @@ -10,7 +10,7 @@ int v9fs_get_acl(struct inode *inode, struct p9_fid *fid); struct posix_acl *v9fs_iop_get_inode_acl(struct inode *inode, int type, bool rcu); -struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, +struct posix_acl *v9fs_iop_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type); int v9fs_iop_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 1e514648521401..bd1187661d6df3 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1090,7 +1090,7 @@ static int ecryptfs_fileattr_set(const struct mnt_idmap *idmap, return rc; } -static struct posix_acl *ecryptfs_get_acl(struct mnt_idmap *idmap, +static struct posix_acl *ecryptfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { return vfs_get_acl(idmap, ecryptfs_dentry_to_lower(dentry), diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c index c3813fff4f2719..738abed9a81667 100644 --- a/fs/fuse/acl.c +++ b/fs/fuse/acl.c @@ -62,7 +62,7 @@ static inline bool fuse_no_acl(const struct fuse_conn *fc, return !fc->posix_acl && (i_user_ns(inode) != &init_user_ns); } -struct posix_acl *fuse_get_acl(struct mnt_idmap *idmap, +struct posix_acl *fuse_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 561ada21ea86d5..9bf4c738bd746d 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1205,7 +1205,7 @@ extern const struct xattr_handler * const fuse_xattr_handlers[]; struct posix_acl; struct posix_acl *fuse_get_inode_acl(struct inode *inode, int type, bool rcu); -struct posix_acl *fuse_get_acl(struct mnt_idmap *idmap, +struct posix_acl *fuse_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type); int fuse_set_acl(const struct mnt_idmap *, struct dentry *dentry, struct posix_acl *acl, int type); diff --git a/fs/internal.h b/fs/internal.h index 8ba0a1613d206f..8a1c7c05c5a28b 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -304,7 +304,7 @@ int may_write_xattr(const struct mnt_idmap *idmap, struct inode *inode); #ifdef CONFIG_FS_POSIX_ACL int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, const void *kvalue, size_t size); -ssize_t do_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +ssize_t do_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, void *kvalue, size_t size); #else static inline int do_set_acl(const struct mnt_idmap *idmap, @@ -313,7 +313,7 @@ static inline int do_set_acl(const struct mnt_idmap *idmap, { return -EOPNOTSUPP; } -static inline ssize_t do_get_acl(struct mnt_idmap *idmap, +static inline ssize_t do_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, void *kvalue, size_t size) { diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c index 063cc15f162659..ed31f0e9f5d2c7 100644 --- a/fs/ntfs/ea.c +++ b/fs/ntfs/ea.c @@ -954,7 +954,7 @@ const struct xattr_handler * const ntfs_xattr_handlers[] = { // clang-format on #ifdef CONFIG_NTFS_FS_POSIX_ACL -struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +struct posix_acl *ntfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/ntfs/ea.h b/fs/ntfs/ea.h index 6d98e7487a2c3d..690fafe181fb01 100644 --- a/fs/ntfs/ea.h +++ b/fs/ntfs/ea.h @@ -17,7 +17,7 @@ int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size, ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size); #ifdef CONFIG_NTFS_FS_POSIX_ACL -struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +struct posix_acl *ntfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type); int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 0a5e81d420f062..d89609a17886b2 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -963,7 +963,7 @@ unsigned long ntfs_names_hash(const u16 *name, size_t len, const u16 *upcase, /* globals from xattr.c */ #ifdef CONFIG_NTFS3_FS_POSIX_ACL -struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +struct posix_acl *ntfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type); int ntfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); diff --git a/fs/ntfs3/xattr.c b/fs/ntfs3/xattr.c index 2d17b80999ca36..941d516ab8dd34 100644 --- a/fs/ntfs3/xattr.c +++ b/fs/ntfs3/xattr.c @@ -543,7 +543,7 @@ static noinline int ntfs_set_ea(struct inode *inode, const char *name, /* * ntfs_get_acl - inode_operations::get_acl */ -struct posix_acl *ntfs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +struct posix_acl *ntfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { struct inode *inode = d_inode(dentry); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 8b335993bf56dd..1759221dcad5d9 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -814,7 +814,7 @@ static inline struct posix_acl *ovl_get_inode_acl(struct inode *inode, int type, { return do_ovl_get_acl(&nop_mnt_idmap, inode, type, rcu, true); } -static inline struct posix_acl *ovl_get_acl(struct mnt_idmap *idmap, +static inline struct posix_acl *ovl_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { return do_ovl_get_acl(idmap, d_inode(dentry), type, false, false); diff --git a/fs/posix_acl.c b/fs/posix_acl.c index cec7fb557278ab..fe77934ea8f209 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -118,7 +118,7 @@ void forget_all_cached_acls(struct inode *inode) } EXPORT_SYMBOL(forget_all_cached_acls); -static struct posix_acl *__get_acl(struct mnt_idmap *idmap, +static struct posix_acl *__get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct inode *inode, int type) { @@ -1168,7 +1168,7 @@ EXPORT_SYMBOL_GPL(vfs_set_acl); * * Return: On success POSIX ACLs in VFS format, on error negative errno. */ -struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap, +struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { struct inode *inode = d_inode(dentry); @@ -1286,7 +1286,7 @@ int do_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, return error; } -ssize_t do_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +ssize_t do_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, void *kvalue, size_t size) { ssize_t error; diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c index 0a05b6b0be5f69..084a4460ca198b 100644 --- a/fs/smb/client/cifsacl.c +++ b/fs/smb/client/cifsacl.c @@ -1867,7 +1867,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode, return rc; } -struct posix_acl *cifs_get_acl(struct mnt_idmap *idmap, +struct posix_acl *cifs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type) { #if defined(CONFIG_CIFS_ALLOW_INSECURE_LEGACY) && defined(CONFIG_CIFS_POSIX) diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 565697c233a9e1..e6beff8aafe09d 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -212,7 +212,7 @@ struct smb_ntsd *get_cifs_acl(struct cifs_sb_info *cifs_sb, struct smb_ntsd *get_cifs_acl_by_fid(struct cifs_sb_info *cifs_sb, const struct cifs_fid *cifsfid, u32 *pacllen, u32 info); -struct posix_acl *cifs_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, +struct posix_acl *cifs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, int type); int cifs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, struct posix_acl *acl, int type); diff --git a/fs/xattr.c b/fs/xattr.c index 758a7ffe3a86f4..d9f035610f0bf1 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -787,7 +787,7 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name, * Extended attribute GET operations */ static ssize_t -do_getxattr(struct mnt_idmap *idmap, struct dentry *d, +do_getxattr(const struct mnt_idmap *idmap, struct dentry *d, struct kernel_xattr_ctx *ctx) { ssize_t error; diff --git a/include/linux/fs.h b/include/linux/fs.h index 9186d9bb2697d2..9809e7c3aa0d1c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2026,7 +2026,7 @@ struct inode_operations { umode_t create_mode); int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); - struct posix_acl *(*get_acl)(struct mnt_idmap *, struct dentry *, + struct posix_acl *(*get_acl)(const struct mnt_idmap *, struct dentry *, int); int (*set_acl)(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index 9220fe0c965dca..caf500bed9933c 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -107,7 +107,7 @@ static inline void cache_no_acl(struct inode *inode) int vfs_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl); -struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap, +struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); int vfs_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name); @@ -148,7 +148,7 @@ static inline int vfs_set_acl(const struct mnt_idmap *idmap, return -EOPNOTSUPP; } -static inline struct posix_acl *vfs_get_acl(struct mnt_idmap *idmap, +static inline struct posix_acl *vfs_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { From ad664d41535a5a8183b15a98ecfa948f5f88779f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:42 +0200 Subject: [PATCH 0768/2286] fs: port ->tmpfile() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-17-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fuse/dir.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/minix/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/ramfs/inode.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/dir.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 2 +- include/linux/lsm_hook_defs.h | 2 +- include/linux/security.h | 4 ++-- mm/shmem.c | 2 +- security/integrity/ima/ima_main.c | 2 +- security/security.c | 2 +- 23 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 167988321a8cac..e4948ddc0a5fcb 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -86,7 +86,7 @@ prototypes:: int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); - int (*tmpfile) (struct mnt_idmap *, struct inode *, + int (*tmpfile) (const struct mnt_idmap *, struct inode *, struct file *, umode_t); int (*fileattr_set)(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 6adb1061a9b64f..e960257ef14a7b 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -438,7 +438,7 @@ As of kernel 2.6.22, the following members are defined: void (*sync_lazytime)(struct inode *inode); int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); - int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t); + int (*tmpfile) (const struct mnt_idmap *, struct inode *, struct file *, umode_t); struct posix_acl * (*get_acl)(const struct mnt_idmap *, struct dentry *, int); int (*set_acl)(const struct mnt_idmap *, struct dentry *, struct posix_acl *, int); int (*fileattr_set)(const struct mnt_idmap *idmap, diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 6e116810f66d04..d8c0ce62cc6358 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -146,7 +146,7 @@ static int bad_inode_atomic_open(struct inode *inode, struct dentry *dentry, return -EIO; } -static int bad_inode_tmpfile(struct mnt_idmap *idmap, +static int bad_inode_tmpfile(const struct mnt_idmap *idmap, struct inode *inode, struct file *file, umode_t mode) { diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 7bf38f069116fd..092057508ae9ed 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9391,7 +9391,7 @@ static int btrfs_permission(const struct mnt_idmap *idmap, return generic_permission(idmap, inode, mask); } -static int btrfs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int btrfs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct btrfs_fs_info *fs_info = inode_to_fs_info(dir); diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 8666233ec63bc7..108dc52cedfc9b 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -117,7 +117,7 @@ static int ext2_create (struct mnt_idmap * idmap, return ext2_add_nondir(dentry, inode); } -static int ext2_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int ext2_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct inode *inode = ext2_new_inode(dir, mode, NULL); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index edc12599e64d88..0bff01fb052a09 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2881,7 +2881,7 @@ static int ext4_mknod(struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ext4_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int ext4_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { handle_t *handle; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index e3378029471370..3adc2345a204e5 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -928,7 +928,7 @@ static int __f2fs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int f2fs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int f2fs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0dbe3c6560f819..14dc9318cd5724 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1098,7 +1098,7 @@ static int fuse_create(struct mnt_idmap *idmap, struct inode *dir, return fuse_mknod(idmap, dir, entry, mode, 0); } -static int fuse_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int fuse_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct fuse_conn *fc = get_fuse_conn(dir); diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 4bd3adc20a4756..8a07a3f18c0511 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -984,7 +984,7 @@ static int hugetlbfs_create(struct mnt_idmap *idmap, return hugetlbfs_mknod(idmap, dir, dentry, mode | S_IFREG, 0); } -static int hugetlbfs_tmpfile(struct mnt_idmap *idmap, +static int hugetlbfs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 5525ba367ed749..0575dea16a3bbb 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -50,7 +50,7 @@ static int minix_mknod(struct mnt_idmap *idmap, struct inode *dir, return add_nondir(dentry, inode); } -static int minix_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int minix_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct inode *inode = minix_new_inode(dir, mode); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a022dc7781bfb1..4f3f1e2bafff38 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -1420,7 +1420,7 @@ static int ovl_dummy_open(struct inode *inode, struct file *file) return 0; } -static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int ovl_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { int err; diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 0a88ede48e0ad7..fc9a79af484081 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -163,7 +163,7 @@ static int ramfs_symlink(struct mnt_idmap *idmap, struct inode *dir, return error; } -static int ramfs_tmpfile(struct mnt_idmap *idmap, +static int ramfs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct inode *inode; diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 0c85daa8386ef8..255f478d3a185b 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -57,7 +57,7 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *direntry, umode_t mode); int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); -int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +int cifs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode); struct dentry *cifs_lookup(struct inode *parent_dir_inode, struct dentry *direntry, unsigned int flags); diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6fa6d48fdfd30f..56a3e1966257fd 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -1059,7 +1059,7 @@ static int set_tmpfile_attr(const unsigned int xid, unsigned int oflags, * The initial dentry state is unhashed-negative. On success, dentry will * become unhashed-positive by calling d_instantiate(). */ -int cifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +int cifs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct dentry *dentry = file->f_path.dentry; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 23ec924162d63b..0e1a4de9bb1823 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -440,7 +440,7 @@ static void unlock_2_inodes(struct inode *inode1, struct inode *inode2) mutex_unlock(&ubifs_inode(inode1)->ui_mutex); } -static int ubifs_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int ubifs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct dentry *dentry = file->f_path.dentry; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index b90841ac0a4031..8b5873b533245c 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -386,7 +386,7 @@ static int udf_create(struct mnt_idmap *idmap, struct inode *dir, return udf_add_nondir(dentry, inode); } -static int udf_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +static int udf_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct inode *inode = udf_new_inode(dir, mode); diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 25d4d42fbc7ffb..06ca999d092773 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -1250,7 +1250,7 @@ xfs_vn_fiemap( STATIC int xfs_vn_tmpfile( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) diff --git a/include/linux/fs.h b/include/linux/fs.h index 9809e7c3aa0d1c..92161c224856e8 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2024,7 +2024,7 @@ struct inode_operations { int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); - int (*tmpfile) (struct mnt_idmap *, struct inode *, + int (*tmpfile) (const struct mnt_idmap *, struct inode *, struct file *, umode_t); struct posix_acl *(*get_acl)(const struct mnt_idmap *, struct dentry *, int); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index e8638f7d22455b..470de3fb22c129 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -122,7 +122,7 @@ LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode, const struct qstr *name, const struct inode *context_inode) LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry, umode_t mode) -LSM_HOOK(void, LSM_RET_VOID, inode_post_create_tmpfile, struct mnt_idmap *idmap, +LSM_HOOK(void, LSM_RET_VOID, inode_post_create_tmpfile, const struct mnt_idmap *idmap, struct inode *inode) LSM_HOOK(int, 0, inode_link, struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) diff --git a/include/linux/security.h b/include/linux/security.h index d5832750418ac6..b07a85e6aed4c7 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -405,7 +405,7 @@ int security_inode_init_security_anon(struct inode *inode, const struct qstr *name, const struct inode *context_inode); int security_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode); -void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, +void security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode); int security_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry); @@ -910,7 +910,7 @@ static inline int security_inode_create(struct inode *dir, } static inline void -security_inode_post_create_tmpfile(struct mnt_idmap *idmap, struct inode *inode) +security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { } static inline int security_inode_link(struct dentry *old_dentry, diff --git a/mm/shmem.c b/mm/shmem.c index 097699ea276206..a107940660a8eb 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3857,7 +3857,7 @@ shmem_mknod(struct mnt_idmap *idmap, struct inode *dir, } static int -shmem_tmpfile(struct mnt_idmap *idmap, struct inode *dir, +shmem_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, struct file *file, umode_t mode) { struct inode *inode; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 8d9c92dc882538..72ecacda7528e9 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -846,7 +846,7 @@ EXPORT_SYMBOL_GPL(ima_inode_hash); * Skip calling process_measurement(), but indicate which newly, created * tmpfiles are in policy. */ -static void ima_post_create_tmpfile(struct mnt_idmap *idmap, +static void ima_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { diff --git a/security/security.c b/security/security.c index e3a14c958bc40d..4e4ef1c496ad74 100644 --- a/security/security.c +++ b/security/security.c @@ -1638,7 +1638,7 @@ EXPORT_SYMBOL_GPL(security_inode_create); * * Update inode security data after a tmpfile has been created. */ -void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, +void security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { if (unlikely(IS_PRIVATE(inode))) From 7d0db72041b7415370ecd1e5260048ddd49e44d8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:43 +0200 Subject: [PATCH 0769/2286] fs: port ->mknod() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-18-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 4 ++-- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/dir.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fuse/dir.c | 4 ++-- fs/gfs2/inode.c | 2 +- fs/hfsplus/dir.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/namei.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/jffs2/dir.c | 4 ++-- fs/jfs/namei.c | 2 +- fs/minix/namei.c | 2 +- fs/nfs/dir.c | 2 +- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/namei.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/ramfs/inode.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/dir.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/ufs/namei.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 2 +- include/linux/lsm_hook_defs.h | 2 +- include/linux/security.h | 4 ++-- mm/shmem.c | 4 ++-- security/integrity/evm/evm_main.c | 2 +- security/integrity/ima/ima_main.c | 2 +- security/security.c | 2 +- 41 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index e4948ddc0a5fcb..1cfdad9286d5b4 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -68,7 +68,7 @@ prototypes:: int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); - int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); + int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index e960257ef14a7b..d7ab34e101eb09 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -422,7 +422,7 @@ As of kernel 2.6.22, the following members are defined: int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); - int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); + int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 3829554ca36929..81fb5f8ec7e958 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1304,7 +1304,7 @@ v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, */ static int -v9fs_vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 116b29e95f21ee..634c4f5665c32f 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -29,7 +29,7 @@ #include "acl.h" static int -v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_mknod_dotl(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t omode, dev_t rdev); /** @@ -809,7 +809,7 @@ v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir, * */ static int -v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_mknod_dotl(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t omode, dev_t rdev) { int err; diff --git a/fs/bad_inode.c b/fs/bad_inode.c index d8c0ce62cc6358..9b7baf1367a689 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -69,7 +69,7 @@ static int bad_inode_rmdir (struct inode *dir, struct dentry *dentry) return -EIO; } -static int bad_inode_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int bad_inode_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { return -EIO; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 092057508ae9ed..32b5648fdf81ac 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7004,7 +7004,7 @@ static int btrfs_create_common(struct inode *dir, struct dentry *dentry, return ret; } -static int btrfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int btrfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 2e5c0ccb1b3464..0f3c54dc97c6c9 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -921,7 +921,7 @@ int ceph_handle_notrace_create(struct inode *dir, struct dentry *dentry) return PTR_ERR(result); } -static int ceph_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ceph_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(dir->i_sb); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index bd1187661d6df3..411aa024d4b82c 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -562,7 +562,7 @@ static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry) } static int -ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +ecryptfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { int rc; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 108dc52cedfc9b..f175b59cccd7aa 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -131,7 +131,7 @@ static int ext2_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return finish_open_simple(file, 0); } -static int ext2_mknod (struct mnt_idmap * idmap, struct inode * dir, +static int ext2_mknod (const struct mnt_idmap * idmap, struct inode * dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode * inode; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 0bff01fb052a09..d7a58d82f320cc 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2847,7 +2847,7 @@ static int ext4_create(struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ext4_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ext4_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { handle_t *handle; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 3adc2345a204e5..0773f64396f838 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -810,7 +810,7 @@ static int f2fs_rmdir(struct inode *dir, struct dentry *dentry) return -ENOTEMPTY; } -static int f2fs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int f2fs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 14dc9318cd5724..3250598e06f27f 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -934,7 +934,7 @@ static int fuse_create_open(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int fuse_mknod(struct mnt_idmap *, struct inode *, struct dentry *, +static int fuse_mknod(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t); static int fuse_atomic_open(struct inode *dir, struct dentry *entry, struct file *file, unsigned flags, @@ -1069,7 +1069,7 @@ static int create_new_nondir(const struct mnt_idmap *idmap, struct fuse_mount *f return PTR_ERR(create_new_entry(idmap, fm, args, dir, entry, mode)); } -static int fuse_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int fuse_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) { struct fuse_mknod_in inarg; diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 1b1d22abe1d47e..733f74603eafeb 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1363,7 +1363,7 @@ static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, * */ -static int gfs2_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int gfs2_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { return gfs2_create_inode(dir, dentry, NULL, mode, dev, NULL, 0, 0); diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 51fcba2e6d4034..7d31211cee97a2 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -511,7 +511,7 @@ static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir, return res; } -static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int hfsplus_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 0d5c5f11f91399..71d15396235aa6 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -719,7 +719,7 @@ static int hostfs_rmdir(struct inode *ino, struct dentry *dentry) return err; } -static int hostfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int hostfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { struct inode *inode; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 9446f4038874cf..ed749e1684d2f6 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -215,7 +215,7 @@ static int hpfs_create(struct mnt_idmap *idmap, struct inode *dir, return err; } -static int hpfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int hpfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { const unsigned char *name = dentry->d_name.name; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 8a07a3f18c0511..0c9cb8d953ab88 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -954,7 +954,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb, /* * File creation. Allocate an inode, and we're done.. */ -static int hugetlbfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int hugetlbfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { struct inode *inode; diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 656c920864c50b..6d4b26d625c749 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -36,7 +36,7 @@ static int jffs2_symlink (struct mnt_idmap *, struct inode *, static struct dentry *jffs2_mkdir (struct mnt_idmap *, struct inode *,struct dentry *, umode_t); static int jffs2_rmdir (struct inode *,struct dentry *); -static int jffs2_mknod (struct mnt_idmap *, struct inode *,struct dentry *, +static int jffs2_mknod (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t,dev_t); static int jffs2_rename (struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, @@ -620,7 +620,7 @@ static int jffs2_rmdir (struct inode *dir_i, struct dentry *dentry) return ret; } -static int jffs2_mknod (struct mnt_idmap *idmap, struct inode *dir_i, +static int jffs2_mknod (const struct mnt_idmap *idmap, struct inode *dir_i, struct dentry *dentry, umode_t mode, dev_t rdev) { struct jffs2_inode_info *f, *dir_f; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 8a36c218f0f7a7..82a5dbdaf93da2 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -1355,7 +1355,7 @@ static int jfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, * * FUNCTION: Create a special file (device) */ -static int jfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int jfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct jfs_inode_info *jfs_ip; diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 0575dea16a3bbb..57b3c2c5b68883 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -33,7 +33,7 @@ static struct dentry *minix_lookup(struct inode * dir, struct dentry *dentry, un return d_splice_alias(inode, dentry); } -static int minix_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int minix_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 7574bc2fb677fa..10c48672a79dbf 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2448,7 +2448,7 @@ EXPORT_SYMBOL_GPL(nfs_create); * See comments for nfs_proc_create regarding failed operations. */ int -nfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +nfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct iattr attr; diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index abc81f5ae57802..ec37568d197beb 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -405,7 +405,7 @@ int nfs_unlink(struct inode *, struct dentry *); int nfs_symlink(struct mnt_idmap *, struct inode *, struct dentry *, const char *); int nfs_link(struct dentry *, struct inode *, struct dentry *); -int nfs_mknod(struct mnt_idmap *, struct inode *, struct dentry *, umode_t, +int nfs_mknod(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t); int nfs_rename(struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index e037e0c6e31a31..ea3df1d5ec29bb 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -113,7 +113,7 @@ static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir, } static int -nilfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +nilfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index e2cf1ab6935a54..82da5f14efa44a 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1447,7 +1447,7 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ntfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct super_block *sb = dir->i_sb; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index ec59bbabd3c50b..dfafadcd8fda92 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -121,7 +121,7 @@ static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, /* * ntfs_mknod - inode_operations::mknod */ -static int ntfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { return ntfs_create_inode(idmap, dir, dentry, NULL, mode, rdev, NULL, 0, diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index e9c7774ccf9153..6cd77f29593932 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -227,7 +227,7 @@ static void ocfs2_cleanup_add_entry_failure(struct ocfs2_super *osb, iput(inode); } -static int ocfs2_mknod(struct mnt_idmap *idmap, +static int ocfs2_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 4f3f1e2bafff38..6d4b511dc3ca6f 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -742,7 +742,7 @@ static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL)); } -static int ovl_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ovl_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { /* Don't allow creation of "whiteout" on overlay */ diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index fc9a79af484081..5b09860dc69bcc 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -95,7 +95,7 @@ struct inode *ramfs_get_inode(struct super_block *sb, */ /* SMP-safe */ static int -ramfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +ramfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { struct inode * inode = ramfs_get_inode(dir->i_sb, dir, mode, dev); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 255f478d3a185b..db7aebb8599ebb 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -64,7 +64,7 @@ struct dentry *cifs_lookup(struct inode *parent_dir_inode, int cifs_unlink(struct inode *dir, struct dentry *dentry); int cifs_hardlink(struct dentry *old_file, struct inode *inode, struct dentry *direntry); -int cifs_mknod(struct mnt_idmap *idmap, struct inode *inode, +int cifs_mknod(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode, dev_t device_number); struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode); diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 56a3e1966257fd..95da43ad7c02a0 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -696,7 +696,7 @@ int cifs_create(struct mnt_idmap *idmap, struct inode *dir, return rc; } -int cifs_mknod(struct mnt_idmap *idmap, struct inode *inode, +int cifs_mknod(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode, dev_t device_number) { int rc = -EPERM; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 0e1a4de9bb1823..50068515f68020 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1077,7 +1077,7 @@ static struct dentry *ubifs_mkdir(struct mnt_idmap *idmap, struct inode *dir, return ERR_PTR(err); } -static int ubifs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ubifs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 8b5873b533245c..4f7200e6046ee7 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -403,7 +403,7 @@ static int udf_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return finish_open_simple(file, 0); } -static int udf_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int udf_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 6703f3bcf76fc7..a38975b3885e5f 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -85,7 +85,7 @@ static int ufs_create (struct mnt_idmap * idmap, return ufs_add_nondir(dentry, inode); } -static int ufs_mknod(struct mnt_idmap *idmap, struct inode *dir, +static int ufs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { struct inode *inode; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 06ca999d092773..13e57b0e7cae7e 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -279,7 +279,7 @@ xfs_generic_create( STATIC int xfs_vn_mknod( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, diff --git a/include/linux/fs.h b/include/linux/fs.h index 92161c224856e8..4ca59a110d1760 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2008,7 +2008,7 @@ struct inode_operations { struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int (*rmdir) (struct inode *,struct dentry *); - int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *, + int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t,dev_t); int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 470de3fb22c129..ad05971e6137c2 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -94,7 +94,7 @@ LSM_HOOK(int, 0, path_mkdir, const struct path *dir, struct dentry *dentry, LSM_HOOK(int, 0, path_rmdir, const struct path *dir, struct dentry *dentry) LSM_HOOK(int, 0, path_mknod, const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev) -LSM_HOOK(void, LSM_RET_VOID, path_post_mknod, struct mnt_idmap *idmap, +LSM_HOOK(void, LSM_RET_VOID, path_post_mknod, const struct mnt_idmap *idmap, struct dentry *dentry) LSM_HOOK(int, 0, path_truncate, const struct path *path) LSM_HOOK(int, 0, path_symlink, const struct path *dir, struct dentry *dentry, diff --git a/include/linux/security.h b/include/linux/security.h index b07a85e6aed4c7..d4100396da27b8 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -2085,7 +2085,7 @@ int security_path_mkdir(const struct path *dir, struct dentry *dentry, umode_t m int security_path_rmdir(const struct path *dir, struct dentry *dentry); int security_path_mknod(const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev); -void security_path_post_mknod(struct mnt_idmap *idmap, struct dentry *dentry); +void security_path_post_mknod(const struct mnt_idmap *idmap, struct dentry *dentry); int security_path_truncate(const struct path *path); int security_path_symlink(const struct path *dir, struct dentry *dentry, const char *old_name); @@ -2120,7 +2120,7 @@ static inline int security_path_mknod(const struct path *dir, struct dentry *den return 0; } -static inline void security_path_post_mknod(struct mnt_idmap *idmap, +static inline void security_path_post_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { } diff --git a/mm/shmem.c b/mm/shmem.c index a107940660a8eb..16a4ea914bdb33 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3818,7 +3818,7 @@ static int shmem_statfs(struct dentry *dentry, struct kstatfs *buf) * File creation. Allocate an inode, and we're done.. */ static int -shmem_mknod(struct mnt_idmap *idmap, struct inode *dir, +shmem_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { struct inode *inode; @@ -3970,7 +3970,7 @@ static int shmem_rmdir(struct inode *dir, struct dentry *dentry) return shmem_unlink(dir, dentry); } -static int shmem_whiteout(struct mnt_idmap *idmap, +static int shmem_whiteout(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry) { struct dentry *whiteout; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 94928549bfab13..10a0b39dd9b79f 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -1140,7 +1140,7 @@ static void evm_file_release(struct file *file) iint->flags &= ~EVM_NEW_FILE; } -static void evm_post_path_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +static void evm_post_path_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct evm_iint_cache *iint = evm_iint_inode(inode); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 72ecacda7528e9..7ac38a98b1f961 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -879,7 +879,7 @@ static void ima_post_create_tmpfile(const struct mnt_idmap *idmap, * Mark files created via the mknodat syscall as new, so that the * file data can be written later. */ -static void ima_post_path_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +static void ima_post_path_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { struct ima_iint_cache *iint; struct inode *inode = dentry->d_inode; diff --git a/security/security.c b/security/security.c index 4e4ef1c496ad74..9a3ae6746fb385 100644 --- a/security/security.c +++ b/security/security.c @@ -1425,7 +1425,7 @@ EXPORT_SYMBOL(security_path_mknod); * * Update inode security field after a regular file has been created. */ -void security_path_post_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +void security_path_post_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) return; From 8ee61322bb530771f78309a5b25eb5485ff0ff90 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:44 +0200 Subject: [PATCH 0770/2286] fs: port ->rename() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-19-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- drivers/android/binder/rust_binderfs.c | 2 +- drivers/android/binderfs.c | 2 +- fs/9p/v9fs.h | 2 +- fs/9p/vfs_inode.c | 2 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 2 +- fs/afs/dir.c | 4 ++-- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/dir.c | 2 +- fs/coda/dir.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/exfat/namei.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fat/namei_msdos.c | 2 +- fs/fat/namei_vfat.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfs/dir.c | 2 +- fs/hfsplus/dir.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/namei.c | 2 +- fs/jffs2/dir.c | 4 ++-- fs/jfs/namei.c | 2 +- fs/kernfs/dir.c | 2 +- fs/libfs.c | 2 +- fs/minix/namei.c | 2 +- fs/nfs/dir.c | 2 +- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/namei.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/omfs/dir.c | 2 +- fs/orangefs/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/ufs/namei.c | 2 +- fs/vboxsf/dir.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 4 ++-- mm/shmem.c | 2 +- 50 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 1cfdad9286d5b4..97f8aaff311e21 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -69,7 +69,7 @@ prototypes:: struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); - int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, + int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); const char *(*get_link) (struct dentry *, struct inode *, struct delayed_call *); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index d7ab34e101eb09..90f8a0b7424987 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -423,7 +423,7 @@ As of kernel 2.6.22, the following members are defined: struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); - int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, + int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); const char *(*get_link) (struct dentry *, struct inode *, diff --git a/drivers/android/binder/rust_binderfs.c b/drivers/android/binder/rust_binderfs.c index 300cc65562d1e9..e98e6ca163df69 100644 --- a/drivers/android/binder/rust_binderfs.c +++ b/drivers/android/binder/rust_binderfs.c @@ -338,7 +338,7 @@ static inline bool is_binderfs_control_device(const struct dentry *dentry) return info->control_dentry == dentry; } -static int binderfs_rename(struct mnt_idmap *idmap, +static int binderfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/drivers/android/binderfs.c b/drivers/android/binderfs.c index 361d69f756f50e..087eae6ca8ecf2 100644 --- a/drivers/android/binderfs.c +++ b/drivers/android/binderfs.c @@ -344,7 +344,7 @@ static inline bool is_binderfs_control_device(const struct dentry *dentry) return info->control_dentry == dentry; } -static int binderfs_rename(struct mnt_idmap *idmap, +static int binderfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index a462bcbfc7da48..54a4a4ec5c15bc 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -188,7 +188,7 @@ extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d); extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d); -extern int v9fs_vfs_rename(struct mnt_idmap *idmap, +extern int v9fs_vfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 81fb5f8ec7e958..7d4d5a4481fa62 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -858,7 +858,7 @@ int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) */ int -v9fs_vfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +v9fs_vfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/affs/affs.h b/fs/affs/affs.h index d1c506c1f3105f..a4b1e449aafc80 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -176,7 +176,7 @@ extern int affs_link(struct dentry *olddentry, struct inode *dir, extern int affs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname); -extern int affs_rename2(struct mnt_idmap *idmap, +extern int affs_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); diff --git a/fs/affs/namei.c b/fs/affs/namei.c index 6cb52efafe5fed..c9cc0ad5c3305a 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -503,7 +503,7 @@ affs_xrename(struct inode *old_dir, struct dentry *old_dentry, return retval; } -int affs_rename2(struct mnt_idmap *idmap, struct inode *old_dir, +int affs_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 81565366d937b9..fee48c544c61f9 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -43,7 +43,7 @@ static int afs_link(struct dentry *from, struct inode *dir, struct dentry *dentry); static int afs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *content); -static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int afs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); static int afs_dir_writepages(struct address_space *mapping, @@ -2067,7 +2067,7 @@ static const struct afs_operation_ops afs_rename_exchange_operation = { /* * rename a file in an AFS filesystem and/or move it between directories */ -static int afs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int afs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 9b7baf1367a689..c5c76e9dcf82e9 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -75,7 +75,7 @@ static int bad_inode_mknod(const struct mnt_idmap *idmap, struct inode *dir, return -EIO; } -static int bad_inode_rename2(struct mnt_idmap *idmap, +static int bad_inode_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index 91a4871fa05191..80534d04db9dfc 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -199,7 +199,7 @@ static int bfs_unlink(struct inode *dir, struct dentry *dentry) return error; } -static int bfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int bfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 32b5648fdf81ac..e77c791f6efab1 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8835,7 +8835,7 @@ static int btrfs_rename(const struct mnt_idmap *idmap, return ret; } -static int btrfs_rename2(struct mnt_idmap *idmap, struct inode *old_dir, +static int btrfs_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 0f3c54dc97c6c9..864b6949fb9f29 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1478,7 +1478,7 @@ static int ceph_unlink(struct inode *dir, struct dentry *dentry) return err; } -static int ceph_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int ceph_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/coda/dir.c b/fs/coda/dir.c index e8db5960b61b87..a888a7ef30e57c 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -300,7 +300,7 @@ static int coda_rmdir(struct inode *dir, struct dentry *de) } /* rename */ -static int coda_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int coda_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 411aa024d4b82c..7f5396e277523f 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -590,7 +590,7 @@ ecryptfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, } static int -ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +ecryptfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index a4dc83b5949c46..863a8c506fe8cd 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -1263,7 +1263,7 @@ static int __exfat_rename(struct inode *old_parent_inode, return ret; } -static int exfat_rename(struct mnt_idmap *idmap, +static int exfat_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index f175b59cccd7aa..19e298533699d4 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -316,7 +316,7 @@ static int ext2_rmdir (struct inode * dir, struct dentry *dentry) return err; } -static int ext2_rename (struct mnt_idmap * idmap, +static int ext2_rename (const struct mnt_idmap * idmap, struct inode * old_dir, struct dentry * old_dentry, struct inode * new_dir, struct dentry * new_dentry, unsigned int flags) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index d7a58d82f320cc..1fbc2efe23bac2 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -4191,7 +4191,7 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry, return retval; } -static int ext4_rename2(struct mnt_idmap *idmap, +static int ext4_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 0773f64396f838..87b588b1d34fe9 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -1341,7 +1341,7 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, return err; } -static int f2fs_rename2(struct mnt_idmap *idmap, +static int f2fs_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index d46d1a3851f25f..1af607cf47cf87 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -600,7 +600,7 @@ static int do_msdos_rename(struct inode *old_dir, unsigned char *old_name, } /***** Rename, a wrapper for rename_same_dir & rename_diff_dir */ -static int msdos_rename(struct mnt_idmap *idmap, +static int msdos_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index da3e89c0b16ac8..d0b161f43a8bed 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -1160,7 +1160,7 @@ static int vfat_rename_exchange(struct inode *old_dir, struct dentry *old_dentry goto out; } -static int vfat_rename2(struct mnt_idmap *idmap, struct inode *old_dir, +static int vfat_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 3250598e06f27f..51ff444dcf9484 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1307,7 +1307,7 @@ static int fuse_rename_common(const struct mnt_idmap *idmap, struct inode *olddi return err; } -static int fuse_rename2(struct mnt_idmap *idmap, struct inode *olddir, +static int fuse_rename2(const struct mnt_idmap *idmap, struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent, unsigned int flags) { diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 733f74603eafeb..0c00fef40af126 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1877,7 +1877,7 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, return error; } -static int gfs2_rename2(struct mnt_idmap *idmap, struct inode *odir, +static int gfs2_rename2(const struct mnt_idmap *idmap, struct inode *odir, struct dentry *odentry, struct inode *ndir, struct dentry *ndentry, unsigned int flags) { diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index e1f1fb3514644d..2958ca702be2ab 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -281,7 +281,7 @@ static int hfs_remove(struct inode *dir, struct dentry *dentry) * new file/directory. * XXX: how do you handle must_be dir? */ -static int hfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int hfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 7d31211cee97a2..d26b87066bd0cd 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -573,7 +573,7 @@ static struct dentry *hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir, return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0)); } -static int hfsplus_rename(struct mnt_idmap *idmap, +static int hfsplus_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 71d15396235aa6..9028c9f27730d9 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -745,7 +745,7 @@ static int hostfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return 0; } -static int hostfs_rename2(struct mnt_idmap *idmap, +static int hostfs_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index ed749e1684d2f6..dfa8e5b7311f03 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -500,7 +500,7 @@ const struct address_space_operations hpfs_symlink_aops = { .read_folio = hpfs_symlink_read_folio }; -static int hpfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int hpfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 6d4b26d625c749..c56cd87a2a733f 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -38,7 +38,7 @@ static struct dentry *jffs2_mkdir (struct mnt_idmap *, struct inode *,struct den static int jffs2_rmdir (struct inode *,struct dentry *); static int jffs2_mknod (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t,dev_t); -static int jffs2_rename (struct mnt_idmap *, struct inode *, +static int jffs2_rename (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); @@ -769,7 +769,7 @@ static int jffs2_mknod (const struct mnt_idmap *idmap, struct inode *dir_i, return ret; } -static int jffs2_rename (struct mnt_idmap *idmap, +static int jffs2_rename (const struct mnt_idmap *idmap, struct inode *old_dir_i, struct dentry *old_dentry, struct inode *new_dir_i, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 82a5dbdaf93da2..a384ced308affe 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -1066,7 +1066,7 @@ static int jfs_symlink(struct mnt_idmap *idmap, struct inode *dip, * * FUNCTION: rename a file or directory */ -static int jfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int jfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 82bbaeb326aab5..d2e978e4ea6f41 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1326,7 +1326,7 @@ static int kernfs_iop_rmdir(struct inode *dir, struct dentry *dentry) return ret; } -static int kernfs_iop_rename(struct mnt_idmap *idmap, +static int kernfs_iop_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/libfs.c b/fs/libfs.c index 27d7dc16fcb0a0..8b379230b64fee 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -867,7 +867,7 @@ int simple_rename_exchange(struct inode *old_dir, struct dentry *old_dentry, } EXPORT_SYMBOL_GPL(simple_rename_exchange); -int simple_rename(struct mnt_idmap *idmap, struct inode *old_dir, +int simple_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 57b3c2c5b68883..f7aaa19eeac0e1 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -187,7 +187,7 @@ static int minix_rmdir(struct inode * dir, struct dentry *dentry) return err; } -static int minix_rename(struct mnt_idmap *idmap, +static int minix_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 10c48672a79dbf..1161c809c9f133 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2771,7 +2771,7 @@ static bool nfs_rename_is_unsafe_cross_dir(struct dentry *old_dentry, * If these conditions are met, we can drop the dentries before doing * the rename. */ -int nfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +int nfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index ec37568d197beb..c7928c16352dde 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -407,7 +407,7 @@ int nfs_symlink(struct mnt_idmap *, struct inode *, struct dentry *, int nfs_link(struct dentry *, struct inode *, struct dentry *); int nfs_mknod(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t); -int nfs_rename(struct mnt_idmap *, struct inode *, struct dentry *, +int nfs_rename(const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); #ifdef CONFIG_NFS_V4_2 diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index ea3df1d5ec29bb..bfc7c4a06743d1 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -358,7 +358,7 @@ static int nilfs_rmdir(struct inode *dir, struct dentry *dentry) return err; } -static int nilfs_rename(struct mnt_idmap *idmap, +static int nilfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 82da5f14efa44a..431ab27472ab8d 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1248,7 +1248,7 @@ static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni, return err; } -static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index dfafadcd8fda92..0d9c8bf79947b6 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -262,7 +262,7 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry) /* * ntfs_rename - inode_operations::rename */ -static int ntfs_rename(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct inode *new_dir, struct dentry *new_dentry, u32 flags) { diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 6cd77f29593932..05c007213fab9d 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -1211,7 +1211,7 @@ static void ocfs2_double_unlock(struct inode *inode1, struct inode *inode2) ocfs2_inode_unlock(inode2, 1); } -static int ocfs2_rename(struct mnt_idmap *idmap, +static int ocfs2_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 692297cf84e745..99e0a7c8e03b33 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -370,7 +370,7 @@ static bool omfs_fill_chain(struct inode *dir, struct dir_context *ctx, return true; } -static int omfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int omfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 8ebc34e112d514..40105ce80bb851 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -364,7 +364,7 @@ static struct dentry *orangefs_mkdir(struct mnt_idmap *idmap, struct inode *dir, return ret ? ERR_PTR(ret) : NULL; } -static int orangefs_rename(struct mnt_idmap *idmap, +static int orangefs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 6d4b511dc3ca6f..cb2b5a8d28bdbb 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -1344,7 +1344,7 @@ static void ovl_rename_end(struct ovl_renamedata *ovlrd) ovl_drop_write(ovlrd->old_dentry); } -static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir, +static int ovl_rename(const struct mnt_idmap *idmap, struct inode *olddir, struct dentry *old, struct inode *newdir, struct dentry *new, unsigned int flags) { diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index db7aebb8599ebb..e50edcbb2cd3f9 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -69,7 +69,7 @@ int cifs_mknod(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode); int cifs_rmdir(struct inode *inode, struct dentry *direntry); -int cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, +int cifs_rename2(const struct mnt_idmap *idmap, struct inode *source_dir, struct dentry *source_dentry, struct inode *target_dir, struct dentry *target_dentry, unsigned int flags); int cifs_revalidate_file_attr(struct file *filp); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 12ed8db10e0046..eec3a46f6e41c1 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2526,7 +2526,7 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry, } int -cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir, +cifs_rename2(const struct mnt_idmap *idmap, struct inode *source_dir, struct dentry *source_dentry, struct inode *target_dir, struct dentry *target_dentry, unsigned int flags) { diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 50068515f68020..bff6489c4a5c42 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1642,7 +1642,7 @@ static int ubifs_xrename(struct inode *old_dir, struct dentry *old_dentry, return err; } -static int ubifs_rename(struct mnt_idmap *idmap, +static int ubifs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 4f7200e6046ee7..3fe746911581bf 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -762,7 +762,7 @@ static int udf_link(struct dentry *old_dentry, struct inode *dir, /* Anybody can rename anything with this: the permission checks are left to the * higher-level routines. */ -static int udf_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int udf_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index a38975b3885e5f..951c92d37d5661 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -240,7 +240,7 @@ static int ufs_rmdir (struct inode * dir, struct dentry *dentry) return err; } -static int ufs_rename(struct mnt_idmap *idmap, struct inode *old_dir, +static int ufs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) { diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 0b9eab157432df..81b92c6cb2017d 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -382,7 +382,7 @@ static int vboxsf_dir_unlink(struct inode *parent, struct dentry *dentry) return 0; } -static int vboxsf_dir_rename(struct mnt_idmap *idmap, +static int vboxsf_dir_rename(const struct mnt_idmap *idmap, struct inode *old_parent, struct dentry *old_dentry, struct inode *new_parent, diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 13e57b0e7cae7e..26f9eb41599d32 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -466,7 +466,7 @@ xfs_vn_symlink( STATIC int xfs_vn_rename( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *odir, struct dentry *odentry, struct inode *ndir, diff --git a/include/linux/fs.h b/include/linux/fs.h index 4ca59a110d1760..2f2a0088e6935a 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2010,7 +2010,7 @@ struct inode_operations { int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t,dev_t); - int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *, + int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (struct mnt_idmap *, const struct path *, @@ -3276,7 +3276,7 @@ void simple_rename_timestamp(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry); extern int simple_rename_exchange(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry); -extern int simple_rename(struct mnt_idmap *, struct inode *, +extern int simple_rename(const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); extern void simple_recursive_removal(struct dentry *, diff --git a/mm/shmem.c b/mm/shmem.c index 16a4ea914bdb33..ab5b2cd34c28c9 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3991,7 +3991,7 @@ static int shmem_whiteout(const struct mnt_idmap *idmap, * it exists so that the VFS layer correctly free's it when it * gets overwritten. */ -static int shmem_rename2(struct mnt_idmap *idmap, +static int shmem_rename2(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) From 566707035a6e0c859632523f679d9454ebbbe184 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:45 +0200 Subject: [PATCH 0771/2286] fs: port ->mkdir() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-20-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 2 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 2 +- fs/afs/dir.c | 4 ++-- fs/autofs/root.c | 4 ++-- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/dir.c | 2 +- fs/coda/dir.c | 2 +- fs/configfs/dir.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/exfat/namei.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fat/namei_msdos.c | 2 +- fs/fat/namei_vfat.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfs/dir.c | 2 +- fs/hfsplus/dir.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/namei.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/jffs2/dir.c | 4 ++-- fs/jfs/namei.c | 2 +- fs/kernfs/dir.c | 2 +- fs/minix/namei.c | 2 +- fs/nfs/dir.c | 2 +- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/namei.c | 2 +- fs/ocfs2/dlmfs/dlmfs.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/omfs/dir.c | 2 +- fs/orangefs/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/ramfs/inode.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 2 +- fs/tracefs/inode.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/ufs/namei.c | 2 +- fs/vboxsf/dir.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 2 +- kernel/bpf/inode.c | 2 +- mm/shmem.c | 2 +- security/apparmor/apparmorfs.c | 2 +- 54 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 97f8aaff311e21..e0851f1859b381 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -66,7 +66,7 @@ prototypes:: int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); - struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); + struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 90f8a0b7424987..b16105a81cfe3a 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -420,7 +420,7 @@ As of kernel 2.6.22, the following members are defined: int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); - struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); + struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 7d4d5a4481fa62..a448e9a9a01436 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -679,7 +679,7 @@ v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, * */ -static struct dentry *v9fs_vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *v9fs_vfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { u32 perm; diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 634c4f5665c32f..3c21006203d232 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -344,7 +344,7 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, * */ -static struct dentry *v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap, +static struct dentry *v9fs_vfs_mkdir_dotl(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t omode) { diff --git a/fs/affs/affs.h b/fs/affs/affs.h index a4b1e449aafc80..3dd795ce446e39 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -168,7 +168,7 @@ extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, unsi extern int affs_unlink(struct inode *dir, struct dentry *dentry); extern int affs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); -extern struct dentry *affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +extern struct dentry *affs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); extern int affs_rmdir(struct inode *dir, struct dentry *dentry); extern int affs_link(struct dentry *olddentry, struct inode *dir, diff --git a/fs/affs/namei.c b/fs/affs/namei.c index c9cc0ad5c3305a..d17f0ba7c33c8e 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -274,7 +274,7 @@ affs_create(struct mnt_idmap *idmap, struct inode *dir, } struct dentry * -affs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +affs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index fee48c544c61f9..6a3cac50dc611e 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -35,7 +35,7 @@ static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nl #define AFS_LOOKUP ((filldir_t)0x137UL) static int afs_create(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); -static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *afs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); static int afs_rmdir(struct inode *dir, struct dentry *dentry); static int afs_unlink(struct inode *dir, struct dentry *dentry); @@ -1310,7 +1310,7 @@ static const struct afs_operation_ops afs_mkdir_operation = { /* * create a directory on an AFS filesystem */ -static struct dentry *afs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *afs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct afs_operation *op; diff --git a/fs/autofs/root.c b/fs/autofs/root.c index 3158df448025e9..c6de44473d6d2e 100644 --- a/fs/autofs/root.c +++ b/fs/autofs/root.c @@ -16,7 +16,7 @@ static int autofs_dir_symlink(struct mnt_idmap *, struct inode *, struct dentry *, const char *); static int autofs_dir_unlink(struct inode *, struct dentry *); static int autofs_dir_rmdir(struct inode *, struct dentry *); -static struct dentry *autofs_dir_mkdir(struct mnt_idmap *, struct inode *, +static struct dentry *autofs_dir_mkdir(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); static long autofs_root_ioctl(struct file *, unsigned int, unsigned long); #ifdef CONFIG_COMPAT @@ -724,7 +724,7 @@ static int autofs_dir_rmdir(struct inode *dir, struct dentry *dentry) return 0; } -static struct dentry *autofs_dir_mkdir(struct mnt_idmap *idmap, +static struct dentry *autofs_dir_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { diff --git a/fs/bad_inode.c b/fs/bad_inode.c index c5c76e9dcf82e9..f0c56db222236b 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -58,7 +58,7 @@ static int bad_inode_symlink(struct mnt_idmap *idmap, return -EIO; } -static struct dentry *bad_inode_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *bad_inode_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(-EIO); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e77c791f6efab1..dc7e4b68410c3e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7115,7 +7115,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, return ret; } -static struct dentry *btrfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *btrfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 864b6949fb9f29..dd75d4c0a11eec 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1106,7 +1106,7 @@ static int ceph_symlink(struct mnt_idmap *idmap, struct inode *dir, return err; } -static struct dentry *ceph_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ceph_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(dir->i_sb); diff --git a/fs/coda/dir.c b/fs/coda/dir.c index a888a7ef30e57c..3a2fa1e13ef87a 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -166,7 +166,7 @@ static int coda_create(struct mnt_idmap *idmap, struct inode *dir, return error; } -static struct dentry *coda_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *coda_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *de, umode_t mode) { struct inode *inode; diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c index 3c88f13f1ca2e9..e0ca643db1acfd 100644 --- a/fs/configfs/dir.c +++ b/fs/configfs/dir.c @@ -1286,7 +1286,7 @@ int configfs_depend_item_unlocked(struct configfs_subsystem *caller_subsys, } EXPORT_SYMBOL(configfs_depend_item_unlocked); -static struct dentry *configfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *configfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int ret = 0; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 7f5396e277523f..f5340d2b42f450 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -503,7 +503,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap, return rc; } -static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ecryptfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int rc; diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 863a8c506fe8cd..4accf96ea7f951 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -825,7 +825,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry) return err; } -static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *exfat_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 19e298533699d4..0b66e7d374643c 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -223,7 +223,7 @@ static int ext2_link (struct dentry * old_dentry, struct inode * dir, return err; } -static struct dentry *ext2_mkdir(struct mnt_idmap * idmap, +static struct dentry *ext2_mkdir(const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, umode_t mode) { diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 1fbc2efe23bac2..4c0ce31c3ce830 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2994,7 +2994,7 @@ int ext4_init_new_dir(handle_t *handle, struct inode *dir, return err; } -static struct dentry *ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ext4_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { handle_t *handle; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 87b588b1d34fe9..c888f02d89fe18 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -751,7 +751,7 @@ static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, goto out; } -static struct dentry *f2fs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *f2fs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 1af607cf47cf87..9ee899391f4f6c 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -345,7 +345,7 @@ static int msdos_rmdir(struct inode *dir, struct dentry *dentry) } /***** Make a directory */ -static struct dentry *msdos_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *msdos_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index d0b161f43a8bed..a3f3320fe63041 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -846,7 +846,7 @@ static int vfat_unlink(struct inode *dir, struct dentry *dentry) return err; } -static struct dentry *vfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *vfat_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 51ff444dcf9484..88da9010b0ceab 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1116,7 +1116,7 @@ static int fuse_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static struct dentry *fuse_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *fuse_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, umode_t mode) { struct fuse_mkdir_in inarg; diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 0c00fef40af126..6a1d405aa6e990 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1345,7 +1345,7 @@ static int gfs2_symlink(struct mnt_idmap *idmap, struct inode *dir, * Returns: the dentry, or ERR_PTR(errno) */ -static struct dentry *gfs2_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *gfs2_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { unsigned dsize = gfs2_max_stuffed_size(GFS2_I(dir)); diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index 2958ca702be2ab..d60399c62e992c 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -213,7 +213,7 @@ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, * in a directory, given the inode for the parent directory and the * name (and its length) of the new directory. */ -static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *hfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index d26b87066bd0cd..651b9f73b567c5 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -567,7 +567,7 @@ static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } -static struct dentry *hfsplus_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *hfsplus_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0)); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 9028c9f27730d9..2eec9ebe15878c 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -686,7 +686,7 @@ static int hostfs_symlink(struct mnt_idmap *idmap, struct inode *ino, return err; } -static struct dentry *hostfs_mkdir(struct mnt_idmap *idmap, struct inode *ino, +static struct dentry *hostfs_mkdir(const struct mnt_idmap *idmap, struct inode *ino, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index dfa8e5b7311f03..280459b00e5631 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -19,7 +19,7 @@ static void hpfs_update_directory_times(struct inode *dir) hpfs_write_inode_nolock(dir); } -static struct dentry *hpfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *hpfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { const unsigned char *name = dentry->d_name.name; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 0c9cb8d953ab88..6298a30e73438c 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -967,7 +967,7 @@ static int hugetlbfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return 0; } -static struct dentry *hugetlbfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *hugetlbfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int retval = hugetlbfs_mknod(idmap, dir, dentry, diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index c56cd87a2a733f..b4f808825738f9 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -33,7 +33,7 @@ static int jffs2_link (struct dentry *,struct inode *,struct dentry *); static int jffs2_unlink (struct inode *,struct dentry *); static int jffs2_symlink (struct mnt_idmap *, struct inode *, struct dentry *, const char *); -static struct dentry *jffs2_mkdir (struct mnt_idmap *, struct inode *,struct dentry *, +static struct dentry *jffs2_mkdir (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t); static int jffs2_rmdir (struct inode *,struct dentry *); static int jffs2_mknod (const struct mnt_idmap *, struct inode *,struct dentry *, @@ -448,7 +448,7 @@ static int jffs2_symlink (struct mnt_idmap *idmap, struct inode *dir_i, } -static struct dentry *jffs2_mkdir (struct mnt_idmap *idmap, struct inode *dir_i, +static struct dentry *jffs2_mkdir (const struct mnt_idmap *idmap, struct inode *dir_i, struct dentry *dentry, umode_t mode) { struct jffs2_inode_info *f, *dir_f; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index a384ced308affe..305eb2cab105a6 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -193,7 +193,7 @@ static int jfs_create(struct mnt_idmap *idmap, struct inode *dip, * note: * EACCES: user needs search+write permission on the parent directory */ -static struct dentry *jfs_mkdir(struct mnt_idmap *idmap, struct inode *dip, +static struct dentry *jfs_mkdir(const struct mnt_idmap *idmap, struct inode *dip, struct dentry *dentry, umode_t mode) { int rc = 0; diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index d2e978e4ea6f41..943d6ee3e505a7 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1288,7 +1288,7 @@ static struct dentry *kernfs_iop_lookup(struct inode *dir, return d_splice_alias(inode, dentry); } -static struct dentry *kernfs_iop_mkdir(struct mnt_idmap *idmap, +static struct dentry *kernfs_iop_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { diff --git a/fs/minix/namei.c b/fs/minix/namei.c index f7aaa19eeac0e1..abf4575f24b19a 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -104,7 +104,7 @@ static int minix_link(struct dentry * old_dentry, struct inode * dir, return add_nondir(dentry, inode); } -static struct dentry *minix_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *minix_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode * inode; diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 1161c809c9f133..dfa9dab7fdcce4 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2475,7 +2475,7 @@ EXPORT_SYMBOL_GPL(nfs_mknod); /* * See comments for nfs_proc_create regarding failed operations. */ -struct dentry *nfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +struct dentry *nfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct iattr attr; diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index c7928c16352dde..da6db93b173009 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -398,7 +398,7 @@ struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, umode_t); -struct dentry *nfs_mkdir(struct mnt_idmap *, struct inode *, struct dentry *, +struct dentry *nfs_mkdir(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int nfs_rmdir(struct inode *, struct dentry *); int nfs_unlink(struct inode *, struct dentry *); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index bfc7c4a06743d1..b21fd2d8cb4a26 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -218,7 +218,7 @@ static int nilfs_link(struct dentry *old_dentry, struct inode *dir, return err; } -static struct dentry *nilfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *nilfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 431ab27472ab8d..cbefeabe81b3ce 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1049,7 +1049,7 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) return err; } -static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index 0d9c8bf79947b6..db02a2fb69c19a 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -228,7 +228,7 @@ static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, /* * ntfs_mkdir - inode_operations::mkdir */ -static struct dentry *ntfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ntfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(ntfs_create_inode(idmap, dir, dentry, NULL, diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 53df5dd10ad05f..5e544d8ded4254 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -402,7 +402,7 @@ static struct inode *dlmfs_get_inode(struct inode *parent, * File creation. Allocate an inode, and we're done.. */ /* SMP-safe */ -static struct dentry *dlmfs_mkdir(struct mnt_idmap * idmap, +static struct dentry *dlmfs_mkdir(const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, umode_t mode) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 05c007213fab9d..89ac6fae34f827 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -655,7 +655,7 @@ static int ocfs2_mknod_locked(struct ocfs2_super *osb, suballoc_loc, suballoc_bit); } -static struct dentry *ocfs2_mkdir(struct mnt_idmap *idmap, +static struct dentry *ocfs2_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index 99e0a7c8e03b33..a476675e8e6197 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -279,7 +279,7 @@ static int omfs_add_node(struct inode *dir, struct dentry *dentry, umode_t mode) return err; } -static struct dentry *omfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *omfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(omfs_add_node(dir, dentry, mode)); diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 40105ce80bb851..749dfc0735a61a 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -296,7 +296,7 @@ static int orangefs_symlink(struct mnt_idmap *idmap, return ret; } -static struct dentry *orangefs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *orangefs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct orangefs_inode_s *parent = ORANGEFS_I(dir); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index cb2b5a8d28bdbb..6e206220a97fe2 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -736,7 +736,7 @@ static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL); } -static struct dentry *ovl_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ovl_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ERR_PTR(ovl_create_object(idmap, dentry, (mode & 07777) | S_IFDIR, 0, NULL)); diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 5b09860dc69bcc..363093354a90ba 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -118,7 +118,7 @@ ramfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return error; } -static struct dentry *ramfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ramfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int retval = ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index e50edcbb2cd3f9..908f6500157871 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -66,7 +66,7 @@ int cifs_hardlink(struct dentry *old_file, struct inode *inode, struct dentry *direntry); int cifs_mknod(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode, dev_t device_number); -struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, +struct dentry *cifs_mkdir(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode); int cifs_rmdir(struct inode *inode, struct dentry *direntry); int cifs_rename2(const struct mnt_idmap *idmap, struct inode *source_dir, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index eec3a46f6e41c1..e3610e456bd41c 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2276,7 +2276,7 @@ cifs_posix_mkdir(struct inode *inode, struct dentry *dentry, umode_t mode, } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ -struct dentry *cifs_mkdir(struct mnt_idmap *idmap, struct inode *inode, +struct dentry *cifs_mkdir(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, umode_t mode) { int rc = 0; diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 7cf32384ad50a4..995b8d0f8d333f 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -94,7 +94,7 @@ static struct tracefs_dir_ops { int (*rmdir)(const char *name); } tracefs_ops __ro_after_init; -static struct dentry *tracefs_syscall_mkdir(struct mnt_idmap *idmap, +static struct dentry *tracefs_syscall_mkdir(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *dentry, umode_t mode) { diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index bff6489c4a5c42..c7723f80e25889 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1002,7 +1002,7 @@ static int ubifs_rmdir(struct inode *dir, struct dentry *dentry) return err; } -static struct dentry *ubifs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ubifs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 3fe746911581bf..7ee11e52737705 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -419,7 +419,7 @@ static int udf_mknod(const struct mnt_idmap *idmap, struct inode *dir, return udf_add_nondir(dentry, inode); } -static struct dentry *udf_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *udf_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 951c92d37d5661..4aee6c3d0f752a 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -165,7 +165,7 @@ static int ufs_link (struct dentry * old_dentry, struct inode * dir, return error; } -static struct dentry *ufs_mkdir(struct mnt_idmap * idmap, struct inode * dir, +static struct dentry *ufs_mkdir(const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, umode_t mode) { struct inode * inode; diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 81b92c6cb2017d..851e4be5eb0d3b 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -303,7 +303,7 @@ static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, return vboxsf_dir_create(parent, dentry, mode, false, true, NULL); } -static struct dentry *vboxsf_dir_mkdir(struct mnt_idmap *idmap, +static struct dentry *vboxsf_dir_mkdir(const struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode) { diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 26f9eb41599d32..495c7f3dece037 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -300,7 +300,7 @@ xfs_vn_create( STATIC struct dentry * xfs_vn_mkdir( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/include/linux/fs.h b/include/linux/fs.h index 2f2a0088e6935a..b02ae54727cbe4 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2005,7 +2005,7 @@ struct inode_operations { int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *, const char *); - struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *, + struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *, diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index 11174037a9ca76..871825d36c8ccc 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -176,7 +176,7 @@ static void bpf_dentry_finalize(struct dentry *dentry, struct inode *inode, inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir)); } -static struct dentry *bpf_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *bpf_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/mm/shmem.c b/mm/shmem.c index ab5b2cd34c28c9..e7635958240662 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3885,7 +3885,7 @@ shmem_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return error; } -static struct dentry *shmem_mkdir(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *shmem_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int error; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 5b42140e12e872..d1386722537d0f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2073,7 +2073,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) return error; } -static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ns_mkdir_op(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; From 6bd532e338759278b5cc1e912cf9b67a997e658c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:46 +0200 Subject: [PATCH 0772/2286] fs: port ->symlink() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-21-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 2 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 2 +- fs/afs/dir.c | 4 ++-- fs/autofs/root.c | 4 ++-- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/dir.c | 2 +- fs/coda/dir.c | 2 +- fs/configfs/configfs_internal.h | 2 +- fs/configfs/symlink.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfsplus/dir.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/namei.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/jffs2/dir.c | 4 ++-- fs/jfs/namei.c | 2 +- fs/minix/namei.c | 2 +- fs/nfs/dir.c | 2 +- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/namei.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/orangefs/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/ramfs/inode.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/link.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/ufs/namei.c | 2 +- fs/vboxsf/dir.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 2 +- kernel/bpf/inode.c | 2 +- mm/shmem.c | 2 +- 46 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index e0851f1859b381..856b9cc88c76ed 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -65,7 +65,7 @@ prototypes:: struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); - int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); + int (*symlink) (const struct mnt_idmap *, struct inode *,struct dentry *,const char *); struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index b16105a81cfe3a..5a9203e98bb633 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -419,7 +419,7 @@ As of kernel 2.6.22, the following members are defined: struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); - int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *); + int (*symlink) (const struct mnt_idmap *, struct inode *,struct dentry *,const char *); struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index a448e9a9a01436..cc52c244bb9558 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1249,7 +1249,7 @@ static int v9fs_vfs_mkspecial(struct inode *dir, struct dentry *dentry, */ static int -v9fs_vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { p9_debug(P9_DEBUG_VFS, " %llu,%pd,%s\n", diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 3c21006203d232..ec58718eec4a82 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -682,7 +682,7 @@ v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode, } static int -v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_symlink_dotl(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { int err; diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 3dd795ce446e39..b1047766a8298d 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -173,7 +173,7 @@ extern struct dentry *affs_mkdir(const struct mnt_idmap *idmap, struct inode *di extern int affs_rmdir(struct inode *dir, struct dentry *dentry); extern int affs_link(struct dentry *olddentry, struct inode *dir, struct dentry *dentry); -extern int affs_symlink(struct mnt_idmap *idmap, +extern int affs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname); extern int affs_rename2(const struct mnt_idmap *idmap, diff --git a/fs/affs/namei.c b/fs/affs/namei.c index d17f0ba7c33c8e..1d609355fc3f54 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -313,7 +313,7 @@ affs_rmdir(struct inode *dir, struct dentry *dentry) } int -affs_symlink(struct mnt_idmap *idmap, struct inode *dir, +affs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct super_block *sb = dir->i_sb; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 6a3cac50dc611e..603dd9323ed22d 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -41,7 +41,7 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry); static int afs_unlink(struct inode *dir, struct dentry *dentry); static int afs_link(struct dentry *from, struct inode *dir, struct dentry *dentry); -static int afs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int afs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *content); static int afs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, @@ -1779,7 +1779,7 @@ static const struct afs_operation_ops afs_symlink_operation = { /* * create a symlink in an AFS filesystem */ -static int afs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int afs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *content) { struct afs_operation *op; diff --git a/fs/autofs/root.c b/fs/autofs/root.c index c6de44473d6d2e..28f38f5d023650 100644 --- a/fs/autofs/root.c +++ b/fs/autofs/root.c @@ -12,7 +12,7 @@ #include "autofs_i.h" static int autofs_dir_permission(const struct mnt_idmap *, struct inode *, int); -static int autofs_dir_symlink(struct mnt_idmap *, struct inode *, +static int autofs_dir_symlink(const struct mnt_idmap *, struct inode *, struct dentry *, const char *); static int autofs_dir_unlink(struct inode *, struct dentry *); static int autofs_dir_rmdir(struct inode *, struct dentry *); @@ -572,7 +572,7 @@ static int autofs_dir_permission(const struct mnt_idmap *idmap, return generic_permission(idmap, inode, mask); } -static int autofs_dir_symlink(struct mnt_idmap *idmap, +static int autofs_dir_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { diff --git a/fs/bad_inode.c b/fs/bad_inode.c index f0c56db222236b..096e6afb5e0782 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -51,7 +51,7 @@ static int bad_inode_unlink(struct inode *dir, struct dentry *dentry) return -EIO; } -static int bad_inode_symlink(struct mnt_idmap *idmap, +static int bad_inode_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index dc7e4b68410c3e..9b5e3a67122d84 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9023,7 +9023,7 @@ int btrfs_start_delalloc_roots(struct btrfs_fs_info *fs_info, long nr, return ret; } -static int btrfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int btrfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct btrfs_fs_info *fs_info = inode_to_fs_info(dir); diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index dd75d4c0a11eec..5417a9948e9867 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -1032,7 +1032,7 @@ static int prep_encrypted_symlink_target(struct ceph_mds_request *req, } #endif -static int ceph_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ceph_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *dest) { struct ceph_mds_client *mdsc = ceph_sb_to_mdsc(dir->i_sb); diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 3a2fa1e13ef87a..02b8259b6a0eb5 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -233,7 +233,7 @@ static int coda_link(struct dentry *source_de, struct inode *dir_inode, } -static int coda_symlink(struct mnt_idmap *idmap, +static int coda_symlink(const struct mnt_idmap *idmap, struct inode *dir_inode, struct dentry *de, const char *symname) { diff --git a/fs/configfs/configfs_internal.h b/fs/configfs/configfs_internal.h index acdeea8e2d69ac..63d70dc0544429 100644 --- a/fs/configfs/configfs_internal.h +++ b/fs/configfs/configfs_internal.h @@ -90,7 +90,7 @@ extern const struct inode_operations configfs_root_inode_operations; extern const struct inode_operations configfs_symlink_inode_operations; extern const struct dentry_operations configfs_dentry_ops; -extern int configfs_symlink(struct mnt_idmap *idmap, +extern int configfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname); extern int configfs_unlink(struct inode *dir, struct dentry *dentry); diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 31eb28b2730913..a07c346817f5c3 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -132,7 +132,7 @@ static int get_target(const char *symname, struct config_item **target, } -int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +int configfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { int ret; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index f5340d2b42f450..f9a3c88c4fbd06 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -462,7 +462,7 @@ static int ecryptfs_unlink(struct inode *dir, struct dentry *dentry) return ecryptfs_do_unlink(dir, dentry, d_inode(dentry)); } -static int ecryptfs_symlink(struct mnt_idmap *idmap, +static int ecryptfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 0b66e7d374643c..7da42f071eab89 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -152,7 +152,7 @@ static int ext2_mknod (const struct mnt_idmap * idmap, struct inode * dir, return err; } -static int ext2_symlink (struct mnt_idmap * idmap, struct inode * dir, +static int ext2_symlink (const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, const char * symname) { struct super_block * sb = dir->i_sb; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 4c0ce31c3ce830..6e493b4d205f2b 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3360,7 +3360,7 @@ static int ext4_init_symlink_block(handle_t *handle, struct inode *inode, return err; } -static int ext4_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ext4_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { handle_t *handle; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index c888f02d89fe18..b2e99822d8b45d 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -662,7 +662,7 @@ static const char *f2fs_get_link(struct dentry *dentry, return link; } -static int f2fs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int f2fs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 88da9010b0ceab..3d8246ff061e49 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1146,7 +1146,7 @@ static struct dentry *fuse_mkdir(const struct mnt_idmap *idmap, struct inode *di return create_new_entry(idmap, fm, &args, dir, entry, S_IFDIR); } -static int fuse_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int fuse_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, const char *link) { struct fuse_mount *fm = get_fuse_mount(dir); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 6a1d405aa6e990..c8f2b01e8cf53c 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1323,7 +1323,7 @@ static int gfs2_unlink(struct inode *dir, struct dentry *dentry) * Returns: errno */ -static int gfs2_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int gfs2_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { unsigned int size; diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 651b9f73b567c5..1b59ddb6d1e173 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -460,7 +460,7 @@ static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry) return res; } -static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int hfsplus_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 2eec9ebe15878c..ef03763aa07474 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -673,7 +673,7 @@ static int hostfs_unlink(struct inode *ino, struct dentry *dentry) return err; } -static int hostfs_symlink(struct mnt_idmap *idmap, struct inode *ino, +static int hostfs_symlink(const struct mnt_idmap *idmap, struct inode *ino, struct dentry *dentry, const char *to) { char *file; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 280459b00e5631..44a7fb79763314 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -289,7 +289,7 @@ static int hpfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int hpfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int hpfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symlink) { const unsigned char *name = dentry->d_name.name; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 6298a30e73438c..9e937602b2ec85 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -998,7 +998,7 @@ static int hugetlbfs_tmpfile(const struct mnt_idmap *idmap, return finish_open_simple(file, 0); } -static int hugetlbfs_symlink(struct mnt_idmap *idmap, +static int hugetlbfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index b4f808825738f9..7d23be0bab728d 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -31,7 +31,7 @@ static struct dentry *jffs2_lookup (struct inode *,struct dentry *, unsigned int); static int jffs2_link (struct dentry *,struct inode *,struct dentry *); static int jffs2_unlink (struct inode *,struct dentry *); -static int jffs2_symlink (struct mnt_idmap *, struct inode *, +static int jffs2_symlink (const struct mnt_idmap *, struct inode *, struct dentry *, const char *); static struct dentry *jffs2_mkdir (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t); @@ -284,7 +284,7 @@ static int jffs2_link (struct dentry *old_dentry, struct inode *dir_i, struct de /***********************************************************************/ -static int jffs2_symlink (struct mnt_idmap *idmap, struct inode *dir_i, +static int jffs2_symlink (const struct mnt_idmap *idmap, struct inode *dir_i, struct dentry *dentry, const char *target) { struct jffs2_inode_info *f, *dir_f; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 305eb2cab105a6..6009d65ab53f07 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -876,7 +876,7 @@ static int jfs_link(struct dentry *old_dentry, * an intermediate result whose length exceeds PATH_MAX [XPG4.2] */ -static int jfs_symlink(struct mnt_idmap *idmap, struct inode *dip, +static int jfs_symlink(const struct mnt_idmap *idmap, struct inode *dip, struct dentry *dentry, const char *name) { int rc; diff --git a/fs/minix/namei.c b/fs/minix/namei.c index abf4575f24b19a..11b66a25e92bd5 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -69,7 +69,7 @@ static int minix_create(struct mnt_idmap *idmap, struct inode *dir, return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); } -static int minix_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int minix_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { int i = strlen(symname)+1; diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index dfa9dab7fdcce4..a917c008a05a63 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2641,7 +2641,7 @@ EXPORT_SYMBOL_GPL(nfs_unlink); * now have a new file handle and can instantiate an in-core NFS inode * and move the raw page into its mapping. */ -int nfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +int nfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct folio *folio; diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index da6db93b173009..4c966dac7f7d98 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -402,7 +402,7 @@ struct dentry *nfs_mkdir(const struct mnt_idmap *, struct inode *, struct dentry umode_t); int nfs_rmdir(struct inode *, struct dentry *); int nfs_unlink(struct inode *, struct dentry *); -int nfs_symlink(struct mnt_idmap *, struct inode *, struct dentry *, +int nfs_symlink(const struct mnt_idmap *, struct inode *, struct dentry *, const char *); int nfs_link(struct dentry *, struct inode *, struct dentry *); int nfs_mknod(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index b21fd2d8cb4a26..8762fdc16c057f 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -138,7 +138,7 @@ nilfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int nilfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int nilfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct nilfs_transaction_info ti; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index cbefeabe81b3ce..637bc3690844dc 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -1399,7 +1399,7 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, return err; } -static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct super_block *sb = dir->i_sb; diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index db02a2fb69c19a..289796da6ba545 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -209,7 +209,7 @@ static int ntfs_unlink(struct inode *dir, struct dentry *dentry) /* * ntfs_symlink - inode_operations::symlink */ -static int ntfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { u32 size = strlen(symname); diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index 89ac6fae34f827..dfa2cb2bcb12fd 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -1815,7 +1815,7 @@ static int ocfs2_create_symlink_data(struct ocfs2_super *osb, return status; } -static int ocfs2_symlink(struct mnt_idmap *idmap, +static int ocfs2_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index 749dfc0735a61a..f5deb8cdbf2f60 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -211,7 +211,7 @@ static int orangefs_unlink(struct inode *dir, struct dentry *dentry) return ret; } -static int orangefs_symlink(struct mnt_idmap *idmap, +static int orangefs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 6e206220a97fe2..f7a3fd3a25b93c 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -752,7 +752,7 @@ static int ovl_mknod(const struct mnt_idmap *idmap, struct inode *dir, return ovl_create_object(idmap, dentry, mode, rdev, NULL); } -static int ovl_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ovl_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *link) { return ovl_create_object(idmap, dentry, S_IFLNK, 0, link); diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index 363093354a90ba..cc06d806b2146b 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -133,7 +133,7 @@ static int ramfs_create(struct mnt_idmap *idmap, struct inode *dir, return ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0); } -static int ramfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ramfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct inode *inode; diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 908f6500157871..483d2b23fc233f 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -129,7 +129,7 @@ struct vfsmount *cifs_d_automount(struct path *path); /* Functions related to symlinks */ const char *cifs_get_link(struct dentry *dentry, struct inode *inode, struct delayed_call *done); -int cifs_symlink(struct mnt_idmap *idmap, struct inode *inode, +int cifs_symlink(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, const char *symname); #ifdef CONFIG_CIFS_XATTR diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c index 8d5d6aca742aa1..76df31abeaeaec 100644 --- a/fs/smb/client/link.c +++ b/fs/smb/client/link.c @@ -533,7 +533,7 @@ cifs_hardlink(struct dentry *old_file, struct inode *inode, } int -cifs_symlink(struct mnt_idmap *idmap, struct inode *inode, +cifs_symlink(const struct mnt_idmap *idmap, struct inode *inode, struct dentry *direntry, const char *symname) { struct cifs_sb_info *cifs_sb = CIFS_SB(inode); diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index c7723f80e25889..d784283d427f7c 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1170,7 +1170,7 @@ static int ubifs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ubifs_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int ubifs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct inode *inode; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 7ee11e52737705..002e79052f926d 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -567,7 +567,7 @@ static int udf_unlink(struct inode *dir, struct dentry *dentry) return ret; } -static int udf_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int udf_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { struct inode *inode; diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 4aee6c3d0f752a..9231a5cda08ebe 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -105,7 +105,7 @@ static int ufs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ufs_symlink (struct mnt_idmap * idmap, struct inode * dir, +static int ufs_symlink (const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, const char * symname) { struct super_block * sb = dir->i_sb; diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 851e4be5eb0d3b..9db16e90f670f3 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -425,7 +425,7 @@ static int vboxsf_dir_rename(const struct mnt_idmap *idmap, return err; } -static int vboxsf_dir_symlink(struct mnt_idmap *idmap, +static int vboxsf_dir_symlink(const struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, const char *symname) { diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 495c7f3dece037..fe388619a13813 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -425,7 +425,7 @@ xfs_vn_unlink( STATIC int xfs_vn_symlink( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) diff --git a/include/linux/fs.h b/include/linux/fs.h index b02ae54727cbe4..f2c7838644c366 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2003,7 +2003,7 @@ struct inode_operations { umode_t); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); - int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *, + int (*symlink) (const struct mnt_idmap *, struct inode *,struct dentry *, const char *); struct dentry *(*mkdir) (const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index 871825d36c8ccc..c6f328e4752e1d 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -424,7 +424,7 @@ bpf_lookup(struct inode *dir, struct dentry *dentry, unsigned flags) return simple_lookup(dir, dentry, flags); } -static int bpf_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int bpf_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *target) { struct inode *inode; diff --git a/mm/shmem.c b/mm/shmem.c index e7635958240662..f633af032a0801 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -4047,7 +4047,7 @@ static int shmem_rename2(const struct mnt_idmap *idmap, return 0; } -static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir, +static int shmem_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { int error; From eb50cbe14ff97ea533708bfc40a74df7af380e5f Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:47 +0200 Subject: [PATCH 0773/2286] fs: port ->create() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-22-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 2 +- fs/affs/affs.h | 2 +- fs/affs/namei.c | 2 +- fs/afs/dir.c | 4 ++-- fs/bad_inode.c | 2 +- fs/bfs/dir.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/dir.c | 2 +- fs/coda/dir.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/efivarfs/inode.c | 2 +- fs/exfat/namei.c | 2 +- fs/ext2/namei.c | 2 +- fs/ext4/namei.c | 2 +- fs/f2fs/namei.c | 2 +- fs/fat/namei_msdos.c | 2 +- fs/fat/namei_vfat.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfs/dir.c | 2 +- fs/hfsplus/dir.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/namei.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/jffs2/dir.c | 4 ++-- fs/jfs/namei.c | 2 +- fs/minix/namei.c | 2 +- fs/nfs/dir.c | 2 +- fs/nfs/internal.h | 2 +- fs/nilfs2/namei.c | 2 +- fs/ntfs/namei.c | 2 +- fs/ntfs3/namei.c | 2 +- fs/ocfs2/dlmfs/dlmfs.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/omfs/dir.c | 2 +- fs/orangefs/namei.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/ramfs/inode.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/dir.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/namei.c | 2 +- fs/ufs/namei.c | 2 +- fs/vboxsf/dir.c | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 2 +- ipc/mqueue.c | 2 +- mm/shmem.c | 2 +- 51 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 856b9cc88c76ed..864768700edacd 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -61,7 +61,7 @@ inode_operations prototypes:: - int (*create) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t); + int (*create) (const struct mnt_idmap *, struct inode *,struct dentry *,umode_t); struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 5a9203e98bb633..2ad273cfe5472a 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -415,7 +415,7 @@ As of kernel 2.6.22, the following members are defined: .. code-block:: c struct inode_operations { - int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, umode_t); + int (*create) (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t); struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index cc52c244bb9558..f2d0eef68acf4a 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -652,7 +652,7 @@ v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir, */ static int -v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index ec58718eec4a82..c7176719a4f92d 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -216,7 +216,7 @@ int v9fs_open_to_dotl_flags(int flags) * */ static int -v9fs_vfs_create_dotl(struct mnt_idmap *idmap, struct inode *dir, +v9fs_vfs_create_dotl(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t omode) { return v9fs_vfs_mknod_dotl(idmap, dir, dentry, omode, 0); diff --git a/fs/affs/affs.h b/fs/affs/affs.h index b1047766a8298d..8d4ca16c7891c8 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -166,7 +166,7 @@ extern const struct export_operations affs_export_ops; extern int affs_hash_name(struct super_block *sb, const u8 *name, unsigned int len); extern struct dentry *affs_lookup(struct inode *dir, struct dentry *dentry, unsigned int); extern int affs_unlink(struct inode *dir, struct dentry *dentry); -extern int affs_create(struct mnt_idmap *idmap, struct inode *dir, +extern int affs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); extern struct dentry *affs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); diff --git a/fs/affs/namei.c b/fs/affs/namei.c index 1d609355fc3f54..2e32899a32d5cc 100644 --- a/fs/affs/namei.c +++ b/fs/affs/namei.c @@ -242,7 +242,7 @@ affs_unlink(struct inode *dir, struct dentry *dentry) } int -affs_create(struct mnt_idmap *idmap, struct inode *dir, +affs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 603dd9323ed22d..63d4f15ae168cf 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -33,7 +33,7 @@ static bool afs_lookup_one_filldir(struct dir_context *ctx, const char *name, in static bool afs_lookup_filldir(struct dir_context *ctx, const char *name, int nlen, u64 ino, u32 uniquifier); #define AFS_LOOKUP ((filldir_t)0x137UL) -static int afs_create(struct mnt_idmap *idmap, struct inode *dir, +static int afs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); static struct dentry *afs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode); @@ -1632,7 +1632,7 @@ static const struct afs_operation_ops afs_create_operation = { /* * create a regular file on an AFS filesystem */ -static int afs_create(struct mnt_idmap *idmap, struct inode *dir, +static int afs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct afs_operation *op; diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 096e6afb5e0782..8c7578ea4bd78f 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -27,7 +27,7 @@ static const struct file_operations bad_file_ops = .open = bad_file_open, }; -static int bad_inode_create(struct mnt_idmap *idmap, +static int bad_inode_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index 80534d04db9dfc..b944bd62f5d025 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -75,7 +75,7 @@ const struct file_operations bfs_dir_operations = { .llseek = generic_file_llseek, }; -static int bfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int bfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { int err; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 9b5e3a67122d84..61cfa03b615299 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7018,7 +7018,7 @@ static int btrfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, return btrfs_create_common(dir, dentry, inode); } -static int btrfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int btrfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c index 5417a9948e9867..d9615d67bf1c01 100644 --- a/fs/ceph/dir.c +++ b/fs/ceph/dir.c @@ -988,7 +988,7 @@ static int ceph_mknod(const struct mnt_idmap *idmap, struct inode *dir, return err; } -static int ceph_create(struct mnt_idmap *idmap, struct inode *dir, +static int ceph_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ceph_mknod(idmap, dir, dentry, mode, 0); diff --git a/fs/coda/dir.c b/fs/coda/dir.c index 02b8259b6a0eb5..a85be5962e6264 100644 --- a/fs/coda/dir.c +++ b/fs/coda/dir.c @@ -133,7 +133,7 @@ static inline void coda_dir_drop_nlink(struct inode *dir) } /* creation routines: create, mknod, mkdir, link, symlink */ -static int coda_create(struct mnt_idmap *idmap, struct inode *dir, +static int coda_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *de, umode_t mode) { int error; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index f9a3c88c4fbd06..432f82bc5b0c90 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -266,7 +266,7 @@ int ecryptfs_initialize_file(struct dentry *ecryptfs_dentry, * Returns zero on success; non-zero on error condition */ static int -ecryptfs_create(struct mnt_idmap *idmap, +ecryptfs_create(const struct mnt_idmap *idmap, struct inode *directory_inode, struct dentry *ecryptfs_dentry, umode_t mode) { diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index f7dcd72e4a0771..d87091b9277655 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -74,7 +74,7 @@ static bool efivarfs_valid_name(const char *str, int len) return uuid_is_valid(s); } -static int efivarfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int efivarfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode = NULL; diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 4accf96ea7f951..5e7836af63b80b 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -552,7 +552,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, return ret; } -static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, +static int exfat_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 7da42f071eab89..bfb6a463a95e06 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -97,7 +97,7 @@ struct dentry *ext2_get_parent(struct dentry *child) * If the create succeeds, we fill in the inode information * with d_instantiate(). */ -static int ext2_create (struct mnt_idmap * idmap, +static int ext2_create (const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, umode_t mode) { diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 6e493b4d205f2b..6e0630a49e4863 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2812,7 +2812,7 @@ static int ext4_add_nondir(handle_t *handle, * If the create succeeds, we fill in the inode information * with d_instantiate(). */ -static int ext4_create(struct mnt_idmap *idmap, struct inode *dir, +static int ext4_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { handle_t *handle; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index b2e99822d8b45d..9934777eb2a9fe 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -365,7 +365,7 @@ static struct inode *f2fs_new_inode(const struct mnt_idmap *idmap, return ERR_PTR(err); } -static int f2fs_create(struct mnt_idmap *idmap, struct inode *dir, +static int f2fs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct f2fs_sb_info *sbi = F2FS_I_SB(dir); diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 9ee899391f4f6c..dde4215616f943 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -263,7 +263,7 @@ static int msdos_add_entry(struct inode *dir, const unsigned char *name, } /***** Create a file */ -static int msdos_create(struct mnt_idmap *idmap, struct inode *dir, +static int msdos_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index a3f3320fe63041..3dc063ba0a738c 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -753,7 +753,7 @@ static struct dentry *vfat_lookup(struct inode *dir, struct dentry *dentry, return ERR_PTR(err); } -static int vfat_create(struct mnt_idmap *idmap, struct inode *dir, +static int vfat_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct super_block *sb = dir->i_sb; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 3d8246ff061e49..ff941e78918ffe 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1092,7 +1092,7 @@ static int fuse_mknod(const struct mnt_idmap *idmap, struct inode *dir, return create_new_nondir(idmap, fm, &args, dir, entry, mode); } -static int fuse_create(struct mnt_idmap *idmap, struct inode *dir, +static int fuse_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *entry, umode_t mode) { return fuse_mknod(idmap, dir, entry, mode, 0); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index c8f2b01e8cf53c..e207ab1f5c23d0 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -967,7 +967,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry, * Returns: errno */ -static int gfs2_create(struct mnt_idmap *idmap, struct inode *dir, +static int gfs2_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return gfs2_create_inode(dir, dentry, NULL, S_IFREG | mode, 0, NULL, 0, 1); diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index d60399c62e992c..f6b97da197887d 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -183,7 +183,7 @@ static int hfs_dir_release(struct inode *inode, struct file *file) * a directory and return a corresponding inode, given the inode for * the directory and the name (and its length) of the new file. */ -static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int hfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index 1b59ddb6d1e173..b3a1193a491f5a 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -561,7 +561,7 @@ static int hfsplus_mknod(const struct mnt_idmap *idmap, struct inode *dir, return res; } -static int hfsplus_create(struct mnt_idmap *idmap, struct inode *dir, +static int hfsplus_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return hfsplus_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index ef03763aa07474..c8ad58be047e07 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -592,7 +592,7 @@ static struct inode *hostfs_iget(struct super_block *sb, char *name) return inode; } -static int hostfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int hostfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c index 44a7fb79763314..ac9b5e3e83fa0b 100644 --- a/fs/hpfs/namei.c +++ b/fs/hpfs/namei.c @@ -128,7 +128,7 @@ static struct dentry *hpfs_mkdir(const struct mnt_idmap *idmap, struct inode *di return ERR_PTR(err); } -static int hpfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int hpfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { const unsigned char *name = dentry->d_name.name; diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 9e937602b2ec85..0b95be14d3ee18 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -977,7 +977,7 @@ static struct dentry *hugetlbfs_mkdir(const struct mnt_idmap *idmap, struct inod return ERR_PTR(retval); } -static int hugetlbfs_create(struct mnt_idmap *idmap, +static int hugetlbfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 7d23be0bab728d..23813f1912818d 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -25,7 +25,7 @@ static int jffs2_readdir (struct file *, struct dir_context *); -static int jffs2_create (struct mnt_idmap *, struct inode *, +static int jffs2_create (const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); static struct dentry *jffs2_lookup (struct inode *,struct dentry *, unsigned int); @@ -162,7 +162,7 @@ static int jffs2_readdir(struct file *file, struct dir_context *ctx) /***********************************************************************/ -static int jffs2_create(struct mnt_idmap *idmap, struct inode *dir_i, +static int jffs2_create(const struct mnt_idmap *idmap, struct inode *dir_i, struct dentry *dentry, umode_t mode) { struct jffs2_raw_inode *ri; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 6009d65ab53f07..8ab2e952ce16b0 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -60,7 +60,7 @@ static inline void free_ea_wmap(struct inode *inode) * RETURN: Errors from subroutines * */ -static int jfs_create(struct mnt_idmap *idmap, struct inode *dip, +static int jfs_create(const struct mnt_idmap *idmap, struct inode *dip, struct dentry *dentry, umode_t mode) { int rc = 0; diff --git a/fs/minix/namei.c b/fs/minix/namei.c index 11b66a25e92bd5..f450b11b98604a 100644 --- a/fs/minix/namei.c +++ b/fs/minix/namei.c @@ -63,7 +63,7 @@ static int minix_tmpfile(const struct mnt_idmap *idmap, struct inode *dir, return finish_open_simple(file, 0); } -static int minix_create(struct mnt_idmap *idmap, struct inode *dir, +static int minix_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return minix_mknod(&nop_mnt_idmap, dir, dentry, mode, 0); diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index a917c008a05a63..354f986e60c42a 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2437,7 +2437,7 @@ static int nfs_do_create(struct inode *dir, struct dentry *dentry, return error; } -int nfs_create(struct mnt_idmap *idmap, struct inode *dir, +int nfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return nfs_do_create(dir, dentry, mode, O_EXCL); diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 4c966dac7f7d98..cbb323c6530d4a 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -396,7 +396,7 @@ extern unsigned long nfs_access_cache_scan(struct shrinker *shrink, struct shrink_control *sc); struct dentry *nfs_lookup(struct inode *, struct dentry *, unsigned int); void nfs_d_prune_case_insensitive_aliases(struct inode *inode); -int nfs_create(struct mnt_idmap *, struct inode *, struct dentry *, +int nfs_create(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); struct dentry *nfs_mkdir(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t); diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index 8762fdc16c057f..d0ae24f378545c 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -85,7 +85,7 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) * If the create succeeds, we fill in the inode information * with d_instantiate(). */ -static int nilfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int nilfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 637bc3690844dc..5d222edadf0ccc 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -732,7 +732,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in return ERR_PTR(err); } -static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct ntfs_volume *vol = NTFS_SB(dir->i_sb); diff --git a/fs/ntfs3/namei.c b/fs/ntfs3/namei.c index 289796da6ba545..ae88cb66fb7e74 100644 --- a/fs/ntfs3/namei.c +++ b/fs/ntfs3/namei.c @@ -111,7 +111,7 @@ static struct dentry *ntfs_lookup(struct inode *dir, struct dentry *dentry, /* * ntfs_create - inode_operations::create */ -static int ntfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ntfs_create_inode(idmap, dir, dentry, NULL, S_IFREG | mode, 0, diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 5e544d8ded4254..fef79b21a4a53c 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -450,7 +450,7 @@ static struct dentry *dlmfs_mkdir(const struct mnt_idmap * idmap, return ERR_PTR(status); } -static int dlmfs_create(struct mnt_idmap *idmap, +static int dlmfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index dfa2cb2bcb12fd..4873525be3b16b 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -671,7 +671,7 @@ static struct dentry *ocfs2_mkdir(const struct mnt_idmap *idmap, return ERR_PTR(ret); } -static int ocfs2_create(struct mnt_idmap *idmap, +static int ocfs2_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/fs/omfs/dir.c b/fs/omfs/dir.c index a476675e8e6197..18f4b4543cc81a 100644 --- a/fs/omfs/dir.c +++ b/fs/omfs/dir.c @@ -285,7 +285,7 @@ static struct dentry *omfs_mkdir(const struct mnt_idmap *idmap, struct inode *di return ERR_PTR(omfs_add_node(dir, dentry, mode)); } -static int omfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int omfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return omfs_add_node(dir, dentry, mode | S_IFREG); diff --git a/fs/orangefs/namei.c b/fs/orangefs/namei.c index f5deb8cdbf2f60..32b7769ea49cea 100644 --- a/fs/orangefs/namei.c +++ b/fs/orangefs/namei.c @@ -15,7 +15,7 @@ /* * Get a newly allocated inode to go with a negative dentry. */ -static int orangefs_create(struct mnt_idmap *idmap, +static int orangefs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index f7a3fd3a25b93c..1194ccf981c37b 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -730,7 +730,7 @@ static int ovl_create_object(const struct mnt_idmap *idmap, struct dentry *dentr return err; } -static int ovl_create(struct mnt_idmap *idmap, struct inode *dir, +static int ovl_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ovl_create_object(idmap, dentry, (mode & 07777) | S_IFREG, 0, NULL); diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index cc06d806b2146b..ef91b933d4e610 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -127,7 +127,7 @@ static struct dentry *ramfs_mkdir(const struct mnt_idmap *idmap, struct inode *d return ERR_PTR(retval); } -static int ramfs_create(struct mnt_idmap *idmap, struct inode *dir, +static int ramfs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return ramfs_mknod(&nop_mnt_idmap, dir, dentry, mode | S_IFREG, 0); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 483d2b23fc233f..cf09d915beddc4 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -53,7 +53,7 @@ void cifs_sb_deactive(struct super_block *sb); /* Functions related to inodes */ extern const struct inode_operations cifs_dir_inode_ops; struct inode *cifs_root_iget(struct super_block *sb); -int cifs_create(struct mnt_idmap *idmap, struct inode *dir, +int cifs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *direntry, umode_t mode); int cifs_atomic_open(struct inode *dir, struct dentry *direntry, struct file *file, unsigned int oflags, umode_t mode); diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 95da43ad7c02a0..52d30ebe67f374 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -640,7 +640,7 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry, * The initial dentry state is hashed-negative. On success, dentry will become * hashed-positive by calling d_instantiate(). */ -int cifs_create(struct mnt_idmap *idmap, struct inode *dir, +int cifs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *direntry, umode_t mode) { struct cifs_sb_info *cifs_sb = CIFS_SB(dir); diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index d784283d427f7c..57640b6ff72d51 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -302,7 +302,7 @@ static int ubifs_prepare_create(struct inode *dir, struct dentry *dentry, return fscrypt_setup_filename(dir, &dentry->d_name, 0, nm); } -static int ubifs_create(struct mnt_idmap *idmap, struct inode *dir, +static int ubifs_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode; diff --git a/fs/udf/namei.c b/fs/udf/namei.c index 002e79052f926d..42d000fe9d6bc5 100644 --- a/fs/udf/namei.c +++ b/fs/udf/namei.c @@ -370,7 +370,7 @@ static int udf_add_nondir(struct dentry *dentry, struct inode *inode) return 0; } -static int udf_create(struct mnt_idmap *idmap, struct inode *dir, +static int udf_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct inode *inode = udf_new_inode(dir, mode); diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index 9231a5cda08ebe..d45347a2574196 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -69,7 +69,7 @@ static struct dentry *ufs_lookup(struct inode * dir, struct dentry *dentry, unsi * If the create succeeds, we fill in the inode information * with d_instantiate(). */ -static int ufs_create (struct mnt_idmap * idmap, +static int ufs_create (const struct mnt_idmap * idmap, struct inode * dir, struct dentry * dentry, umode_t mode) { struct inode *inode; diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 9db16e90f670f3..b9c46c769c4e6d 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -296,7 +296,7 @@ static int vboxsf_dir_create(struct inode *parent, struct dentry *dentry, return err; } -static int vboxsf_dir_mkfile(struct mnt_idmap *idmap, +static int vboxsf_dir_mkfile(const struct mnt_idmap *idmap, struct inode *parent, struct dentry *dentry, umode_t mode) { diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index fe388619a13813..791af2db71240b 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -290,7 +290,7 @@ xfs_vn_mknod( STATIC int xfs_vn_create( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/include/linux/fs.h b/include/linux/fs.h index f2c7838644c366..2b9ae72010f01f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1999,7 +1999,7 @@ struct inode_operations { int (*readlink) (struct dentry *, char __user *,int); - int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, + int (*create) (const struct mnt_idmap *, struct inode *,struct dentry *, umode_t); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); diff --git a/ipc/mqueue.c b/ipc/mqueue.c index d1dd36a651b0d0..e3a1f3b6d00f9c 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -607,7 +607,7 @@ static int mqueue_create_attr(struct dentry *dentry, umode_t mode, void *arg) return error; } -static int mqueue_create(struct mnt_idmap *idmap, struct inode *dir, +static int mqueue_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return mqueue_create_attr(dentry, mode, NULL); diff --git a/mm/shmem.c b/mm/shmem.c index f633af032a0801..656235b2f9c58a 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -3897,7 +3897,7 @@ static struct dentry *shmem_mkdir(const struct mnt_idmap *idmap, struct inode *d return NULL; } -static int shmem_create(struct mnt_idmap *idmap, struct inode *dir, +static int shmem_create(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { return shmem_mknod(idmap, dir, dentry, mode | S_IFREG, 0); From c3150ded7bc0e66e2325ab066ab6a1cfa3140b6c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:48 +0200 Subject: [PATCH 0774/2286] fs: port ->getattr() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-23-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 2 +- fs/afs/inode.c | 2 +- fs/afs/internal.h | 2 +- fs/anon_inodes.c | 2 +- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/inode.c | 4 ++-- fs/ceph/super.h | 2 +- fs/coda/coda_linux.h | 2 +- fs/coda/inode.c | 2 +- fs/ecryptfs/inode.c | 4 ++-- fs/erofs/inode.c | 2 +- fs/erofs/internal.h | 2 +- fs/exfat/exfat_fs.h | 2 +- fs/exfat/file.c | 2 +- fs/ext2/ext2.h | 2 +- fs/ext2/inode.c | 2 +- fs/ext4/ext4.h | 4 ++-- fs/ext4/inode.c | 4 ++-- fs/ext4/symlink.c | 2 +- fs/f2fs/f2fs.h | 2 +- fs/f2fs/file.c | 2 +- fs/f2fs/namei.c | 2 +- fs/failfs.c | 2 +- fs/fat/fat.h | 2 +- fs/fat/file.c | 2 +- fs/fuse/dir.c | 4 ++-- fs/gfs2/inode.c | 2 +- fs/hfsplus/hfsplus_fs.h | 2 +- fs/hfsplus/inode.c | 2 +- fs/internal.h | 2 +- fs/kernfs/inode.c | 2 +- fs/kernfs/kernfs-internal.h | 2 +- fs/libfs.c | 2 +- fs/minix/inode.c | 2 +- fs/minix/minix.h | 2 +- fs/nfs/inode.c | 2 +- fs/nfs/namespace.c | 2 +- fs/ntfs/file.c | 2 +- fs/ntfs/inode.h | 2 +- fs/ntfs3/file.c | 2 +- fs/ntfs3/ntfs_fs.h | 2 +- fs/ocfs2/file.c | 2 +- fs/ocfs2/file.h | 2 +- fs/orangefs/inode.c | 2 +- fs/orangefs/orangefs-kernel.h | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/pidfs.c | 2 +- fs/proc/base.c | 4 ++-- fs/proc/fd.c | 2 +- fs/proc/generic.c | 2 +- fs/proc/internal.h | 2 +- fs/proc/proc_net.c | 2 +- fs/proc/proc_sysctl.c | 2 +- fs/proc/root.c | 2 +- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 2 +- fs/stat.c | 2 +- fs/tracefs/inode.c | 2 +- fs/ubifs/dir.c | 2 +- fs/ubifs/file.c | 2 +- fs/ubifs/ubifs.h | 2 +- fs/udf/symlink.c | 2 +- fs/vboxsf/utils.c | 2 +- fs/vboxsf/vfsmod.h | 2 +- fs/xfs/xfs_iops.c | 2 +- include/linux/fs.h | 6 +++--- include/linux/nfs_fs.h | 2 +- mm/shmem.c | 2 +- 73 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index 864768700edacd..f11bfc2e0c137e 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -77,7 +77,7 @@ prototypes:: int (*permission) (const struct mnt_idmap *, struct inode *, int, unsigned int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); - int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); + int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); void (*update_time)(struct inode *inode, enum fs_update_time type, diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 2ad273cfe5472a..98aefc5d399ed5 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -431,7 +431,7 @@ As of kernel 2.6.22, the following members are defined: int (*permission) (const struct mnt_idmap *, struct inode *, int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); - int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); + int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); void (*update_time)(struct inode *inode, enum fs_update_time type, int flags); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index f2d0eef68acf4a..0c280342f8e982 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -966,7 +966,7 @@ v9fs_vfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir, */ static int -v9fs_vfs_getattr(struct mnt_idmap *idmap, const struct path *path, +v9fs_vfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct dentry *dentry = path->dentry; diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index c7176719a4f92d..e37854ee6346e3 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -414,7 +414,7 @@ static struct dentry *v9fs_vfs_mkdir_dotl(const struct mnt_idmap *idmap, } static int -v9fs_vfs_getattr_dotl(struct mnt_idmap *idmap, +v9fs_vfs_getattr_dotl(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/afs/inode.c b/fs/afs/inode.c index 14f39a9bea6cfe..7b8a2adc3f599b 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -596,7 +596,7 @@ struct inode *afs_root_iget(struct super_block *sb, struct key *key) /* * read the attributes of an inode */ -int afs_getattr(struct mnt_idmap *idmap, const struct path *path, +int afs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/afs/internal.h b/fs/afs/internal.h index e772da2a15f29a..7a8cdde9e0ad46 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1258,7 +1258,7 @@ extern int afs_fetch_status(struct afs_vnode *, struct key *, bool, afs_access_t extern int afs_ilookup5_test_by_fid(struct inode *, void *); extern struct inode *afs_iget(struct afs_operation *, struct afs_vnode_param *); extern struct inode *afs_root_iget(struct super_block *, struct key *); -extern int afs_getattr(struct mnt_idmap *idmap, const struct path *, +extern int afs_getattr(const struct mnt_idmap *idmap, const struct path *, struct kstat *, u32, unsigned int); extern int afs_setattr(struct mnt_idmap *idmap, struct dentry *, struct iattr *); extern void afs_evict_inode(struct inode *); diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c index a7b9b948e33d11..d4a7e0b672896e 100644 --- a/fs/anon_inodes.c +++ b/fs/anon_inodes.c @@ -46,7 +46,7 @@ static struct inode *anon_inode_inode __ro_after_init; * Rather than mess with our internal sane inode data, just fix it * up here in getattr() by masking off the format bits. */ -int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path, +int anon_inode_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/bad_inode.c b/fs/bad_inode.c index 8c7578ea4bd78f..e740b4e658bfae 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -95,7 +95,7 @@ static int bad_inode_permission(const struct mnt_idmap *idmap, return -EIO; } -static int bad_inode_getattr(struct mnt_idmap *idmap, +static int bad_inode_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 61cfa03b615299..da143f9376eede 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8224,7 +8224,7 @@ int __init btrfs_init_cachep(void) return 0; } -static int btrfs_getattr(struct mnt_idmap *idmap, +static int btrfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index b8527f67e3f0a5..185d713218e60b 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2398,7 +2398,7 @@ static const char *ceph_encrypted_get_link(struct dentry *dentry, done); } -static int ceph_encrypted_symlink_getattr(struct mnt_idmap *idmap, +static int ceph_encrypted_symlink_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) @@ -3145,7 +3145,7 @@ static int statx_to_caps(u32 want, umode_t mode) * Get all the attributes. If we have sufficient caps for the requested attrs, * then we can avoid talking to the MDS at all. */ -int ceph_getattr(struct mnt_idmap *idmap, const struct path *path, +int ceph_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index fd603be209e43c..0a5e3916e855e3 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1177,7 +1177,7 @@ extern int __ceph_setattr(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *attr, struct ceph_iattr *cia); extern int ceph_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -extern int ceph_getattr(struct mnt_idmap *idmap, +extern int ceph_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); void ceph_inode_shutdown(struct inode *inode); diff --git a/fs/coda/coda_linux.h b/fs/coda/coda_linux.h index 5ee6cedd38650d..5dc9e392079fd2 100644 --- a/fs/coda/coda_linux.h +++ b/fs/coda/coda_linux.h @@ -49,7 +49,7 @@ int coda_release(struct inode *i, struct file *f); int coda_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int coda_revalidate_inode(struct inode *); -int coda_getattr(struct mnt_idmap *, const struct path *, struct kstat *, +int coda_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); int coda_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); diff --git a/fs/coda/inode.c b/fs/coda/inode.c index 40b43866e6a549..f83ebd94c6be40 100644 --- a/fs/coda/inode.c +++ b/fs/coda/inode.c @@ -294,7 +294,7 @@ static void coda_evict_inode(struct inode *inode) coda_cache_clear_inode(inode); } -int coda_getattr(struct mnt_idmap *idmap, const struct path *path, +int coda_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { int err = coda_revalidate_inode(d_inode(path->dentry)); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 432f82bc5b0c90..e70a8d73d137a1 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -939,7 +939,7 @@ static int ecryptfs_setattr(struct mnt_idmap *idmap, return rc; } -static int ecryptfs_getattr_link(struct mnt_idmap *idmap, +static int ecryptfs_getattr_link(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { @@ -965,7 +965,7 @@ static int ecryptfs_getattr_link(struct mnt_idmap *idmap, return rc; } -static int ecryptfs_getattr(struct mnt_idmap *idmap, +static int ecryptfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c index 45afe5c50de830..26ea3790ff2134 100644 --- a/fs/erofs/inode.c +++ b/fs/erofs/inode.c @@ -311,7 +311,7 @@ struct inode *erofs_iget(struct super_block *sb, erofs_nid_t nid) return inode; } -int erofs_getattr(struct mnt_idmap *idmap, const struct path *path, +int erofs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 65974e57aebfba..b4f52f5cd62627 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -421,7 +421,7 @@ void erofs_onlinefolio_init(struct folio *folio); void erofs_onlinefolio_split(struct folio *folio); void erofs_onlinefolio_end(struct folio *folio, int err, bool dirty); struct inode *erofs_iget(struct super_block *sb, erofs_nid_t nid); -int erofs_getattr(struct mnt_idmap *idmap, const struct path *path, +int erofs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); int erofs_namei(struct inode *dir, const struct qstr *name, diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index a9131fe03302e5..5165a0df068adc 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -557,7 +557,7 @@ extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode); int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, +int exfat_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); struct file_kattr; diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 0530803f71e858..c072a0663e69e6 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -319,7 +319,7 @@ static void exfat_truncate(struct inode *inode) mutex_unlock(&sbi->s_lock); } -int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, +int exfat_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) { diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index 702cb7aa1afd01..f4bb8b888052c9 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -740,7 +740,7 @@ extern void ext2_evict_inode(struct inode *); void ext2_write_failed(struct address_space *mapping, loff_t to); extern int ext2_get_block(struct inode *, sector_t, struct buffer_head *, int); extern int ext2_setattr (struct mnt_idmap *, struct dentry *, struct iattr *); -extern int ext2_getattr (struct mnt_idmap *, const struct path *, +extern int ext2_getattr (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void ext2_set_inode_flags(struct inode *inode); extern int ext2_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index a9245f0cda4d46..d4b2a2dec35e07 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1591,7 +1591,7 @@ int ext2_sync_inode_metadata(struct inode *inode, struct writeback_control *wbc) return err; } -int ext2_getattr(struct mnt_idmap *idmap, const struct path *path, +int ext2_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 2c9a845ed0f83b..eb7cf19f606ada 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3182,11 +3182,11 @@ extern int ext4_sync_inode_metadata(struct inode *, struct writeback_control *) extern int ext4_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); extern u32 ext4_dio_alignment(struct inode *inode); -extern int ext4_getattr(struct mnt_idmap *, const struct path *, +extern int ext4_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void ext4_evict_inode(struct inode *); extern void ext4_clear_inode(struct inode *); -extern int ext4_file_getattr(struct mnt_idmap *, const struct path *, +extern int ext4_file_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void ext4_dirty_inode(struct inode *, int); extern int ext4_change_inode_journal_flag(struct inode *, int); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index bd4b778df9eb27..045f5385f26e28 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6263,7 +6263,7 @@ u32 ext4_dio_alignment(struct inode *inode) return 1; /* use the iomap defaults */ } -int ext4_getattr(struct mnt_idmap *idmap, const struct path *path, +int ext4_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); @@ -6332,7 +6332,7 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path, return 0; } -int ext4_file_getattr(struct mnt_idmap *idmap, +int ext4_file_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index b612262719ede3..e680d1e45b4775 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -55,7 +55,7 @@ static const char *ext4_encrypted_get_link(struct dentry *dentry, return paddr; } -static int ext4_encrypted_symlink_getattr(struct mnt_idmap *idmap, +static int ext4_encrypted_symlink_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 245df8563256f0..06538f5fd72509 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3848,7 +3848,7 @@ int f2fs_sync_file(struct file *file, loff_t start, loff_t end, int datasync); int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock); int f2fs_truncate_blocks(struct inode *inode, u64 from, bool lock); int f2fs_truncate(struct inode *inode); -int f2fs_getattr(struct mnt_idmap *idmap, const struct path *path, +int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index d0cc2ca1397a9a..267a3a191d5479 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1032,7 +1032,7 @@ static bool f2fs_force_buffered_io(struct inode *inode, int rw) return false; } -int f2fs_getattr(struct mnt_idmap *idmap, const struct path *path, +int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 9934777eb2a9fe..233c07fec1719f 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -1394,7 +1394,7 @@ static const char *f2fs_encrypted_get_link(struct dentry *dentry, return target; } -static int f2fs_encrypted_symlink_getattr(struct mnt_idmap *idmap, +static int f2fs_encrypted_symlink_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) diff --git a/fs/failfs.c b/fs/failfs.c index cec6cad99eb212..437cdc981c2ded 100644 --- a/fs/failfs.c +++ b/fs/failfs.c @@ -35,7 +35,7 @@ static struct dentry *failfs_lookup(struct inode *dir, struct dentry *dentry, return ERR_PTR(-EOPNOTSUPP); } -static int failfs_getattr(struct mnt_idmap *idmap, const struct path *path, +static int failfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/fat/fat.h b/fs/fat/fat.h index 61338413d9f3e4..202f209a544b46 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -407,7 +407,7 @@ extern const struct inode_operations fat_file_inode_operations; extern int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern void fat_truncate_blocks(struct inode *inode, loff_t offset); -extern int fat_getattr(struct mnt_idmap *idmap, +extern int fat_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa); diff --git a/fs/fat/file.c b/fs/fat/file.c index 0b8b493d06e497..b37bc637945f90 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -432,7 +432,7 @@ int fat_fileattr_get(struct dentry *dentry, struct file_kattr *fa) } EXPORT_SYMBOL_GPL(fat_fileattr_get); -int fat_getattr(struct mnt_idmap *idmap, const struct path *path, +int fat_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index ff941e78918ffe..dd267d9498bc5e 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1537,7 +1537,7 @@ static int fuse_do_getattr(const struct mnt_idmap *idmap, struct inode *inode, return err; } -static int fuse_update_get_attr(struct mnt_idmap *idmap, struct inode *inode, +static int fuse_update_get_attr(const struct mnt_idmap *idmap, struct inode *inode, struct file *file, struct kstat *stat, u32 request_mask, unsigned int flags) { @@ -2387,7 +2387,7 @@ static int fuse_setattr(struct mnt_idmap *idmap, struct dentry *entry, return ret; } -static int fuse_getattr(struct mnt_idmap *idmap, +static int fuse_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index e207ab1f5c23d0..5ddac2f50f30db 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -2156,7 +2156,7 @@ static int gfs2_setattr(struct mnt_idmap *idmap, * Returns: errno */ -static int gfs2_getattr(struct mnt_idmap *idmap, +static int gfs2_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 86fc1c1cdfd6b1..d55cb16e3897f1 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -459,7 +459,7 @@ void hfsplus_inode_write_fork(struct inode *inode, struct hfsplus_fork_raw *fork); int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd); int hfsplus_cat_write_inode(struct inode *inode); -int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path, +int hfsplus_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 9821662bfc1d4c..2807b29c7028d5 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -335,7 +335,7 @@ static int hfsplus_setattr(struct mnt_idmap *idmap, return 0; } -int hfsplus_getattr(struct mnt_idmap *idmap, const struct path *path, +int hfsplus_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/internal.h b/fs/internal.h index 8a1c7c05c5a28b..88d9296c2a482f 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -356,7 +356,7 @@ void file_f_owner_release(struct file *file); bool file_seek_cur_needs_f_lock(struct file *file); int statmount_mnt_idmap(const struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map); struct dentry *find_next_child(struct dentry *parent, struct dentry *prev); -int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path, +int anon_inode_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index b348d4cd150bb4..6a92aa789ce5aa 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -181,7 +181,7 @@ static void kernfs_refresh_inode(struct kernfs_node *kn, struct inode *inode) set_nlink(inode, kn->dir.subdirs + 2); } -int kernfs_iop_getattr(struct mnt_idmap *idmap, +int kernfs_iop_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 0f6f412213f6bb..8af7849f2c9cc0 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -175,7 +175,7 @@ int kernfs_iop_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); int kernfs_iop_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); -int kernfs_iop_getattr(struct mnt_idmap *idmap, +int kernfs_iop_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); ssize_t kernfs_iop_listxattr(struct dentry *dentry, char *buf, size_t size); diff --git a/fs/libfs.c b/fs/libfs.c index 8b379230b64fee..b70977b10a7c6f 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -29,7 +29,7 @@ #include "internal.h" -int simple_getattr(struct mnt_idmap *idmap, const struct path *path, +int simple_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/minix/inode.c b/fs/minix/inode.c index daf83e4ff25c81..670179173645b3 100644 --- a/fs/minix/inode.c +++ b/fs/minix/inode.c @@ -724,7 +724,7 @@ static int minix_sync_inode_metadata(struct inode *inode, return err; } -int minix_getattr(struct mnt_idmap *idmap, const struct path *path, +int minix_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct super_block *sb = path->dentry->d_sb; diff --git a/fs/minix/minix.h b/fs/minix/minix.h index 78722ce22e1e19..db92cf9e0e1bf3 100644 --- a/fs/minix/minix.h +++ b/fs/minix/minix.h @@ -55,7 +55,7 @@ unsigned long minix_count_free_inodes(struct super_block *sb); int minix_new_block(struct inode *inode); void minix_free_block(struct inode *inode, unsigned long block); unsigned long minix_count_free_blocks(struct super_block *sb); -int minix_getattr(struct mnt_idmap *, const struct path *, +int minix_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); int minix_prepare_chunk(struct folio *folio, loff_t pos, unsigned len); struct mapping_metadata_bhs *minix_get_metadata_bhs(struct inode *inode); diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 3022454f7698ac..b114488715fbae 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -955,7 +955,7 @@ static u32 nfs_get_valid_attrmask(struct inode *inode) return reply_mask; } -int nfs_getattr(struct mnt_idmap *idmap, const struct path *path, +int nfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 6d0073c24771bc..17f54d79efa75a 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -222,7 +222,7 @@ struct vfsmount *nfs_d_automount(struct path *path) } static int -nfs_namespace_getattr(struct mnt_idmap *idmap, +nfs_namespace_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 88747217ba6129..40e01704ea8fcf 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -364,7 +364,7 @@ int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, return err; } -int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) { diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index ff61bd402df033..9cf9239118258f 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -332,7 +332,7 @@ int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size); int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index f7baa202dc28f7..f4d08346d25d22 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -261,7 +261,7 @@ long ntfs_compat_ioctl(struct file *filp, u32 cmd, unsigned long arg) /* * ntfs_getattr - inode_operations::getattr */ -int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, u32 flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index d89609a17886b2..262b7ff2d6e169 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -553,7 +553,7 @@ extern const struct file_operations ntfs_dir_operations; int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); -int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, u32 flags); int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index 24bbf607546cfd..befc6ebb2a0638 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1317,7 +1317,7 @@ int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, return status; } -int ocfs2_getattr(struct mnt_idmap *idmap, const struct path *path, +int ocfs2_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); diff --git a/fs/ocfs2/file.h b/fs/ocfs2/file.h index 08e4d679653177..b51c8e77dad791 100644 --- a/fs/ocfs2/file.h +++ b/fs/ocfs2/file.h @@ -52,7 +52,7 @@ int ocfs2_zero_extend(struct inode *inode, struct buffer_head *di_bh, loff_t zero_to); int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -int ocfs2_getattr(struct mnt_idmap *idmap, const struct path *path, +int ocfs2_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int ocfs2_permission(const struct mnt_idmap *idmap, struct inode *inode, diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 536d5e842c55ec..100669ee5862f9 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -848,7 +848,7 @@ int orangefs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, /* * Obtain attributes of an object given a dentry */ -int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path, +int orangefs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { int ret; diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h index a5c9e7be4005d4..3bf01c0c763b3b 100644 --- a/fs/orangefs/orangefs-kernel.h +++ b/fs/orangefs/orangefs-kernel.h @@ -354,7 +354,7 @@ int __orangefs_setattr(struct inode *, struct iattr *); int __orangefs_setattr_mode(struct dentry *dentry, struct iattr *iattr); int orangefs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); -int orangefs_getattr(struct mnt_idmap *idmap, const struct path *path, +int orangefs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int orangefs_permission(const struct mnt_idmap *idmap, diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 18e4cbe154cf36..b765780686e65d 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -168,7 +168,7 @@ static inline int ovl_real_getattr_nosec(struct super_block *sb, return vfs_getattr_nosec(path, stat, request_mask, flags); } -int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, +int ovl_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct dentry *dentry = path->dentry; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 1759221dcad5d9..02a822dc8e5e36 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -950,6 +950,6 @@ static inline bool ovl_force_readonly(struct ovl_fs *ofs) const struct xattr_handler * const *ovl_xattr_handlers(struct ovl_fs *ofs); int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -int ovl_getattr(struct mnt_idmap *idmap, const struct path *path, +int ovl_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size); diff --git a/fs/pidfs.c b/fs/pidfs.c index 5bc136de866047..38868ac9995090 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -829,7 +829,7 @@ static int pidfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, return anon_inode_setattr(idmap, dentry, attr); } -static int pidfs_getattr(struct mnt_idmap *idmap, const struct path *path, +static int pidfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/base.c b/fs/proc/base.c index 8dc43c84629810..3278c37e5cde23 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -1994,7 +1994,7 @@ static struct inode *proc_pid_make_base_inode(struct super_block *sb, return inode; } -int pid_getattr(struct mnt_idmap *idmap, const struct path *path, +int pid_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); @@ -3936,7 +3936,7 @@ static int proc_task_readdir(struct file *file, struct dir_context *ctx) return 0; } -static int proc_task_getattr(struct mnt_idmap *idmap, +static int proc_task_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/fd.c b/fs/proc/fd.c index 0653bceb730db4..7214a749538015 100644 --- a/fs/proc/fd.c +++ b/fs/proc/fd.c @@ -342,7 +342,7 @@ int proc_fd_permission(const struct mnt_idmap *idmap, return rv; } -static int proc_fd_getattr(struct mnt_idmap *idmap, +static int proc_fd_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 26086a283672bf..4c4f1e63f68fe1 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -135,7 +135,7 @@ static int proc_setattr(struct mnt_idmap *idmap, struct dentry *dentry, return 0; } -static int proc_getattr(struct mnt_idmap *idmap, +static int proc_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 04bd6c9e65a722..706fae7d30be46 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -258,7 +258,7 @@ extern int proc_pid_statm(struct seq_file *, struct pid_namespace *, * base.c */ extern const struct dentry_operations pid_dentry_operations; -extern int pid_getattr(struct mnt_idmap *, const struct path *, +extern int pid_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c index 00cc385bce2121..b1f5eafb069a3c 100644 --- a/fs/proc/proc_net.c +++ b/fs/proc/proc_net.c @@ -308,7 +308,7 @@ static struct dentry *proc_tgid_net_lookup(struct inode *dir, return de; } -static int proc_tgid_net_getattr(struct mnt_idmap *idmap, +static int proc_tgid_net_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 5315d891f24467..bd6730000b27ac 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -834,7 +834,7 @@ static int proc_sys_setattr(struct mnt_idmap *idmap, return 0; } -static int proc_sys_getattr(struct mnt_idmap *idmap, +static int proc_sys_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/proc/root.c b/fs/proc/root.c index 99adddfeb4a44c..7fbbe92bf73a71 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -402,7 +402,7 @@ void __init proc_root_init(void) register_filesystem(&proc_fs_type); } -static int proc_root_getattr(struct mnt_idmap *idmap, +static int proc_root_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index cf09d915beddc4..c0adc52192130b 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -78,7 +78,7 @@ int cifs_revalidate_file(struct file *filp); int cifs_revalidate_dentry(struct dentry *dentry); int cifs_revalidate_mapping(struct inode *inode); int cifs_zap_mapping(struct inode *inode); -int cifs_getattr(struct mnt_idmap *idmap, const struct path *path, +int cifs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int cifs_setattr(struct mnt_idmap *idmap, struct dentry *direntry, struct iattr *attrs); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index e3610e456bd41c..132f9e072b7756 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2932,7 +2932,7 @@ int cifs_revalidate_dentry(struct dentry *dentry) return cifs_revalidate_mapping(inode); } -int cifs_getattr(struct mnt_idmap *idmap, const struct path *path, +int cifs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct cifs_sb_info *cifs_sb = CIFS_SB(path->dentry); diff --git a/fs/stat.c b/fs/stat.c index c461c305423408..fe351818d1d600 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -79,7 +79,7 @@ EXPORT_SYMBOL(fill_mg_cmtime); * uid and gid filds. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -void generic_fillattr(struct mnt_idmap *idmap, u32 request_mask, +void generic_fillattr(const struct mnt_idmap *idmap, u32 request_mask, struct inode *inode, struct kstat *stat) { vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode); diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 995b8d0f8d333f..44448f1a2bb10c 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -196,7 +196,7 @@ static int tracefs_permission(const struct mnt_idmap *idmap, return generic_permission(idmap, inode, mask); } -static int tracefs_getattr(struct mnt_idmap *idmap, +static int tracefs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 57640b6ff72d51..c67954f6bee008 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1667,7 +1667,7 @@ static int ubifs_rename(const struct mnt_idmap *idmap, return do_rename(old_dir, old_dentry, new_dir, new_dentry, flags); } -int ubifs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ubifs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { loff_t size; diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index e73c28b12f97fd..bfc1bd0203adc9 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -1611,7 +1611,7 @@ static const char *ubifs_get_link(struct dentry *dentry, return fscrypt_get_symlink(inode, ui->data, ui->data_len, done); } -static int ubifs_symlink_getattr(struct mnt_idmap *idmap, +static int ubifs_symlink_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index af0ef3583aeb2a..d455a67fe4118a 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -2028,7 +2028,7 @@ int ubifs_update_time(struct inode *inode, enum fs_update_time type, /* dir.c */ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir, umode_t mode, bool is_xattr); -int ubifs_getattr(struct mnt_idmap *idmap, const struct path *path, +int ubifs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); int ubifs_check_dir_empty(struct inode *dir); diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c index a05d1888a2babe..df41bf5a05b1b6 100644 --- a/fs/udf/symlink.c +++ b/fs/udf/symlink.c @@ -133,7 +133,7 @@ static int udf_symlink_filler(struct file *file, struct folio *folio) return err; } -static int udf_symlink_getattr(struct mnt_idmap *idmap, +static int udf_symlink_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c index 298bfc93255c03..6a5a47a32075d8 100644 --- a/fs/vboxsf/utils.c +++ b/fs/vboxsf/utils.c @@ -233,7 +233,7 @@ int vboxsf_inode_revalidate(struct dentry *dentry) return 0; } -int vboxsf_getattr(struct mnt_idmap *idmap, const struct path *path, +int vboxsf_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int flags) { int err; diff --git a/fs/vboxsf/vfsmod.h b/fs/vboxsf/vfsmod.h index b61afd0ce842d2..9c713b8e7aa2e4 100644 --- a/fs/vboxsf/vfsmod.h +++ b/fs/vboxsf/vfsmod.h @@ -98,7 +98,7 @@ int vboxsf_stat(struct vboxsf_sbi *sbi, struct shfl_string *path, struct shfl_fsobjinfo *info); int vboxsf_stat_dentry(struct dentry *dentry, struct shfl_fsobjinfo *info); int vboxsf_inode_revalidate(struct dentry *dentry); -int vboxsf_getattr(struct mnt_idmap *idmap, const struct path *path, +int vboxsf_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int query_flags); int vboxsf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 791af2db71240b..19fd83cea18b2a 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -679,7 +679,7 @@ xfs_report_atomic_write( STATIC int xfs_vn_getattr( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, diff --git a/include/linux/fs.h b/include/linux/fs.h index 2b9ae72010f01f..c79fd3776bbf15 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2013,7 +2013,7 @@ struct inode_operations { int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); - int (*getattr) (struct mnt_idmap *, const struct path *, + int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, @@ -3204,7 +3204,7 @@ extern int page_symlink(struct inode *inode, const char *symname, int len); extern const struct inode_operations page_symlink_inode_operations; extern void kfree_link(void *); void fill_mg_cmtime(struct kstat *stat, u32 request_mask, struct inode *inode); -void generic_fillattr(struct mnt_idmap *, u32, struct inode *, struct kstat *); +void generic_fillattr(const struct mnt_idmap *, u32, struct inode *, struct kstat *); void generic_fill_statx_attr(struct inode *inode, struct kstat *stat); void generic_fill_statx_atomic_writes(struct kstat *stat, unsigned int unit_min, @@ -3263,7 +3263,7 @@ extern loff_t dcache_dir_lseek(struct file *, loff_t, int); extern int dcache_readdir(struct file *, struct dir_context *); extern int simple_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); -extern int simple_getattr(struct mnt_idmap *, const struct path *, +extern int simple_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern int simple_statfs(struct dentry *, struct kstatfs *); extern int simple_open(struct inode *inode, struct file *file); diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index b2828ea03abe76..7b307dea7ce711 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -437,7 +437,7 @@ extern int nfs_refresh_inode(struct inode *, struct nfs_fattr *); extern int nfs_post_op_update_inode(struct inode *inode, struct nfs_fattr *fattr); extern int nfs_post_op_update_inode_force_wcc(struct inode *inode, struct nfs_fattr *fattr); extern int nfs_post_op_update_inode_force_wcc_locked(struct inode *inode, struct nfs_fattr *fattr); -extern int nfs_getattr(struct mnt_idmap *, const struct path *, +extern int nfs_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void nfs_access_add_cache(struct inode *, struct nfs_access_entry *, const struct cred *); extern void nfs_access_set_mask(struct nfs_access_entry *, u32); diff --git a/mm/shmem.c b/mm/shmem.c index 656235b2f9c58a..b27e5f23dc9306 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1292,7 +1292,7 @@ void shmem_truncate_range(struct inode *inode, loff_t lstart, uoff_t lend) } EXPORT_SYMBOL_GPL(shmem_truncate_range); -static int shmem_getattr(struct mnt_idmap *idmap, +static int shmem_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { From e2f0d6d4569d14609644c45b029e23428069175b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:49 +0200 Subject: [PATCH 0775/2286] fs: port ->setattr() to pass const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-24-54ccd48e100b@kernel.org Acked-by: Paul Moore Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 2 +- arch/powerpc/platforms/cell/spufs/inode.c | 2 +- fs/9p/v9fs_vfs.h | 2 +- fs/9p/vfs_inode.c | 2 +- fs/9p/vfs_inode_dotl.c | 2 +- fs/adfs/adfs.h | 2 +- fs/adfs/inode.c | 2 +- fs/affs/affs.h | 2 +- fs/affs/inode.c | 2 +- fs/afs/inode.c | 2 +- fs/afs/internal.h | 2 +- fs/anon_inodes.c | 2 +- fs/attr.c | 2 +- fs/bad_inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/inode.c | 2 +- fs/ceph/super.h | 2 +- fs/coda/coda_linux.h | 2 +- fs/coda/inode.c | 2 +- fs/configfs/configfs_internal.h | 2 +- fs/configfs/inode.c | 2 +- fs/debugfs/inode.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/efivarfs/inode.c | 2 +- fs/exfat/exfat_fs.h | 2 +- fs/exfat/file.c | 2 +- fs/ext2/ext2.h | 2 +- fs/ext2/inode.c | 2 +- fs/ext4/ext4.h | 2 +- fs/ext4/inode.c | 2 +- fs/f2fs/f2fs.h | 2 +- fs/f2fs/file.c | 2 +- fs/fat/fat.h | 2 +- fs/fat/file.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfs/hfs_fs.h | 2 +- fs/hfs/inode.c | 2 +- fs/hfsplus/inode.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/hpfs/hpfs_fn.h | 2 +- fs/hpfs/inode.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/inode.c | 2 +- fs/internal.h | 2 +- fs/jffs2/fs.c | 2 +- fs/jffs2/os-linux.h | 2 +- fs/jfs/file.c | 2 +- fs/jfs/jfs_inode.h | 2 +- fs/kernfs/inode.c | 2 +- fs/kernfs/kernfs-internal.h | 2 +- fs/libfs.c | 4 ++-- fs/minix/file.c | 2 +- fs/nfs/inode.c | 2 +- fs/nfs/namespace.c | 2 +- fs/nilfs2/inode.c | 2 +- fs/nilfs2/nilfs.h | 2 +- fs/ntfs/file.c | 2 +- fs/ntfs/inode.h | 2 +- fs/ntfs3/file.c | 2 +- fs/ntfs3/ntfs_fs.h | 2 +- fs/ocfs2/dlmfs/dlmfs.c | 2 +- fs/ocfs2/file.c | 2 +- fs/ocfs2/file.h | 2 +- fs/omfs/file.c | 2 +- fs/orangefs/inode.c | 2 +- fs/orangefs/orangefs-kernel.h | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 2 +- fs/pidfs.c | 2 +- fs/proc/base.c | 2 +- fs/proc/generic.c | 2 +- fs/proc/internal.h | 2 +- fs/proc/proc_sysctl.c | 2 +- fs/ramfs/file-nommu.c | 4 ++-- fs/smb/client/cifsfs.h | 2 +- fs/smb/client/inode.c | 2 +- fs/tracefs/event_inode.c | 2 +- fs/tracefs/inode.c | 2 +- fs/ubifs/file.c | 2 +- fs/ubifs/ubifs.h | 2 +- fs/udf/file.c | 2 +- fs/ufs/inode.c | 2 +- fs/ufs/ufs.h | 2 +- fs/vboxsf/utils.c | 2 +- fs/vboxsf/vfsmod.h | 2 +- fs/xfs/xfs_iops.c | 2 +- fs/zonefs/super.c | 2 +- include/linux/fs.h | 6 +++--- include/linux/lsm_hook_defs.h | 4 ++-- include/linux/nfs_fs.h | 2 +- include/linux/security.h | 8 ++++---- mm/secretmem.c | 2 +- mm/shmem.c | 2 +- net/socket.c | 2 +- security/integrity/evm/evm_main.c | 4 ++-- security/integrity/ima/ima_appraise.c | 2 +- security/security.c | 4 ++-- security/selinux/hooks.c | 2 +- security/smack/smack_lsm.c | 2 +- virt/kvm/guest_memfd.c | 2 +- 102 files changed, 112 insertions(+), 112 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index f11bfc2e0c137e..6330653287d51a 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -76,7 +76,7 @@ prototypes:: void (*truncate) (struct inode *); int (*permission) (const struct mnt_idmap *, struct inode *, int, unsigned int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); - int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); + int (*setattr) (const struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index 98aefc5d399ed5..7b9475b1282f3d 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -430,7 +430,7 @@ As of kernel 2.6.22, the following members are defined: struct delayed_call *); int (*permission) (const struct mnt_idmap *, struct inode *, int); struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); - int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); + int (*setattr) (const struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); void (*update_time)(struct inode *inode, enum fs_update_time type, diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c index 2b54afb31529f1..a7b2df936c901a 100644 --- a/arch/powerpc/platforms/cell/spufs/inode.c +++ b/arch/powerpc/platforms/cell/spufs/inode.c @@ -92,7 +92,7 @@ spufs_new_inode(struct super_block *sb, umode_t mode) } static int -spufs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +spufs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h index 1856d91f87030a..5e7b600424988d 100644 --- a/fs/9p/v9fs_vfs.h +++ b/fs/9p/v9fs_vfs.h @@ -74,7 +74,7 @@ int v9fs_file_open(struct inode *inode, struct file *file); int v9fs_uflags2omode(int uflags, int extended); void v9fs_blank_wstat(struct p9_wstat *wstat); -int v9fs_vfs_setattr_dotl(struct mnt_idmap *idmap, +int v9fs_vfs_setattr_dotl(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end, int datasync); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 0c280342f8e982..c95e653344f63e 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1014,7 +1014,7 @@ v9fs_vfs_getattr(const struct mnt_idmap *idmap, const struct path *path, * */ -static int v9fs_vfs_setattr(struct mnt_idmap *idmap, +static int v9fs_vfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { int retval, use_dentry = 0; diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index e37854ee6346e3..2cd2898580a94b 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -508,7 +508,7 @@ static int v9fs_mapped_iattr_valid(int iattr_valid) * */ -int v9fs_vfs_setattr_dotl(struct mnt_idmap *idmap, +int v9fs_vfs_setattr_dotl(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { int retval, use_dentry = 0; diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h index 0d32b7cd99b436..6003832277f87e 100644 --- a/fs/adfs/adfs.h +++ b/fs/adfs/adfs.h @@ -144,7 +144,7 @@ struct adfs_discmap { /* Inode stuff */ struct inode *adfs_iget(struct super_block *sb, struct object_info *obj); int adfs_write_inode(struct inode *inode, struct writeback_control *wbc); -int adfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int adfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); /* map.c */ diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 4ac442d0a8c044..34598b499372b6 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -299,7 +299,7 @@ adfs_iget(struct super_block *sb, struct object_info *obj) * later. */ int -adfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) +adfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); struct super_block *sb = inode->i_sb; diff --git a/fs/affs/affs.h b/fs/affs/affs.h index 8d4ca16c7891c8..2518de96a0c8ea 100644 --- a/fs/affs/affs.h +++ b/fs/affs/affs.h @@ -184,7 +184,7 @@ extern int affs_rename2(const struct mnt_idmap *idmap, /* inode.c */ extern struct inode *affs_new_inode(struct inode *dir); -extern int affs_setattr(struct mnt_idmap *idmap, +extern int affs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern void affs_evict_inode(struct inode *inode); extern struct inode *affs_iget(struct super_block *sb, diff --git a/fs/affs/inode.c b/fs/affs/inode.c index d4a3f381c4bcff..2a48d742209171 100644 --- a/fs/affs/inode.c +++ b/fs/affs/inode.c @@ -213,7 +213,7 @@ affs_write_inode(struct inode *inode, struct writeback_control *wbc) } int -affs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) +affs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); int error; diff --git a/fs/afs/inode.c b/fs/afs/inode.c index 7b8a2adc3f599b..8e6ca6b45c6b1e 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -759,7 +759,7 @@ static const struct afs_operation_ops afs_setattr_operation = { /* * set the attributes of an inode */ -int afs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int afs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { const unsigned int supported = diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 7a8cdde9e0ad46..0bd851f00bbe95 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1260,7 +1260,7 @@ extern struct inode *afs_iget(struct afs_operation *, struct afs_vnode_param *); extern struct inode *afs_root_iget(struct super_block *, struct key *); extern int afs_getattr(const struct mnt_idmap *idmap, const struct path *, struct kstat *, u32, unsigned int); -extern int afs_setattr(struct mnt_idmap *idmap, struct dentry *, struct iattr *); +extern int afs_setattr(const struct mnt_idmap *idmap, struct dentry *, struct iattr *); extern void afs_evict_inode(struct inode *); extern int afs_drop_inode(struct inode *); diff --git a/fs/anon_inodes.c b/fs/anon_inodes.c index d4a7e0b672896e..8f07c8d9bda0c4 100644 --- a/fs/anon_inodes.c +++ b/fs/anon_inodes.c @@ -57,7 +57,7 @@ int anon_inode_getattr(const struct mnt_idmap *idmap, const struct path *path, return 0; } -int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int anon_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { return -EOPNOTSUPP; diff --git a/fs/attr.c b/fs/attr.c index 1845d145fe4c83..9706d370f34afb 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -424,7 +424,7 @@ EXPORT_SYMBOL(may_setattr); * permissions. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -int notify_change(struct mnt_idmap *idmap, struct dentry *dentry, +int notify_change(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr, struct delegated_inode *delegated_inode) { struct inode *inode = dentry->d_inode; diff --git a/fs/bad_inode.c b/fs/bad_inode.c index e740b4e658bfae..bea9f4876ee086 100644 --- a/fs/bad_inode.c +++ b/fs/bad_inode.c @@ -102,7 +102,7 @@ static int bad_inode_getattr(const struct mnt_idmap *idmap, return -EIO; } -static int bad_inode_setattr(struct mnt_idmap *idmap, +static int bad_inode_setattr(const struct mnt_idmap *idmap, struct dentry *direntry, struct iattr *attrs) { return -EIO; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index da143f9376eede..149ec3b23063ce 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -5479,7 +5479,7 @@ static int btrfs_setsize(struct inode *inode, struct iattr *attr) return ret; } -static int btrfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int btrfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 185d713218e60b..a695dba82554ee 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2921,7 +2921,7 @@ int __ceph_setattr(const struct mnt_idmap *idmap, struct inode *inode, /* * setattr */ -int ceph_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ceph_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index 0a5e3916e855e3..a033331bb151a5 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -1175,7 +1175,7 @@ struct ceph_iattr { extern int __ceph_setattr(const struct mnt_idmap *idmap, struct inode *inode, struct iattr *attr, struct ceph_iattr *cia); -extern int ceph_setattr(struct mnt_idmap *idmap, +extern int ceph_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern int ceph_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, diff --git a/fs/coda/coda_linux.h b/fs/coda/coda_linux.h index 5dc9e392079fd2..0c0d5f81653c60 100644 --- a/fs/coda/coda_linux.h +++ b/fs/coda/coda_linux.h @@ -51,7 +51,7 @@ int coda_permission(const struct mnt_idmap *idmap, struct inode *inode, int coda_revalidate_inode(struct inode *); int coda_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); -int coda_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +int coda_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); /* this file: helpers */ char *coda_f2s(struct CodaFid *f); diff --git a/fs/coda/inode.c b/fs/coda/inode.c index f83ebd94c6be40..c449954e23c216 100644 --- a/fs/coda/inode.c +++ b/fs/coda/inode.c @@ -304,7 +304,7 @@ int coda_getattr(const struct mnt_idmap *idmap, const struct path *path, return err; } -int coda_setattr(struct mnt_idmap *idmap, struct dentry *de, +int coda_setattr(const struct mnt_idmap *idmap, struct dentry *de, struct iattr *iattr) { struct inode *inode = d_inode(de); diff --git a/fs/configfs/configfs_internal.h b/fs/configfs/configfs_internal.h index 63d70dc0544429..860f1b69592190 100644 --- a/fs/configfs/configfs_internal.h +++ b/fs/configfs/configfs_internal.h @@ -76,7 +76,7 @@ extern int configfs_make_dirent(struct configfs_dirent *, struct dentry *, extern int configfs_dirent_is_ready(struct configfs_dirent *); extern const unsigned char * configfs_get_name(struct configfs_dirent *sd); -extern int configfs_setattr(struct mnt_idmap *idmap, +extern int configfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); extern struct dentry *configfs_pin_fs(void); diff --git a/fs/configfs/inode.c b/fs/configfs/inode.c index 68290fe0e374fc..6ba125d25c66af 100644 --- a/fs/configfs/inode.c +++ b/fs/configfs/inode.c @@ -32,7 +32,7 @@ static const struct inode_operations configfs_inode_operations ={ .setattr = configfs_setattr, }; -int configfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int configfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode * inode = d_inode(dentry); diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index e054e62919ec6d..287ec424ceed72 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -42,7 +42,7 @@ static bool debugfs_enabled __ro_after_init = IS_ENABLED(CONFIG_DEBUG_FS_ALLOW_A * so that we can use the file mode as part of a heuristic to determine whether * to lock down individual files. */ -static int debugfs_setattr(struct mnt_idmap *idmap, +static int debugfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *ia) { int ret; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index e70a8d73d137a1..48e520960d6657 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -869,7 +869,7 @@ ecryptfs_permission(const struct mnt_idmap *idmap, struct inode *inode, * All other metadata changes will be passed right to the lower filesystem, * and we will just update our inode to look like the lower. */ -static int ecryptfs_setattr(struct mnt_idmap *idmap, +static int ecryptfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *ia) { struct inode *inode = d_inode(dentry); diff --git a/fs/efivarfs/inode.c b/fs/efivarfs/inode.c index d87091b9277655..07602cd5d33c3c 100644 --- a/fs/efivarfs/inode.c +++ b/fs/efivarfs/inode.c @@ -170,7 +170,7 @@ efivarfs_fileattr_set(const struct mnt_idmap *idmap, } /* copy of simple_setattr except that it doesn't do i_size updates */ -static int efivarfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int efivarfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 5165a0df068adc..5acfe7ef57d6a4 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -555,7 +555,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode); -int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int exfat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int exfat_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, diff --git a/fs/exfat/file.c b/fs/exfat/file.c index c072a0663e69e6..3867e78c23125b 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -347,7 +347,7 @@ int exfat_fileattr_get(struct dentry *dentry, struct file_kattr *fa) return 0; } -int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int exfat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct exfat_sb_info *sbi = EXFAT_SB(dentry->d_sb); diff --git a/fs/ext2/ext2.h b/fs/ext2/ext2.h index f4bb8b888052c9..cf2796e5703e30 100644 --- a/fs/ext2/ext2.h +++ b/fs/ext2/ext2.h @@ -739,7 +739,7 @@ extern int ext2_sync_inode_metadata(struct inode *, struct writeback_control *); extern void ext2_evict_inode(struct inode *); void ext2_write_failed(struct address_space *mapping, loff_t to); extern int ext2_get_block(struct inode *, sector_t, struct buffer_head *, int); -extern int ext2_setattr (struct mnt_idmap *, struct dentry *, struct iattr *); +extern int ext2_setattr (const struct mnt_idmap *, struct dentry *, struct iattr *); extern int ext2_getattr (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); extern void ext2_set_inode_flags(struct inode *inode); diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index d4b2a2dec35e07..b52ded333aa92c 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1617,7 +1617,7 @@ int ext2_getattr(const struct mnt_idmap *idmap, const struct path *path, return 0; } -int ext2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ext2_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index eb7cf19f606ada..cbc59d03ca8159 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -3179,7 +3179,7 @@ extern struct inode *__ext4_iget(struct super_block *sb, unsigned long ino, extern int ext4_write_inode(struct inode *, struct writeback_control *); extern int ext4_sync_inode_metadata(struct inode *, struct writeback_control *); -extern int ext4_setattr(struct mnt_idmap *, struct dentry *, +extern int ext4_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); extern u32 ext4_dio_alignment(struct inode *inode); extern int ext4_getattr(const struct mnt_idmap *, const struct path *, diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 045f5385f26e28..3c39bb945821bb 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6006,7 +6006,7 @@ static void ext4_wait_for_tail_page_commit(struct inode *inode) * * Called with inode->i_rwsem down. */ -int ext4_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ext4_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 06538f5fd72509..128813d4f6e3cf 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -3850,7 +3850,7 @@ int f2fs_truncate_blocks(struct inode *inode, u64 from, bool lock); int f2fs_truncate(struct inode *inode); int f2fs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); -int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int f2fs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int f2fs_truncate_hole(struct inode *inode, pgoff_t pg_start, pgoff_t pg_end); void f2fs_truncate_data_blocks_range(struct dnode_of_data *dn, int count); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 267a3a191d5479..fb8b03a9972d9b 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1121,7 +1121,7 @@ static void __setattr_copy(const struct mnt_idmap *idmap, #define __setattr_copy setattr_copy #endif -int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int f2fs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/fat/fat.h b/fs/fat/fat.h index 202f209a544b46..dbbcfc90a9c289 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -404,7 +404,7 @@ extern long fat_generic_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); extern const struct file_operations fat_file_operations; extern const struct inode_operations fat_file_inode_operations; -extern int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +extern int fat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern void fat_truncate_blocks(struct inode *inode, loff_t offset); extern int fat_getattr(const struct mnt_idmap *idmap, diff --git a/fs/fat/file.c b/fs/fat/file.c index b37bc637945f90..2c6aee9f030526 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -514,7 +514,7 @@ static int fat_allow_set_time(const struct mnt_idmap *idmap, /* valid file mode bits */ #define FAT_VALID_MODE (S_IFREG | S_IFDIR | S_IRWXUGO) -int fat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int fat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct msdos_sb_info *sbi = MSDOS_SB(dentry->d_sb); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index dd267d9498bc5e..6d23878cfae5f0 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -2324,7 +2324,7 @@ int fuse_do_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, return err; } -static int fuse_setattr(struct mnt_idmap *idmap, struct dentry *entry, +static int fuse_setattr(const struct mnt_idmap *idmap, struct dentry *entry, struct iattr *attr) { struct inode *inode = d_inode(entry); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 5ddac2f50f30db..a040da86d8ed22 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -2095,7 +2095,7 @@ static int setattr_chown(struct inode *inode, struct iattr *attr) * Returns: errno */ -static int gfs2_setattr(struct mnt_idmap *idmap, +static int gfs2_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h index e250f87a5e3367..fdfa5d303d5efc 100644 --- a/fs/hfs/hfs_fs.h +++ b/fs/hfs/hfs_fs.h @@ -212,7 +212,7 @@ extern struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, extern void hfs_inode_write_fork(struct inode *inode, struct hfs_extent *ext, __be32 *log_size, __be32 *phys_size); extern int hfs_write_inode(struct inode *inode, struct writeback_control *wbc); -extern int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +extern int hfs_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern void hfs_inode_read_fork(struct inode *inode, struct hfs_extent *ext, __be32 __log_size, __be32 phys_size, diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 2aef3c36a150a2..c81314f668ac74 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -643,7 +643,7 @@ static int hfs_file_release(struct inode *inode, struct file *file) return 0; } -int hfs_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int hfs_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 2807b29c7028d5..aed0866499b26d 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -305,7 +305,7 @@ static int hfsplus_file_release(struct inode *inode, struct file *file) return 0; } -static int hfsplus_setattr(struct mnt_idmap *idmap, +static int hfsplus_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index c8ad58be047e07..613146e76dec24 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -801,7 +801,7 @@ static int hostfs_permission(const struct mnt_idmap *idmap, return err; } -static int hostfs_setattr(struct mnt_idmap *idmap, +static int hostfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/hpfs/hpfs_fn.h b/fs/hpfs/hpfs_fn.h index 237c1c23e8550b..a398dd8bdf3055 100644 --- a/fs/hpfs/hpfs_fn.h +++ b/fs/hpfs/hpfs_fn.h @@ -280,7 +280,7 @@ void hpfs_init_inode(struct inode *); void hpfs_read_inode(struct inode *); void hpfs_write_inode(struct inode *); void hpfs_write_inode_nolock(struct inode *); -int hpfs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +int hpfs_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); void hpfs_write_if_changed(struct inode *); void hpfs_evict_inode(struct inode *); diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c index 1b4fcf760aadcd..396773d0b669d8 100644 --- a/fs/hpfs/inode.c +++ b/fs/hpfs/inode.c @@ -257,7 +257,7 @@ void hpfs_write_inode_nolock(struct inode *i) brelse(bh); } -int hpfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int hpfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 0b95be14d3ee18..6656807ce43772 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -828,7 +828,7 @@ static long hugetlbfs_fallocate(struct file *file, int mode, loff_t offset, return error; } -static int hugetlbfs_setattr(struct mnt_idmap *idmap, +static int hugetlbfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/inode.c b/fs/inode.c index 99f1aab89eae9e..bd9808f07eb8cf 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2356,7 +2356,7 @@ int dentry_needs_remove_privs(const struct mnt_idmap *idmap, return mask; } -static int __remove_privs(struct mnt_idmap *idmap, +static int __remove_privs(const struct mnt_idmap *idmap, struct dentry *dentry, int kill) { struct iattr newattrs; diff --git a/fs/internal.h b/fs/internal.h index 88d9296c2a482f..2b8cffc22fb35b 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -359,7 +359,7 @@ struct dentry *find_next_child(struct dentry *parent, struct dentry *prev); int anon_inode_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags); -int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int anon_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); void pidfs_get_root(struct path *path); void nsfs_get_root(struct path *path); diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c index 6ada8369a76227..05cf860307c737 100644 --- a/fs/jffs2/fs.c +++ b/fs/jffs2/fs.c @@ -190,7 +190,7 @@ int jffs2_do_setattr (struct inode *inode, struct iattr *iattr) return 0; } -int jffs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int jffs2_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h index 86ab014a349c5e..bff2134d771d17 100644 --- a/fs/jffs2/os-linux.h +++ b/fs/jffs2/os-linux.h @@ -164,7 +164,7 @@ long jffs2_ioctl(struct file *, unsigned int, unsigned long); extern const struct inode_operations jffs2_symlink_inode_operations; /* fs.c */ -int jffs2_setattr (struct mnt_idmap *, struct dentry *, struct iattr *); +int jffs2_setattr (const struct mnt_idmap *, struct dentry *, struct iattr *); int jffs2_do_setattr (struct inode *, struct iattr *); struct inode *jffs2_iget(struct super_block *, unsigned long); void jffs2_evict_inode (struct inode *); diff --git a/fs/jfs/file.c b/fs/jfs/file.c index 246568cb9a6ec1..2f5bb79c05916a 100644 --- a/fs/jfs/file.c +++ b/fs/jfs/file.c @@ -89,7 +89,7 @@ static int jfs_release(struct inode *inode, struct file *file) return 0; } -int jfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int jfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/jfs/jfs_inode.h b/fs/jfs/jfs_inode.h index 88cfc38862822e..5a118b07fbff31 100644 --- a/fs/jfs/jfs_inode.h +++ b/fs/jfs/jfs_inode.h @@ -28,7 +28,7 @@ extern struct dentry *jfs_fh_to_parent(struct super_block *sb, struct fid *fid, int fh_len, int fh_type); extern void jfs_set_inode_flags(struct inode *); extern int jfs_get_block(struct inode *, sector_t, struct buffer_head *, int); -extern int jfs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +extern int jfs_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); extern const struct address_space_operations jfs_aops; extern const struct inode_operations jfs_dir_inode_operations; diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 6a92aa789ce5aa..b9dbb504d226ca 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -107,7 +107,7 @@ int kernfs_setattr(struct kernfs_node *kn, const struct iattr *iattr) return ret; } -int kernfs_iop_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int kernfs_iop_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/kernfs/kernfs-internal.h b/fs/kernfs/kernfs-internal.h index 8af7849f2c9cc0..d0c68ec2d236ad 100644 --- a/fs/kernfs/kernfs-internal.h +++ b/fs/kernfs/kernfs-internal.h @@ -173,7 +173,7 @@ extern const struct xattr_handler * const kernfs_xattr_handlers[]; void kernfs_evict_inode(struct inode *inode); int kernfs_iop_permission(const struct mnt_idmap *idmap, struct inode *inode, int mask); -int kernfs_iop_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int kernfs_iop_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); int kernfs_iop_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, diff --git a/fs/libfs.c b/fs/libfs.c index b70977b10a7c6f..8e2cc627bc7f41 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -913,7 +913,7 @@ EXPORT_SYMBOL(simple_rename); * on simple regular filesystems. Anything that needs to change on-disk * or wire state on size changes needs its own setattr method. */ -int simple_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int simple_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); @@ -1727,7 +1727,7 @@ static struct dentry *empty_dir_lookup(struct inode *dir, struct dentry *dentry, return ERR_PTR(-ENOENT); } -static int empty_dir_setattr(struct mnt_idmap *idmap, +static int empty_dir_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { return -EPERM; diff --git a/fs/minix/file.c b/fs/minix/file.c index 02aabbdb5deaa0..c0929fc38fbcba 100644 --- a/fs/minix/file.c +++ b/fs/minix/file.c @@ -23,7 +23,7 @@ const struct file_operations minix_file_operations = { .splice_read = filemap_splice_read, }; -static int minix_setattr(struct mnt_idmap *idmap, +static int minix_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index b114488715fbae..3c9b2ec4e24430 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -690,7 +690,7 @@ EXPORT_SYMBOL_GPL(nfs_update_delegated_mtime); #define NFS_VALID_ATTRS (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE|ATTR_ATIME|ATTR_ATIME_SET|ATTR_MTIME|ATTR_MTIME_SET|ATTR_FILE|ATTR_OPEN) int -nfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +nfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 17f54d79efa75a..c50d59c52c50bb 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -235,7 +235,7 @@ nfs_namespace_getattr(const struct mnt_idmap *idmap, } static int -nfs_namespace_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +nfs_namespace_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { if (NFS_FH(d_inode(dentry))->size != 0) diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 9342f651bf9b14..8953719c4950cf 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -904,7 +904,7 @@ void nilfs_evict_inode(struct inode *inode) */ } -int nilfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int nilfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct nilfs_transaction_info ti; diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h index 829df7be11fe62..28d35b2fa7f7a5 100644 --- a/fs/nilfs2/nilfs.h +++ b/fs/nilfs2/nilfs.h @@ -299,7 +299,7 @@ struct inode *nilfs_iget_for_shadow(struct inode *inode); extern void nilfs_update_inode(struct inode *, struct buffer_head *, int); extern void nilfs_truncate(struct inode *); extern void nilfs_evict_inode(struct inode *); -extern int nilfs_setattr(struct mnt_idmap *, struct dentry *, +extern int nilfs_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); extern void nilfs_write_failed(struct address_space *mapping, loff_t to); int nilfs_permission(const struct mnt_idmap *idmap, struct inode *inode, diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c index 40e01704ea8fcf..da85cbae24203f 100644 --- a/fs/ntfs/file.c +++ b/fs/ntfs/file.c @@ -294,7 +294,7 @@ static int ntfs_setattr_size(struct inode *vi, struct iattr *attr) * NOTE: Changes in inode size are not supported yet for compressed or * encrypted files. */ -int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *vi = d_inode(dentry); diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h index 9cf9239118258f..45a396c9784688 100644 --- a/fs/ntfs/inode.h +++ b/fs/ntfs/inode.h @@ -330,7 +330,7 @@ int ntfs_read_inode_mount(struct inode *vi); int ntfs_show_options(struct seq_file *sf, struct dentry *root); int ntfs_truncate_vfs(struct inode *vi, loff_t new_size, loff_t i_size); -int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, unsigned int request_mask, diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c index f4d08346d25d22..52e1cd969e067f 100644 --- a/fs/ntfs3/file.c +++ b/fs/ntfs3/file.c @@ -703,7 +703,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t vbo, loff_t len) /* * ntfs_setattr - inode_operations::setattr */ -int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ntfs3/ntfs_fs.h b/fs/ntfs3/ntfs_fs.h index 262b7ff2d6e169..ea24126c70db43 100644 --- a/fs/ntfs3/ntfs_fs.h +++ b/fs/ntfs3/ntfs_fs.h @@ -555,7 +555,7 @@ int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry, struct file_kattr *fa); int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, u32 flags); -int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ntfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ntfs_file_open(struct inode *inode, struct file *file); int ntfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index fef79b21a4a53c..d3bfcada3e3bf5 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -188,7 +188,7 @@ static int dlmfs_file_release(struct inode *inode, * We do ->setattr() just to override size changes. Our size is the size * of the LVB and nothing else. */ -static int dlmfs_file_setattr(struct mnt_idmap *idmap, +static int dlmfs_file_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { int error; diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index befc6ebb2a0638..62f45a1b5ca1a6 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1117,7 +1117,7 @@ static int ocfs2_extend_file(struct inode *inode, return ret; } -int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ocfs2_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { int status = 0, size_change; diff --git a/fs/ocfs2/file.h b/fs/ocfs2/file.h index b51c8e77dad791..97492ee5789e8b 100644 --- a/fs/ocfs2/file.h +++ b/fs/ocfs2/file.h @@ -50,7 +50,7 @@ int ocfs2_extend_no_holes(struct inode *inode, struct buffer_head *di_bh, u64 new_i_size, u64 zero_to); int ocfs2_zero_extend(struct inode *inode, struct buffer_head *di_bh, loff_t zero_to); -int ocfs2_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ocfs2_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ocfs2_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/omfs/file.c b/fs/omfs/file.c index 28f3b113340e89..79a413f1dc0dbf 100644 --- a/fs/omfs/file.c +++ b/fs/omfs/file.c @@ -338,7 +338,7 @@ const struct file_operations omfs_file_operations = { .splice_read = filemap_splice_read, }; -static int omfs_setattr(struct mnt_idmap *idmap, +static int omfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 100669ee5862f9..75ac1e485e3566 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -828,7 +828,7 @@ int __orangefs_setattr_mode(struct dentry *dentry, struct iattr *iattr) /* * Change attributes of an object referenced by dentry. */ -int orangefs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int orangefs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { int ret; diff --git a/fs/orangefs/orangefs-kernel.h b/fs/orangefs/orangefs-kernel.h index 3bf01c0c763b3b..348fe340c5d5fc 100644 --- a/fs/orangefs/orangefs-kernel.h +++ b/fs/orangefs/orangefs-kernel.h @@ -352,7 +352,7 @@ struct inode *orangefs_new_inode(struct super_block *sb, int __orangefs_setattr(struct inode *, struct iattr *); int __orangefs_setattr_mode(struct dentry *dentry, struct iattr *iattr); -int orangefs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +int orangefs_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); int orangefs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index b765780686e65d..622e9bb833affd 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -18,7 +18,7 @@ #include "overlayfs.h" -int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ovl_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { int err; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 02a822dc8e5e36..f0eac9ae1e38a7 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -948,7 +948,7 @@ static inline bool ovl_force_readonly(struct ovl_fs *ofs) /* xattr.c */ const struct xattr_handler * const *ovl_xattr_handlers(struct ovl_fs *ofs); -int ovl_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ovl_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ovl_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/pidfs.c b/fs/pidfs.c index 38868ac9995090..c37c17bcdbdc80 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -823,7 +823,7 @@ static struct vfsmount *pidfs_mnt __ro_after_init; * implemented. Let's reject it completely until we have a clean * permission concept for pidfds. */ -static int pidfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int pidfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { return anon_inode_setattr(idmap, dentry, attr); diff --git a/fs/proc/base.c b/fs/proc/base.c index 3278c37e5cde23..5a18636ba81fb6 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -702,7 +702,7 @@ static int proc_pid_syscall(struct seq_file *m, struct pid_namespace *ns, /* Here the fs part begins */ /************************************************************************/ -int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int proc_nochmod_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { int error; diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 4c4f1e63f68fe1..2b1971da4a85a8 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -117,7 +117,7 @@ static bool pde_subdir_insert(struct proc_dir_entry *dir, return true; } -static int proc_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int proc_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/fs/proc/internal.h b/fs/proc/internal.h index 706fae7d30be46..b9aaac41c2839f 100644 --- a/fs/proc/internal.h +++ b/fs/proc/internal.h @@ -260,7 +260,7 @@ extern int proc_pid_statm(struct seq_file *, struct pid_namespace *, extern const struct dentry_operations pid_dentry_operations; extern int pid_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); -int proc_nochmod_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int proc_nochmod_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); extern void proc_pid_evict_inode(struct proc_inode *); extern struct inode *proc_pid_make_inode(struct super_block *, struct task_struct *, umode_t); diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index bd6730000b27ac..d1cfd294135908 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -817,7 +817,7 @@ static int proc_sys_permission(const struct mnt_idmap *idmap, return error; } -static int proc_sys_setattr(struct mnt_idmap *idmap, +static int proc_sys_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index fb471bf88ab7a6..7ed6fba134c67e 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c @@ -22,7 +22,7 @@ #include #include "internal.h" -static int ramfs_nommu_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +static int ramfs_nommu_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); static unsigned long ramfs_nommu_get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, @@ -161,7 +161,7 @@ static int ramfs_nommu_resize(struct inode *inode, loff_t newsize, loff_t size) * handle a change of attributes * - we're specifically interested in a change of size */ -static int ramfs_nommu_setattr(struct mnt_idmap *idmap, +static int ramfs_nommu_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *ia) { struct inode *inode = d_inode(dentry); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index c0adc52192130b..51692e14c4dd38 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -80,7 +80,7 @@ int cifs_revalidate_mapping(struct inode *inode); int cifs_zap_mapping(struct inode *inode); int cifs_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); -int cifs_setattr(struct mnt_idmap *idmap, struct dentry *direntry, +int cifs_setattr(const struct mnt_idmap *idmap, struct dentry *direntry, struct iattr *attrs); int cifs_fiemap(struct inode *inode, struct fiemap_extent_info *fei, u64 start, u64 len); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 132f9e072b7756..248b7891e687d8 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -3549,7 +3549,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) } int -cifs_setattr(struct mnt_idmap *idmap, struct dentry *direntry, +cifs_setattr(const struct mnt_idmap *idmap, struct dentry *direntry, struct iattr *attrs) { struct cifs_sb_info *cifs_sb = CIFS_SB(direntry->d_sb); diff --git a/fs/tracefs/event_inode.c b/fs/tracefs/event_inode.c index 6e3513b13cfa21..6f6daac88621ed 100644 --- a/fs/tracefs/event_inode.c +++ b/fs/tracefs/event_inode.c @@ -182,7 +182,7 @@ static void update_attr(struct eventfs_attr *attr, struct iattr *iattr) } } -static int eventfs_set_attr(struct mnt_idmap *idmap, struct dentry *dentry, +static int eventfs_set_attr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { const struct eventfs_entry *entry; diff --git a/fs/tracefs/inode.c b/fs/tracefs/inode.c index 44448f1a2bb10c..5020365ca70487 100644 --- a/fs/tracefs/inode.c +++ b/fs/tracefs/inode.c @@ -207,7 +207,7 @@ static int tracefs_getattr(const struct mnt_idmap *idmap, return 0; } -static int tracefs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int tracefs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index bfc1bd0203adc9..244b835fc82a73 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -1251,7 +1251,7 @@ static int do_setattr(struct ubifs_info *c, struct inode *inode, return err; } -int ubifs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ubifs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { int err; diff --git a/fs/ubifs/ubifs.h b/fs/ubifs/ubifs.h index d455a67fe4118a..b85b45a0564bc0 100644 --- a/fs/ubifs/ubifs.h +++ b/fs/ubifs/ubifs.h @@ -2020,7 +2020,7 @@ int ubifs_calc_dark(const struct ubifs_info *c, int spc); /* file.c */ int ubifs_fsync(struct file *file, loff_t start, loff_t end, int datasync); -int ubifs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ubifs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); int ubifs_update_time(struct inode *inode, enum fs_update_time type, unsigned int flags); diff --git a/fs/udf/file.c b/fs/udf/file.c index 57d11606a2a776..02e9314818dc64 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -212,7 +212,7 @@ const struct file_operations udf_file_operations = { .setlease = generic_setlease, }; -static int udf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int udf_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c index 440d014cc5ed58..c9ff8673fa668b 100644 --- a/fs/ufs/inode.c +++ b/fs/ufs/inode.c @@ -1195,7 +1195,7 @@ static int ufs_truncate(struct inode *inode, loff_t size) return err; } -int ufs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int ufs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/fs/ufs/ufs.h b/fs/ufs/ufs.h index 788e025056b2bf..541566f5b5fb85 100644 --- a/fs/ufs/ufs.h +++ b/fs/ufs/ufs.h @@ -120,7 +120,7 @@ extern struct inode *ufs_iget(struct super_block *, unsigned long); extern int ufs_write_inode (struct inode *, struct writeback_control *); extern int ufs_sync_inode (struct inode *); extern void ufs_evict_inode (struct inode *); -extern int ufs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +extern int ufs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); /* namei.c */ diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c index 6a5a47a32075d8..8775fbee1ce648 100644 --- a/fs/vboxsf/utils.c +++ b/fs/vboxsf/utils.c @@ -258,7 +258,7 @@ int vboxsf_getattr(const struct mnt_idmap *idmap, const struct path *path, return 0; } -int vboxsf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int vboxsf_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct vboxsf_inode *sf_i = VBOXSF_I(d_inode(dentry)); diff --git a/fs/vboxsf/vfsmod.h b/fs/vboxsf/vfsmod.h index 9c713b8e7aa2e4..59a4e44c400577 100644 --- a/fs/vboxsf/vfsmod.h +++ b/fs/vboxsf/vfsmod.h @@ -101,7 +101,7 @@ int vboxsf_inode_revalidate(struct dentry *dentry); int vboxsf_getattr(const struct mnt_idmap *idmap, const struct path *path, struct kstat *kstat, u32 request_mask, unsigned int query_flags); -int vboxsf_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +int vboxsf_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr); struct shfl_string *vboxsf_path_from_dentry(struct vboxsf_sbi *sbi, struct dentry *dentry); diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 19fd83cea18b2a..535e8a9e071ef2 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -1130,7 +1130,7 @@ xfs_vn_setattr_size( STATIC int xfs_vn_setattr( - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c index ff43d6d1ea30ae..b97f1f2b8dda24 100644 --- a/fs/zonefs/super.c +++ b/fs/zonefs/super.c @@ -533,7 +533,7 @@ static int zonefs_show_options(struct seq_file *seq, struct dentry *root) return 0; } -static int zonefs_inode_setattr(struct mnt_idmap *idmap, +static int zonefs_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/include/linux/fs.h b/include/linux/fs.h index c79fd3776bbf15..55d8e7e473ff24 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2012,7 +2012,7 @@ struct inode_operations { umode_t,dev_t); int (*rename) (const struct mnt_idmap *, struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); - int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *); + int (*setattr) (const struct mnt_idmap *, struct dentry *, struct iattr *); int (*getattr) (const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); @@ -2707,7 +2707,7 @@ static inline int bmap(struct inode *inode, sector_t *block) } #endif -int notify_change(struct mnt_idmap *, struct dentry *, +int notify_change(const struct mnt_idmap *, struct dentry *, struct iattr *, struct delegated_inode *); int inode_permission(const struct mnt_idmap *, struct inode *, int); int generic_permission(const struct mnt_idmap *, struct inode *, int); @@ -3261,7 +3261,7 @@ extern int dcache_dir_open(struct inode *, struct file *); extern int dcache_dir_close(struct inode *, struct file *); extern loff_t dcache_dir_lseek(struct file *, loff_t, int); extern int dcache_readdir(struct file *, struct dir_context *); -extern int simple_setattr(struct mnt_idmap *, struct dentry *, +extern int simple_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); extern int simple_getattr(const struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index ad05971e6137c2..194084f60746dc 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -140,9 +140,9 @@ LSM_HOOK(int, 0, inode_readlink, struct dentry *dentry) LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode, bool rcu) LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask) -LSM_HOOK(int, 0, inode_setattr, struct mnt_idmap *idmap, struct dentry *dentry, +LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) -LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, struct mnt_idmap *idmap, +LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) LSM_HOOK(int, 0, inode_getattr, const struct path *path) LSM_HOOK(int, 0, inode_xattr_skipcap, const char *name) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 7b307dea7ce711..d2c716322c6f26 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -450,7 +450,7 @@ extern int nfs_clear_invalid_mapping(struct address_space *mapping); extern bool nfs_mapping_need_revalidate_inode(struct inode *inode); extern int nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping); extern int nfs_revalidate_mapping_rcu(struct inode *inode); -extern int nfs_setattr(struct mnt_idmap *, struct dentry *, struct iattr *); +extern int nfs_setattr(const struct mnt_idmap *, struct dentry *, struct iattr *); extern void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr, struct nfs_fattr *); extern void nfs_setsecurity(struct inode *inode, struct nfs_fattr *fattr); extern struct nfs_open_context *get_nfs_open_context(struct nfs_open_context *ctx); diff --git a/include/linux/security.h b/include/linux/security.h index d4100396da27b8..c7691feb8f8e4a 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -422,9 +422,9 @@ int security_inode_readlink(struct dentry *dentry); int security_inode_follow_link(struct dentry *dentry, struct inode *inode, bool rcu); int security_inode_permission(struct inode *inode, int mask); -int security_inode_setattr(struct mnt_idmap *idmap, +int security_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); -void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid); int security_inode_getattr(const struct path *path); int security_inode_setxattr(const struct mnt_idmap *idmap, @@ -979,7 +979,7 @@ static inline int security_inode_permission(struct inode *inode, int mask) return 0; } -static inline int security_inode_setattr(struct mnt_idmap *idmap, +static inline int security_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { @@ -987,7 +987,7 @@ static inline int security_inode_setattr(struct mnt_idmap *idmap, } static inline void -security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { } diff --git a/mm/secretmem.c b/mm/secretmem.c index d29865075b6ea0..7bb6e426ac6e8d 100644 --- a/mm/secretmem.c +++ b/mm/secretmem.c @@ -161,7 +161,7 @@ const struct address_space_operations secretmem_aops = { .migrate_folio = secretmem_migrate_folio, }; -static int secretmem_setattr(struct mnt_idmap *idmap, +static int secretmem_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct inode *inode = d_inode(dentry); diff --git a/mm/shmem.c b/mm/shmem.c index b27e5f23dc9306..2c086c2b604e8c 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1326,7 +1326,7 @@ static int shmem_getattr(const struct mnt_idmap *idmap, return 0; } -static int shmem_setattr(struct mnt_idmap *idmap, +static int shmem_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_inode(dentry); diff --git a/net/socket.c b/net/socket.c index 052273ad081acc..c0ab6a3ad8edff 100644 --- a/net/socket.c +++ b/net/socket.c @@ -670,7 +670,7 @@ static ssize_t sockfs_listxattr(struct dentry *dentry, char *buffer, return used; } -static int sockfs_setattr(struct mnt_idmap *idmap, +static int sockfs_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { int err = simple_setattr(&nop_mnt_idmap, dentry, iattr); diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 10a0b39dd9b79f..9bfea858df2d3b 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -956,7 +956,7 @@ static int evm_attr_change(const struct mnt_idmap *idmap, * Permit update of file attributes when files have a valid EVM signature, * except in the case of them having an immutable portable signature. */ -static int evm_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; @@ -1008,7 +1008,7 @@ static int evm_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * This function is called from notify_change(), which expects the caller * to lock the inode's i_mutex. */ -static void evm_inode_post_setattr(struct mnt_idmap *idmap, +static void evm_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { if (!evm_revalidate_status(NULL)) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index cfc20c63a81450..659b71e77ac85e 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -634,7 +634,7 @@ void ima_update_xattr(struct ima_iint_cache *iint, struct file *file) * This function is called from notify_change(), which expects the caller * to lock the inode's i_mutex. */ -static void ima_inode_post_setattr(struct mnt_idmap *idmap, +static void ima_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { struct inode *inode = d_backing_inode(dentry); diff --git a/security/security.c b/security/security.c index 9a3ae6746fb385..304048f26d03e9 100644 --- a/security/security.c +++ b/security/security.c @@ -1855,7 +1855,7 @@ int security_inode_permission(struct inode *inode, int mask) * * Return: Returns 0 if permission is granted. */ -int security_inode_setattr(struct mnt_idmap *idmap, +int security_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -1872,7 +1872,7 @@ EXPORT_SYMBOL_GPL(security_inode_setattr); * * Update inode security field after successful setting file attributes. */ -void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b7c6eba08fdc7d..1e59fb67dffb08 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3297,7 +3297,7 @@ static int selinux_inode_permission(struct inode *inode, int requested) return rc; } -static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int selinux_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index df4fc6ff26aa8f..bb78569b3d5b8e 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1270,7 +1270,7 @@ static int smack_inode_permission(struct inode *inode, int mask) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int smack_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct smk_audit_info ad; diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 625e62e1a03184..5c084d5393a693 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -506,7 +506,7 @@ static const struct address_space_operations kvm_gmem_aops = { #endif }; -static int kvm_gmem_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int kvm_gmem_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { return -EINVAL; From e2f7f94b5a25ecfbb05df1437c5b46e848adc948 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:50 +0200 Subject: [PATCH 0776/2286] fs: port vfs_*() helpers to const mnt_idmap Convert to const struct mnt_idmap. A mount's idmapping is immutable. The only thing that is allowed to be modified afterwards is the reference count and that is hidden behind mnt_idmap_get() and mnt_idmap_put(). Everything else only ever reads from the idmapping. This is the same model that struct cred uses and the idmapping is also rather sensitive. So make the idmap argument const wherever we can. The conversion is done from the bottom up so callers can continue to pass a non-const pointer to a const parameter until the conversion is finished. No functional changes. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-25-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/btrfs/ioctl.c | 4 ++-- fs/internal.h | 2 +- fs/namei.c | 44 +++++++++++++++++++++---------------------- fs/open.c | 2 +- include/linux/fs.h | 30 ++++++++++++++--------------- include/linux/namei.h | 16 ++++++++-------- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 94c98801800fc0..e20e66c0fead31 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -864,7 +864,7 @@ static int create_snapshot(struct btrfs_root *root, struct inode *dir, * inside this filesystem so it's quite a bit simpler. */ static noinline int btrfs_mksubvol(struct dentry *parent, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct qstr *qname, struct btrfs_root *snap_src, bool readonly, struct btrfs_qgroup_inherit *inherit) @@ -911,7 +911,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent, } static noinline int btrfs_mksnapshot(struct dentry *parent, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct qstr *qname, struct btrfs_root *root, bool readonly, diff --git a/fs/internal.h b/fs/internal.h index 2b8cffc22fb35b..973436f44770dd 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -63,7 +63,7 @@ int filename_mknodat(int dfd, struct filename *name, umode_t mode, unsigned int int filename_symlinkat(struct filename *from, int newdfd, struct filename *to); int filename_linkat(int olddfd, struct filename *old, int newdfd, struct filename *new, int flags); -int vfs_tmpfile(struct mnt_idmap *idmap, +int vfs_tmpfile(const struct mnt_idmap *idmap, const struct path *parentpath, struct file *file, umode_t mode); struct dentry *d_hash_and_lookup(struct dentry *, struct qstr *); diff --git a/fs/namei.c b/fs/namei.c index f5e00ae41ed4a2..14970fe083879b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3111,7 +3111,7 @@ int lookup_noperm_common(struct qstr *qname, struct dentry *base) return 0; } -static int lookup_one_common(struct mnt_idmap *idmap, +static int lookup_one_common(const struct mnt_idmap *idmap, struct qstr *qname, struct dentry *base) { int err; @@ -3190,7 +3190,7 @@ EXPORT_SYMBOL(lookup_noperm); * * The caller must hold base->i_rwsem. */ -struct dentry *lookup_one(struct mnt_idmap *idmap, struct qstr *name, +struct dentry *lookup_one(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base) { struct dentry *dentry; @@ -3223,7 +3223,7 @@ EXPORT_SYMBOL(lookup_one); * - ERR_PTR(-ENOENT) if parent has been removed, or * - ERR_PTR(-EACCES) if parent directory is not searchable. */ -struct dentry *lookup_one_unlocked(struct mnt_idmap *idmap, struct qstr *name, +struct dentry *lookup_one_unlocked(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base) { int err; @@ -3263,7 +3263,7 @@ EXPORT_SYMBOL(lookup_one_unlocked); * - same errors as lookup_one_unlocked() or * - ERR_PTR(-EINTR) if a fatal signal is pending. */ -struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap, +struct dentry *lookup_one_positive_killable(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base) { @@ -3306,7 +3306,7 @@ EXPORT_SYMBOL(lookup_one_positive_killable); * - ERR_PTR(-ENOENT) if the name could not be found, or * - same errors as lookup_one_unlocked(). */ -struct dentry *lookup_one_positive_unlocked(struct mnt_idmap *idmap, +struct dentry *lookup_one_positive_unlocked(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base) { @@ -3396,7 +3396,7 @@ EXPORT_SYMBOL(lookup_noperm_positive_unlocked); * * Returns: a negative or positive dentry, or an error. */ -struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent, +struct dentry *start_creating(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name) { int err = lookup_one_common(idmap, name, parent); @@ -3423,7 +3423,7 @@ EXPORT_SYMBOL(start_creating); * * Returns: a positive dentry, or an error. */ -struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent, +struct dentry *start_removing(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name) { int err = lookup_one_common(idmap, name, parent); @@ -3451,7 +3451,7 @@ EXPORT_SYMBOL(start_removing); * * Returns: a negative or positive dentry, or an error. */ -struct dentry *start_creating_killable(struct mnt_idmap *idmap, +struct dentry *start_creating_killable(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name) { @@ -3482,7 +3482,7 @@ EXPORT_SYMBOL(start_creating_killable); * * Returns: a positive dentry, or an error. */ -struct dentry *start_removing_killable(struct mnt_idmap *idmap, +struct dentry *start_removing_killable(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name) { @@ -3642,7 +3642,7 @@ int user_path_at(int dfd, const char __user *name, unsigned flags, } EXPORT_SYMBOL(user_path_at); -int __check_sticky(struct mnt_idmap *idmap, struct inode *dir, +int __check_sticky(const struct mnt_idmap *idmap, struct inode *dir, struct inode *inode) { kuid_t fsuid = current_fsuid(); @@ -3675,7 +3675,7 @@ EXPORT_SYMBOL(__check_sticky); * 11. We don't allow removal of NFS sillyrenamed files; it's handled by * nfs_async_unlink(). */ -int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir, +int may_delete_dentry(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *victim, bool isdir) { struct inode *inode = d_backing_inode(victim); @@ -3728,7 +3728,7 @@ EXPORT_SYMBOL(may_delete_dentry); * 4. We should have write and exec permissions on dir * 5. We can't do it if dir is immutable (done in permission()) */ -int may_create_dentry(struct mnt_idmap *idmap, +int may_create_dentry(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *child) { audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE); @@ -4174,7 +4174,7 @@ static inline umode_t vfs_prepare_mode(const struct mnt_idmap *idmap, * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, +int vfs_create(const struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode, struct delegated_inode *di) { struct inode *dir = d_inode(dentry->d_parent); @@ -4287,7 +4287,7 @@ static int may_open(const struct mnt_idmap *idmap, const struct path *path, return 0; } -static int handle_truncate(struct mnt_idmap *idmap, struct file *filp) +static int handle_truncate(const struct mnt_idmap *idmap, struct file *filp) { const struct path *path = &filp->f_path; struct inode *inode = path->dentry->d_inode; @@ -4863,7 +4863,7 @@ static int do_open(struct nameidata *nd, * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_tmpfile(struct mnt_idmap *idmap, +int vfs_tmpfile(const struct mnt_idmap *idmap, const struct path *parentpath, struct file *file, umode_t mode) { @@ -4921,7 +4921,7 @@ int vfs_tmpfile(struct mnt_idmap *idmap, * hence this is only for kernel internal use, and must not be installed into * file tables or such. */ -struct file *kernel_tmpfile_open(struct mnt_idmap *idmap, +struct file *kernel_tmpfile_open(const struct mnt_idmap *idmap, const struct path *parentpath, umode_t mode, int open_flag, const struct cred *cred) @@ -5236,7 +5236,7 @@ EXPORT_SYMBOL(dentry_create); * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, +int vfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev, struct delegated_inode *delegated_inode) { @@ -5378,7 +5378,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d * * In case of an error the dentry is dput() and an ERR_PTR() is returned. */ -struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, +struct dentry *vfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, struct delegated_inode *delegated_inode) { @@ -5485,7 +5485,7 @@ SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode) * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_rmdir(struct mnt_idmap *idmap, struct inode *dir, +int vfs_rmdir(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct delegated_inode *delegated_inode) { int error = may_delete_dentry(idmap, dir, dentry, true); @@ -5620,7 +5620,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_unlink(struct mnt_idmap *idmap, struct inode *dir, +int vfs_unlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, struct delegated_inode *delegated_inode) { struct inode *target = dentry->d_inode; @@ -5770,7 +5770,7 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname) * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, +int vfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *oldname, struct delegated_inode *delegated_inode) { @@ -5872,7 +5872,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn * On non-idmapped mounts or if permission checking is to be performed on the * raw inode simply pass @nop_mnt_idmap. */ -int vfs_link(struct dentry *old_dentry, struct mnt_idmap *idmap, +int vfs_link(struct dentry *old_dentry, const struct mnt_idmap *idmap, struct inode *dir, struct dentry *new_dentry, struct delegated_inode *delegated_inode) { diff --git a/fs/open.c b/fs/open.c index 6b1c14e684a93b..971e51008e6752 100644 --- a/fs/open.c +++ b/fs/open.c @@ -36,7 +36,7 @@ #include "internal.h" -int do_truncate(struct mnt_idmap *idmap, struct dentry *dentry, +int do_truncate(const struct mnt_idmap *idmap, struct dentry *dentry, loff_t length, unsigned int time_attrs, struct file *filp) { int ret; diff --git a/include/linux/fs.h b/include/linux/fs.h index 55d8e7e473ff24..935b0f92786fc3 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1761,19 +1761,19 @@ bool inode_owner_or_capable(const struct mnt_idmap *idmap, /* * VFS helper functions.. */ -int vfs_create(struct mnt_idmap *, struct dentry *, umode_t, +int vfs_create(const struct mnt_idmap *, struct dentry *, umode_t, struct delegated_inode *); -struct dentry *vfs_mkdir(struct mnt_idmap *, struct inode *, +struct dentry *vfs_mkdir(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, struct delegated_inode *); -int vfs_mknod(struct mnt_idmap *, struct inode *, struct dentry *, +int vfs_mknod(const struct mnt_idmap *, struct inode *, struct dentry *, umode_t, dev_t, struct delegated_inode *); -int vfs_symlink(struct mnt_idmap *, struct inode *, +int vfs_symlink(const struct mnt_idmap *, struct inode *, struct dentry *, const char *, struct delegated_inode *); -int vfs_link(struct dentry *, struct mnt_idmap *, struct inode *, +int vfs_link(struct dentry *, const struct mnt_idmap *, struct inode *, struct dentry *, struct delegated_inode *); -int vfs_rmdir(struct mnt_idmap *, struct inode *, struct dentry *, +int vfs_rmdir(const struct mnt_idmap *, struct inode *, struct dentry *, struct delegated_inode *); -int vfs_unlink(struct mnt_idmap *, struct inode *, struct dentry *, +int vfs_unlink(const struct mnt_idmap *, struct inode *, struct dentry *, struct delegated_inode *); /** @@ -1787,7 +1787,7 @@ int vfs_unlink(struct mnt_idmap *, struct inode *, struct dentry *, * @flags: rename flags */ struct renamedata { - struct mnt_idmap *mnt_idmap; + const struct mnt_idmap *mnt_idmap; struct dentry *old_parent; struct dentry *old_dentry; struct dentry *new_parent; @@ -1798,14 +1798,14 @@ struct renamedata { int vfs_rename(struct renamedata *); -static inline int vfs_whiteout(struct mnt_idmap *idmap, +static inline int vfs_whiteout(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry) { return vfs_mknod(idmap, dir, dentry, S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV, NULL); } -struct file *kernel_tmpfile_open(struct mnt_idmap *idmap, +struct file *kernel_tmpfile_open(const struct mnt_idmap *idmap, const struct path *parentpath, umode_t mode, int open_flag, const struct cred *cred); @@ -2483,7 +2483,7 @@ static inline bool is_idmapped_mnt(const struct vfsmount *mnt) } int vfs_truncate(const struct path *, loff_t); -int do_truncate(struct mnt_idmap *, struct dentry *, loff_t start, +int do_truncate(const struct mnt_idmap *, struct dentry *, loff_t start, unsigned int time_attrs, struct file *filp); extern int vfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len); @@ -2721,12 +2721,12 @@ static inline int path_permission(const struct path *path, int mask) return inode_permission(mnt_idmap(path->mnt), d_inode(path->dentry), mask); } -int __check_sticky(struct mnt_idmap *idmap, struct inode *dir, +int __check_sticky(const struct mnt_idmap *idmap, struct inode *dir, struct inode *inode); -int may_delete_dentry(struct mnt_idmap *idmap, struct inode *dir, +int may_delete_dentry(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *victim, bool isdir); -int may_create_dentry(struct mnt_idmap *idmap, +int may_create_dentry(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *child); static inline bool execute_ok(struct inode *inode) @@ -3578,7 +3578,7 @@ static inline bool is_sxid(umode_t mode) return mode & (S_ISUID | S_ISGID); } -static inline int check_sticky(struct mnt_idmap *idmap, +static inline int check_sticky(const struct mnt_idmap *idmap, struct inode *dir, struct inode *inode) { if (!(dir->i_mode & S_ISVTX)) diff --git a/include/linux/namei.h b/include/linux/namei.h index 86d657b24fc6a1..da5b4ccca1b52d 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -70,24 +70,24 @@ extern struct dentry *try_lookup_noperm(struct qstr *, struct dentry *); extern struct dentry *lookup_noperm(struct qstr *, struct dentry *); extern struct dentry *lookup_noperm_unlocked(struct qstr *, struct dentry *); extern struct dentry *lookup_noperm_positive_unlocked(struct qstr *, struct dentry *); -struct dentry *lookup_one(struct mnt_idmap *, struct qstr *, struct dentry *); -struct dentry *lookup_one_unlocked(struct mnt_idmap *idmap, +struct dentry *lookup_one(const struct mnt_idmap *, struct qstr *, struct dentry *); +struct dentry *lookup_one_unlocked(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base); -struct dentry *lookup_one_positive_unlocked(struct mnt_idmap *idmap, +struct dentry *lookup_one_positive_unlocked(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base); -struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap, +struct dentry *lookup_one_positive_killable(const struct mnt_idmap *idmap, struct qstr *name, struct dentry *base); -struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent, +struct dentry *start_creating(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name); -struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent, +struct dentry *start_removing(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name); -struct dentry *start_creating_killable(struct mnt_idmap *idmap, +struct dentry *start_creating_killable(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name); -struct dentry *start_removing_killable(struct mnt_idmap *idmap, +struct dentry *start_removing_killable(const struct mnt_idmap *idmap, struct dentry *parent, struct qstr *name); struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name); From 025756ee7b476241270dd8ccbe17cc4c473800fa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:51 +0200 Subject: [PATCH 0777/2286] fs: port mnt_idmap() and file_mnt_idmap() to const mnt_idmap Now that everything takes a mnt_idmap as const store a const pointer in struct vfsmount, struct mount_kattr and struct kstatmount and return one from mnt_idmap(), file_mnt_idmap() and ovl_upper_mnt_idmap(). Finally, also make mnt_idmap_get() return a const pointer. Also convert the remaining local variables that are initialized from the accessors. alloc_mnt_idmap() keeps returning a non-const pointer. It is the only place where an idmapping is actually written to. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-26-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/porting.rst | 13 +++++++++++++ fs/backing-file.c | 2 +- fs/btrfs/ioctl.c | 4 ++-- fs/ceph/file.c | 2 +- fs/coredump.c | 2 +- fs/exec.c | 4 ++-- fs/ext4/ioctl.c | 2 +- fs/f2fs/file.c | 6 +++--- fs/fhandle.c | 2 +- fs/file_attr.c | 4 ++-- fs/fuse/dir.c | 2 +- fs/fuse/file.c | 2 +- fs/internal.h | 2 +- fs/mnt_idmapping.c | 4 ++-- fs/namei.c | 14 +++++++------- fs/namespace.c | 8 ++++---- fs/open.c | 4 ++-- fs/overlayfs/file.c | 2 +- fs/overlayfs/inode.c | 2 +- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/util.c | 4 ++-- fs/remap_range.c | 2 +- fs/smb/server/oplock.c | 2 +- fs/smb/server/smb2pdu.c | 20 ++++++++++---------- fs/smb/server/smb_common.c | 2 +- fs/smb/server/smbacl.c | 6 +++--- fs/smb/server/vfs.c | 10 +++++----- fs/stat.c | 2 +- include/linux/fs.h | 2 +- include/linux/mnt_idmapping.h | 2 +- include/linux/mount.h | 4 ++-- net/unix/af_unix.c | 2 +- security/apparmor/lsm.c | 4 ++-- 33 files changed, 79 insertions(+), 66 deletions(-) diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index 60880eb0c49d1a..b967a129e3800a 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -1409,3 +1409,16 @@ use only if you have no alternative. The .create inode_operation no longer receives the 'excl' arg. It must always assume the file does not already exist. If the filesystem needs to be involved in non-exclusive create, it should provide atomic_open. + +--- + +**mandatory** + +All struct mnt_idmap pointers handed to filesystems are const now. +->create(), ->mkdir(), ->mknod(), ->symlink(), ->rename(), ->setattr(), +->getattr(), ->permission(), ->tmpfile(), ->get_acl(), ->set_acl() and +->fileattr_set() as well as the xattr ->set() handler and the vfs_*() +helpers take a const struct mnt_idmap *. mnt_idmap() and file_mnt_idmap() +return one. The idmapping is immutable so nothing should have modified it +anyway. References are taken and dropped via mnt_idmap_get() and +mnt_idmap_put() as before, both accept a const pointer. diff --git a/fs/backing-file.c b/fs/backing-file.c index cc101143f92129..5614cb7801e1b5 100644 --- a/fs/backing-file.c +++ b/fs/backing-file.c @@ -59,7 +59,7 @@ struct file *backing_tmpfile_open(const struct file *user_file, int flags, const struct path *real_parentpath, umode_t mode, const struct cred *cred) { - struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt); + const struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt); const struct path *user_path = &user_file->f_path; struct file *f; int error; diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index e20e66c0fead31..efeb5561015ced 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -1149,7 +1149,7 @@ static noinline int __btrfs_ioctl_snap_create(struct file *file, { int ret; struct qstr qname = QSTR(name); - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); if (!S_ISDIR(file_inode(file)->i_mode)) return -ENOTDIR; @@ -2226,7 +2226,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file, struct btrfs_root *dest = NULL; struct btrfs_ioctl_vol_args AUTO_KFREE(vol_args); struct btrfs_ioctl_vol_args_v2 AUTO_KFREE(vol_args2); - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); char *subvol_name, *subvol_name_ptr = NULL; int ret = 0; bool destroy_parent = false; diff --git a/fs/ceph/file.c b/fs/ceph/file.c index bd3e3f5c269e89..2c994c08ed4b3e 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -795,7 +795,7 @@ static int ceph_finish_async_create(struct inode *dir, struct inode *inode, int ceph_atomic_open(struct inode *dir, struct dentry *dentry, struct file *file, unsigned flags, umode_t mode) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct ceph_fs_client *fsc = ceph_sb_to_fs_client(dir->i_sb); struct ceph_client *cl = fsc->client; struct ceph_mds_client *mdsc = fsc->mdsc; diff --git a/fs/coredump.c b/fs/coredump.c index ac3cd74808c640..9f75e16d3b3642 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -878,7 +878,7 @@ static inline bool coredump_force_suid_safe(const struct coredump_params *cprm) static bool coredump_file(struct core_name *cn, struct coredump_params *cprm, const struct linux_binfmt *binfmt) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct inode *inode; struct file *file __free(fput) = NULL; int open_flags = O_CREAT | O_WRONLY | O_NOFOLLOW | O_LARGEFILE | O_EXCL; diff --git a/fs/exec.c b/fs/exec.c index 745f6eb5279e65..a7785559d0d32f 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1339,7 +1339,7 @@ EXPORT_SYMBOL(begin_new_exec); void would_dump(struct linux_binprm *bprm, struct file *file) { struct inode *inode = file_inode(file); - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); if (inode_permission(idmap, inode, MAY_READ) < 0) { struct user_namespace *old, *user_ns; bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP; @@ -1623,7 +1623,7 @@ static void check_unsafe_exec(struct linux_binprm *bprm) static void bprm_fill_uid(struct linux_binprm *bprm, struct file *file) { /* Handle suid and sgid on files */ - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct inode *inode = file_inode(file); unsigned int mode; vfsuid_t vfsuid; diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index 2719f20493c87e..0a54b00e5be5a5 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -1539,7 +1539,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = file_inode(filp); struct super_block *sb = inode->i_sb; - struct mnt_idmap *idmap = file_mnt_idmap(filp); + const struct mnt_idmap *idmap = file_mnt_idmap(filp); ext4_debug("cmd = %u, arg = %lu\n", cmd, arg); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index fb8b03a9972d9b..8596c3bc4704a0 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -2361,7 +2361,7 @@ static int f2fs_ioc_getversion(struct file *filp, unsigned long arg) static int f2fs_ioc_start_atomic_write(struct file *filp, bool truncate) { struct inode *inode = file_inode(filp); - struct mnt_idmap *idmap = file_mnt_idmap(filp); + const struct mnt_idmap *idmap = file_mnt_idmap(filp); struct f2fs_inode_info *fi = F2FS_I(inode); struct f2fs_sb_info *sbi = F2FS_I_SB(inode); loff_t isize; @@ -2473,7 +2473,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp, bool truncate) static int f2fs_ioc_commit_atomic_write(struct file *filp) { struct inode *inode = file_inode(filp); - struct mnt_idmap *idmap = file_mnt_idmap(filp); + const struct mnt_idmap *idmap = file_mnt_idmap(filp); int ret; if (!(filp->f_mode & FMODE_WRITE)) @@ -2508,7 +2508,7 @@ static int f2fs_ioc_commit_atomic_write(struct file *filp) static int f2fs_ioc_abort_atomic_write(struct file *filp) { struct inode *inode = file_inode(filp); - struct mnt_idmap *idmap = file_mnt_idmap(filp); + const struct mnt_idmap *idmap = file_mnt_idmap(filp); int ret; if (!(filp->f_mode & FMODE_WRITE)) diff --git a/fs/fhandle.c b/fs/fhandle.c index f8829231e3d758..2aa55b8a878a90 100644 --- a/fs/fhandle.c +++ b/fs/fhandle.c @@ -201,7 +201,7 @@ static int vfs_dentry_acceptable(void *context, struct dentry *dentry) struct handle_to_path_ctx *ctx = context; struct user_namespace *user_ns = current_user_ns(); struct dentry *d, *root = ctx->root.dentry; - struct mnt_idmap *idmap = mnt_idmap(ctx->root.mnt); + const struct mnt_idmap *idmap = mnt_idmap(ctx->root.mnt); int retval = 0; if (!root) diff --git a/fs/file_attr.c b/fs/file_attr.c index 5393c4dfa4a86e..81af4364e33ae7 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -323,7 +323,7 @@ int ioctl_getflags(struct file *file, unsigned int __user *argp) int ioctl_setflags(struct file *file, unsigned int __user *argp) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct dentry *dentry = file->f_path.dentry; struct file_kattr fa = {}; unsigned int flags; @@ -355,7 +355,7 @@ int ioctl_fsgetxattr(struct file *file, void __user *argp) int ioctl_fssetxattr(struct file *file, void __user *argp) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct dentry *dentry = file->f_path.dentry; struct file_kattr fa = {}; int err; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 6d23878cfae5f0..1759f7fd72b0de 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -941,7 +941,7 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry, umode_t mode) { int err; - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct fuse_conn *fc = get_fuse_conn(dir); if (fuse_is_bad(dir)) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 8d6135a6108a4a..6ae350f8b48623 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1487,7 +1487,7 @@ static const struct iomap_write_ops fuse_iomap_write_ops = { static ssize_t fuse_cache_write_iter(struct kiocb *iocb, struct iov_iter *from) { struct file *file = iocb->ki_filp; - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct address_space *mapping = file->f_mapping; ssize_t written = 0; struct inode *inode = mapping->host; diff --git a/fs/internal.h b/fs/internal.h index 973436f44770dd..3dda57fb3c7d86 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -327,7 +327,7 @@ ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *po * fs/attr.c */ struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns); -struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); +const struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); void mnt_idmap_put(const struct mnt_idmap *idmap); struct stashed_operations { struct dentry *(*stash_dentry)(struct dentry **stashed, diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c index 4d5fbf18560895..26704eeb7318d2 100644 --- a/fs/mnt_idmapping.c +++ b/fs/mnt_idmapping.c @@ -312,14 +312,14 @@ struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns) * * Return: @idmap with reference count bumped if @not_mnt_idmap isn't passed. */ -struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap) +const struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap) { struct mnt_idmap *nonconst_idmap = (struct mnt_idmap *)idmap; if (idmap != &nop_mnt_idmap && idmap != &invalid_mnt_idmap) refcount_inc(&nonconst_idmap->count); - return nonconst_idmap; + return idmap; } EXPORT_SYMBOL_GPL(mnt_idmap_get); diff --git a/fs/namei.c b/fs/namei.c index 14970fe083879b..818444a52b8872 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1273,7 +1273,7 @@ fs_initcall(init_fs_namei_sysctls); */ static inline int may_follow_link(struct nameidata *nd, const struct inode *inode) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; vfsuid_t vfsuid; if (!sysctl_protected_symlinks) @@ -2596,7 +2596,7 @@ static int link_path_walk(const char *name, struct nameidata *nd) /* At this point we know we have a real path component. */ for(;;) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; const char *link; unsigned long lastword; @@ -4432,7 +4432,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, const struct open_flags *op) { struct delegated_inode delegated_inode = { }; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct dentry *dir = nd->path.dentry; struct inode *dir_inode = dir->d_inode; int open_flag; @@ -4789,7 +4789,7 @@ static const char *open_last_lookups(struct nameidata *nd, static int do_open(struct nameidata *nd, struct file *file, const struct open_flags *op) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; int open_flag = op->open_flag; bool do_truncate; int acc_mode; @@ -5169,7 +5169,7 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode, struct dentry *orig_dentry = dentry; struct dentry *dir = dentry->d_parent; struct inode *dir_inode = d_inode(dir); - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; int error, create_error; file = alloc_empty_file(flags, cred); @@ -5294,7 +5294,7 @@ int filename_mknodat(int dfd, struct filename *name, umode_t mode, unsigned int dev) { struct delegated_inode di = { }; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct dentry *dentry; struct path path; int error; @@ -5949,7 +5949,7 @@ EXPORT_SYMBOL(vfs_link); int filename_linkat(int olddfd, struct filename *old, int newdfd, struct filename *new, int flags) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct dentry *new_dentry; struct path old_path, new_path; struct delegated_inode delegated_inode = { }; diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b335..919c27724a5955 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -109,7 +109,7 @@ struct mount_kattr { unsigned int lookup_flags; enum mount_kattr_flags_t kflags; struct user_namespace *mnt_userns; - struct mnt_idmap *mnt_idmap; + const struct mnt_idmap *mnt_idmap; }; /* /sys/fs */ @@ -4898,7 +4898,7 @@ static int mount_setattr_prepare(struct mount_kattr *kattr, struct mount *mnt) static void do_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt) { - struct mnt_idmap *old_idmap; + const struct mnt_idmap *old_idmap; if (!kattr->mnt_idmap) return; @@ -4941,7 +4941,7 @@ static int do_mount_setattr(const struct path *path, struct mount_kattr *kattr) return -EINVAL; if (kattr->mnt_userns) { - struct mnt_idmap *mnt_idmap; + const struct mnt_idmap *mnt_idmap; mnt_idmap = alloc_mnt_idmap(kattr->mnt_userns); if (IS_ERR(mnt_idmap)) @@ -5246,7 +5246,7 @@ struct kstatmount { struct statmount __user *buf; size_t bufsize; struct vfsmount *mnt; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; u64 mask; struct path root; struct seq_file seq; diff --git a/fs/open.c b/fs/open.c index 971e51008e6752..031858d77fcc43 100644 --- a/fs/open.c +++ b/fs/open.c @@ -72,7 +72,7 @@ int do_truncate(const struct mnt_idmap *idmap, struct dentry *dentry, int vfs_truncate(const struct path *path, loff_t length) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct inode *inode; int error; @@ -787,7 +787,7 @@ static inline bool setattr_vfsgid(struct iattr *attr, kgid_t kgid) int chown_common(const struct path *path, uid_t user, gid_t group) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct user_namespace *fs_userns; struct inode *inode = path->dentry->d_inode; struct delegated_inode delegated_inode = { }; diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index f3d97eb146e85b..7433220d4ad6c4 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -30,7 +30,7 @@ static struct file *ovl_open_realfile(const struct file *file, { struct inode *realinode = d_inode(realpath->dentry); struct inode *inode = file_inode(file); - struct mnt_idmap *real_idmap; + const struct mnt_idmap *real_idmap; struct file *realfile; int flags = file->f_flags | OVL_OPEN_FLAGS; int acc_mode = ACC_MODE(flags); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 622e9bb833affd..70183d516e5e96 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -406,7 +406,7 @@ struct posix_acl *ovl_get_acl_path(const struct path *path, const char *acl_name, bool noperm) { struct posix_acl *real_acl, *clone; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct inode *realinode = d_inode(path->dentry); idmap = mnt_idmap(path->mnt); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 80cad4ea96a3ed..ac07e8769f9be6 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -105,7 +105,7 @@ static inline struct vfsmount *ovl_upper_mnt(struct ovl_fs *ofs) return ofs->layers[0].mnt; } -static inline struct mnt_idmap *ovl_upper_mnt_idmap(struct ovl_fs *ofs) +static inline const struct mnt_idmap *ovl_upper_mnt_idmap(struct ovl_fs *ofs) { return mnt_idmap(ovl_upper_mnt(ofs)); } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index b41f4788e4f06b..521717209b2e44 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -657,7 +657,7 @@ bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path) struct file *ovl_path_open(const struct path *path, int flags) { struct inode *inode = d_inode(path->dentry); - struct mnt_idmap *real_idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *real_idmap = mnt_idmap(path->mnt); int err, acc_mode; if (flags & ~(O_ACCMODE | O_LARGEFILE)) @@ -1496,7 +1496,7 @@ void ovl_copyattr(struct inode *inode) { struct path realpath; struct inode *realinode; - struct mnt_idmap *real_idmap; + const struct mnt_idmap *real_idmap; vfsuid_t vfsuid; vfsgid_t vfsgid; diff --git a/fs/remap_range.c b/fs/remap_range.c index 26afbbbfb10c2e..6eb7d845de5d4b 100644 --- a/fs/remap_range.c +++ b/fs/remap_range.c @@ -415,7 +415,7 @@ EXPORT_SYMBOL(vfs_clone_file_range); /* Check whether we are allowed to dedupe the destination file */ static bool may_dedupe_file(struct file *file) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct inode *inode = file_inode(file); if (capable(CAP_SYS_ADMIN)) diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 58af0fddf39f2d..341a812da3a8c0 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -2229,7 +2229,7 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp) { struct create_posix_rsp *buf; struct inode *inode = file_inode(fp->filp); - struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode); vfsgid_t vfsgid = i_gid_into_vfsgid(idmap, inode); diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c index 0789200d602f8e..e2dcf8228fa4c0 100644 --- a/fs/smb/server/smb2pdu.c +++ b/fs/smb/server/smb2pdu.c @@ -3234,7 +3234,7 @@ static bool smb2_is_private_ea(const char *name, size_t name_len) static int smb2_set_ea(struct smb2_ea_info *eabuf, unsigned int buf_len, const struct path *path, bool get_write) { - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); char *attr_name = NULL, *value; int rc = 0; unsigned int next = 0; @@ -3335,7 +3335,7 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path, struct ksmbd_file *fp, char *stream_name, int s_type) { - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); size_t xattr_stream_size; char *xattr_stream_name; int rc; @@ -3411,7 +3411,7 @@ static loff_t ksmbd_stream_eof(struct ksmbd_file *fp) static int smb2_remove_smb_xattrs(const struct path *path) { - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); char *name, *xattr_list = NULL; ssize_t xattr_list_len; int err = 0; @@ -4088,7 +4088,7 @@ int smb2_open(struct ksmbd_work *work) struct ksmbd_share_config *share = tcon->share_conf; struct ksmbd_file *fp = NULL; struct file *filp = NULL; - struct mnt_idmap *idmap = NULL; + const struct mnt_idmap *idmap = NULL; struct kstat stat; struct create_context *context; struct lease_ctx_info *lc = NULL; @@ -5753,7 +5753,7 @@ struct smb2_query_dir_private { static int process_query_dir_entries(struct smb2_query_dir_private *priv) { - struct mnt_idmap *idmap = file_mnt_idmap(priv->dir_fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(priv->dir_fp->filp); struct kstat kstat; struct ksmbd_kstat ksmbd_kstat; int rc; @@ -6352,7 +6352,7 @@ static int smb2_get_ea(struct ksmbd_work *work, struct ksmbd_file *fp, ssize_t buf_free_len, alignment_bytes, next_offset, rsp_data_cnt = 0; struct smb2_ea_info_req *ea_req = NULL; const struct path *path; - struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); if (!(fp->daccess & FILE_READ_EA_LE)) { pr_err("Not permitted to read ext attr : 0x%x\n", @@ -7062,7 +7062,7 @@ static int find_file_posix_info(struct smb2_query_info_rsp *rsp, { struct smb311_posix_qinfo *file_info; struct inode *inode = file_inode(fp->filp); - struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode); vfsgid_t vfsgid = i_gid_into_vfsgid(idmap, inode); struct kstat stat; @@ -7547,7 +7547,7 @@ static int smb2_get_info_sec(struct ksmbd_work *work, struct smb2_query_info_rsp *rsp) { struct ksmbd_file *fp; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct smb_ntsd *pntsd = NULL, *ppntsd = NULL; struct smb_fattr fattr = {{0}}; struct inode *inode; @@ -8060,7 +8060,7 @@ static int set_file_basic_info(struct ksmbd_file *fp, struct iattr attrs; struct file *filp; struct inode *inode; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; __le32 attrs_mask = FILE_ATTRIBUTE_DIRECTORY_LE | FILE_ATTRIBUTE_COMPRESSED_LE; int rc = 0; @@ -10652,7 +10652,7 @@ static inline int fsctl_set_sparse(struct ksmbd_work *work, u64 id, struct file_sparse *sparse) { struct ksmbd_file *fp; - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; int ret = 0; __le32 old_fattr; diff --git a/fs/smb/server/smb_common.c b/fs/smb/server/smb_common.c index 4c2da65510bcd2..ec6ee56293ce5b 100644 --- a/fs/smb/server/smb_common.c +++ b/fs/smb/server/smb_common.c @@ -467,7 +467,7 @@ int ksmbd_populate_dot_dotdot_entries(struct ksmbd_work *work, int info_level, { int i, rc = 0; struct ksmbd_conn *conn = work->conn; - struct mnt_idmap *idmap = file_mnt_idmap(dir->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(dir->filp); for (i = 0; i < 2; i++) { struct kstat kstat; diff --git a/fs/smb/server/smbacl.c b/fs/smb/server/smbacl.c index 915dbfab62ea0c..784067d14f56e5 100644 --- a/fs/smb/server/smbacl.c +++ b/fs/smb/server/smbacl.c @@ -1183,7 +1183,7 @@ int smb_inherit_dacl(struct ksmbd_conn *conn, struct smb_ntsd *parent_pntsd = NULL; struct smb_sid owner_sid, group_sid; struct dentry *parent = path->dentry->d_parent; - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); int inherited_flags = 0, flags = 0, i, nt_size = 0, pdacl_size; int rc = 0, pntsd_type, ppntsd_size, acl_len, aces_size; unsigned int dacloffset; @@ -1438,7 +1438,7 @@ int smb_check_perm_dacl(struct ksmbd_conn *conn, const struct path *path, __le32 *pdaccess, __le32 raw_daccess, int uid, bool strict) { - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); struct smb_ntsd *pntsd = NULL; struct smb_acl *pdacl; struct posix_acl *posix_acls; @@ -1660,7 +1660,7 @@ int set_info_sec(struct ksmbd_conn *conn, struct ksmbd_tree_connect *tcon, int rc; struct smb_fattr fattr = {{0}}; struct inode *inode = d_inode(path->dentry); - struct mnt_idmap *idmap = mnt_idmap(path->mnt); + const struct mnt_idmap *idmap = mnt_idmap(path->mnt); struct iattr newattrs; fattr.cf_uid = INVALID_UID; diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c index 75edff8dde31ce..ccfdc726d06325 100644 --- a/fs/smb/server/vfs.c +++ b/fs/smb/server/vfs.c @@ -183,7 +183,7 @@ int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode) */ int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct path path; struct dentry *dentry, *d; int err = 0; @@ -386,7 +386,7 @@ static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos, { const struct cred *saved_cred; char *stream_buf = NULL, *wbuf; - struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); size_t size; ssize_t v_len; int err = 0; @@ -577,7 +577,7 @@ int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id) */ int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct dentry *parent = path->dentry->d_parent; int err; @@ -1202,7 +1202,7 @@ int ksmbd_vfs_unlink(struct file *filp) const struct cred *saved_cred; int err = 0; struct dentry *dir, *dentry = filp->f_path.dentry; - struct mnt_idmap *idmap = file_mnt_idmap(filp); + const struct mnt_idmap *idmap = file_mnt_idmap(filp); saved_cred = override_creds(filp->f_cred); err = mnt_want_write(filp->f_path.mnt); @@ -2316,7 +2316,7 @@ static int __ksmbd_vfs_set_compression(struct ksmbd_work *work, const struct cred *saved_cred = NULL; struct file_kattr fa; struct dentry *dentry = fp->filp->f_path.dentry; - struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); + const struct mnt_idmap *idmap = file_mnt_idmap(fp->filp); u32 flags; __le32 old_fattr; int rc; diff --git a/fs/stat.c b/fs/stat.c index fe351818d1d600..a9b7383d538da8 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -181,7 +181,7 @@ EXPORT_SYMBOL_GPL(generic_fill_statx_atomic_writes); int vfs_getattr_nosec(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct inode *inode = d_backing_inode(path->dentry); memset(stat, 0, sizeof(*stat)); diff --git a/include/linux/fs.h b/include/linux/fs.h index 935b0f92786fc3..ca3e9d4797eea1 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2459,7 +2459,7 @@ struct filename { static_assert(offsetof(struct filename, iname) % sizeof(long) == 0); static_assert(sizeof(struct filename) % 64 == 0); -static inline struct mnt_idmap *file_mnt_idmap(const struct file *file) +static inline const struct mnt_idmap *file_mnt_idmap(const struct file *file) { return mnt_idmap(file->f_path.mnt); } diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h index 6656aea23be2d9..b445c76600402b 100644 --- a/include/linux/mnt_idmapping.h +++ b/include/linux/mnt_idmapping.h @@ -121,7 +121,7 @@ static inline bool vfsgid_eq_kgid(vfsgid_t vfsgid, kgid_t kgid) int vfsgid_in_group_p(vfsgid_t vfsgid); -struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); +const struct mnt_idmap *mnt_idmap_get(const struct mnt_idmap *idmap); void mnt_idmap_put(const struct mnt_idmap *idmap); vfsuid_t make_vfsuid(const struct mnt_idmap *idmap, diff --git a/include/linux/mount.h b/include/linux/mount.h index acfe7ef86a1b3f..e90ccafef281b8 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -59,10 +59,10 @@ struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree */ struct super_block *mnt_sb; /* pointer to superblock */ int mnt_flags; - struct mnt_idmap *mnt_idmap; + const struct mnt_idmap *mnt_idmap; } __randomize_layout; -static inline struct mnt_idmap *mnt_idmap(const struct vfsmount *mnt) +static inline const struct mnt_idmap *mnt_idmap(const struct vfsmount *mnt) { /* Pairs with smp_store_release() in do_idmap_mount(). */ return READ_ONCE(mnt->mnt_idmap); diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 13f9926bf20577..56e1bf51ffe521 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1361,7 +1361,7 @@ static int unix_bind_bsd(struct sock *sk, struct sockaddr_un *sunaddr, struct unix_sock *u = unix_sk(sk); unsigned int new_hash, old_hash; struct net *net = sock_net(sk); - struct mnt_idmap *idmap; + const struct mnt_idmap *idmap; struct unix_address *addr; struct dentry *dentry; struct path parent; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index d502ad0ac26f5c..c73681d820a068 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -397,7 +397,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { - struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); + const struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); vfsuid_t vfsuid; struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; @@ -485,7 +485,7 @@ static int apparmor_file_open(struct file *file) label = aa_get_newest_cred_label_condref(file->f_cred, &needput); if (!unconfined(label)) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct inode *inode = file_inode(file); vfsuid_t vfsuid; struct path_cond cond = { From 04fcaedd250ed2213eb65eec40d379c2f275aa02 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 1 Sep 2026 14:14:52 +0200 Subject: [PATCH 0778/2286] fs: make nop_mnt_idmap and invalid_mnt_idmap const Make both static maps const so they end up in rodata. Any attempt to modify them now faults instead of silently corrupting the initial idmapping. Link: https://patch.msgid.link/20260901-work-idmap-const-v1-27-54ccd48e100b@kernel.org Reviewed-by: Jan Kara Reviewed-by: Seth Forshee Signed-off-by: Christian Brauner (Amutable) --- fs/mnt_idmapping.c | 4 ++-- include/linux/mnt_idmapping.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/mnt_idmapping.c b/fs/mnt_idmapping.c index 26704eeb7318d2..bed57094cef00a 100644 --- a/fs/mnt_idmapping.c +++ b/fs/mnt_idmapping.c @@ -28,7 +28,7 @@ struct mnt_idmap { * mapping. This means that {g,u}id 0 is mapped to {g,u}id 0, {g,u}id 1 is * mapped to {g,u}id 1, [...], {g,u}id 1000 to {g,u}id 1000, [...]. */ -struct mnt_idmap nop_mnt_idmap = { +const struct mnt_idmap nop_mnt_idmap = { .count = REFCOUNT_INIT(1), }; EXPORT_SYMBOL_GPL(nop_mnt_idmap); @@ -37,7 +37,7 @@ EXPORT_SYMBOL_GPL(nop_mnt_idmap); * Carries the invalid idmapping of a full 0-4294967295 {g,u}id range. * This means that all {g,u}ids are mapped to INVALID_VFS{G,U}ID. */ -struct mnt_idmap invalid_mnt_idmap = { +const struct mnt_idmap invalid_mnt_idmap = { .count = REFCOUNT_INIT(1), }; EXPORT_SYMBOL_GPL(invalid_mnt_idmap); diff --git a/include/linux/mnt_idmapping.h b/include/linux/mnt_idmapping.h index b445c76600402b..78eeef4c29965a 100644 --- a/include/linux/mnt_idmapping.h +++ b/include/linux/mnt_idmapping.h @@ -8,8 +8,8 @@ struct mnt_idmap; struct user_namespace; -extern struct mnt_idmap nop_mnt_idmap; -extern struct mnt_idmap invalid_mnt_idmap; +extern const struct mnt_idmap nop_mnt_idmap; +extern const struct mnt_idmap invalid_mnt_idmap; extern struct user_namespace init_user_ns; typedef struct { From 6ffe91a84f4434edea8373ce95e3f93cbc3ffa50 Mon Sep 17 00:00:00 2001 From: Yingchao Deng Date: Mon, 14 Sep 2026 15:53:37 +0800 Subject: [PATCH 0779/2286] fs: fix misleading comments in read_write.c generic_write_checks() returns the number of bytes that may be written (possibly truncated by generic_write_check_limits()), not zero. The comment was likely copied from generic_copy_file_checks(), which is the analogous function for copy_file_range() and does return 0 on success. Also, no_seek_end_llseek() and no_seek_end_llseek_size() are helpers for files that do not support SEEK_END (they only handle SEEK_SET and SEEK_CUR), not for "fixed-sized devices"; that description was copied from fixed_size_llseek(). Signed-off-by: Yingchao Deng Link: https://patch.msgid.link/918177B3298981ED+20260914075337.1739-1-dengyingchao@kylinsec.com.cn Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/read_write.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/read_write.c b/fs/read_write.c index e8c14e2760b203..4da846a2bf17b8 100644 --- a/fs/read_write.c +++ b/fs/read_write.c @@ -274,7 +274,7 @@ loff_t fixed_size_llseek(struct file *file, loff_t offset, int whence, loff_t si EXPORT_SYMBOL(fixed_size_llseek); /** - * no_seek_end_llseek - llseek implementation for fixed-sized devices + * no_seek_end_llseek - llseek implementation for files without SEEK_END * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek @@ -293,7 +293,7 @@ loff_t no_seek_end_llseek(struct file *file, loff_t offset, int whence) EXPORT_SYMBOL(no_seek_end_llseek); /** - * no_seek_end_llseek_size - llseek implementation for fixed-sized devices + * no_seek_end_llseek_size - llseek implementation for files without SEEK_END * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek @@ -1761,8 +1761,8 @@ EXPORT_SYMBOL(generic_write_checks_count); * Performs necessary checks before doing a write * * Can adjust writing position or amount of bytes to write. - * Returns appropriate error code that caller should return or - * zero in case that write should be allowed. + * Returns the number of bytes that may be written on success (which + * may be less than requested if truncated), or a negative error code. */ ssize_t generic_write_checks(struct kiocb *iocb, struct iov_iter *from) { From 30ce4b073efc0ac77fe017b1e7cb49658c62a09d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 11:09:42 +0200 Subject: [PATCH 0780/2286] file: simplify FD_PREPARE() It was originally built as an ACQUIRE-style guard but most of the infrastructure was never needed and introduced complexity that we really didn't need. Drop the ACQUIRE machinery and compute the error where the fd and file are set. No functional changes. Link: https://patch.msgid.link/20260917-work-file-fd_prepare-v1-1-b87534ca49f3@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/file.h | 99 ++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 58 deletions(-) diff --git a/include/linux/file.h b/include/linux/file.h index 27484b444d3155..2b864f45721159 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -12,6 +12,7 @@ #include #include #include +#include struct file; @@ -159,87 +160,69 @@ typedef struct fd_prepare class_fd_prepare_t; (_Generic((_fdf), struct fd_prepare: (_fdf).__file)) /* Do not use directly. */ -static inline void class_fd_prepare_destructor(const struct fd_prepare *fdf) +static __always_inline void class_fd_prepare_destructor(const struct fd_prepare *fdf) { - if (unlikely(fdf->__fd >= 0)) + if (unlikely(fdf->__fd >= 0)) { put_unused_fd(fdf->__fd); - if (unlikely(!IS_ERR_OR_NULL(fdf->__file))) fput(fdf->__file); + } } /* Do not use directly. */ -static inline int class_fd_prepare_lock_err(const struct fd_prepare *fdf) +static __always_inline struct fd_prepare __fd_prepare(int fd, struct file *file) { - if (unlikely(fdf->err)) - return fdf->err; - if (unlikely(fdf->__fd < 0)) - return fdf->__fd; - if (unlikely(IS_ERR(fdf->__file))) - return PTR_ERR(fdf->__file); - if (unlikely(!fdf->__file)) - return -ENOMEM; - return 0; + if (fd >= 0 && IS_ERR_OR_NULL(file)) { + int err = file ? PTR_ERR(file) : -ENOMEM; + + put_unused_fd(fd); + fd = err; + file = NULL; + } + return (struct fd_prepare){ + .err = fd < 0 ? fd : 0, + .__fd = fd, + .__file = file, + }; } /* - * __FD_PREPARE_INIT - Helper to initialize fd_prepare class. - * @_fd_flags: flags for get_unused_fd_flags() - * @_file_owned: expression that returns struct file * - * - * Returns a struct fd_prepare with fd, file, and err set. - * If fd allocation fails, fd will be negative and err will be set. If - * fd succeeds but file_init_expr fails, file will be ERR_PTR and err - * will be set. The err field is the single source of truth for error - * checking. - */ -#define __FD_PREPARE_INIT(_fd_flags, _file_owned) \ - ({ \ - struct fd_prepare fdf = { \ - .__fd = get_unused_fd_flags((_fd_flags)), \ - }; \ - if (likely(fdf.__fd >= 0)) \ - fdf.__file = (_file_owned); \ - fdf.err = ACQUIRE_ERR(fd_prepare, &fdf); \ - fdf; \ - }) - -/* - * FD_PREPARE - Macro to declare and initialize an fd_prepare variable. + * FD_PREPARE - Declare and initialize an fd_prepare instance. * - * Declares and initializes an fd_prepare variable with automatic - * cleanup. No separate scope required - cleanup happens when variable - * goes out of scope. + * This allocates a new fd and only evaluates @_file_owned if the + * allocation succeeded. Cleanup happens when the variable goes out of + * scope and the guard releases whichever of the descriptor and the file + * was allocated. If fd_publish() was called the fd and file are + * published and cleanup becomes a nop. * * @_fdf: name of struct fd_prepare variable to define * @_fd_flags: flags for get_unused_fd_flags() * @_file_owned: struct file to take ownership of (can be expression) */ -#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ - CLASS_INIT(fd_prepare, _fdf, __FD_PREPARE_INIT(_fd_flags, _file_owned)) +#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ + CLASS_INIT(fd_prepare, _fdf, ({ \ + int __fd = get_unused_fd_flags(_fd_flags); \ + __fd_prepare(__fd, __fd < 0 ? NULL : (_file_owned)); \ + })) + +/* Do not use directly. */ +static __always_inline int __fd_publish(struct fd_prepare *fdf) +{ + VFS_WARN_ON_ONCE(fdf->__fd < 0); + fd_install(fdf->__fd, fdf->__file); + return take_fd(fdf->__fd); +} /* * fd_publish - Publish prepared fd and file to the fd table. * @_fdf: struct fd_prepare variable */ -#define fd_publish(_fdf) \ - ({ \ - struct fd_prepare *fdp = &(_fdf); \ - VFS_WARN_ON_ONCE(fdp->err); \ - VFS_WARN_ON_ONCE(fdp->__fd < 0); \ - VFS_WARN_ON_ONCE(IS_ERR_OR_NULL(fdp->__file)); \ - fd_install(fdp->__fd, fdp->__file); \ - retain_and_null_ptr(fdp->__file); \ - take_fd(fdp->__fd); \ - }) +#define fd_publish(_fdf) __fd_publish(&(_fdf)) /* Do not use directly. */ -#define __FD_ADD(_fdf, _fd_flags, _file_owned) \ - ({ \ - FD_PREPARE(_fdf, _fd_flags, _file_owned); \ - s32 ret = _fdf.err; \ - if (likely(!ret)) \ - ret = fd_publish(_fdf); \ - ret; \ +#define __FD_ADD(_fdf, _fd_flags, _file_owned) \ + ({ \ + FD_PREPARE(_fdf, _fd_flags, _file_owned); \ + _fdf.err ?: fd_publish(_fdf); \ }) /* From cba449b765b39c0f71914bf81c04d96dd457248c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 11:09:43 +0200 Subject: [PATCH 0781/2286] file: declare the FD_PREPARE() variable with __cleanup() directly The whole CLASS_INIT() detour isn't needed. Use __cleanup() directly and give the destructor a private name. No functional changes. Link: https://patch.msgid.link/20260917-work-file-fd_prepare-v1-2-b87534ca49f3@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/file.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/include/linux/file.h b/include/linux/file.h index 2b864f45721159..926cc58d0b7f15 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -146,9 +146,6 @@ struct fd_prepare { struct file *__file; /* do not access directly */ }; -/* Typedef for fd_prepare cleanup guards. */ -typedef struct fd_prepare class_fd_prepare_t; - /* * Accessors for fd_prepare class members. * _Generic() is used for zero-cost type safety. @@ -160,7 +157,7 @@ typedef struct fd_prepare class_fd_prepare_t; (_Generic((_fdf), struct fd_prepare: (_fdf).__file)) /* Do not use directly. */ -static __always_inline void class_fd_prepare_destructor(const struct fd_prepare *fdf) +static __always_inline void __fd_prepare_cleanup(const struct fd_prepare *fdf) { if (unlikely(fdf->__fd >= 0)) { put_unused_fd(fdf->__fd); @@ -199,10 +196,10 @@ static __always_inline struct fd_prepare __fd_prepare(int fd, struct file *file) * @_file_owned: struct file to take ownership of (can be expression) */ #define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ - CLASS_INIT(fd_prepare, _fdf, ({ \ + struct fd_prepare _fdf __cleanup(__fd_prepare_cleanup) = ({ \ int __fd = get_unused_fd_flags(_fd_flags); \ __fd_prepare(__fd, __fd < 0 ? NULL : (_file_owned)); \ - })) + }) /* Do not use directly. */ static __always_inline int __fd_publish(struct fd_prepare *fdf) From 9e4f1bc488bd799f22c8087544adcee9620d0f44 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 11:09:44 +0200 Subject: [PATCH 0782/2286] cleanup: remove CLASS_INIT() CLASS_INIT() was added for FD_PREPARE() in commit 011703a9acd7 ("file: add FD_{ADD,PREPARE}()") and never got another user. FD_PREPARE() now declares its variable with __cleanup() directly. Remove CLASS_INIT(). No functional changes. Link: https://patch.msgid.link/20260917-work-file-fd_prepare-v1-3-b87534ca49f3@kernel.org Signed-off-by: Christian Brauner (Amutable) --- include/linux/cleanup.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h index b1b5698cbf1b6f..1fb8058b897d4a 100644 --- a/include/linux/cleanup.h +++ b/include/linux/cleanup.h @@ -261,10 +261,6 @@ const volatile void * __must_check_fn(const volatile void *val) * CLASS(name, var)(args...): * declare the variable @var as an instance of the named class * - * CLASS_INIT(name, var, init_expr): - * declare the variable @var as an instance of the named class with - * custom initialization expression. - * * Ex. * * DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd) @@ -302,9 +298,6 @@ static __always_inline class_##_name##_t class_##_name##ext##_constructor(_init_ class_##_name##_t var __cleanup(class_##_name##_destructor) = \ class_##_name##_constructor -#define CLASS_INIT(_name, _var, _init_expr) \ - class_##_name##_t _var __cleanup(class_##_name##_destructor) = (_init_expr) - #define __scoped_class(_name, var, _label, args...) \ for (CLASS(_name, var)(args); ; ({ goto _label; })) \ if (0) { \ From 64cbb36448ff22ce8400744e296888efef1d1c04 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 17 Sep 2026 11:09:45 +0200 Subject: [PATCH 0783/2286] file: make struct fd_prepare const and kill its err field FD_PREPARE() releases the fd as soon as the file expression fails. The fd either holds the descriptor or the error. The separate field is redundant. Make fd and file plain members and struct fd_prepare simpler. Callers of FD_PREPARE() get a const pointer to the guard. Simplify all users as they can now easily access the trivial struct. No functional changes. Link: https://patch.msgid.link/20260917-work-file-fd_prepare-v1-4-b87534ca49f3@kernel.org Signed-off-by: Christian Brauner (Amutable) --- arch/powerpc/platforms/cell/spufs/inode.c | 12 ++--- drivers/gpio/gpiolib-cdev.c | 18 +++---- drivers/gpu/drm/msm/msm_perfcntr.c | 4 +- drivers/media/mc/mc-request.c | 8 +-- drivers/misc/ntsync.c | 6 +-- fs/eventfd.c | 4 +- fs/eventpoll.c | 6 +-- fs/file.c | 8 +-- fs/namespace.c | 12 ++--- fs/nsfs.c | 4 +- fs/xfs/xfs_handle.c | 6 +-- include/linux/file.h | 66 ++++++++++------------- io_uring/mock_file.c | 8 +-- kernel/bpf/bpf_iter.c | 6 +-- kernel/bpf/token.c | 6 +-- mm/userfaultfd.c | 6 +-- net/core/scm.c | 8 +-- net/handshake/netlink.c | 8 +-- net/kcm/kcmsock.c | 6 +-- 19 files changed, 95 insertions(+), 107 deletions(-) diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c index 2b54afb31529f1..5c06f69c9f7f9e 100644 --- a/arch/powerpc/platforms/cell/spufs/inode.c +++ b/arch/powerpc/platforms/cell/spufs/inode.c @@ -266,9 +266,9 @@ spufs_mkdir(struct inode *dir, struct dentry *dentry, unsigned int flags, static int spufs_context_open(const struct path *path) { FD_PREPARE(fdf, 0, dentry_open(path, O_RDONLY, current_cred())); - if (fdf.err) - return fdf.err; - fd_prepare_file(fdf)->f_op = &spufs_context_fops; + if (fdf->fd < 0) + return fdf->fd; + fdf->file->f_op = &spufs_context_fops; return fd_publish(fdf); } @@ -499,9 +499,9 @@ static int spufs_gang_open(const struct path *path) * in error path of *_open(). */ FD_PREPARE(fdf, 0, dentry_open(path, O_RDONLY, current_cred())); - if (fdf.err) - return fdf.err; - fd_prepare_file(fdf)->f_op = &spufs_gang_fops; + if (fdf->fd < 0) + return fdf->fd; + fdf->file->f_op = &spufs_gang_fops; return fd_publish(fdf); } diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c index 9f3b628d57934b..6526ce27ea4466 100644 --- a/drivers/gpio/gpiolib-cdev.c +++ b/drivers/gpio/gpiolib-cdev.c @@ -377,11 +377,11 @@ static int linehandle_create(struct gpio_device *gdev, void __user *ip) FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC, anon_inode_getfile("gpio-linehandle", &linehandle_fileops, lh, O_RDONLY | O_CLOEXEC)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; retain_and_null_ptr(lh); - handlereq.fd = fd_prepare_fd(fdf); + handlereq.fd = fdf->fd; if (copy_to_user(ip, &handlereq, sizeof(handlereq))) return -EFAULT; @@ -1715,11 +1715,11 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip) FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC, anon_inode_getfile("gpio-line", &line_fileops, lr, O_RDONLY | O_CLOEXEC)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; retain_and_null_ptr(lr); - ulr.fd = fd_prepare_fd(fdf); + ulr.fd = fdf->fd; if (copy_to_user(ip, &ulr, sizeof(ulr))) return -EFAULT; @@ -2115,11 +2115,11 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip) FD_PREPARE(fdf, O_RDONLY | O_CLOEXEC, anon_inode_getfile("gpio-event", &lineevent_fileops, le, O_RDONLY | O_CLOEXEC)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; retain_and_null_ptr(le); - eventreq.fd = fd_prepare_fd(fdf); + eventreq.fd = fdf->fd; if (copy_to_user(ip, &eventreq, sizeof(eventreq))) return -EFAULT; diff --git a/drivers/gpu/drm/msm/msm_perfcntr.c b/drivers/gpu/drm/msm/msm_perfcntr.c index ce65b1160955e2..7fa2e858bd0823 100644 --- a/drivers/gpu/drm/msm/msm_perfcntr.c +++ b/drivers/gpu/drm/msm/msm_perfcntr.c @@ -543,8 +543,8 @@ msm_ioctl_perfcntr_config(struct drm_device *dev, void *data, struct drm_file *f FD_PREPARE(fdf, O_CLOEXEC, anon_inode_getfile("[msm_perfcntrs]", &stream_fops, stream, 0)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; INIT_WORK(&stream->sel_work, sel_worker); kthread_init_work(&stream->sample_work, sample_worker); diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c index 13e77648807c29..e1387f039780b7 100644 --- a/drivers/media/mc/mc-request.c +++ b/drivers/media/mc/mc-request.c @@ -316,15 +316,15 @@ int media_request_alloc(struct media_device *mdev, int *alloc_fd) FD_PREPARE(fdf, O_CLOEXEC, anon_inode_getfile("request", &request_fops, NULL, O_CLOEXEC)); - if (fdf.err) { - ret = fdf.err; + if (fdf->fd < 0) { + ret = fdf->fd; goto err_free_req; } - fd_prepare_file(fdf)->private_data = req; + fdf->file->private_data = req; snprintf(req->debug_str, sizeof(req->debug_str), "%u:%d", - atomic_inc_return(&mdev->request_id), fd_prepare_fd(fdf)); + atomic_inc_return(&mdev->request_id), fdf->fd); atomic_inc(&mdev->num_requests); dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str); diff --git a/drivers/misc/ntsync.c b/drivers/misc/ntsync.c index 4a805919bb0c59..2857ae37d3c81c 100644 --- a/drivers/misc/ntsync.c +++ b/drivers/misc/ntsync.c @@ -724,9 +724,9 @@ static int ntsync_obj_get_fd(struct ntsync_obj *obj) { FD_PREPARE(fdf, O_CLOEXEC, anon_inode_getfile("ntsync", &ntsync_obj_fops, obj, O_RDWR)); - if (fdf.err) - return fdf.err; - obj->file = fd_prepare_file(fdf); + if (fdf->fd < 0) + return fdf->fd; + obj->file = fdf->file; return fd_publish(fdf); } diff --git a/fs/eventfd.c b/fs/eventfd.c index 9d33a02757d52b..52426795752ece 100644 --- a/fs/eventfd.c +++ b/fs/eventfd.c @@ -403,8 +403,8 @@ static int do_eventfd(unsigned int count, int flags) FD_PREPARE(fdf, flags, anon_inode_getfile_fmode("[eventfd]", &eventfd_fops, ctx, flags, FMODE_NOWAIT)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; ctx->id = ida_alloc(&eventfd_ida, GFP_KERNEL); retain_and_null_ptr(ctx); diff --git a/fs/eventpoll.c b/fs/eventpoll.c index e0c4bf88a83836..f48b829a710f75 100644 --- a/fs/eventpoll.c +++ b/fs/eventpoll.c @@ -2514,11 +2514,11 @@ static int do_epoll_create(int flags) FD_PREPARE(fdf, O_RDWR | (flags & O_CLOEXEC), anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC))); - if (fdf.err) { + if (fdf->fd < 0) { ep_clear_and_put(ep); - return fdf.err; + return fdf->fd; } - ep->file = fd_prepare_file(fdf); + ep->file = fdf->file; return fd_publish(fdf); } diff --git a/fs/file.c b/fs/file.c index 9c7001b901cfaa..173d5569818e34 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1391,17 +1391,17 @@ int receive_fd(struct file *file, int __user *ufd, unsigned int o_flags) return error; FD_PREPARE(fdf, o_flags, file); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; get_file(file); if (ufd) { - error = put_user(fd_prepare_fd(fdf), ufd); + error = put_user(fdf->fd, ufd); if (error) return error; } - __receive_sock(fd_prepare_file(fdf)); + __receive_sock(fdf->file); return fd_publish(fdf); } EXPORT_SYMBOL_GPL(receive_fd); diff --git a/fs/namespace.c b/fs/namespace.c index 1ecd96c918b335..0f5c00816775b5 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4544,16 +4544,16 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, FD_PREPARE(fdf, (flags & FSMOUNT_CLOEXEC) ? O_CLOEXEC : 0, dentry_open(&new_path, O_PATH, fc->cred)); - if (fdf.err) { + if (fdf->fd < 0) { dissolve_on_fput(new_path.mnt); - return fdf.err; + return fdf->fd; } /* * Attach to an apparent O_PATH fd with a note that we * need to unmount it, not just simply put it. */ - fd_prepare_file(fdf)->f_mode |= FMODE_NEED_UNMOUNT; + fdf->file->f_mode |= FMODE_NEED_UNMOUNT; return fd_publish(fdf); } @@ -5198,12 +5198,12 @@ SYSCALL_DEFINE5(open_tree_attr, int, dfd, const char __user *, filename, return -EINVAL; FD_PREPARE(fdf, flags, vfs_open_tree(dfd, filename, flags)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; if (uattr) { struct mount_kattr kattr = {}; - struct file *file = fd_prepare_file(fdf); + struct file *file = fdf->file; int ret; if (flags & OPEN_TREE_CLONE) diff --git a/fs/nsfs.c b/fs/nsfs.c index c3b6ae76594a81..56ea0bb9ef3a34 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -348,8 +348,8 @@ static long ns_ioctl(struct file *filp, unsigned int ioctl, return ret; FD_PREPARE(fdf, O_CLOEXEC, dentry_open(&path, O_RDONLY, current_cred())); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; /* * If @uinfo is passed return all information about the * mount namespace as well. diff --git a/fs/xfs/xfs_handle.c b/fs/xfs/xfs_handle.c index 0689cade8f74c2..303bcff644596d 100644 --- a/fs/xfs/xfs_handle.c +++ b/fs/xfs/xfs_handle.c @@ -272,11 +272,11 @@ xfs_open_by_handle( path.mnt = mntget(parfilp->f_path.mnt); FD_PREPARE(fdf, 0, dentry_open(&path, hreq->oflags, cred)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; if (S_ISREG(inode->i_mode)) { - struct file *filp = fd_prepare_file(fdf); + struct file *filp = fdf->file; filp->f_flags |= O_NOATIME; filp->f_mode |= FMODE_NOCMTIME; diff --git a/include/linux/file.h b/include/linux/file.h index 926cc58d0b7f15..41c3c0be106441 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -130,38 +130,26 @@ extern unsigned int sysctl_nr_open_min, sysctl_nr_open_max; /* * fd_prepare: Combined fd + file allocation cleanup class. - * @err: Error code to indicate if allocation succeeded. - * @__fd: Allocated fd (may not be accessed directly) - * @__file: Allocated struct file pointer (may not be accessed directly) + * @fd: Allocated fd + * @file: Allocated struct file pointer * * Allocates an fd and a file together. On error paths, automatically cleans * up whichever resource was successfully allocated. Allows flexible file * allocation with different functions per usage. * - * Do not use directly. + * Do not declare directly, use FD_PREPARE(). */ struct fd_prepare { - s32 err; - s32 __fd; /* do not access directly */ - struct file *__file; /* do not access directly */ + int fd; + struct file *file; }; -/* - * Accessors for fd_prepare class members. - * _Generic() is used for zero-cost type safety. - */ -#define fd_prepare_fd(_fdf) \ - (_Generic((_fdf), struct fd_prepare: (_fdf).__fd)) - -#define fd_prepare_file(_fdf) \ - (_Generic((_fdf), struct fd_prepare: (_fdf).__file)) - /* Do not use directly. */ static __always_inline void __fd_prepare_cleanup(const struct fd_prepare *fdf) { - if (unlikely(fdf->__fd >= 0)) { - put_unused_fd(fdf->__fd); - fput(fdf->__file); + if (unlikely(fdf->fd >= 0)) { + put_unused_fd(fdf->fd); + fput(fdf->file); } } @@ -175,11 +163,7 @@ static __always_inline struct fd_prepare __fd_prepare(int fd, struct file *file) fd = err; file = NULL; } - return (struct fd_prepare){ - .err = fd < 0 ? fd : 0, - .__fd = fd, - .__file = file, - }; + return (struct fd_prepare){ .fd = fd, .file = file }; } /* @@ -191,35 +175,39 @@ static __always_inline struct fd_prepare __fd_prepare(int fd, struct file *file) * was allocated. If fd_publish() was called the fd and file are * published and cleanup becomes a nop. * - * @_fdf: name of struct fd_prepare variable to define + * @_fdf: name of the const struct fd_prepare pointer to define * @_fd_flags: flags for get_unused_fd_flags() * @_file_owned: struct file to take ownership of (can be expression) */ -#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ - struct fd_prepare _fdf __cleanup(__fd_prepare_cleanup) = ({ \ +#define __FD_PREPARE(_guard, _fdf, _fd_flags, _file_owned) \ + struct fd_prepare _guard __cleanup(__fd_prepare_cleanup) = ({ \ int __fd = get_unused_fd_flags(_fd_flags); \ __fd_prepare(__fd, __fd < 0 ? NULL : (_file_owned)); \ - }) + }); \ + const struct fd_prepare *const _fdf = &_guard -/* Do not use directly. */ -static __always_inline int __fd_publish(struct fd_prepare *fdf) -{ - VFS_WARN_ON_ONCE(fdf->__fd < 0); - fd_install(fdf->__fd, fdf->__file); - return take_fd(fdf->__fd); -} +#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \ + __FD_PREPARE(__UNIQUE_ID(fd_prepare), _fdf, _fd_flags, _file_owned) /* * fd_publish - Publish prepared fd and file to the fd table. - * @_fdf: struct fd_prepare variable + * @fdf: struct fd_prepare pointer defined by FD_PREPARE() */ -#define fd_publish(_fdf) __fd_publish(&(_fdf)) +static __always_inline int fd_publish(const struct fd_prepare *fdf) +{ + /* Callers only get a const view, the guard itself is writable. */ + struct fd_prepare *guard = (struct fd_prepare *)fdf; + + VFS_WARN_ON_ONCE(guard->fd < 0); + fd_install(guard->fd, guard->file); + return take_fd(guard->fd); +} /* Do not use directly. */ #define __FD_ADD(_fdf, _fd_flags, _file_owned) \ ({ \ FD_PREPARE(_fdf, _fd_flags, _file_owned); \ - _fdf.err ?: fd_publish(_fdf); \ + _fdf->fd < 0 ? _fdf->fd : fd_publish(_fdf); \ }) /* diff --git a/io_uring/mock_file.c b/io_uring/mock_file.c index b318ed697998e4..9f0b4d850c12f8 100644 --- a/io_uring/mock_file.c +++ b/io_uring/mock_file.c @@ -257,17 +257,17 @@ static int io_create_mock_file(struct io_uring_cmd *cmd, unsigned int issue_flag FD_PREPARE(fdf, O_RDWR | O_CLOEXEC, anon_inode_create_getfile("[io_uring_mock]", fops, mf, O_RDWR | O_CLOEXEC, NULL)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; retain_and_null_ptr(mf); - file = fd_prepare_file(fdf); + file = fdf->file; file->f_mode |= FMODE_READ | FMODE_CAN_READ | FMODE_WRITE | FMODE_CAN_WRITE | FMODE_LSEEK; if (mc.flags & IORING_MOCK_CREATE_F_SUPPORT_NOWAIT) file->f_mode |= FMODE_NOWAIT; - mc.out_fd = fd_prepare_fd(fdf); + mc.out_fd = fdf->fd; if (copy_to_user(uarg, &mc, uarg_size)) return -EFAULT; diff --git a/kernel/bpf/bpf_iter.c b/kernel/bpf/bpf_iter.c index 14a5fdfa04214b..5f790d12f72e14 100644 --- a/kernel/bpf/bpf_iter.c +++ b/kernel/bpf/bpf_iter.c @@ -643,11 +643,11 @@ int bpf_iter_new_fd(struct bpf_link *link) flags = O_RDONLY | O_CLOEXEC; FD_PREPARE(fdf, flags, anon_inode_getfile("bpf_iter", &bpf_iter_fops, NULL, flags)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; iter_link = container_of(link, struct bpf_iter_link, link); - err = prepare_seq_file(fd_prepare_file(fdf), iter_link); + err = prepare_seq_file(fdf->file, iter_link); if (err) return err; /* Automatic cleanup handles fput */ diff --git a/kernel/bpf/token.c b/kernel/bpf/token.c index e85a179523f02a..da915a4f972b17 100644 --- a/kernel/bpf/token.c +++ b/kernel/bpf/token.c @@ -169,8 +169,8 @@ int bpf_token_create(union bpf_attr *attr) FD_PREPARE(fdf, O_CLOEXEC, alloc_file_pseudo(inode, path.mnt, BPF_TOKEN_INODE_NAME, O_RDWR, &bpf_token_fops)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; token = kzalloc_obj(*token, GFP_USER); if (!token) @@ -190,7 +190,7 @@ int bpf_token_create(union bpf_attr *attr) return err; get_user_ns(token->userns); - fd_prepare_file(fdf)->private_data = no_free_ptr(token); + fdf->file->private_data = no_free_ptr(token); return fd_publish(fdf); } diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index 23fb68fce000e7..ea382aedfc90d5 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -4809,12 +4809,12 @@ static int new_userfaultfd(int flags) anon_inode_create_getfile("[userfaultfd]", &userfaultfd_fops, ctx, O_RDONLY | (flags & UFFD_SHARED_FCNTL_FLAGS), NULL)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; /* prevent the mm struct to be freed */ mmgrab(ctx->mm); - fd_prepare_file(fdf)->f_mode |= FMODE_NOWAIT; + fdf->file->f_mode |= FMODE_NOWAIT; retain_and_null_ptr(ctx); return fd_publish(fdf); } diff --git a/net/core/scm.c b/net/core/scm.c index f0d44ecdb11fca..15c330784a6992 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -364,14 +364,14 @@ int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags, return notrunc ? put_user(error, ufd) : error; FD_PREPARE(fdf, flags, get_file(f)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; - error = put_user(fd_prepare_fd(fdf), ufd); + error = put_user(fdf->fd, ufd); if (error) return error; - __receive_sock(fd_prepare_file(fdf)); + __receive_sock(fdf->file); return fd_publish(fdf); } diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c index 3fd4fef9bab1a4..73b8314d9010ce 100644 --- a/net/handshake/netlink.c +++ b/net/handshake/netlink.c @@ -107,17 +107,17 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) req = handshake_req_next(hn, class); if (req) { FD_PREPARE(fdf, O_CLOEXEC, req->hr_file); - if (fdf.err) { + if (fdf->fd < 0) { fput(req->hr_file); /* drop ref from handshake_req_next() */ - err = fdf.err; + err = fdf->fd; goto out_complete; } - err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf)); + err = req->hr_proto->hp_accept(req, info, fdf->fd); if (err) goto out_complete; /* Automatic cleanup handles fput */ - trace_handshake_cmd_accept(net, req, req->hr_sk, fd_prepare_fd(fdf)); + trace_handshake_cmd_accept(net, req, req->hr_sk, fdf->fd); fd_publish(fdf); return 0; } diff --git a/net/kcm/kcmsock.c b/net/kcm/kcmsock.c index 71af69d442f211..962ee2c4acd2fb 100644 --- a/net/kcm/kcmsock.c +++ b/net/kcm/kcmsock.c @@ -1580,10 +1580,10 @@ static int kcm_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) struct kcm_clone info; FD_PREPARE(fdf, 0, kcm_clone(sock)); - if (fdf.err) - return fdf.err; + if (fdf->fd < 0) + return fdf->fd; - info.fd = fd_prepare_fd(fdf); + info.fd = fdf->fd; if (copy_to_user((void __user *)arg, &info, sizeof(info))) return -EFAULT; From b9026ef6189ef44b32b4163431c8666be3cbd881 Mon Sep 17 00:00:00 2001 From: Lalit Shankar Chowdhury Date: Mon, 21 Sep 2026 08:14:11 +0000 Subject: [PATCH 0784/2286] ACPI: WMI: use IS_ENABLED() to simplify built-in or module check Replace the remaining built-in/module check for CONFIG_ACPI_WMI with IS_ENABLED() macro. Signed-off-by: Lalit Shankar Chowdhury Reviewed-by: Armin Wolf [ rjw: Subject tweaks ] Link: https://patch.msgid.link/20260921081411.4819-1-lalitshankarch@gmail.com Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac81209473..b590f520dc57d5 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -434,7 +434,7 @@ extern acpi_handle ec_get_handle(void); extern bool acpi_is_pnp_device(struct acpi_device *); -#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) +#if IS_ENABLED(CONFIG_ACPI_WMI) typedef void (*wmi_notify_handler) (union acpi_object *data, void *context); From a0039113053d4d3f629ea1b9cd74130ca597f14a Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 7 Sep 2026 09:04:49 +0200 Subject: [PATCH 0785/2286] ACPI: PM: Add quirk for Acer Swift 3 SF314-56G (MX250) The Acer Swift 3 SF314-56G (with NVIDIA GeForce MX250) suffers from the same ACPI power resource regression as the Thunderobot ZERO. The power resource controlling the dGPU is incorrectly turned off during initialization, causing the GPU to fall off the bus (D3cold). This commit adds a DMI quirk to keep the power resource on for this model, similar to the existing Thunderobot quirk. Tested on Acer Swift 3 SF314-56G with NVIDIA MX250. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221087 Signed-off-by: Matthias Dittrich [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 23a4e207a01eb3..c6955d1ad29db7 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1120,6 +1120,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), } + }, + { + /* + * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the + * same ACPI power resource regression as the Thunderobot ZERO. + * The power resource controlling the dGPU is incorrectly turned off + * during initialization, causing the GPU to fall off the bus (D3cold). + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), + } + }, {} }; From cfc77cf89fa9cf29474fb90c02e44a4c546e5378 Mon Sep 17 00:00:00 2001 From: Bryan Chan Date: Wed, 23 Sep 2026 00:18:53 +0100 Subject: [PATCH 0786/2286] EDAC/skx_base: Fix ambiguous bitwise and logical operator style The code works fine and works how it should be, the fix is just for uniformity/consistency of the bitwise operations and to fix a sparse warning: drivers/edac/skx_base.c:359:46: warning: dubious: x | !y lchan here is always 0 or 1 here, replace the inconsistent logical operator by xor operation. This removes the sparse warning and makes the switch case easier to read. [Tony: updated commit grammar and s/compiler warning/sparse warning/] Signed-off-by: Bryan Chan Signed-off-by: Tony Luck Link: https://patch.msgid.link/20260922231854.108188-1-bchimhim15@gmail.com --- drivers/edac/skx_base.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/edac/skx_base.c b/drivers/edac/skx_base.c index de749413ff9a84..ae2fda006632c9 100644 --- a/drivers/edac/skx_base.c +++ b/drivers/edac/skx_base.c @@ -356,7 +356,7 @@ static bool skx_sad_decode(struct decoded_addr *res) break; case 2: lchan = (addr >> shift) % 2; - lchan = (lchan << 1) | !lchan; + lchan = (lchan << 1) | (lchan ^ 1); break; case 3: lchan = ((addr >> shift) % 2) << 1; From 9d9523b6cf4292731c73e326720db74e785b5e8e Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Wed, 16 Sep 2026 11:21:41 +0530 Subject: [PATCH 0787/2286] arm64/cpufeature: Change description for ARM64_HAS_LPA2 ARM64_HAS_LPA2 represents CPU capability detection for FEAT_LPA2 both in S1 and S2, regardless whether KVM gets enabled and uses the same subsequently. ARM64_HAS_LPA2 in itself is not really anything KVM specific, although the description suggests otherwise. Drop 'KVM' from the description and update it appropriately. Cc: Will Deacon Cc: Ryan Roberts Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpufeature.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa7b..0428ac0cc7d060 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -3103,7 +3103,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = { .matches = has_bbml3, }, { - .desc = "52-bit Virtual Addressing for KVM (LPA2)", + .desc = "52-bit Virtual-Physical Addressing for S1 and S2 (LPA2)", .capability = ARM64_HAS_LPA2, .type = ARM64_CPUCAP_SYSTEM_FEATURE, .matches = has_lpa2, From 228718f6babd189ed6e5b196aeca4752fa34e7b6 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 2 Sep 2026 12:32:31 +0100 Subject: [PATCH 0788/2286] arm64: mte-kasan: Use IS_ENABLED() rather than ALTERNATIVE() 'cfg' parameter The ALTERNATIVE() macro accepts an optional 'cfg' parameter, which accepts a CONFIG_* symbol. Where a CONFIG_* symbol is provided, but is not selected, the ALTERNATIVE() macro will expand to nothing. It would be nicer overall to remove the 'cfg' parameter and to have an explicit check for 'cfg' outside of the asm() block. This is clearer to a reader, and permits the compiler to elide the asm() block when 'cfg' is not selected. In the mte-kasan code, replace the usage of the 'cfg' parameter with an explicit IS_ENABLED() check. Signed-off-by: Mark Rutland Cc: James Morse Cc: Marc Zyngier Cc: Will Deacon Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/mte-kasan.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/mte-kasan.h b/arch/arm64/include/asm/mte-kasan.h index 35213927191892..de910f1c19e1b2 100644 --- a/arch/arm64/include/asm/mte-kasan.h +++ b/arch/arm64/include/asm/mte-kasan.h @@ -53,14 +53,16 @@ static inline bool system_uses_mte_async_or_asymm_mode(void) */ static inline void mte_disable_tco(void) { - asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(0), - ARM64_MTE, CONFIG_KASAN_HW_TAGS)); + if (!IS_ENABLED(CONFIG_KASAN_HW_TAGS)) + return; + asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(0), ARM64_MTE)); } static inline void mte_enable_tco(void) { - asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(1), - ARM64_MTE, CONFIG_KASAN_HW_TAGS)); + if (!IS_ENABLED(CONFIG_KASAN_HW_TAGS)) + return; + asm volatile(ALTERNATIVE("nop", SET_PSTATE_TCO(1), ARM64_MTE)); } /* From 0406e2e71dafef4251aa4290f6c4fec0ecb4575b Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Wed, 2 Sep 2026 12:32:32 +0100 Subject: [PATCH 0789/2286] arm64: alternative: Remove 'cfg' parameter There are no remaining users of the 'cfg' parameter for alternative sequences. Remove support for the 'cfg' parameter. In general, it's better to use explicit ifdeffery or IS_ENABLED() checks. Signed-off-by: Mark Rutland Cc: James Morse Cc: Marc Zyngier Cc: Will Deacon Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/alternative-macros.h | 30 +++++++-------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/arch/arm64/include/asm/alternative-macros.h b/arch/arm64/include/asm/alternative-macros.h index 86241662485286..ee75a2f1a575fe 100644 --- a/arch/arm64/include/asm/alternative-macros.h +++ b/arch/arm64/include/asm/alternative-macros.h @@ -53,8 +53,7 @@ * * Alternatives with callbacks do not generate replacement instructions. */ -#define __ALTERNATIVE_CFG(oldinstr, newinstr, cpucap, cfg_enabled) \ - ".if "__stringify(cfg_enabled)" == 1\n" \ +#define __ALTERNATIVE(oldinstr, newinstr, cpucap) \ "661:\n\t" \ oldinstr "\n" \ "662:\n" \ @@ -68,10 +67,8 @@ ".org . - (664b-663b) + (662b-661b)\n\t" \ ".org . - (662b-661b) + (664b-663b)\n\t" \ ".previous\n" \ - ".endif\n" -#define __ALTERNATIVE_CFG_CB(oldinstr, cpucap, cfg_enabled, cb) \ - ".if "__stringify(cfg_enabled)" == 1\n" \ +#define __ALTERNATIVE_CB(oldinstr, cpucap, cb) \ "661:\n\t" \ oldinstr "\n" \ "662:\n" \ @@ -80,13 +77,12 @@ ".popsection\n" \ "663:\n\t" \ "664:\n\t" \ - ".endif\n" -#define _ALTERNATIVE_CFG(oldinstr, newinstr, cpucap, cfg, ...) \ - __ALTERNATIVE_CFG(oldinstr, newinstr, cpucap, IS_ENABLED(cfg)) +#define _ALTERNATIVE(oldinstr, newinstr, cpucap) \ + __ALTERNATIVE(oldinstr, newinstr, cpucap) #define ALTERNATIVE_CB(oldinstr, cpucap, cb) \ - __ALTERNATIVE_CFG_CB(oldinstr, (1 << ARM64_CB_SHIFT) | (cpucap), 1, cb) + __ALTERNATIVE_CB(oldinstr, (1 << ARM64_CB_SHIFT) | (cpucap), cb) #else #include @@ -99,8 +95,7 @@ .byte \alt_len .endm -.macro alternative_insn insn1, insn2, cap, enable = 1 - .if \enable +.macro alternative_insn insn1, insn2, cap 661: \insn1 662: .pushsection .altinstructions, "a" altinstruction_entry 661b, 663f, \cap, 662b-661b, 664f-663f @@ -110,7 +105,6 @@ 664: .org . - (664b-663b) + (662b-661b) .org . - (662b-661b) + (664b-663b) .previous - .endif .endm /* @@ -204,20 +198,16 @@ alternative_else alternative_endif .endm -#define _ALTERNATIVE_CFG(insn1, insn2, cap, cfg, ...) \ - alternative_insn insn1, insn2, cap, IS_ENABLED(cfg) +#define _ALTERNATIVE(insn1, insn2, cap) \ + alternative_insn insn1, insn2, cap #endif /* __ASSEMBLER__ */ /* * Usage: asm(ALTERNATIVE(oldinstr, newinstr, cpucap)); - * - * Usage: asm(ALTERNATIVE(oldinstr, newinstr, cpucap, CONFIG_FOO)); - * N.B. If CONFIG_FOO is specified, but not selected, the whole block - * will be omitted, including oldinstr. */ -#define ALTERNATIVE(oldinstr, newinstr, ...) \ - _ALTERNATIVE_CFG(oldinstr, newinstr, __VA_ARGS__, 1) +#define ALTERNATIVE(oldinstr, newinstr, cpucap) \ + _ALTERNATIVE(oldinstr, newinstr, cpucap) #ifndef __ASSEMBLER__ From 8c37fb7f67ce914de53ff367d71145e6e55dac52 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 17:11:46 +0530 Subject: [PATCH 0790/2286] docs: arm64: Fix typo "initalised" in booting.rst Add the missing "i" to "initalised" in the description of the spin-table cpu-release-addr property. The British spelling is deliberate. doc-guide/contributing.rst asks that both American and British spellings be left alone, so only the typo is corrected, not the dialect. checkpatch.pl suggests "initialized" here because scripts/spelling.txt maps this entry to the American form; that is addressed separately. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Acked-by: Randy Dunlap Signed-off-by: Catalin Marinas --- Documentation/arch/arm64/booting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/arch/arm64/booting.rst b/Documentation/arch/arm64/booting.rst index 13ef311dace83f..891fd22f722909 100644 --- a/Documentation/arch/arm64/booting.rst +++ b/Documentation/arch/arm64/booting.rst @@ -588,7 +588,7 @@ following manner: - CPUs with a "spin-table" enable-method must have a 'cpu-release-addr' property in their cpu node. This property identifies a - naturally-aligned 64-bit zero-initalised memory location. + naturally-aligned 64-bit zero-initialised memory location. These CPUs should spin outside of the kernel in a reserved area of memory (communicated to the kernel by a /memreserve/ region in the From dc8afa34b5dec78f0420e8873cbfd4422329231b Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:21:08 +0530 Subject: [PATCH 0791/2286] arm64: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/stage2_pgtable.h | 2 +- arch/arm64/include/uapi/asm/ptrace.h | 4 ++-- arch/arm64/kernel/cpufeature.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm64/include/asm/stage2_pgtable.h b/arch/arm64/include/asm/stage2_pgtable.h index 23d27623e478b8..cf57da90b5d152 100644 --- a/arch/arm64/include/asm/stage2_pgtable.h +++ b/arch/arm64/include/asm/stage2_pgtable.h @@ -17,7 +17,7 @@ * * This implies, the total number of page table levels required for * IPA_SHIFT at stage2 expected by the hardware can be calculated using - * the same logic used for the (non-collapsable) stage1 page tables but for + * the same logic used for the (non-collapsible) stage1 page tables but for * (IPA_SHIFT - 4). */ #define stage2_pgtable_levels(ipa) ARM64_HW_PGTABLE_LEVELS((ipa) - 4) diff --git a/arch/arm64/include/uapi/asm/ptrace.h b/arch/arm64/include/uapi/asm/ptrace.h index 15649a253a5796..0c8c6a126c3b5b 100644 --- a/arch/arm64/include/uapi/asm/ptrace.h +++ b/arch/arm64/include/uapi/asm/ptrace.h @@ -113,7 +113,7 @@ struct user_hwdebug_state { struct user_sve_header { __u32 size; /* total meaningful regset content in bytes */ - __u32 max_size; /* maxmium possible size for this thread */ + __u32 max_size; /* maximum possible size for this thread */ __u16 vl; /* current vector length */ __u16 max_vl; /* maximum possible vector length */ __u16 flags; @@ -272,7 +272,7 @@ struct user_pac_generic_keys { struct user_za_header { __u32 size; /* total meaningful regset content in bytes */ - __u32 max_size; /* maxmium possible size for this thread */ + __u32 max_size; /* maximum possible size for this thread */ __u16 vl; /* current vector length */ __u16 max_vl; /* maximum possible vector length */ __u16 flags; diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 0428ac0cc7d060..1f694a61d3b5db 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -888,7 +888,7 @@ static int search_cmp_ftr_reg(const void *id, const void *regp) * entry. * * returns - Upon success, matching ftr_reg entry for id. - * - NULL on failure. It is upto the caller to decide + * - NULL on failure. It is up to the caller to decide * the impact of a failure. */ static struct arm64_ftr_reg *get_arm64_ftr_reg_nowarn(u32 sys_id) @@ -3707,7 +3707,7 @@ static void verify_local_cpu_caps(u16 scope_mask) /* * We have to issue cpu_enable() irrespective of * whether the CPU has it or not, as it is enabeld - * system wide. It is upto the call back to take + * system wide. It is up to the call back to take * appropriate action on this CPU. */ if (caps->cpu_enable) From 1ad42ed8a6669795782fbffe1c7a6c9ec60053fc Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:21:09 +0530 Subject: [PATCH 0792/2286] selftests: arm64: fix typo "hander" in comment Correct "hander" to "handler", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Catalin Marinas --- .../arm64/signal/testcases/fake_sigreturn_sve_change_vl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/arm64/signal/testcases/fake_sigreturn_sve_change_vl.c b/tools/testing/selftests/arm64/signal/testcases/fake_sigreturn_sve_change_vl.c index e1ccf8f85a70c8..bcef280582ede5 100644 --- a/tools/testing/selftests/arm64/signal/testcases/fake_sigreturn_sve_change_vl.c +++ b/tools/testing/selftests/arm64/signal/testcases/fake_sigreturn_sve_change_vl.c @@ -2,7 +2,7 @@ /* * Copyright (C) 2021 ARM Limited * - * Attempt to change the SVE vector length in a signal hander, this is not + * Attempt to change the SVE vector length in a signal handler, this is not * supported and is expected to segfault. */ From 629a472598a0af1f8eac0fc0d0a256a128e6af87 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:21:10 +0530 Subject: [PATCH 0793/2286] arm64: include: fix repeated word 'to' in comment Drop the second 'to', reported by checkpatch.pl as a possible repeated word. Only touches a comment, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/pgtable-prot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/pgtable-prot.h b/arch/arm64/include/asm/pgtable-prot.h index 09d7c00cf40541..526ee54c0d33bd 100644 --- a/arch/arm64/include/asm/pgtable-prot.h +++ b/arch/arm64/include/asm/pgtable-prot.h @@ -139,7 +139,7 @@ static inline bool __pure lpa2_is_enabled(void) /* * Page types used via Permission Indirection Extension (PIE). PIE uses - * the USER, DBM, PXN and UXN bits to to generate an index which is used + * the USER, DBM, PXN and UXN bits to generate an index which is used * to look up the actual permission in PIR_ELx and PIRE0_EL1. We define * combinations we use on non-PIE systems with the same encoding, for * convenience these are listed here as comments as are the unallocated From 97c1757db39d852e6c1ec234476b508c7895cbcf Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:03 +0100 Subject: [PATCH 0794/2286] selftests/arm64: Remove big-endian support Big-endian support on arm64 depends on BROKEN, so the arm64 selftests do not need to worry about testing this configuration any more. Remove the 128-bit byte-swapping helper from the fp-ptrace test and the big-endian definition of RSEQ_SIG_DATA from the rseq test. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Thomas Gleixner Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- tools/testing/selftests/arm64/fp/fp-ptrace.c | 19 +------------------ tools/testing/selftests/rseq/rseq-arm64.h | 5 ----- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/tools/testing/selftests/arm64/fp/fp-ptrace.c b/tools/testing/selftests/arm64/fp/fp-ptrace.c index b435837c8c0e87..9b184d78f4fd6b 100644 --- a/tools/testing/selftests/arm64/fp/fp-ptrace.c +++ b/tools/testing/selftests/arm64/fp/fp-ptrace.c @@ -115,23 +115,6 @@ static void handle_alarm(int sig, siginfo_t *info, void *context) got_alarm = true; } -#ifdef CONFIG_CPU_BIG_ENDIAN -static __uint128_t arm64_cpu_to_le128(__uint128_t x) -{ - u64 a = swab64(x); - u64 b = swab64(x >> 64); - - return ((__uint128_t)a << 64) | b; -} -#else -static __uint128_t arm64_cpu_to_le128(__uint128_t x) -{ - return x; -} -#endif - -#define arm64_le128_to_cpu(x) arm64_cpu_to_le128(x) - static bool sve_supported(void) { return getauxval(AT_HWCAP) & HWCAP_SVE; @@ -932,7 +915,7 @@ static void fpsimd_to_sve(__uint128_t *v, char *z, int vl) for (i = 0; i < __SVE_NUM_ZREGS; i++) { p = (__uint128_t *)&z[__SVE_ZREG_OFFSET(vq, i)]; - *p = arm64_cpu_to_le128(v[i]); + *p = v[i]; } } diff --git a/tools/testing/selftests/rseq/rseq-arm64.h b/tools/testing/selftests/rseq/rseq-arm64.h index 21e1626a723584..f9fecf941bf22d 100644 --- a/tools/testing/selftests/rseq/rseq-arm64.h +++ b/tools/testing/selftests/rseq/rseq-arm64.h @@ -12,12 +12,7 @@ * matches code endianness. */ #define RSEQ_SIG_CODE 0xd428bc00 /* BRK #0x45E0. */ - -#ifdef __AARCH64EB__ -#define RSEQ_SIG_DATA 0x00bc28d4 /* BRK #0x45E0. */ -#else #define RSEQ_SIG_DATA RSEQ_SIG_CODE -#endif #define RSEQ_SIG RSEQ_SIG_DATA From 883247de6500e78b5fc644268a24bb9d6147df28 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:04 +0100 Subject: [PATCH 0795/2286] arm64: bpf: Remove big-endian support from the JIT compiler Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove the big-endian-specific support from the arm64 BPF JIT backend. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Acked-by: Alexei Starovoitov Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/net/bpf_jit_comp.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index c18e005a41dbeb..a5634428fdd5bc 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -1439,13 +1439,8 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn case BPF_ALU | BPF_END | BPF_FROM_LE: case BPF_ALU | BPF_END | BPF_FROM_BE: case BPF_ALU64 | BPF_END | BPF_FROM_LE: -#ifdef CONFIG_CPU_BIG_ENDIAN - if (BPF_CLASS(code) == BPF_ALU && BPF_SRC(code) == BPF_FROM_BE) - goto emit_bswap_uxt; -#else /* !CONFIG_CPU_BIG_ENDIAN */ if (BPF_CLASS(code) == BPF_ALU && BPF_SRC(code) == BPF_FROM_LE) goto emit_bswap_uxt; -#endif switch (imm) { case 16: emit(A64_REV16(is64, dst, dst), ctx); @@ -2559,15 +2554,9 @@ static void clear_garbage(struct jit_ctx *ctx, int reg, int effective_bytes) { if (effective_bytes) { int garbage_bits = 64 - 8 * effective_bytes; -#ifdef CONFIG_CPU_BIG_ENDIAN - /* garbage bits are at the right end */ - emit(A64_LSR(1, reg, reg, garbage_bits), ctx); - emit(A64_LSL(1, reg, reg, garbage_bits), ctx); -#else /* garbage bits are at the left end */ emit(A64_LSL(1, reg, reg, garbage_bits), ctx); emit(A64_LSR(1, reg, reg, garbage_bits), ctx); -#endif } } From 2768ccadc1b4953e8666cc3f61cbda3096f2c33a Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:05 +0100 Subject: [PATCH 0796/2286] arm64: crypto: Assume a little-endian kernel Commit 5276ea17a23c ("lib/crc: arm64: Assume a little-endian kernel") removed all usage of the CPU_LE()/CPU_BE() macros from the arm64 CRC assembly now that big-endian support depends on BROKEN. Do the same for the crypto assembly code. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/crypto/aes-ce-ccm-core.S | 4 ++-- arch/arm64/crypto/aes-neonbs-core.S | 4 ++-- arch/arm64/crypto/ghash-ce-core.S | 6 +++--- arch/arm64/crypto/sm4-ce-cipher-core.S | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/arm64/crypto/aes-ce-ccm-core.S b/arch/arm64/crypto/aes-ce-ccm-core.S index f2624238fd9543..c2e8f500da1446 100644 --- a/arch/arm64/crypto/aes-ce-ccm-core.S +++ b/arch/arm64/crypto/aes-ce-ccm-core.S @@ -50,7 +50,7 @@ ld1 {v0.16b}, [x5] /* load mac */ cbz x2, ce_aes_ccm_final ldr x8, [x6, #8] /* load lower ctr */ -CPU_LE( rev x8, x8 ) /* keep swabbed ctr in reg */ + rev x8, x8 /* keep swabbed ctr in reg */ 0: /* outer loop */ ld1 {v1.8b}, [x6] /* load upper ctr */ prfm pldl1strm, [x1] @@ -73,7 +73,7 @@ CPU_LE( rev x8, x8 ) /* keep swabbed ctr in reg */ eor v0.16b, v0.16b, v2.16b /* xor mac with pt ^ rk[last] */ st1 {v6.16b}, [x0], #16 /* write output block */ bne 0b -CPU_LE( rev x8, x8 ) + rev x8, x8 str x8, [x6, #8] /* store lsb end of ctr (BE) */ cbnz x7, ce_aes_ccm_final st1 {v0.16b}, [x5] /* store mac */ diff --git a/arch/arm64/crypto/aes-neonbs-core.S b/arch/arm64/crypto/aes-neonbs-core.S index baf450717b24ba..ba1f981a5464d1 100644 --- a/arch/arm64/crypto/aes-neonbs-core.S +++ b/arch/arm64/crypto/aes-neonbs-core.S @@ -824,8 +824,8 @@ SYM_FUNC_START(aesbs_ctr_encrypt) frame_push 0 ldp x7, x8, [x5] ld1 {v0.16b}, [x5] -CPU_LE( rev x7, x7 ) -CPU_LE( rev x8, x8 ) + rev x7, x7 + rev x8, x8 adds x8, x8, #1 adc x7, x7, xzr diff --git a/arch/arm64/crypto/ghash-ce-core.S b/arch/arm64/crypto/ghash-ce-core.S index 33772d8fe6b5e0..ba350d57fc766d 100644 --- a/arch/arm64/crypto/ghash-ce-core.S +++ b/arch/arm64/crypto/ghash-ce-core.S @@ -148,7 +148,7 @@ SYM_FUNC_START(pmull_ghash_update_p64) sub w0, w0, #1 3: /* multiply XL by SHASH in GF(2^128) */ -CPU_LE( rev64 T1.16b, T1.16b ) + rev64 T1.16b, T1.16b ext T2.16b, XL.16b, XL.16b, #8 ext IN1.16b, T1.16b, T1.16b, #8 @@ -272,7 +272,7 @@ SYM_FUNC_END(pmull_ghash_update_p64) cbz x0, 3f // tag only? ldr w8, [x5, #12] // load lower counter -CPU_LE( rev w8, w8 ) + rev w8, w8 0: mov w9, #4 // max blocks per round add x10, x0, #0xf @@ -380,7 +380,7 @@ CPU_LE( rev w8, w8 ) ret 5: -CPU_LE( rev w8, w8 ) + rev w8, w8 str w8, [x5, #12] // store lower counter st1 {XL.2d}, [x4] b 4b diff --git a/arch/arm64/crypto/sm4-ce-cipher-core.S b/arch/arm64/crypto/sm4-ce-cipher-core.S index 4ac6cfbc57970e..1682d727e0970d 100644 --- a/arch/arm64/crypto/sm4-ce-cipher-core.S +++ b/arch/arm64/crypto/sm4-ce-cipher-core.S @@ -18,7 +18,7 @@ SYM_FUNC_START(sm4_ce_do_crypt) ld1 {v8.4s}, [x2] ld1 {v0.4s-v3.4s}, [x0], #64 -CPU_LE( rev32 v8.16b, v8.16b ) + rev32 v8.16b, v8.16b ld1 {v4.4s-v7.4s}, [x0] sm4e v8.4s, v0.4s sm4e v8.4s, v1.4s @@ -30,7 +30,7 @@ CPU_LE( rev32 v8.16b, v8.16b ) sm4e v8.4s, v7.4s rev64 v8.4s, v8.4s ext v8.16b, v8.16b, v8.16b, #8 -CPU_LE( rev32 v8.16b, v8.16b ) + rev32 v8.16b, v8.16b st1 {v8.4s}, [x1] ret SYM_FUNC_END(sm4_ce_do_crypt) From 42b69efd34186c1b202cd290464cd090b86a327c Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:06 +0100 Subject: [PATCH 0797/2286] arm64: lib: Assume a little-endian kernel in custom library routines Commit 5276ea17a23c ("lib/crc: arm64: Assume a little-endian kernel") removed all usage of the CPU_LE()/CPU_BE() macros from the arm64 CRC assembly now that big-endian support depends on BROKEN. Do the same for the rest of the custom arm64 library code, as well as removing all usage of the '__LITTLE_ENDIAN' macro. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/lib/csum.c | 17 ----------------- arch/arm64/lib/memchr.S | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/arch/arm64/lib/csum.c b/arch/arm64/lib/csum.c index 2432683e48a61f..75db718e5b6a71 100644 --- a/arch/arm64/lib/csum.c +++ b/arch/arm64/lib/csum.c @@ -47,11 +47,7 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len) */ shift = offset * 8; data = *ptr++; -#ifdef __LITTLE_ENDIAN data = (data >> shift) << shift; -#else - data = (data << shift) >> shift; -#endif /* * Body: straightforward aligned loads from here on (the paired loads @@ -94,13 +90,8 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len) len -= 16; ptr += 2; -#ifdef __LITTLE_ENDIAN data = tmp >> 64; sum64 = accumulate(sum64, tmp); -#else - data = tmp; - sum64 = accumulate(sum64, tmp >> 64); -#endif } if (len > 0) { sum64 = accumulate(sum64, data); @@ -112,11 +103,7 @@ unsigned int __no_sanitize_address do_csum(const unsigned char *buff, int len) * preserving odd/even alignment. */ shift = len * -8; -#ifdef __LITTLE_ENDIAN data = (data << shift) >> shift; -#else - data = (data >> shift) << shift; -#endif sum64 = accumulate(sum64, data); /* Finally, folding */ @@ -140,11 +127,7 @@ __sum16 csum_ipv6_magic(const struct in6_addr *saddr, dst = *(const __uint128_t *)daddr->s6_addr; sum += (__force u32)htonl(len); -#ifdef __LITTLE_ENDIAN sum += (u32)proto << 24; -#else - sum += proto; -#endif src += (src >> 64) | (src << 64); dst += (dst >> 64) | (dst << 64); diff --git a/arch/arm64/lib/memchr.S b/arch/arm64/lib/memchr.S index 37a9f2a4f7f4b5..b14b27cb841953 100644 --- a/arch/arm64/lib/memchr.S +++ b/arch/arm64/lib/memchr.S @@ -63,7 +63,7 @@ L(byte_loop): sub srcin, srcin, #1 ret L(found_word): -CPU_LE( rev tmp, tmp) + rev tmp, tmp clz tmp, tmp sub tmp, tmp, #64 add result, srcin, tmp, asr #3 From fcec2a198957e52f76519be369245cad73d02fb2 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:07 +0100 Subject: [PATCH 0798/2286] arm64: lib: Assume a little-endian kernel in optimised string routines Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove the big-endian-specific support from the arm64 optimised string routines. Although these started life as a fork of Arm's "cortex strings" library and later got updated with the "optimized-routines" code, the in-kernel implementation has always been different enough that removing the unused big-endian portion still makes sense. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/lib/memcmp.S | 2 - arch/arm64/lib/strcmp.S | 34 +-------------- arch/arm64/lib/strlen.S | 26 ----------- arch/arm64/lib/strncmp.S | 93 +++------------------------------------- arch/arm64/lib/strnlen.S | 16 +------ 5 files changed, 8 insertions(+), 163 deletions(-) diff --git a/arch/arm64/lib/memcmp.S b/arch/arm64/lib/memcmp.S index a5ccf2c55f9119..1f528ce046e495 100644 --- a/arch/arm64/lib/memcmp.S +++ b/arch/arm64/lib/memcmp.S @@ -103,10 +103,8 @@ L(last_bytes): /* Compare data bytes and set return value to 0, -1 or 1. */ L(return): -#ifndef __AARCH64EB__ rev data1, data1 rev data2, data2 -#endif cmp data1, data2 L(ret_eq): cset result, ne diff --git a/arch/arm64/lib/strcmp.S b/arch/arm64/lib/strcmp.S index 9b89b453360746..2389b47dca2277 100644 --- a/arch/arm64/lib/strcmp.S +++ b/arch/arm64/lib/strcmp.S @@ -38,14 +38,6 @@ #define shift x9 #define off2 x10 -/* On big-endian early bytes are at MSB and on little-endian LSB. - LS_FW means shifting towards early bytes. */ -#ifdef __AARCH64EB__ -# define LS_FW lsl -#else -# define LS_FW lsr -#endif - /* NUL detection works on the principle that (X - 1) & (~X) & 0x80 (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and can be done in parallel across the entire word. @@ -67,28 +59,17 @@ L(loop_aligned): ldr data2, [src1, off2] ldr data1, [src1], 8 L(start_realigned): -#ifdef __AARCH64EB__ - rev tmp, data1 - sub has_nul, tmp, zeroones - orr tmp, tmp, REP8_7f -#else sub has_nul, data1, zeroones orr tmp, data1, REP8_7f -#endif bics has_nul, has_nul, tmp /* Non-zero if NUL terminator. */ ccmp data1, data2, 0, eq b.eq L(loop_aligned) -#ifdef __AARCH64EB__ - rev has_nul, has_nul -#endif eor diff, data1, data2 orr syndrome, diff, has_nul L(end): -#ifndef __AARCH64EB__ rev syndrome, syndrome rev data1, data1 rev data2, data2 -#endif clz shift, syndrome /* The most-significant-non-zero bit of the syndrome marks either the first bit that is different, or the top bit of the first zero byte. @@ -113,7 +94,7 @@ L(mutual_align): ldr data1, [src1], 8 neg shift, src2, lsl 3 /* Bits to alignment -64. */ mov tmp, -1 - LS_FW tmp, tmp, shift + lsr tmp, tmp, shift orr data1, data1, tmp orr data2, data2, tmp b L(start_realigned) @@ -135,9 +116,6 @@ L(src1_aligned): neg shift, src2, lsl 3 bic src2, src2, 7 ldr data3, [src2], 8 -#ifdef __AARCH64EB__ - rev data3, data3 -#endif lsr tmp, zeroones, shift orr data3, data3, tmp sub has_nul, data3, zeroones @@ -152,9 +130,6 @@ L(src1_aligned): L(loop_unaligned): ldr data3, [src1, off1] ldr data2, [src1, off2] -#ifdef __AARCH64EB__ - rev data3, data3 -#endif sub has_nul, data3, zeroones orr tmp, data3, REP8_7f ldr data1, [src1], 8 @@ -163,9 +138,6 @@ L(loop_unaligned): b.eq L(loop_unaligned) lsl tmp, has_nul, shift -#ifdef __AARCH64EB__ - rev tmp, tmp -#endif eor diff, data1, data2 orr syndrome, diff, tmp cbnz syndrome, L(end) @@ -174,10 +146,6 @@ L(tail): neg shift, shift lsr data2, data3, shift lsr has_nul, has_nul, shift -#ifdef __AARCH64EB__ - rev data2, data2 - rev has_nul, has_nul -#endif eor diff, data1, data2 orr syndrome, diff, has_nul b L(end) diff --git a/arch/arm64/lib/strlen.S b/arch/arm64/lib/strlen.S index 4919fe81ae540e..a8c49a3886f638 100644 --- a/arch/arm64/lib/strlen.S +++ b/arch/arm64/lib/strlen.S @@ -85,14 +85,6 @@ SYM_FUNC_START(__pi_strlen) cmp tmp1, MIN_PAGE_SIZE - 16 b.gt L(page_cross) ldp data1, data2, [srcin] -#ifdef __AARCH64EB__ - /* For big-endian, carry propagation (if the final byte in the - string is 0x01) means we cannot use has_nul1/2 directly. - Since we expect strings to be small and early-exit, - byte-swap the data now so has_null1/2 will be correct. */ - rev data1, data1 - rev data2, data2 -#endif sub tmp1, data1, zeroones orr tmp2, data1, REP8_7f sub tmp3, data2, zeroones @@ -144,19 +136,7 @@ L(page_cross_entry): /* Enter with C = has_nul1 == 0. */ L(tail): -#ifdef __AARCH64EB__ - /* For big-endian, carry propagation (if the final byte in the - string is 0x01) means we cannot use has_nul1/2 directly. The - easiest way to get the correct byte is to byte-swap the data - and calculate the syndrome a second time. */ - csel data1, data1, data2, cc - rev data1, data1 - sub tmp1, data1, zeroones - orr tmp2, data1, REP8_7f - bic has_nul1, tmp1, tmp2 -#else csel has_nul1, has_nul1, has_nul2, cc -#endif sub len, src, srcin rev has_nul1, has_nul1 add tmp2, len, 8 @@ -194,13 +174,7 @@ L(page_cross): ldp data1, data2, [src] lsl tmp1, srcin, 3 mov tmp4, -1 -#ifdef __AARCH64EB__ - /* Big-endian. Early bytes are at MSB. */ - lsr tmp1, tmp4, tmp1 /* Shift (tmp1 & 63). */ -#else - /* Little-endian. Early bytes are at LSB. */ lsl tmp1, tmp4, tmp1 /* Shift (tmp1 & 63). */ -#endif orr tmp1, tmp1, REP8_80 orn data1, data1, tmp1 orn tmp2, data2, tmp1 diff --git a/arch/arm64/lib/strncmp.S b/arch/arm64/lib/strncmp.S index fe7bbc0b42a787..e4fde8586089a6 100644 --- a/arch/arm64/lib/strncmp.S +++ b/arch/arm64/lib/strncmp.S @@ -45,19 +45,6 @@ #define offset pos #define neg_offset x15 -/* Define endian dependent shift operations. - On big-endian early bytes are at MSB and on little-endian LSB. - LS_FW means shifting towards early bytes. - LS_BK means shifting towards later bytes. - */ -#ifdef __AARCH64EB__ -#define LS_FW lsl -#define LS_BK lsr -#else -#define LS_FW lsr -#define LS_BK lsl -#endif - SYM_FUNC_START(__pi_strncmp) cbz limit, L(ret0) eor tmp1, src1, src2 @@ -86,7 +73,6 @@ L(start_realigned): /* End of main loop */ L(full_check): -#ifndef __AARCH64EB__ orr syndrome, diff, has_nul add limit, limit, 8 /* Rewind limit to before last subs. */ L(syndrome_check): @@ -105,55 +91,6 @@ L(syndrome_check): sub result, data1, data2, lsr #56 csel result, result, xzr, hi ret -#else - /* Not reached the limit, must have found the end or a diff. */ - tbz limit, #63, L(not_limit) - add tmp1, limit, 8 - cbz limit, L(not_limit) - - lsl limit, tmp1, #3 /* Bits -> bytes. */ - mov mask, #~0 - lsr mask, mask, limit - bic data1, data1, mask - bic data2, data2, mask - - /* Make sure that the NUL byte is marked in the syndrome. */ - orr has_nul, has_nul, mask - -L(not_limit): - /* For big-endian we cannot use the trick with the syndrome value - as carry-propagation can corrupt the upper bits if the trailing - bytes in the string contain 0x01. */ - /* However, if there is no NUL byte in the dword, we can generate - the result directly. We can't just subtract the bytes as the - MSB might be significant. */ - cbnz has_nul, 1f - cmp data1, data2 - cset result, ne - cneg result, result, lo - ret -1: - /* Re-compute the NUL-byte detection, using a byte-reversed value. */ - rev tmp3, data1 - sub tmp1, tmp3, zeroones - orr tmp2, tmp3, #REP8_7f - bic has_nul, tmp1, tmp2 - rev has_nul, has_nul - orr syndrome, diff, has_nul - clz pos, syndrome - /* The most-significant-non-zero bit of the syndrome marks either the - first bit that is different, or the top bit of the first zero byte. - Shifting left now will bring the critical information into the - top bits. */ -L(end_quick): - lsl data1, data1, pos - lsl data2, data2, pos - /* But we need to zero-extend (char is unsigned) the value and then - perform a signed 32-bit subtraction. */ - lsr data1, data1, #56 - sub result, data1, data2, lsr #56 - ret -#endif L(mutual_align): /* Sources are mutually aligned, but are not currently at an @@ -167,7 +104,7 @@ L(mutual_align): neg tmp3, count, lsl #3 /* 64 - bits(bytes beyond align). */ ldr data2, [src2], #8 mov tmp2, #~0 - LS_FW tmp2, tmp2, tmp3 /* Shift (count & 63). */ + lsr tmp2, tmp2, tmp3 /* Shift (count & 63). */ /* Adjust the limit and ensure it doesn't overflow. */ adds limit, limit, count csinv limit, limit, xzr, lo @@ -236,15 +173,15 @@ L(src1_aligned): neg neg_offset, offset ldr data1, [src1], #8 ldp tmp1, tmp2, [src2], #16 - LS_BK mask, mask, neg_offset + lsl mask, mask, neg_offset and neg_offset, neg_offset, #63 /* Need actual value for cmp later. */ /* Skip the first compare if data in tmp1 is irrelevant. */ tbnz offset, 6, L(misaligned_mid_loop) L(loop_misaligned): /* STEP_A: Compare full 8 bytes when there is enough data from SRC2.*/ - LS_FW data2, tmp1, offset - LS_BK tmp1, tmp2, neg_offset + lsr data2, tmp1, offset + lsl tmp1, tmp2, neg_offset subs limit, limit, #8 orr data2, data2, tmp1 /* 8 bytes from SRC2 combined from two regs.*/ sub has_nul, data1, zeroones @@ -258,23 +195,12 @@ L(loop_misaligned): ldr data1, [src1], #8 L(misaligned_mid_loop): /* STEP_B: Compare first part of data1 to second part of tmp2. */ - LS_FW data2, tmp2, offset -#ifdef __AARCH64EB__ - /* For big-endian we do a byte reverse to avoid carry-propagation - problem described above. This way we can reuse the has_nul in the - next step and also use syndrome value trick at the end. */ - rev tmp3, data1 - #define data1_fixed tmp3 -#else + lsr data2, tmp2, offset #define data1_fixed data1 -#endif sub has_nul, data1_fixed, zeroones orr tmp3, data1_fixed, #REP8_7f eor diff, data2, data1 /* Non-zero if differences found. */ bic has_nul, has_nul, tmp3 /* Non-zero if NUL terminator. */ -#ifdef __AARCH64EB__ - rev has_nul, has_nul -#endif cmp limit, neg_offset, lsr #3 orr syndrome, diff, has_nul bic syndrome, syndrome, mask /* Ignore later bytes. */ @@ -284,7 +210,7 @@ L(misaligned_mid_loop): /* STEP_C: Compare second part of data1 to first part of tmp1. */ ldp tmp1, tmp2, [src2], #16 cmp limit, #8 - LS_BK data2, tmp1, neg_offset + lsl data2, tmp1, neg_offset eor diff, data2, data1 /* Non-zero if differences found. */ orr syndrome, diff, has_nul and syndrome, syndrome, mask /* Ignore earlier bytes. */ @@ -295,13 +221,6 @@ L(misaligned_mid_loop): sub limit, limit, #8 b L(loop_misaligned) -#ifdef __AARCH64EB__ -L(syndrome_check): - clz pos, syndrome - cmp pos, limit, lsl #3 - b.lo L(end_quick) -#endif - L(ret0): mov result, #0 ret diff --git a/arch/arm64/lib/strnlen.S b/arch/arm64/lib/strnlen.S index d5ac0e10a01db7..b20011d559f197 100644 --- a/arch/arm64/lib/strnlen.S +++ b/arch/arm64/lib/strnlen.S @@ -91,22 +91,10 @@ SYM_FUNC_START(__pi_strnlen) */ sub len, src, srcin cbz has_nul1, .Lnul_in_data2 -CPU_BE( mov data2, data1 ) /*perpare data to re-calculate the syndrome*/ sub len, len, #8 mov has_nul2, has_nul1 .Lnul_in_data2: - /* - * For big-endian, carry propagation (if the final byte in the - * string is 0x01) means we cannot use has_nul directly. The - * easiest way to get the correct byte is to byte-swap the data - * and calculate the syndrome a second time. - */ -CPU_BE( rev data2, data2 ) -CPU_BE( sub tmp1, data2, zeroones ) -CPU_BE( orr tmp2, data2, #REP8_7f ) -CPU_BE( bic has_nul2, tmp1, tmp2 ) - sub len, len, #8 rev has_nul2, has_nul2 clz pos, has_nul2 @@ -139,10 +127,8 @@ CPU_BE( bic has_nul2, tmp1, tmp2 ) lsl tmp4, tmp4, #3 /* Bytes beyond alignment -> bits. */ mov tmp2, #~0 - /* Big-endian. Early bytes are at MSB. */ -CPU_BE( lsl tmp2, tmp2, tmp4 ) /* Shift (tmp1 & 63). */ /* Little-endian. Early bytes are at LSB. */ -CPU_LE( lsr tmp2, tmp2, tmp4 ) /* Shift (tmp1 & 63). */ + lsr tmp2, tmp2, tmp4 /* Shift (tmp1 & 63). */ cmp tmp1, #8 From 6a1aeecf3db92def2d505e4a579667c958d51262 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:08 +0100 Subject: [PATCH 0799/2286] arm64: assembler: Remove endianness helper macros The CPU_LE()/CPU_BE() helper macros are only used by head.S. Since big-endian support depends on BROKEN, remove the last users along with the macro definitions. The unused regs_to_64() assembly macro, which somewhat bizarrely behaves differently depend on the endianness, is also removed. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/assembler.h | 31 ------------------------------ arch/arm64/kernel/head.S | 3 +-- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/arch/arm64/include/asm/assembler.h b/arch/arm64/include/asm/assembler.h index 0b58b550e8dce7..42a167a75486ea 100644 --- a/arch/arm64/include/asm/assembler.h +++ b/arch/arm64/include/asm/assembler.h @@ -129,37 +129,6 @@ lr .req x30 // link register b \label .endm -/* - * Select code when configured for BE. - */ -#ifdef CONFIG_CPU_BIG_ENDIAN -#define CPU_BE(code...) code -#else -#define CPU_BE(code...) -#endif - -/* - * Select code when configured for LE. - */ -#ifdef CONFIG_CPU_BIG_ENDIAN -#define CPU_LE(code...) -#else -#define CPU_LE(code...) code -#endif - -/* - * Define a macro that constructs a 64-bit value by concatenating two - * 32-bit registers. Note that on big endian systems the order of the - * registers is swapped. - */ -#ifndef CONFIG_CPU_BIG_ENDIAN - .macro regs_to_64, rd, lbits, hbits -#else - .macro regs_to_64, rd, hbits, lbits -#endif - orr \rd, \lbits, \hbits, lsl #32 - .endm - /* * Pseudo-ops for PC-relative adr/ldr/str , where * is within the range +/- 4 GB of the PC. diff --git a/arch/arm64/kernel/head.S b/arch/arm64/kernel/head.S index 87a822e5c4ca83..8951ce69355206 100644 --- a/arch/arm64/kernel/head.S +++ b/arch/arm64/kernel/head.S @@ -138,8 +138,7 @@ SYM_CODE_START_LOCAL(record_mmu_state) b.ne 0f mrs x19, sctlr_el2 0: -CPU_LE( tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f ) -CPU_BE( tbz x19, #SCTLR_ELx_EE_SHIFT, 1f ) + tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f tst x19, #SCTLR_ELx_C // Z := (C == 0) and x19, x19, #SCTLR_ELx_M // isolate M bit csel x19, xzr, x19, eq // clear x19 if Z From b8409b9eb74db49ff43adc9015107a90449c2e33 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:09 +0100 Subject: [PATCH 0800/2286] arm64: vdso32: Always build compat vDSO object as little-endian Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove support for building a big-endian 32-bit vDSO. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/kernel/vdso32/Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/arm64/kernel/vdso32/Makefile b/arch/arm64/kernel/vdso32/Makefile index bea3675fa668bd..00efdc5d23543d 100644 --- a/arch/arm64/kernel/vdso32/Makefile +++ b/arch/arm64/kernel/vdso32/Makefile @@ -45,11 +45,7 @@ endif # From arm Makefile VDSO_CAFLAGS += $(call cc32-option,-fno-dwarf2-cfi-asm) VDSO_CAFLAGS += -mabi=aapcs-linux -mfloat-abi=soft -ifeq ($(CONFIG_CPU_BIG_ENDIAN), y) -VDSO_CAFLAGS += -mbig-endian -else VDSO_CAFLAGS += -mlittle-endian -endif # From arm vDSO Makefile VDSO_CAFLAGS += -fPIC -fno-builtin -fno-stack-protector From 9afaed358394ac06080bc6f72f052557b607678e Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:10 +0100 Subject: [PATCH 0801/2286] KVM: arm64: Remove support for a big-endian hypervisor object Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove the support for generating ELF relocations for a big-endian hypervisor object and drop the unused big-endian spinlock definition from nVHE. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/kvm/hyp/include/nvhe/spinlock.h | 4 ---- arch/arm64/kvm/hyp/nvhe/gen-hyprel.c | 16 ---------------- 2 files changed, 20 deletions(-) diff --git a/arch/arm64/kvm/hyp/include/nvhe/spinlock.h b/arch/arm64/kvm/hyp/include/nvhe/spinlock.h index 7c7ea8c55405f5..b6978f03e86aa1 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/spinlock.h +++ b/arch/arm64/kvm/hyp/include/nvhe/spinlock.h @@ -20,11 +20,7 @@ typedef union hyp_spinlock { u32 __val; struct { -#ifdef __AARCH64EB__ - u16 next, owner; -#else u16 owner, next; -#endif }; } hyp_spinlock_t; diff --git a/arch/arm64/kvm/hyp/nvhe/gen-hyprel.c b/arch/arm64/kvm/hyp/nvhe/gen-hyprel.c index b63f4e1c10330d..99243e0fefb0f2 100644 --- a/arch/arm64/kvm/hyp/nvhe/gen-hyprel.c +++ b/arch/arm64/kvm/hyp/nvhe/gen-hyprel.c @@ -139,28 +139,12 @@ static struct { const char *sh_string; } elf; -#if defined(CONFIG_CPU_LITTLE_ENDIAN) - #define elf16toh(x) le16toh(x) #define elf32toh(x) le32toh(x) #define elf64toh(x) le64toh(x) #define ELFENDIAN ELFDATA2LSB -#elif defined(CONFIG_CPU_BIG_ENDIAN) - -#define elf16toh(x) be16toh(x) -#define elf32toh(x) be32toh(x) -#define elf64toh(x) be64toh(x) - -#define ELFENDIAN ELFDATA2MSB - -#else - -#error PDP-endian sadly unsupported... - -#endif - #define fatal_error(fmt, ...) \ ({ \ fprintf(stderr, "error: %s: " fmt "\n", \ From eab76a8e18803b38b3dd58c6c102b06715813199 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:11 +0100 Subject: [PATCH 0802/2286] arm64: Remove all usage of CONFIG_CPU_BIG_ENDIAN Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove all usage of CONFIG_CPU_BIG_ENDIAN from the arm64 kernel port. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/processor.h | 2 -- arch/arm64/include/asm/ptrace.h | 6 ---- arch/arm64/include/asm/sysreg.h | 50 ++++++++-------------------- arch/arm64/include/asm/thread_info.h | 5 --- arch/arm64/kernel/fpsimd.c | 21 ++---------- arch/arm64/kernel/image.h | 18 ++-------- arch/arm64/kernel/kexec_image.c | 5 ++- arch/arm64/kernel/kgdb.c | 6 +--- arch/arm64/kernel/ptrace.c | 8 ++--- arch/arm64/kernel/signal32.c | 3 -- arch/arm64/kernel/sys32.c | 5 --- 11 files changed, 23 insertions(+), 106 deletions(-) diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h index 6dfbcacd9ba090..481f1999f7c142 100644 --- a/arch/arm64/include/asm/processor.h +++ b/arch/arm64/include/asm/processor.h @@ -351,8 +351,6 @@ static inline void compat_start_thread(struct pt_regs *regs, unsigned long pc, unsigned long pstate = PSR_AA32_MODE_USR; if (pc & 1) pstate |= PSR_AA32_T_BIT; - if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) - pstate |= PSR_AA32_E_BIT; start_thread_common(regs, pc, pstate); spectre_v4_enable_task_mitigation(current); diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h index f7dc5fb9427d63..57c98c775acaeb 100644 --- a/arch/arm64/include/asm/ptrace.h +++ b/arch/arm64/include/asm/ptrace.h @@ -66,12 +66,6 @@ #define PSR_AA32_IT_MASK 0x0600fc00 /* If-Then execution state mask */ #define PSR_AA32_GE_MASK 0x000f0000 -#ifdef CONFIG_CPU_BIG_ENDIAN -#define PSR_AA32_ENDSTATE PSR_AA32_E_BIT -#else -#define PSR_AA32_ENDSTATE 0 -#endif - /* AArch32 CPSR bits, as seen in AArch32 */ #define COMPAT_PSR_DIT_BIT 0x00200000 diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h index 7aa08d59d49448..f97c60b5dfcaab 100644 --- a/arch/arm64/include/asm/sysreg.h +++ b/arch/arm64/include/asm/sysreg.h @@ -62,19 +62,10 @@ #else /* CONFIG_BROKEN_GAS_INST */ -#ifndef CONFIG_CPU_BIG_ENDIAN -#define __INSTR_BSWAP(x) (x) -#else /* CONFIG_CPU_BIG_ENDIAN */ -#define __INSTR_BSWAP(x) ((((x) << 24) & 0xff000000) | \ - (((x) << 8) & 0x00ff0000) | \ - (((x) >> 8) & 0x0000ff00) | \ - (((x) >> 24) & 0x000000ff)) -#endif /* CONFIG_CPU_BIG_ENDIAN */ - #ifdef __ASSEMBLER__ -#define __emit_inst(x) .long __INSTR_BSWAP(x) +#define __emit_inst(x) .long (x) #else /* __ASSEMBLER__ */ -#define __emit_inst(x) ".long " __stringify(__INSTR_BSWAP(x)) "\n\t" +#define __emit_inst(x) ".long " __stringify(x) "\n\t" #endif /* __ASSEMBLER__ */ #endif /* CONFIG_BROKEN_GAS_INST */ @@ -835,39 +826,26 @@ #define SCTLR_ELx_A (BIT(1)) #define SCTLR_ELx_M (BIT(0)) -#ifdef CONFIG_CPU_BIG_ENDIAN -#define ENDIAN_SET_EL2 SCTLR_ELx_EE -#else -#define ENDIAN_SET_EL2 0 -#endif - #define INIT_SCTLR_EL2_MMU_ON \ (SCTLR_ELx_M | SCTLR_ELx_C | SCTLR_ELx_SA | SCTLR_ELx_I | \ - SCTLR_ELx_IESB | SCTLR_ELx_WXN | ENDIAN_SET_EL2 | \ - SCTLR_ELx_ITFSB | SCTLR_ELx_EIS | SCTLR_ELx_EOS | SCTLR_EL2_RES1) + SCTLR_ELx_IESB | SCTLR_ELx_WXN | SCTLR_ELx_ITFSB | \ + SCTLR_ELx_EIS | SCTLR_ELx_EOS | SCTLR_EL2_RES1) -#define INIT_SCTLR_EL2_MMU_OFF \ - (SCTLR_EL2_RES1 | ENDIAN_SET_EL2) +#define INIT_SCTLR_EL2_MMU_OFF (SCTLR_EL2_RES1) /* SCTLR_EL1 specific flags. */ -#ifdef CONFIG_CPU_BIG_ENDIAN -#define ENDIAN_SET_EL1 (SCTLR_EL1_E0E | SCTLR_ELx_EE) -#else -#define ENDIAN_SET_EL1 0 -#endif - #define INIT_SCTLR_EL1_MMU_OFF \ - (ENDIAN_SET_EL1 | SCTLR_EL1_LSMAOE | SCTLR_EL1_nTLSMD | \ - SCTLR_EL1_EIS | SCTLR_EL1_TSCXT | SCTLR_EL1_EOS) + (SCTLR_EL1_LSMAOE | SCTLR_EL1_nTLSMD | SCTLR_EL1_EIS | \ + SCTLR_EL1_TSCXT | SCTLR_EL1_EOS) #define INIT_SCTLR_EL1_MMU_ON \ - (SCTLR_ELx_M | SCTLR_ELx_C | SCTLR_ELx_SA | \ - SCTLR_EL1_SA0 | SCTLR_EL1_SED | SCTLR_ELx_I | \ - SCTLR_EL1_DZE | SCTLR_EL1_UCT | SCTLR_EL1_nTWE | \ - SCTLR_ELx_IESB | SCTLR_EL1_SPAN | SCTLR_ELx_ITFSB | \ - ENDIAN_SET_EL1 | SCTLR_EL1_UCI | SCTLR_EL1_EPAN | \ - SCTLR_EL1_LSMAOE | SCTLR_EL1_nTLSMD | SCTLR_EL1_EIS | \ - SCTLR_EL1_TSCXT | SCTLR_EL1_EOS) + (SCTLR_ELx_M | SCTLR_ELx_C | SCTLR_ELx_SA | \ + SCTLR_EL1_SA0 | SCTLR_EL1_SED | SCTLR_ELx_I | \ + SCTLR_EL1_DZE | SCTLR_EL1_UCT | SCTLR_EL1_nTWE | \ + SCTLR_ELx_IESB | SCTLR_EL1_SPAN | SCTLR_ELx_ITFSB | \ + SCTLR_EL1_UCI | SCTLR_EL1_EPAN | SCTLR_EL1_LSMAOE | \ + SCTLR_EL1_nTLSMD | SCTLR_EL1_EIS | SCTLR_EL1_TSCXT | \ + SCTLR_EL1_EOS) /* MAIR_ELx memory attributes (used by Linux) */ #define MAIR_ATTR_DEVICE_nGnRnE UL(0x00) diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h index 5d7fe3e153c850..161b91762e9393 100644 --- a/arch/arm64/include/asm/thread_info.h +++ b/arch/arm64/include/asm/thread_info.h @@ -29,13 +29,8 @@ struct thread_info { union { u64 preempt_count; /* 0 => preemptible, <0 => bug */ struct { -#ifdef CONFIG_CPU_BIG_ENDIAN - u32 need_resched; - u32 count; -#else u32 count; u32 need_resched; -#endif } preempt; }; #ifdef CONFIG_SHADOW_CALL_STACK diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c index e7f1682a3059b5..65e7147067aa3c 100644 --- a/arch/arm64/kernel/fpsimd.c +++ b/arch/arm64/kernel/fpsimd.c @@ -621,23 +621,6 @@ static int __init sme_sysctl_init(void) { return 0; } #define ZREG(sve_state, vq, n) ((char *)(sve_state) + \ (SVE_SIG_ZREG_OFFSET(vq, n) - SVE_SIG_REGS_OFFSET)) -#ifdef CONFIG_CPU_BIG_ENDIAN -static __uint128_t arm64_cpu_to_le128(__uint128_t x) -{ - u64 a = swab64(x); - u64 b = swab64(x >> 64); - - return ((__uint128_t)a << 64) | b; -} -#else -static __uint128_t arm64_cpu_to_le128(__uint128_t x) -{ - return x; -} -#endif - -#define arm64_le128_to_cpu(x) arm64_cpu_to_le128(x) - static void __fpsimd_to_sve(struct arm64_sve_state *sst, struct user_fpsimd_state const *fst, unsigned int vq) @@ -647,7 +630,7 @@ static void __fpsimd_to_sve(struct arm64_sve_state *sst, for (i = 0; i < SVE_NUM_ZREGS; ++i) { p = (__uint128_t *)ZREG(sst, vq, i); - *p = arm64_cpu_to_le128(fst->vregs[i]); + *p = fst->vregs[i]; } } @@ -702,7 +685,7 @@ static inline void sve_to_fpsimd(struct task_struct *task) vq = sve_vq_from_vl(vl); for (i = 0; i < SVE_NUM_ZREGS; ++i) { p = (__uint128_t const *)ZREG(sst, vq, i); - fst->vregs[i] = arm64_le128_to_cpu(*p); + fst->vregs[i] = *p; } } diff --git a/arch/arm64/kernel/image.h b/arch/arm64/kernel/image.h index 7bc3ba89790191..5c10ca77336388 100644 --- a/arch/arm64/kernel/image.h +++ b/arch/arm64/kernel/image.h @@ -24,28 +24,14 @@ * when PIE is in effect. So we need to split them up in 32-bit high and low * words. */ -#ifdef CONFIG_CPU_BIG_ENDIAN -#define DATA_LE32(data) \ - ((((data) & 0x000000ff) << 24) | \ - (((data) & 0x0000ff00) << 8) | \ - (((data) & 0x00ff0000) >> 8) | \ - (((data) & 0xff000000) >> 24)) -#else -#define DATA_LE32(data) ((data) & 0xffffffff) -#endif - #define DEFINE_IMAGE_LE64(sym, data) \ - sym##_lo32 = DATA_LE32((data) & 0xffffffff); \ - sym##_hi32 = DATA_LE32((data) >> 32) + sym##_lo32 = (data) & 0xffffffff; \ + sym##_hi32 = (data) >> 32 #define __HEAD_FLAG(field) (__HEAD_FLAG_##field << \ ARM64_IMAGE_FLAG_##field##_SHIFT) -#ifdef CONFIG_CPU_BIG_ENDIAN -#define __HEAD_FLAG_BE ARM64_IMAGE_FLAG_BE -#else #define __HEAD_FLAG_BE ARM64_IMAGE_FLAG_LE -#endif #define __HEAD_FLAG_PAGE_SIZE ((PAGE_SHIFT - 10) / 2) diff --git a/arch/arm64/kernel/kexec_image.c b/arch/arm64/kernel/kexec_image.c index b70f4df15a1ae5..b8d0fb0a9dafa7 100644 --- a/arch/arm64/kernel/kexec_image.c +++ b/arch/arm64/kernel/kexec_image.c @@ -40,7 +40,7 @@ static void *image_load(struct kimage *image, { struct arm64_image_header *h; u64 flags, value; - bool be_image, be_kernel; + bool be_image; struct kexec_buf kbuf = {}; unsigned long text_offset, kernel_segment_number; struct kexec_segment *kernel_segment; @@ -58,8 +58,7 @@ static void *image_load(struct kimage *image, /* Check cpu features */ flags = le64_to_cpu(h->flags); be_image = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_BE); - be_kernel = IS_ENABLED(CONFIG_CPU_BIG_ENDIAN); - if ((be_image != be_kernel) && !system_supports_mixed_endian()) + if (be_image && !system_supports_mixed_endian()) return ERR_PTR(-EINVAL); value = arm64_image_flag_field(flags, ARM64_IMAGE_FLAG_PAGE_SIZE); diff --git a/arch/arm64/kernel/kgdb.c b/arch/arm64/kernel/kgdb.c index 968324a79a8972..3426d0908973ab 100644 --- a/arch/arm64/kernel/kgdb.c +++ b/arch/arm64/kernel/kgdb.c @@ -60,11 +60,7 @@ struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = { * 32-bits. Look for the big comment in asm/kgdb.h for more * detail. */ - { "pstate", 4, offsetof(struct pt_regs, pstate) -#ifdef CONFIG_CPU_BIG_ENDIAN - + 4 -#endif - }, + { "pstate", 4, offsetof(struct pt_regs, pstate)}, { "v0", 16, -1 }, { "v1", 16, -1 }, { "v2", 16, -1 }, diff --git a/arch/arm64/kernel/ptrace.c b/arch/arm64/kernel/ptrace.c index f743cbec1c3ad3..7405c76724bfec 100644 --- a/arch/arm64/kernel/ptrace.c +++ b/arch/arm64/kernel/ptrace.c @@ -2517,12 +2517,8 @@ static int valid_compat_regs(struct user_pt_regs *regs) { regs->pstate &= ~SPSR_EL1_AARCH32_RES0_BITS; - if (!system_supports_mixed_endian_el0()) { - if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) - regs->pstate |= PSR_AA32_E_BIT; - else - regs->pstate &= ~PSR_AA32_E_BIT; - } + if (!system_supports_mixed_endian_el0()) + regs->pstate &= ~PSR_AA32_E_BIT; if (user_mode(regs) && (regs->pstate & PSR_MODE32_BIT) && (regs->pstate & PSR_AA32_A_BIT) == 0 && diff --git a/arch/arm64/kernel/signal32.c b/arch/arm64/kernel/signal32.c index bb3b526ff43f71..5edcab50f75341 100644 --- a/arch/arm64/kernel/signal32.c +++ b/arch/arm64/kernel/signal32.c @@ -334,9 +334,6 @@ static void compat_setup_return(struct pt_regs *regs, struct k_sigaction *ka, /* The IT state must be cleared for both ARM and Thumb-2 */ spsr &= ~PSR_AA32_IT_MASK; - /* Restore the original endianness */ - spsr |= PSR_AA32_ENDSTATE; - if (ka->sa.sa_flags & SA_RESTORER) { retcode = ptr_to_compat(ka->sa.sa_restorer); } else { diff --git a/arch/arm64/kernel/sys32.c b/arch/arm64/kernel/sys32.c index 12a948f3a50437..8ac07aeb018df5 100644 --- a/arch/arm64/kernel/sys32.c +++ b/arch/arm64/kernel/sys32.c @@ -59,12 +59,7 @@ COMPAT_SYSCALL_DEFINE6(aarch32_mmap2, unsigned long, addr, unsigned long, len, return ksys_mmap_pgoff(addr, len, prot, flags, fd, off_4k); } -#ifdef CONFIG_CPU_BIG_ENDIAN -#define arg_u32p(name) u32, name##_hi, u32, name##_lo -#else #define arg_u32p(name) u32, name##_lo, u32, name##_hi -#endif - #define arg_u64(name) (((u64)name##_hi << 32) | name##_lo) COMPAT_SYSCALL_DEFINE6(aarch32_pread64, unsigned int, fd, char __user *, buf, From e93a371bb49fc4ea7cbd51a54d7dc453bf0d8ed3 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:12 +0100 Subject: [PATCH 0803/2286] arm64: Remove all usage of __AARCH64EB__ Big-endian support on arm64 depends on BROKEN. In preparation for removing the dead code altogether, remove all usage of __AARCH64EB__ from the arm64 kernel port. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/compat.h | 14 -------------- arch/arm64/include/asm/elf.h | 12 ------------ arch/arm64/include/asm/word-at-a-time.h | 6 ------ arch/arm64/include/uapi/asm/byteorder.h | 4 ---- arch/arm64/kernel/signal32.c | 10 ---------- arch/arm64/mm/extable.c | 5 ----- 6 files changed, 51 deletions(-) diff --git a/arch/arm64/include/asm/compat.h b/arch/arm64/include/asm/compat.h index ae904a1ad5293b..5e4cc4d2f872dd 100644 --- a/arch/arm64/include/asm/compat.h +++ b/arch/arm64/include/asm/compat.h @@ -28,34 +28,20 @@ typedef u16 compat_ipc_pid_t; #include #include -#ifdef __AARCH64EB__ -#define COMPAT_UTS_MACHINE "armv8b\0\0" -#else #define COMPAT_UTS_MACHINE "armv8l\0\0" -#endif typedef u16 __compat_uid16_t; typedef u16 __compat_gid16_t; typedef s32 compat_nlink_t; struct compat_stat { -#ifdef __AARCH64EB__ - short st_dev; - short __pad1; -#else compat_dev_t st_dev; -#endif compat_ino_t st_ino; compat_mode_t st_mode; compat_ushort_t st_nlink; __compat_uid16_t st_uid; __compat_gid16_t st_gid; -#ifdef __AARCH64EB__ - short st_rdev; - short __pad2; -#else compat_dev_t st_rdev; -#endif compat_off_t st_size; compat_off_t st_blksize; compat_off_t st_blocks; diff --git a/arch/arm64/include/asm/elf.h b/arch/arm64/include/asm/elf.h index d2779d604c7be2..5a0bd3d38b1a79 100644 --- a/arch/arm64/include/asm/elf.h +++ b/arch/arm64/include/asm/elf.h @@ -72,11 +72,7 @@ * These are used to set parameters in the core dumps. */ #define ELF_CLASS ELFCLASS64 -#ifdef __AARCH64EB__ -#define ELF_DATA ELFDATA2MSB -#else #define ELF_DATA ELFDATA2LSB -#endif #define ELF_ARCH EM_AARCH64 /* @@ -85,11 +81,7 @@ * intent than poking at uname or /proc/cpuinfo. */ #define ELF_PLATFORM_SIZE 16 -#ifdef __AARCH64EB__ -#define ELF_PLATFORM ("aarch64_be") -#else #define ELF_PLATFORM ("aarch64") -#endif /* * This is used to ensure we don't load something for the wrong architecture. @@ -195,11 +187,7 @@ extern int arch_setup_additional_pages(struct linux_binprm *bprm, #define STACK_RND_MASK (0x3ffff >> (PAGE_SHIFT - 12)) #endif -#ifdef __AARCH64EB__ -#define COMPAT_ELF_PLATFORM ("v8b") -#else #define COMPAT_ELF_PLATFORM ("v8l") -#endif /* AArch32 registers. */ #define COMPAT_ELF_NGREG 18 diff --git a/arch/arm64/include/asm/word-at-a-time.h b/arch/arm64/include/asm/word-at-a-time.h index 824ca6987a516d..d7a3b69dc8c6f6 100644 --- a/arch/arm64/include/asm/word-at-a-time.h +++ b/arch/arm64/include/asm/word-at-a-time.h @@ -7,8 +7,6 @@ #include -#ifndef __AARCH64EB__ - #include #include @@ -36,10 +34,6 @@ static inline unsigned long zero_bytemask(unsigned long bits) return bits >> 7; } -#else /* __AARCH64EB__ */ -#include -#endif - /* * Load an unaligned word from kernel space. * diff --git a/arch/arm64/include/uapi/asm/byteorder.h b/arch/arm64/include/uapi/asm/byteorder.h index ca9cfdab33fc59..b078e60f050656 100644 --- a/arch/arm64/include/uapi/asm/byteorder.h +++ b/arch/arm64/include/uapi/asm/byteorder.h @@ -17,10 +17,6 @@ #ifndef __ASM_BYTEORDER_H #define __ASM_BYTEORDER_H -#ifdef __AARCH64EB__ -#include -#else #include -#endif #endif /* __ASM_BYTEORDER_H */ diff --git a/arch/arm64/kernel/signal32.c b/arch/arm64/kernel/signal32.c index 5edcab50f75341..3e1ce0b00a6802 100644 --- a/arch/arm64/kernel/signal32.c +++ b/arch/arm64/kernel/signal32.c @@ -70,22 +70,12 @@ static inline int get_sigset_t(sigset_t *set, /* * VFP save/restore code. - * - * We have to be careful with endianness, since the fpsimd context-switch - * code operates on 128-bit (Q) register values whereas the compat ABI - * uses an array of 64-bit (D) registers. Consequently, we need to swap - * the two halves of each Q register when running on a big-endian CPU. */ union __fpsimd_vreg { __uint128_t raw; struct { -#ifdef __AARCH64EB__ - u64 hi; - u64 lo; -#else u64 lo; u64 hi; -#endif }; }; diff --git a/arch/arm64/mm/extable.c b/arch/arm64/mm/extable.c index 6e0528831cd3a6..299a244f68e608 100644 --- a/arch/arm64/mm/extable.c +++ b/arch/arm64/mm/extable.c @@ -79,12 +79,7 @@ ex_handler_load_unaligned_zeropad(const struct exception_table_entry *ex, addr &= ~0x7UL; data = *(unsigned long*)addr; - -#ifndef __AARCH64EB__ data >>= 8 * offset; -#else - data <<= 8 * offset; -#endif pt_regs_write_reg(regs, reg_data, data); From bbce799407f989924b9974cf6805c1e9686b29ed Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:13 +0100 Subject: [PATCH 0804/2286] crypto: hisilicon/sec2: Remove check of CONFIG_CPU_LITTLE_ENDIAN The hisilicon "sec" driver targets arm64 but can be compile-tested on other 64-bit architectures. In preparation for removing the definition of CONFIG_CPU_LITTLE_ENDIAN for arm64 (since it will be little-endian only), invert the check in sec_set_endian() so that it behaves as little-endian by default. Cc: Chenghai Huang Cc: Weili Qian Cc: Zaibo Xu Cc: Longfang Liu Cc: Kai Ye Cc: Wei Zhang Signed-off-by: Will Deacon Reviewed-by: Longfang Liu Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- drivers/crypto/hisilicon/sec2/sec_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/crypto/hisilicon/sec2/sec_main.c b/drivers/crypto/hisilicon/sec2/sec_main.c index 752565200c1651..03523cfbb28e2e 100644 --- a/drivers/crypto/hisilicon/sec2/sec_main.c +++ b/drivers/crypto/hisilicon/sec2/sec_main.c @@ -484,7 +484,7 @@ static void sec_set_endian(struct hisi_qm *qm) if (!IS_ENABLED(CONFIG_64BIT)) reg |= BIT(1); - if (!IS_ENABLED(CONFIG_CPU_LITTLE_ENDIAN)) + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) reg |= BIT(0); writel_relaxed(reg, qm->io_base + SEC_CONTROL_REG); From 0f2d90c19e6f1bb4485a027f4c562d69838ef2a3 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Wed, 23 Sep 2026 16:52:07 +0100 Subject: [PATCH 0805/2286] gve: Add explicit ARM64 dependency to Kconfig The gve driver depends on X86 || CPU_LITTLE_ENDIAN, the latter being the default on arm64. In preparation for removing the definition of CONFIG_CPU_LITTLE_ENDIAN for arm64 (since it will be little-endian only), add an explicit ARM64 dependency so that the driver remains selectable. Suggested-by: Will Deacon Signed-off-by: Catalin Marinas --- drivers/net/ethernet/google/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/google/Kconfig b/drivers/net/ethernet/google/Kconfig index 14c9431e15e544..a68327f47acb42 100644 --- a/drivers/net/ethernet/google/Kconfig +++ b/drivers/net/ethernet/google/Kconfig @@ -17,7 +17,7 @@ if NET_VENDOR_GOOGLE config GVE tristate "Google Virtual NIC (gVNIC) support" - depends on (PCI_MSI && (X86 || CPU_LITTLE_ENDIAN)) + depends on (PCI_MSI && (X86 || ARM64 || CPU_LITTLE_ENDIAN)) depends on PTP_1588_CLOCK_OPTIONAL select PAGE_POOL help From 134b0fbc5c242d1c785447850e0a0c9ed7bac6c1 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:14 +0100 Subject: [PATCH 0806/2286] arm64: Kbuild: Remove vestigial big-endian support Since 1cf89b6bf660 ("arm64: Kconfig: Make CPU_BIG_ENDIAN depend on BROKEN"), CONFIG_CPU_BIG_ENDIAN has depended on BROKEN for arm64. Remove the last traces of its existence and unconditionally force a little-endian kernel build. Acked-by: Catalin Marinas Acked-by: Arnd Bergmann Acked-by: Marc Zyngier Acked-by: Eric Biggers Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/Kconfig | 35 ++++------------------------------- arch/arm64/Makefile | 12 ++---------- 2 files changed, 6 insertions(+), 41 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..766410827f3c4f 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -73,7 +73,7 @@ config ARM64 select ARCH_SUPPORTS_HUGETLBFS select ARCH_SUPPORTS_MEMORY_FAILURE select ARCH_SUPPORTS_SHADOW_CALL_STACK if CC_HAVE_SHADOW_CALL_STACK - select ARCH_SUPPORTS_LTO_CLANG if CPU_LITTLE_ENDIAN + select ARCH_SUPPORTS_LTO_CLANG select ARCH_SUPPORTS_LTO_CLANG_THIN select ARCH_SUPPORTS_CFI select ARCH_SUPPORTS_ATOMIC_RMW @@ -224,7 +224,7 @@ config ARM64 select HAVE_FUNCTION_ARG_ACCESS_API select MMU_GATHER_RCU_TABLE_FREE select HAVE_RSEQ - select HAVE_RUST if RUSTC_SUPPORTS_ARM64 + select HAVE_RUST select HAVE_STACKPROTECTOR select HAVE_STATIC_CALL if CFI select HAVE_SYSCALL_TRACEPOINTS @@ -263,10 +263,6 @@ config ARM64 help ARM 64-bit (AArch64) Linux support. -config RUSTC_SUPPORTS_ARM64 - def_bool y - depends on CPU_LITTLE_ENDIAN - config CLANG_SUPPORTS_DYNAMIC_FTRACE_WITH_ARGS def_bool CC_IS_CLANG # https://github.com/ClangBuiltLinux/linux/issues/1507 @@ -1596,28 +1592,6 @@ config ARM64_LPA2 def_bool y depends on ARM64_PA_BITS_52 && !ARM64_64K_PAGES -choice - prompt "Endianness" - default CPU_LITTLE_ENDIAN - help - Select the endianness of data accesses performed by the CPU. Userspace - applications will need to be compiled and linked for the endianness - that is selected here. - -config CPU_BIG_ENDIAN - bool "Build big-endian kernel" - depends on BROKEN - help - Say Y if you plan on running a kernel with a big-endian userspace. - -config CPU_LITTLE_ENDIAN - bool "Build little-endian kernel" - help - Say Y if you plan on running a kernel with a little-endian userspace. - This is usually the case for distributions targeting arm64. - -endchoice - config NR_CPUS int "Maximum number of CPUs (2-4096)" range 2 4096 @@ -1855,7 +1829,6 @@ config KUSER_HELPERS config COMPAT_VDSO bool "Enable vDSO for 32-bit applications" - depends on !CPU_BIG_ENDIAN depends on (CC_IS_CLANG && LD_IS_LLD) || "$(CROSS_COMPILE_COMPAT)" != "" default y help @@ -2319,7 +2292,7 @@ menu "ARMv9.6 architectural features" config ARM64_LSUI bool "Support Unprivileged Load Store Instructions (LSUI)" default y - depends on AS_HAS_LSUI && !CPU_BIG_ENDIAN + depends on AS_HAS_LSUI help The Unprivileged Load Store Instructions (LSUI) provides variants load/store instructions that access user-space memory @@ -2524,7 +2497,7 @@ config EFI_STUB config EFI bool "UEFI runtime support" - depends on OF && !CPU_BIG_ENDIAN + depends on OF depends on KERNEL_MODE_NEON select ARCH_SUPPORTS_ACPI select LIBFDT diff --git a/arch/arm64/Makefile b/arch/arm64/Makefile index 6b005c8fef7066..04e1a0d12f8f51 100644 --- a/arch/arm64/Makefile +++ b/arch/arm64/Makefile @@ -119,20 +119,12 @@ KBUILD_CFLAGS += -ffixed-x18 KBUILD_RUSTFLAGS += -Zfixed-x18 endif -ifeq ($(CONFIG_CPU_BIG_ENDIAN), y) -KBUILD_CPPFLAGS += -mbig-endian -CHECKFLAGS += -D__AARCH64EB__ -# Prefer the baremetal ELF build target, but not all toolchains include -# it so fall back to the standard linux version if needed. -KBUILD_LDFLAGS += -EB $(call ld-option, -maarch64elfb, -maarch64linuxb -z norelro) -UTS_MACHINE := aarch64_be -else KBUILD_CPPFLAGS += -mlittle-endian CHECKFLAGS += -D__AARCH64EL__ -# Same as above, prefer ELF but fall back to linux target if needed. +# Prefer the baremetal ELF build target, but not all toolchains include +# it so fall back to the standard linux version if needed. KBUILD_LDFLAGS += -EL $(call ld-option, -maarch64elf, -maarch64linux -z norelro) UTS_MACHINE := aarch64 -endif ifeq ($(CONFIG_LD_IS_LLD), y) KBUILD_LDFLAGS += -z norelro From d80ff69c9a3722c2b68b590efda7d9903cfe609e Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 18 Sep 2026 15:52:15 +0100 Subject: [PATCH 0807/2286] drivers: Kconfig: Simplify dependencies on ARM64 && !CPU_BIG_ENDIAN Now that arm64 no longer supports a big-endian kernel, simplify some driver Kconfig entries so that the dependency on a little-endian kernel no longer needs to be called out explicitly. Reviewed-by: Michael Kelley # Hyper-V Suggested-by: Arnd Bergmann Signed-off-by: Will Deacon Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- drivers/hv/Kconfig | 3 +-- drivers/misc/vmw_vmci/Kconfig | 2 +- drivers/net/ethernet/microsoft/Kconfig | 2 +- drivers/soc/tegra/Kconfig | 5 ----- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig index aa11bcefddf2c0..36445c2619ed1c 100644 --- a/drivers/hv/Kconfig +++ b/drivers/hv/Kconfig @@ -4,8 +4,7 @@ menu "Microsoft Hyper-V guest support" config HYPERV bool "Microsoft Hyper-V core hypervisor support" - depends on (X86 && X86_LOCAL_APIC && HYPERVISOR_GUEST) \ - || (ARM64 && !CPU_BIG_ENDIAN) + depends on (X86 && X86_LOCAL_APIC && HYPERVISOR_GUEST) || ARM64 select PARAVIRT select X86_HV_CALLBACK_VECTOR if X86 select OF_EARLY_FLATTREE if OF diff --git a/drivers/misc/vmw_vmci/Kconfig b/drivers/misc/vmw_vmci/Kconfig index b6d4d7fd686a52..ca03cbfdab1e1f 100644 --- a/drivers/misc/vmw_vmci/Kconfig +++ b/drivers/misc/vmw_vmci/Kconfig @@ -5,7 +5,7 @@ config VMWARE_VMCI tristate "VMware VMCI Driver" - depends on (X86 || ARM64) && !CPU_BIG_ENDIAN && PCI + depends on (X86 || ARM64) && PCI help This is VMware's Virtual Machine Communication Interface. It enables high-speed communication between host and guest in a virtual diff --git a/drivers/net/ethernet/microsoft/Kconfig b/drivers/net/ethernet/microsoft/Kconfig index e9be18c92ca5f2..c4ad3d85875a67 100644 --- a/drivers/net/ethernet/microsoft/Kconfig +++ b/drivers/net/ethernet/microsoft/Kconfig @@ -18,7 +18,7 @@ if NET_VENDOR_MICROSOFT config MICROSOFT_MANA tristate "Microsoft Azure Network Adapter (MANA) support" depends on PCI_MSI - depends on X86_64 || (ARM64 && !CPU_BIG_ENDIAN) + depends on X86_64 || ARM64 depends on PCI_HYPERV select AUXILIARY_BUS select DIMLIB diff --git a/drivers/soc/tegra/Kconfig b/drivers/soc/tegra/Kconfig index 8b6fdb9ad1ea3d..318e7b60434249 100644 --- a/drivers/soc/tegra/Kconfig +++ b/drivers/soc/tegra/Kconfig @@ -102,7 +102,6 @@ config ARCH_TEGRA_210_SOC config ARCH_TEGRA_186_SOC bool "NVIDIA Tegra186 SoC" default ARCH_TEGRA - depends on !CPU_BIG_ENDIAN select PINCTRL_TEGRA186 select MAILBOX select SOC_TEGRA_PMC @@ -117,7 +116,6 @@ config ARCH_TEGRA_186_SOC config ARCH_TEGRA_194_SOC bool "NVIDIA Tegra194 SoC" default ARCH_TEGRA - depends on !CPU_BIG_ENDIAN select MAILBOX select PINCTRL_TEGRA194 select SOC_TEGRA_PMC @@ -127,7 +125,6 @@ config ARCH_TEGRA_194_SOC config ARCH_TEGRA_234_SOC bool "NVIDIA Tegra234 SoC" default ARCH_TEGRA - depends on !CPU_BIG_ENDIAN select MAILBOX select PINCTRL_TEGRA234 select SOC_TEGRA_PMC @@ -137,7 +134,6 @@ config ARCH_TEGRA_234_SOC config ARCH_TEGRA_238_SOC bool "NVIDIA Tegra238 SoC" default ARCH_TEGRA - depends on !CPU_BIG_ENDIAN select MAILBOX select SOC_TEGRA_PMC help @@ -152,7 +148,6 @@ config ARCH_TEGRA_241_SOC config ARCH_TEGRA_264_SOC bool "NVIDIA Tegra264 SoC" default ARCH_TEGRA - depends on !CPU_BIG_ENDIAN select MAILBOX select SOC_TEGRA_PMC help From 6e69419ca6a4bfc3f3445ab139fd07a0e9cf74be Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Wed, 23 Sep 2026 23:32:20 +0800 Subject: [PATCH 0808/2286] isofs: validate directory records in isofs_read_level3_size() isofs_read_level3_size() walks the multi-extent directory records of a file and dereferences de->size and de->flags for each one without ever checking the record's length byte. A record with a short length placed near the end of a block makes those fixed-field reads run past the record, and for a record at the end of the last block of a page, past the buffer. readdir, lookup and the NFS get_parent path have validated every record with isofs_dir_record_valid() since commit e2ee4078ec58 ("isofs: validate directory records consistently"). Do the same here, after moving on to the next block when the previous record ended exactly at the end of the block or the rest of the block is zero padding, as commit 3c01d9263683 ("isofs: Fix handling of directories with tight blocks") does for the other two walkers. The check rejects a record that would run past its block, so the code that reassembled such a record from two blocks can no longer run. ECMA-119 does not allow directory records to straddle sector boundaries, which is what commit b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") relied on when it removed the same code from readdir and lookup. Remove it here too, along with its temporary record buffer. Found by fuzzing fs/isofs in a userspace harness with ASan (heap-buffer-overflow reads in de->flags). Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260923153220.777907-1-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/inode.c | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index b766b5c9c593bd..184350d2e6ad54 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -1176,7 +1176,6 @@ static int isofs_read_level3_size(struct inode *inode) unsigned long block, offset, block_saved, offset_saved; int i = 0; int more_entries = 0; - struct iso_directory_record *tmpde = NULL; struct iso_inode_info *ei = ISOFS_I(inode); inode->i_size = 0; @@ -1200,9 +1199,12 @@ static int isofs_read_level3_size(struct inode *inode) goto out_noread; } de = (struct iso_directory_record *) (bh->b_data + offset); - de_len = *(unsigned char *) de; - if (de_len == 0) { + /* + * If we are at the end of a block (or at its zero-padded + * tail), move on to the next block. + */ + if (offset >= bufsize || de->length[0] == 0) { brelse(bh); bh = NULL; ++block; @@ -1210,32 +1212,18 @@ static int isofs_read_level3_size(struct inode *inode) continue; } + if (!isofs_dir_record_valid(de, offset, bufsize)) { + printk(KERN_NOTICE "iso9660: Corrupted directory entry in block %lu of inode %llu\n", + block, inode->i_ino); + brelse(bh); + return -EIO; + } + + de_len = de->length[0]; block_saved = block; offset_saved = offset; offset += de_len; - /* Make sure we have a full directory entry */ - if (offset >= bufsize) { - int slop = bufsize - offset + de_len; - if (!tmpde) { - tmpde = kmalloc(256, GFP_KERNEL); - if (!tmpde) - goto out_nomem; - } - memcpy(tmpde, de, slop); - offset &= bufsize - 1; - block++; - brelse(bh); - bh = NULL; - if (offset) { - bh = sb_bread(inode->i_sb, block); - if (!bh) - goto out_noread; - memcpy((void *)tmpde+slop, bh->b_data, offset); - } - de = tmpde; - } - inode->i_size += isonum_733(de->size); if (i == 1) { ei->i_next_section_block = block_saved; @@ -1249,17 +1237,11 @@ static int isofs_read_level3_size(struct inode *inode) goto out_toomany; } while (more_entries); out: - kfree(tmpde); brelse(bh); return 0; -out_nomem: - brelse(bh); - return -ENOMEM; - out_noread: printk(KERN_INFO "ISOFS: unable to read i-node block %lu\n", block); - kfree(tmpde); return -EIO; out_toomany: From 966bbfa5f24f601593a0890154e3fb5fddc51e6e Mon Sep 17 00:00:00 2001 From: David Laight Date: Mon, 3 Aug 2026 10:47:02 +0100 Subject: [PATCH 0809/2286] x86/local: Support large immediate values in local_(add|sub)() Replace the "ir" constraint with "er" so that constants over 2^31 get loaded into a register due to ADD/SUB supporting only 32-bit signed immediates. [ bp: Massage commit message. ] Signed-off-by: David Laight Signed-off-by: Borislav Petkov (AMD) Reviewed-by: H. Peter Anvin Acked-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260803094702.3852-3-david.laight.linux@gmail.com --- arch/x86/include/asm/local.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/local.h b/arch/x86/include/asm/local.h index 4957018fef3ede..68af7b74450af7 100644 --- a/arch/x86/include/asm/local.h +++ b/arch/x86/include/asm/local.h @@ -32,14 +32,14 @@ static inline void local_add(long i, local_t *l) { asm volatile(_ASM_ADD "%1,%0" : "+m" (l->a.counter) - : "ir" (i)); + : "er" (i)); } static inline void local_sub(long i, local_t *l) { asm volatile(_ASM_SUB "%1,%0" : "+m" (l->a.counter) - : "ir" (i)); + : "er" (i)); } /** From d267d2f41222912a5f40dc064df64cee57c8bade Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 23 Sep 2026 23:09:53 +0100 Subject: [PATCH 0810/2286] bitfield: Restore be16_replace_bits() Commit 1c88ceb09d5e5 ("bitfield: get rid of __MAKE_OP machinery") removed a bunch of unused bitfield operations including be16_replace_bits() but commit 508bdb4938928 ("iio: adc: Add AD7768 and AD7768-4 core support") introduces a new user. Restore the function so the build still works once both branches are merged. Signed-off-by: Mark Brown Signed-off-by: Yury Norov --- include/linux/bitfield-fix-width.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/bitfield-fix-width.h b/include/linux/bitfield-fix-width.h index fba86bb76999f1..f73b8cd656010b 100644 --- a/include/linux/bitfield-fix-width.h +++ b/include/linux/bitfield-fix-width.h @@ -93,6 +93,12 @@ u16 be16_get_bits(__be16 v, u16 field) return (be16_to_cpu(v) & field) / field_multiplier(field); } +static __always_inline __must_check +__be16 be16_replace_bits(__be16 old, u16 val, u16 field) +{ + return (old & ~cpu_to_be16(field)) | be16_encode_bits(val, field); +} + static __always_inline __must_check __u16 u16_encode_bits(u16 v, u16 field) { From 368fae47c6cae28a312c5dc86e5bdb8f9369fd26 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Wed, 23 Sep 2026 15:56:21 -0500 Subject: [PATCH 0811/2286] tools/nolibc: fix WIFSIGNALED() for status 0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WIFSIGNALED() needs the subtraction to wrap around for status 0, so that only 1 to 0xff, a terminating signal with or without the core dump flag, pass the check. But status is an int, and 0 - 1 is -1, which is less than 0xff. A child that exits with 0 is therefore reported as both exited and killed, and a caller that tests WIFSIGNALED() first sees "killed by signal 0". Make the subtraction unsigned, as musl does in the same macro. Fixes: 8c934d4822c7 ("tools/nolibc: add helpers for wait() signal exits") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260923205622.1123270-2-danishkhateeb03@gmail.com [Thomas: Drop stable@ Cc] Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/types.h b/tools/include/nolibc/types.h index 8f3cb18df7f1c8..4373bd8a5b5f89 100644 --- a/tools/include/nolibc/types.h +++ b/tools/include/nolibc/types.h @@ -119,7 +119,7 @@ struct timeval { #define WEXITSTATUS(status) (((status) & 0xff00) >> 8) #define WIFEXITED(status) (((status) & 0x7f) == 0) #define WTERMSIG(status) ((status) & 0x7f) -#define WIFSIGNALED(status) ((status) - 1 < 0xff) +#define WIFSIGNALED(status) ((status) - 1U < 0xff) /* standard exit() codes */ #define EXIT_SUCCESS 0 From 61fb00d6efeeeb9fd65c81db3bd8b2ac140f573c Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Wed, 23 Sep 2026 15:56:22 -0500 Subject: [PATCH 0812/2286] selftests/nolibc: add tests for the wait status macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until the previous commit, WIFSIGNALED() was true for status 0, which is what waitpid() reports for a child that exits with 0. nolibc-test did not notice, because its only use of WIFSIGNALED(), in the abort test, also checks WTERMSIG(). Add tests for WEXITSTATUS(), WIFEXITED(), WIFSIGNALED() and WTERMSIG(), on the statuses the kernel reports for an exit with 0 or 0x12, a kill by SIGKILL, a SIGSEGV with the core dump flag, and a stop by SIGSTOP. Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260923205622.1123270-3-danishkhateeb03@gmail.com [Thomas: Use EXPECT_EQ() for all assertions] Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index f62183a46205c1..37c11a7fce238a 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -2038,6 +2038,14 @@ int run_stdlib(int min, int max) CASE_TEST(htole32); EXPECT_EQ(1, htole32(is_le ? 0x01234567 : 0x67452301), 0x01234567); break; CASE_TEST(htobe64); EXPECT_EQ(1, htobe64(is_le ? 0x0123456789000000 : 0x8967452301), 0x8967452301); break; CASE_TEST(htole64); EXPECT_EQ(1, htole64(is_le ? 0x0123456789 : 0x8967452301000000), 0x0123456789); break; + CASE_TEST(wexitstatus); EXPECT_EQ(1, WEXITSTATUS(0x1200), 0x12); break; + CASE_TEST(wifexited_exit0); EXPECT_EQ(1, WIFEXITED(0), 1); break; + CASE_TEST(wifexited_signal); EXPECT_EQ(1, WIFEXITED(SIGKILL), 0); break; + CASE_TEST(wifsignaled_exit0); EXPECT_EQ(1, WIFSIGNALED(0), 0); break; + CASE_TEST(wifsignaled_exit); EXPECT_EQ(1, WIFSIGNALED(0x1200), 0); break; + CASE_TEST(wifsignaled_signal); EXPECT_EQ(1, WIFSIGNALED(SIGKILL), 1); break; + CASE_TEST(wifsignaled_stopped); EXPECT_EQ(1, WIFSIGNALED((SIGSTOP << 8) | 0x7f), 0); break; + CASE_TEST(wtermsig_core); EXPECT_EQ(1, WTERMSIG(0x80 | SIGSEGV), SIGSEGV); break; case __LINE__: return ret; /* must be last */ From fe7f2a805443368be4e2ed1b4faf3797d65fd134 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:33:58 +0530 Subject: [PATCH 0813/2286] firmware: smccc: Add an Arm SMCCC bus SMCCC-discovered firmware services are currently represented by separate platform devices, such as smccc_trng and arm-cca-dev. Those devices do not represent independent DT/ACPI-described platform resources; they are features of the SMCCC firmware interface. Add an Arm SMCCC bus for services discovered through the SMCCC firmware interface. The bus provides SMCCC device and driver registration helpers, function ID based matching, modalias generation, and a sysfs modalias attribute so SMCCC service drivers can bind to discovered firmware services and autoload as modules. Follow-up changes can then register SMCCC firmware services as arm-smccc devices instead of creating independent per-feature platform devices. Based on arm_ffa code Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Reviewed-by: Sudeep Holla Cc: Mark Rutland Cc: Lorenzo Pieralisi Cc: Sudeep Holla Cc: Nathan Chancellor Cc: Nicolas Schier Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- MAINTAINERS | 2 + drivers/firmware/smccc/Makefile | 2 +- drivers/firmware/smccc/bus.c | 158 ++++++++++++++++++++++++++++ include/linux/arm-smccc-bus.h | 48 +++++++++ include/linux/device-id/arm_smccc.h | 19 ++++ include/linux/mod_devicetable.h | 1 + scripts/mod/devicetable-offsets.c | 3 + scripts/mod/file2alias.c | 8 ++ 8 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/smccc/bus.c create mode 100644 include/linux/arm-smccc-bus.h create mode 100644 include/linux/device-id/arm_smccc.h diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b34..b8e8b362f932a1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24742,7 +24742,9 @@ M: Sudeep Holla L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: drivers/firmware/smccc/ +F: include/linux/arm-smccc-bus.h F: include/linux/arm-smccc.h +F: include/linux/device-id/arm_smccc.h SECURITY CONTACT M: Security Officers diff --git a/drivers/firmware/smccc/Makefile b/drivers/firmware/smccc/Makefile index 40d19144a86072..68bbff1407b8b4 100644 --- a/drivers/firmware/smccc/Makefile +++ b/drivers/firmware/smccc/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 # -obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smccc.o kvm_guest.o +obj-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += bus.o smccc.o kvm_guest.o obj-$(CONFIG_ARM_SMCCC_SOC_ID) += soc_id.o diff --git a/drivers/firmware/smccc/bus.c b/drivers/firmware/smccc/bus.c new file mode 100644 index 00000000000000..0357a7c48af0ba --- /dev/null +++ b/drivers/firmware/smccc/bus.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 Arm Limited + */ + +#include +#include + +static int arm_smccc_bus_match(struct device *dev, + const struct device_driver *drv) +{ + const struct arm_smccc_device_id *id_table; + const struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev); + + id_table = to_arm_smccc_driver(drv)->id_table; + if (!id_table) + return 0; + + while (id_table->func_id) { + if (smccc_dev->func_id == id_table->func_id) + return 1; + id_table++; + } + + return 0; +} + +static int arm_smccc_bus_probe(struct device *dev) +{ + struct arm_smccc_driver *smccc_drv = to_arm_smccc_driver(dev->driver); + + return smccc_drv->probe(to_arm_smccc_device(dev)); +} + +static void arm_smccc_bus_remove(struct device *dev) +{ + struct arm_smccc_driver *smcc_drv = to_arm_smccc_driver(dev->driver); + + if (smcc_drv->remove) + smcc_drv->remove(to_arm_smccc_device(dev)); +} + +static int arm_smccc_bus_uevent(const struct device *dev, + struct kobj_uevent_env *env) +{ + const struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev); + + return add_uevent_var(env, "MODALIAS=" ARM_SMCCC_MODULE_PREFIX "f%08X", + smccc_dev->func_id); +} + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev); + + return sysfs_emit(buf, ARM_SMCCC_MODULE_PREFIX "f%08X\n", + smccc_dev->func_id); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *arm_smccc_device_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(arm_smccc_device); + +const struct bus_type arm_smccc_bus_type = { + .name = "arm_smccc", + .match = arm_smccc_bus_match, + .probe = arm_smccc_bus_probe, + .remove = arm_smccc_bus_remove, + .uevent = arm_smccc_bus_uevent, + .dev_groups = arm_smccc_device_groups, +}; +EXPORT_SYMBOL_GPL(arm_smccc_bus_type); + +int arm_smccc_driver_register(struct arm_smccc_driver *driver, + struct module *owner, const char *mod_name) +{ + if (!driver->probe || !driver->id_table) + return -EINVAL; + + driver->driver.bus = &arm_smccc_bus_type; + driver->driver.name = driver->name; + driver->driver.owner = owner; + driver->driver.mod_name = mod_name; + + return driver_register(&driver->driver); +} +EXPORT_SYMBOL_GPL(arm_smccc_driver_register); + +void arm_smccc_driver_unregister(struct arm_smccc_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(arm_smccc_driver_unregister); + +static void arm_smccc_release_device(struct device *dev) +{ + struct arm_smccc_device *smccc_dev = to_arm_smccc_device(dev); + + kfree(smccc_dev); +} + +static struct device *arm_smccc_device_alloc(u32 func_id) +{ + struct arm_smccc_device *smccc_dev; + + smccc_dev = kzalloc_obj(*smccc_dev); + if (!smccc_dev) + return NULL; + + smccc_dev->func_id = func_id; + smccc_dev->dev.bus = &arm_smccc_bus_type; + smccc_dev->dev.release = arm_smccc_release_device; + device_initialize(&smccc_dev->dev); + + return &smccc_dev->dev; +} + +struct arm_smccc_device *arm_smccc_device_register(const char *name, u32 func_id) +{ + int ret; + + if (!name) + return ERR_PTR(-EINVAL); + + struct device *dev __free(put_device) = arm_smccc_device_alloc(func_id); + if (!dev) + return ERR_PTR(-ENOMEM); + + ret = dev_set_name(dev, "%s", name); + if (ret) + return ERR_PTR(ret); + + ret = device_add(dev); + if (ret) + return ERR_PTR(ret); + + return to_arm_smccc_device(no_free_ptr(dev)); +} +EXPORT_SYMBOL_GPL(arm_smccc_device_register); + +void arm_smccc_device_unregister(struct arm_smccc_device *smccc_dev) +{ + if (!smccc_dev) + return; + + device_unregister(&smccc_dev->dev); +} +EXPORT_SYMBOL_GPL(arm_smccc_device_unregister); + +static int __init arm_smccc_bus_init(void) +{ + return bus_register(&arm_smccc_bus_type); +} +subsys_initcall(arm_smccc_bus_init); diff --git a/include/linux/arm-smccc-bus.h b/include/linux/arm-smccc-bus.h new file mode 100644 index 00000000000000..05c0c850510e05 --- /dev/null +++ b/include/linux/arm-smccc-bus.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2026 Arm Limited + */ +#ifndef __LINUX_ARM_SMCCC_BUS_H +#define __LINUX_ARM_SMCCC_BUS_H + +#include +#include +#include + +struct arm_smccc_device { + struct device dev; + u32 func_id; +}; + +#define to_arm_smccc_device(d) container_of_const(d, struct arm_smccc_device, dev) + +struct arm_smccc_driver { + struct device_driver driver; + + const char *name; + int (*probe)(struct arm_smccc_device *sdev); + void (*remove)(struct arm_smccc_device *sdev); + const struct arm_smccc_device_id *id_table; +}; + +#define to_arm_smccc_driver(d) \ + container_of_const(d, struct arm_smccc_driver, driver) + +int arm_smccc_driver_register(struct arm_smccc_driver *driver, + struct module *owner, const char *mod_name); +void arm_smccc_driver_unregister(struct arm_smccc_driver *driver); +struct arm_smccc_device *arm_smccc_device_register(const char *name, u32 func_id); +void arm_smccc_device_unregister(struct arm_smccc_device *smcc_dev); + +#define arm_smccc_register(driver) \ + arm_smccc_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) +#define arm_smccc_unregister(driver) \ + arm_smccc_driver_unregister(driver) + +#define module_arm_smccc_driver(__arm_smccc_driver) \ + module_driver(__arm_smccc_driver, arm_smccc_register, \ + arm_smccc_unregister) + +extern const struct bus_type arm_smccc_bus_type; + +#endif /* __LINUX_ARM_SMCCC_BUS_H */ diff --git a/include/linux/device-id/arm_smccc.h b/include/linux/device-id/arm_smccc.h new file mode 100644 index 00000000000000..a8579832fc1cbe --- /dev/null +++ b/include/linux/device-id/arm_smccc.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __LINUX_DEVICE_ID_ARM_SMCCC_H +#define __LINUX_DEVICE_ID_ARM_SMCCC_H + +#ifdef __KERNEL__ +#include +#endif + +#define ARM_SMCCC_MODULE_PREFIX "arm_smccc:" + +/** + * struct arm_smccc_device_id - Arm SMCCC bus device identifier + * @func_id: SMCCC function identifier + */ +struct arm_smccc_device_id { + __u32 func_id; +}; + +#endif /* __LINUX_DEVICE_ID_ARM_SMCCC_H */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index a397213bedace6..318c4b5c3451b9 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -16,6 +16,7 @@ #include "device-id/amba.h" #include "device-id/ap.h" #include "device-id/apr.h" +#include "device-id/arm_smccc.h" #include "device-id/auxiliary.h" #include "device-id/bcma.h" #include "device-id/ccw.h" diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index b4178c42d08f50..1492e9fdc7cb46 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -254,6 +254,9 @@ int main(void) DEVID(auxiliary_device_id); DEVID_FIELD(auxiliary_device_id, name); + DEVID(arm_smccc_device_id); + DEVID_FIELD(arm_smccc_device_id, func_id); + DEVID(ssam_device_id); DEVID_FIELD(ssam_device_id, match_flags); DEVID_FIELD(ssam_device_id, domain); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 8d36c74dec2d55..980d7da456bbab 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1349,6 +1349,13 @@ static void do_auxiliary_entry(struct module *mod, void *symval) module_alias_printf(mod, false, AUXILIARY_MODULE_PREFIX "%s", *name); } +static void do_arm_smccc_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, arm_smccc_device_id, func_id); + + module_alias_printf(mod, false, ARM_SMCCC_MODULE_PREFIX "f%08X", func_id); +} + /* * Looks like: ssam:dNcNtNiNfN * @@ -1519,6 +1526,7 @@ static const struct devtable devtable[] = { {"mhi", SIZE_mhi_device_id, do_mhi_entry}, {"mhi_ep", SIZE_mhi_device_id, do_mhi_ep_entry}, {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, + {"arm_smccc", SIZE_arm_smccc_device_id, do_arm_smccc_entry}, {"ssam", SIZE_ssam_device_id, do_ssam_entry}, {"dfl", SIZE_dfl_device_id, do_dfl_entry}, {"ishtp", SIZE_ishtp_device_id, do_ishtp_entry}, From 78746ba584fca1e8d4b35f4499ba33455b27672b Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:24 +0100 Subject: [PATCH 0814/2286] arm64: cmpxchg: LL/SC: Avoid redundant extension The __ll_sc__cmpxchg_case_##name##sz() template always takes 'old' as an unsigned long. When 'sz' is less than 32 bits, the 'old' is explicitly truncated so that old[31:sz-1] is zero, which is necessary so that the W-register EOR+CBNZ sequence doesn't fail spuriously. For 32-bit types specifically, no extension is necessary, but as 'old' is 64 bits, the caller must extend 'old' to 64 bits. This leads to a redundant instruction (typically a MOV) to extend the value, as can be seen from disassembly of the test case below. Avoid this by always taking 'old' as a 'u##sz' type, and explicitly zero-extend this to a 64-bit or 32-bit type matching the X or W register used by the assembly. To make this work, the table of cases now explicitly lists 'x' for the cases where an X register is used. For 32-bit types this removes the redundant instruction. For 64-bit types there is no truncation, and hence no change. For {16,8}-bit types, the existing zero-extension to 64-bit is equivalent to the new zero-extension to 32-bit, and compilers happen to use the same instructions for this. I've placed the logic for zero extension into a new header as it will be used by other logic in subsequent patches. Test case: | u32 outline_cmpxchg_u32(u32 *p, u32 o, u32 n) | { | return cmpxchg(p, o, n); | } Before this patch: | : | b 1f | casal w1, w2, [x0] | mov w0, w1 | ret | 1: mov w3, w1 | prfm pstl1strm, [x0] | 2: ldxr w1, [x0] | eor w4, w1, w3 | cbnz w4, 3f | stlxr w4, w2, [x0] | cbnz w4, 2b | dmb ish | 3: mov w0, w1 | ret After this patch: | : | b 1f | casal w1, w2, [x0] | mov w0, w1 | ret | 1: prfm pstl1strm, [x0] | 2: ldxr w3, [x0] | eor w4, w3, w1 | cbnz w4, 3f | stlxr w4, w2, [x0] | cbnz w4, 2b | dmb ish | 3: mov w0, w3 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/atomic_ll_sc.h | 27 +++++++++++++-------------- arch/arm64/include/asm/xwreg.h | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 arch/arm64/include/asm/xwreg.h diff --git a/arch/arm64/include/asm/atomic_ll_sc.h b/arch/arm64/include/asm/atomic_ll_sc.h index 89d2ba2723590d..160c1251f0ec91 100644 --- a/arch/arm64/include/asm/atomic_ll_sc.h +++ b/arch/arm64/include/asm/atomic_ll_sc.h @@ -11,6 +11,7 @@ #define __ASM_ATOMIC_LL_SC_H #include +#include #ifndef CONFIG_CC_HAS_K_CONSTRAINT #define K @@ -239,20 +240,17 @@ __ll_sc_atomic64_dec_if_positive(atomic64_t *v) #define __CMPXCHG_CASE(w, sfx, name, sz, mb, acq, rel, cl, constraint) \ static __always_inline u##sz \ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \ - unsigned long old, \ + u##sz old, \ u##sz new) \ { \ + /* \ + * Sub-word sizes require zero extension so that EOR+CBNZ won't \ + * consume non-zero upper bits of the register containing "old".\ + */ \ + xwreg_t(w) cmpval = xwreg_zero_extend(old, w, sz); \ unsigned long tmp; \ u##sz oldval; \ \ - /* \ - * Sub-word sizes require explicit casting so that the compare \ - * part of the cmpxchg doesn't end up interpreting non-zero \ - * upper bits of the register containing "old". \ - */ \ - if (sz < 32) \ - old = (u##sz)old; \ - \ asm volatile( \ " prfm pstl1strm, %[v]\n" \ "1: ld" #acq "xr" #sfx "\t%" #w "[oldval], %[v]\n" \ @@ -264,7 +262,8 @@ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \ "2:" \ : [tmp] "=&r" (tmp), [oldval] "=&r" (oldval), \ [v] "+Q" (*(u##sz *)ptr) \ - : [old] __stringify(constraint) "r" (old), [new] "r" (new) \ + : [old] __stringify(constraint) "r" (cmpval), \ + [new] "r" (new) \ : cl); \ \ return oldval; \ @@ -278,19 +277,19 @@ __ll_sc__cmpxchg_case_##name##sz(volatile void *ptr, \ __CMPXCHG_CASE(w, b, , 8, , , , , K) __CMPXCHG_CASE(w, h, , 16, , , , , K) __CMPXCHG_CASE(w, , , 32, , , , , K) -__CMPXCHG_CASE( , , , 64, , , , , L) +__CMPXCHG_CASE(x, , , 64, , , , , L) __CMPXCHG_CASE(w, b, acq_, 8, , a, , "memory", K) __CMPXCHG_CASE(w, h, acq_, 16, , a, , "memory", K) __CMPXCHG_CASE(w, , acq_, 32, , a, , "memory", K) -__CMPXCHG_CASE( , , acq_, 64, , a, , "memory", L) +__CMPXCHG_CASE(x, , acq_, 64, , a, , "memory", L) __CMPXCHG_CASE(w, b, rel_, 8, , , l, "memory", K) __CMPXCHG_CASE(w, h, rel_, 16, , , l, "memory", K) __CMPXCHG_CASE(w, , rel_, 32, , , l, "memory", K) -__CMPXCHG_CASE( , , rel_, 64, , , l, "memory", L) +__CMPXCHG_CASE(x, , rel_, 64, , , l, "memory", L) __CMPXCHG_CASE(w, b, mb_, 8, dmb ish, , l, "memory", K) __CMPXCHG_CASE(w, h, mb_, 16, dmb ish, , l, "memory", K) __CMPXCHG_CASE(w, , mb_, 32, dmb ish, , l, "memory", K) -__CMPXCHG_CASE( , , mb_, 64, dmb ish, , l, "memory", L) +__CMPXCHG_CASE(x, , mb_, 64, dmb ish, , l, "memory", L) #undef __CMPXCHG_CASE diff --git a/arch/arm64/include/asm/xwreg.h b/arch/arm64/include/asm/xwreg.h new file mode 100644 index 00000000000000..d55e3ac68f7ebf --- /dev/null +++ b/arch/arm64/include/asm/xwreg.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __ASM_XWREG_H +#define __ASM_XWREG_H + +#include + +#define __xwreg_t_x u64 +#define __xwreg_t_w u32 +#define xwreg_t(xw) __xwreg_t_##xw + +/* + * Zero extend 'v' from 'sz' bits (8/16/32/64) to fill an X or W register. + */ +#define xwreg_zero_extend(v, xw, sz) ((xwreg_t(xw))(u##sz)(v)) + +#endif /* __ASM_XWREG_H */ From de2824489899f34db976f6c8f1a548d4cf76fc44 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:25 +0100 Subject: [PATCH 0815/2286] arm64: cmpxchg128: LSE: Remove redundant operands The LSE assembly for cmpxhg128*() has redundant operands which cause unnecessary register pressure. These operands can be removed without adverse effects, as described below. It is necessary to force specific registers for the 64-bit halves of 'old' and 'new', as the encoding of CASP[A][L] requires that these halves are allocated into even/odd register pairs, and contemporary versions of LLVM don't support a mechanism to allocate a value into an even/odd register pair for inline assembly. We manually allocate these into x0/x1/x2/x3. It is not necessary to force the address into a specific register, as the encoding of CASP[A][L] can accept this in any GPR or SP. Hence, it is not necessary to manually allocate the address into x4 for the '[ptr]' operand. The assembly uses the '[v]' operand, and contemporary compilers happen to allocate '[v]' into a separate register from '[ptr]', meaning that '[ptr]' only serves to create register pressure. It is not necessary to allocate the '[oldval1]' and '[oldval2]' operands, as these are not used by the assembly. The assembly uses the '[old1]' and '[old2]' operands for both input and output. The '[oldval1]' and '[oldval2]' operands only serve to create register pressure. Remove the redundant operands. This saves on register pressure, as demonstrated by the test case below. There's still some unfortunate register shuffling due to the manual allocation of 'old' and 'new', but this should be less prominent within a larger function. Test case: | u128 outline_cmpxchg128(u128 *p, u128 old, u128 new) | { | return cmpxchg128(p, old, new); | } Generated code before this patch: | : | mov x6, x0 | mov x1, x3 | mov x0, x2 | b 1f | mov x2, x4 | mov x3, x5 | mov x4, x6 | mov x5, x0 | mov x7, x1 | caspal x0, x1, x2, x3, [x6] | ret | 1: prfm pstl1strm, [x6] | 2: ldxp x8, x7, [x6] | cmp x8, x0 | ccmp x7, x3, #0x0, eq | b.ne 3f | stlxp w2, x4, x5, [x6] | cbnz w2, 2b | dmb ish | 3: mov x0, x8 | mov x1, x7 | ret Generated code after this patch: | : | mov x6, x0 | mov x0, x2 | b 1f | mov x1, x3 | mov x2, x4 | mov x3, x5 | caspal x0, x1, x2, x3, [x6] | ret | 1: prfm pstl1strm, [x6] | 2: ldxp x8, x7, [x6] | cmp x8, x0 | ccmp x7, x3, #0x0, eq | 3f | stlxp w2, x4, x5, [x6] | cbnz w2, 2b | dmb ish | 3: mov x0, x8 | mov x1, x7 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Reviewed-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/atomic_lse.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arch/arm64/include/asm/atomic_lse.h b/arch/arm64/include/asm/atomic_lse.h index afad1849c4cf5b..d588af05653319 100644 --- a/arch/arm64/include/asm/atomic_lse.h +++ b/arch/arm64/include/asm/atomic_lse.h @@ -291,15 +291,13 @@ __lse__cmpxchg128##name(volatile u128 *ptr, u128 old, u128 new) \ register unsigned long x1 asm ("x1") = o.high; \ register unsigned long x2 asm ("x2") = n.low; \ register unsigned long x3 asm ("x3") = n.high; \ - register unsigned long x4 asm ("x4") = (unsigned long)ptr; \ \ asm volatile( \ __LSE_PREAMBLE \ " casp" #mb "\t%[old1], %[old2], %[new1], %[new2], %[v]\n"\ : [old1] "+&r" (x0), [old2] "+&r" (x1), \ [v] "+Q" (*(u128 *)ptr) \ - : [new1] "r" (x2), [new2] "r" (x3), [ptr] "r" (x4), \ - [oldval1] "r" (o.low), [oldval2] "r" (o.high) \ + : [new1] "r" (x2), [new2] "r" (x3) \ : cl); \ \ r.low = x0; r.high = x1; \ From 1e2ef9f221e142ed19d874a71b2cae7fabbaf138 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Thu, 24 Sep 2026 18:51:23 +0800 Subject: [PATCH 0816/2286] ext2: drain in-flight DIO before buffered write fallback An asynchronous direct write can remain in flight after the inode lock is released. If another direct write falls back to buffered I/O while the first write is still pending, generic_perform_write() can dirty pages before the first write completes its post-I/O page cache invalidation. The invalidation then finds dirty pages, reports a page cache invalidation failure, and records -EIO in the mapping error sequence. A later fsync() therefore returns -EIO. Commit 15cdefd0c0522f9d5e12d947fa04f4c11649b699 ("ext4: drain in-flight DIO before buffered write fallback") fixed the same race in ext4. Ext2 has an equivalent fallback after iomap_dio_rw() returns -ENOTBLK or a short write, but does not drain other in-flight DIO before dirtying the page cache. Wait for in-flight DIO before calling generic_perform_write() in the fallback path. A reproducer using concurrent AIO direct writes and buffered fallback triggered the following warning and made a subsequent fsync() return -EIO: Page cache invalidation failure on direct I/O. Possible data corruption due to collision with buffered I/O! Fixes: fb5de4358e1a ("ext2: Move direct-io to use iomap") Link: https://lore.kernel.org/r/20260629113827.4074335-3-libaokun@linux.alibaba.com Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20260924105125.577714-2-yaojiale02@163.com Signed-off-by: Jan Kara --- fs/ext2/file.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/ext2/file.c b/fs/ext2/file.c index b9020df7d89e03..67fe423c3828d8 100644 --- a/fs/ext2/file.c +++ b/fs/ext2/file.c @@ -135,6 +135,13 @@ static ssize_t ext2_dio_write_iter(struct kiocb *iocb, struct iov_iter *from) int ret2; iocb->ki_flags &= ~IOCB_DIRECT; + + /* + * Prevent concurrent direct I/O and buffered I/O to the same file + * range. Wait for in-flight DIO to finish before dirtying pages. + */ + inode_dio_wait(inode); + pos = iocb->ki_pos; status = generic_perform_write(iocb, from); if (unlikely(status < 0)) { From 1bee18253c2ea9db07fca4496b0e25ab3f5db8e1 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:26 +0100 Subject: [PATCH 0817/2286] arm64: preempt: Simplify and optimize __preempt_count_dec_and_test() In arm64's __preempt_count_dec_and_test(), the final conditional load of 'ti->preempt_count' is always executed in the common case. The conditional load leads to unfortunate code generation for preempt_enable[_notrace](), and it would be better to unconditionally load 'ti->preempt_count', as described below. On arm64, struct thread_info contains the following union: | union { | u64 preempt_count; | struct { | u32 count; | u32 need_resched; | } preempt; | }; Note: 'need_resched' is encoded so that '0' means a reschedule is needed, and '1' means a reschedule is NOT needed. The core logic of __preempt_count_dec_and_test() is: | static inline bool __preempt_count_dec_and_test(void) | { | struct thread_info *ti = current_thread_info(); | u64 pc = READ_ONCE(ti->preempt_count); | | WRITE_ONCE(ti->preempt.count, --pc); | | return !pc || !READ_ONCE(ti->preempt_count); | } The '!pc' condition can only be true when both: * The initial value of 'need_resched' was 0, meaning that a reschedule is needed. This should be rare. * The initial value of 'count' was exactly 1. This cannot be true for a nested preempt_disable() ... preempt_enable() sequence. Hence in common cases, '!pc' will be false, and it's necessary to execute the final READ_ONCE(ti->preempt_count). This results in a conditional branch in the common case, as can be seen when __preempt_count_dec_and_test() is outlined: | : | mrs x2, sp_el0 | ldr x1, [x2, #8] | mov w0, #0x1 | sub x1, x1, #0x1 | str w1, [x2, #8] | cbz x1, 1f | ldr x0, [x2, #8] | cmp x0, #0x0 | cset w0, eq // eq = none | 1: ret It would be better to avoid the special case for 'pc == 0', and to always load 'ti->preempt_count' after decrementing 'ti->preempt.count'. For the common cases this will remove a conditional branch. For the rare cases where preemption is needed initially, this only adds a single load, whose cost should be dominated by other factors. Remove the special case for 'pc == 0', and always load the combined 'ti->preempt_count' after decrementing 'ti->preempt.count'. The removal of the conditional branch helps with code generation, as the compiler can more easily move a dependent slow path out-of-line, as demonstrated with the following compiled with GCC 15.2.0: | void outline_preempt_enable_notrace(void) | { | preempt_enable_notrace(); | } Before this patch: | : | mrs x1, sp_el0 | ldr x0, [x1, #8] | sub x0, x0, #0x1 | str w0, [x1, #8] | cbz x0, 1f | ldr x0, [x1, #8] | cbnz x0, 2f | 1: paciasp | stp x29, x30, [sp, #-16]! | mov x29, sp | bl preempt_schedule_notrace | ldp x29, x30, [sp], #16 | autiasp | ret | 2: ret After this patch: | : | mrs x0, sp_el0 | ldr w1, [x0, #8] | sub w1, w1, #0x1 | str w1, [x0, #8] | ldr x0, [x0, #8] | cbz x0, 1f | ret | 1: paciasp | stp x29, x30, [sp, #-16]! | mov x29, sp | bl preempt_schedule_notrace | ldp x29, x30, [sp], #16 | autiasp | ret Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi [catalin.marinas@arm.com: fixed typo in comment] Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/preempt.h | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h index 9ecc2766a9f218..811479fe21bfb7 100644 --- a/arch/arm64/include/asm/preempt.h +++ b/arch/arm64/include/asm/preempt.h @@ -75,30 +75,23 @@ static inline int __preempt_count_sub_return(int val) return pc; } -static inline bool __preempt_count_dec_and_test(void) -{ - struct thread_info *ti = current_thread_info(); - u64 pc = READ_ONCE(ti->preempt_count); - - /* Update only the count field, leaving need_resched unchanged */ - WRITE_ONCE(ti->preempt.count, --pc); - - /* - * If we wrote back all zeroes, then we're preemptible and in - * need of a reschedule. Otherwise, we need to reload the - * preempt_count in case the need_resched flag was cleared by an - * interrupt occurring between the non-atomic READ_ONCE/WRITE_ONCE - * pair. - */ - return !pc || !READ_ONCE(ti->preempt_count); -} - static inline bool should_resched(int preempt_offset) { u64 pc = READ_ONCE(current_thread_info()->preempt_count); return pc == preempt_offset; } +static inline bool __preempt_count_dec_and_test(void) +{ + /* + * We must load the combined 'preempt_count' after decrementing + * 'preempt.count' as an interrupt could modify 'need_resched' before + * __preempt_count_sub() writes back to 'preempt.count'. + */ + __preempt_count_sub(1); + return should_resched(0); +} + #ifdef CONFIG_PREEMPTION void preempt_schedule(void); From 6254b223153d65c6a59fd1dd0e4c370a52374452 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:27 +0100 Subject: [PATCH 0818/2286] arm64: preempt: Treat should_resched() as unlikely The arm64 implementation of should_resched() doesn't treat the need to resched as unlikely, while all other implementations do, e.g. * asm-generic since: bdb43806589096ac ("sched: Extract the basic add/sub preempt_count modifiers") * x86 since: c2daa3bed53a8117 ("sched, x86: Provide a per-cpu preempt_count implementation") * s390 since: c360192bf4a8dc72 ("s390/preempt: move preempt_count to the lowcore") Do the same for arm64. Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/preempt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/preempt.h b/arch/arm64/include/asm/preempt.h index 811479fe21bfb7..3c85cacc1d1963 100644 --- a/arch/arm64/include/asm/preempt.h +++ b/arch/arm64/include/asm/preempt.h @@ -78,7 +78,7 @@ static inline int __preempt_count_sub_return(int val) static inline bool should_resched(int preempt_offset) { u64 pc = READ_ONCE(current_thread_info()->preempt_count); - return pc == preempt_offset; + return unlikely(pc == preempt_offset); } static inline bool __preempt_count_dec_and_test(void) From 76e55bbec15c78efb519165f637695d406bc227f Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:28 +0100 Subject: [PATCH 0819/2286] arm64: ptrace: Always inline pt_regs_{read,write}_reg() In subsequent patches we'll want to use pt_regs_read_reg() and pt_regs_write_reg() in noinstr code. To ensure noinstr safety, both functions must be inlined into their caller. As both functions are marked as 'inline' rather than '__always_inline', that's not strictly guaranteed, and the compiler is permitted to generate out-of-line copies. Mark both functions as '__always_inline' to ensure this. There should be no functional change as a result of this patch. Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/ptrace.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h index f7dc5fb9427d63..f635c3453a8582 100644 --- a/arch/arm64/include/asm/ptrace.h +++ b/arch/arm64/include/asm/ptrace.h @@ -266,7 +266,8 @@ static inline u64 regs_get_register(struct pt_regs *regs, unsigned int offset) * Read a register given an architectural register index r. * This handles the common case where 31 means XZR, not SP. */ -static inline unsigned long pt_regs_read_reg(const struct pt_regs *regs, int r) +static __always_inline unsigned long +pt_regs_read_reg(const struct pt_regs *regs, int r) { return (r == 31) ? 0 : regs->regs[r]; } @@ -275,8 +276,8 @@ static inline unsigned long pt_regs_read_reg(const struct pt_regs *regs, int r) * Write a register given an architectural register index r. * This handles the common case where 31 means XZR, not SP. */ -static inline void pt_regs_write_reg(struct pt_regs *regs, int r, - unsigned long val) +static __always_inline void +pt_regs_write_reg(struct pt_regs *regs, int r, unsigned long val) { if (r != 31) regs->regs[r] = val; From b7f3aa93f4070e622ecf3cf184d0c639cce6532d Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:29 +0100 Subject: [PATCH 0820/2286] arm64: percpu: Factor out percpu offset asm Depending on whether the kernel runs at EL1 or EL2, the percpu offset is stored in either TPIDR_EL1 or TPIDR_EL2, and __kern_my_cpu_offset() uses an ALTERNATIVE() sequence to read the relevant TPIDR_ELx. In subsequent patches we'll need to read the percpu offset in other assembly sequences. Factor the ALTERNATIVE sequence out of __kern_my_cpu_offset() into a new __KERN_ASM_CPU_OFFSET() macro so that we can share the code sequence. There should be no functional change as a result of this patch. Signed-off-by: Mark Rutland Reviewed-by: Jinjie Ruan Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 8cf4068ce1b562..746d2a56f48bb2 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -29,6 +29,11 @@ static inline unsigned long __hyp_my_cpu_offset(void) return read_sysreg(tpidr_el2); } +#define __KERN_ASM_CPU_OFFSET(dst) \ + ALTERNATIVE("mrs " dst ", tpidr_el1", \ + "mrs " dst ", tpidr_el2", \ + ARM64_HAS_VIRT_HOST_EXTN) + static inline unsigned long __kern_my_cpu_offset(void) { unsigned long off; @@ -37,11 +42,11 @@ static inline unsigned long __kern_my_cpu_offset(void) * We want to allow caching the value, so avoid using volatile and * instead use a fake stack read to hazard against barrier(). */ - asm(ALTERNATIVE("mrs %0, tpidr_el1", - "mrs %0, tpidr_el2", - ARM64_HAS_VIRT_HOST_EXTN) - : "=r" (off) : - "Q" (*(const unsigned long *)current_stack_pointer)); + asm( + __KERN_ASM_CPU_OFFSET("%0") + : "=r" (off) + : "Q" (*(const unsigned long *)current_stack_pointer) + ); return off; } From 47c62b09f03b06e6a991c0efdd7482a337528fa7 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:30 +0100 Subject: [PATCH 0821/2286] arm64: gpr-num: Add wxN aliases for wN registers Some A64 instructions require a W register name for specific register arguments, and will not accept an X register name. For example: STRH , [, (|){, {}}] This is painful when writing macros for inline assembly, especially where a single register is used as both an X register and a W register. We solved this for plain assembly by adding a wxN alias for each xN register in commit: 4c4dcd3541f83d21 ("arm64: assembler: introduce wxN aliases for wN registers") Add the same facility for inline assembly, with a new __DEFINE_ASM_GPR_ALIASES macro. As with the other macros in gpr-num.h, different escaping is necessary for inline vs non-inline assembly, so we can't share a single definition that we stringify(). Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/gpr-num.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/arch/arm64/include/asm/gpr-num.h b/arch/arm64/include/asm/gpr-num.h index a114e4f8209b09..240cd25d55c5a3 100644 --- a/arch/arm64/include/asm/gpr-num.h +++ b/arch/arm64/include/asm/gpr-num.h @@ -21,6 +21,11 @@ " .equ .L__gpr_num_xzr, 31\n" \ " .equ .L__gpr_num_wzr, 31\n" +#define __DEFINE_ASM_GPR_ALIASES \ +" .irp n,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30\n" \ +" wx\\n .req w\\n\n" \ +" .endr\n" + #endif /* __ASSEMBLER__ */ #endif /* __ASM_GPR_NUM_H */ From 05c44981d9ecca52681e93c8368b910bde61982d Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:31 +0100 Subject: [PATCH 0822/2286] arm64: gpr-num: add __GPR_NUM() helper The gpr-num infrastructure requires users to concatenate the register name with a long prefix in order to get a symbol that evaluates to a compile-time constant. Doing this manually is error-prone and painful to read. Add a new __GPR_NUM() helper which wraps the concatenation. For the moment I've left existing open-coded concatenation as-is. In particular, the DEFINE_MRS_S() and DEFINE_MSR_S() macros have awkward stringification requirements and would require invasive changes that outweigh the benefit. I have plans to rework those in the near future to remove the need to use gpr-num values. Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/gpr-num.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/include/asm/gpr-num.h b/arch/arm64/include/asm/gpr-num.h index 240cd25d55c5a3..30fb8a67b19f00 100644 --- a/arch/arm64/include/asm/gpr-num.h +++ b/arch/arm64/include/asm/gpr-num.h @@ -11,6 +11,8 @@ .equ .L__gpr_num_xzr, 31 .equ .L__gpr_num_wzr, 31 +#define __GPR_NUM(gpr) (.L__gpr_num_##gpr) + #else /* __ASSEMBLER__ */ #define __DEFINE_ASM_GPR_NUMS \ @@ -26,6 +28,8 @@ " wx\\n .req w\\n\n" \ " .endr\n" +#define __GPR_NUM(gpr) "(.L__gpr_num_" gpr ")" + #endif /* __ASSEMBLER__ */ #endif /* __ASM_GPR_NUM_H */ From 1e0a5cc90aabeae668c859674bad7cd4dd9dc9ec Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:32 +0100 Subject: [PATCH 0823/2286] arm64: entry: sdei: Restore all clobberable GPRs Currently __sdei_asm_handler() avoids restoring x20 to x27, as these are not be clobbered by existing code. This is correct today, but it makes it awkward to rework __sdei_asm_handler(). Given the rarity of SDEI events, restoring x20 to x27 should not have a measurable impact. Restore x20 to x27 when returning from an SDEI event handler. This will free up those GPRs for usage in __sdei_asm_handler() prior to return. Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/kernel/entry.S | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S index f63049ac32dc6c..a6ec5c5a2d29c6 100644 --- a/arch/arm64/kernel/entry.S +++ b/arch/arm64/kernel/entry.S @@ -1044,10 +1044,14 @@ SYM_CODE_START(__sdei_asm_handler) bl __sdei_handler msr sp_el0, x28 - /* restore regs >x17 that we clobbered */ + /* restore regs >x17 that firmware won't restore */ mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline - ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14] ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9] + ldp x20, x21, [x4, #SDEI_EVENT_INTREGS + 16 * 10] + ldp x22, x23, [x4, #SDEI_EVENT_INTREGS + 16 * 11] + ldp x24, x25, [x4, #SDEI_EVENT_INTREGS + 16 * 12] + ldp x26, x27, [x4, #SDEI_EVENT_INTREGS + 16 * 13] + ldp x28, x29, [x4, #SDEI_EVENT_INTREGS + 16 * 14] ldp lr, x1, [x4, #SDEI_EVENT_INTREGS + S_LR] mov sp, x1 From fb532fdb443055dce5c31e999e064711179151fb Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:33 +0100 Subject: [PATCH 0824/2286] arm64: entry: sdei: Make 'tsk' available For regular entry/exit sequences, we use 'tsk' (x28) to hold the current task pointer, allowing this to be used by various assembly macros. The SDEI entry/exit sequence uses x28 to hold the value of the interrupted sp_el0, and doesn't retain the current task pointer in a register. These differences makes it awkward to share assembly macros across regular entry/exit and SDEI entry/exit, and risk surprises. Align the SDEI entry/exit sequence with regular entry/exit, keeping the current task pointer in 'tsk', and preserving the interrupted sp_el0 in another (callee-saved) register. I've used x20 as x19 was already in used for the relevant sdei_registered_event, and these are the lowest-numbered registers above x18 (which we must preserve for shadow call stack). Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/kernel/entry.S | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S index a6ec5c5a2d29c6..0902c1bd9dd3dc 100644 --- a/arch/arm64/kernel/entry.S +++ b/arch/arm64/kernel/entry.S @@ -1023,11 +1023,12 @@ SYM_CODE_START(__sdei_asm_handler) /* * We may have interrupted userspace, or a guest, or exit-from or - * return-to either of these. We can't trust sp_el0, restore it. + * return-to either of these. Preserve the interrupted sp_el0, and + * initialize sp_el0 to the current task. */ - mrs x28, sp_el0 - ldr_this_cpu dst=x0, sym=__entry_task, tmp=x1 - msr sp_el0, x0 + mrs x20, sp_el0 + ldr_this_cpu dst=tsk, sym=__entry_task, tmp=x1 + msr sp_el0, tsk /* If we interrupted the kernel point to the previous stack/frame. */ and x0, x3, #0xc @@ -1043,7 +1044,7 @@ SYM_CODE_START(__sdei_asm_handler) mov x1, x19 bl __sdei_handler - msr sp_el0, x28 + msr sp_el0, x20 /* restore regs >x17 that firmware won't restore */ mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline ldp x18, x19, [x4, #SDEI_EVENT_INTREGS + 16 * 9] From 95d2c5f6663bb416f9cb94a920d840dadc958215 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:34 +0100 Subject: [PATCH 0825/2286] arm64: percpu: Add infrastructure for preemptible this_cpu_*() ops Currently arm64's this_cpu_*() ops transiently disable preemption in order to guarantee that the address generation and memory access(es) occur on the same CPU. Transiently disabling preemption can be expensive. When re-enabling preemption it is necessary to make a conditional function call to preempt_schedule[_notrace]() in order to handle the rare case that the task needs to be rescheduled. The potential function call has a number of negative effects on code generation (e.g. due to the need to create a stack frame and spill registers), and the conditionality can result in poor code generation and/or poor branch prediction. This patch adds infrastructure for a scheme where this_cpu_*() ops do not need to transiently disable preemption, avoiding the negative impacts described above. Individual operations will be converted in subsequent patches. Each operation registers a critical section during which the exception return code will adjust the offset and addresses if preemption occurs mid-sequence. The critical section is registered/unregistered with a small prologue and epilogue which encodes three distinct GPRs (, , ) into a new thread_info::pcpu_gprs field: // Prologue. Enable fixups for and . mrs , sp_el0 mov , #__VAL_PCPU_GPRS(, , ) strh , [, #TSK_TI_PCPU_GPRS] // Generate cpu-specific address mrs , TPIDR_ELx add , , // Perform access sequence ldr , [] // Epilogue. Disable fixups strh wzr, [, #TSK_TI_PCPU_GPRS] If an exception is taken from within the critical section, the exception return code will adjust to be the current CPU's offset, and will adjust to be ( + ). Distinct registers are used for , , and , so that the fixup can be applied safely at any point during the critical section. To ensure that this_cpu_*() operations within exception handlers work correctly and do not corrupt state, thread_info::pcpu_gprs is saved into a new pt_regs::pcpu_gprs field upon exception entry, and restored upon exception return. Looking at a simple this_cpu_operation: | void outline_this_cpu_add_u64(u64 __percpu *p, u64 v) | { | this_cpu_add(*p, v); | } Atop v7.2-rc4, with GCC 15.2.0 and defconfig, this is compiled as: | : | paciasp | stp x29, x30, [sp, #-16]! | mrs x2, sp_el0 | mov x29, sp | ldr w3, [x2, #8] | add w3, w3, #0x1 | str w3, [x2, #8] | mrs x3, tpidr_el1 | add x0, x0, x3 | 1: ldxr x5, [x0] | add x5, x5, x1 | stxr w4, x5, [x0] | cbnz w4, 1b | ldr x0, [x2, #8] | sub x0, x0, #0x1 | str w0, [x2, #8] | cbz x0, 2f | ldr x0, [x2, #8] | cbnz x0, 3f | 2: bl preempt_schedule_notrace | 3: ldp x29, x30, [sp], #16 | autiasp | ret With the scheme added in this patch, this can be compiled as: | : | mrs x2, sp_el0 | mov x4, #0xc80 // __VAL_PCPU_GPRS(x0, x4, x3) | strh w4, [x2, #20] | mrs x4, tpidr_el1 | add x3, x0, x4 | 1: ldxr x6, [x3] | add x6, x6, x1 | stxr w5, x6, [x3] | cbnz w5, 1b | strh wzr, [x2, #20] | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin [catalin.marinas@arm.com: fixed typos in commit log and code comments] Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 71 ++++++++++++++++++++++++++++ arch/arm64/include/asm/ptrace.h | 5 ++ arch/arm64/include/asm/thread_info.h | 1 + arch/arm64/kernel/asm-offsets.c | 2 + arch/arm64/kernel/entry-common.c | 38 +++++++++++++++ arch/arm64/kernel/entry.S | 22 +++++++++ 6 files changed, 139 insertions(+) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 746d2a56f48bb2..65204a2bbc1c8e 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -5,10 +5,13 @@ #ifndef __ASM_PERCPU_H #define __ASM_PERCPU_H +#include #include +#include #include #include +#include #include #include @@ -51,6 +54,74 @@ static inline unsigned long __kern_my_cpu_offset(void) return off; } +#define PCPU_GPR_PCP_SHIFT 0 +#define PCPU_GPR_PCP GENMASK(4, 0) +#define PCPU_GPR_OFF_SHIFT 5 +#define PCPU_GPR_OFF GENMASK(9, 5) +#define PCPU_GPR_ADDR_SHIFT 10 +#define PCPU_GPR_ADDR GENMASK(14, 10) + +#define __VAL_PCPU_GPRS(pcp, off, addr) \ + "(" \ + "(" __GPR_NUM(pcp) " << " __stringify(PCPU_GPR_PCP_SHIFT) ") | " \ + "(" __GPR_NUM(off) " << " __stringify(PCPU_GPR_OFF_SHIFT) ") | " \ + "(" __GPR_NUM(addr) " << " __stringify(PCPU_GPR_ADDR_SHIFT) ")" \ + ")" + +#define __ASSERT_PCPU_GPRS_DISTINCT(pcp, off, addr) \ + ".if (" \ + "(" __GPR_NUM(pcp) " == " __GPR_NUM(off) ") || " \ + "(" __GPR_NUM(pcp) " == " __GPR_NUM(addr) ") || " \ + "(" __GPR_NUM(off) " == " __GPR_NUM(addr) ")" \ + " )\n" \ + ".error \"PCPU GPRS overlap: {" pcp "," off "," addr "}\"\n" \ + ".endif\n" + +#define ____PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \ + "// ____PCPU_GPRS_BEGIN(" gprs ", " pcp ", " off ", " addr")\n" \ + __DEFINE_ASM_GPR_NUMS \ + __DEFINE_ASM_GPR_ALIASES \ + __ASSERT_PCPU_GPRS_DISTINCT(pcp, off, addr) \ + " mov w" off ", #" __VAL_PCPU_GPRS(pcp, off, addr) "\n" \ + " strh w" off ", " gprs "\n" \ + __KERN_ASM_CPU_OFFSET(off) "\n" + +/* + * Begin a PCPU GPR critical section which requires (and ). + * + * At the start of the critical section, and upon any (preemptible) exception + * until __PCPU_GPRS_END(): + * - will be set to the current CPU's percpu offset. + * - will be set to + . + * + * The , , and registers must be distinct GPRs. + * + * must be '¤t_thread_info()->pcpu_gprs', as a memory operand + * which can be written both at the start and end of the critical section + * (e.g. using "=Qo" constraints). + */ +#define __PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \ + ____PCPU_GPRS_BEGIN(gprs, pcp, off, addr) \ + " add " addr ", " pcp ", " off "\n" + +/* + * Begin a PCPU GPR critical section which only requires and does not + * require . + * + * This is only for operations that can use register-offset addressing, + * e.g. STR , [, ]. + * + * All other details are the same as __PCPU_GPRS_BEGIN(). + */ +#define __PCPU_GPRS_BEGIN_OFFSET(gprs, pcp, off) \ + ____PCPU_GPRS_BEGIN(gprs, pcp, off, "xzr") + +/* + * End a PCPU GPR critical section. + */ +#define __PCPU_GPRS_END(gprs) \ + " strh wzr, " gprs "\n" + #ifdef __KVM_NVHE_HYPERVISOR__ #define __my_cpu_offset __hyp_my_cpu_offset() #else diff --git a/arch/arm64/include/asm/ptrace.h b/arch/arm64/include/asm/ptrace.h index f635c3453a8582..25c70374cb7b45 100644 --- a/arch/arm64/include/asm/ptrace.h +++ b/arch/arm64/include/asm/ptrace.h @@ -167,6 +167,11 @@ struct pt_regs { u64 sdei_ttbr1; struct frame_record_meta stackframe; + + u16 pcpu_gprs; + u16 __unused1; + u32 __unused2; + u64 __unused3; }; /* For correct stack alignment, pt_regs has to be a multiple of 16 bytes. */ diff --git a/arch/arm64/include/asm/thread_info.h b/arch/arm64/include/asm/thread_info.h index 5d7fe3e153c850..6db5fa72211d12 100644 --- a/arch/arm64/include/asm/thread_info.h +++ b/arch/arm64/include/asm/thread_info.h @@ -46,6 +46,7 @@ struct thread_info { u64 mpam_partid_pmg; #endif u32 cpu; + u16 pcpu_gprs; }; #define thread_saved_pc(tsk) \ diff --git a/arch/arm64/kernel/asm-offsets.c b/arch/arm64/kernel/asm-offsets.c index 9c853ed3ceab86..9d603317eeb996 100644 --- a/arch/arm64/kernel/asm-offsets.c +++ b/arch/arm64/kernel/asm-offsets.c @@ -36,6 +36,7 @@ int main(void) DEFINE(TSK_TI_SCS_BASE, offsetof(struct task_struct, thread_info.scs_base)); DEFINE(TSK_TI_SCS_SP, offsetof(struct task_struct, thread_info.scs_sp)); #endif + DEFINE(TSK_TI_PCPU_GPRS, offsetof(struct task_struct, thread_info.pcpu_gprs)); DEFINE(TSK_STACK, offsetof(struct task_struct, stack)); #ifdef CONFIG_STACKPROTECTOR DEFINE(TSK_STACK_CANARY, offsetof(struct task_struct, stack_canary)); @@ -78,6 +79,7 @@ int main(void) DEFINE(S_PMR, offsetof(struct pt_regs, pmr)); DEFINE(S_STACKFRAME, offsetof(struct pt_regs, stackframe)); DEFINE(S_STACKFRAME_TYPE, offsetof(struct pt_regs, stackframe.type)); + DEFINE(S_PCPU_GPRS, offsetof(struct pt_regs, pcpu_gprs)); DEFINE(PT_REGS_SIZE, sizeof(struct pt_regs)); BLANK(); #ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS diff --git a/arch/arm64/kernel/entry-common.c b/arch/arm64/kernel/entry-common.c index 72c03ccea59fec..11d3f888fcd873 100644 --- a/arch/arm64/kernel/entry-common.c +++ b/arch/arm64/kernel/entry-common.c @@ -25,12 +25,49 @@ #include #include #include +#include #include #include #include #include #include +/* + * Where the context being returned to had an active percpu GPR critical + * section, ensure that the offset and address GPRs are updated to match the + * current CPU. + * + * For simplicity we always update the GPRs when a critical section is active + * and preemption was *possible*, regardless of whether preemption actually + * occurred. Where preemption did not occur, the updates are redundant but not + * harmful. + */ +static __always_inline void irqentry_exit_pcpu_adjust(struct pt_regs *regs) +{ + int reg_pcp, reg_off, reg_addr; + unsigned long pcp, off, addr; + u16 gprs = regs->pcpu_gprs; + + /* + * Zero means no active PCPU GPRs. As the PCPU GPRs must be distinct, + * a PCPU critical section cannot possibly use {x0,x0,x0}. + */ + if (likely(!gprs)) + return; + + reg_pcp = FIELD_GET(PCPU_GPR_PCP, gprs); + reg_off = FIELD_GET(PCPU_GPR_OFF, gprs); + reg_addr = FIELD_GET(PCPU_GPR_ADDR, gprs); + + pcp = pt_regs_read_reg(regs, reg_pcp); + + off = __kern_my_cpu_offset(); + pt_regs_write_reg(regs, reg_off, off); + + addr = pcp + off; + pt_regs_write_reg(regs, reg_addr, addr); +} + /* * Handle IRQ/context state management when entering from kernel mode. * Before this function is called it is not safe to call regular kernel code, @@ -56,6 +93,7 @@ static void noinstr __arm64_exit_to_kernel_mode(struct pt_regs *regs, irqentry_state_t state) { local_daif_mask(); + irqentry_exit_pcpu_adjust(regs); mte_check_tfsr_exit(); irqentry_exit_to_kernel_mode_after_preempt(regs, state); } diff --git a/arch/arm64/kernel/entry.S b/arch/arm64/kernel/entry.S index 0902c1bd9dd3dc..8d17f61d1b5b2c 100644 --- a/arch/arm64/kernel/entry.S +++ b/arch/arm64/kernel/entry.S @@ -194,6 +194,17 @@ alternative_cb_end #endif .endm + .macro pcpu_gprs_entry, tsk:req, regs:req, tmp:req + ldrh w\tmp, [\tsk, #TSK_TI_PCPU_GPRS] + strh w\tmp, [\regs, #S_PCPU_GPRS] + strh wzr, [\tsk, #TSK_TI_PCPU_GPRS] + .endm + + .macro pcpu_gprs_exit, tsk:req, regs:req, tmp:req + ldrh w\tmp, [\regs, #S_PCPU_GPRS] + strh w\tmp, [\tsk, #TSK_TI_PCPU_GPRS] + .endm + .macro kernel_entry, el, regsize = 64 .if \el == 0 alternative_insn nop, SET_PSTATE_DIT(1), ARM64_HAS_DIT @@ -277,6 +288,7 @@ alternative_else_nop_endif .else add x21, sp, #PT_REGS_SIZE get_current_task tsk + pcpu_gprs_entry tsk, sp, x0 .endif /* \el == 0 */ mrs x22, elr_el1 mrs x23, spsr_el1 @@ -333,6 +345,10 @@ alternative_else_nop_endif .endm .macro kernel_exit, el + .if \el != 0 + pcpu_gprs_exit tsk, sp, x0 + .endif + #ifdef CONFIG_ARM64_PSEUDO_NMI alternative_if_not ARM64_HAS_GIC_PRIO_MASKING b .Lskip_pmr_restore\@ @@ -1040,10 +1056,16 @@ SYM_CODE_START(__sdei_asm_handler) stp x29, x4, [sp, #-16]! mov x29, sp + add x16, x19, #SDEI_EVENT_INTREGS + pcpu_gprs_entry tsk, x16, x17 + add x0, x19, #SDEI_EVENT_INTREGS mov x1, x19 bl __sdei_handler + add x16, x19, #SDEI_EVENT_INTREGS + pcpu_gprs_exit tsk, x16, x17 + msr sp_el0, x20 /* restore regs >x17 that firmware won't restore */ mov x4, x19 // keep x4 for __sdei_asm_exit_trampoline From 6e2cec1c39b2f251c5fb5a31307f3c931a15c0b9 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:35 +0100 Subject: [PATCH 0826/2286] arm64: percpu: Implement preemptible read/write ops Use the PCPU GPR infrastructure to implement preemptible this_cpu_read() and this_cpu_write(). This change means that this_cpu_read() will always use a plain LDR, even in LTO configurations where READ_ONCE() will use LDA[P]R. Using plain LDR is preferable, given that the rationale for using LDA[P]R in READ_ONCE() was to retain address dependencies against values written by other CPUs, which isn't expected usage for this_cpu_read(). Using plain LDR will enforce fewer ordering constraints. Test case: | void outline_this_cpu_write_u64(u64 __percpu *p, u64 v) | { | this_cpu_write(*p, v); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-16]! | mrs x2, sp_el0 | mov x29, sp | ldr w3, [x2, #8] | add w3, w3, #0x1 | str w3, [x2, #8] | mrs x3, tpidr_el1 | str x1, [x0, x3] | ldr x0, [x2, #8] | sub x0, x0, #0x1 | str w0, [x2, #8] | cbz x0, 1f | ldr x0, [x2, #8] | cbnz x0, 2f | 1: bl preempt_schedule_notrace | 2: ldp x29, x30, [sp], #16 | autiasp | ret Generated code after this patch: | : | mrs x2, sp_el0 | mov w3, #0x7c60 | strh w3, [x2, #20] | mrs x3, tpidr_el1 | str x1, [x0, x3] | strh wzr, [x2, #20] | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 79 ++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 65204a2bbc1c8e..7358c4d2336eae 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -128,15 +128,42 @@ static inline unsigned long __kern_my_cpu_offset(void) #define __my_cpu_offset __kern_my_cpu_offset() #endif -#define PERCPU_RW_OPS(sz) \ -static inline unsigned long __percpu_read_##sz(void *ptr) \ -{ \ - return READ_ONCE(*(u##sz *)ptr); \ -} \ - \ -static inline void __percpu_write_##sz(void *ptr, unsigned long val) \ -{ \ - WRITE_ONCE(*(u##sz *)ptr, (u##sz)val); \ +#define PERCPU_RW_OPS(w, sfx, sz) \ +static inline unsigned long __percpu_read_##sz(void __percpu *pcp) \ +{ \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long off; \ + unsigned long val; \ + \ + asm volatile( \ + __PCPU_GPRS_BEGIN_OFFSET("%[gprs]", "%[pcp]", "%[off]") \ + " ldr" #sfx "\t%" #w "[val], [%[pcp], %[off]]\n" \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [off] "=&r" (off), \ + [val] "=&r" (val) \ + : [pcp] "r" (pcp) \ + : "memory" \ + ); \ + \ + return val; \ +} \ + \ +static inline void __percpu_write_##sz(void __percpu *pcp, unsigned long val) \ +{ \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long off; \ + \ + asm volatile( \ + __PCPU_GPRS_BEGIN_OFFSET("%[gprs]", "%[pcp]", "%[off]") \ + " str" #sfx "\t%" #w "[val], [%[pcp], %[off]]\n" \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [off] "=&r" (off) \ + : [pcp] "r" (pcp), \ + [val] "r" ((u##sz)val) \ + : "memory" \ + ); \ } #define __PERCPU_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \ @@ -196,10 +223,10 @@ __percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \ __PERCPU_RET_OP_CASE(w, , name, 32, op_llsc, op_lse) \ __PERCPU_RET_OP_CASE( , , name, 64, op_llsc, op_lse) -PERCPU_RW_OPS(8) -PERCPU_RW_OPS(16) -PERCPU_RW_OPS(32) -PERCPU_RW_OPS(64) +PERCPU_RW_OPS(w, b, 8) +PERCPU_RW_OPS(w, h, 16) +PERCPU_RW_OPS(w, , 32) +PERCPU_RW_OPS( , , 64) /* * Use value-returning atomics for CPU-local ops as they are more likely @@ -245,23 +272,33 @@ PERCPU_RET_OP(add, add, ldadd) __retval; \ }) +#define _pcp_wrap(op, pcp, ...) \ +({ \ + op(&(pcp), __VA_ARGS__); \ +}) + +#define _pcp_wrap_return(op, pcp, args...) \ +({ \ + (typeof(pcp))op(&(pcp), ##args); \ +}) + #define this_cpu_read_1(pcp) \ - _pcp_protect_return(__percpu_read_8, pcp) + _pcp_wrap_return(__percpu_read_8, pcp) #define this_cpu_read_2(pcp) \ - _pcp_protect_return(__percpu_read_16, pcp) + _pcp_wrap_return(__percpu_read_16, pcp) #define this_cpu_read_4(pcp) \ - _pcp_protect_return(__percpu_read_32, pcp) + _pcp_wrap_return(__percpu_read_32, pcp) #define this_cpu_read_8(pcp) \ - _pcp_protect_return(__percpu_read_64, pcp) + _pcp_wrap_return(__percpu_read_64, pcp) #define this_cpu_write_1(pcp, val) \ - _pcp_protect(__percpu_write_8, pcp, (unsigned long)(val)) + _pcp_wrap(__percpu_write_8, pcp, (unsigned long)(val)) #define this_cpu_write_2(pcp, val) \ - _pcp_protect(__percpu_write_16, pcp, (unsigned long)(val)) + _pcp_wrap(__percpu_write_16, pcp, (unsigned long)(val)) #define this_cpu_write_4(pcp, val) \ - _pcp_protect(__percpu_write_32, pcp, (unsigned long)(val)) + _pcp_wrap(__percpu_write_32, pcp, (unsigned long)(val)) #define this_cpu_write_8(pcp, val) \ - _pcp_protect(__percpu_write_64, pcp, (unsigned long)(val)) + _pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val)) #define this_cpu_add_1(pcp, val) \ _pcp_protect(__percpu_add_case_8, pcp, val) From c7ac8f9baaef12f46391d9157caceedbad59c423 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:36 +0100 Subject: [PATCH 0827/2286] arm64: percpu: Implement preemptible void RMW ops Use the PCPU GPR infrastructure to implement all of the RMW ops which do not return a value. Test case: | void outline_this_cpu_add_u64(u64 __percpu *p, u64 v) | { | this_cpu_add(*p, v); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-16]! | mrs x2, sp_el0 | mov x29, sp | ldr w3, [x2, #8] | add w3, w3, #0x1 | str w3, [x2, #8] | mrs x3, tpidr_el1 | add x0, x0, x3 | 1: ldxr x5, [x0] | add x5, x5, x1 | stxr w4, x5, [x0] | cbnz w4, 1b | ldr x0, [x2, #8] | sub x0, x0, #0x1 | str w0, [x2, #8] | cbz x0, 2f | ldr x0, [x2, #8] | cbnz x0, 3f | 2: bl preempt_schedule_notrace | 3: ldp x29, x30, [sp], #16 | autiasp | ret Generated code after this patch: | : | mrs x2, sp_el0 | mov w4, #0xc80 | strh w4, [x2, #20] | mrs x4, tpidr_el1 | add x3, x0, x4 | 1: ldxr x6, [x3] | add x6, x6, x1 | stxr w5, x6, [x3] | cbnz w5, 1b | strh wzr, [x2, #20] | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 52 ++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 7358c4d2336eae..4d1a94b70ac2b3 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -168,23 +168,35 @@ static inline void __percpu_write_##sz(void __percpu *pcp, unsigned long val) \ #define __PERCPU_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \ static inline void \ -__percpu_##name##_case_##sz(void *ptr, unsigned long val) \ +__percpu_##name##_case_##sz(void __percpu *pcp, unsigned long val) \ { \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long addr; \ + unsigned long off; \ unsigned int loop; \ u##sz tmp; \ \ - asm volatile (ARM64_LSE_ATOMIC_INSN( \ + asm volatile ( \ + __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \ + ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ - "1: ldxr" #sfx "\t%" #w "[tmp], %[ptr]\n" \ + "1: ldxr" #sfx "\t%" #w "[tmp], [%[addr]]\n" \ #op_llsc "\t%" #w "[tmp], %" #w "[tmp], %" #w "[val]\n" \ - " stxr" #sfx "\t%w[loop], %" #w "[tmp], %[ptr]\n" \ + " stxr" #sfx "\t%w[loop], %" #w "[tmp], [%[addr]]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[tmp], [%[addr]]\n"\ __nops(3)) \ - : [loop] "=&r" (loop), [tmp] "=&r" (tmp), \ - [ptr] "+Q"(*(u##sz *)ptr) \ - : [val] "r" ((u##sz)(val))); \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [addr] "=&r" (addr), \ + [off] "=&r" (off), \ + [loop] "=&r" (loop), \ + [tmp] "=&r" (tmp) \ + : [pcp] "r" (pcp), \ + [val] "r" ((u##sz)(val)) \ + : "memory" \ + ); \ } #define __PERCPU_RET_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \ @@ -301,13 +313,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val)) #define this_cpu_add_1(pcp, val) \ - _pcp_protect(__percpu_add_case_8, pcp, val) + _pcp_wrap(__percpu_add_case_8, pcp, val) #define this_cpu_add_2(pcp, val) \ - _pcp_protect(__percpu_add_case_16, pcp, val) + _pcp_wrap(__percpu_add_case_16, pcp, val) #define this_cpu_add_4(pcp, val) \ - _pcp_protect(__percpu_add_case_32, pcp, val) + _pcp_wrap(__percpu_add_case_32, pcp, val) #define this_cpu_add_8(pcp, val) \ - _pcp_protect(__percpu_add_case_64, pcp, val) + _pcp_wrap(__percpu_add_case_64, pcp, val) #define this_cpu_add_return_1(pcp, val) \ _pcp_protect_return(__percpu_add_return_case_8, pcp, val) @@ -319,22 +331,22 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_protect_return(__percpu_add_return_case_64, pcp, val) #define this_cpu_and_1(pcp, val) \ - _pcp_protect(__percpu_andnot_case_8, pcp, ~(u8)(val)) + _pcp_wrap(__percpu_andnot_case_8, pcp, ~(u8)(val)) #define this_cpu_and_2(pcp, val) \ - _pcp_protect(__percpu_andnot_case_16, pcp, ~(u16)(val)) + _pcp_wrap(__percpu_andnot_case_16, pcp, ~(u16)(val)) #define this_cpu_and_4(pcp, val) \ - _pcp_protect(__percpu_andnot_case_32, pcp, ~(u32)(val)) + _pcp_wrap(__percpu_andnot_case_32, pcp, ~(u32)(val)) #define this_cpu_and_8(pcp, val) \ - _pcp_protect(__percpu_andnot_case_64, pcp, ~(u64)(val)) + _pcp_wrap(__percpu_andnot_case_64, pcp, ~(u64)(val)) #define this_cpu_or_1(pcp, val) \ - _pcp_protect(__percpu_or_case_8, pcp, val) + _pcp_wrap(__percpu_or_case_8, pcp, val) #define this_cpu_or_2(pcp, val) \ - _pcp_protect(__percpu_or_case_16, pcp, val) + _pcp_wrap(__percpu_or_case_16, pcp, val) #define this_cpu_or_4(pcp, val) \ - _pcp_protect(__percpu_or_case_32, pcp, val) + _pcp_wrap(__percpu_or_case_32, pcp, val) #define this_cpu_or_8(pcp, val) \ - _pcp_protect(__percpu_or_case_64, pcp, val) + _pcp_wrap(__percpu_or_case_64, pcp, val) #define this_cpu_xchg_1(pcp, val) \ _pcp_protect_return(xchg_relaxed, pcp, val) From 6fe3da372d04e6294d8875b9b1da9b1f6887c320 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:37 +0100 Subject: [PATCH 0828/2286] arm64: percpu: Implement preemptible return RMW ops Use the PCPU GPR infrastructure to implement all of the RMW ops which return a value. Test case: | u64 outline_this_cpu_add_return_u64(u64 __percpu *p, u64 v) | { | return this_cpu_add_return(*p, v); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-32]! | mrs x3, sp_el0 | mov x29, sp | ldr w2, [x3, #8] | add w2, w2, #0x1 | str w2, [x3, #8] | mov x2, x0 | mrs x4, tpidr_el1 | add x2, x2, x4 | 1: ldxr x0, [x2] | add x0, x0, x1 | stxr w5, x0, [x2] | cbnz w5, 1b | ldr x1, [x3, #8] | sub x1, x1, #0x1 | str w1, [x3, #8] | cbz x1, 2f | ldr x1, [x3, #8] | cbnz x1, 3f | 2: str x0, [sp, #24] | bl 0 | ldr x0, [sp, #24] | 3: ldp x29, x30, [sp], #32 | autiasp | ret Generated code after this patch: | : | mrs x3, sp_el0 | mov w5, #0x10a0 | strh w5, [x3, #20] | mrs x5, tpidr_el1 | add x4, x0, x5 | 1: ldxr x2, [x4] | add x2, x2, x1 | stxr w6, x2, [x4] | cbnz w6, 1b | strh wzr, [x3, #20] | mov x0, x2 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 36 ++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 4d1a94b70ac2b3..bed49ac7484a45 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -201,24 +201,36 @@ __percpu_##name##_case_##sz(void __percpu *pcp, unsigned long val) \ #define __PERCPU_RET_OP_CASE(w, sfx, name, sz, op_llsc, op_lse) \ static inline u##sz \ -__percpu_##name##_return_case_##sz(void *ptr, unsigned long val) \ +__percpu_##name##_return_case_##sz(void __percpu *pcp, unsigned long val) \ { \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long addr; \ + unsigned long off; \ unsigned int loop; \ u##sz ret; \ \ - asm volatile (ARM64_LSE_ATOMIC_INSN( \ + asm volatile ( \ + __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \ + ARM64_LSE_ATOMIC_INSN( \ /* LL/SC */ \ - "1: ldxr" #sfx "\t%" #w "[ret], %[ptr]\n" \ + "1: ldxr" #sfx "\t%" #w "[ret], [%[addr]]\n" \ #op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \ - " stxr" #sfx "\t%w[loop], %" #w "[ret], %[ptr]\n" \ + " stxr" #sfx "\t%w[loop], %" #w "[ret], [%[addr]]\n" \ " cbnz %w[loop], 1b", \ /* LSE atomics */ \ - #op_lse #sfx "\t%" #w "[val], %" #w "[ret], %[ptr]\n" \ + #op_lse #sfx "\t%" #w "[val], %" #w "[ret], [%[addr]]\n"\ #op_llsc "\t%" #w "[ret], %" #w "[ret], %" #w "[val]\n" \ __nops(2)) \ - : [loop] "=&r" (loop), [ret] "=&r" (ret), \ - [ptr] "+Q"(*(u##sz *)ptr) \ - : [val] "r" ((u##sz)(val))); \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [addr] "=&r" (addr), \ + [off] "=&r" (off), \ + [loop] "=&r" (loop), \ + [ret] "=&r" (ret) \ + : [pcp] "r" (pcp), \ + [val] "r" ((u##sz)(val)) \ + : "memory" \ + ); \ \ return ret; \ } @@ -322,13 +334,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_wrap(__percpu_add_case_64, pcp, val) #define this_cpu_add_return_1(pcp, val) \ - _pcp_protect_return(__percpu_add_return_case_8, pcp, val) + _pcp_wrap_return(__percpu_add_return_case_8, pcp, val) #define this_cpu_add_return_2(pcp, val) \ - _pcp_protect_return(__percpu_add_return_case_16, pcp, val) + _pcp_wrap_return(__percpu_add_return_case_16, pcp, val) #define this_cpu_add_return_4(pcp, val) \ - _pcp_protect_return(__percpu_add_return_case_32, pcp, val) + _pcp_wrap_return(__percpu_add_return_case_32, pcp, val) #define this_cpu_add_return_8(pcp, val) \ - _pcp_protect_return(__percpu_add_return_case_64, pcp, val) + _pcp_wrap_return(__percpu_add_return_case_64, pcp, val) #define this_cpu_and_1(pcp, val) \ _pcp_wrap(__percpu_andnot_case_8, pcp, ~(u8)(val)) From 5e8ef5fb7720f308d5e693e032448c746407b56f Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:38 +0100 Subject: [PATCH 0829/2286] arm64: percpu: Implement preemptible XCHG ops Use the PCPU GPR infrastructure to implement all of the xchg ops Test case: | u64 outline_xchg_name(u64 *p, u64 v) | { | return xchg(p, v); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-32]! | mrs x3, sp_el0 | mov x29, sp | ldr w2, [x3, #8] | add w2, w2, #0x1 | str w2, [x3, #8] | mov x2, x0 | mrs x4, tpidr_el1 | add x2, x2, x4 | prfm pstl1strm, [x2] | 1: ldxr x0, [x2] | stxr w5, x1, [x2] | cbnz w5, 1b | ldr x1, [x3, #8] | sub x1, x1, #0x1 | str w1, [x3, #8] | cbz x1, 2f | ldr x1, [x3, #8] | cbnz x1, 3f | 2: str x0, [sp, #24] | bl preempt_schedule_notrace | ldr x0, [sp, #24] | 3: ldp x29, x30, [sp], #32 | autiasp | ret Generated code after this patch: | : | mrs x3, sp_el0 | mov w5, #0x10a0 | strh w5, [x3, #20] | mrs x5, tpidr_el1 | add x4, x0, x5 | prfm pstl1strm, [x4] | 1: ldxr x2, [x4] | stxr w6, x1, [x4] | cbnz w6, 1b | strh wzr, [x3, #20] | mov x0, x2 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 57 ++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index bed49ac7484a45..af4e20a9e3c1b6 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -269,6 +269,50 @@ PERCPU_RET_OP(add, add, ldadd) #undef PERCPU_OP #undef PERCPU_RET_OP +#define PERCPU_XCHG_OP(w, sfx, sz) \ +static inline unsigned long \ +__percpu_xchg_case_##sz(void __percpu *pcp, u##sz val) \ +{ \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long addr; \ + unsigned long off; \ + unsigned int loop; \ + unsigned long ret; \ + \ + asm volatile ( \ + __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \ + ARM64_LSE_ATOMIC_INSN( \ + /* LL/SC */ \ + " prfm pstl1strm, [%[addr]]\n" \ + "1: ldxr" #sfx "\t%" #w "[ret], [%[addr]]\n" \ + " stxr" #sfx "\t%w[loop], %" #w "[val], [%[addr]]\n" \ + " cbnz %w[loop], 1b\n" \ + , \ + /* LSE atomics */ \ + " swp" #sfx "\t%" #w "[val], %" #w "[ret], [%[addr]]\n" \ + __nops(3) \ + ) \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [addr] "=&r" (addr), \ + [off] "=&r" (off), \ + [loop] "=&r" (loop), \ + [ret] "=&r" (ret) \ + : [pcp] "r" (pcp), \ + [val] "r" (val) \ + : "memory" \ + ); \ + \ + return ret; \ +} + +PERCPU_XCHG_OP(w, b, 8) +PERCPU_XCHG_OP(w, h, 16) +PERCPU_XCHG_OP(w, , 32) +PERCPU_XCHG_OP(x, , 64) + +#undef PERCPU_XCHG_OP + /* * It would be nice to avoid the conditional call into the scheduler when * re-enabling preemption for preemptible kernels, but doing that in a way @@ -306,6 +350,11 @@ PERCPU_RET_OP(add, add, ldadd) (typeof(pcp))op(&(pcp), ##args); \ }) +#define _pcp_wrap_xchg(op, pcp, val) \ +({ \ + (typeof(pcp))op(&(pcp), (unsigned long)(val)); \ +}) + #define this_cpu_read_1(pcp) \ _pcp_wrap_return(__percpu_read_8, pcp) #define this_cpu_read_2(pcp) \ @@ -361,13 +410,13 @@ PERCPU_RET_OP(add, add, ldadd) _pcp_wrap(__percpu_or_case_64, pcp, val) #define this_cpu_xchg_1(pcp, val) \ - _pcp_protect_return(xchg_relaxed, pcp, val) + _pcp_wrap_xchg(__percpu_xchg_case_8, pcp, val) #define this_cpu_xchg_2(pcp, val) \ - _pcp_protect_return(xchg_relaxed, pcp, val) + _pcp_wrap_xchg(__percpu_xchg_case_16, pcp, val) #define this_cpu_xchg_4(pcp, val) \ - _pcp_protect_return(xchg_relaxed, pcp, val) + _pcp_wrap_xchg(__percpu_xchg_case_32, pcp, val) #define this_cpu_xchg_8(pcp, val) \ - _pcp_protect_return(xchg_relaxed, pcp, val) + _pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val) #define this_cpu_cmpxchg_1(pcp, o, n) \ _pcp_protect_return(cmpxchg_relaxed, pcp, o, n) From 39d50a10e3181fa4f2f40d2aa8669fb22504de3d Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:39 +0100 Subject: [PATCH 0830/2286] arm64: percpu: Implement preemptible CMPXCHG ops Use the PCPU GPR infrastructure to implement all of the {8,16,32,64}-bit cmpxchg ops. Note that before this patch, the LL/SC and LSE implementation was chosen with an alternative branch. After this patch the implementations are patched inline, matching the style of the other percpu ops. Test case: | u64 outline_this_cpu_cmpxchg_u64(u64 __percpu *p, u64 o, u64 n) | { | return this_cpu_cmpxchg(*p, o, n); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-32]! | mrs x4, sp_el0 | mov x29, sp | ldr w3, [x4, #8] | add w3, w3, #0x1 | str w3, [x4, #8] | mrs x3, tpidr_el1 | add x3, x0, x3 | b 4f // alternative branch | cas x1, x2, [x3] | mov x0, x1 | 1: mrs x2, sp_el0 | ldr x1, [x2, #8] | sub x1, x1, #0x1 | str w1, [x2, #8] | cbz x1, 2f | ldr x1, [x2, #8] | cbnz x1, 3f | 2: str x0, [sp, #24] | bl preempt_schedule_notrace | ldr x0, [sp, #24] | 3: ldp x29, x30, [sp], #32 | autiasp | ret | 4: prfm pstl1strm, [x3] | 5: ldxr x0, [x3] | eor x4, x0, x1 | cbnz x4, 6f | stxr w4, x2, [x3] | cbnz w4, 5b | 6: b 1b Generated code after this patch: | : | mrs x4, sp_el0 | mov w6, #0x14c0 | strh w6, [x4, #20] | mrs x6, tpidr_el1 | add x5, x0, x6 | prfm pstl1strm, [x5] | 1: ldxr x3, [x5] | eor x7, x3, x1 | cbnz x7, 2f | stxr w7, x2, [x5] | cbnz w7, 1b | 2: strh wzr, [x4, #20] | mov x0, x3 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 69 +++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index af4e20a9e3c1b6..b877f8ef545585 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -306,12 +306,67 @@ __percpu_xchg_case_##sz(void __percpu *pcp, u##sz val) \ return ret; \ } +#define PERCPU_CMPXCHG_OP(w, sfx, sz) \ +static inline unsigned long \ +__percpu_cmpxchg_case_##sz(void __percpu *pcp, \ + u##sz old, \ + u##sz new) \ +{ \ + /* \ + * Sub-word sizes require zero extension so that EOR+CBNZ don't \ + * consume non-zero upper bits of the register containing "old".\ + */ \ + xwreg_t(w) cmpval = xwreg_zero_extend(old, w, sz); \ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; \ + unsigned long addr; \ + unsigned long off; \ + unsigned long tmp; \ + unsigned long oldval; \ + \ + asm volatile ( \ + __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") \ + ARM64_LSE_ATOMIC_INSN( \ + /* LL/SC */ \ + " prfm pstl1strm, [%[addr]]\n" \ + "1: ldxr" #sfx "\t%" #w "[oldval], [%[addr]]\n" \ + " eor %" #w "[tmp], %" #w "[oldval], %" #w "[old]\n" \ + " cbnz %" #w "[tmp], 2f\n" \ + " stxr" #sfx "\t%w[tmp], %" #w "[new], [%[addr]]\n" \ + " cbnz %w[tmp], 1b\n" \ + "2:\n" \ + , \ + /* LSE atomics */ \ + " mov %" #w "[oldval], %" #w "[old]\n" \ + " cas" #sfx "\t%" #w "[oldval], %" #w "[new], [%[addr]]\n"\ + __nops(4) \ + ) \ + __PCPU_GPRS_END("%[gprs]") \ + : [gprs] "=Qo" (*gprs), \ + [addr] "=&r" (addr), \ + [off] "=&r" (off), \ + [tmp] "=&r" (tmp), \ + [oldval] "=&r" (oldval) \ + : [pcp] "r" (pcp), \ + [old] "r" (cmpval), \ + [new] "r" (new) \ + : "memory" \ + ); \ + \ + return oldval; \ +} + PERCPU_XCHG_OP(w, b, 8) PERCPU_XCHG_OP(w, h, 16) PERCPU_XCHG_OP(w, , 32) PERCPU_XCHG_OP(x, , 64) +PERCPU_CMPXCHG_OP(w, b, 8) +PERCPU_CMPXCHG_OP(w, h, 16) +PERCPU_CMPXCHG_OP(w, , 32) +PERCPU_CMPXCHG_OP(x, , 64) + #undef PERCPU_XCHG_OP +#undef PERCPU_CMPXCHG_OP /* * It would be nice to avoid the conditional call into the scheduler when @@ -355,6 +410,12 @@ PERCPU_XCHG_OP(x, , 64) (typeof(pcp))op(&(pcp), (unsigned long)(val)); \ }) +#define _pcp_wrap_cmpxchg(op, pcp, old, new) \ +({ \ + (typeof(pcp))op(&(pcp), (unsigned long)(old), \ + (unsigned long)(new)); \ +}) + #define this_cpu_read_1(pcp) \ _pcp_wrap_return(__percpu_read_8, pcp) #define this_cpu_read_2(pcp) \ @@ -419,13 +480,13 @@ PERCPU_XCHG_OP(x, , 64) _pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val) #define this_cpu_cmpxchg_1(pcp, o, n) \ - _pcp_protect_return(cmpxchg_relaxed, pcp, o, n) + _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_8, pcp, o, n) #define this_cpu_cmpxchg_2(pcp, o, n) \ - _pcp_protect_return(cmpxchg_relaxed, pcp, o, n) + _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_16, pcp, o, n) #define this_cpu_cmpxchg_4(pcp, o, n) \ - _pcp_protect_return(cmpxchg_relaxed, pcp, o, n) + _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_32, pcp, o, n) #define this_cpu_cmpxchg_8(pcp, o, n) \ - _pcp_protect_return(cmpxchg_relaxed, pcp, o, n) + _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_64, pcp, o, n) #define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n) From c0ff52c3683c243e92a8712684ee000ea6942834 Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:40 +0100 Subject: [PATCH 0831/2286] arm64: percpu: Implement preemptible CMPXCHG128 ops Use the PCPU GPR infrastructure to implement this_cpu_cmpxchg128(). Note that before this patch, the LL/SC and LSE implementation was chosen with an alternative branch. After this patch the implementations are patched inline, matching the style of the other percpu ops. The sub-optimal register shuffling before and after this patch occurs because we hard-code specific registers, as LLVM (currently) lacks a way to place a 128-bit type in a compiler-allocated even-odd pair of registers for inline assembly. We should be able to improve that in future, given compiler support. Test case: | u128 outline_this_cpu_cmpxchg128(u128 __percpu *p, u128 old, u128 new) | { | return this_cpu_cmpxchg128(*p, old, new); | } Generated code before this patch (v7.2-rc4): | : | paciasp | stp x29, x30, [sp, #-32]! | mov x6, x0 | mov x0, x2 | mov x29, sp | mov x1, x3 | mrs x2, sp_el0 | ldr w7, [x2, #8] | add w7, w7, #0x1 | str w7, [x2, #8] | mrs x2, tpidr_el1 | add x6, x6, x2 | b 4f | mov x2, x4 | mov x3, x5 | mov x4, x6 | mov x5, x0 | mov x7, x1 | casp x0, x1, x2, x3, [x6] | 1: mrs x3, sp_el0 | ldr x2, [x3, #8] | sub x2, x2, #0x1 | str w2, [x3, #8] | cbz x2, 2f | ldr x2, [x3, #8] | cbnz x2, 3f | 2: stp x0, x1, [sp, #16] | bl preempt_schedule_notrace | ldp x0, x1, [sp, #16] | 3: ldp x29, x30, [sp], #32 | autiasp | ret | 4: prfm pstl1strm, [x6] | 5: ldxp x8, x7, [x6] | cmp x8, x0 | ccmp x7, x3, #0x0, eq | b.ne 6f | stxp w2, x4, x5, [x6] | cbnz w2, 5b | 6: mov x0, x8 | mov x1, x7 | b 1b Generated code after this patch: | : | mov x6, x0 | mov x1, x3 | mov x0, x2 | mov x3, x5 | mrs x7, sp_el0 | mov x2, x4 | mov w5, #0x10a6 | strh w5, [x7, #20] | mrs x5, tpidr_el1 | add x4, x6, x5 | prfm pstl1strm, [x4] | 1: ldxp x9, x10, [x4] | cmp x9, x0 | ccmp x10, x1, #0x0, eq | b.ne 2f | stxp w8, x2, x3, [x4] | cbnz w8, 1b | 2: strh wzr, [x7, #20] | mov x0, x9 | mov x1, x10 | ret Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 70 +++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index b877f8ef545585..861eb882f62927 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -490,19 +490,63 @@ PERCPU_CMPXCHG_OP(x, , 64) #define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n) -#define this_cpu_cmpxchg128(pcp, o, n) \ -({ \ - typedef typeof(pcp) pcp_op_T__; \ - u128 old__, new__, ret__; \ - pcp_op_T__ *ptr__; \ - old__ = o; \ - new__ = n; \ - preempt_disable_notrace(); \ - ptr__ = raw_cpu_ptr(&(pcp)); \ - ret__ = cmpxchg128_local((void *)ptr__, old__, new__); \ - preempt_enable_notrace(); \ - ret__; \ -}) +static inline u128 +__percpu_cmpxchg_128(void __percpu *pcp, u128 old, u128 new) +{ + u16 *gprs = ¤t_thread_info()->pcpu_gprs; + unsigned long addr; + unsigned long off; + union __u128_halves r, o = { .full = (old) }, + n = { .full = (new) }; + register unsigned long ol asm ("x0") = o.low; + register unsigned long oh asm ("x1") = o.high; + register unsigned long nl asm ("x2") = n.low; + register unsigned long nh asm ("x3") = n.high; + unsigned long rl, rh; + unsigned long tmp; + + asm volatile ( + __PCPU_GPRS_BEGIN("%[gprs]", "%[pcp]", "%[off]", "%[addr]") + ARM64_LSE_ATOMIC_INSN( + /* LL/SC */ + " prfm pstl1strm, [%[addr]]\n" + "1: ldxp %[rl], %[rh], [%[addr]]\n" + " cmp %[rl], %[ol]\n" + " ccmp %[rh], %[oh], 0, eq\n" + " b.ne 2f\n" + " stxp %w[tmp], %[nl], %[nh], [%[addr]]\n" + " cbnz %w[tmp], 1b\n" + "2:\n" + , + /* LSE atomics */ + " casp %[ol], %[oh], %[nl], %[nh], [%[addr]]\n" + " mov %[rl], %[ol]\n" + " mov %[rh], %[oh]\n" + __nops(4) + ) + __PCPU_GPRS_END("%[gprs]") + : [gprs] "=Qo" (*gprs), + [addr] "=&r" (addr), + [off] "=&r" (off), + [tmp] "=&r" (tmp), + [ol] "+&r" (ol), + [oh] "+&r" (oh), + [rl] "=&r" (rl), + [rh] "=&r" (rh) + : [pcp] "r" (pcp), + [nl] "r" (nl), + [nh] "r" (nh) + : "memory", "cc" + ); + + r.low = rl; + r.high = rh; + + return r.full; +} + +#define this_cpu_cmpxchg128(pcp, o, n) \ + _pcp_wrap_return(__percpu_cmpxchg_128, pcp, o, n) #ifdef __KVM_NVHE_HYPERVISOR__ extern unsigned long __hyp_per_cpu_offset(unsigned int cpu); From 479671f943c9283559fc5cdf6918dea1af9ecbef Mon Sep 17 00:00:00 2001 From: Mark Rutland Date: Tue, 8 Sep 2026 16:17:41 +0100 Subject: [PATCH 0832/2286] arm64: percpu: Remove _pcp_protect*() wrappers Now that all this_cpu_*() operations are preemptible, there are no users of _pcp_protect() or _pcp_protect_return(). Remove them both, along with the associated comment regarding preemption. Signed-off-by: Mark Rutland Tested-by: Muhammad Usama Anjum Cc: Ada Couprie Diaz Cc: Ard Biesheuvel Cc: James Morse Cc: Jinjie Ruan Cc: Marc Zyngier Cc: Peter Zijlstra Cc: Vladimir Murzin Cc: Will Deacon Cc: Yang Shi Reviewed-by: Vladimir Murzin Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/percpu.h | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/arch/arm64/include/asm/percpu.h b/arch/arm64/include/asm/percpu.h index 861eb882f62927..3cc316091d6655 100644 --- a/arch/arm64/include/asm/percpu.h +++ b/arch/arm64/include/asm/percpu.h @@ -368,33 +368,6 @@ PERCPU_CMPXCHG_OP(x, , 64) #undef PERCPU_XCHG_OP #undef PERCPU_CMPXCHG_OP -/* - * It would be nice to avoid the conditional call into the scheduler when - * re-enabling preemption for preemptible kernels, but doing that in a way - * which builds inside a module would mean messing directly with the preempt - * count. If you do this, peterz and tglx will hunt you down. - * - * Not to mention it'll break the actual preemption model for missing a - * preemption point when TIF_NEED_RESCHED gets set while preemption is - * disabled. - */ - -#define _pcp_protect(op, pcp, ...) \ -({ \ - preempt_disable_notrace(); \ - op(raw_cpu_ptr(&(pcp)), __VA_ARGS__); \ - preempt_enable_notrace(); \ -}) - -#define _pcp_protect_return(op, pcp, args...) \ -({ \ - typeof(pcp) __retval; \ - preempt_disable_notrace(); \ - __retval = (typeof(pcp))op(raw_cpu_ptr(&(pcp)), ##args); \ - preempt_enable_notrace(); \ - __retval; \ -}) - #define _pcp_wrap(op, pcp, ...) \ ({ \ op(&(pcp), __VA_ARGS__); \ From b64f41a7ace1b22457c656d931a20bb1f2d81486 Mon Sep 17 00:00:00 2001 From: Steffen Eiden Date: Thu, 24 Sep 2026 12:57:18 +0200 Subject: [PATCH 0833/2286] arm64: Use proper include variant Use <> for including the sysreg header as it is not discoverable from the current directory. Acked-by: Marc Zyngier Signed-off-by: Steffen Eiden Acked-by: Mark Rutland Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/sysreg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h index 7aa08d59d49448..4bfdac9401bd90 100644 --- a/arch/arm64/include/asm/sysreg.h +++ b/arch/arm64/include/asm/sysreg.h @@ -173,7 +173,7 @@ * come from here. The header relies on the definition of sys_reg() * earlier in this file. */ -#include "asm/sysreg-defs.h" +#include /* * System registers, organised loosely by encoding but grouped together From 9f01cd3a324390b235c67ae4ca3d35085369ebdd Mon Sep 17 00:00:00 2001 From: Dmitrii Tulnov Date: Sat, 19 Sep 2026 00:34:30 +0300 Subject: [PATCH 0834/2286] kconfig: warn about malformed KCONFIG_PROBABILITY values randconfig checks the numeric range of each probability but does not validate where strtol() stops. For example, KCONFIG_PROBABILITY=50% is accepted as 50:0:0: the '%' is parsed repeatedly as zero. For the documented value 50, tristate y/m/n probabilities are 25%/25%/50%. With 50%, both y/m probabilities silently become zero, reducing the coverage of random configuration builds. Empty fields and extra fields are also accepted. Warn when the value does not follow the documented decimal format. For malformed inputs whose parsed probabilities are in range, preserve the existing interpretation unless KCONFIG_WERROR is set. In that case, exit with an error before writing the configuration. Leading whitespace and signs are accepted by strtol(), but also trigger the warning because they are outside the documented format. Keep the strtol() result as long until the range check. This rejects out-of-range values that narrowing to int previously made valid, such as 4294967296 becoming zero on a 64-bit host, regardless of KCONFIG_WERROR. Add regression tests for one warning per malformed input, preserved configurations, KCONFIG_WERROR, out-of-range values, the supported probability formats, and the documented empty-value default. Fixes: e43956e60769 ("kconfig: implement KCONFIG_PROBABILITY for randconfig") Assisted-by: LLM Signed-off-by: Dmitrii Tulnov Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260918213430.3-1-tulnov.dl@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/conf.c | 16 ++- scripts/kconfig/tests/conftest.py | 6 + .../tests/randconfig_probability/Kconfig | 12 ++ .../tests/randconfig_probability/__init__.py | 130 ++++++++++++++++++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 scripts/kconfig/tests/randconfig_probability/Kconfig create mode 100644 scripts/kconfig/tests/randconfig_probability/__init__.py diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index fe8ba09b003910..ca6d9d735922fb 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -186,12 +186,23 @@ static void conf_set_all_new_symbols(enum conf_def_mode mode) if (mode == def_random) { int n, p[3]; + bool warned = false; char *env = getenv("KCONFIG_PROBABILITY"); n = 0; while (env && *env) { char *endp; - int tmp = strtol(env, &endp, 10); + long tmp = strtol(env, &endp, 10); + + if (!isdigit((unsigned char)*env) || + (*endp && *endp != ':') || + (*endp == ':' && (!endp[1] || n == 2))) { + if (!warned) { + fprintf(stderr, + "warning: KCONFIG_PROBABILITY has malformed format\n"); + warned = true; + } + } if (tmp >= 0 && tmp <= 100) { p[n++] = tmp; @@ -227,6 +238,9 @@ static void conf_set_all_new_symbols(enum conf_def_mode mode) perror("KCONFIG_PROBABILITY"); exit(1); } + + if (warned && getenv("KCONFIG_WERROR")) + exit(1); } menu_for_each_entry(menu) { diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 66f95e4ed58c19..2263bd2a3b3ac9 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -312,6 +312,12 @@ def stderr_matches(self, expected): return self._matches('stderr', expected) +@pytest.fixture(autouse=True) +def clear_werror(monkeypatch): + # extra_env can set strict mode, but cannot remove an inherited flag. + monkeypatch.delenv('KCONFIG_WERROR', raising=False) + + @pytest.fixture(scope="module") def conf(request): """Create a Conf instance and provide it to test functions.""" diff --git a/scripts/kconfig/tests/randconfig_probability/Kconfig b/scripts/kconfig/tests/randconfig_probability/Kconfig new file mode 100644 index 00000000000000..84f4e5fcc51e4b --- /dev/null +++ b/scripts/kconfig/tests/randconfig_probability/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config MODULES + bool + default y + modules + +config BOOL + bool "Bool" + +config TRI + tristate "Tristate" diff --git a/scripts/kconfig/tests/randconfig_probability/__init__.py b/scripts/kconfig/tests/randconfig_probability/__init__.py new file mode 100644 index 00000000000000..e6e5dda10ad929 --- /dev/null +++ b/scripts/kconfig/tests/randconfig_probability/__init__.py @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: GPL-2.0-only +"""Validate KCONFIG_PROBABILITY without changing the supported distributions.""" + + +def test_malformed_warns(conf): + probabilities = [ + 'invalid', ' ', '50%', '50 ', '0x32', '10 20', '10:20x', + '10:20:invalid', '10:20:30x', + ':50', '50:', '10::20', '10:20:', '10:20:30:', '10:20:30:40', + '-0', '+0', '+', '-', '+100:0', ' \t100:0', '0: \t100:0', + ] + for probability in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 0, repr(probability) + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), repr(probability) + assert conf.stderr.count('warning:') == 1, repr(probability) + assert conf.config is not None, repr(probability) + + +def test_malformed_preserves_config(conf): + probabilities = [('50%', '50:0:0'), ('-0', '0')] + for seed in range(20): + for probability, equivalent in probabilities: + context = 'probability={!r}, equivalent={!r}, seed={}'.format( + probability, equivalent, seed) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), context + assert conf.stderr.count('warning:') == 1, context + malformed = conf.config + + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': equivalent, + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + assert conf.config == malformed, context + + +def test_werror(conf): + probabilities = [ + ('', 0), ('50', 0), ('50%', 1), ('-0', 1), ('10:20:30:40', 1), + ] + for probability, status in probabilities: + for werror in ['', '0', '1']: + context = 'probability={!r}, werror={!r}'.format( + probability, werror) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + 'KCONFIG_WERROR': werror, + }) == status, context + if status: + assert ('warning: KCONFIG_PROBABILITY has malformed format' in + conf.stderr), context + assert conf.stderr.count('warning:') == 1, context + else: + assert 'warning:' not in conf.stderr, context + + +def test_out_of_range(conf): + probabilities = [ + '-1', '101', '+101', '0:101', '0:0:101', '60:41', '0:60:41', + '4294967296', '-4294967296', + '999999999999999999999999', '-999999999999999999999999', + ] + for probability in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 1, repr(probability) + assert 'KCONFIG_PROBABILITY:' in conf.stderr, repr(probability) + + +def test_valid(conf): + probabilities = [ + ('0', 'n', 'n'), + ('0:0', 'n', 'n'), + ('100:0', 'y', 'y'), + ('0:100', 'y', 'm'), + ('100:0:0', 'y', 'n'), + ('0:100:0', 'n', 'y'), + ('0:0:100', 'n', 'm'), + ('000:000:100', 'n', 'm'), + ] + for probability, boolean, tristate in probabilities: + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': probability, + 'KCONFIG_SEED': '0', + }) == 0, repr(probability) + assert 'warning:' not in conf.stderr, repr(probability) + for symbol, value in [('BOOL', boolean), ('TRI', tristate)]: + if value == 'n': + expected = '# CONFIG_{} is not set'.format(symbol) + else: + expected = 'CONFIG_{}={}'.format(symbol, value) + assert expected in conf.config.splitlines(), repr(probability) + + +def test_single_probability_matches_tristate_split(conf): + for seed in range(20): + context = 'seed={}'.format(seed) + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '50', + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + single = conf.config + + # 50% boolean y; 25% tristate y, 25% m, and 50% n. + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '50:25:25', + 'KCONFIG_SEED': hex(seed), + }) == 0, context + assert 'warning:' not in conf.stderr, context + assert conf.config == single, context + + +def test_empty(conf): + assert conf._run_conf('--randconfig', extra_env={ + 'KCONFIG_PROBABILITY': '', + 'KCONFIG_SEED': '0', + }) == 0 + assert 'warning:' not in conf.stderr From 651fb291c4c86b7be96720b129ef4619fbcef53c Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:25 -0700 Subject: [PATCH 0835/2286] kconfig: Add "def_string", "def_int" and "def_hex" Kconfig has offered "def_bool" and "def_tristate" as a shorthand for a type definition plus a default since before the git era, but has never offered the equivalent for the other three types. Conor Dooley ran into this gap[1] when fixing a symbol that had been given the wrong type: Unfortunately, there is no such thing as "def_string", but in this case we can use "default" to propagate the value of ... Nothing in the grammar requires the restriction. The rule that consumes a default is already type agnostic. Add the three missing types. No changes are needed to existing diagnostics. E.g. declaring a symbol "bool" and then assigning it with "def_string" still reports warning: ignoring type redefinition of 'CONFLICT' from 'bool' to 'string' Added tests for the types. Build tested ARCH=x86_64 with GCC 16.2.0. Tests pass with "make testconfig". Link: https://lore.kernel.org/all/20230111104848.2088516-1-conor.dooley@microchip.com/ [1] Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-1-kees@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kconfig-language.rst | 13 ++++++++- scripts/kconfig/kconfig-sym-check.pl | 2 +- scripts/kconfig/lexer.l | 3 ++ scripts/kconfig/parser.y | 6 ++++ scripts/kconfig/tests/def_type/Kconfig | 28 +++++++++++++++++++ scripts/kconfig/tests/def_type/__init__.py | 18 ++++++++++++ .../kconfig/tests/def_type/expected_guard_n | 9 ++++++ .../kconfig/tests/def_type/expected_guard_y | 11 ++++++++ scripts/kconfig/tests/def_type/guard_n.config | 1 + scripts/kconfig/tests/def_type/guard_y.config | 1 + 10 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 scripts/kconfig/tests/def_type/Kconfig create mode 100644 scripts/kconfig/tests/def_type/__init__.py create mode 100644 scripts/kconfig/tests/def_type/expected_guard_n create mode 100644 scripts/kconfig/tests/def_type/expected_guard_y create mode 100644 scripts/kconfig/tests/def_type/guard_n.config create mode 100644 scripts/kconfig/tests/def_type/guard_y.config diff --git a/Documentation/kbuild/kconfig-language.rst b/Documentation/kbuild/kconfig-language.rst index d9338407c1c68d..00402d43e0dc53 100644 --- a/Documentation/kbuild/kconfig-language.rst +++ b/Documentation/kbuild/kconfig-language.rst @@ -113,11 +113,22 @@ applicable everywhere (see syntax). - type definition + default value:: - "def_bool"/"def_tristate" ["if" ] + "def_bool" ["if" ] + "def_tristate" ["if" ] + "def_string" ["if" ] + "def_int" ["if" ] + "def_hex" ["if" ] This is a shorthand notation for a type definition plus a value. Optionally dependencies for this default value can be added with "if". + The shorthand supplies the type once, and is otherwise an ordinary + default: it is the first entry of the list described above, so any + further "default" entries still apply when its "if" is not met. Since + that leaves the type definition inside one arm of a list, spelling the + type out on its own line reads better for a symbol with several + defaults. + - dependencies: "depends on" ["if" ] This defines a dependency for this menu entry. If multiple diff --git a/scripts/kconfig/kconfig-sym-check.pl b/scripts/kconfig/kconfig-sym-check.pl index daa5285fdefc85..c8dd07f8b27c58 100755 --- a/scripts/kconfig/kconfig-sym-check.pl +++ b/scripts/kconfig/kconfig-sym-check.pl @@ -90,7 +90,7 @@ sub indent_depth { next; } - if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { + if (/^\s*(default|def_bool|def_tristate|def_string|def_int|def_hex|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) { my $s = $2; $s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g; $s =~ s/#.*//; diff --git a/scripts/kconfig/lexer.l b/scripts/kconfig/lexer.l index a6155422b4a688..1fa521199d4a30 100644 --- a/scripts/kconfig/lexer.l +++ b/scripts/kconfig/lexer.l @@ -105,6 +105,9 @@ n [A-Za-z0-9_-] "comment" return T_COMMENT; "config" return T_CONFIG; "def_bool" return T_DEF_BOOL; +"def_hex" return T_DEF_HEX; +"def_int" return T_DEF_INT; +"def_string" return T_DEF_STRING; "def_tristate" return T_DEF_TRISTATE; "default" return T_DEFAULT; "depends" return T_DEPENDS; diff --git a/scripts/kconfig/parser.y b/scripts/kconfig/parser.y index 40ceb9908c6fba..ecb18c213d83b0 100644 --- a/scripts/kconfig/parser.y +++ b/scripts/kconfig/parser.y @@ -53,6 +53,9 @@ struct menu *current_menu, *current_entry, *current_choice; %token T_CONFIG %token T_DEFAULT %token T_DEF_BOOL +%token T_DEF_HEX +%token T_DEF_INT +%token T_DEF_STRING %token T_DEF_TRISTATE %token T_DEPENDS %token T_ENDCHOICE @@ -309,6 +312,9 @@ type: default: T_DEFAULT { $$ = S_UNKNOWN; } | T_DEF_BOOL { $$ = S_BOOLEAN; } + | T_DEF_HEX { $$ = S_HEX; } + | T_DEF_INT { $$ = S_INT; } + | T_DEF_STRING { $$ = S_STRING; } | T_DEF_TRISTATE { $$ = S_TRISTATE; } /* if entry */ diff --git a/scripts/kconfig/tests/def_type/Kconfig b/scripts/kconfig/tests/def_type/Kconfig new file mode 100644 index 00000000000000..fbee37a631790f --- /dev/null +++ b/scripts/kconfig/tests/def_type/Kconfig @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# The def_ shorthands: a type definition plus a default value. + +config MODULES + bool "Enable loadable module support" + modules + default y + +config GUARD + bool "Guard symbol" + +config DEF_BOOL + def_bool GUARD + +config DEF_TRISTATE + def_tristate m if GUARD + +config DEF_STRING + def_string "guarded" if GUARD + default "fallback" + +config DEF_INT + def_int 64 if GUARD + default 32 + +config DEF_HEX + def_hex 0xdead if GUARD + default 0x0 diff --git a/scripts/kconfig/tests/def_type/__init__.py b/scripts/kconfig/tests/def_type/__init__.py new file mode 100644 index 00000000000000..1ebaf5da07c821 --- /dev/null +++ b/scripts/kconfig/tests/def_type/__init__.py @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +""" +Set a symbol's type and its default value in one line. + +"def_bool", "def_tristate", "def_string", "def_int" and "def_hex" are +shorthand for a type definition plus a "default" property. Check that +each one sets the type, and that an "if" on the shorthand does not +disturb the usual default cascade: the shorthand is only the first arm +of the list, so a later "default" still applies when its condition is +not met. +""" + +def test(conf): + assert conf.olddefconfig(dot_config='guard_y.config') == 0 + assert conf.config_matches('expected_guard_y') + + assert conf.olddefconfig(dot_config='guard_n.config') == 0 + assert conf.config_matches('expected_guard_n') diff --git a/scripts/kconfig/tests/def_type/expected_guard_n b/scripts/kconfig/tests/def_type/expected_guard_n new file mode 100644 index 00000000000000..14719720a8b931 --- /dev/null +++ b/scripts/kconfig/tests/def_type/expected_guard_n @@ -0,0 +1,9 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +# CONFIG_GUARD is not set +CONFIG_DEF_STRING="fallback" +CONFIG_DEF_INT=32 +CONFIG_DEF_HEX=0x0 diff --git a/scripts/kconfig/tests/def_type/expected_guard_y b/scripts/kconfig/tests/def_type/expected_guard_y new file mode 100644 index 00000000000000..b2844072d0b8f4 --- /dev/null +++ b/scripts/kconfig/tests/def_type/expected_guard_y @@ -0,0 +1,11 @@ +# +# Automatically generated file; DO NOT EDIT. +# Main menu +# +CONFIG_MODULES=y +CONFIG_GUARD=y +CONFIG_DEF_BOOL=y +CONFIG_DEF_TRISTATE=m +CONFIG_DEF_STRING="guarded" +CONFIG_DEF_INT=64 +CONFIG_DEF_HEX=0xdead diff --git a/scripts/kconfig/tests/def_type/guard_n.config b/scripts/kconfig/tests/def_type/guard_n.config new file mode 100644 index 00000000000000..ed9ad6c1a2d4ec --- /dev/null +++ b/scripts/kconfig/tests/def_type/guard_n.config @@ -0,0 +1 @@ +# CONFIG_GUARD is not set diff --git a/scripts/kconfig/tests/def_type/guard_y.config b/scripts/kconfig/tests/def_type/guard_y.config new file mode 100644 index 00000000000000..afe35b0845425f --- /dev/null +++ b/scripts/kconfig/tests/def_type/guard_y.config @@ -0,0 +1 @@ +CONFIG_GUARD=y From 889a621272f9b7e19f35efa3d836347b4e209290 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:26 -0700 Subject: [PATCH 0836/2286] kconfig: Replace "cc-option-bit" with "cc-option-str" $(cc-option-bit) returns the flag it was given when the compiler accepts it, and an empty string otherwise. It was originally only used internally for machine bit flags, but it is generally useful[1]. Rename it to $(cc-option-str), which says what it returns rather than what it was first used for, and extract the probe logic from the existing $(cc-option) macro to reuse it. Additionally add support for an optional fallback, much like the Kbuild cc-option macro. This can be used as: config CC_OPT_FOO def_string "$(cc-option-str,-fnew-option,-fold-option)" If the fallback is omitted, it is just an empty string. Build tested ARCH=x86_64 defconfig with GCC 16.2.0. Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-2-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index fc10671c297cf3..4ef2b64e97bed3 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -23,9 +23,19 @@ success = $(if-success,$(1),y,n) # Return n if exits with 0, y otherwise failure = $(if-success,$(1),n,y) +# $(cc-probe,) +# The command $(cc-option) and $(cc-option-str) test with. Not meant +# to be called directly; use one of those two instead. +cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o + # $(cc-option,) # Return y if the compiler supports , n otherwise -cc-option = $(success,trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o) +cc-option = $(success,$(cc-probe,$(1))) + +# $(cc-option-str,[,]) +# Return if the compiler supports it, otherwise. +# defaults to an empty string and is returned untested. +cc-option-str = $(if-success,$(cc-probe,$(1)),$(1),$(2)) # $(ld-option,) # Return y if the linker supports , n otherwise @@ -61,9 +71,8 @@ ld-version := $(shell,set -- $(ld-info) && echo $2) # machine bit flags # $(m32-flag): -m32 if the compiler supports it, or an empty string otherwise. # $(m64-flag): -m64 if the compiler supports it, or an empty string otherwise. -cc-option-bit = $(if-success,$(CC) -Werror $(1) -E -x c /dev/null -o /dev/null,$(1)) -m32-flag := $(cc-option-bit,-m32) -m64-flag := $(cc-option-bit,-m64) +m32-flag := $(cc-option-str,-m32) +m64-flag := $(cc-option-str,-m64) # Test whether the compiler can link userspace applications cc_can_link_user = $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(USERCFLAGS) $(USERLDFLAGS) $(1)) From cfcc465018818fb4f08148a28adb5b0cb40a9ec1 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Fri, 18 Sep 2026 17:59:27 -0700 Subject: [PATCH 0837/2286] kconfig: Add "ld-option-str" Add the linker counterpart to $(cc-option-str), sharing a $(ld-probe) with $(ld-option) the way the compiler macros share $(cc-probe). Like $(cc-option-str), it can now be used as: config LD_OPT_FOO def_string "$(ld-option-str,--new-option,--old-option)" An omitted fallback expands to the empty string. Build tested ARCH=x86_64 defconfig with GCC 16.2.0, and checked against GNU ld 2.47. Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260919005929.4077729-3-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index 4ef2b64e97bed3..622d21e6953f5a 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -37,9 +37,19 @@ cc-option = $(success,$(cc-probe,$(1))) # defaults to an empty string and is returned untested. cc-option-str = $(if-success,$(cc-probe,$(1)),$(1),$(2)) +# $(ld-probe,) +# The command $(ld-option) and $(ld-option-str) test with. Not meant +# to be called directly; use one of those two instead. +ld-probe = $(LD) -v $(1) + # $(ld-option,) # Return y if the linker supports , n otherwise -ld-option = $(success,$(LD) -v $(1)) +ld-option = $(success,$(ld-probe,$(1))) + +# $(ld-option-str,[,]) +# Return if the linker supports it, otherwise. +# defaults to an empty string and is returned untested. +ld-option-str = $(if-success,$(ld-probe,$(1)),$(1),$(2)) # $(as-instr,) # Return y if the assembler supports , n otherwise From d8263a131bb05398faa72ac32be54a03e48fa197 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:33:59 +0530 Subject: [PATCH 0838/2286] firmware: hwrng: arm_smccc_trng: Register as an SMCCC device The SMCCC TRNG interface is a firmware-provided SMCCC service rather than a standalone platform device. Now that the SMCCC core has an SMCCC bus, create an arm-smccc-trng device for the discovered TRNG service and convert the hwrng driver to an SMCCC driver. The SMCCC id table preserves module autoloading for systems where the TRNG driver is built as a module. The sysfs device path changes from the old smccc_trng platform-device path to an arm-smccc device path. No known userspace dependency on the old path was found; a Debian Code Search lookup for the existing platform-device name/path did not find any users. Reviewed-by: Catalin Marinas Reviewed-by: Jonathan Cameron Reviewed-by: Jason Gunthorpe Reviewed-by: Sudeep Holla Tested-by: Andre Przywara Cc: Russell King Cc: Will Deacon Cc: Mark Rutland Cc: Olivia Mackall Cc: Herbert Xu Cc: Lorenzo Pieralisi Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- arch/arm/include/asm/archrandom.h | 2 +- arch/arm64/include/asm/archrandom.h | 2 +- drivers/char/hw_random/arm_smccc_trng.c | 32 ++++++++----- drivers/firmware/smccc/smccc.c | 62 +++++++++++++++++++++---- 4 files changed, 76 insertions(+), 22 deletions(-) diff --git a/arch/arm/include/asm/archrandom.h b/arch/arm/include/asm/archrandom.h index cc4714eb1a7556..ee39a03ddf8abe 100644 --- a/arch/arm/include/asm/archrandom.h +++ b/arch/arm/include/asm/archrandom.h @@ -2,7 +2,7 @@ #ifndef _ASM_ARCHRANDOM_H #define _ASM_ARCHRANDOM_H -static inline bool __init smccc_probe_trng(void) +static inline bool smccc_probe_trng(void) { return false; } diff --git a/arch/arm64/include/asm/archrandom.h b/arch/arm64/include/asm/archrandom.h index 8babfbe31f9541..7605dd81bd1e1f 100644 --- a/arch/arm64/include/asm/archrandom.h +++ b/arch/arm64/include/asm/archrandom.h @@ -12,7 +12,7 @@ extern bool smccc_trng_available; -static inline bool __init smccc_probe_trng(void) +static inline bool smccc_probe_trng(void) { struct arm_smccc_res res; diff --git a/drivers/char/hw_random/arm_smccc_trng.c b/drivers/char/hw_random/arm_smccc_trng.c index dcb8e7f37f25c6..01c1a30cbab8d7 100644 --- a/drivers/char/hw_random/arm_smccc_trng.c +++ b/drivers/char/hw_random/arm_smccc_trng.c @@ -16,8 +16,10 @@ #include #include #include -#include #include +#include + +#include #ifdef CONFIG_ARM64 #define ARM_SMCCC_TRNG_RND ARM_SMCCC_TRNG_RND64 @@ -94,29 +96,37 @@ static int smccc_trng_read(struct hwrng *rng, void *data, size_t max, bool wait) return copied; } -static int smccc_trng_probe(struct platform_device *pdev) +static int smccc_trng_probe(struct arm_smccc_device *sdev) { struct hwrng *trng; - trng = devm_kzalloc(&pdev->dev, sizeof(*trng), GFP_KERNEL); + /* validate the minimum version requirement */ + if (!smccc_probe_trng()) + return -ENODEV; + + trng = devm_kzalloc(&sdev->dev, sizeof(*trng), GFP_KERNEL); if (!trng) return -ENOMEM; trng->name = "smccc_trng"; trng->read = smccc_trng_read; - return devm_hwrng_register(&pdev->dev, trng); + return devm_hwrng_register(&sdev->dev, trng); } -static struct platform_driver smccc_trng_driver = { - .driver = { - .name = "smccc_trng", - }, - .probe = smccc_trng_probe, +static const struct arm_smccc_device_id smccc_trng_id_table[] = { + { .func_id = ARM_SMCCC_TRNG_VERSION }, + {} +}; +MODULE_DEVICE_TABLE(arm_smccc, smccc_trng_id_table); + +static struct arm_smccc_driver smccc_trng_driver = { + .name = KBUILD_MODNAME, + .probe = smccc_trng_probe, + .id_table = smccc_trng_id_table, }; -module_platform_driver(smccc_trng_driver); +module_arm_smccc_driver(smccc_trng_driver); -MODULE_ALIAS("platform:smccc_trng"); MODULE_AUTHOR("Andre Przywara"); MODULE_DESCRIPTION("Arm SMCCC TRNG firmware interface support"); MODULE_LICENSE("GPL"); diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c index bdee057db2fd3b..67e503dac409ff 100644 --- a/drivers/firmware/smccc/smccc.c +++ b/drivers/firmware/smccc/smccc.c @@ -9,7 +9,8 @@ #include #include #include -#include +#include + #include static u32 smccc_version = ARM_SMCCC_VERSION_1_0; @@ -81,16 +82,59 @@ bool arm_smccc_hypervisor_has_uuid(const uuid_t *hyp_uuid) } EXPORT_SYMBOL_GPL(arm_smccc_hypervisor_has_uuid); +struct smccc_device_info { + u32 func_id; + bool requires_smc; + const char *device_name; +}; + +static const struct smccc_device_info smccc_devices[] __initconst = { + { + .func_id = ARM_SMCCC_TRNG_VERSION, + .requires_smc = false, + .device_name = "arm-smccc-trng", + }, +}; + +static bool __init smccc_probe_smccc_device(const struct smccc_device_info *smccc_dev) +{ + int ret; + struct arm_smccc_res res = {}; + + if (smccc_conduit == SMCCC_CONDUIT_NONE) + return false; + + if (smccc_dev->requires_smc && smccc_conduit != SMCCC_CONDUIT_SMC) + return false; + + if (IS_ENABLED(CONFIG_ARM) && ARM_SMCCC_IS_64(smccc_dev->func_id)) + return false; + + arm_smccc_1_1_invoke(smccc_dev->func_id, + 0, 0, 0, 0, 0, 0, 0, &res); + ret = res.a0; + + if (ret == SMCCC_RET_NOT_SUPPORTED) + return false; + + return true; +} + static int __init smccc_devices_init(void) { - struct platform_device *pdev; - - if (smccc_trng_available) { - pdev = platform_device_register_simple("smccc_trng", -1, - NULL, 0); - if (IS_ERR(pdev)) - pr_err("smccc_trng: could not register device: %ld\n", - PTR_ERR(pdev)); + struct arm_smccc_device *sdev; + const struct smccc_device_info *smccc_dev; + + for (int i = 0; i < ARRAY_SIZE(smccc_devices); i++) { + smccc_dev = &smccc_devices[i]; + + if (!smccc_probe_smccc_device(smccc_dev)) + continue; + + sdev = arm_smccc_device_register(smccc_dev->device_name, smccc_dev->func_id); + if (IS_ERR(sdev)) + pr_err("%s: could not register device: %pe\n", + smccc_dev->device_name, sdev); } return 0; From 4cd849026e2cad097da1864dbd6f58e9db51af0f Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:00 +0530 Subject: [PATCH 0839/2286] firmware: arm_rmm: Move RSI support out of arch/arm64 The RSI SMCCC function IDs describe a firmware ABI and are not arm64 architecture specific definitions. Follow-up changes need to use them from non-arch code, including drivers/firmware/smccc and the Arm CCA guest driver. Move the complete Realm Service Interface (RSI) implementation from arch/arm64 to drivers/firmware/arm_rmm. The RSI SMCCC definitions and command helpers are also moved to include/linux so they can be shared by architecture code and firmware or driver code. This also keeps the firmware interface outside architecture code, as requested [1]. [1] https://lore.kernel.org/all/agsNO9cc7H-b0H8L@willie-the-truck Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Acked-by: Suzuki K Poulose Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- MAINTAINERS | 3 + arch/arm64/Kconfig | 1 + arch/arm64/include/asm/io.h | 2 +- arch/arm64/include/asm/mem_encrypt.h | 2 +- arch/arm64/include/asm/pgtable-prot.h | 2 +- arch/arm64/include/asm/rsi.h | 70 ----------------- arch/arm64/kernel/Makefile | 2 +- arch/arm64/kernel/setup.c | 2 +- arch/arm64/mm/init.c | 3 +- drivers/firmware/Kconfig | 1 + drivers/firmware/Makefile | 1 + drivers/firmware/arm_rmm/Kconfig | 17 ++++ drivers/firmware/arm_rmm/Makefile | 2 + .../kernel => drivers/firmware/arm_rmm}/rsi.c | 2 +- drivers/virt/coco/arm-cca-guest/Kconfig | 2 +- .../virt/coco/arm-cca-guest/arm-cca-guest.c | 5 +- .../linux/arm-rsi-cmds.h | 77 +++++++++++++++++-- .../linux/arm-smccc-rsi.h | 6 +- 18 files changed, 111 insertions(+), 89 deletions(-) delete mode 100644 arch/arm64/include/asm/rsi.h create mode 100644 drivers/firmware/arm_rmm/Kconfig create mode 100644 drivers/firmware/arm_rmm/Makefile rename {arch/arm64/kernel => drivers/firmware/arm_rmm}/rsi.c (99%) rename arch/arm64/include/asm/rsi_cmds.h => include/linux/arm-rsi-cmds.h (69%) rename arch/arm64/include/asm/rsi_smc.h => include/linux/arm-smccc-rsi.h (98%) diff --git a/MAINTAINERS b/MAINTAINERS index b8e8b362f932a1..7a68c8c6a5a605 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3951,8 +3951,11 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git F: Documentation/arch/arm64/ F: arch/arm64/ +F: drivers/firmware/arm_rmm/ F: drivers/virt/coco/arm-cca-guest/ F: drivers/virt/coco/pkvm-guest/ +F: include/linux/arm-rsi-cmds.h +F: include/linux/arm-smccc-rsi.h F: tools/testing/selftests/arm64/ X: arch/arm64/boot/dts/ X: arch/arm64/configs/defconfig diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..ff9565d3ffa596 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -38,6 +38,7 @@ config ARM64 select ARCH_HAS_MEMBARRIER_SYNC_CORE select ARCH_HAS_MEM_ENCRYPT select ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS + select ARCH_SUPPORTS_RMM select ARCH_HAS_NMI_SAFE_THIS_CPU_OPS select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE select ARCH_HAS_NONLEAF_PMD_YOUNG if ARM64_HAFT diff --git a/arch/arm64/include/asm/io.h b/arch/arm64/include/asm/io.h index 49a7002661a928..1620537f0332d9 100644 --- a/arch/arm64/include/asm/io.h +++ b/arch/arm64/include/asm/io.h @@ -8,6 +8,7 @@ #ifndef __ASM_IO_H #define __ASM_IO_H +#include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include /* * Generic IO read/write. These perform native-endian accesses. diff --git a/arch/arm64/include/asm/mem_encrypt.h b/arch/arm64/include/asm/mem_encrypt.h index 636f45b4d8afc7..f03b9d7b83b438 100644 --- a/arch/arm64/include/asm/mem_encrypt.h +++ b/arch/arm64/include/asm/mem_encrypt.h @@ -2,8 +2,8 @@ #ifndef __ASM_MEM_ENCRYPT_H #define __ASM_MEM_ENCRYPT_H +#include #include -#include struct device; diff --git a/arch/arm64/include/asm/pgtable-prot.h b/arch/arm64/include/asm/pgtable-prot.h index 09d7c00cf40541..58e71e592175ef 100644 --- a/arch/arm64/include/asm/pgtable-prot.h +++ b/arch/arm64/include/asm/pgtable-prot.h @@ -66,9 +66,9 @@ #ifndef __ASSEMBLER__ +#include #include #include -#include extern bool arm64_use_ng_mappings; extern unsigned long prot_ns_shared; diff --git a/arch/arm64/include/asm/rsi.h b/arch/arm64/include/asm/rsi.h deleted file mode 100644 index 88b50d660e85a0..00000000000000 --- a/arch/arm64/include/asm/rsi.h +++ /dev/null @@ -1,70 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2024 ARM Ltd. - */ - -#ifndef __ASM_RSI_H_ -#define __ASM_RSI_H_ - -#include -#include -#include - -#define RSI_PDEV_NAME "arm-cca-dev" - -DECLARE_STATIC_KEY_FALSE(rsi_present); - -void __init arm64_rsi_init(void); - -bool arm64_rsi_is_protected(phys_addr_t base, size_t size); - -static inline bool is_realm_world(void) -{ - return static_branch_unlikely(&rsi_present); -} - -static inline int rsi_set_memory_range(phys_addr_t start, phys_addr_t end, - enum ripas state, unsigned long flags) -{ - unsigned long ret; - phys_addr_t top; - - while (start != end) { - ret = rsi_set_addr_range_state(start, end, state, flags, &top); - if (ret || top < start || top > end) - return -EINVAL; - start = top; - } - - return 0; -} - -/* - * Convert the specified range to RAM. Do not use this if you rely on the - * contents of a page that may already be in RAM state. - */ -static inline int rsi_set_memory_range_protected(phys_addr_t start, - phys_addr_t end) -{ - return rsi_set_memory_range(start, end, RSI_RIPAS_RAM, - RSI_CHANGE_DESTROYED); -} - -/* - * Convert the specified range to RAM. Do not convert any pages that may have - * been DESTROYED, without our permission. - */ -static inline int rsi_set_memory_range_protected_safe(phys_addr_t start, - phys_addr_t end) -{ - return rsi_set_memory_range(start, end, RSI_RIPAS_RAM, - RSI_NO_CHANGE_DESTROYED); -} - -static inline int rsi_set_memory_range_shared(phys_addr_t start, - phys_addr_t end) -{ - return rsi_set_memory_range(start, end, RSI_RIPAS_EMPTY, - RSI_CHANGE_DESTROYED); -} -#endif /* __ASM_RSI_H_ */ diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile index d2690c3ec52885..21fc3a3d25c91f 100644 --- a/arch/arm64/kernel/Makefile +++ b/arch/arm64/kernel/Makefile @@ -34,7 +34,7 @@ obj-y := debug-monitors.o entry.o irq.o fpsimd.o \ cpufeature.o alternative.o cacheinfo.o \ smp.o smp_spin_table.o topology.o smccc-call.o \ syscall.o proton-pack.o idle.o patching.o pi/ \ - rsi.o jump_label.o + jump_label.o obj-$(CONFIG_COMPAT) += sys32.o signal32.o \ sys_compat.o diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c index 29c6100f0c50b2..a707deba6ae701 100644 --- a/arch/arm64/kernel/setup.c +++ b/arch/arm64/kernel/setup.c @@ -7,6 +7,7 @@ */ #include +#include #include #include #include @@ -43,7 +44,6 @@ #include #include #include -#include #include #include #include diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c index fbf215ecc7d061..2a8de20d2b447b 100644 --- a/arch/arm64/mm/init.c +++ b/arch/arm64/mm/init.c @@ -34,6 +34,8 @@ #include #include #include +#include + #include #include @@ -43,7 +45,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index b7cc11e4fbfa65..62660bf520a8da 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -310,5 +310,6 @@ source "drivers/firmware/samsung/Kconfig" source "drivers/firmware/smccc/Kconfig" source "drivers/firmware/tegra/Kconfig" source "drivers/firmware/xilinx/Kconfig" +source "drivers/firmware/arm_rmm/Kconfig" endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index be46f1e1dc77fa..196a650ccf025b 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -39,3 +39,4 @@ obj-y += samsung/ obj-y += smccc/ obj-y += tegra/ obj-y += xilinx/ +obj-y += arm_rmm/ diff --git a/drivers/firmware/arm_rmm/Kconfig b/drivers/firmware/arm_rmm/Kconfig new file mode 100644 index 00000000000000..e981e12fdbe6a8 --- /dev/null +++ b/drivers/firmware/arm_rmm/Kconfig @@ -0,0 +1,17 @@ + +config ARCH_SUPPORTS_RMM + bool + +config ARM_RMM_RSI + bool "Realm Service Interface (RSI) Support" + depends on ARCH_SUPPORTS_RMM + default y + help + Enable support for running Linux as a Realm guest under a Realm + Management Monitor (RMM), as defined by the Arm Confidential + Compute Architecture. + + The RMM exposes the Realm Service Interface (RSI) to Realm guests. + Linux uses RSI to negotiate the interface version, obtain the Realm + configuration, and manage the protected or shared state of guest + memory. diff --git a/drivers/firmware/arm_rmm/Makefile b/drivers/firmware/arm_rmm/Makefile new file mode 100644 index 00000000000000..3708b37a9aafc6 --- /dev/null +++ b/drivers/firmware/arm_rmm/Makefile @@ -0,0 +1,2 @@ + +obj-$(CONFIG_ARM_RMM_RSI) = rsi.o diff --git a/arch/arm64/kernel/rsi.c b/drivers/firmware/arm_rmm/rsi.c similarity index 99% rename from arch/arm64/kernel/rsi.c rename to drivers/firmware/arm_rmm/rsi.c index 25ca75ce1a4dff..8e716f1c1e319d 100644 --- a/arch/arm64/kernel/rsi.c +++ b/drivers/firmware/arm_rmm/rsi.c @@ -8,11 +8,11 @@ #include #include #include +#include #include #include #include -#include static struct realm_config config; diff --git a/drivers/virt/coco/arm-cca-guest/Kconfig b/drivers/virt/coco/arm-cca-guest/Kconfig index 3f0f013f03f159..2582ebb36922ea 100644 --- a/drivers/virt/coco/arm-cca-guest/Kconfig +++ b/drivers/virt/coco/arm-cca-guest/Kconfig @@ -1,6 +1,6 @@ config ARM_CCA_GUEST tristate "Arm CCA Guest driver" - depends on ARM64 + depends on ARM_RMM_RSI select TSM_REPORTS help The driver provides userspace interface to request and diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c b/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c index 0eeddd1ff05b51..b741643d136476 100644 --- a/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c +++ b/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c @@ -3,7 +3,8 @@ * Copyright (C) 2023 ARM Ltd. */ -#include +#include +#include #include #include #include @@ -12,8 +13,6 @@ #include #include -#include - /** * struct arm_cca_token_info - a descriptor for the token buffer. * @granule: PA of the granule to which the token will be written diff --git a/arch/arm64/include/asm/rsi_cmds.h b/include/linux/arm-rsi-cmds.h similarity index 69% rename from arch/arm64/include/asm/rsi_cmds.h rename to include/linux/arm-rsi-cmds.h index c1fab41f671ec9..9e8a8c189ca1a5 100644 --- a/arch/arm64/include/asm/rsi_cmds.h +++ b/include/linux/arm-rsi-cmds.h @@ -3,14 +3,36 @@ * Copyright (C) 2023 ARM Ltd. */ -#ifndef __ASM_RSI_CMDS_H -#define __ASM_RSI_CMDS_H +#ifndef __LINUX_ARM_RSI_CMDS_H_ +#define __LINUX_ARM_RSI_CMDS_H_ -#include +#include +#include #include #include -#include +#define RSI_PDEV_NAME "arm-cca-dev" +#ifdef CONFIG_ARM_RMM_RSI +DECLARE_STATIC_KEY_FALSE(rsi_present); + +void __init arm64_rsi_init(void); + +bool arm64_rsi_is_protected(phys_addr_t base, size_t size); + +static inline bool is_realm_world(void) +{ + return static_branch_unlikely(&rsi_present); +} +#else +static inline void arm64_rsi_init(void) { } + +static inline bool arm64_rsi_is_protected(phys_addr_t base, size_t size) +{ + return false; +} + +static inline bool is_realm_world(void) { return false; } +#endif #define RSI_GRANULE_SHIFT 12 #define RSI_GRANULE_SIZE (_AC(1, UL) << RSI_GRANULE_SHIFT) @@ -88,6 +110,51 @@ static inline long rsi_set_addr_range_state(phys_addr_t start, return res.a0; } +static inline int rsi_set_memory_range(phys_addr_t start, phys_addr_t end, + enum ripas state, unsigned long flags) +{ + unsigned long ret; + phys_addr_t top; + + while (start != end) { + ret = rsi_set_addr_range_state(start, end, state, flags, &top); + if (ret || top < start || top > end) + return -EINVAL; + start = top; + } + + return 0; +} + +/* + * Convert the specified range to RAM. Do not use this if you rely on the + * contents of a page that may already be in RAM state. + */ +static inline int rsi_set_memory_range_protected(phys_addr_t start, + phys_addr_t end) +{ + return rsi_set_memory_range(start, end, RSI_RIPAS_RAM, + RSI_CHANGE_DESTROYED); +} + +/* + * Convert the specified range to RAM. Do not convert any pages that may have + * been DESTROYED, without our permission. + */ +static inline int rsi_set_memory_range_protected_safe(phys_addr_t start, + phys_addr_t end) +{ + return rsi_set_memory_range(start, end, RSI_RIPAS_RAM, + RSI_NO_CHANGE_DESTROYED); +} + +static inline int rsi_set_memory_range_shared(phys_addr_t start, + phys_addr_t end) +{ + return rsi_set_memory_range(start, end, RSI_RIPAS_EMPTY, + RSI_CHANGE_DESTROYED); +} + #define RSI_ATTEST_CHALLENGE_MIN_SIZE 32 #define RSI_ATTEST_CHALLENGE_MAX_SIZE 64 @@ -170,4 +237,4 @@ static inline unsigned long rsi_attestation_token_continue(phys_addr_t granule, return res.a0; } -#endif /* __ASM_RSI_CMDS_H */ +#endif /* __LINUX_ARM_RSI_CMDS_H_ */ diff --git a/arch/arm64/include/asm/rsi_smc.h b/include/linux/arm-smccc-rsi.h similarity index 98% rename from arch/arm64/include/asm/rsi_smc.h rename to include/linux/arm-smccc-rsi.h index e19253f96c9407..fddb77986f706c 100644 --- a/arch/arm64/include/asm/rsi_smc.h +++ b/include/linux/arm-smccc-rsi.h @@ -3,8 +3,8 @@ * Copyright (C) 2023 ARM Ltd. */ -#ifndef __ASM_RSI_SMC_H_ -#define __ASM_RSI_SMC_H_ +#ifndef __LINUX_ARM_SMCCC_RSI_H_ +#define __LINUX_ARM_SMCCC_RSI_H_ #include @@ -190,4 +190,4 @@ struct realm_config { */ #define SMC_RSI_HOST_CALL SMC_RSI_FID(0x199) -#endif /* __ASM_RSI_SMC_H_ */ +#endif /* __LINUX_ARM_SMCCC_RSI_H_ */ From 714153dca3a85e2be141308103b575ac73004138 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:01 +0530 Subject: [PATCH 0840/2286] arm64: realm: Move Realm memory encryption ops to RSI code Realm memory encryption callbacks are CCA-specific. Keep the Realm callback registration with the RSI initialization code instead of pageattr.c, which only needs to provide the low-level page-attribute transition helper. Export __set_memory_enc_dec() within arm64 so the RSI code can wrap it with the Realm-specific encrypt/decrypt callbacks and warning policy. No functional changes in this patch. Reviewed-by: Jonathan Cameron Reviewed-by: Suzuki K Poulose Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/mem_encrypt.h | 3 +-- arch/arm64/mm/pageattr.c | 38 +--------------------------- drivers/firmware/arm_rmm/rsi.c | 34 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/arch/arm64/include/asm/mem_encrypt.h b/arch/arm64/include/asm/mem_encrypt.h index f03b9d7b83b438..ef8b8463e52b19 100644 --- a/arch/arm64/include/asm/mem_encrypt.h +++ b/arch/arm64/include/asm/mem_encrypt.h @@ -16,8 +16,7 @@ int arm64_mem_crypt_ops_register(const struct arm64_mem_crypt_ops *ops); int set_memory_encrypted(unsigned long addr, int numpages); int set_memory_decrypted(unsigned long addr, int numpages); - -int realm_register_memory_enc_ops(void); +int __set_memory_enc_dec(unsigned long addr, int numpages, bool encrypt); static inline bool force_dma_unencrypted(struct device *dev) { diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c index bbe98ac9ad8c67..14b2a3801f4041 100644 --- a/arch/arm64/mm/pageattr.c +++ b/arch/arm64/mm/pageattr.c @@ -275,9 +275,7 @@ int set_direct_map_default_noflush(struct page *page) PAGE_SIZE, set_mask, clear_mask); } -static int __set_memory_enc_dec(unsigned long addr, - int numpages, - bool encrypt) +int __set_memory_enc_dec(unsigned long addr, int numpages, bool encrypt) { unsigned long set_prot = 0, clear_prot = 0; phys_addr_t start, end; @@ -321,40 +319,6 @@ static int __set_memory_enc_dec(unsigned long addr, __pgprot(PTE_PRESENT_INVALID)); } -static int realm_set_memory_encrypted(unsigned long addr, int numpages) -{ - int ret = __set_memory_enc_dec(addr, numpages, true); - - /* - * If the request to change state fails, then the only sensible cause - * of action for the caller is to leak the memory - */ - WARN(ret, "Failed to encrypt memory, %d pages will be leaked", - numpages); - - return ret; -} - -static int realm_set_memory_decrypted(unsigned long addr, int numpages) -{ - int ret = __set_memory_enc_dec(addr, numpages, false); - - WARN(ret, "Failed to decrypt memory, %d pages will be leaked", - numpages); - - return ret; -} - -static const struct arm64_mem_crypt_ops realm_crypt_ops = { - .encrypt = realm_set_memory_encrypted, - .decrypt = realm_set_memory_decrypted, -}; - -int realm_register_memory_enc_ops(void) -{ - return arm64_mem_crypt_ops_register(&realm_crypt_ops); -} - int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid) { unsigned long addr = (unsigned long)page_address(page); diff --git a/drivers/firmware/arm_rmm/rsi.c b/drivers/firmware/arm_rmm/rsi.c index 8e716f1c1e319d..ada139d0e34482 100644 --- a/drivers/firmware/arm_rmm/rsi.c +++ b/drivers/firmware/arm_rmm/rsi.c @@ -127,6 +127,40 @@ static int realm_ioremap_hook(phys_addr_t phys, size_t size, pgprot_t *prot) return 0; } +static int realm_set_memory_encrypted(unsigned long addr, int numpages) +{ + int ret = __set_memory_enc_dec(addr, numpages, true); + + /* + * If the request to change state fails, then the only sensible cause + * of action for the caller is to leak the memory + */ + WARN(ret, "Failed to encrypt memory, %d pages will be leaked", + numpages); + + return ret; +} + +static int realm_set_memory_decrypted(unsigned long addr, int numpages) +{ + int ret = __set_memory_enc_dec(addr, numpages, false); + + WARN(ret, "Failed to decrypt memory, %d pages will be leaked", + numpages); + + return ret; +} + +static const struct arm64_mem_crypt_ops realm_crypt_ops = { + .encrypt = realm_set_memory_encrypted, + .decrypt = realm_set_memory_decrypted, +}; + +static int realm_register_memory_enc_ops(void) +{ + return arm64_mem_crypt_ops_register(&realm_crypt_ops); +} + void __init arm64_rsi_init(void) { if (arm_smccc_1_1_get_conduit() != SMCCC_CONDUIT_SMC) From 07005b64d72a435d453dd546ca9b4b1b8c484a60 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:02 +0530 Subject: [PATCH 0841/2286] virt: coco: arm-cca-guest: Rename TSM report source file The Arm CCA guest driver currently only implements TSM report support, but follow-up changes will add more TSM-related functionality to the same module. Rename arm-cca-guest.c to main.c and build it as an object of the arm-cca-guest module. This leaves room for the module to grow additional source files. Also rename the arm_cca_tsm_ops instance to arm_cca_tsm_report_ops to distinguish the report operations from other TSM functionality that will be added. Reviewed-by: Jonathan Cameron Reviewed-by: Suzuki K Poulose Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- drivers/virt/coco/arm-cca-guest/Makefile | 2 ++ drivers/virt/coco/arm-cca-guest/{arm-cca-guest.c => main.c} | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) rename drivers/virt/coco/arm-cca-guest/{arm-cca-guest.c => main.c} (97%) diff --git a/drivers/virt/coco/arm-cca-guest/Makefile b/drivers/virt/coco/arm-cca-guest/Makefile index 69eeba08e98a85..7564f4fcc4a41a 100644 --- a/drivers/virt/coco/arm-cca-guest/Makefile +++ b/drivers/virt/coco/arm-cca-guest/Makefile @@ -1,2 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest.o + +arm-cca-guest-y += main.o diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c b/drivers/virt/coco/arm-cca-guest/main.c similarity index 97% rename from drivers/virt/coco/arm-cca-guest/arm-cca-guest.c rename to drivers/virt/coco/arm-cca-guest/main.c index b741643d136476..80659f0885e9a8 100644 --- a/drivers/virt/coco/arm-cca-guest/arm-cca-guest.c +++ b/drivers/virt/coco/arm-cca-guest/main.c @@ -156,7 +156,7 @@ static int arm_cca_report_new(struct tsm_report *report, void *data) return ret; } -static const struct tsm_report_ops arm_cca_tsm_ops = { +static const struct tsm_report_ops arm_cca_tsm_report_ops = { .name = KBUILD_MODNAME, .report_new = arm_cca_report_new, }; @@ -177,7 +177,7 @@ static int __init arm_cca_guest_init(void) if (!is_realm_world()) return -ENODEV; - ret = tsm_report_register(&arm_cca_tsm_ops, NULL); + ret = tsm_report_register(&arm_cca_tsm_report_ops, NULL); if (ret < 0) pr_err("Error %d registering with TSM\n", ret); @@ -191,7 +191,7 @@ module_init(arm_cca_guest_init); */ static void __exit arm_cca_guest_exit(void) { - tsm_report_unregister(&arm_cca_tsm_ops); + tsm_report_unregister(&arm_cca_tsm_report_ops); } module_exit(arm_cca_guest_exit); From 0d5be76b6086298b46c76e15349e591b81d95085 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:03 +0530 Subject: [PATCH 0842/2286] firmware: smccc: arm-cca-guest: Bind the TSM provider to an SMCCC device The Arm CCA guest TSM provider currently binds through the arm-cca-dev platform device. Like arm-smccc-trng, this device is not an independent platform resource; it is a software representation of the RSI firmware service discovered through SMCCC. Move RSI discovery into the SMCCC firmware driver. When the SMCCC conduit is SMC and if RSI ABI version call is supported, create an arm-rsi SMCCC device. Convert the Arm CCA guest TSM provider to an SMCCC driver so it binds to that discovered RSI service and keeps module autoloading through the SMCCC device id table. Keep the old arm-cca-dev platform-device registration for now. Userspace has used that device as a Realm-guest indicator, so removing it is left to a follow-up patch that adds a replacement sysfs ABI. Reviewed-by: Jason Gunthorpe Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Reviewed-by: Sudeep Holla Reviewed-by: Suzuki K Poulose Cc: Will Deacon Cc: Mark Rutland Cc: Lorenzo Pieralisi Signed-off-by: Aneesh Kumar K.V (Arm) Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rsi.c | 2 +- drivers/firmware/smccc/smccc.c | 6 ++++ drivers/virt/coco/arm-cca-guest/Kconfig | 1 + drivers/virt/coco/arm-cca-guest/main.c | 41 ++++++++++--------------- include/linux/arm-rsi-cmds.h | 1 - 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/drivers/firmware/arm_rmm/rsi.c b/drivers/firmware/arm_rmm/rsi.c index ada139d0e34482..2dd69febedbd4f 100644 --- a/drivers/firmware/arm_rmm/rsi.c +++ b/drivers/firmware/arm_rmm/rsi.c @@ -183,7 +183,7 @@ void __init arm64_rsi_init(void) } static struct platform_device rsi_dev = { - .name = RSI_PDEV_NAME, + .name = "arm-cca-dev", .id = PLATFORM_DEVID_NONE }; diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c index 67e503dac409ff..afc2e4d602ba62 100644 --- a/drivers/firmware/smccc/smccc.c +++ b/drivers/firmware/smccc/smccc.c @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -94,6 +95,11 @@ static const struct smccc_device_info smccc_devices[] __initconst = { .requires_smc = false, .device_name = "arm-smccc-trng", }, + { + .func_id = SMC_RSI_ABI_VERSION, + .requires_smc = true, + .device_name = "arm-rsi", + }, }; static bool __init smccc_probe_smccc_device(const struct smccc_device_info *smccc_dev) diff --git a/drivers/virt/coco/arm-cca-guest/Kconfig b/drivers/virt/coco/arm-cca-guest/Kconfig index 2582ebb36922ea..0d4ce72e2d8622 100644 --- a/drivers/virt/coco/arm-cca-guest/Kconfig +++ b/drivers/virt/coco/arm-cca-guest/Kconfig @@ -1,6 +1,7 @@ config ARM_CCA_GUEST tristate "Arm CCA Guest driver" depends on ARM_RMM_RSI + depends on HAVE_ARM_SMCCC_DISCOVERY select TSM_REPORTS help The driver provides userspace interface to request and diff --git a/drivers/virt/coco/arm-cca-guest/main.c b/drivers/virt/coco/arm-cca-guest/main.c index 80659f0885e9a8..f97f593da6e3d3 100644 --- a/drivers/virt/coco/arm-cca-guest/main.c +++ b/drivers/virt/coco/arm-cca-guest/main.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -161,16 +162,7 @@ static const struct tsm_report_ops arm_cca_tsm_report_ops = { .report_new = arm_cca_report_new, }; -/** - * arm_cca_guest_init - Register with the Trusted Security Module (TSM) - * interface. - * - * Return: - * * %0 - Registered successfully with the TSM interface. - * * %-ENODEV - The execution context is not an Arm Realm. - * * %-EBUSY - Already registered. - */ -static int __init arm_cca_guest_init(void) +static int cca_tsm_probe(struct arm_smccc_device *sdev) { int ret; @@ -179,29 +171,30 @@ static int __init arm_cca_guest_init(void) ret = tsm_report_register(&arm_cca_tsm_report_ops, NULL); if (ret < 0) - pr_err("Error %d registering with TSM\n", ret); + return dev_err_probe(&sdev->dev, + ret, "Error registering with TSM\n"); - return ret; + return 0; } -module_init(arm_cca_guest_init); -/** - * arm_cca_guest_exit - unregister with the Trusted Security Module (TSM) - * interface. - */ -static void __exit arm_cca_guest_exit(void) +static void cca_tsm_remove(struct arm_smccc_device *sdev) { tsm_report_unregister(&arm_cca_tsm_report_ops); } -module_exit(arm_cca_guest_exit); -/* modalias, so userspace can autoload this module when RSI is available */ -static const struct platform_device_id arm_cca_match[] __maybe_unused = { - { .name = RSI_PDEV_NAME }, - { } +static const struct arm_smccc_device_id cca_tsm_id_table[] = { + { .func_id = SMC_RSI_ABI_VERSION }, + {} }; +MODULE_DEVICE_TABLE(arm_smccc, cca_tsm_id_table); -MODULE_DEVICE_TABLE(platform, arm_cca_match); +static struct arm_smccc_driver cca_tsm_driver = { + .name = KBUILD_MODNAME, + .probe = cca_tsm_probe, + .remove = cca_tsm_remove, + .id_table = cca_tsm_id_table, +}; +module_arm_smccc_driver(cca_tsm_driver); MODULE_AUTHOR("Sami Mujawar "); MODULE_DESCRIPTION("Arm CCA Guest TSM Driver"); MODULE_LICENSE("GPL"); diff --git a/include/linux/arm-rsi-cmds.h b/include/linux/arm-rsi-cmds.h index 9e8a8c189ca1a5..3f7a6a833993b2 100644 --- a/include/linux/arm-rsi-cmds.h +++ b/include/linux/arm-rsi-cmds.h @@ -11,7 +11,6 @@ #include #include -#define RSI_PDEV_NAME "arm-cca-dev" #ifdef CONFIG_ARM_RMM_RSI DECLARE_STATIC_KEY_FALSE(rsi_present); From d541df166d18d0526a377ba13022c109b8169db2 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:04 +0530 Subject: [PATCH 0843/2286] coco: guest: arm64: Add sysfs ABI for CCA Realm guests Userspace has used the arm-cca-dev platform device to detect Arm CCA Realm guests. Removing it without a replacement would break that detection and would also leave userspace depending on kernel device-model details. Add /sys/firmware/cca/realm_guest as a stable ABI for detecting whether the kernel is running as an Arm CCA Realm guest. Create the file only in Realm world, where it reads 1. Document the new ABI in Documentation/ABI/testing/sysfs-firmware-cca. Link: https://lore.kernel.org/all/4a7d84b2-2ec4-4773-a2d5-7b63d5c683cf@arm.com Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Aneesh Kumar K.V (Arm) Reviewed-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- Documentation/ABI/testing/sysfs-firmware-cca | 7 ++++ MAINTAINERS | 1 + drivers/firmware/arm_rmm/rsi.c | 35 ++++++++++++++++++++ 3 files changed, 43 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-firmware-cca diff --git a/Documentation/ABI/testing/sysfs-firmware-cca b/Documentation/ABI/testing/sysfs-firmware-cca new file mode 100644 index 00000000000000..d545b752353049 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-firmware-cca @@ -0,0 +1,7 @@ +What: /sys/firmware/cca/realm_guest +Date: September 2026 +Contact: Linux ARM Kernel Mailing list +Description: Read-only. Present only when the kernel is running as an + Arm Confidential Compute Architecture (CCA) Realm guest, in + which case it reads 1. If the file does not exist, the kernel + is not running as a Realm guest. diff --git a/MAINTAINERS b/MAINTAINERS index 7a68c8c6a5a605..bd50cad42e3763 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3949,6 +3949,7 @@ R: Mark Rutland L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git +F: Documentation/ABI/testing/sysfs-firmware-cca F: Documentation/arch/arm64/ F: arch/arm64/ F: drivers/firmware/arm_rmm/ diff --git a/drivers/firmware/arm_rmm/rsi.c b/drivers/firmware/arm_rmm/rsi.c index 2dd69febedbd4f..29eeb90ffc6b26 100644 --- a/drivers/firmware/arm_rmm/rsi.c +++ b/drivers/firmware/arm_rmm/rsi.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -182,6 +184,39 @@ void __init arm64_rsi_init(void) static_branch_enable(&rsi_present); } +static ssize_t realm_guest_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "1\n"); +} + +static struct kobj_attribute cca_realm_guest = __ATTR_RO(realm_guest); +static const struct attribute *cca_realm_attrs[] = { + &cca_realm_guest.attr, + NULL +}; + +static int __init realm_sysfs_init(void) +{ + int ret; + struct kobject *cca_kobj; + + if (!is_realm_world()) + return 0; + + cca_kobj = kobject_create_and_add("cca", firmware_kobj); + if (!cca_kobj) + return -ENOMEM; + + ret = sysfs_create_files(cca_kobj, cca_realm_attrs); + if (!ret) + return 0; + + kobject_put(cca_kobj); + return ret; +} +device_initcall(realm_sysfs_init); + static struct platform_device rsi_dev = { .name = "arm-cca-dev", .id = PLATFORM_DEVID_NONE From bce57945f5e7207960b2bd567a6e880f90258ffb Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Wed, 23 Sep 2026 19:34:05 +0530 Subject: [PATCH 0844/2286] coco: guest: arm64: Remove dummy CCA platform device The SMCCC firmware driver now creates the arm_smccc bus and instantiates the CCA RSI device once the RSI ABI is discovered. The arm64-specific arm-cca-dev platform device stub is therefore no longer needed. Userspace now has /sys/firmware/cca/realm_guest as a dedicated CCA Realm guest indicator. Remove the dummy arm-cca-dev registration. Link: https://lore.kernel.org/all/4a7d84b2-2ec4-4773-a2d5-7b63d5c683cf@arm.com Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Reviewed-by: Jason Gunthorpe Cc: Will Deacon Cc: Mark Rutland Signed-off-by: Aneesh Kumar K.V (Arm) Reviewed-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rsi.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/firmware/arm_rmm/rsi.c b/drivers/firmware/arm_rmm/rsi.c index 29eeb90ffc6b26..0fcc4e33bd92f6 100644 --- a/drivers/firmware/arm_rmm/rsi.c +++ b/drivers/firmware/arm_rmm/rsi.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -216,18 +215,3 @@ static int __init realm_sysfs_init(void) return ret; } device_initcall(realm_sysfs_init); - -static struct platform_device rsi_dev = { - .name = "arm-cca-dev", - .id = PLATFORM_DEVID_NONE -}; - -static int __init arm64_create_dummy_rsi_dev(void) -{ - if (is_realm_world() && - platform_device_register(&rsi_dev)) - pr_err("failed to register rsi platform device\n"); - return 0; -} - -arch_initcall(arm64_create_dummy_rsi_dev) From ff1227f70eead8704ecfec69259fb4c041f92cda Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Thu, 24 Sep 2026 01:41:19 +0900 Subject: [PATCH 0845/2286] nilfs2: fix deadlock between nilfs_evict_inode() and find_inode() Syzbot reported a deadlock between nilfs_evict_inode(), executed via an asynchronous workqueue (kworker) as: process_one_work() process_scheduled_works() nilfs_iput_work_func() nilfs_dispose_list() iput() iput_final() evict() s_op->evict_inode() nilfs_evict_inode() and find_inode(), called via nilfs_mkdir() nilfs_new_inode() nilfs_insert_inode_locked() insert_inode_locked4() inode_insert5() find_inode() When an inode is deleted (i_nlink == 0), nilfs_evict_inode() clears its ifile bitmap entry and then synchronously triggers the log writer via nilfs_transaction_commit() if IS_SYNC(inode) is true. If a concurrent directory creation via nilfs_new_inode() attempts to allocate the same newly freed inode number, however, find_inode() called just before inserting it into the inode hash, blocks waiting for the old inode's evict() to complete. Meanwhile, the synchronous log writer invoked from nilfs_evict_inode() tries to acquire a write lock on 'ns_segctor_sem' which is read locked by the caller of nilfs_new_inode() (in this case, nilfs_mkdir()), resulting in a deadlock. The synchronous log writer invocation from nilfs_evict_inode() is a legacy artifact; it is no longer necessary because the call itself (triggered by iput()) is now handled asynchronously with a kworker if i_nlink == 0. It merely increases the risk of deadlock. Fix this deadlock by removing the synchronous log writer trigger from nilfs_evict_inode(). Reported-by: syzbot+6646318bbcf419411bc5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=6646318bbcf419411bc5 Fixes: 7ef3ff2fea8b ("nilfs2: fix deadlock of segment constructor over I_SYNC flag") Tested-by: syzbot+6646318bbcf419411bc5@syzkaller.appspotmail.com Cc: stable@vger.kernel.org Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/inode.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 05e0389a5fa37a..c739726f986247 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -875,13 +875,7 @@ void nilfs_evict_inode(struct inode *inode) nilfs_clear_inode(inode); - if (IS_SYNC(inode)) - nilfs_set_transaction_flag(NILFS_TI_SYNC); nilfs_transaction_commit(sb); - /* - * May construct a logical segment and may fail in sync mode. - * But delete_inode has no return value. - */ } int nilfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, From 4f8c177caac8ecdedeb95caa673214fc3f6a75cb Mon Sep 17 00:00:00 2001 From: "Jose A. Perez de Azpillaga" Date: Wed, 23 Sep 2026 23:27:25 +0200 Subject: [PATCH 0846/2286] x86/mm: Fix void/int conditional in pgd_clear() sparse building mm/ reports an error for every pgd_clear() caller: mm/pgtable-generic.c:30:9: error: incompatible types in conditional expression (different base types): mm/pgtable-generic.c:30:9: void mm/pgtable-generic.c:30:9: int pgtable_l5_enabled() ? native_pgd_clear(pgd) : 0 mixes a void function call with an int constant. C11 6.5.15 requires the second and third operands of a conditional expression to both have arithmetic type, both have the same structure or union type, both have void type, or to be compatible pointer types. void and int are none of those, so this is a type error, not just a style issue. GCC accepts it as an extension, and the result type is void, so there is no runtime effect today. The macro only exists when the p4d level is not folded, that is, when CONFIG_PGTABLE_LEVELS > 4; with a folded p4d, asm-generic/pgtable-nop4d.h defines pgd_clear() as a no-op. All callers use pgd_clear() as a statement (mm/memory.c and friends), so use the do { } while (0) form, matching the paravirt definition in asm/paravirt.h. Found by running sparse 0.6.5-rc1 over mm/. No functional change. Signed-off-by: Jose A. Perez de Azpillaga Signed-off-by: Dave Hansen Link: https://patch.msgid.link/20260923212731.19599-1-azpijr@gmail.com --- arch/x86/include/asm/pgtable.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h index d5f4917c1edcba..153cb62327e4c1 100644 --- a/arch/x86/include/asm/pgtable.h +++ b/arch/x86/include/asm/pgtable.h @@ -66,7 +66,11 @@ extern pmdval_t early_pmd_flags; #ifndef __PAGETABLE_P4D_FOLDED #define set_pgd(pgdp, pgd) native_set_pgd(pgdp, pgd) -#define pgd_clear(pgd) (pgtable_l5_enabled() ? native_pgd_clear(pgd) : 0) +#define pgd_clear(pgdp) \ +do { \ + if (pgtable_l5_enabled()) \ + native_pgd_clear(pgdp); \ +} while (0) #endif #ifndef set_p4d From 6f114ae7b6006ca7cf72e3e3348d263bbfec4525 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Thu, 24 Sep 2026 07:09:57 -0500 Subject: [PATCH 0847/2286] tools/nolibc: fix verrx() and errx() message formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verrx() passes its va_list to warnx() instead of vwarnx(). warnx() is variadic, so it takes the va_list as its first and only argument, and the conversions in the format string print the va_list itself and then whatever is left in the following argument slots. On x86_64, for instance, errx(1, "int args: %d %d %d", 1, 2, 3); prints something like "int args: -1699009032 1 2", where the first number comes from the address of the va_list and changes from run to run, and errx(1, "%d %s", 1234, "foo"); crashes with SIGSEGV, as "%s" takes a leftover argument slot for the string. errx() goes through verrx(), so every message with arguments printed by either of them is wrong. Call vwarnx() from verrx(), as verr() calls vwarn(). Fixes: 9da0f529c089 ("tools/nolibc: add err.h") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260924120958.23698-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/err.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/err.h b/tools/include/nolibc/err.h index e22ae87a728920..61978d537ed8f8 100644 --- a/tools/include/nolibc/err.h +++ b/tools/include/nolibc/err.h @@ -60,7 +60,7 @@ void verr(int eval, const char *fmt, va_list args) static __attribute__((noreturn, unused)) void verrx(int eval, const char *fmt, va_list args) { - warnx(fmt, args); + vwarnx(fmt, args); exit(eval); } From b2ddc080570c9012359a786bf8ae5480c2911a6a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 16 Sep 2026 21:42:54 +0200 Subject: [PATCH 0848/2286] ACPI: fan: Use __free() to simplify AML error handling Introduce acpi_object_free for freeing union acpi_object objects allocated by AML and use it for simplifying AML error handling in the ACPI fan driver. While at it, update the driver to use consistent error values across all function using the union acpi_object data type. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Armin Wolf [ rjw: Put the new free definition under CONFIG_ACPI ] Link: https://patch.msgid.link/6029013.DvuYhMxLoT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/fan_core.c | 80 ++++++++++++++++------------------------- include/linux/acpi.h | 2 ++ 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 5ad65975fbb41f..3674f9613d40df 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -71,37 +71,29 @@ static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; - int ret = 0; status = acpi_evaluate_object(handle, "_FST", NULL, &buffer); if (ACPI_FAILURE(status)) - return -EIO; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj) return -ENODATA; - if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) { - ret = -EPROTO; - goto err; - } + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) + return -EPROTO; if (obj->package.elements[0].type != ACPI_TYPE_INTEGER || obj->package.elements[1].type != ACPI_TYPE_INTEGER || - obj->package.elements[2].type != ACPI_TYPE_INTEGER) { - ret = -EPROTO; - goto err; - } + obj->package.elements[2].type != ACPI_TYPE_INTEGER) + return -EPROTO; fst->revision = obj->package.elements[0].integer.value; fst->control = obj->package.elements[1].integer.value; fst->speed = obj->package.elements[2].integer.value; -err: - kfree(obj); - return ret; + return 0; } static int fan_get_state_acpi4(struct acpi_fan *fan, unsigned long *state) @@ -237,25 +229,22 @@ static int acpi_fan_get_fif(struct acpi_device *device) struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; u64 fields[4]; struct acpi_buffer fif = { sizeof(fields), fields }; - union acpi_object *obj; acpi_status status; status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); if (ACPI_FAILURE(status)) - return status; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE) { dev_err(&device->dev, "Invalid _FIF data\n"); - status = -EINVAL; - goto err; + return -ENODATA; } status = acpi_extract_package(obj, &format, &fif); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FIF element\n"); - status = -EINVAL; - goto err; + return -ENODATA; } fan->fif.revision = fields[0]; @@ -269,9 +258,8 @@ static int acpi_fan_get_fif(struct acpi_device *device) /* If step size > 9, change to 9 (by spec valid values 1-9) */ else if (fan->fif.step_size > 9) fan->fif.step_size = 9; -err: - kfree(obj); - return status; + + return 0; } static int acpi_fan_speed_cmp(const void *a, const void *b) @@ -285,27 +273,24 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) { struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; int i; status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); if (ACPI_FAILURE(status)) - return status; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { dev_err(&device->dev, "Invalid _FPS data\n"); - status = -EINVAL; - goto err; + return -ENODATA; } fan->fps_count = obj->package.count - 1; /* minus revision field */ fan->fps = devm_kcalloc(dev, fan->fps_count, sizeof(*fan->fps), GFP_KERNEL); - if (!fan->fps) { - status = -ENOMEM; - goto err; - } + if (!fan->fps) + return -ENOMEM; + for (i = 0; i < fan->fps_count; i++) { struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, name), @@ -314,7 +299,7 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) &format, &fps); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FPS element\n"); - goto err; + return -ENODATA; } } @@ -322,9 +307,7 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) sort(fan->fps, fan->fps_count, sizeof(*fan->fps), acpi_fan_speed_cmp, NULL); -err: - kfree(obj); - return status; + return 0; } static int acpi_fan_dsm_init(struct device *dev) @@ -338,8 +321,6 @@ static int acpi_fan_dsm_init(struct device *dev) }; struct acpi_fan *fan = dev_get_drvdata(dev); acpi_handle fan_handle = fan->adev->handle; - union acpi_object *obj; - int ret = 0; if (!acpi_check_dsm(fan_handle, &acpi_fan_microsoft_guid, 0, BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) | @@ -348,20 +329,19 @@ static int acpi_fan_dsm_init(struct device *dev) dev_info(dev, "Using Microsoft fan extensions\n"); - obj = acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, - ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy, - ACPI_TYPE_INTEGER); + union acpi_object *obj __free(acpi_object_free) = + acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, + ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, + &dummy, ACPI_TYPE_INTEGER); if (!obj) - return -EIO; + return -ENXIO; if (obj->integer.value > U32_MAX) - ret = -EOVERFLOW; - else - fan->fan_trip_granularity = obj->integer.value; + return -EOVERFLOW; - kfree(obj); + fan->fan_trip_granularity = obj->integer.value; - return ret; + return 0; } static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower) @@ -392,7 +372,7 @@ static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower obj = acpi_evaluate_dsm(fan->adev->handle, &acpi_fan_microsoft_guid, 0, ACPI_FAN_DSM_SET_TRIP_POINTS, &in); - kfree(obj); + ACPI_FREE(obj); return 0; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac81209473..99d7aa7afaa162 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -29,6 +29,8 @@ struct irq_domain_ops; #ifdef CONFIG_ACPI +DEFINE_FREE(acpi_object_free, union acpi_object *, if (_T) ACPI_FREE(_T)); + #include #include #include From e6b4f0bac981dcb5c1fd682be0d7a8d69892f5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 17 Sep 2026 16:13:16 +0200 Subject: [PATCH 0849/2286] selinux: constify avc function parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Constify read-only decision structs to ease reasoning where decisions are actually modified, and where only processed. Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley [PM: fixed line length issues] Signed-off-by: Paul Moore --- security/selinux/avc.c | 34 ++++++++++++++++++---------------- security/selinux/include/avc.h | 7 ++++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index a9401d6c2e5f1c..d8c9dd6411d787 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -190,8 +190,8 @@ avc_xperms_decision_lookup(u8 driver, u8 base_perm, } static inline unsigned int -avc_xperms_has_perm(struct extended_perms_decision *xpd, - u8 perm, u8 which) +avc_xperms_has_perm(const struct extended_perms_decision *xpd, + u8 perm, u8 which) { unsigned int rc = 0; @@ -247,7 +247,7 @@ static void avc_xperms_free(struct avc_xperms_node *xp_node) } static void avc_copy_xperms_decision(struct extended_perms_decision *dest, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { dest->base_perm = src->base_perm; dest->driver = src->driver; @@ -269,7 +269,7 @@ static void avc_copy_xperms_decision(struct extended_perms_decision *dest, */ static inline void avc_quick_copy_xperms_decision(u8 perm, struct extended_perms_decision *dest, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { /* * compute index of the u32 of the 256 bits (8 u32s) that contain this @@ -323,7 +323,7 @@ static struct avc_xperms_decision_node } static int avc_add_xperms_decision(struct avc_node *node, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { struct avc_xperms_decision_node *dest_xpd; @@ -348,7 +348,7 @@ static struct avc_xperms_node *avc_xperms_alloc(void) } static int avc_xperms_populate(struct avc_node *node, - struct avc_xperms_node *src) + const struct avc_xperms_node *src) { struct avc_xperms_node *dest; struct avc_xperms_decision_node *dest_xpd; @@ -381,11 +381,11 @@ static int avc_xperms_populate(struct avc_node *node, } static inline u32 avc_xperms_audit_required(u32 requested, - struct av_decision *avd, - struct extended_perms_decision *xpd, - u8 perm, - int result, - u32 *deniedp) + const struct av_decision *avd, + const struct extended_perms_decision *xpd, + u8 perm, + int result, + u32 *deniedp) { u32 denied, audited; @@ -411,8 +411,8 @@ static inline u32 avc_xperms_audit_required(u32 requested, } static inline int avc_xperms_audit(u32 ssid, u32 tsid, u16 tclass, - u32 requested, struct av_decision *avd, - struct extended_perms_decision *xpd, + u32 requested, const struct av_decision *avd, + const struct extended_perms_decision *xpd, u8 perm, int result, struct common_audit_data *ad) { @@ -509,7 +509,8 @@ static struct avc_node *avc_alloc_node(void) return node; } -static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, + u16 tclass, const struct av_decision *avd) { node->ae.ssid = ssid; node->ae.tsid = tsid; @@ -603,7 +604,8 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) * the access vectors into a cache entry. */ static void avc_insert(u32 ssid, u32 tsid, u16 tclass, - struct av_decision *avd, struct avc_xperms_node *xp_node) + const struct av_decision *avd, + const struct avc_xperms_node *xp_node) { struct avc_node *pos, *node = NULL; u32 hvalue; @@ -828,7 +830,7 @@ int __init avc_add_callback(int (*callback)(u32 event), u32 events) */ static int avc_update_node(u32 event, u32 perms, u8 driver, u8 base_perm, u8 xperm, u32 ssid, u32 tsid, u16 tclass, u32 seqno, - struct extended_perms_decision *xpd, u32 flags) + const struct extended_perms_decision *xpd, u32 flags) { u32 hvalue; int rc = 0; diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 01b5167fee1a89..87027a934a118d 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -61,8 +61,9 @@ struct selinux_audit_data { void __init avc_init(void); -static inline u32 avc_audit_required(u32 requested, struct av_decision *avd, - int result, u32 auditdeny, u32 *deniedp) +static inline u32 avc_audit_required(u32 requested, + const struct av_decision *avd, int result, + u32 auditdeny, u32 *deniedp) { u32 denied, audited; @@ -121,7 +122,7 @@ int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, u32 audited, * before calling the auditing code. */ static inline int avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, - struct av_decision *avd, int result, + const struct av_decision *avd, int result, struct common_audit_data *a) { u32 audited, denied; From 508d1fcc2d099e1e8d9b6fc14cb150adb8c4b092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 17 Sep 2026 16:20:38 +0200 Subject: [PATCH 0850/2286] selinux: reject a policy that both rejects and allows unknown classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handle-unknown is a single three-valued choice - deny, reject or allow - but the policydb header spells it as two independent bits and policydb_read() takes any combination of them. libsepol writes exactly one of the three values (POLICYDB_CONFIG_UNKNOWN_MASK & p->handle_unknown), so no policy built by the reference toolchain carries this combination. Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 4342258cb148fd..7eeaf7076c52ec 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -2844,7 +2844,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; } - if ((le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS)) { + val = le32_to_cpu(buf[1]); + if (val & POLICYDB_CONFIG_MLS) { p->mls_enabled = 1; rc = -EINVAL; @@ -2855,8 +2856,13 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; } } - p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); - p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + rc = -EINVAL; + if ((val & (REJECT_UNKNOWN | ALLOW_UNKNOWN)) == (REJECT_UNKNOWN | ALLOW_UNKNOWN)) { + pr_err("SELinux: policydb configuration both rejects and allows unknown classes and permissions\n"); + goto bad; + } + p->reject_unknown = !!(val & REJECT_UNKNOWN); + p->allow_unknown = !!(val & ALLOW_UNKNOWN); if (p->policyvers >= POLICYDB_VERSION_POLCAP) { rc = ebitmap_read(&p->policycaps, fp); From 641bd5750517d2e5c128c566669c190857260693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 3 Sep 2026 13:35:38 +0200 Subject: [PATCH 0851/2286] selinux: reject duplicate symbol values during policy load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The symbol index functions assign unconditionally, so two symbols that share a value silently overwrite each other and leave another value with no entry. Reject a value whose slot is already populated, for commons, classes, roles, types, users, sensitivities and categories; booleans already have their own "declared but not defined" check. Signed-off-by: Christian Göttsche Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 7eeaf7076c52ec..6119df551f2308 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -560,6 +560,9 @@ static int common_index(void *key, void *datum, void *datap) if (!comdatum->value || comdatum->value > p->p_commons.nprim) return -EINVAL; + if (p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key; return 0; @@ -575,6 +578,9 @@ static int class_index(void *key, void *datum, void *datap) if (!cladatum->value || cladatum->value > p->p_classes.nprim) return -EINVAL; + if (p->class_val_to_struct[cladatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key; p->class_val_to_struct[cladatum->value - 1] = cladatum; return 0; @@ -591,6 +597,9 @@ static int role_index(void *key, void *datum, void *datap) role->bounds > p->p_roles.nprim) return -EINVAL; + if (p->role_val_to_struct[role->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_ROLES][role->value - 1] = key; p->role_val_to_struct[role->value - 1] = role; return 0; @@ -613,6 +622,8 @@ static int type_index(void *key, void *datum, void *datap) } if (typdatum->primary) { + if (p->type_val_to_struct[typdatum->value - 1]) + return -EINVAL; p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key; p->type_val_to_struct[typdatum->value - 1] = typdatum; } @@ -631,6 +642,9 @@ static int user_index(void *key, void *datum, void *datap) usrdatum->bounds > p->p_users.nprim) return -EINVAL; + if (p->user_val_to_struct[usrdatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_USERS][usrdatum->value - 1] = key; p->user_val_to_struct[usrdatum->value - 1] = usrdatum; return 0; @@ -647,8 +661,11 @@ static int sens_index(void *key, void *datum, void *datap) if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim) return -EINVAL; - if (!levdatum->isalias) + if (!levdatum->isalias) { + if (p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1]) + return -EINVAL; p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1] = key; + } return 0; } @@ -664,8 +681,11 @@ static int cat_index(void *key, void *datum, void *datap) if (!catdatum->value || catdatum->value > p->p_cats.nprim) return -EINVAL; - if (!catdatum->isalias) + if (!catdatum->isalias) { + if (p->sym_val_to_name[SYM_CATS][catdatum->value - 1]) + return -EINVAL; p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key; + } return 0; } From 0037abbab6fb9cb18539b9ee2b59d9c3056d3bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 3 Sep 2026 13:35:34 +0200 Subject: [PATCH 0852/2286] selinux: bounds-check filename transition source types at load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit filename_trans_read_helper() validates the target type, target class and each datum's otype, but the per-datum stypes ebitmap is read without any bound on its set bits. Reject a stypes bitmap whose highest set bit is not a defined type. Signed-off-by: Christian Göttsche Reviewed-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 6119df551f2308..13d88e47db3c9f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -2367,6 +2367,10 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp if (rc) goto out; + rc = -EINVAL; + if (ebitmap_get_highest_set_bit(&datum->stypes) >= p->p_types.nprim) + goto out; + rc = next_entry(buf, fp, sizeof(u32)); if (rc) goto out; From 7e3e23305d13db5fa7cfaa2dbc9ab430218025c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 17 Sep 2026 16:13:17 +0200 Subject: [PATCH 0853/2286] selinux: check neveraudit in avc_xperms_audit_required() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avc_audit_required() returns early for a neveraudit domain, while avc_xperms_audit_required() instead relies on security_compute_av() (and security_compute_av_user()) having already zeroed avd->auditallow and avd->auditdeny for such a domain. That leaves its "else if (result)" branch as the only way left to audit a neveraudit domain, and makes the two helpers read differently for the same rule. Add the same early return to avc_xperms_audit_required(), so both audit gates state the neveraudit rule directly and the extended permission audit path no longer depends on the auditallow/auditdeny mask having been zeroed elsewhere. No behavior changes today: avc_has_extended_perms() only reaches the "result" branch once the identically computed "denied" is already nonzero, so that branch was already unreachable in practice. Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/avc.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/selinux/avc.c b/security/selinux/avc.c index d8c9dd6411d787..dd8905648b6ad5 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -389,6 +389,9 @@ static inline u32 avc_xperms_audit_required(u32 requested, { u32 denied, audited; + if (avd->flags & AVD_FLAGS_NEVERAUDIT) + return 0; + denied = requested & ~avd->allowed; if (unlikely(denied)) { audited = denied & avd->auditdeny; From fd00a46e36a12c5279d3d163f0d0c8f83d12dc09 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Sun, 20 Sep 2026 15:07:50 +0800 Subject: [PATCH 0854/2286] kthread: Annotate lockless to_kthread() flags access do_exit() calls synchronize_group_exit(), which modifies task->flags under sighand->siglock: tsk->flags |= PF_POSTCOREDUMP; The sanity check in to_kthread() reads the same flags word without holding that lock: WARN_ON(!(k->flags & PF_KTHREAD)); PF_KTHREAD is set once when the task becomes a kthread and is never cleared for the lifetime of the task. Since PF_KTHREAD is immutable, the check does not depend on synchronization with updates to other bits in task->flags. Use READ_ONCE() to annotate the intentional lockless access. Signed-off-by: Kunwu Chan Link: https://patch.msgid.link/20260920070750.2871923-1-kunwu.chan@gmail.com Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- kernel/kthread.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/kthread.c b/kernel/kthread.c index 63beb59b7a3d18..af1675d896602c 100644 --- a/kernel/kthread.c +++ b/kernel/kthread.c @@ -81,7 +81,7 @@ enum KTHREAD_BITS { static inline struct kthread *to_kthread(struct task_struct *k) { - WARN_ON(!(k->flags & PF_KTHREAD)); + WARN_ON(!(READ_ONCE(k->flags) & PF_KTHREAD)); return k->worker_private; } From df935e26ca9a8fd4ac7431b73348938250f80c6e Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Tue, 15 Sep 2026 17:45:16 -0700 Subject: [PATCH 0855/2286] perf/x86/amd/uncore: Turn amd_uncore_ctx events into a flexible array The events pointer was kzalloc_node()'d immediately after the parent struct allocation, with the count (pmu->num_counters) trivially available beforehand. Move events to the struct tail as a flexible array member and fold the two allocations into a single kzalloc_node() with struct_size(), dropping the separate kfree(ctx->events) in the free path. Assisted-by: Claude:Opus-4.7 Signed-off-by: Rosen Penev Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sandipan Das Link: https://patch.msgid.link/20260916004516.268192-1-rosenp@gmail.com --- arch/x86/events/amd/uncore.c | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index 7181973b5b1273..74b9c92d517a77 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -39,11 +39,11 @@ static int pmu_version; struct amd_uncore_ctx { int refcnt; int cpu; - struct perf_event **events; unsigned long active_mask[BITS_TO_LONGS(NUM_COUNTERS_MAX)]; int nr_active; struct hrtimer hrtimer; u64 hrtimer_duration; + struct perf_event *events[]; }; struct amd_uncore_pmu { @@ -519,10 +519,8 @@ static void amd_uncore_ctx_free(struct amd_uncore *uncore, unsigned int cpu) if (cpu == ctx->cpu) cpumask_clear_cpu(cpu, &pmu->active_mask); - if (!--ctx->refcnt) { - kfree(ctx->events); + if (!--ctx->refcnt) kfree(ctx); - } *per_cpu_ptr(pmu->ctx, cpu) = NULL; } @@ -567,18 +565,11 @@ static int amd_uncore_ctx_init(struct amd_uncore *uncore, unsigned int cpu) /* Allocate context if sibling does not exist */ if (!curr) { node = cpu_to_node(cpu); - curr = kzalloc_node(sizeof(*curr), GFP_KERNEL, node); + curr = kzalloc_node(struct_size(curr, events, pmu->num_counters), GFP_KERNEL, node); if (!curr) goto fail; curr->cpu = cpu; - curr->events = kzalloc_node(sizeof(*curr->events) * - pmu->num_counters, - GFP_KERNEL, node); - if (!curr->events) { - kfree(curr); - goto fail; - } amd_uncore_init_hrtimer(curr); curr->hrtimer_duration = (u64)update_interval * NSEC_PER_MSEC; From 52a6457f59adf70d624ef1b1bed8d55cb7b73af6 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 4 Sep 2026 16:17:00 +0200 Subject: [PATCH 0856/2286] perf/x86/intel: Annotate x86_pmu::hybrid_pmu with __counted_by_ptr Add __counted_by_ptr() to x86_pmu::hybrid_pmu to improve access bounds checking via CONFIG_UBSAN_BOUNDS and CONFIG_FORTIFY_SOURCE. Signed-off-by: Thorsten Blum Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Ian Rogers Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260904141659.224248-2-blum@kernel.org --- arch/x86/events/perf_event.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 6eb1bf4ca4423a..5deca1a95d3011 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1245,7 +1245,7 @@ struct x86_pmu { * unique capabilities. */ int num_hybrid_pmus; - struct x86_hybrid_pmu *hybrid_pmu; + struct x86_hybrid_pmu *hybrid_pmu __counted_by_ptr(num_hybrid_pmus); enum intel_cpu_type (*get_hybrid_cpu_type) (void); }; From b7b84fff2bd630b593ee47a733381e3886aac1a7 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:13 -0700 Subject: [PATCH 0857/2286] perf/x86/intel: Invert names of intel_ctrl_{guest,host}_mask Rename intel_ctrl_{guest,host}_mask to intel_ctrl_exclude_{host,guest}_mask to more accurately capture what they actually track. Specifically, an event that is excluded from the guest is NOT guaranteed to count in the host, and vice versa, as it legal (albeit bizarre) to configure an event to exclude both the host and the guest, i.e. to not count at all. Subjectively (though anyone who disagrees is wrong), aligning with perf_event_attr.exclude_{guest,host} also makes all related code much easier to follow. No functional change intended. Suggested-by: Jim Mattson Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-6-seanjc@google.com --- arch/x86/events/intel/core.c | 22 +++++++++++----------- arch/x86/events/intel/lbr.c | 2 +- arch/x86/events/perf_event.h | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index a73a3556959e06..076518719d29f1 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -2797,7 +2797,7 @@ static void __intel_pmu_enable_all(int added, bool pmi) } wrmsrq(MSR_CORE_PERF_GLOBAL_CTRL, - intel_ctrl & ~cpuc->intel_ctrl_guest_mask); + intel_ctrl & ~cpuc->intel_ctrl_exclude_host_mask); if (test_bit(INTEL_PMC_IDX_FIXED_BTS, cpuc->active_mask)) { struct perf_event *event = @@ -2995,9 +2995,9 @@ static inline void intel_set_masks(struct perf_event *event, int idx) struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); if (event->attr.exclude_host) - __set_bit(idx, (unsigned long *)&cpuc->intel_ctrl_guest_mask); + __set_bit(idx, (unsigned long *)&cpuc->intel_ctrl_exclude_host_mask); if (event->attr.exclude_guest) - __set_bit(idx, (unsigned long *)&cpuc->intel_ctrl_host_mask); + __set_bit(idx, (unsigned long *)&cpuc->intel_ctrl_exclude_guest_mask); if (event_is_checkpointed(event)) __set_bit(idx, (unsigned long *)&cpuc->intel_cp_status); } @@ -3006,8 +3006,8 @@ static inline void intel_clear_masks(struct perf_event *event, int idx) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); - __clear_bit(idx, (unsigned long *)&cpuc->intel_ctrl_guest_mask); - __clear_bit(idx, (unsigned long *)&cpuc->intel_ctrl_host_mask); + __clear_bit(idx, (unsigned long *)&cpuc->intel_ctrl_exclude_host_mask); + __clear_bit(idx, (unsigned long *)&cpuc->intel_ctrl_exclude_guest_mask); __clear_bit(idx, (unsigned long *)&cpuc->intel_cp_status); } @@ -3789,7 +3789,7 @@ static int x86_pmu_handle_guest_pebs(struct pt_regs *regs, struct perf_sample_data *data) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); - u64 guest_pebs_idxs = cpuc->pebs_enabled & ~cpuc->intel_ctrl_host_mask; + u64 guest_pebs_idxs = cpuc->pebs_enabled & ~cpuc->intel_ctrl_exclude_guest_mask; struct perf_event *event = NULL; int bit; @@ -5349,8 +5349,8 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) global_ctrl = (*nr)++; arr[global_ctrl] = (struct perf_guest_switch_msr){ .msr = MSR_CORE_PERF_GLOBAL_CTRL, - .host = intel_ctrl & ~cpuc->intel_ctrl_guest_mask, - .guest = intel_ctrl & ~cpuc->intel_ctrl_host_mask & ~pebs_mask, + .host = intel_ctrl & ~cpuc->intel_ctrl_exclude_host_mask, + .guest = intel_ctrl & ~cpuc->intel_ctrl_exclude_guest_mask & ~pebs_mask, }; if (!x86_pmu.ds_pebs) @@ -5387,8 +5387,8 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) * in the guest, and (d) _are_ excluded from counting in the host. */ guest_pebs_mask = pebs_mask & intel_ctrl & kvm_pmu->pebs_enable & - ~cpuc->intel_ctrl_host_mask & - cpuc->intel_ctrl_guest_mask; + ~cpuc->intel_ctrl_exclude_guest_mask & + cpuc->intel_ctrl_exclude_host_mask; /* * Disable counters where the guest PMC is different than the host PMC @@ -5404,7 +5404,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) * What exactly goes wrong if guest and host are using PEBS is * unknown. */ - if (pebs_mask & ~cpuc->intel_ctrl_guest_mask) + if (pebs_mask & ~cpuc->intel_ctrl_exclude_host_mask) guest_pebs_mask = 0; /* diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c index 22e2a06d5786cf..0b446d8912e4eb 100644 --- a/arch/x86/events/intel/lbr.c +++ b/arch/x86/events/intel/lbr.c @@ -714,7 +714,7 @@ static inline bool vlbr_exclude_host(void) struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); return test_bit(INTEL_PMC_IDX_FIXED_VLBR, - (unsigned long *)&cpuc->intel_ctrl_guest_mask); + (unsigned long *)&cpuc->intel_ctrl_exclude_host_mask); } void intel_pmu_lbr_enable_all(bool pmi) diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 5deca1a95d3011..7c3a84e08e0412 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -540,8 +540,8 @@ struct cpu_hw_events { /* * Intel host/guest exclude bits */ - u64 intel_ctrl_guest_mask; - u64 intel_ctrl_host_mask; + u64 intel_ctrl_exclude_host_mask; + u64 intel_ctrl_exclude_guest_mask; struct perf_guest_switch_msr guest_switch_msrs[X86_PMC_IDX_MAX]; /* From a634e4536ec22a17e2391fd5ed38c06ad99cbf99 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:14 -0700 Subject: [PATCH 0858/2286] perf/x86: KVM: Have perf define a dedicated struct for getting guest PEBS data Have perf define a struct for getting guest PEBS data from KVM instead of poking into the kvm_pmu structure. Passing in an entire "struct kvm_pmu" _as an opaque pointer_ to get at four fields is silly, especially since one of the fields exists purely to convey information to perf, i.e. isn't used by KVM. Perf should also own its APIs, i.e. define what fields/data it needs, not rely on KVM to throw fields into data structures that effectively hold KVM-internal state. Opportunistically rephrase the comment about cross-mapped counters to explain *why* PEBS needs to be disabled. Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Reviewed-by: Jim Mattson Link: https://patch.msgid.link/20260921191418.950933-7-seanjc@google.com --- arch/x86/events/core.c | 5 +++-- arch/x86/events/intel/core.c | 16 ++++++++-------- arch/x86/events/perf_event.h | 3 ++- arch/x86/include/asm/kvm_host.h | 9 --------- arch/x86/include/asm/perf_event.h | 12 ++++++++++-- arch/x86/kvm/vmx/pmu_intel.c | 17 ++++++++++++++--- arch/x86/kvm/vmx/vmx.c | 11 ++++++++--- arch/x86/kvm/vmx/vmx.h | 2 +- 8 files changed, 46 insertions(+), 29 deletions(-) diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index ef9b8df2e1f717..9b1a9032e36d50 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -862,9 +862,10 @@ void x86_pmu_disable_all(void) } } -struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, void *data) +struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, + struct x86_guest_pebs *guest_pebs) { - return static_call(x86_pmu_guest_get_msrs)(nr, data); + return static_call(x86_pmu_guest_get_msrs)(nr, guest_pebs); } EXPORT_SYMBOL_FOR_KVM(perf_guest_get_msrs); diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 076518719d29f1..87274b3bf5038c 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -5328,11 +5327,11 @@ static int intel_pmu_hw_config(struct perf_event *event) * when it uses {RD,WR}MSR, which should be handled by the KVM context, * specifically in the intel_pmu_{get,set}_msr(). */ -static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) +static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, + struct x86_guest_pebs *guest_pebs) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_guest_switch_msr *arr = cpuc->guest_switch_msrs; - struct kvm_pmu *kvm_pmu = (struct kvm_pmu *)data; u64 intel_ctrl = hybrid(cpuc->pmu, intel_ctrl); u64 pebs_mask = cpuc->pebs_enabled & x86_pmu.pebs_capable; u64 guest_pebs_mask; @@ -5386,7 +5385,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) * the guest wants to use for PEBS, (c) are not excluded from counting * in the guest, and (d) _are_ excluded from counting in the host. */ - guest_pebs_mask = pebs_mask & intel_ctrl & kvm_pmu->pebs_enable & + guest_pebs_mask = pebs_mask & intel_ctrl & guest_pebs->enable & ~cpuc->intel_ctrl_exclude_guest_mask & cpuc->intel_ctrl_exclude_host_mask; @@ -5396,7 +5395,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the * wrong counter(s). */ - guest_pebs_mask &= ~kvm_pmu->host_cross_mapped_mask; + guest_pebs_mask &= ~guest_pebs->cross_mapped_mask; /* * FIXME: Allow guest and host usage of PEBS events to co-exist instead @@ -5415,14 +5414,14 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) arr[(*nr)++] = (struct perf_guest_switch_msr){ .msr = MSR_IA32_DS_AREA, .host = (unsigned long)cpuc->ds, - .guest = guest_pebs_mask ? kvm_pmu->ds_area : (unsigned long)cpuc->ds, + .guest = guest_pebs_mask ? guest_pebs->ds_area : (unsigned long)cpuc->ds, }; if (x86_pmu.intel_cap.pebs_baseline) { arr[(*nr)++] = (struct perf_guest_switch_msr){ .msr = MSR_PEBS_DATA_CFG, .host = cpuc->active_pebs_data_cfg, - .guest = guest_pebs_mask ? kvm_pmu->pebs_data_cfg : + .guest = guest_pebs_mask ? guest_pebs->data_cfg : cpuc->active_pebs_data_cfg, }; } @@ -5438,7 +5437,8 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, void *data) return arr; } -static struct perf_guest_switch_msr *core_guest_get_msrs(int *nr, void *data) +static struct perf_guest_switch_msr *core_guest_get_msrs(int *nr, + struct x86_guest_pebs *guest_pebs) { struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_guest_switch_msr *arr = cpuc->guest_switch_msrs; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 7c3a84e08e0412..e274802ef06250 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1226,7 +1226,8 @@ struct x86_pmu { /* * Intel host/guest support (KVM) */ - struct perf_guest_switch_msr *(*guest_get_msrs)(int *nr, void *data); + struct perf_guest_switch_msr *(*guest_get_msrs)(int *nr, + struct x86_guest_pebs *guest_pebs); /* * Check period value for PERF_EVENT_IOC_PERIOD ioctl. diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h index 683bb8bf43a945..5e8aecfc795ff1 100644 --- a/arch/x86/include/asm/kvm_host.h +++ b/arch/x86/include/asm/kvm_host.h @@ -608,15 +608,6 @@ struct kvm_pmu { u64 pebs_data_cfg; u64 pebs_data_cfg_rsvd; - /* - * If a guest counter is cross-mapped to host counter with different - * index, its PEBS capability will be temporarily disabled. - * - * The user should make sure that this mask is updated - * after disabling interrupts and before perf_guest_get_msrs(); - */ - u64 host_cross_mapped_mask; - /* * The gate to release perf_events not marked in * pmc_in_use only once in a vcpu time slice. diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 12f7db8c57b437..27f9c5bee7b0c7 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -819,11 +819,19 @@ extern void perf_load_guest_lvtpc(u32 guest_lvtpc); extern void perf_put_guest_lvtpc(void); #endif +struct x86_guest_pebs { + u64 enable; + u64 ds_area; + u64 data_cfg; + u64 cross_mapped_mask; +}; #if defined(CONFIG_PERF_EVENTS) && defined(CONFIG_CPU_SUP_INTEL) -extern struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, void *data); +extern struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, + struct x86_guest_pebs *guest_pebs); extern void x86_perf_get_lbr(struct x86_pmu_lbr *lbr); #else -struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, void *data); +struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, + struct x86_guest_pebs *guest_pebs); static inline void x86_perf_get_lbr(struct x86_pmu_lbr *lbr) { memset(lbr, 0, sizeof(*lbr)); diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index bfa8612fb4508f..a21e146ad4f2e3 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -747,11 +747,21 @@ static void intel_pmu_cleanup(struct kvm_vcpu *vcpu) intel_pmu_release_guest_lbr_event(vcpu); } -void intel_pmu_cross_mapped_check(struct kvm_pmu *pmu) +u64 intel_pmu_get_cross_mapped_mask(struct kvm_pmu *pmu) { - struct kvm_pmc *pmc = NULL; + u64 host_cross_mapped_mask; + struct kvm_pmc *pmc; int bit, hw_idx; + /* + * Provide a mask of counters that are cross-mapped between the guest + * and the host, i.e. where a guest PMC is mapped to a host PMC with a + * different index. PEBS records hold a PERF_GLOBAL_STATUS snapshot, + * and so PEBS-enabled counters need to hold the correct index so as + * not to confuse the guest. + */ + host_cross_mapped_mask = 0; + kvm_for_each_pmc(pmu, pmc, bit, (unsigned long *)&pmu->global_ctrl) { if (!pmc_is_locally_enabled(pmc) || !pmc_is_globally_enabled(pmc) || !pmc->perf_event) @@ -763,8 +773,9 @@ void intel_pmu_cross_mapped_check(struct kvm_pmu *pmu) */ hw_idx = pmc->perf_event->hw.idx; if (hw_idx != pmc->idx && hw_idx > -1) - pmu->host_cross_mapped_mask |= BIT_ULL(hw_idx); + host_cross_mapped_mask |= BIT_ULL(hw_idx); } + return host_cross_mapped_mask; } static bool intel_pmu_is_mediated_pmu_supported(struct x86_pmu_capability *host_pmu) diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 612ab07d4100fe..b097db296792f4 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -7363,12 +7363,17 @@ static void atomic_switch_perf_msrs(struct vcpu_vmx *vmx) if (kvm_vcpu_has_mediated_pmu(&vmx->vcpu)) return; - pmu->host_cross_mapped_mask = 0; + struct x86_guest_pebs guest_pebs = { + .enable = pmu->pebs_enable, + .ds_area = pmu->ds_area, + .data_cfg = pmu->pebs_data_cfg, + }; + if (pmu->pebs_enable & pmu->global_ctrl) - intel_pmu_cross_mapped_check(pmu); + guest_pebs.cross_mapped_mask = intel_pmu_get_cross_mapped_mask(pmu); /* Note, nr_msrs may be garbage if perf_guest_get_msrs() returns NULL. */ - msrs = perf_guest_get_msrs(&nr_msrs, (void *)pmu); + msrs = perf_guest_get_msrs(&nr_msrs, &guest_pebs); if (!msrs) return; diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h index dc8517f15bc463..5c4e2cf6256c10 100644 --- a/arch/x86/kvm/vmx/vmx.h +++ b/arch/x86/kvm/vmx/vmx.h @@ -664,7 +664,7 @@ static __always_inline struct vcpu_vmx *to_vmx(struct kvm_vcpu *vcpu) return container_of(vcpu, struct vcpu_vmx, vcpu); } -void intel_pmu_cross_mapped_check(struct kvm_pmu *pmu); +u64 intel_pmu_get_cross_mapped_mask(struct kvm_pmu *pmu); int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu); void vmx_passthrough_lbr_msrs(struct kvm_vcpu *vcpu); From 31f7cf337ce717666ce29fbd793f06435eb48408 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:15 -0700 Subject: [PATCH 0859/2286] perf/x86/intel: KVM: Handle cross-mapped PEBS PMCs entirely within KVM Now that perf operates on a KVM-provided snapshot of PMU state, handled cross-mapped PEBS counters entirely in KVM by clearing unusable counters from the to-be-enabled mask instead of foisting the work on perf. No functional change intended. Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-8-seanjc@google.com --- arch/x86/events/intel/core.c | 8 -------- arch/x86/include/asm/perf_event.h | 1 - arch/x86/kvm/vmx/vmx.c | 10 ++++++++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 87274b3bf5038c..1bc96b9fe0e4c0 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5389,14 +5389,6 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, ~cpuc->intel_ctrl_exclude_guest_mask & cpuc->intel_ctrl_exclude_host_mask; - /* - * Disable counters where the guest PMC is different than the host PMC - * being used on behalf of the guest, as the PEBS record includes - * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the - * wrong counter(s). - */ - guest_pebs_mask &= ~guest_pebs->cross_mapped_mask; - /* * FIXME: Allow guest and host usage of PEBS events to co-exist instead * of disabling guest PEBS entirely if the host is using PEBS. diff --git a/arch/x86/include/asm/perf_event.h b/arch/x86/include/asm/perf_event.h index 27f9c5bee7b0c7..5c92d43bbef831 100644 --- a/arch/x86/include/asm/perf_event.h +++ b/arch/x86/include/asm/perf_event.h @@ -823,7 +823,6 @@ struct x86_guest_pebs { u64 enable; u64 ds_area; u64 data_cfg; - u64 cross_mapped_mask; }; #if defined(CONFIG_PERF_EVENTS) && defined(CONFIG_CPU_SUP_INTEL) extern struct perf_guest_switch_msr *perf_guest_get_msrs(int *nr, diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index b097db296792f4..8f7d0620f44c71 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -7369,8 +7369,14 @@ static void atomic_switch_perf_msrs(struct vcpu_vmx *vmx) .data_cfg = pmu->pebs_data_cfg, }; - if (pmu->pebs_enable & pmu->global_ctrl) - guest_pebs.cross_mapped_mask = intel_pmu_get_cross_mapped_mask(pmu); + /* + * Disable counters where the guest PMC is different than the host PMC + * being used on behalf of the guest, as the PEBS record includes + * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the + * wrong counter(s). + */ + if (guest_pebs.enable & pmu->global_ctrl) + guest_pebs.enable &= ~intel_pmu_get_cross_mapped_mask(pmu); /* Note, nr_msrs may be garbage if perf_guest_get_msrs() returns NULL. */ msrs = perf_guest_get_msrs(&nr_msrs, &guest_pebs); From 57c76478333012159b5cbec89527f925011cdefd Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:16 -0700 Subject: [PATCH 0860/2286] KVM: VMX: Drop a redundant pmu->global_ctrl check when processing pebs_enable Drop a redundant check that a PMC is globally enabled when looking for PEBS counters that are cross-mapped between the guest and the host. The for-loop explicitly iterates over pmu->global_ctrl, and since PEBS requires PMU v2+, kvm_pmu_has_perf_global_ctrl() must be true, and thus pmc_is_globally_enabled() is simply checking that the bit is set in pmu->global_ctrl. No functional change intended. Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-9-seanjc@google.com --- arch/x86/kvm/vmx/pmu_intel.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index a21e146ad4f2e3..040b8e80cf86a9 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -763,8 +763,7 @@ u64 intel_pmu_get_cross_mapped_mask(struct kvm_pmu *pmu) host_cross_mapped_mask = 0; kvm_for_each_pmc(pmu, pmc, bit, (unsigned long *)&pmu->global_ctrl) { - if (!pmc_is_locally_enabled(pmc) || - !pmc_is_globally_enabled(pmc) || !pmc->perf_event) + if (!pmc_is_locally_enabled(pmc) || !pmc->perf_event) continue; /* From 193ef44e32619550478fce9d0b91edc0046ed8c3 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:17 -0700 Subject: [PATCH 0861/2286] KVM: VMX: Only tell perf to enable PEBS counters for fully enabled PMCs When passing the guest's requested PEBS_ENABLE (or rather, KVM's version of PEBS_ENABLE on behalf of the guest), omit counters that are locally disable and/or don't have a perf event (due to contention), in addition to omitting counters that are cross-mapped in the host. In practice, this should be a nop as perf will already have disabled the associated counter, i.e. cpuc->pebs_enabled should have been cleared, but paranoia is cheap, and the existing code _looks_ wrong. Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-10-seanjc@google.com --- arch/x86/kvm/vmx/pmu_intel.c | 30 ++++++++++++++++-------------- arch/x86/kvm/vmx/vmx.c | 11 +---------- arch/x86/kvm/vmx/vmx.h | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c index 040b8e80cf86a9..472cf51298b438 100644 --- a/arch/x86/kvm/vmx/pmu_intel.c +++ b/arch/x86/kvm/vmx/pmu_intel.c @@ -747,34 +747,36 @@ static void intel_pmu_cleanup(struct kvm_vcpu *vcpu) intel_pmu_release_guest_lbr_event(vcpu); } -u64 intel_pmu_get_cross_mapped_mask(struct kvm_pmu *pmu) +u64 __intel_pmu_compute_pebs_enable(struct kvm_pmu *pmu) { - u64 host_cross_mapped_mask; + u64 guest_pebs_enable = pmu->pebs_enable & pmu->global_ctrl; + u64 pebs_enable = 0; struct kvm_pmc *pmc; int bit, hw_idx; /* - * Provide a mask of counters that are cross-mapped between the guest - * and the host, i.e. where a guest PMC is mapped to a host PMC with a - * different index. PEBS records hold a PERF_GLOBAL_STATUS snapshot, - * and so PEBS-enabled counters need to hold the correct index so as - * not to confuse the guest. + * Omit counters that are locally disabled, don't have a perf event, or + * ended up with a perf event that is using a different counter than + * the guest, i.e. where the guest PMC is different than the host PMC + * being used on behalf of the guest. PEBS records include + * PERF_GLOBAL_STATUS, and so using a counter with a different index + * means the guest will see overflow status for the wrong counter(s). */ - host_cross_mapped_mask = 0; - - kvm_for_each_pmc(pmu, pmc, bit, (unsigned long *)&pmu->global_ctrl) { + kvm_for_each_pmc(pmu, pmc, bit, (unsigned long *)&guest_pebs_enable) { if (!pmc_is_locally_enabled(pmc) || !pmc->perf_event) continue; /* - * A negative index indicates the event isn't mapped to a + * Note, a negative index indicates the event isn't mapped to a * physical counter in the host, e.g. due to contention. */ hw_idx = pmc->perf_event->hw.idx; - if (hw_idx != pmc->idx && hw_idx > -1) - host_cross_mapped_mask |= BIT_ULL(hw_idx); + if (hw_idx != pmc->idx) + continue; + + pebs_enable |= BIT_ULL(pmc->idx); } - return host_cross_mapped_mask; + return pebs_enable; } static bool intel_pmu_is_mediated_pmu_supported(struct x86_pmu_capability *host_pmu) diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 8f7d0620f44c71..2d0443562a1686 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -7364,20 +7364,11 @@ static void atomic_switch_perf_msrs(struct vcpu_vmx *vmx) return; struct x86_guest_pebs guest_pebs = { - .enable = pmu->pebs_enable, + .enable = intel_pmu_compute_pebs_enable(pmu), .ds_area = pmu->ds_area, .data_cfg = pmu->pebs_data_cfg, }; - /* - * Disable counters where the guest PMC is different than the host PMC - * being used on behalf of the guest, as the PEBS record includes - * PERF_GLOBAL_STATUS, i.e. the guest will see overflow status for the - * wrong counter(s). - */ - if (guest_pebs.enable & pmu->global_ctrl) - guest_pebs.enable &= ~intel_pmu_get_cross_mapped_mask(pmu); - /* Note, nr_msrs may be garbage if perf_guest_get_msrs() returns NULL. */ msrs = perf_guest_get_msrs(&nr_msrs, &guest_pebs); if (!msrs) diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h index 5c4e2cf6256c10..1db461060c7e8a 100644 --- a/arch/x86/kvm/vmx/vmx.h +++ b/arch/x86/kvm/vmx/vmx.h @@ -664,7 +664,20 @@ static __always_inline struct vcpu_vmx *to_vmx(struct kvm_vcpu *vcpu) return container_of(vcpu, struct vcpu_vmx, vcpu); } -u64 intel_pmu_get_cross_mapped_mask(struct kvm_pmu *pmu); +u64 __intel_pmu_compute_pebs_enable(struct kvm_pmu *pmu); + +static inline u64 intel_pmu_compute_pebs_enable(struct kvm_pmu *pmu) +{ + /* + * Avoid the function call overhead in the common case that the guest + * isn't using PEBS. + */ + if (!(pmu->pebs_enable & pmu->global_ctrl)) + return 0; + + return __intel_pmu_compute_pebs_enable(pmu); +} + int intel_pmu_create_guest_lbr_event(struct kvm_vcpu *vcpu); void vmx_passthrough_lbr_msrs(struct kvm_vcpu *vcpu); From ba29babd881e09a7bb8b01fcfeda3d637d8115c5 Mon Sep 17 00:00:00 2001 From: Sean Christopherson Date: Mon, 21 Sep 2026 12:14:18 -0700 Subject: [PATCH 0862/2286] perf/x86/intel: Check only PMC bits in PEBS_ENABLED when detecting host PEBS usage When checking to see if guest PEBS usage should be disabled due to host PEBS usage, filter out non-counter bits in PEBS_ENABLE to avoid getting false positives in the unlikely scenario that "Enable Precise Store" is enabled without PEBS actually being active for any PMCs. Note, the other "hidden" usage of pebs_mask is already explicitly masked with intel_ctrl: .guest = intel_ctrl & ~cpuc->intel_ctrl_exclude_guest_mask & ~pebs_mask, I.e. removing the non-counter bits from pebs_mask doesn't change the value that is loaded into arr[global_ctrl].guest. Fixes: 854250329c02 ("KVM: x86/pmu: Disable guest PEBS temporarily in two rare situations") Closes: https://lore.kernel.org/all/20260508234018.F06AEC2BCB0@smtp.kernel.org Reported-by: Sashiko Bot Suggested-by: Dapeng Mi Signed-off-by: Sean Christopherson Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Dapeng Mi Link: https://patch.msgid.link/20260921191418.950933-11-seanjc@google.com --- arch/x86/events/intel/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c index 1bc96b9fe0e4c0..0a34d674df590c 100644 --- a/arch/x86/events/intel/core.c +++ b/arch/x86/events/intel/core.c @@ -5333,7 +5333,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events); struct perf_guest_switch_msr *arr = cpuc->guest_switch_msrs; u64 intel_ctrl = hybrid(cpuc->pmu, intel_ctrl); - u64 pebs_mask = cpuc->pebs_enabled & x86_pmu.pebs_capable; + u64 pebs_mask = intel_ctrl & cpuc->pebs_enabled & x86_pmu.pebs_capable; u64 guest_pebs_mask; int global_ctrl; @@ -5385,7 +5385,7 @@ static struct perf_guest_switch_msr *intel_guest_get_msrs(int *nr, * the guest wants to use for PEBS, (c) are not excluded from counting * in the guest, and (d) _are_ excluded from counting in the host. */ - guest_pebs_mask = pebs_mask & intel_ctrl & guest_pebs->enable & + guest_pebs_mask = pebs_mask & guest_pebs->enable & ~cpuc->intel_ctrl_exclude_guest_mask & cpuc->intel_ctrl_exclude_host_mask; From 4a8557a4e5d2d5f96a989a428e38569451e74405 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 21 Sep 2026 08:30:14 -0700 Subject: [PATCH 0863/2286] perf/x86/amd/uncore: Remove redundant event slot scan amd_uncore_add() first checks the slot recorded in event->hw.idx. If that misses, it scans ctx->events[] for the event before looking for a free slot. Perf serializes ->add() and ->del() for an event. Initialization sets idx to -1. A successful ->add() claims a slot and records its index before returning, while ->del() clears the slot before resetting idx. CPU context migration follows the same delete/add sequence. Thus an installed event can only reside at the recorded index. If the direct check misses, the event is not present in ctx->events[]. Remove the redundant scan. This avoids walking all counters before each new slot search after multiplexing has scheduled an event out. On a host running a production workload in the Meta fleet, amd_uncore_add() was called 56,575 times per second from mux rotation. Its 16-counter DF PMU makes each failed search scan 16 pointers across two cache lines. Signed-off-by: Usama Arif Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sandipan Das Link: https://patch.msgid.link/20260921153108.2713179-2-usama.arif@linux.dev --- arch/x86/events/amd/uncore.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index 74b9c92d517a77..c1158f3691d4e0 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -206,17 +206,15 @@ static int amd_uncore_add(struct perf_event *event, int flags) struct amd_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu); struct hw_perf_event *hwc = &event->hw; - /* are we already assigned? */ + /* + * Perf serializes ->add() and ->del() for an event. A successful + * ->add() records the claimed slot in hwc->idx before returning, and + * ->del() clears that slot before resetting hwc->idx. Therefore, an + * existing assignment must be at hwc->idx. + */ if (hwc->idx != -1 && ctx->events[hwc->idx] == event) goto out; - for (i = 0; i < pmu->num_counters; i++) { - if (ctx->events[i] == event) { - hwc->idx = i; - goto out; - } - } - /* if not, take the first available counter */ hwc->idx = -1; for (i = 0; i < pmu->num_counters; i++) { From 6350de8671b94afb7691d110f63bcda42f658a69 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Mon, 21 Sep 2026 08:30:15 -0700 Subject: [PATCH 0864/2286] perf/x86/amd/uncore: Free counter slot by index amd_uncore_del() scans ctx->events[] from slot zero and attempts a compare-exchange until it finds the event. amd_uncore_add() records the slot it claimed in event->hw.idx. Perf calls ->del() only after a successful ->add(), and the driver never moves an installed event between slots. The index therefore remains valid until deletion. Use the recorded index directly and retain the compare-exchange ownership check. Warn if it fails, as that means the driver lost track of the slot. Deleting all events from a full PMU with N counters now requires N compare-exchanges instead of N * (N + 1) / 2. On a host running a production workload in the Meta fleet, amd_uncore_del() was called 56,575 times per second from mux rotation. Signed-off-by: Usama Arif Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sandipan Das Link: https://patch.msgid.link/20260921153108.2713179-3-usama.arif@linux.dev --- arch/x86/events/amd/uncore.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c index c1158f3691d4e0..7aa5a5b652a2bd 100644 --- a/arch/x86/events/amd/uncore.c +++ b/arch/x86/events/amd/uncore.c @@ -246,19 +246,15 @@ static int amd_uncore_add(struct perf_event *event, int flags) static void amd_uncore_del(struct perf_event *event, int flags) { - int i; struct amd_uncore_pmu *pmu = event_to_amd_uncore_pmu(event); struct amd_uncore_ctx *ctx = *per_cpu_ptr(pmu->ctx, event->cpu); struct hw_perf_event *hwc = &event->hw; + struct perf_event *old = event; event->pmu->stop(event, PERF_EF_UPDATE); - for (i = 0; i < pmu->num_counters; i++) { - struct perf_event *tmp = event; - - if (try_cmpxchg(&ctx->events[i], &tmp, NULL)) - break; - } + /* ->del() follows a successful ->add(), so hwc->idx owns this slot. */ + WARN_ON_ONCE(!try_cmpxchg(&ctx->events[hwc->idx], &old, NULL)); hwc->idx = -1; } From c72945693b90423f1b46ac2dbc8749c2b7804fc8 Mon Sep 17 00:00:00 2001 From: "Shubhang Kaushik (Ampere)" Date: Thu, 17 Sep 2026 16:06:59 -0700 Subject: [PATCH 0865/2286] sched: Restart fair hrtick after same-task repicks Fair hrticks are one-shot timers. A same-task repick after hrtick expiry skips set_next_task_fair() and leaves no hrtick armed for the next fair preemption point: hrtick() -> task_tick_fair(..., queued=1) -> entity_tick() -> resched_curr() -> schedule() -> pick_task_fair() selects current -> put_prev_set_next_task(rq, prev, next) -> next == prev -> return Introduce SNT_REPICK for the next == prev path: put_prev_set_next_task(rq, prev, next) -> next == prev -> next->sched_class->set_next_task(rq, next, SNT_REPICK) -> set_next_task_fair() -> hrtick_start_fair() set_next_task_fair() skips task-transition work for SNT_REPICK and restarts the fair hrtick. hrtick_start() records the delay during schedule(), and hrtick_schedule_exit() rearms the timer. Do not restart the SCHED_DEADLINE hrtick for SNT_REPICK. Same-task repicks skip put_prev_task() for both fair and SCHED_DEADLINE, but pick_task_fair() refreshes the selected fair entity before selecting it again. hrtick_start_fair() reads that refreshed entity, including with group scheduling. The SCHED_DEADLINE picker has no corresponding current-runtime update. Restarting its hrtick on SNT_REPICK could therefore use stale dl_se->runtime and delay the next runtime enforcement point. Suggested-by: Peter Zijlstra (Intel) Signed-off-by: Shubhang Kaushik (Ampere) Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Zhan Xusheng Link: https://patch.msgid.link/20260917-sched-fair-hrtick-restart-v4-1-4dd1414da81a@gentwo.org --- kernel/sched/core.c | 2 +- kernel/sched/deadline.c | 7 +++++-- kernel/sched/ext/ext.c | 5 ++++- kernel/sched/fair.c | 21 ++++++++++++++++----- kernel/sched/idle.c | 5 ++++- kernel/sched/rt.c | 7 +++++-- kernel/sched/sched.h | 16 ++++++++++++---- kernel/sched/stop_task.c | 5 ++++- 8 files changed, 51 insertions(+), 17 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 5a61650f12b7d5..d368aaf68c8e40 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7240,7 +7240,7 @@ static void __sched notrace __schedule(int sched_mode) * on_cpu. */ donor->sched_class->put_prev_task(rq, donor, donor); - donor->sched_class->set_next_task(rq, donor, true); + donor->sched_class->set_next_task(rq, donor, SNT_PICK); } } else { rq_set_donor(rq, next); diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c index de6a361a87c77c..c0ebdcde5fe5d4 100644 --- a/kernel/sched/deadline.c +++ b/kernel/sched/deadline.c @@ -2773,11 +2773,14 @@ static void start_hrtick_dl(struct rq *rq, struct sched_dl_entity *dl_se) * DL keeps current in tree, because ->deadline is not typically changed while * a task is runnable. */ -static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first) +static void set_next_task_dl(struct rq *rq, struct task_struct *p, enum snt_e type) { struct sched_dl_entity *dl_se = &p->dl; struct dl_rq *dl_rq = &rq->dl; + if (type == SNT_REPICK) + return; + p->se.exec_start = rq_clock_task(rq); if (on_dl_rq(&p->dl)) update_stats_wait_end_dl(dl_rq, dl_se); @@ -2788,7 +2791,7 @@ static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first) WARN_ON_ONCE(dl_rq->curr); dl_rq->curr = dl_se; - if (!first) + if (type != SNT_PICK) return; if (rq->donor->sched_class != &dl_sched_class) diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 10af28a9f2c0c9..53275bc7c0299a 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -3001,10 +3001,13 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev return verdict; } -static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first) +static void set_next_task_scx(struct rq *rq, struct task_struct *p, enum snt_e type) { struct scx_sched *sch = scx_task_sched(p); + if (type == SNT_REPICK) + return; + if (p->scx.flags & SCX_TASK_QUEUED) { /* * Core-sched might decide to execute @p before it is diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index a42360ca94c3d0..36861f435d8a6f 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -15234,14 +15234,18 @@ static void switched_to_fair(struct rq *rq, struct task_struct *p) } } -static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first) +static void set_next_task_fair(struct rq *rq, struct task_struct *p, enum snt_e type) { struct sched_entity *se = &p->se; - bool throttled = false; struct cfs_rq *cfs_rq = &rq->cfs; unsigned long weight = NICE_0_LOAD; + bool first = type == SNT_PICK; + bool throttled = false; bool on_rq = se->on_rq; + if (type == SNT_REPICK) + goto repick; + clear_buddies(cfs_rq, se); if (on_rq) @@ -15285,11 +15289,18 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first) WARN_ON_ONCE(se->sched_delayed); - if (hrtick_enabled_fair(rq)) - hrtick_start_fair(rq, p); - update_misfit_status(p, rq); sched_fair_update_stop_tick(rq, p); + +repick: + /* + * A same-task repick skips put_prev_task_fair(), but + * pick_task_fair() refreshed the entity hrtick_start_fair() reads + * before selecting it again. rq->cfs.curr identifies that entity, + * including with group scheduling. + */ + if (hrtick_enabled_fair(rq)) + hrtick_start_fair(rq, p); } void init_cfs_rq(struct cfs_rq *cfs_rq) diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c index eb73b65ce6c471..76f3c84ca6846a 100644 --- a/kernel/sched/idle.c +++ b/kernel/sched/idle.c @@ -487,8 +487,11 @@ static void put_prev_task_idle(struct rq *rq, struct task_struct *prev, struct t update_rq_avg_idle(rq); } -static void set_next_task_idle(struct rq *rq, struct task_struct *next, bool first) +static void set_next_task_idle(struct rq *rq, struct task_struct *next, enum snt_e type) { + if (type == SNT_REPICK) + return; + update_idle_core(rq); scx_update_idle(rq, true, true); schedstat_inc(rq->sched_goidle); diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c index 85303add726df4..1535046a23ffa0 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -1654,11 +1654,14 @@ static void wakeup_preempt_rt(struct rq *rq, struct task_struct *p, int flags) check_preempt_equal_prio(rq, p); } -static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, bool first) +static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, enum snt_e type) { struct sched_rt_entity *rt_se = &p->rt; struct rt_rq *rt_rq = &rq->rt; + if (type == SNT_REPICK) + return; + p->se.exec_start = rq_clock_task(rq); if (on_rt_rq(&p->rt)) update_stats_wait_end_rt(rt_rq, rt_se); @@ -1666,7 +1669,7 @@ static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, bool f /* The running task is never eligible for pushing */ dequeue_pushable_task(rq, p); - if (!first) + if (type != SNT_PICK) return; /* diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 6c3ad70e58b8e1..1b64be5771ca45 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2630,6 +2630,12 @@ struct affinity_context { extern s64 update_curr_common(struct rq *rq); +enum snt_e { + SNT_NORMAL, /* set_next_task() */ + SNT_PICK, /* put_prev_set_next_task(): prev != next */ + SNT_REPICK, /* put_prev_set_next_task(): prev == next */ +}; + struct sched_class { #ifdef CONFIG_UCLAMP_TASK @@ -2687,7 +2693,7 @@ struct sched_class { * __schedule: rq->lock */ void (*put_prev_task)(struct rq *rq, struct task_struct *p, struct task_struct *next); - void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first); + void (*set_next_task)(struct rq *rq, struct task_struct *p, enum snt_e type); /* * select_task_rq: p->pi_lock @@ -2790,7 +2796,7 @@ static inline void put_prev_task(struct rq *rq, struct task_struct *prev) static inline void set_next_task(struct rq *rq, struct task_struct *next) { - next->sched_class->set_next_task(rq, next, false); + next->sched_class->set_next_task(rq, next, SNT_NORMAL); } static inline void @@ -2811,11 +2817,13 @@ static inline void put_prev_set_next_task(struct rq *rq, __put_prev_set_next_dl_server(rq, prev, next); - if (next == prev) + if (next == prev) { + next->sched_class->set_next_task(rq, next, SNT_REPICK); return; + } prev->sched_class->put_prev_task(rq, prev, next); - next->sched_class->set_next_task(rq, next, true); + next->sched_class->set_next_task(rq, next, SNT_PICK); } /* diff --git a/kernel/sched/stop_task.c b/kernel/sched/stop_task.c index c909ca0d8c87c1..1e0109ec36b3f9 100644 --- a/kernel/sched/stop_task.c +++ b/kernel/sched/stop_task.c @@ -27,8 +27,11 @@ wakeup_preempt_stop(struct rq *rq, struct task_struct *p, int flags) /* we're never preempted */ } -static void set_next_task_stop(struct rq *rq, struct task_struct *stop, bool first) +static void set_next_task_stop(struct rq *rq, struct task_struct *stop, enum snt_e type) { + if (type == SNT_REPICK) + return; + stop->se.exec_start = rq_clock_task(rq); } From 819224e506bc7c2d61ec6a58ec6e876b505abce9 Mon Sep 17 00:00:00 2001 From: Kayra Cizmeci Date: Fri, 11 Sep 2026 18:54:48 +0300 Subject: [PATCH 0866/2286] sched/fair: Remove dead code on enqueue_task_fair() The 'cfs_rq->curr == se' path on enqueue_task_fair() seems to be unachievable. Remove the code that depends on this cfs_rq->curr == se case on enqueue_task_fair() and bool curr too since there are two possibilities and one has been removed, we can proceed directly with the other. Signed-off-by: Kayra Cizmeci Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911155449.1249726-1-kayracizmeci@gmail.com --- kernel/sched/fair.c | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 36861f435d8a6f..a03e7f4fc9cde8 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -7988,7 +7988,6 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) struct sched_entity *se = &p->se; struct cfs_rq *cfs_rq = &rq->cfs; unsigned long weight; - bool curr; if (task_is_throttled(p) && enqueue_throttled_task(p)) return; @@ -8017,23 +8016,14 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags) if (p->in_iowait) cpufreq_update_util(rq, SCHED_CPUFREQ_IOWAIT); - /* - * XXX comment on the curr thing - */ - curr = (cfs_rq->curr == se); - if (curr) - place_entity(cfs_rq, se, flags); if (se->on_rq && se->sched_delayed) requeue_delayed_entity(cfs_rq, se); weight = enqueue_hierarchy(p, flags); - - if (!curr) { - reweight_eevdf(cfs_rq, se, weight, false); - place_entity(cfs_rq, se, flags | ENQUEUE_QUEUED); - __enqueue_entity(cfs_rq, se); - } + reweight_eevdf(cfs_rq, se, weight, false); + place_entity(cfs_rq, se, flags | ENQUEUE_QUEUED); + __enqueue_entity(cfs_rq, se); if (!rq_h_nr_queued && rq->cfs.h_nr_queued) dl_server_start(&rq->fair_server); From fbbc63fed0b09c8c5cf3972db8922ae98406ceec Mon Sep 17 00:00:00 2001 From: Hui Su Date: Wed, 16 Sep 2026 01:31:38 +0900 Subject: [PATCH 0867/2286] sched/core: Remove redundant core_sched_seq core_sched_seq records whether an rq has consumed the pick made by the last core-wide selection. However, rq->core_pick already carries the same per-rq state. A core-wide selection leaves core_pick populated only for siblings which still need to consume their picks. It is cleared when the current CPU consumes its pick, when a sibling is already running the selected task, or when a pending pick is consumed through the fastpath. The CPU offline path clears it as well. core_task_seq and core_pick_seq serve a separate purpose. core_task_seq changes when the task set changes and for each new core-wide pick, while core_pick_seq records the task sequence on which the selection was made. Their equality therefore establishes that a pending core_pick is still valid. Consequently, once core_pick_seq == core_task_seq, a non-NULL core_pick is sufficient to tell that this rq still has a pick to consume. core_sched_seq duplicates that pending/consumed state. Remove core_sched_seq and use core_pick directly as the pending marker. Testing included an instrumented comparison of the old and new pending predicates under core-scheduling stress. Additional guest tests exercised repeated core-cookie lifecycles, forced idle, and CPU hotplug under load. No predicate divergence or kernel warning was observed. Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260915163138.2973969-1-sh_def@163.com --- kernel/sched/core.c | 10 ++++------ kernel/sched/sched.h | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index d368aaf68c8e40..ee9b443f760d7e 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6276,10 +6276,7 @@ pick_next_task(struct rq *rq, struct rq_flags *rf) * selection. In this case, do a core-wide selection. */ if (rq->core->core_pick_seq == rq->core->core_task_seq && - rq->core->core_pick_seq != rq->core_sched_seq && rq->core_pick) { - WRITE_ONCE(rq->core_sched_seq, rq->core->core_pick_seq); - next = rq->core_pick; rq->dl_server = rq->core_dl_server; rq->core_pick = NULL; @@ -6311,11 +6308,13 @@ pick_next_task(struct rq *rq, struct rq_flags *rf) } /* - * core->core_task_seq, core->core_pick_seq, rq->core_sched_seq + * core->core_task_seq, core->core_pick_seq * * @task_seq guards the task state ({en,de}queues) * @pick_seq is the @task_seq we did a selection on - * @sched_seq is the @pick_seq we scheduled + * + * Once a core-wide selection is committed, a non-NULL core_pick denotes + * a pick which still needs to be consumed on this CPU. * * However, preemptions can cause multiple picks on the same task set. * 'Fix' this by also increasing @task_seq for every pick. @@ -6422,7 +6421,6 @@ pick_next_task(struct rq *rq, struct rq_flags *rf) rq->core->core_pick_seq = rq->core->core_task_seq; next = rq->core_pick; - rq->core_sched_seq = rq->core->core_pick_seq; /* Something should have been selected for current CPU */ WARN_ON_ONCE(!next); diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index 1b64be5771ca45..f7d0b64b9d3569 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -1371,7 +1371,6 @@ struct rq { struct task_struct *core_pick; struct sched_dl_entity *core_dl_server; unsigned int core_enabled; - unsigned int core_sched_seq; struct rb_root core_tree; /* shared state -- careful with sched_core_cpu_deactivate() */ From abe440b3770fce8eb8ee92a563dfb55a7b9a1c0e Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Thu, 17 Sep 2026 16:39:14 +0100 Subject: [PATCH 0868/2286] sched/fair: Drop idle recency from slow-path CPU selection The slow-path CPU picker favours the most recently idle CPU as a proxy for cache warmth. A more recent idle stamp may make ongoing entry more likely. If entry cannot be aborted, that CPU must finish entry and then exit, potentially paying more than an already-resident CPU with the same advertised exit latency. The advertised worst-case latency covers both cases. idle_stamp does not timestamp the current CPUIdle entry, so this is only a heuristic. A recent scheduler-idle transition may also mark a short gap in recurring task activity, making the CPU likely to be busy again soon. Drop the timestamp tie-break and retain the first candidate unless a lower advertised exit latency is found. Signed-off-by: Christian Loehle Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Vincent Guittot Link: https://patch.msgid.link/20260917153915.1563875-2-christian.loehle@arm.com --- kernel/sched/fair.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index a03e7f4fc9cde8..c4d4b55a09b1c8 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -8444,7 +8444,6 @@ sched_balance_find_dst_group_cpu(struct sched_group *group, struct task_struct * { unsigned long load, min_load = ULONG_MAX; unsigned int min_exit_latency = UINT_MAX; - u64 latest_idle_timestamp = 0; int least_loaded_cpu = this_cpu; int shallowest_idle_cpu = -1; int i; @@ -8466,22 +8465,10 @@ sched_balance_find_dst_group_cpu(struct sched_group *group, struct task_struct * if (available_idle_cpu(i)) { struct cpuidle_state *idle = idle_get_state(rq); if (idle && idle->exit_latency < min_exit_latency) { - /* - * We give priority to a CPU whose idle state - * has the smallest exit latency irrespective - * of any idle timestamp. - */ min_exit_latency = idle->exit_latency; - latest_idle_timestamp = rq->idle_stamp; shallowest_idle_cpu = i; } else if ((!idle || idle->exit_latency == min_exit_latency) && - rq->idle_stamp > latest_idle_timestamp) { - /* - * If equal or no active idle state, then - * the most recently idled CPU might have - * a warmer cache. - */ - latest_idle_timestamp = rq->idle_stamp; + shallowest_idle_cpu == -1) { shallowest_idle_cpu = i; } } else if (shallowest_idle_cpu == -1) { From c9ce69fc43bd07dbed9d23a504e56f84604ecf42 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Thu, 17 Sep 2026 16:39:15 +0100 Subject: [PATCH 0869/2286] sched/fair: Randomize equally shallow slow-path candidates Picking the first eligible idle CPU leaves a scan-order bias. Concurrent slow-path selectors can choose the same CPU before either task is enqueued. Use reservoir sampling for equal exit latencies, resetting the candidate count when a shallower candidate appears. Use the per-CPU scheduler PRNG and reciprocal_scale() to avoid variable division or a second scan. Use a u64 latency key with U64_MAX for unpublished states. Published states take precedence; when none are found, sample among the idle CPUs without a published state. Signed-off-by: Christian Loehle Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Vincent Guittot Link: https://patch.msgid.link/20260917153915.1563875-3-christian.loehle@arm.com --- kernel/sched/fair.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index c4d4b55a09b1c8..2f5c7063b032bd 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -8443,7 +8444,8 @@ static int sched_balance_find_dst_group_cpu(struct sched_group *group, struct task_struct *p, int this_cpu) { unsigned long load, min_load = ULONG_MAX; - unsigned int min_exit_latency = UINT_MAX; + u64 min_exit_latency = U64_MAX; + unsigned int nr_candidates = 0; int least_loaded_cpu = this_cpu; int shallowest_idle_cpu = -1; int i; @@ -8464,12 +8466,16 @@ sched_balance_find_dst_group_cpu(struct sched_group *group, struct task_struct * if (available_idle_cpu(i)) { struct cpuidle_state *idle = idle_get_state(rq); - if (idle && idle->exit_latency < min_exit_latency) { - min_exit_latency = idle->exit_latency; - shallowest_idle_cpu = i; - } else if ((!idle || idle->exit_latency == min_exit_latency) && - shallowest_idle_cpu == -1) { + u64 exit_latency = idle ? idle->exit_latency : U64_MAX; + + if (shallowest_idle_cpu == -1 || exit_latency < min_exit_latency) { + min_exit_latency = exit_latency; shallowest_idle_cpu = i; + nr_candidates = 1; + } else if (exit_latency == min_exit_latency) { + nr_candidates++; + if (!reciprocal_scale(sched_rng(), nr_candidates)) + shallowest_idle_cpu = i; } } else if (shallowest_idle_cpu == -1) { load = cpu_load(cpu_rq(i)); From aae2a33ea66299f39252bc1891bb4f05f631255d Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Mon, 21 Sep 2026 17:22:31 +0200 Subject: [PATCH 0870/2286] sched/eevdf: Ensure that vprot will never go above a min slice ineligible_vruntime() can be after the duration of a min slice but we want to make sure that current will not run more than a min slice. Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260921152238.3804392-2-vincent.guittot@linaro.org --- kernel/sched/fair.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 2f5c7063b032bd..0969be26c4d6db 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1128,10 +1128,9 @@ static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity /* If there are shorter slices than se's one */ if (slice != se->slice) { + vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se)); if (sched_feat(PREEMPT_SHORT)) vprot = min_vruntime(vprot, ineligible_vruntime(cfs_rq)); - else - vprot = min_vruntime(vprot, se->vruntime + calc_delta_fair(slice, se)); } se->vprot = vprot; From 4bf32ec3327d2d2286e96508cd128ce1592f43ce Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Mon, 21 Sep 2026 17:22:32 +0200 Subject: [PATCH 0871/2286] sched/eevdf: Align update_protect_slice to set_protect_slice Apply the same logic as for set_protect_slice when updating it. Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260921152238.3804392-3-vincent.guittot@linaro.org --- kernel/sched/fair.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 0969be26c4d6db..82155d91de06ba 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -821,12 +821,6 @@ static u64 ineligible_vruntime(struct cfs_rq *cfs_rq) if (curr && !curr->on_rq) curr = NULL; - /* - * This is called from set_next_task_fair(.first=true) / - * set_protect_slice() so curr had better be set and on_rq. - */ - WARN_ON_ONCE(!curr); - if (weight) { s64 runtime = cfs_rq->sum_w_vruntime; @@ -1138,10 +1132,19 @@ static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity static inline void update_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) { - u64 slice = cfs_rq_min_slice(cfs_rq); u64 vruntime = min_vruntime(se->vruntime, avg_vruntime(cfs_rq)); + u64 slice = normalized_sysctl_sched_base_slice; + u64 vprot; + + if (sched_feat(RUN_TO_PARITY)) + slice = cfs_rq_min_slice(cfs_rq); + + vprot = min_vruntime(se->vprot, vruntime + calc_delta_fair(slice, se)); - se->vprot = min_vruntime(se->vprot, vruntime + calc_delta_fair(slice, se)); + if (sched_feat(PREEMPT_SHORT) && slice != se->slice) + vprot = min_vruntime(vprot, ineligible_vruntime(cfs_rq)); + + se->vprot = vprot; } static inline bool protect_slice(struct sched_entity *se) From d2e0100827578641df2f85546bdb57bb1d8ff55b Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Mon, 21 Sep 2026 17:22:33 +0200 Subject: [PATCH 0872/2286] sched/eevdf: Handle more short slice waking cases Handle better the cases with several short slice tasks waking up simultaneously. Signed-off-by: Vincent Guittot Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260921152238.3804392-4-vincent.guittot@linaro.org --- kernel/sched/fair.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 82155d91de06ba..03206e15e6fe4e 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -9836,8 +9836,14 @@ static inline bool set_preempt_buddy(struct cfs_rq *cfs_rq, struct sched_entity static inline bool set_short_buddy(struct cfs_rq *cfs_rq, struct sched_entity *pse) { - if (cfs_rq->next && cfs_rq->next->slice < pse->slice) - return false; + if (cfs_rq->next) { + if (cfs_rq->next->slice < pse->slice) + return false; + + if (cfs_rq->next->slice == pse->slice && + entity_before(cfs_rq->next, pse)) + return false; + } set_next_buddy(cfs_rq, pse); return true; From caddcdb829b212595114ce21d2bc48edcb90a260 Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Thu, 17 Sep 2026 15:27:05 +0530 Subject: [PATCH 0873/2286] selftests/filesystems/openat2: fix O_LARGEFILE definitions for non-arm64 architectures The openat2_test.c code previously hardcoded O_LARGEFILE to 0x8000 on all architectures other than arm64 (aarch64). However, on several architectures this value is different. For example, on PowerPC (including ppc64le), O_LARGEFILE is defined as 0x10000, whereas 0x8000 is actually O_NOFOLLOW. This mismatch caused the flag_validation test case to pass O_PATH | O_NOFOLLOW instead of O_PATH | O_LARGEFILE. Since O_PATH | O_NOFOLLOW is a valid and compatible flag combination, the kernel successfully opened the file instead of failing with -EINVAL, causing a false test failure on PowerPC. Fix this by introducing proper architecture-specific definitions for O_LARGEFILE matching their respective UAPI asm/fcntl.h values. Signed-off-by: Disha Goel Link: https://patch.msgid.link/20260917095707.16986-1-disgoel@linux.ibm.com Signed-off-by: Christian Brauner (Amutable) --- .../selftests/filesystems/openat2/openat2_test.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/filesystems/openat2/openat2_test.c b/tools/testing/selftests/filesystems/openat2/openat2_test.c index 6f5afbe2d8d310..e08c94ce05305e 100644 --- a/tools/testing/selftests/filesystems/openat2/openat2_test.c +++ b/tools/testing/selftests/filesystems/openat2/openat2_test.c @@ -23,8 +23,16 @@ * XXX: This is wrong on {mips, parisc, powerpc, sparc}. */ #undef O_LARGEFILE -#ifdef __aarch64__ +#if defined(__aarch64__) || defined(__alpha__) #define O_LARGEFILE 0x20000 +#elif defined(__powerpc__) || defined(__ppc__) +#define O_LARGEFILE 0x10000 +#elif defined(__sparc__) +#define O_LARGEFILE 0x40000 +#elif defined(__mips__) +#define O_LARGEFILE 0x2000 +#elif defined(__parisc__) +#define O_LARGEFILE 0x800 #else #define O_LARGEFILE 0x8000 #endif From 26915b4664906b627a8210620c7d49d547cbab49 Mon Sep 17 00:00:00 2001 From: Disha Goel Date: Thu, 17 Sep 2026 15:27:06 +0530 Subject: [PATCH 0874/2286] selftests/filesystems/openat2: make /tmp a mountpoint inside private namespace In resolve_test.c, the test unshares the mount namespace and attempts to make /tmp private via: mount("", "/tmp", "", MS_PRIVATE, "") This fails with EINVAL on environments where /tmp is not already a separate mount point (e.g. if /tmp is just a standard directory on the root filesystem /). Furthermore, the resolve_no_xdev test relies on crossing into /tmp to verify RESOLVE_NO_XDEV and trigger an -EXDEV error. If /tmp is not a mount point, resolving /tmp never crosses a mount boundary, leading to a test failure. Fix this by: 1. Making the entire root directory recursively private (MS_PRIVATE | MS_REC) after unsharing the mount namespace. 2. Performing a self-bind-mount of /tmp on /tmp inside the isolated private mount namespace, which guarantees that /tmp is a separate mount point and that resolving /tmp will cross a mount boundary as expected by the test. Signed-off-by: Disha Goel Link: https://patch.msgid.link/20260917095707.16986-2-disgoel@linux.ibm.com Tested-by: andy_gao Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/filesystems/openat2/resolve_test.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/filesystems/openat2/resolve_test.c b/tools/testing/selftests/filesystems/openat2/resolve_test.c index eacde59ce15830..6216a854638847 100644 --- a/tools/testing/selftests/filesystems/openat2/resolve_test.c +++ b/tools/testing/selftests/filesystems/openat2/resolve_test.c @@ -140,9 +140,12 @@ FIXTURE_SETUP(openat2_resolve) if (!openat2_supported) SKIP(return, "openat2(2) not supported"); - /* Unshare and make /tmp a new directory. */ + /* Unshare and make the mount tree private. */ ASSERT_EQ(unshare(CLONE_NEWNS), 0); - ASSERT_EQ(mount("", "/tmp", "", MS_PRIVATE, ""), 0); + ASSERT_EQ(mount("", "/", "", MS_PRIVATE | MS_REC, ""), 0); + + /* Ensure /tmp is a mountpoint for RESOLVE_NO_XDEV test crossing into /tmp. */ + ASSERT_EQ(mount("/tmp", "/tmp", NULL, MS_BIND, NULL), 0); /* Make the top-level directory. */ ASSERT_NE(mkdtemp(dirname), NULL); From 6b125c73aaa0dab9ce625437c63b176afe453776 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 14 Sep 2026 12:22:35 +0200 Subject: [PATCH 0875/2286] microblaze: Enable xilinx dmas and axi emac drivers Both drivers are used on Microblaze systems for quite a long time that's why enable them. Link: https://patch.msgid.link/6d41613ed4ccfdc38ec47070b79a5893e3abc2a6.1789381354.git.michal.simek@amd.com Signed-off-by: Michal Simek --- arch/microblaze/configs/mmu_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/microblaze/configs/mmu_defconfig b/arch/microblaze/configs/mmu_defconfig index fbbdcb394ca24e..7f9b19d0d9dbf5 100644 --- a/arch/microblaze/configs/mmu_defconfig +++ b/arch/microblaze/configs/mmu_defconfig @@ -70,6 +70,8 @@ CONFIG_XILINX_WATCHDOG=y CONFIG_FB=y CONFIG_FB_XILINX=y # CONFIG_USB_SUPPORT is not set +CONFIG_DMADEVICES=y +CONFIG_XILINX_DMA=y CONFIG_UIO=y CONFIG_UIO_PDRV_GENIRQ=y CONFIG_UIO_DMEM_GENIRQ=y From 8e17b922aff03487816b467ddd871f5440d57e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Ahmet=20Memi=C5=9F?= Date: Wed, 9 Sep 2026 15:10:25 +0300 Subject: [PATCH 0876/2286] nls: fix the Unicode to charset tables of iso8859-14 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit page01 and page1e in nls_iso8859-14.c are identical, so the U+01xx table is incorrectly replaced by the U+1Exx table. Both are used by page_uni2charset, causing uni2char() to map 29 code points to the wrong bytes. charset2uni() is unaffected and matches the Unicode 8859-14 mapping. For example: U+010A -> 0xa6 -> U+1E0A U+010B -> 0xab -> U+1E0B U+1E20 -> 0xb2 -> U+0120 22 code points that are not part of ISO 8859-14 are also incorrectly accepted instead of being rejected. Regenerate page01 and page1e from charset2uni. glibc iconv agrees with the resulting mappings, and a full BMP round trip is now lossless. Signed-off-by: Ali Ahmet Memiş Link: https://patch.msgid.link/20260909121032.76647-1-aliamemis@disroot.org Signed-off-by: Christian Brauner (Amutable) --- fs/nls/nls_iso8859-14.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/fs/nls/nls_iso8859-14.c b/fs/nls/nls_iso8859-14.c index c789eccb8a695c..60b9400f915af2 100644 --- a/fs/nls/nls_iso8859-14.c +++ b/fs/nls/nls_iso8859-14.c @@ -138,24 +138,23 @@ static const unsigned char page00[256] = { }; static const unsigned char page01[256] = { - 0x00, 0x00, 0xa1, 0xa2, 0x00, 0x00, 0x00, 0x00, /* 0x00-0x07 */ - 0x00, 0x00, 0xa6, 0xab, 0x00, 0x00, 0x00, 0x00, /* 0x08-0x0f */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x00-0x07 */ + 0x00, 0x00, 0xa4, 0xa5, 0x00, 0x00, 0x00, 0x00, /* 0x08-0x0f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x10-0x17 */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xb1, /* 0x18-0x1f */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x18-0x1f */ 0xb2, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20-0x27 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x28-0x2f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x30-0x37 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x38-0x3f */ - 0xb4, 0xb5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40-0x47 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x40-0x47 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x48-0x4f */ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xb9, /* 0x50-0x57 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x50-0x57 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x58-0x5f */ - 0xbb, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60-0x67 */ - 0x00, 0x00, 0xd7, 0xf7, 0x00, 0x00, 0x00, 0x00, /* 0x68-0x6f */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60-0x67 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x68-0x6f */ 0x00, 0x00, 0x00, 0x00, 0xd0, 0xf0, 0xde, 0xfe, /* 0x70-0x77 */ 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x78-0x7f */ - - 0xa8, 0xb8, 0xaa, 0xba, 0xbd, 0xbe, 0x00, 0x00, /* 0x80-0x87 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x80-0x87 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x88-0x8f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x90-0x97 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x98-0x9f */ @@ -169,7 +168,7 @@ static const unsigned char page01[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xd8-0xdf */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xe0-0xe7 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xe8-0xef */ - 0x00, 0x00, 0xac, 0xbc, 0x00, 0x00, 0x00, 0x00, /* 0xf0-0xf7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xf0-0xf7 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0xf8-0xff */ }; @@ -178,7 +177,7 @@ static const unsigned char page1e[256] = { 0x00, 0x00, 0xa6, 0xab, 0x00, 0x00, 0x00, 0x00, /* 0x08-0x0f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x10-0x17 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xb1, /* 0x18-0x1f */ - 0xb2, 0xb3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20-0x27 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x20-0x27 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x28-0x2f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x30-0x37 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x38-0x3f */ @@ -188,9 +187,8 @@ static const unsigned char page1e[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x58-0x5f */ 0xbb, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x60-0x67 */ 0x00, 0x00, 0xd7, 0xf7, 0x00, 0x00, 0x00, 0x00, /* 0x68-0x6f */ - 0x00, 0x00, 0x00, 0x00, 0xd0, 0xf0, 0xde, 0xfe, /* 0x70-0x77 */ - 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x78-0x7f */ - + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x70-0x77 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x78-0x7f */ 0xa8, 0xb8, 0xaa, 0xba, 0xbd, 0xbe, 0x00, 0x00, /* 0x80-0x87 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x88-0x8f */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 0x90-0x97 */ From 392f5390d5d89de304ac8587bab90c32d2855489 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 09:32:50 +0200 Subject: [PATCH 0877/2286] docs/vfs: Fix the struct file_system_type copy Commit dc651e25a6d2 ("fs: RCU-ify filesystems list") replaced the next pointer of struct file_system_type with an hlist_node, but the copy of the structure in vfs.rst still shows "struct file_system_type * next". Show the hlist_node. Fixes: dc651e25a6d2 ("fs: RCU-ify filesystems list") Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260912073250.51074-1-kmehltretter@gmail.com Reviewed-by: Randy Dunlap Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/vfs.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index d3a93eec3945f8..56c68f4aefbac6 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -117,7 +117,7 @@ members are defined: const struct fs_parameter_spec *parameters; void (*kill_sb) (struct super_block *); struct module *owner; - struct file_system_type * next; + struct hlist_node list; struct hlist_head fs_supers; struct lock_class_key s_lock_key; From f8550827bf3df4c74040d5245d019908eceba4f7 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:10 +1000 Subject: [PATCH 0878/2286] VFS: fix various typos in documentation for start_creating start_removing etc Various typos fixes. start_creating_dentry() now documented as *creating*, not *removing* the entry. Unwanted spaces in Documentation/filesystems/porting.rst removed. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-2-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/porting.rst | 10 ++++---- fs/namei.c | 34 +++++++++++++-------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index 60880eb0c49d1a..4e015f1bf1f840 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -1203,16 +1203,16 @@ will fail-safe. --- -** mandatory** +**mandatory** lookup_one(), lookup_one_unlocked(), lookup_one_positive_unlocked() now take a qstr instead of a name and len. These, not the "one_len" versions, should be used whenever accessing a filesystem from outside -that filesysmtem, through a mount point - which will have a mnt_idmap. +that filesystem, through a mount point - which will have a mnt_idmap. --- -** mandatory** +**mandatory** Functions try_lookup_one_len(), lookup_one_len(), lookup_one_len_unlocked() and lookup_positive_unlocked() have been @@ -1229,7 +1229,7 @@ already been performed such as after vfs_path_parent_lookup() --- -** mandatory** +**mandatory** d_hash_and_lookup() is no longer exported or available outside the VFS. Use try_lookup_noperm() instead. This adds name validation and takes @@ -1370,7 +1370,7 @@ similar. --- -** mandatory** +**mandatory** lock_rename(), lock_rename_child(), unlock_rename() are no longer available. Use start_renaming() or similar. diff --git a/fs/namei.c b/fs/namei.c index cffc98f50f3e89..19bba7fa26f00d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2946,8 +2946,8 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name, * end_dirop - signal completion of a dirop * @de: the dentry which was returned by start_dirop or similar. * - * If the de is an error, nothing happens. Otherwise any lock taken to - * protect the dentry is dropped and the dentry itself is release (dput()). + * If the @de is an error, nothing happens. Otherwise any lock taken to + * protect the dentry is dropped and the dentry itself is released (dput()). */ void end_dirop(struct dentry *de) { @@ -3210,7 +3210,7 @@ EXPORT_SYMBOL(lookup_one); /** * lookup_one_unlocked - lookup single pathname component * @idmap: idmap of the mount the lookup is performed from - * @name: qstr olding pathname component to lookup + * @name: qstr holding pathname component to lookup * @base: base directory to lookup from * * This can be used for in-kernel filesystem clients such as file servers. @@ -3243,7 +3243,7 @@ EXPORT_SYMBOL(lookup_one_unlocked); /** * lookup_one_positive_killable - lookup single pathname component * @idmap: idmap of the mount the lookup is performed from - * @name: qstr olding pathname component to lookup + * @name: qstr holding pathname component to lookup * @base: base directory to lookup from * * This helper will yield ERR_PTR(-ENOENT) on negatives. The helper returns @@ -3259,7 +3259,7 @@ EXPORT_SYMBOL(lookup_one_unlocked); * the i_rwsem itself if necessary. If a fatal signal is pending or * delivered, it will return %-EINTR if the lock is needed. * - * Returns: A dentry, possibly negative, or + * Returns: A positive dentry, or * - same errors as lookup_one_unlocked() or * - ERR_PTR(-EINTR) if a fatal signal is pending. */ @@ -3381,7 +3381,7 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name, EXPORT_SYMBOL(lookup_noperm_positive_unlocked); /** - * start_creating - prepare to create a given name with permission checking + * start_creating - prepare to access or create a given name with permission checking * @idmap: idmap of the mount * @parent: directory in which to prepare to create the name * @name: the name to be created @@ -3413,8 +3413,8 @@ EXPORT_SYMBOL(start_creating); * @parent: directory in which to find the name * @name: the name to be removed * - * Locks are taken and a lookup in performed prior to removing - * an object from a directory. Permission checking (MAY_EXEC) is performed + * Locks are taken and a lookup is performed prior to removing an object + * from a directory. Permission checking (MAY_EXEC) is performed * against @idmap. * * If the name doesn't exist, an error is returned. @@ -3440,7 +3440,7 @@ EXPORT_SYMBOL(start_removing); * @parent: directory in which to prepare to create the name * @name: the name to be created * - * Locks are taken and a lookup in performed prior to creating + * Locks are taken and a lookup is performed prior to creating * an object in a directory. Permission checking (MAY_EXEC) is performed * against @idmap. * @@ -3469,7 +3469,7 @@ EXPORT_SYMBOL(start_creating_killable); * @parent: directory in which to find the name * @name: the name to be removed * - * Locks are taken and a lookup in performed prior to removing + * Locks are taken and a lookup is performed prior to removing * an object from a directory. Permission checking (MAY_EXEC) is performed * against @idmap. * @@ -3499,7 +3499,7 @@ EXPORT_SYMBOL(start_removing_killable); * @parent: directory in which to prepare to create the name * @name: the name to be created * - * Locks are taken and a lookup in performed prior to creating + * Locks are taken and a lookup is performed prior to creating * an object in a directory. * * If the name already exists, a positive dentry is returned. @@ -3522,7 +3522,7 @@ EXPORT_SYMBOL(start_creating_noperm); * @parent: directory in which to find the name * @name: the name to be removed * - * Locks are taken and a lookup in performed prior to removing + * Locks are taken and a lookup is performed prior to removing * an object from a directory. * * If the name doesn't exist, an error is returned. @@ -3543,11 +3543,11 @@ struct dentry *start_removing_noperm(struct dentry *parent, EXPORT_SYMBOL(start_removing_noperm); /** - * start_creating_dentry - prepare to create a given dentry - * @parent: directory from which dentry should be removed - * @child: the dentry to be removed + * start_creating_dentry - prepare to access or create a given dentry + * @parent: directory of dentry + * @child: the dentry to be prepared * - * A lock is taken to protect the dentry again other dirops and + * A lock is taken to protect the dentry against other dirops and * the validity of the dentry is checked: correct parent and still hashed. * * If the dentry is valid and negative a reference is taken and @@ -3580,7 +3580,7 @@ EXPORT_SYMBOL(start_creating_dentry); * @parent: directory from which dentry should be removed * @child: the dentry to be removed * - * A lock is taken to protect the dentry again other dirops and + * A lock is taken to protect the dentry against other dirops and * the validity of the dentry is checked: correct parent and still hashed. * * If the dentry is valid and positive, a reference is taken and From f1ac607f786f5216d40ffbbc557d0cf87e21bfd9 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:11 +1000 Subject: [PATCH 0879/2286] VFS: enhance d_splice_alias() to handle hashed dentries We currently have three interfaces for attaching existing inodes to normal filesystems(*). - d_add() requires an unhashed or in-lookup dentry and doesn't handle splicing in case a directory already has dentry - d_instantiate() requires a hashed dentry, and also doesn't handle splicing. - d_splice_alias() requires unhashed or in-lookup and does handle splicing, and can return an alternate dentry. So there is no interface that supports both hashed and in-lookup, which is what ->atomic_open needs to deal with. Some filesystems check for in-lookup in their atomic_open and if found, perform a ->lookup and can subsequently use d_instantiate() if the dentry is still negative. Others d_drop() the dentry so they can use d_splice_alias(). This last will cause a problem for proposed changes to locking which require the dentry to remain hashed while an operation proceeds on it. There is also no interface which splices a directory (which might already have a dentry) to a hashed dentry. Filesystems which need to do this d_drop() first. Some filesystems (NFS) skip ->lookup processing for LOOKUP_CREATE|LOOKUP_EXCL which includes mknod, link, symlink etc. So these inode operations might get an unhashed or a hashed-negative dentry. There is no interface for instantiating these so again they need to unhash first (nfs_link) So with this patch d_splice_alias() can handle hashed, unhashed, or in-lookup dentries. This makes it suitable for ->lookup, ->atomic_open, and ->mkdir as well as others. As a side effect d_add() will also now handle hashed dentries, but I have plans to remove d_add() as there is no benefit having it as well as the others. Once updated to handle nr_dentry_negative as is required for hashed dentrties, __d_add() contains code that is identical to __d_instantiate(), so the former is changed to call the later so now: - d_add() calls __d_add() which hashes and might call __d_instantiate. - d_instantiate() calls __d_instantiate() with appropriate locks. It was suggested by Al Viro https://lore.kernel.org/all/20250813050717.GD222315@ZenIV/ that rather than allow d_splice_alias() to handle both hashed and unhashed, we should have a new d_splice_alias_hashed(). I chose not to follow this path because, as noted above, there are several cases where the filesystem has no a priori knowledge of the state of the dentry, and so would need if (d_unhashed(dentry)) alias = d_splice_alias(inode, dentry); else alias = d_splice_alias_hashed(inode, dentry); which is clumsy. Also I hope to minimise the distinction between hashed and in-lookup (they will both be hashed, just with different DCACHE_ENTRY_TYPE) and reduce the use of unhashed dentries. Unhashed dentries would only be created by d_alloc_name() and all of those are passed to d_make_persistent() (though configfs passes some to d_add(dentry, NULL) first!). So the use-case for d_splice_alias() on unhashed dentries would disappear. Note that d_make_persistent() already handles both hashed and unhashed dentries - just not in-lookup. * There is also d_make_persistent() for filesystems which are dcache-based and don't support mkdir, create etc, and d_instantiate_new() for newly created inodes that are still locked. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-3-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/vfs.rst | 4 ++-- fs/dcache.c | 30 ++++++++++++------------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst index d3a93eec3945f8..de8f8502056e84 100644 --- a/Documentation/filesystems/vfs.rst +++ b/Documentation/filesystems/vfs.rst @@ -507,8 +507,8 @@ otherwise noted. dentry before the first mkdir returns. If there is any chance this could happen, then the new inode - should be d_drop()ed and attached with d_splice_alias(). The - returned dentry (if any) should be returned by ->mkdir(). + should be attached with d_splice_alias(). The returned + dentry (if any) should be returned by ->mkdir(). ``rmdir`` called by the rmdir(2) system call. Only required if you want diff --git a/fs/dcache.c b/fs/dcache.c index 1b1a81f10da67c..fd74753e771580 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -2168,7 +2168,6 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode) * (or otherwise set) by the caller to indicate that it is now * in use by the dcache. */ - void d_instantiate(struct dentry *entry, struct inode * inode) { BUG_ON(d_really_is_positive(entry)); @@ -2931,15 +2930,10 @@ static inline void __d_add(struct dentry *dentry, struct inode *inode, } if (unlikely(ops)) d_set_d_op(dentry, ops); - if (inode) { - unsigned add_flags = d_flags_for_inode(inode); - hlist_add_head(&dentry->d_alias, &inode->i_dentry); - raw_write_seqcount_begin(&dentry->d_seq); - __d_set_inode_and_type(dentry, inode, add_flags); - raw_write_seqcount_end(&dentry->d_seq); - fsnotify_update_flags(dentry); - } - __d_rehash(dentry); + if (inode) + __d_instantiate(dentry, inode); + if (d_unhashed(dentry)) + __d_rehash(dentry); if (dir) { end_dir_add(dir, n); __d_wake_in_lookup_waiters(dentry); @@ -3241,7 +3235,7 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry, if (IS_ERR(inode)) return ERR_CAST(inode); - BUG_ON(!d_unhashed(dentry)); + BUG_ON(d_really_is_positive(dentry)); if (!inode) goto out; @@ -3297,6 +3291,8 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry, * @inode: the inode which may have a disconnected dentry * @dentry: a negative dentry which we want to point to the inode. * + * @dentry must be negative and may be in-lookup or unhashed or hashed. + * * If inode is a directory and has an IS_ROOT alias, then d_move that in * place of the given dentry and return it, else simply d_add the inode * to the dentry and return NULL. @@ -3304,16 +3300,14 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry, * If a non-IS_ROOT directory is found, the filesystem is corrupt, and * we should error out: directories can't have multiple aliases. * - * This is needed in the lookup routine of any filesystem that is exportable - * (via knfsd) so that we can build dcache paths to directories effectively. + * This should be used to return the result of ->lookup() and to + * instantiate the result of ->mkdir(), is often useful for + * ->atomic_open, and may be used to instantiate other objects. * * If a dentry was found and moved, then it is returned. Otherwise NULL - * is returned. This matches the expected return value of ->lookup. + * is returned. This matches the expected return value of ->lookup and + * ->mkdir. * - * Cluster filesystems may call this function with a negative, hashed dentry. - * In that case, we know that the inode will be a regular file, and also this - * will only occur during atomic_open. So we need to check for the dentry - * being already hashed only in the final case. */ struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry) { From 17c7d109d8c9a6744b40ded06cc596cd6a80ec49 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:12 +1000 Subject: [PATCH 0880/2286] VFS: introduce d_alloc_trylock() Several filesystems use the results of readdir to prime the dcache. These filesystems use d_alloc_parallel() which can block if there is a concurrent lookup. Blocking in that case is pointless as the lookup will add info to the dcache and there is no value in the readdir waiting to see if it should add the info too. Also these calls to d_alloc_parallel() are made while the parent directory is locked. A proposed change to locking will lock the parent later, after d_alloc_parallel(). This means it won't be safe to wait in d_alloc_parallel() while holding the directory lock. So this patch introduces d_alloc_trylock() which doesn't block but instead returns ERR_PTR(-EWOULDBLOCK). Filesystems that prime the dcache (smb/client, nfs, fuse, cephfs) can now use that and ignore -EWOULDBLOCK errors as harmless. Unlike d_alloc_parallel(), d_alloc_trylock() calculates the hash and performs a lookup before an allocation, as that is what all callers want. This is done using try_lookup_noperm(), necessitating the inclusion of namei.h in dcache.c. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-4-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 81 ++++++++++++++++++++++++++++++++++++++++-- include/linux/dcache.h | 1 + 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/fs/dcache.c b/fs/dcache.c index fd74753e771580..43149c7849d921 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -32,6 +32,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -2756,8 +2757,16 @@ static void d_wait_lookup(struct dentry *dentry) } } -struct dentry *d_alloc_parallel(struct dentry *parent, - const struct qstr *name) +/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */ +enum alloc_para { + ALLOC_PARA_WAIT, + ALLOC_PARA_FAIL, +}; + +static inline +struct dentry *__d_alloc_parallel(struct dentry *parent, + const struct qstr *name, + enum alloc_para how) { unsigned int hash = name->hash; struct hlist_bl_head *b = in_lookup_hash(parent, hash); @@ -2830,6 +2839,12 @@ struct dentry *d_alloc_parallel(struct dentry *parent, spin_unlock(&dentry->d_lock); goto retry; } + if (unlikely(how == ALLOC_PARA_FAIL)) { + /* mustn't wait for concurrent lookup to complete */ + spin_unlock(&dentry->d_lock); + dput(new); + return ERR_PTR(-EWOULDBLOCK); + } /* * somebody is likely to be still doing lookup for it; * pin it and wait for them to finish @@ -2863,8 +2878,70 @@ struct dentry *d_alloc_parallel(struct dentry *parent, dput(dentry); goto retry; } + +/** + * d_alloc_parallel() - allocate a new dentry and ensure uniqueness + * @parent: dentry of the parent + * @name: name of the dentry within that parent. + * + * A new dentry is allocated and, providing it is unique, added to the + * relevant index. + * If an existing dentry is found with the same parent/name that is + * not d_in_lookup(), then that is returned instead. + * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for + * that lookup to complete before returning the dentry and then ensures the + * match is still valid. + * Thus if the returned dentry is d_in_lookup() then the caller has + * exclusive access until it completes the lookup. + * If the returned dentry is not d_in_lookup() then a lookup has + * already completed. + * + * The @name must already have ->hash set, as can be achieved + * by e.g. try_lookup_noperm(). + * + * Returns: the dentry, whether found or allocated, or an error %-ENOMEM. + */ +struct dentry *d_alloc_parallel(struct dentry *parent, + const struct qstr *name) +{ + return __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT); +} EXPORT_SYMBOL(d_alloc_parallel); +/** + * d_alloc_trylock() - find or allocate a new dentry + * @parent: dentry of the parent + * @name: name of the dentry within that parent. + * + * A new dentry is allocated and, providing it is unique, added to the + * relevant index. + * If an existing dentry is found with the same parent/name that is + * not d_in_lookup() then that is returned instead. + * If the existing dentry is d_in_lookup(), d_alloc_trylock() + * returns with error %-EWOULDBLOCK. + * Thus if the returned dentry is d_in_lookup() then the caller has + * exclusive access until it completes the lookup. + * If the returned dentry is not d_in_lookup() then a lookup has + * already completed. + * + * The @name need not already have ->hash set. + * + * Returns: the dentry, whether found or allocated, or an error + * %-ENOMEM, %-EWOULDBLOCK, %-EACCES (for a bad name) or + * anything returned by ->d_hash(). + */ +struct dentry *d_alloc_trylock(struct dentry *parent, + struct qstr *name) +{ + struct dentry *de; + + de = try_lookup_noperm(name, parent); + if (!de) + de = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL); + return de; +} +EXPORT_SYMBOL(d_alloc_trylock); + /* * Move dentry from in-lookup state to busy-negative one. * diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 4b1ff99608e01c..7afe16d4664d39 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -257,6 +257,7 @@ extern void d_delete(struct dentry *); extern struct dentry * d_alloc(struct dentry *, const struct qstr *); extern struct dentry * d_alloc_anon(struct super_block *); extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *); +extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *); extern struct dentry * d_splice_alias(struct inode *, struct dentry *); /* weird procfs mess; *NOT* exported */ extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *, From 33d76519f0ccb55b975a399f2a9a9c8933fa89d7 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:13 +1000 Subject: [PATCH 0881/2286] VFS: add d_duplicate() Occasionally a single operation can require two sub-operations on the same name, and it is important that a d_alloc_parallel() (once that can be run unlocked) does not create another dentry with the same name between the operations. Two examples: 1/ rename where the target name (a positive dentry) needs to be "silly-renamed" to a temporary name so it will remain available on the server (NFS and AFS). Here the same name needs to be the subject of one rename, and the target of another. 2/ rename where the subject needs to be replaced with a white-out (shmemfs). Here the same name need to be the subject of a rename and the target of a mknod() In both cases the original dentry is renamed to something else, and a replacement is instantiated, possibly as the target of d_move(), possibly by d_instantiate(). Currently d_alloc() is used to create the dentry and the exclusive lock on the parent ensures no other dentry is created. When d_alloc_parallel() is moved out of the parent lock, this will no longer be sufficient. In particular if the original is renamed away before the new is instantiated, there is a window where d_alloc_parallel() could create another name. "silly-rename" does work in this order. shmemfs whiteout doesn't open this hole but is essentially the same pattern and should use the same approach. The new d_duplicate() creates an in-lookup dentry with the same name as the original dentry, which must be hashed. There is no need to check if an in-lookup dentry exists with the same name as d_alloc_parallel() will never try add one while the hashed dentry exists. Once the new in-lookup is created, d_alloc_parallel() will find it and wait for it to complete, then use it. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-5-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 51 ++++++++++++++++++++++++++++++++++++++++++ include/linux/dcache.h | 1 + 2 files changed, 52 insertions(+) diff --git a/fs/dcache.c b/fs/dcache.c index 43149c7849d921..cbd5738de168b0 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -2000,6 +2000,57 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name) } EXPORT_SYMBOL(d_alloc); +/** + * d_duplicate - duplicate a dentry for combined atomic operation + * @dentry: the dentry to duplicate + * + * Some rename operations need to be combined with another operation + * inside the filesystem. + * 1/ A cluster filesystem when renaming to an in-use file might need to + * first "silly-rename" that target out of the way before the main rename + * 2/ A filesystem that supports white-out might want to create a whiteout + * in place of the file being moved. + * + * For this they need two dentries which temporarily have the same name, + * before one is renamed. d_duplicate() provides for this. Given a + * positive hashed dentry, it creates a second in-lookup dentry. + * Because the original dentry exists, no other thread will try to + * create an in-lookup dentry, so there can be no race in this create. + * + * The caller should d_move() the original to a new name, often via a + * rename request, and should call d_lookup_done() on the newly created + * dentry. If the new is instantiated then the old MUST either be moved + * or dropped. + * + * Parent must be locked. + * + * Returns: an in-lookup dentry, or -ENOMEM. + */ +struct dentry *d_duplicate(struct dentry *dentry) +{ + unsigned int hash = dentry->d_name.hash; + struct dentry *parent = dentry->d_parent; + struct hlist_bl_head *b = in_lookup_hash(parent, hash); + struct dentry *new = __d_alloc(parent->d_sb, &dentry->d_name); + + if (unlikely(!new)) + return ERR_PTR(-ENOMEM); + + new->d_flags |= DCACHE_PAR_LOOKUP; + spin_lock(&parent->d_lock); + new->d_parent = dget_dlock(parent); + hlist_add_head(&new->d_sib, &parent->d_children); + if (parent->d_flags & DCACHE_DISCONNECTED) + new->d_flags |= DCACHE_DISCONNECTED; + spin_unlock(&parent->d_lock); + + hlist_bl_lock(b); + hlist_bl_add_head(&new->d_in_lookup_hash, b); + hlist_bl_unlock(b); + return new; +} +EXPORT_SYMBOL(d_duplicate); + struct dentry *d_alloc_anon(struct super_block *sb) { return __d_alloc(sb, NULL); diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 7afe16d4664d39..2b7d99ec930686 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -259,6 +259,7 @@ extern struct dentry * d_alloc_anon(struct super_block *); extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *); extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *); extern struct dentry * d_splice_alias(struct inode *, struct dentry *); +struct dentry *d_duplicate(struct dentry *dentry); /* weird procfs mess; *NOT* exported */ extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *, const struct dentry_operations *); From cc47a1ba1983116ee7a720a79197eb5299ae5d31 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:14 +1000 Subject: [PATCH 0882/2286] VFS: Add LOOKUP_SHARED flag. Some ->lookup handlers will need to drop and retake the parent lock, so they can safely use d_alloc_parallel(). ->lookup can be called with the parent lock either exclusive or shared. A new flag, LOOKUP_SHARED, tells ->lookup how the parent is locked. This is rather ugly, but will be gone soon after we move d_alloc_parallel() out of the directory lock as ->lookup() will *always* called with a shared lock on the parent. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-6-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- fs/namei.c | 20 +++++++++++--------- include/linux/namei.h | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 19bba7fa26f00d..d4b2e5883cbaad 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1933,7 +1933,7 @@ static noinline struct dentry *lookup_slow(const struct qstr *name, struct inode *inode = dir->d_inode; struct dentry *res; inode_lock_shared(inode); - res = __lookup_slow(name, dir, flags); + res = __lookup_slow(name, dir, flags | LOOKUP_SHARED); inode_unlock_shared(inode); return res; } @@ -1947,7 +1947,7 @@ static struct dentry *lookup_slow_killable(const struct qstr *name, if (inode_lock_shared_killable(inode)) return ERR_PTR(-EINTR); - res = __lookup_slow(name, dir, flags); + res = __lookup_slow(name, dir, flags | LOOKUP_SHARED); inode_unlock_shared(inode); return res; } @@ -4440,12 +4440,14 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, int error, create_error; umode_t mode; bool got_write; + unsigned int shared_flag; retry: open_flag = op->open_flag; got_write = false; mode = op->mode; create_error = 0; + shared_flag = (open_flag & O_CREAT) ? 0 : LOOKUP_SHARED; if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { got_write = !mnt_want_write(nd->path.mnt); @@ -4454,10 +4456,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, * a different error; we'll be dropping this one anyway. */ } - if (open_flag & O_CREAT) - inode_lock(dir_inode); - else + if (shared_flag) inode_lock_shared(dir_inode); + else + inode_lock(dir_inode); if (unlikely(IS_DEADDIR(dir_inode))) { dentry = ERR_PTR(-ENOENT); @@ -4526,7 +4528,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (d_in_lookup(dentry)) { struct dentry *res = dir_inode->i_op->lookup(dir_inode, dentry, - nd->flags); + nd->flags | shared_flag); d_lookup_done(dentry); if (unlikely(res)) { if (IS_ERR(res)) { @@ -4574,10 +4576,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (file->f_mode & FMODE_OPENED) fsnotify_open(file); } - if ((open_flag & O_CREAT) || create_error) - inode_unlock(dir_inode); - else + if (shared_flag) inode_unlock_shared(dir_inode); + else + inode_unlock(dir_inode); if (got_write) mnt_drop_write(nd->path.mnt); diff --git a/include/linux/namei.h b/include/linux/namei.h index 86d657b24fc6a1..65f0f5712885c3 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -32,8 +32,9 @@ enum { MAX_NESTED_LINKS = 8 }; #define LOOKUP_CREATE BIT(17) /* ... in object creation */ #define LOOKUP_EXCL BIT(18) /* ... in target must not exist */ #define LOOKUP_RENAME_TARGET BIT(19) /* ... in destination of rename() */ +#define LOOKUP_SHARED BIT(20) /* Parent lock is held shared */ -/* 4 spare bits for intent */ +/* 3 spare bits for intent */ /* Scoping flags for lookup. */ #define LOOKUP_NO_SYMLINKS BIT(24) /* No symlink crossing. */ From 59492f9991dc19d5cc2c34295f1403d0f211e734 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:15 +1000 Subject: [PATCH 0883/2286] VFS: add lockdep monitoring of DCACHE_PAR_LOOKUP lock. DCACHE_PAR_LOOKUP acts like a lock in that threads can block waiting for it to clear. As we plan to make changes to lock order for this lock, teach lockdep to monitor it so as to help detect bugs early. As NFS allocates an in-lookup dentry to unlink a silly-renamed file, and completes the lookup in a different thread, we need interfaces to release and the acquire ownership of the lock. This avoids lockdep complaining that a lock is still held on return to user-space. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-7-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 15 +++++++++++++++ fs/nfs/unlink.c | 3 +++ include/linux/dcache.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/fs/dcache.c b/fs/dcache.c index cbd5738de168b0..83790c7a4dee27 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1901,6 +1901,7 @@ EXPORT_SYMBOL(d_invalidate); static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) { + static struct lock_class_key __lookup_key; struct dentry *dentry; char *dname; int err; @@ -1958,6 +1959,8 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name) dentry->waiters = NULL; INIT_HLIST_NODE(&dentry->d_sib); + lockdep_init_map(&dentry->lookup_map, "DCACHE_PAR_LOOKUP", &__lookup_key, 0); + if (dentry->d_op && dentry->d_op->d_init) { err = dentry->d_op->d_init(dentry); if (err) { @@ -2037,6 +2040,7 @@ struct dentry *d_duplicate(struct dentry *dentry) return ERR_PTR(-ENOMEM); new->d_flags |= DCACHE_PAR_LOOKUP; + lock_map_acquire_try(&new->lookup_map); spin_lock(&parent->d_lock); new->d_parent = dget_dlock(parent); hlist_add_head(&new->d_sib, &parent->d_children); @@ -2801,6 +2805,15 @@ static inline void end_dir_add(struct inode *dir, unsigned int n) static void d_wait_lookup(struct dentry *dentry) { if (likely(d_in_lookup(dentry))) { + /* + * Tell lockdep we will wait for the lookup lock, after + * dropping ->d_lock, but won't actually take it. + */ + spin_release(&dentry->d_lock.dep_map, _THIS_IP_); + lock_map_acquire(&dentry->lookup_map); + lock_map_release(&dentry->lookup_map); + spin_acquire(&dentry->d_lock.dep_map, 0, 1, _THIS_IP_); + dentry->d_flags |= DCACHE_LOOKUP_WAITERS; wait_var_event_spinlock(&dentry->d_flags, !d_in_lookup(dentry), @@ -2923,6 +2936,7 @@ struct dentry *__d_alloc_parallel(struct dentry *parent, } hlist_bl_add_head(&new->d_in_lookup_hash, b); hlist_bl_unlock(b); + lock_map_acquire_try(&new->lookup_map); return new; mismatch: spin_unlock(&dentry->d_lock); @@ -3021,6 +3035,7 @@ static void __d_lookup_unhash(struct dentry *dentry) b = in_lookup_hash(dentry->d_parent, dentry->d_name.hash); hlist_bl_lock(b); dentry->d_flags &= ~DCACHE_PAR_LOOKUP; + lock_map_release(&dentry->lookup_map); __hlist_bl_del(&dentry->d_in_lookup_hash); hlist_bl_unlock(b); dentry->waiters = NULL; diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c index b57cfaa4d516f0..c8d712204e6493 100644 --- a/fs/nfs/unlink.c +++ b/fs/nfs/unlink.c @@ -67,6 +67,7 @@ static void nfs_async_unlink_release(void *calldata) struct super_block *sb = dentry->d_sb; up_read_non_owner(&NFS_I(d_inode(dentry->d_parent))->rmdir_sem); + d_lookup_acquire(dentry); d_lookup_done(dentry); nfs_free_unlinkdata(data); dput(dentry); @@ -159,6 +160,8 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf return ret; } data->dentry = alias; + d_lookup_release(alias); + nfs_do_call_unlink(inode, data); return 1; } diff --git a/include/linux/dcache.h b/include/linux/dcache.h index 2b7d99ec930686..e7e3ef05313b80 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -116,6 +116,8 @@ struct dentry { * possible! */ + /* lockdep tracking of DCACHE_PAR_LOOKUP locks */ + struct lockdep_map lookup_map; struct list_head d_lru; /* LRU list */ struct hlist_node d_sib; /* child of parent list */ struct hlist_head d_children; /* our children */ @@ -554,6 +556,36 @@ static inline int simple_positive(const struct dentry *dentry) unsigned long vfs_pressure_ratio(unsigned long val); +/** + * d_lookup_release - release ownership of DCACHE_PAR_LOOKUP lock + * @dentry: dentry that is locked + * + * If an in-lookup dentry is to be passed to another thread which + * will drop the in-lookup lock, then d_lookup_release() must be called + * to tell lockdep that this thread no lock holds the lock. The + * thread that receives the lock must call d_lookup_acquire() to + * acquire the lock. + */ +static inline void d_lookup_release(struct dentry *dentry) +{ + if (d_in_lookup(dentry)) + lock_map_release(&dentry->lookup_map); +} + +/** + * d_lookup_acquire - acquire ownership of DCACHE_PAR_LOOKUP lock + * @dentry: dentry that is locked + * + * If an in-lookup dentry was passed to this thread, the + * d_lookup_acquire() must be called to tell lockdep that this + * thread now owns the DCACHE_PAR_LOOKUP lock. + */ +static inline void d_lookup_acquire(struct dentry *dentry) +{ + if (d_in_lookup(dentry)) + lock_map_acquire_try(&dentry->lookup_map); +} + /** * d_inode - Get the actual inode of this dentry * @dentry: The dentry to query From fd96e30426ff3e1352309ccf6843dc7fd9d38fde Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sat, 5 Sep 2026 07:48:16 +1000 Subject: [PATCH 0884/2286] VFS: reserve a d_flags bit for fs-specific usage DCACHE_PRIVATE may be used by any filesystem for its own purposes, much like d_fsdata and d_time. I plan to use this in a similar manner the way nfs stores NFS_FSDATA_BLOCKED in d_fsdata. Signed-off-by: NeilBrown Link: https://patch.msgid.link/20260904215142.1060510-8-neilb@ownmail.net Signed-off-by: Christian Brauner (Amutable) --- include/linux/dcache.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/dcache.h b/include/linux/dcache.h index e7e3ef05313b80..adf239f8205f5c 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -238,7 +238,9 @@ enum dentry_flags { DCACHE_PAR_LOOKUP = BIT(24), /* being looked up (with parent locked shared) */ DCACHE_DENTRY_CURSOR = BIT(25), DCACHE_NORCU = BIT(26), /* No RCU delay for freeing */ - DCACHE_PERSISTENT = BIT(27) + DCACHE_PERSISTENT = BIT(27), +/* 28, 29, 30 free */ + DCACHE_PRIVATE = BIT(31) /* fs-specific flag */ }; #define DCACHE_MANAGED_DENTRY \ From 6c21c52cea96bc225f0bb760b2d6ba7ab6acabdf Mon Sep 17 00:00:00 2001 From: Mike Marshall Date: Tue, 15 Sep 2026 12:26:33 -0400 Subject: [PATCH 0885/2286] orangefs_super: don't wait on service operation on system shutdown. If someone reboots the system while an orangefs filesystem is still mounted, don't wait for the completion of an unmount service operation that's never going to return. Signed-off-by: Mike Marshall Link: https://patch.msgid.link/20260915162637.499735-1-hubcap@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/orangefs/super.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fs/orangefs/super.c b/fs/orangefs/super.c index 4ec7329b41f613..e6eedb2faef769 100644 --- a/fs/orangefs/super.c +++ b/fs/orangefs/super.c @@ -383,6 +383,24 @@ static int orangefs_unmount(int id, __s32 fs_id, const char *devname) { struct orangefs_kernel_op_s *op; int r; + + /* + * If someone reboots linux without unmounting orangefs first, + * don't bother firing off an unmount service operation that + * will never complete, or the whole system shutdown + * stalls for ORANGEFS_DEFAULT_OP_TIMEOUT_SECS. + */ + + if (!__is_daemon_in_service() || + system_state == SYSTEM_HALT || + system_state == SYSTEM_POWER_OFF || + system_state == SYSTEM_RESTART) { + gossip_debug(GOSSIP_SUPER_DEBUG, + "%s: dirty unmount.\n", + __func__); + return 0; + } + op = op_alloc(ORANGEFS_VFS_OP_FS_UMOUNT); if (!op) return -ENOMEM; From 0129ac825a37783493d2dd1ac10dbc14b074afbe Mon Sep 17 00:00:00 2001 From: Mike Marshall Date: Tue, 15 Sep 2026 12:30:25 -0400 Subject: [PATCH 0886/2286] orangefs: use ssize_t instead of size_t for error codes... strscpy can return size or errno. Signed-off-by: Mike Marshall Link: https://patch.msgid.link/20260915163027.500798-1-hubcap@kernel.org Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604071606.asPgJ8Fk-lkp@intel.com/ Signed-off-by: Christian Brauner (Amutable) --- fs/orangefs/orangefs-debugfs.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/orangefs/orangefs-debugfs.c b/fs/orangefs/orangefs-debugfs.c index 9f94919a6bc626..fec7c1e864d2cc 100644 --- a/fs/orangefs/orangefs-debugfs.c +++ b/fs/orangefs/orangefs-debugfs.c @@ -238,7 +238,7 @@ void orangefs_debugfs_init(int debug_mask) static void orangefs_kernel_debug_init(void) { static char k_buffer[ORANGEFS_MAX_DEBUG_STRING_LEN] = { }; - size_t len = + ssize_t len = strscpy(k_buffer, kernel_debug_string, sizeof(k_buffer) - 1); if (len > 0) { @@ -246,7 +246,8 @@ static void orangefs_kernel_debug_init(void) k_buffer[len + 1] = '\0'; } else { strscpy(k_buffer, "none\n"); - pr_info("%s: overflow 1!\n", __func__); + if (len <0) + pr_info("%s: overflow 1!\n", __func__); } debugfs_create_file_aux_num(ORANGEFS_KMOD_DEBUG_FILE, 0444, debug_dir, k_buffer, @@ -337,7 +338,7 @@ static int help_show(struct seq_file *m, void *v) static void orangefs_client_debug_init(void) { static char c_buffer[ORANGEFS_MAX_DEBUG_STRING_LEN] = { }; - size_t len = + ssize_t len = strscpy(c_buffer, client_debug_string, sizeof(c_buffer) - 1); if (len > 0) { @@ -345,7 +346,8 @@ static void orangefs_client_debug_init(void) c_buffer[len + 1] = '\0'; } else { strscpy(c_buffer, "none\n"); - pr_info("%s: overflow! 2\n", __func__); + if (len <0) + pr_info("%s: overflow! 2\n", __func__); } client_debug_dentry = debugfs_create_file_aux_num( From b76721a82cbe36ed3c28850566ac9041e71fda28 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 16 Sep 2026 08:57:14 -0700 Subject: [PATCH 0887/2286] pipe: pass a poll key when waking writers after a resize pipe_resize_ring() wakes pipe->wr_wait with a NULL key. ep_poll_callback() and pollwake() only filter on the registered event mask when a key is supplied, so every epoll, poll() and select() waiter on the write queue is woken, whether or not it asked for the event that occurred. The event is the one the comment above the wakeup already names: more room for writers. anon_pipe_write() passes EPOLLOUT | EPOLLWRNORM when waking the same queue; do the same here. Writers blocked in wait_event_interruptible_exclusive() are unaffected, as autoremove_wake_function() ignores the key. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260916-pipe-resize-poll-key-v1-1-0f8f04f7dd5e@debian.org Signed-off-by: Christian Brauner (Amutable) --- fs/pipe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/pipe.c b/fs/pipe.c index 292425a834dd58..5791db016e2426 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -1433,7 +1433,7 @@ int pipe_resize_ring(struct pipe_inode_info *pipe, unsigned int nr_slots) spin_unlock_irq(&pipe->rd_wait.lock); /* This might have made more room for writers */ - wake_up_interruptible(&pipe->wr_wait); + wake_up_interruptible_poll(&pipe->wr_wait, EPOLLOUT | EPOLLWRNORM); return 0; } From 5045900e1bc444192835838a4d1522288a755818 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Wed, 16 Sep 2026 08:51:02 -0700 Subject: [PATCH 0888/2286] splice: pass a poll key when waking pipe readers and writers wakeup_pipe_readers() and wakeup_pipe_writers() wake the pipe wait queues with a NULL key. ep_poll_callback() and pollwake() only filter on the registered event mask when a key is supplied, so every epoll, poll() and select() waiter on the pipe is woken, whether or not it asked for the event that occurred. anon_pipe_read() and anon_pipe_write() already pass EPOLLIN | EPOLLRDNORM and EPOLLOUT | EPOLLWRNORM when waking the same wait queues. Do the same here. This is not visible in common pipe use: ordinary pipelines move data with read()/write(), which already passes the key. While here, both helpers open-code wq_has_sleeper(); use it instead. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260916-b4-splice-poll-key-v2-1-da9ff83469c3@debian.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/splice.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/splice.c b/fs/splice.c index 96cff31d97e1c7..bc243ab8dc431b 100644 --- a/fs/splice.c +++ b/fs/splice.c @@ -177,9 +177,9 @@ static const struct pipe_buf_operations user_page_pipe_buf_ops = { static void wakeup_pipe_readers(struct pipe_inode_info *pipe) { - smp_mb(); - if (waitqueue_active(&pipe->rd_wait)) - wake_up_interruptible(&pipe->rd_wait); + if (wq_has_sleeper(&pipe->rd_wait)) + wake_up_interruptible_poll(&pipe->rd_wait, + EPOLLIN | EPOLLRDNORM); kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN); } @@ -413,9 +413,9 @@ EXPORT_SYMBOL(nosteal_pipe_buf_ops); static void wakeup_pipe_writers(struct pipe_inode_info *pipe) { - smp_mb(); - if (waitqueue_active(&pipe->wr_wait)) - wake_up_interruptible(&pipe->wr_wait); + if (wq_has_sleeper(&pipe->wr_wait)) + wake_up_interruptible_poll(&pipe->wr_wait, + EPOLLOUT | EPOLLWRNORM); kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT); } From 9a82c64e133fdaf6e250dcb25c57e8d0d837ea64 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 21 Sep 2026 03:45:42 -0700 Subject: [PATCH 0889/2286] vmcore: report the release of the crashed kernel kdump runs in a very small environment and cut-down set of drivers, and it can die before the dump lands: it runs out of memory, a device hangs on reset, or a watchdog fires while the dump is still being written out. This happens about a single high digit percentage on most of big scale datacenters. When that happens the only evidence left is what the capture kernel printed to the console, and that does not say which kernel crashed. A fleet typically pins one kdump image and boots it after crashes from many different production kernels, and it is useful to understand what are the combination of crashed kernel panic and succesful kdump collection. Whether the dump completes at all tends to depend on the kernel that died: memory pinned, devices in flight, dump size. The release is already recorded. crash_save_vmcoreinfo_init() puts OSRELEASE= in the VMCOREINFO note, and by the time vmcore_init() has run parse_crash_elf_headers() that note sits in elfnotes_buf. Walk the notes for it and print: vmcore: dump is from kernel 7.3.0-rc3-next-20260917 Given this is cheap and useful, I think it is worth adding to vmcore, to improve kdump monitoring. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260921-kdump_print-v1-1-e445fec3177c@debian.org Signed-off-by: Christian Brauner (Amutable) --- fs/proc/vmcore.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fs/proc/vmcore.c b/fs/proc/vmcore.c index 44d15436439fd9..406898247d7af1 100644 --- a/fs/proc/vmcore.c +++ b/fs/proc/vmcore.c @@ -1709,6 +1709,24 @@ static void vmcore_free_device_dumps(void) #endif /* CONFIG_PROC_VMCORE_DEVICE_DUMP */ } +#define VMCOREINFO_OSRELEASE_KEY "OSRELEASE=" + +static void __init vmcore_report_crashed_release(void) +{ + const char *ver, *eol; + + ver = strnstr(elfnotes_buf, VMCOREINFO_OSRELEASE_KEY, elfnotes_sz); + if (!ver) + return; + + ver += sizeof(VMCOREINFO_OSRELEASE_KEY) - 1; + eol = memchr(ver, '\n', elfnotes_buf + elfnotes_sz - ver); + if (!eol) + return; + + pr_notice("dump is from kernel %.*s\n", (int)(eol - ver), ver); +} + /* Init function for vmcore module. */ static int __init vmcore_init(void) { @@ -1733,6 +1751,8 @@ static int __init vmcore_init(void) elfcorehdr_free(elfcorehdr_addr); elfcorehdr_addr = ELFCORE_ADDR_ERR; + vmcore_report_crashed_release(); + proc_vmcore = proc_create("vmcore", S_IRUSR, NULL, &vmcore_proc_ops); if (proc_vmcore) proc_vmcore->size = vmcore_size; From a647a53bc89a046bee4a12a0a5627f2dfaaef807 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 18 Sep 2026 04:01:57 -0700 Subject: [PATCH 0890/2286] dcache: report a Tasks-RCU quiescent state in dentry_kill() I am constantly seeing the following warning on Meta's fleet: INFO: rcu_tasks detected stalls on tasks: native_queued_spin_lock_slowpath locked_inode_to_wb_and_lock_list inode_io_list_del evict __dentry_kill shrink_dentry_list shrink_dcache_tree d_invalidate btrfs_delete_subvolume btrfs_ioctl_snap_destroy btrfs_ioctl do_syscall_64 dentry_kill() is the common funnel for dentry teardown: dput(), shrink_dentry_list() and shrink_dcache_tree() all reach it, once per dentry, and its cond_resched() is the only reschedule point on that path. cond_resched() is not an RCU-tasks quiescent state. Use cond_resched_tasks_rcu_qs() so the teardown reports an RCU-tasks quiescent state as it proceeds. Fixes: 9c5f1d30199d ("dcache: move cond_resched() into the end of __dentry_kill()") Cc: stable@vger.kernel.org Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260918-dcache-task-rcu-qa-v1-1-963ec83e6d0b@debian.org Reviewed-by: Paul E. McKenney Signed-off-by: Christian Brauner (Amutable) --- fs/dcache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/dcache.c b/fs/dcache.c index 8f17db2724b38a..e974d42b03e72c 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -843,7 +843,7 @@ static struct dentry *dentry_kill(struct dentry *dentry) if (dentry->d_op && dentry->d_op->d_release) dentry->d_op->d_release(dentry); - cond_resched(); + cond_resched_tasks_rcu_qs(); /* now that it's negative, ->d_parent is stable */ if (!IS_ROOT(dentry)) { parent = dentry->d_parent; From 4424f06120fda0f63f3d9e9992990d7790d902a4 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 12 Sep 2026 19:14:51 -0700 Subject: [PATCH 0891/2286] kernfs: activate a new node without dropping kernfs_rwsem kernfs_add_one() links the node in, drops the kernfs_rwsem write lock, then calls kernfs_activate(), which takes it again. Roots that do not set KERNFS_ROOT_CREATE_DEACTIVATED, such as sysfs, therefore pay two write locks for every file, directory and symlink created. A new node has no children, so kernfs_activate() would walk only that node. Call kernfs_activate_one() before dropping the lock instead. Its two WARN_ON_ONCE()s still hold: the node was just linked, and nothing can have changed its active count yet. This also closes the window where a node is linked but not yet activated. lock_stat, creating and destroying five dummy netdevs: before after kernfs_rwsem write acquires 1200 830 Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260913021453.21507-2-shakeel.butt@linux.dev Acked-by: Tejun Heo Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 07abf59f0264a6..8953e8a07537ce 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -30,6 +30,8 @@ static char kernfs_pr_cont_buf[PATH_MAX]; /* protected by pr_cont_lock */ #define rb_to_kn(X) rb_entry((X), struct kernfs_node, rb) +static void kernfs_activate_one(struct kernfs_node *kn); + static bool __kernfs_active(struct kernfs_node *kn) { return atomic_read(&kn->active) >= 0; @@ -861,7 +863,6 @@ int kernfs_add_one(struct kernfs_node *kn) } up_write(&root->kernfs_iattr_rwsem); - up_write(&root->kernfs_rwsem); /* * Activate the new node unless CREATE_DEACTIVATED is requested. @@ -869,9 +870,15 @@ int kernfs_add_one(struct kernfs_node *kn) * activating the node with kernfs_activate(). A node which hasn't * been activated is not visible to userland and its removal won't * trigger deactivation. + * + * @kn has no children yet, so kernfs_activate() would walk only @kn. + * Do it here rather than dropping the write lock and taking it again + * for every new node. */ - if (!(kernfs_root(kn)->flags & KERNFS_ROOT_CREATE_DEACTIVATED)) - kernfs_activate(kn); + if (!(root->flags & KERNFS_ROOT_CREATE_DEACTIVATED)) + kernfs_activate_one(kn); + + up_write(&root->kernfs_rwsem); return 0; out_unlock: From 8b64f32fee95a977b137b0dd5050bc68d93f6f60 Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 12 Sep 2026 19:14:52 -0700 Subject: [PATCH 0892/2286] kernfs: allocate the new name outside kernfs_rwsem kernfs_rename_ns() calls kstrdup_const() while holding the kernfs_rwsem write lock. It is the only GFP_KERNEL allocation under that lock in kernfs, so a rename can enter reclaim while every create, remove and rename in the hierarchy waits behind it. Copy the name before taking the lock and free the copy again if the rename turns out not to need it. A rename that keeps the name, hits a collision or finds the node gone now does one extra kstrdup_const(); renames are rare. -ENOMEM is still reported at the same point as before, so nothing changes about which error a caller sees. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260913021453.21507-3-shakeel.butt@linux.dev Acked-by: Tejun Heo Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 8953e8a07537ce..3bbdd9a8acc892 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1819,6 +1819,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, const char *new_name, const struct ns_common *new_ns) { struct kernfs_node *old_parent; + const char *dup_name = NULL; struct kernfs_root *root; const char *old_name; bool reparent; @@ -1828,6 +1829,9 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, if (!rcu_access_pointer(kn->__parent)) return -EINVAL; + if (new_name) + dup_name = kstrdup_const(new_name, GFP_KERNEL); + root = kernfs_root(kn); down_write(&root->kernfs_rwsem); @@ -1859,9 +1863,10 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, /* rename kernfs_node */ if (strcmp(old_name, new_name) != 0) { error = -ENOMEM; - new_name = kstrdup_const(new_name, GFP_KERNEL); - if (!new_name) + if (!dup_name) goto out; + new_name = dup_name; + dup_name = NULL; } else { new_name = NULL; } @@ -1901,6 +1906,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, error = 0; out: up_write(&root->kernfs_rwsem); + kfree_const(dup_name); return error; } From 09452e5114bd59ea79e05b5ca4579df898178bfd Mon Sep 17 00:00:00 2001 From: Shakeel Butt Date: Sat, 12 Sep 2026 19:14:53 -0700 Subject: [PATCH 0893/2286] kernfs: free the old name outside kernfs_rwsem kernfs_rename_ns() frees the replaced name with kfree_rcu_mightsleep() while still holding the kernfs_rwsem write lock. If the batching allocation fails, which is what happens under memory pressure, kvfree_call_rcu() falls back to a full synchronize_rcu() before freeing. A rename can then wait out a grace period with the write lock held, and every create, remove and rename in the hierarchy waits with it. The name is already unpublished by then, so nothing needs the free to happen under the lock. Move it past the unlock. Signed-off-by: Shakeel Butt Link: https://patch.msgid.link/20260913021453.21507-4-shakeel.butt@linux.dev Acked-by: Tejun Heo Signed-off-by: Christian Brauner (Amutable) --- fs/kernfs/dir.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/kernfs/dir.c b/fs/kernfs/dir.c index 3bbdd9a8acc892..41d693114cdc86 100644 --- a/fs/kernfs/dir.c +++ b/fs/kernfs/dir.c @@ -1820,6 +1820,7 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, { struct kernfs_node *old_parent; const char *dup_name = NULL; + const char *put_name = NULL; struct kernfs_root *root; const char *old_name; bool reparent; @@ -1901,12 +1902,14 @@ int kernfs_rename_ns(struct kernfs_node *kn, struct kernfs_node *new_parent, kernfs_link_sibling(kn); if (new_name && !is_kernel_rodata((unsigned long)old_name)) - kfree_rcu_mightsleep(old_name); + put_name = old_name; error = 0; out: up_write(&root->kernfs_rwsem); kfree_const(dup_name); + if (put_name) + kfree_rcu_mightsleep(put_name); return error; } From 98abb2b35835a7d62a53ad87942ff52c8babdb9e Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Sat, 19 Sep 2026 01:51:03 +0900 Subject: [PATCH 0894/2286] fs: remove unused vfs_empty_path() Since commit e896474fe485 ("getname_maybe_null() - the third variant of pathname copy-in"), vfs_empty_path() has no callers. So remove it. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260918165105.1013792-1-ekffu200098@gmail.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- include/linux/fs.h | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/include/linux/fs.h b/include/linux/fs.h index 05d05bb9c951a2..ef8eb5af06c5cf 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3653,23 +3653,6 @@ extern int vfs_fadvise(struct file *file, loff_t offset, loff_t len, extern int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice); -static inline bool vfs_empty_path(int dfd, const char __user *path) -{ - char c; - - if (dfd < 0) - return false; - - /* We now allow NULL to be used for empty path. */ - if (!path) - return true; - - if (unlikely(get_user(c, path))) - return false; - - return !c; -} - int generic_atomic_write_valid(struct kiocb *iocb, struct iov_iter *iter); static inline bool extensible_ioctl_valid(unsigned int cmd_a, From b53f422491af2a134a57c1cffea67c575a1f699f Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 19 Sep 2026 23:39:00 +0200 Subject: [PATCH 0895/2286] initramfs: Reduce hardlink hash allocation sizes Each hardlink hash entry reserves N_ALIGN(PATH_MAX) bytes for its name. This makes every allocation larger than 4 KiB, placing it in the kmalloc-8k bucket even for short names. Use a flexible array with the already validated cpio name_len to reduce allocation sizes. Also use const for the read-only name parameter. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260919213859.24667-3-blum@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- init/initramfs.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/init/initramfs.c b/init/initramfs.c index 3cee8b50ad8281..ebddde9c8f0d91 100644 --- a/init/initramfs.c +++ b/init/initramfs.c @@ -80,7 +80,7 @@ static __initdata struct hash { int ino, minor, major; umode_t mode; struct hash *next; - char name[N_ALIGN(PATH_MAX)]; + char name[]; } *head[32]; static __initdata bool hardlink_seen; @@ -92,7 +92,7 @@ static inline int hash(int major, int minor, int ino) } static char __init *find_link(int major, int minor, int ino, - umode_t mode, char *name) + umode_t mode, const char *name, size_t nlen) { struct hash **p, *q; for (p = head + hash(major, minor, ino); *p; p = &(*p)->next) { @@ -106,14 +106,15 @@ static char __init *find_link(int major, int minor, int ino, continue; return (*p)->name; } - q = kmalloc_obj(struct hash); + + q = kmalloc_flex(struct hash, name, nlen); if (!q) panic_show_mem("can't allocate link hash entry"); q->major = major; q->minor = minor; q->ino = ino; q->mode = mode; - strscpy(q->name, name); + strscpy(q->name, name, nlen); q->next = NULL; *p = q; hardlink_seen = true; @@ -355,7 +356,7 @@ static void __init clean_path(char *path, umode_t fmode) static int __init maybe_link(void) { if (nlink >= 2) { - char *old = find_link(major, minor, ino, mode, collected); + char *old = find_link(major, minor, ino, mode, collected, name_len); if (old) { clean_path(collected, 0); return (init_link(old, collected) < 0) ? -1 : 1; From 168a8c13159c6e3f0f08da6f8fa2f633a91ba9fd Mon Sep 17 00:00:00 2001 From: Xin Yin Date: Thu, 24 Sep 2026 14:54:43 +0800 Subject: [PATCH 0896/2286] writeback: size foreign flushes by target wb dirty pages Foreign dirty tracking records the bdi and wb memcg IDs of recently dirtied foreign inodes. When the source memcg needs foreign writeback, mem_cgroup_flush_foreign() queues WB_REASON_FOREIGN_FLUSH to the recorded target bdi_writeback. cgroup_writeback_by_id() currently sizes this best-effort work from the target memcg's NR_FILE_DIRTY counter. The work is scoped to one target wb, so a memcg-wide dirty count can over-size a single-bdi flush when the target memcg has dirty pages on other devices. It can also under-size the flush when the target wb has dirty pages charged to other memcgs. Over-sizing keeps the target wb busy longer and can delay later writeback work. Under-sizing can finish before enough pages are written back for the source memcg. Use the target wb's WB_RECLAIMABLE counter and keep the existing 25% headroom. This keeps the budget aligned with the writeback object being queued. Fixes: 97b27821b485 ("writeback, memcg: Implement foreign dirty flushing") Reviewed-by: Jan Kara Acked-by: Tejun Heo Signed-off-by: Xin Yin Link: https://patch.msgid.link/20260924065443.1650126-1-yinxin.x@bytedance.com Signed-off-by: Christian Brauner (Amutable) --- fs/fs-writeback.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index f303936446daff..d26b2cf05283a3 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -1166,7 +1166,7 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id, struct cgroup_subsys_state *memcg_css; struct bdi_writeback *wb; struct wb_writeback_work *work; - unsigned long dirty; + long dirty; int ret; /* lookup bdi and memcg */ @@ -1195,16 +1195,13 @@ int cgroup_writeback_by_id(u64 bdi_id, int memcg_id, } /* - * The caller is attempting to write out most of - * the currently dirty pages. Let's take the current dirty page - * count and inflate it by 25% which should be large enough to - * flush out most dirty pages while avoiding getting livelocked by - * concurrent dirtiers. - * - * BTW the memcg stats are flushed periodically and this is best-effort - * estimation, so some potential error is ok. + * The caller is attempting to write out most of the target wb's + * currently dirty pages. Size the work from the wb's reclaimable pages + * and inflate the count by 25%, which should be large enough to flush + * out most dirty pages while avoiding getting livelocked by concurrent + * dirtiers. */ - dirty = memcg_page_state(mem_cgroup_from_css(memcg_css), NR_FILE_DIRTY); + dirty = wb_stat_sum(wb, WB_RECLAIMABLE); dirty = dirty * 10 / 8; /* issue the writeback work */ From dbb3d99ee95c32711950850ddd33eb22a6042eb2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 25 Sep 2026 07:35:10 -0700 Subject: [PATCH 0897/2286] ACPICA: Fix memory leak in acpi_ds_create_field() acpi_ds_create_field() allocates internal_pcc_buffer for operation regions in the PCC address space. The function runs once per field, so a second field on the same region overwrites the pointer and orphans the previous buffer. The matching ACPI_FREE() in acpi_ut_delete_internal_obj() only ever sees the last one. An arm64 platform whose SSDT declares regions with multiple fields each leaks two buffers at boot, as detected by kmemleak: unreferenced object 0xffff000051e65400 (size 192): comm "swapper/0", pid 1, jiffies 4294668436 backtrace (crc 0): __kmalloc_noprof acpi_ds_create_field acpi_ds_load2_end_op acpi_ds_exec_end_op acpi_ps_parse_loop acpi_ps_parse_aml acpi_ns_load_table acpi_load_tables acpi_init Do not allocate a region if it was previously allocated before, preserving the current design, but, not leaking memory. Fixes: aa6ec56b574d ("ACPICA: ACPI 6.3: add PCC operation region support for AML interpreter") Link: https://github.com/open-acpica/acpica/commit/c71849d476bb5f97ca1867849e893368e12a69e6 Signed-off-by: Breno Leitao [ rjw: Subject tweak, fixed the Fixes: tag ] Link: https://patch.msgid.link/20260925-acpica-pcc-field-leak-v2-1-e37008e715af@debian.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpica/dsfield.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/dsfield.c b/drivers/acpi/acpica/dsfield.c index d7d56cc600d318..f7e503bf57c87c 100644 --- a/drivers/acpi/acpica/dsfield.c +++ b/drivers/acpi/acpica/dsfield.c @@ -522,7 +522,8 @@ acpi_ds_create_field(union acpi_parse_object *op, } if (info.region_node->object->region.space_id == - ACPI_ADR_SPACE_PLATFORM_COMM) { + ACPI_ADR_SPACE_PLATFORM_COMM && + !region_node->object->field.internal_pcc_buffer) { region_node->object->field.internal_pcc_buffer = ACPI_ALLOCATE_ZEROED(info.region_node->object->region. length); From 888dab4b04ea8725168c6d50d0bfb8057fccca18 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:42:59 +0100 Subject: [PATCH 0898/2286] ACPICA: Fix PCC OperationRegion command offsets ACPI 6.3, section 5.5.2.4.7.3, states that the PCC Operation Region is associated with the region of shared memory that follows the PCC signature. The generic and extended PCC shared memory layouts include the 4-byte signature at offset 0, so their raw shared memory COMMAND fields are at offsets 4 and 12 respectively. Since AML field offsets for the PCC OperationRegion are relative to the region after that signature, ACPICA must look for those COMMAND fields at OperationRegion offsets 0 and 8. Adjust the generic and master subspace command checks to use those OperationRegion-relative offsets. Otherwise writes to the COMMAND field can fail to invoke the PCC address space handler at the offset described by the PCC OperationRegion definition. Link: https://github.com/open-acpica/acpica/pull/1205 Fixes: aa6ec56b574d ("ACPICA: ACPI 6.3: add PCC operation region support for AML interpreter") Signed-off-by: Sudeep Holla [ rjw: Fixed the Fixes: tag ] Link: https://patch.msgid.link/20260922144302.3847593-2-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpica/exfield.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c index 9a55524ed8f423..50317fe0cbd5ec 100644 --- a/drivers/acpi/acpica/exfield.c +++ b/drivers/acpi/acpica/exfield.c @@ -45,12 +45,13 @@ static const u8 acpi_protocol_lengths[] = { /* * The following macros determine a given offset is a COMD field. - * According to the specification, generic subspaces (types 0-2) contains a - * 2-byte COMD field at offset 4 and master subspaces (type 3) contains a 4-byte - * COMD field starting at offset 12. + * According to the specification, the PCC OperationRegion begins after + * the PCC signature. The raw shared memory COMD offsets of 4 for generic + * subspaces (types 0-2) and 12 for master subspaces (type 3) therefore + * appear at OperationRegion offsets 0 and 8. */ -#define GENERIC_SUBSPACE_COMMAND(a) (4 == a || a == 5) -#define MASTER_SUBSPACE_COMMAND(a) (12 <= a && a <= 15) +#define GENERIC_SUBSPACE_COMMAND(a) (0 == a || a == 1) +#define MASTER_SUBSPACE_COMMAND(a) (8 <= a && a <= 11) /******************************************************************************* * From 46c263c213876bdc2297ef61aa90f0b210ebac73 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:00 +0100 Subject: [PATCH 0899/2286] ACPI: PCC: Preserve shared memory signature in OpRegion handler ACPI 6.3 introduced PCC OperationRegions. Section 5.5.2.4.7.3, "Declaring message fields within a PCC OperationRegion", states that, for all PCC subspace types, the PCC Operation Region pertains to the region of PCC subspace that succeeds the PCC signature. Its length therefore excludes the 4-byte signature. The PCC address space handler currently copies the OperationRegion buffer to and from the start of the shared memory. This overwrites or exposes the platform-populated signature and omits the final four bytes of the OperationRegion. Offset the copies by the size of the signature and reject an OperationRegion that does not fit in the remaining shared memory. The example added by commit 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") incorrectly included the signature as an OperationRegion field. A Type 3 OperationRegion containing a 100-byte communication area is declared as follows: OperationRegion (PFRM, PCC, 2, 0x70) Field (PFRM, ByteAcc, NoLock, Preserve) { FLGS, 32, LEN, 32, CMD, 32, DATA, 800 } This contains three 32-bit fields followed by a 100-byte DATA field. The fields correspond to the Type 3 shared memory layout in ACPI 6.3, Table 14-372, after excluding its 4-byte signature. Fixes: 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-3-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 438c6718951128..9881c9ee293d10 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -28,6 +28,7 @@ * to PCC commands */ #define PCC_CMD_WAIT_RETRIES_NUM 500ULL +#define PCC_SIGNATURE_SIZE sizeof(u32) struct pcc_data { struct pcc_mbox_chan *pcc_chan; @@ -74,6 +75,14 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, } pcc_chan = data->pcc_chan; + if (pcc_chan->shmem_size < PCC_SIGNATURE_SIZE || + ctx->length > pcc_chan->shmem_size - PCC_SIGNATURE_SIZE) { + pr_err("PCC channel-%d shared memory is too small.\n", + ctx->subspace_id); + ret = AE_AML_REGION_LIMIT; + goto err_free_channel; + } + if (!pcc_chan->mchan->mbox->txdone_irq) { pr_err("This channel-%d does not support interrupt.\n", ctx->subspace_id); @@ -97,14 +106,17 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, u32 bits, acpi_integer *value, void *handler_context, void *region_context) { - int ret; struct pcc_data *data = region_context; + void __iomem *pcc_opregion; u64 usecs_lat; + int ret; + + pcc_opregion = data->pcc_chan->shmem + PCC_SIGNATURE_SIZE; reinit_completion(&data->done); - /* Write to Shared Memory */ - memcpy_toio(data->pcc_chan->shmem, (void *)value, data->ctx.length); + /* Write to the PCC OperationRegion after the shared memory signature. */ + memcpy_toio(pcc_opregion, (void *)value, data->ctx.length); ret = mbox_send_message(data->pcc_chan->mchan, NULL); if (ret < 0) @@ -125,7 +137,7 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, mbox_chan_txdone(data->pcc_chan->mchan, ret); - memcpy_fromio(value, data->pcc_chan->shmem, data->ctx.length); + memcpy_fromio(value, pcc_opregion, data->ctx.length); return AE_OK; } From fa9c7ee692988edddc1da74bc5257d53dd3c7130 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:01 +0100 Subject: [PATCH 0900/2286] ACPI: PCC: Free channel on OpRegion deactivation ACPICA calls the address space setup callback with ACPI_REGION_DEACTIVATE when a PCC OperationRegion is torn down. The PCC setup callback currently allocates a fresh pcc_data and requests the mailbox channel before looking at the function argument. If ACPICA deactivates a region, this can leave the existing region context and mailbox channel unreleased, and may also request a channel during teardown. Handle ACPI_REGION_DEACTIVATE before allocation. Free the PCC mailbox channel, release the region context and clear the context pointer. Fixes: 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-4-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 9881c9ee293d10..57d13b25c1d6b7 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -55,6 +55,19 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, struct pcc_mbox_chan *pcc_chan; acpi_status ret; + if (function == ACPI_REGION_DEACTIVATE) { + data = *region_context; + if (data) { + pcc_mbox_free_channel(data->pcc_chan); + kfree(data); + *region_context = NULL; + } + return AE_OK; + } + + if (function != ACPI_REGION_ACTIVATE) + return AE_BAD_PARAMETER; + data = kzalloc_obj(*data); if (!data) return AE_NO_MEMORY; From c0232f00f703a409bb6e6080d94f6dc7fdebe5ca Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:02 +0100 Subject: [PATCH 0901/2286] ACPI: PCC: Cache OpRegion command timeout The PCC OperationRegion handler computes the same command completion wait timeout each time it sends a command. The timeout is derived from static channel properties, so compute it once when the PCC channel is set up and store the millisecond value in the mailbox client timeout field. Use the cached timeout when waiting for the OperationRegion command to complete. This keeps the timeout calculation in one place and avoids recomputing it for every command. Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-5-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 57d13b25c1d6b7..345f233d77cdcf 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -50,10 +50,11 @@ static acpi_status acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, void *handler_context, void **region_context) { - struct pcc_data *data; struct acpi_pcc_info *ctx = handler_context; struct pcc_mbox_chan *pcc_chan; + struct pcc_data *data; acpi_status ret; + u64 usecs_lat; if (function == ACPI_REGION_DEACTIVATE) { data = *region_context; @@ -103,6 +104,16 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, goto err_free_channel; } + /* + * pcc_chan->latency is just a Nominal value. In reality the remote + * processor could be much slower to reply. So add an arbitrary + * amount of wait on top of Nominal. + */ + usecs_lat = PCC_CMD_WAIT_RETRIES_NUM * pcc_chan->latency; + data->cl.tx_tout = DIV_ROUND_UP_ULL(usecs_lat, 1000); + if (!data->cl.tx_tout) + data->cl.tx_tout = 1; + *region_context = data; return AE_OK; @@ -121,7 +132,6 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, { struct pcc_data *data = region_context; void __iomem *pcc_opregion; - u64 usecs_lat; int ret; pcc_opregion = data->pcc_chan->shmem + PCC_SIGNATURE_SIZE; @@ -135,14 +145,8 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, if (ret < 0) return AE_ERROR; - /* - * pcc_chan->latency is just a Nominal value. In reality the remote - * processor could be much slower to reply. So add an arbitrary - * amount of wait on top of Nominal. - */ - usecs_lat = PCC_CMD_WAIT_RETRIES_NUM * data->pcc_chan->latency; ret = wait_for_completion_timeout(&data->done, - usecs_to_jiffies(usecs_lat)); + msecs_to_jiffies(data->cl.tx_tout)); if (ret == 0) { pr_err("PCC command executed timeout!\n"); return AE_TIME; From 57c59c0bda8b19d8cbb877f61dbc8372e5f97749 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 25 Sep 2026 11:23:35 +0200 Subject: [PATCH 0902/2286] driver core/ACPI: Introduce companion_bus_register() The ACPI bus type does not allow drivers to be matched to devices, so the sysfs attributes related to drivers created for it are useless and their existence is confusing. Moreover, it is better to prevent drivers from being registered and looked up for a bus like that. To allow skipping the creation of those sysfs attributes and preventing driver registration and lookup for the ACPI bus type, introduce a "companion" bus type concept and add a special registration function for registering "companion" bus types, companion_bus_register(). The "drivers" directory under the ACPI bus type is still needed because there are versions of systemd that depend on it [1]. Link: https://lore.kernel.org/linux-acpi/SN6PR02MB41575266A4580339E186E5D9D4812@SN6PR02MB4157.namprd02.prod.outlook.com/ [1] Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Danilo Krummrich Tested-by: Michael Kelley Reviewed-by: Michael Kelley [ rjw: Add comment regarding drivers_kset creation in bus_register_internal() ] Link: https://patch.msgid.link/12985239.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/bus.c | 8 +---- drivers/base/base.h | 2 ++ drivers/base/bus.c | 67 ++++++++++++++++++++++++++++++-------- include/linux/device/bus.h | 1 + 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 808c6746be14dd..48f0b4809f9495 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -1120,11 +1120,6 @@ EXPORT_SYMBOL_GPL(acpi_driver_match_device); ACPI Bus operations -------------------------------------------------------------------------- */ -static int acpi_bus_match(struct device *dev, const struct device_driver *drv) -{ - return 0; -} - static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env *env) { return __acpi_device_uevent_modalias(to_acpi_device(dev), env); @@ -1132,7 +1127,6 @@ static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env * const struct bus_type acpi_bus_type = { .name = "acpi", - .match = acpi_bus_match, .uevent = acpi_device_uevent, }; @@ -1451,7 +1445,7 @@ static int __init acpi_bus_init(void) */ acpi_root_dir = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL); - result = bus_register(&acpi_bus_type); + result = companion_bus_register(&acpi_bus_type); if (!result) return 0; diff --git a/drivers/base/base.h b/drivers/base/base.h index a5b7abc10ff02c..21c7d244f8aa7c 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -27,6 +27,7 @@ * @drivers_autoprobe: gate whether new devices are automatically attached to * registered drivers, or new drivers automatically attach * to existing devices. + * @no_drivers: gate whether drivers can be registered. * @bus: pointer back to the struct bus_type that this structure is associated * with. * @dev_root: Default device to use as the parent. @@ -51,6 +52,7 @@ struct subsys_private { struct klist klist_drivers; struct blocking_notifier_head bus_notifier; unsigned int drivers_autoprobe:1; + unsigned int no_drivers:1; const struct bus_type *bus; struct device *dev_root; diff --git a/drivers/base/bus.c b/drivers/base/bus.c index d17bd91490ee4e..d89333806a4e9e 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -738,6 +738,11 @@ int bus_add_driver(struct device_driver *drv) if (!sp) return -EINVAL; + if (sp->no_drivers) { + error = -ENXIO; + goto out_put_bus; + } + /* * Reference in sp is now incremented and will be dropped when * the driver is removed from the bus @@ -930,15 +935,7 @@ static ssize_t bus_uevent_store(const struct bus_type *bus, static struct bus_attribute bus_attr_uevent = __ATTR(uevent, 0200, NULL, bus_uevent_store); -/** - * bus_register - register a driver-core subsystem - * @bus: bus to register - * - * Once we have that, we register the bus with the kobject - * infrastructure, then register the children subsystems it has: - * the devices and drivers that belong to the subsystem. - */ -int bus_register(const struct bus_type *bus) +static int bus_register_internal(const struct bus_type *bus, bool use_drivers) { int retval; struct subsys_private *priv; @@ -960,7 +957,8 @@ int bus_register(const struct bus_type *bus) bus_kobj->kset = bus_kset; bus_kobj->ktype = &bus_ktype; - priv->drivers_autoprobe = 1; + priv->drivers_autoprobe = use_drivers; + priv->no_drivers = !use_drivers; retval = kset_register(&priv->subsys); if (retval) @@ -976,6 +974,10 @@ int bus_register(const struct bus_type *bus) goto bus_devices_fail; } + /* + * Create the drivers kset even if use_drivers is unset to prevent some + * older versions of systemd from failing. + */ priv->drivers_kset = kset_create_and_add("drivers", NULL, bus_kobj); if (!priv->drivers_kset) { retval = -ENOMEM; @@ -989,9 +991,11 @@ int bus_register(const struct bus_type *bus) klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); - retval = add_probe_files(bus); - if (retval) - goto bus_probe_files_fail; + if (use_drivers) { + retval = add_probe_files(bus); + if (retval) + goto bus_probe_files_fail; + } retval = sysfs_create_groups(bus_kobj, bus->bus_groups); if (retval) @@ -1016,8 +1020,40 @@ int bus_register(const struct bus_type *bus) kfree(priv); return retval; } + +/** + * bus_register - register a driver-core subsystem + * @bus: bus to register + * + * Once we have that, we register the bus with the kobject + * infrastructure, then register the children subsystems it has: + * the devices and drivers that belong to the subsystem. + */ +int bus_register(const struct bus_type *bus) +{ + return bus_register_internal(bus, true); +} EXPORT_SYMBOL_GPL(bus_register); +/** + * companion_bus_register - register a companion bus type + * @bus: companion bus to register + * + * A companion bus is a bus without drivers. Devices that belong to it can be + * bound to other devices as their "companions" and represent interfaces that + * can be used by the drivers of those other devices. They may also be used for + * the enumeration of those other devices. + * + * The ACPI bus is a specific example of a companion bus. + * + * Registering a companion bus is like registering a regular bus except that it + * skips the creation of sysfs interfaces related to drivers for @bus. + */ +int companion_bus_register(const struct bus_type *bus) +{ + return bus_register_internal(bus, false); +} + /** * bus_unregister - remove a bus from the system * @bus: bus. @@ -1415,6 +1451,11 @@ struct device_driver *driver_find(const char *name, const struct bus_type *bus) if (!sp) return NULL; + if (sp->no_drivers) { + subsys_put(sp); + return NULL; + } + k = kset_find_obj(sp->drivers_kset, name); subsys_put(sp); if (!k) diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index a38f7229b8f4d0..89e156b9acb587 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -113,6 +113,7 @@ struct bus_type { bool need_parent_lock; }; +int __must_check companion_bus_register(const struct bus_type *bus); int __must_check bus_register(const struct bus_type *bus); void bus_unregister(const struct bus_type *bus); From 62d6cb1f7de0f88c6f13415e0cdb413b09cdf348 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 25 Sep 2026 11:26:24 -0500 Subject: [PATCH 0903/2286] ACPI: Add stub for acpi_find_child_device() Provide a NULL-returning stub when CONFIG_ACPI is disabled so callers can be compiled without ACPI support. Reported-by: Arnd Bergmann Closes: https://lore.kernel.org/linux-sound/20260925131104.118993-1-arnd@kernel.org/ Suggested-by: Arnd Bergmann Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260925162628.4142365-1-mario.limonciello@amd.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index dca4df750c5481..c060eb8f455646 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -943,6 +943,13 @@ u32 arch_acpi_add_auto_dep(acpi_handle handle); #else /* CONFIG_ACPI */ +static inline struct acpi_device * +acpi_find_child_device(struct acpi_device *parent, u64 address, + bool check_children) +{ + return NULL; +} + static inline bool acpi_has_method(acpi_handle handle, char *name) { return false; From 33392343c74e417375b87032c66078a3bbc1dc91 Mon Sep 17 00:00:00 2001 From: Mayank Rungta Date: Wed, 9 Sep 2026 15:48:18 -0700 Subject: [PATCH 0904/2286] PM: sleep: Add DPM watchdog to prepare/late/early/noirq/complete phases Extend the DPM watchdog to wrap device_prepare, device_suspend_late, device_suspend_noirq, device_resume_noirq, device_resume_early, and device_complete callbacks. If a driver hangs during these transitions, the watchdog will fire and dump a stack trace to help identify the offending driver. To prevent false-positive timeouts, the watchdog is set only after waiting for subordinate (during suspend) and superior (during resume) devices. With this change, DPM watchdog coverage is extended across all phases of system sleep transitions. Signed-off-by: Mayank Rungta Reviewed-by: Douglas Anderson Reviewed-by: Tzung-Bi Shih Link: https://patch.msgid.link/20260909224818.2177311-1-mrungta@google.com Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index e130da42814106..4e0d6357a37cb7 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -802,6 +802,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy const char *info = NULL; bool skip_resume; int error = 0; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); TRACE_DEVICE(dev); TRACE_RESUME(0); @@ -827,6 +828,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy if (!dpm_wait_for_superior(dev, async)) goto Out; + dpm_watchdog_set(&wd, dev); skip_resume = dev_pm_skip_resume(dev); /* * If the driver callback is skipped below or by the middle layer @@ -871,6 +873,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy error = dpm_run_callback(callback, dev, state, info); Skip: + dpm_watchdog_clear(&wd); dev->power.is_noirq_suspended = false; Out: @@ -971,6 +974,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy pm_callback_t callback = NULL; const char *info = NULL; int error = 0; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); TRACE_DEVICE(dev); TRACE_RESUME(0); @@ -987,6 +991,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy if (!dpm_wait_for_superior(dev, async)) goto Out; + dpm_watchdog_set(&wd, dev); if (dev->pm_domain) { info = "early power domain "; callback = pm_late_early_op(&dev->pm_domain->ops, state); @@ -1004,7 +1009,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy goto Run; if (dev_pm_skip_resume(dev)) - goto Skip; + goto End; if (dev->driver && dev->driver->pm) { info = "early driver "; @@ -1014,6 +1019,9 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy Run: error = dpm_run_callback(callback, dev, state, info); +End: + dpm_watchdog_clear(&wd); + Skip: dev->power.is_late_suspended = false; pm_runtime_enable(dev); @@ -1281,10 +1289,12 @@ static void device_complete(struct device *dev, pm_message_t state) { void (*callback)(struct device *) = NULL; const char *info = NULL; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); if (dev->power.syscore) goto out; + dpm_watchdog_set(&wd, dev); device_lock(dev); if (dev->pm_domain) { @@ -1312,6 +1322,7 @@ static void device_complete(struct device *dev, pm_message_t state) } device_unlock(dev); + dpm_watchdog_clear(&wd); out: /* If enabling runtime PM for the device is blocked, unblock it. */ @@ -1508,6 +1519,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as pm_callback_t callback = NULL; const char *info = NULL; int error = 0; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); TRACE_DEVICE(dev); TRACE_SUSPEND(0); @@ -1520,6 +1532,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as if (dev->power.syscore || dev->power.direct_complete) goto Complete; + dpm_watchdog_set(&wd, dev); if (dev->pm_domain) { info = "noirq power domain "; callback = pm_noirq_op(&dev->pm_domain->ops, state); @@ -1550,7 +1563,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as WRITE_ONCE(async_error, error); dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, state, async ? " async noirq" : " noirq", error); - goto Complete; + goto End; } Skip: @@ -1569,6 +1582,9 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as if (dev->power.must_resume) dpm_superior_set_must_resume(dev); +End: + dpm_watchdog_clear(&wd); + Complete: complete_all(&dev->power.completion); TRACE_SUSPEND(error); @@ -1703,6 +1719,7 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy pm_callback_t callback = NULL; const char *info = NULL; int error = 0; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); TRACE_DEVICE(dev); TRACE_SUSPEND(0); @@ -1720,6 +1737,8 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy if (dev->power.direct_complete) goto Complete; + dpm_watchdog_set(&wd, dev); + /* * After this point, any runtime PM operations targeting the device * will fail until the corresponding pm_runtime_enable() call in @@ -1761,13 +1780,16 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy dpm_save_failed_dev(dev_name(dev)); pm_dev_err(dev, state, async ? " async late" : " late", error); pm_runtime_enable(dev); - goto Complete; + goto End; } dpm_propagate_wakeup_to_parent(dev); Skip: dev->power.is_late_suspended = true; +End: + dpm_watchdog_clear(&wd); + Complete: TRACE_SUSPEND(error); complete_all(&dev->power.completion); @@ -2201,6 +2223,7 @@ static int device_prepare(struct device *dev, pm_message_t state) int (*callback)(struct device *) = NULL; bool smart_suspend; int ret = 0; + DECLARE_DPM_WATCHDOG_ON_STACK(wd); /* * If a device's parent goes into runtime suspend at the wrong time, @@ -2220,6 +2243,7 @@ static int device_prepare(struct device *dev, pm_message_t state) if (dev->power.syscore) return 0; + dpm_watchdog_set(&wd, dev); device_lock(dev); dev->power.wakeup_path = false; @@ -2245,6 +2269,7 @@ static int device_prepare(struct device *dev, pm_message_t state) unlock: device_unlock(dev); + dpm_watchdog_clear(&wd); if (ret < 0) { suspend_report_result(dev, callback, ret); From 53d39bfd205da83ff1f67a7da75d1505bd781f3d Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:17:21 +0530 Subject: [PATCH 0905/2286] PM: clk: fix typo "sucessfully" in comment Correct "sucessfully" to "successfully", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Randy Dunlap [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260907064725.10686-2-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/base/power/clock_ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/power/clock_ops.c b/drivers/base/power/clock_ops.c index 59bb37e8244c57..3b8ba27802a800 100644 --- a/drivers/base/power/clock_ops.c +++ b/drivers/base/power/clock_ops.c @@ -250,7 +250,7 @@ EXPORT_SYMBOL_GPL(pm_clk_add); * * Add the clock to the list of clocks used for the power management of @dev. * The power-management code will take control of the clock reference, so - * callers should not call clk_put() on @clk after this function sucessfully + * callers should not call clk_put() on @clk after this function successfully * returned. */ int pm_clk_add_clk(struct device *dev, struct clk *clk) From 284864b1f740043c08a008bf62454a8a1553f5d6 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:17:22 +0530 Subject: [PATCH 0906/2286] thermal: fix assorted typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Randy Dunlap [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260907064725.10686-3-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/thermal_core.c | 2 +- drivers/thermal/thermal_of.c | 4 ++-- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 12 ++++++------ include/uapi/linux/thermal.h | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 82e2f0d8a26dee..5db52de333983b 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1662,7 +1662,7 @@ EXPORT_SYMBOL_GPL(thermal_zone_device_unregister); * * Return: On success returns a reference to an unique thermal zone with * matching name equals to @name, an ERR_PTR otherwise (-EINVAL for invalid - * paramenters, -ENODEV for not found and -EEXIST for multiple matches). + * parameters, -ENODEV for not found and -EEXIST for multiple matches). */ struct thermal_zone_device *thermal_zone_get_zone_by_name(const char *name) { diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae33..f6566b5b916c43 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -349,10 +349,10 @@ static bool thermal_of_should_bind(struct thermal_zone_device *tz, } /** - * thermal_of_zone_unregister - Cleanup the specific allocated ressources + * thermal_of_zone_unregister - Cleanup the specific allocated resources * * This function disables the thermal zone and frees the different - * ressources allocated specific to the thermal OF. + * resources allocated specific to the thermal OF. * * @tz: a pointer to the thermal zone structure */ diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index ba43399d0b384c..5c028d25a5654e 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -332,7 +332,7 @@ static inline int ti_bandgap_validate(struct ti_bandgap *bgp, int id) * ti_bandgap_read_counter() - read the sensor counter * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds */ static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id, int *interval) @@ -352,7 +352,7 @@ static void ti_bandgap_read_counter(struct ti_bandgap *bgp, int id, * ti_bandgap_read_counter_delay() - read the sensor counter delay * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds */ static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id, int *interval) @@ -394,7 +394,7 @@ static void ti_bandgap_read_counter_delay(struct ti_bandgap *bgp, int id, * ti_bandgap_read_update_interval() - read the sensor update interval * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: resulting update interval in miliseconds + * @interval: resulting update interval in milliseconds * * Return: 0 on success or the proper error code */ @@ -427,7 +427,7 @@ int ti_bandgap_read_update_interval(struct ti_bandgap *bgp, int id, * ti_bandgap_write_counter_delay() - set the counter_delay * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds * * Return: 0 on success or the proper error code */ @@ -471,7 +471,7 @@ static int ti_bandgap_write_counter_delay(struct ti_bandgap *bgp, int id, * ti_bandgap_write_counter() - set the bandgap sensor counter * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds */ static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id, u32 interval) @@ -486,7 +486,7 @@ static void ti_bandgap_write_counter(struct ti_bandgap *bgp, int id, * ti_bandgap_write_update_interval() - set the update interval * @bgp: pointer to bandgap instance * @id: sensor id - * @interval: desired update interval in miliseconds + * @interval: desired update interval in milliseconds * * Return: 0 on success or the proper error code */ diff --git a/include/uapi/linux/thermal.h b/include/uapi/linux/thermal.h index 46a2633d33aaa4..36f3da2d6a7caa 100644 --- a/include/uapi/linux/thermal.h +++ b/include/uapi/linux/thermal.h @@ -81,11 +81,11 @@ enum thermal_genl_event { THERMAL_GENL_EVENT_CDEV_STATE_UPDATE, /* Cdev state updated */ THERMAL_GENL_EVENT_TZ_GOV_CHANGE, /* Governor policy changed */ THERMAL_GENL_EVENT_CPU_CAPABILITY_CHANGE, /* CPU capability changed */ - THERMAL_GENL_EVENT_THRESHOLD_ADD, /* A thresold has been added */ - THERMAL_GENL_EVENT_THRESHOLD_DELETE, /* A thresold has been deleted */ + THERMAL_GENL_EVENT_THRESHOLD_ADD, /* A threshold has been added */ + THERMAL_GENL_EVENT_THRESHOLD_DELETE, /* A threshold has been deleted */ THERMAL_GENL_EVENT_THRESHOLD_FLUSH, /* All thresolds have been deleted */ - THERMAL_GENL_EVENT_THRESHOLD_UP, /* A thresold has been crossed the way up */ - THERMAL_GENL_EVENT_THRESHOLD_DOWN, /* A thresold has been crossed the way down */ + THERMAL_GENL_EVENT_THRESHOLD_UP, /* A threshold has been crossed the way up */ + THERMAL_GENL_EVENT_THRESHOLD_DOWN, /* A threshold has been crossed the way down */ __THERMAL_GENL_EVENT_MAX, }; #define THERMAL_GENL_EVENT_MAX (__THERMAL_GENL_EVENT_MAX - 1) From c237f6b429b914905bb379de4089d661fb370446 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:17:25 +0530 Subject: [PATCH 0907/2286] ACPI: NUMA: fix typo in log message Correct the misspelling in a message the user sees, reported by scripts/checkpatch.pl using the list in scripts/spelling.txt. Only the message text changes, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260907064725.10686-6-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/numa/srat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/numa/srat.c b/drivers/acpi/numa/srat.c index 5c407dc6401e8b..f5616830f5a975 100644 --- a/drivers/acpi/numa/srat.c +++ b/drivers/acpi/numa/srat.c @@ -111,7 +111,7 @@ int __init fix_pxm_node_maps(int max_nid) for (j = 0; j <= max_nid; j++) { if ((emu_nid_to_phys[j] == i) && WARN(node_to_pxm_map_copy[j] != PXM_INVAL, - "Node %d is already binded to PXM %d\n", + "Node %d is already bound to PXM %d\n", j, node_to_pxm_map_copy[j])) return -1; if (emu_nid_to_phys[j] == i) { From 7f301e86d0058b15de3b3dbfd39e8b94bff31991 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 13 Sep 2026 00:41:54 +0530 Subject: [PATCH 0908/2286] thermal: Use %pe to print error pointers symbolically Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to pr_err() and dev_err(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Link: https://patch.msgid.link/20260912191156.185448-5-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/thermal/cpufreq_cooling.c | 4 ++-- drivers/thermal/devfreq_cooling.c | 4 ++-- drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 768859a7aed059..856d619370db80 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -661,8 +661,8 @@ of_cpufreq_cooling_register(struct cpufreq_policy *policy) cdev = __cpufreq_cooling_register(np, policy, em); if (IS_ERR(cdev)) { - pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %ld\n", - policy->cpu, PTR_ERR(cdev)); + pr_err("cpufreq_cooling: cpu%d failed to register as cooling device: %pe\n", + policy->cpu, cdev); cdev = NULL; } } diff --git a/drivers/thermal/devfreq_cooling.c b/drivers/thermal/devfreq_cooling.c index 0330a811283233..9ada52cbf7dd73 100644 --- a/drivers/thermal/devfreq_cooling.c +++ b/drivers/thermal/devfreq_cooling.c @@ -156,8 +156,8 @@ static unsigned long get_voltage(struct devfreq *df, unsigned long freq) opp = dev_pm_opp_find_freq_exact(dev, freq, false); if (IS_ERR(opp)) { - dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %ld\n", - freq, PTR_ERR(opp)); + dev_err_ratelimited(dev, "Failed to find OPP for frequency %lu: %pe\n", + freq, opp); return 0; } diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c index af72db6299cdf4..4554a6fa55d113 100644 --- a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c @@ -680,8 +680,8 @@ static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm) continue; } - dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n", - adc_tm->channels[i].channel, PTR_ERR(tzd)); + dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %pe\n", + adc_tm->channels[i].channel, tzd); return PTR_ERR(tzd); } adc_tm->channels[i].tzd = tzd; From 459199ccd1813b0cb9193962a474c2e24b1b373f Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 13 Sep 2026 00:41:55 +0530 Subject: [PATCH 0909/2286] powercap: intel_rapl: Use %pe to print error pointers symbolically Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to pr_debug(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Link: https://patch.msgid.link/20260912191156.185448-6-sumeet4linux@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/powercap/intel_rapl_msr.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c index 2c1b799d3c54d0..d6686d03056a6b 100644 --- a/drivers/powercap/intel_rapl_msr.c +++ b/drivers/powercap/intel_rapl_msr.c @@ -583,8 +583,8 @@ static int intel_rapl_msr_init(void) rapl_msr_platdev = platform_device_register_data(NULL, "intel_rapl_msr", 0, def, sizeof(*def)); if (IS_ERR(rapl_msr_platdev)) - pr_debug("intel_rapl_msr device register failed, ret:%ld\n", - PTR_ERR(rapl_msr_platdev)); + pr_debug("intel_rapl_msr device register failed, ret:%pe\n", + rapl_msr_platdev); return 0; } From 627ea30aca3b309af40cdd07a35eb1027ba4a0bc Mon Sep 17 00:00:00 2001 From: "Shubhang Kaushik (Ampere)" Date: Tue, 22 Sep 2026 14:57:05 -0700 Subject: [PATCH 0910/2286] sched/wait: Clarify WF_SYNC wakeup semantics The synchronous waitqueue wakeup comments currently state that a synchronous wakee will not be migrated to another CPU. This is not guaranteed by the scheduler wakeup path. WF_SYNC is an advisory hint that the caller expects the waker to schedule away soon. Scheduler classes may use it for placement or preemption, but callers must not rely on it to prevent migration, preserve CPU locality, or make the wakee run next. Keep this contract next to the flag definition, remove the stale waitqueue wording, and make the locked helper refer to the unlocked variant. Signed-off-by: Shubhang Kaushik (Ampere) Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Link: https://patch.msgid.link/20260922-sched-wf-sync-doc-v3-1-23ebe9e27bef@gentwo.org --- kernel/sched/sched.h | 9 +++++++-- kernel/sched/wait.c | 22 +++++----------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index f7d0b64b9d3569..b98084e1f5b08d 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2535,8 +2535,13 @@ static inline int task_on_rq_migrating(struct task_struct *p) #define WF_EXEC 0x02 /* Wakeup after exec; maps to SD_BALANCE_EXEC */ #define WF_FORK 0x04 /* Wakeup after fork; maps to SD_BALANCE_FORK */ #define WF_TTWU 0x08 /* Wakeup; maps to SD_BALANCE_WAKE */ - -#define WF_SYNC 0x10 /* Waker goes to sleep after wakeup */ +/* + * Hint that the caller expects the waker to sleep soon. + * Scheduler classes may use it for placement or preemption. + * Callers must not rely on it to prevent migration, + * preserve CPU locality or make the wakee run next. + */ +#define WF_SYNC 0x10 #define WF_MIGRATED 0x20 /* Internal use, task got migrated */ #define WF_CURRENT_CPU 0x40 /* Prefer to move the wakee to the current CPU. */ #define WF_RQ_SELECTED 0x80 /* ->select_task_rq() was called */ diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c index d033f600f48c6f..477e4bf9c01e19 100644 --- a/kernel/sched/wait.c +++ b/kernel/sched/wait.c @@ -174,15 +174,11 @@ EXPORT_SYMBOL_GPL(__wake_up_locked_key); * @mode: which threads * @key: opaque value to be passed to wakeup targets * - * The sync wakeup differs that the waker knows that it will schedule - * away soon, so while the target thread will be woken up, it will not - * be migrated to another CPU - ie. the two threads are 'synchronized' - * with each other. This can prevent needless bouncing between CPUs. + * Passes WF_SYNC to waitqueue wake functions. The default wake function + * forwards it to the scheduler; see WF_SYNC for the hint's semantics. * - * On UP it can prevent extra preemption. - * - * If this function wakes up a task, it executes a full memory barrier before - * accessing the task state. + * If this function wakes up a task, it executes a full memory barrier + * before accessing the task state. */ void __wake_up_sync_key(struct wait_queue_head *wq_head, unsigned int mode, void *key) @@ -200,15 +196,7 @@ EXPORT_SYMBOL_GPL(__wake_up_sync_key); * @mode: which threads * @key: opaque value to be passed to wakeup targets * - * The sync wakeup differs in that the waker knows that it will schedule - * away soon, so while the target thread will be woken up, it will not - * be migrated to another CPU - ie. the two threads are 'synchronized' - * with each other. This can prevent needless bouncing between CPUs. - * - * On UP it can prevent extra preemption. - * - * If this function wakes up a task, it executes a full memory barrier before - * accessing the task state. + * Same as __wake_up_sync_key(), but called with @wq_head->lock held. */ void __wake_up_locked_sync_key(struct wait_queue_head *wq_head, unsigned int mode, void *key) From 3d69596c684bb943ff2f74c13635bb11c3c75e86 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:47 +0000 Subject: [PATCH 0911/2286] x86/fpu: Document signal frame layout and portability The x86 signal frame is designed to be self-describing, with the 'xstate_size' field in the software-reserved bytes indicating the actual size of the context. This design is required for portability, allowing a signal frame created on a system with a specific set of xstate features to be restored on a machine with a different (larger) set of features. Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Link: https://patch.msgid.link/20260925162454.1403405-2-avagin@google.com --- Documentation/arch/x86/xstate.rst | 54 ++++++++++++++++++++++++++ arch/x86/include/uapi/asm/sigcontext.h | 15 +++++++ 2 files changed, 69 insertions(+) diff --git a/Documentation/arch/x86/xstate.rst b/Documentation/arch/x86/xstate.rst index cec05ac464c121..e2944f744255ba 100644 --- a/Documentation/arch/x86/xstate.rst +++ b/Documentation/arch/x86/xstate.rst @@ -172,3 +172,57 @@ are extended to control the guest permission: Note that some VMMs may have already established a set of supported state components. These options are not presumed to support any particular VMM. + +Signal Frame Layout and Portability +----------------------------------- + +The signal frame is designed to be self-describing and portable. This is +especially important for checkpoint/restore tools like CRIU, which may restore +a process on a different host than where it was checkpointed. A signal frame +created on a machine with fewer CPU features can be successfully restored on a +machine with more CPU features, but not vice-versa. + +Signal Frame Software Reserved Bytes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On CPUs supporting XSAVE, bytes 464..511 in the 512-byte FXSAVE/FXRSTOR frame +are reserved for software use and contain ``struct _fpx_sw_bytes`` (defined in +````):: + + struct _fpx_sw_bytes { + __u32 magic1; + __u32 extended_size; + __u64 xfeatures; + __u32 xstate_size; + __u32 padding[7]; + }; + +- ``magic1``: Set to ``FP_XSTATE_MAGIC1`` (``0x46505853U``) if an extended + xstate context is present; 0 for a legacy frame. +- ``extended_size``: The total size allocated on the stack for the frame, + measured from the ``fpstate`` pointer. In 32-bit signal frames, this also + includes the 112-byte legacy FPU state prefix of ``struct _fpstate_32``. +- ``xfeatures``: The mask of xstate features saved in the frame. +- ``xstate_size``: The actual size of the xstate context for the enabled + features (including the 512-byte FXSAVE area and the 64-byte XSAVE header). + +The kernel uses ``xstate_size`` in conjunction with the pointer to the xstate +context to locate the ``FP_XSTATE_MAGIC2`` (``0x46505845U``) marker right after +the xstate context (at ``xstate_context + xstate_size``). In 64-bit signal frames, +the ``fpstate`` pointer points directly to the xstate context. In 32-bit signal +frames (including 32-bit compat tasks on 64-bit kernels), the ``fpstate`` +pointer points to ``struct _fpstate_32``, which contains the 112-byte legacy +FPU state followed by the 512-byte FXSR state (and any extended xstate). Since +there is no standalone UAPI structure defined for just the 112-byte legacy +state, the xstate context starts at ``fpstate + 112`` (and ``extended_size`` +spans the entire allocation from ``fpstate``). + +Portability Constraints +^^^^^^^^^^^^^^^^^^^^^^^ + +Signal frame portability is constrained by the architectural XSAVE layout. +Restoration is supported only if the destination host supports all features +present in the frame and uses matching component offsets and sizes for them. +While layout compatibility is generally maintained across CPUs from the same +vendor, differences can occur across vendors or if the XSAVE space of a +deprecated feature (e.g. MPX) is repurposed for a newer feature (e.g. APX). diff --git a/arch/x86/include/uapi/asm/sigcontext.h b/arch/x86/include/uapi/asm/sigcontext.h index d0d9b331d3a138..cff01406c0f453 100644 --- a/arch/x86/include/uapi/asm/sigcontext.h +++ b/arch/x86/include/uapi/asm/sigcontext.h @@ -34,6 +34,21 @@ * fpstate+extended_size-FP_XSTATE_MAGIC2_SIZE address) is set to * FP_XSTATE_MAGIC2 so that you can sanity check your size calculations.) * + * The xstate_size field indicates the actual size of the xstate context + * (including the 512-byte FXSAVE area and the 64-byte XSAVE header struct + * _header). This size is used in conjunction with the pointer to the xstate + * context to locate FP_XSTATE_MAGIC2. + * + * In 64-bit signal frames, the fpstate pointer points directly to the xstate + * context. In 32-bit signal frames (including 32-bit compat tasks on 64-bit + * kernels), the fpstate pointer points to struct _fpstate_32, which contains + * the 112-byte legacy FPU state followed by the 512-byte FXSR state (and any + * extended xstate), so the xstate context starts at fpstate + 112. + * + * This makes the signal frame self-describing and portable across machines + * with different xstate features. See Documentation/arch/x86/xstate.rst + * for details on signal frame portability and its architectural constraints. + * * This extended area typically grows with newer CPUs that have larger and * larger XSAVE areas. */ From 344907fab5fdf2ab908e5d2f456cda87a51e9497 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:48 +0000 Subject: [PATCH 0912/2286] x86/fpu: Clean up and rename variables in signal frame handling Clean up signal frame handling code by renaming several variables for clarity and consistency, and moving masking logic closer to its usage. - Rename 'fxbuf' to 'buf_fx' in check_xstate_in_sigframe() for consistency. - Rename label 'setfx' to 'err_setfx' in check_xstate_in_sigframe() to indicate it is an error path. - In __restore_fpregs_from_user(), rename 'ufeatures' to 'task_xfeatures' and 'xrestore' to 'xrestore_mask'. - Move the masking logic 'xrestore_mask &= task_xfeatures' from restore_fpregs_from_user() into __restore_fpregs_from_user(). - Rename 'xrestore' to 'xrestore_mask' in restore_fpregs_from_user() to match the name in __restore_fpregs_from_user() and __fpu_restore_sig(). - In __fpu_restore_sig(), rename 'buf' to 'buf_f' to distinguish it from 'buf_fx', and 'user_xfeatures' to 'xrestore_mask'. No functional changes. Suggested-by: Ingo Molnar Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Link: https://patch.msgid.link/20260925162454.1403405-3-avagin@google.com --- arch/x86/kernel/fpu/signal.c | 41 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index 33e1284bf3e436..cd7db6dc819b2f 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -24,15 +24,15 @@ * Check for the presence of extended state information in the * user fpstate pointer in the sigcontext. */ -static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf, +static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx, struct _fpx_sw_bytes *fx_sw) { int min_xstate_size = sizeof(struct fxregs_state) + sizeof(struct xstate_header); - void __user *fpstate = fxbuf; + void __user *fpstate = buf_fx; unsigned int magic2; - if (__copy_from_user(fx_sw, &fxbuf->sw_reserved[0], sizeof(*fx_sw))) + if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw))) return false; /* Check for the first magic field and other error scenarios. */ @@ -40,7 +40,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf, fx_sw->xstate_size < min_xstate_size || fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size || fx_sw->xstate_size > fx_sw->extended_size) - goto setfx; + goto err_setfx; /* * Check for the presence of second magic word at the end of memory @@ -53,7 +53,7 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *fxbuf, if (likely(magic2 == FP_XSTATE_MAGIC2)) return true; -setfx: +err_setfx: trace_x86_fpu_xstate_check_failed(x86_task_fpu(current)); /* Set the parameters for fx only state */ @@ -240,15 +240,18 @@ bool copy_fpstate_to_sigframe(void __user *buf, void __user *buf_fx, int size, u return true; } -static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures, - u64 xrestore, bool fx_only) +static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures, + u64 xrestore_mask, bool fx_only) { if (use_xsave()) { - u64 init_bv = ufeatures & ~xrestore; + u64 init_bv; int ret; + /* Restore enabled features only. */ + xrestore_mask &= task_xfeatures; + init_bv = task_xfeatures & ~xrestore_mask; if (likely(!fx_only)) - ret = xrstor_from_user_sigframe(buf, xrestore); + ret = xrstor_from_user_sigframe(buf, xrestore_mask); else ret = fxrstor_from_user_sigframe(buf); @@ -266,20 +269,18 @@ static int __restore_fpregs_from_user(void __user *buf, u64 ufeatures, * Attempt to restore the FPU registers directly from user memory. * Pagefaults are handled and any errors returned are fatal. */ -static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_only) +static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool fx_only) { struct fpu *fpu = x86_task_fpu(current); int ret; - /* Restore enabled features only. */ - xrestore &= fpu->fpstate->user_xfeatures; retry: fpregs_lock(); /* Ensure that XFD is up to date */ xfd_update_state(fpu->fpstate); pagefault_disable(); ret = __restore_fpregs_from_user(buf, fpu->fpstate->user_xfeatures, - xrestore, fx_only); + xrestore_mask, fx_only); pagefault_enable(); if (unlikely(ret)) { @@ -324,7 +325,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore, bool fx_onl return true; } -static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx, +static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx, bool ia32_fxstate) { struct task_struct *tsk = current; @@ -332,7 +333,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx, struct user_i387_ia32_struct env; bool success, fx_only = false; union fpregs_state *fpregs; - u64 user_xfeatures = 0; + u64 xrestore_mask = 0; if (use_xsave()) { struct _fpx_sw_bytes fx_sw_user; @@ -341,14 +342,14 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx, return false; fx_only = !fx_sw_user.magic1; - user_xfeatures = fx_sw_user.xfeatures; + xrestore_mask = fx_sw_user.xfeatures; } else { - user_xfeatures = XFEATURE_MASK_FPSSE; + xrestore_mask = XFEATURE_MASK_FPSSE; } if (likely(!ia32_fxstate)) { /* Restore the FPU registers directly from user memory. */ - return restore_fpregs_from_user(buf_fx, user_xfeatures, fx_only); + return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only); } /* @@ -356,7 +357,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx, * to be ignored for histerical raisins. The legacy state is folded * in once the larger state has been copied. */ - if (__copy_from_user(&env, buf, sizeof(env))) + if (__copy_from_user(&env, buf_f, sizeof(env))) return false; /* @@ -420,7 +421,7 @@ static bool __fpu_restore_sig(void __user *buf, void __user *buf_fx, * * Preserve supervisor states! */ - u64 mask = user_xfeatures | xfeatures_mask_supervisor(); + u64 mask = xrestore_mask | xfeatures_mask_supervisor(); fpregs->xsave.header.xfeatures &= mask; success = !os_xrstor_safe(fpu->fpstate, From bd3135fba0f37e1de31b1e19499660d1aaf9f19e Mon Sep 17 00:00:00 2001 From: Syazwina Fazrul Date: Tue, 15 Sep 2026 23:00:55 +0100 Subject: [PATCH 0913/2286] Documentation: power/cpuidle: Fix repeating phrase remove repeating 'with the' phrase Signed-off-by: Syazwina Fazrul Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260915220105.14655-1-norsyazwina.fazrul@gmail.com Signed-off-by: Rafael J. Wysocki --- Documentation/admin-guide/pm/cpuidle.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/pm/cpuidle.rst b/Documentation/admin-guide/pm/cpuidle.rst index be4c1120e3f0b6..e91ebd432f6fd9 100644 --- a/Documentation/admin-guide/pm/cpuidle.rst +++ b/Documentation/admin-guide/pm/cpuidle.rst @@ -316,7 +316,7 @@ interval" value. Now, the governor is ready to walk the list of idle states and choose one of them. For this purpose, it compares the target residency of each state with -the predicted idle duration and the exit latency of it with the with the latency +the predicted idle duration and the exit latency of it with the latency limit coming from the power management quality of service, or `PM QoS `_, framework. It selects the state with the target residency closest to the predicted idle duration, but still below it, and exit latency that does not exceed the From 6916dec7a02fe0f50d6dc80e5f5c55f11cebd874 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:49 +0000 Subject: [PATCH 0914/2286] x86/fpu: Extract restore_from_ia32_fxstate() and clean up fpu__restore_sig() When restoring an FPU signal frame for a 32-bit or IA32-compat task on FXSR-enabled systems, a legacy 32-bit FP frame is present alongside the FX/XSAVE frame. Because the legacy FP frame duplicates the FP state portion of the FX/XSAVE frame, for backward compatibility it is treated as the source of truth, and its state is folded into the FX/XSAVE state before restoring the registers. Currently, most of __fpu_restore_sig() is dedicated to handling this 32-bit legacy/compat fpstate, while the native direct restore path lives in restore_fpregs_from_user(). Having the compat handling intermixed with the main signal restoration flow makes it tricky to quickly see what code is doing what. Extract the 32-bit legacy/compat FPU restore handling into a separate helper function, restore_from_ia32_fxstate(), and inline the remainder of __fpu_restore_sig() directly into fpu__restore_sig(). Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Chang S. Bae Link: https://patch.msgid.link/20260925162454.1403405-4-avagin@google.com --- arch/x86/kernel/fpu/signal.c | 56 +++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index cd7db6dc819b2f..b9dcd0cd0e412a 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -325,32 +325,26 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f return true; } -static bool __fpu_restore_sig(void __user *buf_f, void __user *buf_fx, - bool ia32_fxstate) +/* + * Restore FPU state from a signal frame when a legacy 32-bit FP frame + * (buf_f) is present. + * + * The legacy FP frame duplicates the FP state portion of the FX/XSAVE + * frame (buf_fx). For backward compatibility, the legacy FP frame is + * treated as the source of truth, and its state is folded into the + * FX/XSAVE state before restoring the registers. + */ +static bool restore_from_ia32_fxstate(void __user *buf_f, void __user *buf_fx, + u64 xrestore_mask, bool fx_only) { struct task_struct *tsk = current; struct fpu *fpu = x86_task_fpu(tsk); struct user_i387_ia32_struct env; - bool success, fx_only = false; union fpregs_state *fpregs; - u64 xrestore_mask = 0; - - if (use_xsave()) { - struct _fpx_sw_bytes fx_sw_user; - - if (!check_xstate_in_sigframe(buf_fx, &fx_sw_user)) - return false; - - fx_only = !fx_sw_user.magic1; - xrestore_mask = fx_sw_user.xfeatures; - } else { - xrestore_mask = XFEATURE_MASK_FPSSE; - } + bool success; - if (likely(!ia32_fxstate)) { - /* Restore the FPU registers directly from user memory. */ - return restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only); - } + if (!IS_ENABLED(CONFIG_X86_32) && !IS_ENABLED(CONFIG_IA32_EMULATION)) + return false; /* * Copy the legacy state because the FP portion of the FX frame has @@ -450,10 +444,11 @@ static inline unsigned int xstate_sigframe_size(struct fpstate *fpstate) bool fpu__restore_sig(void __user *buf, int ia32_frame) { struct fpu *fpu = x86_task_fpu(current); - void __user *buf_fx = buf; + bool success = false, fx_only = false; bool ia32_fxstate = false; - bool success = false; + void __user *buf_fx = buf; unsigned int size; + u64 xrestore_mask; if (unlikely(!buf)) { fpu__clear_user_states(fpu); @@ -482,10 +477,25 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame) success = !fpregs_soft_set(current, NULL, 0, sizeof(struct user_i387_ia32_struct), NULL, buf); + goto out; + } + + if (use_xsave()) { + struct _fpx_sw_bytes fx_sw_user; + + if (!check_xstate_in_sigframe(buf_fx, &fx_sw_user)) + goto out; + + fx_only = !fx_sw_user.magic1; + xrestore_mask = fx_sw_user.xfeatures; } else { - success = __fpu_restore_sig(buf, buf_fx, ia32_fxstate); + xrestore_mask = XFEATURE_MASK_FPSSE; } + if (ia32_fxstate) + success = restore_from_ia32_fxstate(buf, buf_fx, xrestore_mask, fx_only); + else + success = restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only); out: if (unlikely(!success)) fpu__clear_user_states(fpu); From 23b16b517814c367e593be96e477818ec5b2abff Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 16 Sep 2026 00:15:48 +0200 Subject: [PATCH 0915/2286] thermal: intel: powerclamp: Simplify duration_get() Use guard(mutex) and return the result of sysfs_emit() directly to simplify the code. Also use %u to format the unsigned duration. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260915221548.104852-3-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index 044b185f748b80..ee1f2fa8d20ff9 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -103,13 +103,9 @@ static int duration_set(const char *arg, const struct kernel_param *kp) static int duration_get(char *buf, const struct kernel_param *kp) { - int ret; + guard(mutex)(&powerclamp_lock); - mutex_lock(&powerclamp_lock); - ret = sysfs_emit(buf, "%d\n", duration / 1000); - mutex_unlock(&powerclamp_lock); - - return ret; + return sysfs_emit(buf, "%u\n", duration / 1000); } static const struct kernel_param_ops duration_ops = { From d89c557bb0c0ed828287af8c62d1c2f7f638e112 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 16 Sep 2026 00:15:49 +0200 Subject: [PATCH 0916/2286] thermal: intel: powerclamp: Simplify max_idle_set() Use guard(mutex) and return errors directly to simplify the code. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260915221548.104852-4-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/intel/intel_powerclamp.c | 29 ++++++++---------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/drivers/thermal/intel/intel_powerclamp.c b/drivers/thermal/intel/intel_powerclamp.c index ee1f2fa8d20ff9..1ffcb0a699e57d 100644 --- a/drivers/thermal/intel/intel_powerclamp.c +++ b/drivers/thermal/intel/intel_powerclamp.c @@ -207,41 +207,32 @@ MODULE_PARM_DESC(cpumask, "Mask of CPUs to use for idle injection."); static int max_idle_set(const char *arg, const struct kernel_param *kp) { u8 new_max_idle; - int ret = 0; + int ret; - mutex_lock(&powerclamp_lock); + guard(mutex)(&powerclamp_lock); /* Can't set mask when cooling device is in use */ - if (powerclamp_data.clamping) { - ret = -EAGAIN; - goto skip_limit_set; - } + if (powerclamp_data.clamping) + return -EAGAIN; ret = kstrtou8(arg, 10, &new_max_idle); if (ret) - goto skip_limit_set; + return ret; - if (new_max_idle > MAX_TARGET_RATIO) { - ret = -EINVAL; - goto skip_limit_set; - } + if (new_max_idle > MAX_TARGET_RATIO) + return -EINVAL; if (!cpumask_available(idle_injection_cpu_mask)) { ret = allocate_copy_idle_injection_mask(cpu_present_mask); if (ret) - goto skip_limit_set; + return ret; } - if (check_invalid(idle_injection_cpu_mask, new_max_idle)) { - ret = -EINVAL; - goto skip_limit_set; - } + if (check_invalid(idle_injection_cpu_mask, new_max_idle)) + return -EINVAL; max_idle = new_max_idle; -skip_limit_set: - mutex_unlock(&powerclamp_lock); - return ret; } From 731300df4f7d7d7153a2aa23b6c4992df3d2a60b Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:50 +0000 Subject: [PATCH 0917/2286] x86/fpu: Document reasoning of FX-only fallback Add a comment to check_xstate_in_sigframe() to explain the reasoning behind falling back to the FX-only state when signal frame metadata is inconsistent. The fallback is intended to preserve backward compatibility with legacy user-space processes that are not aware of XSAVE states and might only fill or copy the legacy FP state. However, this fallback should be avoided whenever possible. If a process was actively using extended features, falling back to the FX-only state silently resets those extended registers to their initial state, which can lead to silent user-space state corruption. Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Chang S. Bae Link: https://patch.msgid.link/20260925162454.1403405-5-avagin@google.com --- arch/x86/kernel/fpu/signal.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index b9dcd0cd0e412a..d56819fe491e42 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -54,6 +54,14 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx, if (likely(magic2 == FP_XSTATE_MAGIC2)) return true; err_setfx: + /* + * The fallback to FX-only state is used to preserve backward + * compatibility with user-space processes that are not aware of xsave + * states. + * + * In all other cases, returning false (to trigger SIGSEGV) is + * preferred to avoid silent user-space state corruption. + */ trace_x86_fpu_xstate_check_failed(x86_task_fpu(current)); /* Set the parameters for fx only state */ From dce0e0248205bb3dc5b4c6793cd2ecb453152259 Mon Sep 17 00:00:00 2001 From: Bruno Produit Date: Mon, 21 Sep 2026 17:37:29 +0200 Subject: [PATCH 0918/2286] hfs/hfsplus: serialize B-tree close against folio release B-tree nodes with a zero reference count remain in the node hash until folio reclaim or tree teardown frees them. The folio release callbacks remove nodes while holding hash_lock, but hfs_btree_close() walks and frees the same hash without that lock. Reclaim can therefore unhash and free a node after close has loaded its pointer, causing a use-after-free or double-free. The following syzkaller crashes seem to be the same UAF in {hfs,hfsplus}_btree_close() (or {hfs,hfsplus}_bnode_unhash()) and follow the same pattern, but do not contain a reproducer to confirm. Detach each node with hfs_bnode_unhash() while holding hash_lock before inspecting and freeing it. Drop the lock before hfs_bnode_free() so a large tree is not freed while holding a spinlock. Apply the same fix to the matching HFS+ implementation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Reported-by: syzbot+d729df28d933979e017a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d729df28d933979e017a Reported-by: syzbot+2eac7d175baf21e6a5d5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2eac7d175baf21e6a5d5 Reported-by: syzbot+7155b2fe09e033c91381@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=7155b2fe09e033c91381 Reported-by: syzbot+adeb387cede15eb11607@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=adeb387cede15eb11607 Reported-by: syzbot+ae7f2423f3648100506d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=ae7f2423f3648100506d Assisted-by: Codex:gpt-5.6-sol Reported-by: Kyle Zeng Signed-off-by: Kyle Zeng Signed-off-by: Bruno Produit Reviewed-by: Viacheslav Dubeyko Tested-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260921153729.600313-1-bruno.produit@trailofbits.com Signed-off-by: Viacheslav Dubeyko --- fs/hfs/btree.c | 7 +++++-- fs/hfsplus/btree.c | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/hfs/btree.c b/fs/hfs/btree.c index 41b4e8fc94637d..4f0ddc76e8c5f1 100644 --- a/fs/hfs/btree.c +++ b/fs/hfs/btree.c @@ -310,15 +310,18 @@ void hfs_btree_close(struct hfs_btree *tree) return; for (i = 0; i < NODE_HASH_SIZE; i++) { + spin_lock(&tree->hash_lock); while ((node = tree->node_hash[i])) { - tree->node_hash[i] = node->next_hash; + hfs_bnode_unhash(node); + spin_unlock(&tree->hash_lock); if (atomic_read(&node->refcnt)) pr_err("node %d:%d still has %d user(s)!\n", node->tree->cnid, node->this, atomic_read(&node->refcnt)); hfs_bnode_free(node); - tree->node_hash_cnt--; + spin_lock(&tree->hash_lock); } + spin_unlock(&tree->hash_lock); } iput(tree->inode); kfree(tree); diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 2ea8cd5658e1c9..bd4dbdbc8f98c3 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -417,16 +417,19 @@ void hfs_btree_close(struct hfs_btree *tree) return; for (i = 0; i < NODE_HASH_SIZE; i++) { + spin_lock(&tree->hash_lock); while ((node = tree->node_hash[i])) { - tree->node_hash[i] = node->next_hash; + hfs_bnode_unhash(node); + spin_unlock(&tree->hash_lock); if (atomic_read(&node->refcnt)) pr_crit("node %d:%d " "still has %d user(s)!\n", node->tree->cnid, node->this, atomic_read(&node->refcnt)); hfs_bnode_free(node); - tree->node_hash_cnt--; + spin_lock(&tree->hash_lock); } + spin_unlock(&tree->hash_lock); } iput(tree->inode); kfree(tree); From f1751b220bc8a9bd2dc38ecf61f5c29f3826ace0 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:51 +0000 Subject: [PATCH 0919/2286] x86/fpu: Fix potential underflow in xstate_calculate_size() xstate_calculate_size() calculates the size required for a given set of xfeatures. It determines the topmost feature by finding the most significant bit in xfeatures using fls64(xfeatures) - 1. If xfeatures is 0, fls64(0) returns 0, and topmost becomes -1. Previously, topmost was unsigned int, so -1 underflowed to UINT_MAX. This caused the subsequent check `topmost <= XFEATURE_SSE` to fail, and the code proceeded to access xstate arrays using topmost (UINT_MAX) as an index, leading to an out-of-bounds access. [ bp: Remove text explaining what the patch does. ] Fixes: d6d6d50f1e80 ("x86/fpu/xstate: Consolidate size calculations") Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Chang S. Bae Link: https://patch.msgid.link/20260925162454.1403405-6-avagin@google.com --- arch/x86/kernel/fpu/xstate.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index 97cfd4fb6cc00f..efa7879d2f977e 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -589,12 +589,13 @@ static bool __init check_xstate_against_struct(int nr) static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted) { - unsigned int topmost = fls64(xfeatures) - 1; - unsigned int offset, i; + unsigned int topmost, offset, i; - if (topmost <= XFEATURE_SSE) + if (!(xfeatures & ~XFEATURE_MASK_FPSSE)) return sizeof(struct xregs_state); + topmost = fls64(xfeatures) - 1; + if (compacted) { offset = xfeature_get_offset(xfeatures, topmost); } else { From fd14edd82077b8655273b6d32503903a2ee35ffd Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:52 +0000 Subject: [PATCH 0920/2286] x86/fpu: Pre-fault only required size of xstate buffer The kernel previously used the default task FPU state size (user_size) to fault in the user buffer when restoring FPU registers from a signal frame. This can lead to attempting to fault in memory past the end of the actual frame if the frame was smaller than the default size. Introduce consistency checks to calculate the actual required size for the features enabled in the xfeatures mask, ensure that the provided xstate_size is sufficient, and shrink it to the actual required size. Use this validated size to fault in the user buffer. Keep the strict check that the provided xstate_size does not exceed the default user_size for now. Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Chang S. Bae Link: https://patch.msgid.link/20260925162454.1403405-7-avagin@google.com --- arch/x86/include/asm/fpu/xstate.h | 2 ++ arch/x86/kernel/fpu/signal.c | 38 +++++++++++++++++++++++-------- arch/x86/kernel/fpu/xstate.c | 2 +- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/arch/x86/include/asm/fpu/xstate.h b/arch/x86/include/asm/fpu/xstate.h index 7a7dc9d5602750..33343e5d54b09c 100644 --- a/arch/x86/include/asm/fpu/xstate.h +++ b/arch/x86/include/asm/fpu/xstate.h @@ -111,6 +111,8 @@ int xfeature_size(int xfeature_nr); void xsaves(struct xregs_state *xsave, u64 mask); void xrstors(struct xregs_state *xsave, u64 mask); +unsigned int xstate_calculate_size(u64 xfeatures, bool compacted); + int xfd_enable_feature(u64 xfd_err); #ifdef CONFIG_X86_64 diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index d56819fe491e42..594ba36dec734f 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -27,9 +27,10 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx, struct _fpx_sw_bytes *fx_sw) { + struct fpstate *fpstate = x86_task_fpu(current)->fpstate; int min_xstate_size = sizeof(struct fxregs_state) + sizeof(struct xstate_header); - void __user *fpstate = buf_fx; + void __user *buf = buf_fx; unsigned int magic2; if (__copy_from_user(fx_sw, &buf_fx->sw_reserved[0], sizeof(*fx_sw))) @@ -38,8 +39,8 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx, /* Check for the first magic field and other error scenarios. */ if (fx_sw->magic1 != FP_XSTATE_MAGIC1 || fx_sw->xstate_size < min_xstate_size || - fx_sw->xstate_size > x86_task_fpu(current)->fpstate->user_size || - fx_sw->xstate_size > fx_sw->extended_size) + fx_sw->xstate_size > fpstate->user_size || + fx_sw->extended_size < fx_sw->xstate_size + FP_XSTATE_MAGIC2_SIZE) goto err_setfx; /* @@ -48,11 +49,27 @@ static inline bool check_xstate_in_sigframe(struct fxregs_state __user *buf_fx, * fpstate layout with out copying the extended state information * in the memory layout. */ - if (__get_user(magic2, (__u32 __user *)(fpstate + fx_sw->xstate_size))) + if (__get_user(magic2, (__u32 __user *)(buf + fx_sw->xstate_size))) return false; + if (unlikely(magic2 != FP_XSTATE_MAGIC2)) + goto err_setfx; - if (likely(magic2 == FP_XSTATE_MAGIC2)) - return true; + if (fx_sw->xstate_size != fpstate->user_size || + fx_sw->xfeatures != fpstate->user_xfeatures) { + unsigned int xsize; + u64 xfeatures; + + /* Calculate size of enabled features only. */ + xfeatures = fx_sw->xfeatures & fpstate->user_xfeatures; + + xsize = xstate_calculate_size(xfeatures, false); + if (fx_sw->xstate_size < xsize) + return false; + + fx_sw->xstate_size = xsize; + } + + return true; err_setfx: /* * The fallback to FX-only state is used to preserve backward @@ -277,7 +294,8 @@ static int __restore_fpregs_from_user(void __user *buf, u64 task_xfeatures, * Attempt to restore the FPU registers directly from user memory. * Pagefaults are handled and any errors returned are fatal. */ -static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool fx_only) +static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, + bool fx_only, size_t xstate_size) { struct fpu *fpu = x86_task_fpu(current); int ret; @@ -311,7 +329,7 @@ static bool restore_fpregs_from_user(void __user *buf, u64 xrestore_mask, bool f if (ret != X86_TRAP_PF) return false; - if (!fault_in_readable(buf, fpu->fpstate->user_size)) + if (!fault_in_readable(buf, xstate_size)) goto retry; return false; } @@ -496,14 +514,16 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame) fx_only = !fx_sw_user.magic1; xrestore_mask = fx_sw_user.xfeatures; + size = fx_sw_user.xstate_size; } else { xrestore_mask = XFEATURE_MASK_FPSSE; + size = fpu->fpstate->user_size; } if (ia32_fxstate) success = restore_from_ia32_fxstate(buf, buf_fx, xrestore_mask, fx_only); else - success = restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only); + success = restore_fpregs_from_user(buf_fx, xrestore_mask, fx_only, size); out: if (unlikely(!success)) fpu__clear_user_states(fpu); diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index efa7879d2f977e..a2b6ede343fd1c 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -587,7 +587,7 @@ static bool __init check_xstate_against_struct(int nr) return true; } -static unsigned int xstate_calculate_size(u64 xfeatures, bool compacted) +unsigned int xstate_calculate_size(u64 xfeatures, bool compacted) { unsigned int topmost, offset, i; From 7f33d2e5b0a6ab249807ba9a08e46793b0166ad8 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Fri, 18 Sep 2026 17:16:05 -0700 Subject: [PATCH 0921/2286] hfsplus: add Unicode combining-class and legacy decomposition data macOS's own fsck_hfs rejects a catalog name as "Illegal name" in two cases this driver's decomposition tables don't currently account for: - a decomposed name whose combining marks aren't in Unicode canonical combining-class order (fsck_hfs's FixDecomps(), CatalogCheck.c); - a small, fixed set of characters whose canonical decomposition either wasn't yet defined, or was later corrected, since the driver's existing hfsplus_decompose_table (Apple Technote #1150) predates those corrections, which macOS has applied since Mac OS X 10.2 ("Jaguar", 2002). This is purely a data patch and adds no new behavior on its own: - hfsplus_ccc_table: canonical combining class per BMP code point, generated from the Unicode Character Database version 12.1.0 (https://www.unicode.org/Public/12.1.0/ucd/extracted/ DerivedCombiningClass.txt), the same UCD version fs/unicode's README.utf8data documents. - hfsplus_legacy_decomp_table and hfsplus_legacy_seq_fixups: the corrected decompositions and sequence replacements fsck_hfs itself requires, decoded from hfsprogs 540.1's fsck_hfs.tproj/dfalib/DecompData.h (upstream: Apple's diskdev_cmds, https://opensource.apple.com/source/diskdev_cmds/). The three new struct types and the extern declarations for all of tables.c's tables (existing and new) move into a new fs/hfsplus/ unicode.h, replacing the ad hoc externs that used to live in hfsplus_fs.h. cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260919001607.2777138-2-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- fs/hfsplus/hfsplus_fs.h | 6 +- fs/hfsplus/tables.c | 397 ++++++++++++++++++++++++++++++++++++++ fs/hfsplus/unicode.c | 1 + fs/hfsplus/unicode.h | 59 ++++++ fs/hfsplus/unicode_test.c | 1 + 5 files changed, 459 insertions(+), 5 deletions(-) create mode 100644 fs/hfsplus/unicode.h diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 1e5b58e6a13fc3..916e6552e3f0b5 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -17,6 +17,7 @@ #include #include #include "hfsplus_raw.h" +#include "unicode.h" /* Runtime config options */ #define HFSPLUS_DEF_CR_TYPE 0x3F3F3F3F /* '????' */ @@ -486,11 +487,6 @@ void hfsplus_mark_mdb_dirty(struct super_block *sb); void hfsplus_prepare_volume_header_for_commit(struct hfsplus_vh *vhdr); int hfsplus_commit_superblock(struct super_block *sb); -/* tables.c */ -extern u16 hfsplus_case_fold_table[]; -extern u16 hfsplus_decompose_table[]; -extern u16 hfsplus_compose_table[]; - /* unicode.c */ int hfsplus_strcasecmp(const struct hfsplus_unistr *s1, const struct hfsplus_unistr *s2); diff --git a/fs/hfsplus/tables.c b/fs/hfsplus/tables.c index a5fb8ee7d019d4..3bbdc83debb524 100644 --- a/fs/hfsplus/tables.c +++ b/fs/hfsplus/tables.c @@ -3244,3 +3244,400 @@ u16 hfsplus_compose_table[] = { /* 0x0342 0x0314 0x03c9 0x0345 */ 0x1fa7, 0x0000, }; + +/* + * Unicode canonical combining class (ccc) table, restricted to the Basic + * Multilingual Plane: HFS+ names are stored as UTF-16 and this kernel's + * wchar_t (see MAX_WCHAR_T in include/linux/nls.h) tops out at U+FFFF, so + * no HFS+ catalog name can contain a code point above the BMP anyway. + * + * Generated from the Unicode Character Database, version 12.1.0 + * (https://www.unicode.org/Public/12.1.0/ucd/extracted/DerivedCombiningClass.txt), + * the same UCD version fs/unicode/README.utf8data documents. Class 0 + * ("Not_Reordered") is the default for every code point not listed here + * and needs no entry. + * + * Used by hfsplus_canonical_reorder() to apply the Unicode Canonical + * Ordering Algorithm to a decomposed name, matching what macOS's own + * fsck_hfs (FixDecomps()) requires of a stored catalog name. + */ +struct hfsplus_ccc_range hfsplus_ccc_table[] = { + { 0x0300, 0x0314, 230 }, + { 0x0315, 0x0315, 232 }, + { 0x0316, 0x0319, 220 }, + { 0x031a, 0x031a, 232 }, + { 0x031b, 0x031b, 216 }, + { 0x031c, 0x0320, 220 }, + { 0x0321, 0x0322, 202 }, + { 0x0323, 0x0326, 220 }, + { 0x0327, 0x0328, 202 }, + { 0x0329, 0x0333, 220 }, + { 0x0334, 0x0338, 1 }, + { 0x0339, 0x033c, 220 }, + { 0x033d, 0x0344, 230 }, + { 0x0345, 0x0345, 240 }, + { 0x0346, 0x0346, 230 }, + { 0x0347, 0x0349, 220 }, + { 0x034a, 0x034c, 230 }, + { 0x034d, 0x034e, 220 }, + { 0x0350, 0x0352, 230 }, + { 0x0353, 0x0356, 220 }, + { 0x0357, 0x0357, 230 }, + { 0x0358, 0x0358, 232 }, + { 0x0359, 0x035a, 220 }, + { 0x035b, 0x035b, 230 }, + { 0x035c, 0x035c, 233 }, + { 0x035d, 0x035e, 234 }, + { 0x035f, 0x035f, 233 }, + { 0x0360, 0x0361, 234 }, + { 0x0362, 0x0362, 233 }, + { 0x0363, 0x036f, 230 }, + { 0x0483, 0x0487, 230 }, + { 0x0591, 0x0591, 220 }, + { 0x0592, 0x0595, 230 }, + { 0x0596, 0x0596, 220 }, + { 0x0597, 0x0599, 230 }, + { 0x059a, 0x059a, 222 }, + { 0x059b, 0x059b, 220 }, + { 0x059c, 0x05a1, 230 }, + { 0x05a2, 0x05a7, 220 }, + { 0x05a8, 0x05a9, 230 }, + { 0x05aa, 0x05aa, 220 }, + { 0x05ab, 0x05ac, 230 }, + { 0x05ad, 0x05ad, 222 }, + { 0x05ae, 0x05ae, 228 }, + { 0x05af, 0x05af, 230 }, + { 0x05b0, 0x05b0, 10 }, + { 0x05b1, 0x05b1, 11 }, + { 0x05b2, 0x05b2, 12 }, + { 0x05b3, 0x05b3, 13 }, + { 0x05b4, 0x05b4, 14 }, + { 0x05b5, 0x05b5, 15 }, + { 0x05b6, 0x05b6, 16 }, + { 0x05b7, 0x05b7, 17 }, + { 0x05b8, 0x05b8, 18 }, + { 0x05b9, 0x05ba, 19 }, + { 0x05bb, 0x05bb, 20 }, + { 0x05bc, 0x05bc, 21 }, + { 0x05bd, 0x05bd, 22 }, + { 0x05bf, 0x05bf, 23 }, + { 0x05c1, 0x05c1, 24 }, + { 0x05c2, 0x05c2, 25 }, + { 0x05c4, 0x05c4, 230 }, + { 0x05c5, 0x05c5, 220 }, + { 0x05c7, 0x05c7, 18 }, + { 0x0610, 0x0617, 230 }, + { 0x0618, 0x0618, 30 }, + { 0x0619, 0x0619, 31 }, + { 0x061a, 0x061a, 32 }, + { 0x064b, 0x064b, 27 }, + { 0x064c, 0x064c, 28 }, + { 0x064d, 0x064d, 29 }, + { 0x064e, 0x064e, 30 }, + { 0x064f, 0x064f, 31 }, + { 0x0650, 0x0650, 32 }, + { 0x0651, 0x0651, 33 }, + { 0x0652, 0x0652, 34 }, + { 0x0653, 0x0654, 230 }, + { 0x0655, 0x0656, 220 }, + { 0x0657, 0x065b, 230 }, + { 0x065c, 0x065c, 220 }, + { 0x065d, 0x065e, 230 }, + { 0x065f, 0x065f, 220 }, + { 0x0670, 0x0670, 35 }, + { 0x06d6, 0x06dc, 230 }, + { 0x06df, 0x06e2, 230 }, + { 0x06e3, 0x06e3, 220 }, + { 0x06e4, 0x06e4, 230 }, + { 0x06e7, 0x06e8, 230 }, + { 0x06ea, 0x06ea, 220 }, + { 0x06eb, 0x06ec, 230 }, + { 0x06ed, 0x06ed, 220 }, + { 0x0711, 0x0711, 36 }, + { 0x0730, 0x0730, 230 }, + { 0x0731, 0x0731, 220 }, + { 0x0732, 0x0733, 230 }, + { 0x0734, 0x0734, 220 }, + { 0x0735, 0x0736, 230 }, + { 0x0737, 0x0739, 220 }, + { 0x073a, 0x073a, 230 }, + { 0x073b, 0x073c, 220 }, + { 0x073d, 0x073d, 230 }, + { 0x073e, 0x073e, 220 }, + { 0x073f, 0x0741, 230 }, + { 0x0742, 0x0742, 220 }, + { 0x0743, 0x0743, 230 }, + { 0x0744, 0x0744, 220 }, + { 0x0745, 0x0745, 230 }, + { 0x0746, 0x0746, 220 }, + { 0x0747, 0x0747, 230 }, + { 0x0748, 0x0748, 220 }, + { 0x0749, 0x074a, 230 }, + { 0x07eb, 0x07f1, 230 }, + { 0x07f2, 0x07f2, 220 }, + { 0x07f3, 0x07f3, 230 }, + { 0x07fd, 0x07fd, 220 }, + { 0x0816, 0x0819, 230 }, + { 0x081b, 0x0823, 230 }, + { 0x0825, 0x0827, 230 }, + { 0x0829, 0x082d, 230 }, + { 0x0859, 0x085b, 220 }, + { 0x08d3, 0x08d3, 220 }, + { 0x08d4, 0x08e1, 230 }, + { 0x08e3, 0x08e3, 220 }, + { 0x08e4, 0x08e5, 230 }, + { 0x08e6, 0x08e6, 220 }, + { 0x08e7, 0x08e8, 230 }, + { 0x08e9, 0x08e9, 220 }, + { 0x08ea, 0x08ec, 230 }, + { 0x08ed, 0x08ef, 220 }, + { 0x08f0, 0x08f0, 27 }, + { 0x08f1, 0x08f1, 28 }, + { 0x08f2, 0x08f2, 29 }, + { 0x08f3, 0x08f5, 230 }, + { 0x08f6, 0x08f6, 220 }, + { 0x08f7, 0x08f8, 230 }, + { 0x08f9, 0x08fa, 220 }, + { 0x08fb, 0x08ff, 230 }, + { 0x093c, 0x093c, 7 }, + { 0x094d, 0x094d, 9 }, + { 0x0951, 0x0951, 230 }, + { 0x0952, 0x0952, 220 }, + { 0x0953, 0x0954, 230 }, + { 0x09bc, 0x09bc, 7 }, + { 0x09cd, 0x09cd, 9 }, + { 0x09fe, 0x09fe, 230 }, + { 0x0a3c, 0x0a3c, 7 }, + { 0x0a4d, 0x0a4d, 9 }, + { 0x0abc, 0x0abc, 7 }, + { 0x0acd, 0x0acd, 9 }, + { 0x0b3c, 0x0b3c, 7 }, + { 0x0b4d, 0x0b4d, 9 }, + { 0x0bcd, 0x0bcd, 9 }, + { 0x0c4d, 0x0c4d, 9 }, + { 0x0c55, 0x0c55, 84 }, + { 0x0c56, 0x0c56, 91 }, + { 0x0cbc, 0x0cbc, 7 }, + { 0x0ccd, 0x0ccd, 9 }, + { 0x0d3b, 0x0d3c, 9 }, + { 0x0d4d, 0x0d4d, 9 }, + { 0x0dca, 0x0dca, 9 }, + { 0x0e38, 0x0e39, 103 }, + { 0x0e3a, 0x0e3a, 9 }, + { 0x0e48, 0x0e4b, 107 }, + { 0x0eb8, 0x0eb9, 118 }, + { 0x0eba, 0x0eba, 9 }, + { 0x0ec8, 0x0ecb, 122 }, + { 0x0f18, 0x0f19, 220 }, + { 0x0f35, 0x0f35, 220 }, + { 0x0f37, 0x0f37, 220 }, + { 0x0f39, 0x0f39, 216 }, + { 0x0f71, 0x0f71, 129 }, + { 0x0f72, 0x0f72, 130 }, + { 0x0f74, 0x0f74, 132 }, + { 0x0f7a, 0x0f7d, 130 }, + { 0x0f80, 0x0f80, 130 }, + { 0x0f82, 0x0f83, 230 }, + { 0x0f84, 0x0f84, 9 }, + { 0x0f86, 0x0f87, 230 }, + { 0x0fc6, 0x0fc6, 220 }, + { 0x1037, 0x1037, 7 }, + { 0x1039, 0x103a, 9 }, + { 0x108d, 0x108d, 220 }, + { 0x135d, 0x135f, 230 }, + { 0x1714, 0x1714, 9 }, + { 0x1734, 0x1734, 9 }, + { 0x17d2, 0x17d2, 9 }, + { 0x17dd, 0x17dd, 230 }, + { 0x18a9, 0x18a9, 228 }, + { 0x1939, 0x1939, 222 }, + { 0x193a, 0x193a, 230 }, + { 0x193b, 0x193b, 220 }, + { 0x1a17, 0x1a17, 230 }, + { 0x1a18, 0x1a18, 220 }, + { 0x1a60, 0x1a60, 9 }, + { 0x1a75, 0x1a7c, 230 }, + { 0x1a7f, 0x1a7f, 220 }, + { 0x1ab0, 0x1ab4, 230 }, + { 0x1ab5, 0x1aba, 220 }, + { 0x1abb, 0x1abc, 230 }, + { 0x1abd, 0x1abd, 220 }, + { 0x1b34, 0x1b34, 7 }, + { 0x1b44, 0x1b44, 9 }, + { 0x1b6b, 0x1b6b, 230 }, + { 0x1b6c, 0x1b6c, 220 }, + { 0x1b6d, 0x1b73, 230 }, + { 0x1baa, 0x1baa, 9 }, + { 0x1bab, 0x1bab, 9 }, + { 0x1be6, 0x1be6, 7 }, + { 0x1bf2, 0x1bf3, 9 }, + { 0x1c37, 0x1c37, 7 }, + { 0x1cd0, 0x1cd2, 230 }, + { 0x1cd4, 0x1cd4, 1 }, + { 0x1cd5, 0x1cd9, 220 }, + { 0x1cda, 0x1cdb, 230 }, + { 0x1cdc, 0x1cdf, 220 }, + { 0x1ce0, 0x1ce0, 230 }, + { 0x1ce2, 0x1ce8, 1 }, + { 0x1ced, 0x1ced, 220 }, + { 0x1cf4, 0x1cf4, 230 }, + { 0x1cf8, 0x1cf9, 230 }, + { 0x1dc0, 0x1dc1, 230 }, + { 0x1dc2, 0x1dc2, 220 }, + { 0x1dc3, 0x1dc9, 230 }, + { 0x1dca, 0x1dca, 220 }, + { 0x1dcb, 0x1dcc, 230 }, + { 0x1dcd, 0x1dcd, 234 }, + { 0x1dce, 0x1dce, 214 }, + { 0x1dcf, 0x1dcf, 220 }, + { 0x1dd0, 0x1dd0, 202 }, + { 0x1dd1, 0x1df5, 230 }, + { 0x1df6, 0x1df6, 232 }, + { 0x1df7, 0x1df8, 228 }, + { 0x1df9, 0x1df9, 220 }, + { 0x1dfb, 0x1dfb, 230 }, + { 0x1dfc, 0x1dfc, 233 }, + { 0x1dfd, 0x1dfd, 220 }, + { 0x1dfe, 0x1dfe, 230 }, + { 0x1dff, 0x1dff, 220 }, + { 0x20d0, 0x20d1, 230 }, + { 0x20d2, 0x20d3, 1 }, + { 0x20d4, 0x20d7, 230 }, + { 0x20d8, 0x20da, 1 }, + { 0x20db, 0x20dc, 230 }, + { 0x20e1, 0x20e1, 230 }, + { 0x20e5, 0x20e6, 1 }, + { 0x20e7, 0x20e7, 230 }, + { 0x20e8, 0x20e8, 220 }, + { 0x20e9, 0x20e9, 230 }, + { 0x20ea, 0x20eb, 1 }, + { 0x20ec, 0x20ef, 220 }, + { 0x20f0, 0x20f0, 230 }, + { 0x2cef, 0x2cf1, 230 }, + { 0x2d7f, 0x2d7f, 9 }, + { 0x2de0, 0x2dff, 230 }, + { 0x302a, 0x302a, 218 }, + { 0x302b, 0x302b, 228 }, + { 0x302c, 0x302c, 232 }, + { 0x302d, 0x302d, 222 }, + { 0x302e, 0x302f, 224 }, + { 0x3099, 0x309a, 8 }, + { 0xa66f, 0xa66f, 230 }, + { 0xa674, 0xa67d, 230 }, + { 0xa69e, 0xa69f, 230 }, + { 0xa6f0, 0xa6f1, 230 }, + { 0xa806, 0xa806, 9 }, + { 0xa8c4, 0xa8c4, 9 }, + { 0xa8e0, 0xa8f1, 230 }, + { 0xa92b, 0xa92d, 220 }, + { 0xa953, 0xa953, 9 }, + { 0xa9b3, 0xa9b3, 7 }, + { 0xa9c0, 0xa9c0, 9 }, + { 0xaab0, 0xaab0, 230 }, + { 0xaab2, 0xaab3, 230 }, + { 0xaab4, 0xaab4, 220 }, + { 0xaab7, 0xaab8, 230 }, + { 0xaabe, 0xaabf, 230 }, + { 0xaac1, 0xaac1, 230 }, + { 0xaaf6, 0xaaf6, 9 }, + { 0xabed, 0xabed, 9 }, + { 0xfb1e, 0xfb1e, 26 }, + { 0xfe20, 0xfe26, 230 }, + { 0xfe27, 0xfe2d, 220 }, + { 0xfe2e, 0xfe2f, 230 }, +}; + +/* + * Some HFS Plus catalog names contain characters whose canonical + * decomposition either was not yet defined, or was later corrected, in + * the version of Unicode this file's tables above were generated from + * (Apple Technote #1150, itself derived from pre-Unicode-3.0/pre-"Jaguar" + * (Mac OS X 10.2) data). macOS's own fsck_hfs has carried a permanent + * "FixDecomps" fixup for exactly this since 2002 (see + * fsck_hfs.tproj/dfalib/CatalogCheck.c and DecompData.h in the hfsprogs / + * diskdev_cmds sources) and flags any catalog name it finds still using + * the old form as "Illegal name". The two tables and functions below are + * a port of that same fixup data, decoded from hfsprogs 540.1's + * DecompData.h, so this driver stores names the same way macOS has since + * 10.2 rather than the way the original HFS Plus format documentation + * described in 2001. + */ + +/* Category A: single code point -> missing canonical decomposition */ +/* 44 entries */ +struct hfsplus_legacy_decomp hfsplus_legacy_decomp_table[] = { + { 0x01f8, 2, { 0x004e, 0x0300 } }, + { 0x01f9, 2, { 0x006e, 0x0300 } }, + { 0x0218, 2, { 0x0053, 0x0326 } }, + { 0x0219, 2, { 0x0073, 0x0326 } }, + { 0x021a, 2, { 0x0054, 0x0326 } }, + { 0x021b, 2, { 0x0074, 0x0326 } }, + { 0x021e, 2, { 0x0048, 0x030c } }, + { 0x021f, 2, { 0x0068, 0x030c } }, + { 0x0226, 2, { 0x0041, 0x0307 } }, + { 0x0227, 2, { 0x0061, 0x0307 } }, + { 0x0228, 2, { 0x0045, 0x0327 } }, + { 0x0229, 2, { 0x0065, 0x0327 } }, + { 0x022a, 3, { 0x004f, 0x0308, 0x0304 } }, + { 0x022b, 3, { 0x006f, 0x0308, 0x0304 } }, + { 0x022c, 3, { 0x004f, 0x0303, 0x0304 } }, + { 0x022d, 3, { 0x006f, 0x0303, 0x0304 } }, + { 0x022e, 2, { 0x004f, 0x0307 } }, + { 0x022f, 2, { 0x006f, 0x0307 } }, + { 0x0230, 3, { 0x004f, 0x0307, 0x0304 } }, + { 0x0231, 3, { 0x006f, 0x0307, 0x0304 } }, + { 0x0232, 2, { 0x0059, 0x0304 } }, + { 0x0233, 2, { 0x0079, 0x0304 } }, + { 0x0400, 2, { 0x0415, 0x0300 } }, + { 0x040d, 2, { 0x0418, 0x0300 } }, + { 0x0450, 2, { 0x0435, 0x0300 } }, + { 0x045d, 2, { 0x0438, 0x0300 } }, + { 0x04ec, 2, { 0x042d, 0x0308 } }, + { 0x04ed, 2, { 0x044d, 0x0308 } }, + { 0x0622, 2, { 0x0627, 0x0653 } }, + { 0x0623, 2, { 0x0627, 0x0654 } }, + { 0x0624, 2, { 0x0648, 0x0654 } }, + { 0x0625, 2, { 0x0627, 0x0655 } }, + { 0x0626, 2, { 0x064a, 0x0654 } }, + { 0x06c0, 2, { 0x06d5, 0x0654 } }, + { 0x06c2, 2, { 0x06c1, 0x0654 } }, + { 0x06d3, 2, { 0x06d2, 0x0654 } }, + { 0x0a33, 2, { 0x0a32, 0x0a3c } }, + { 0x0a36, 2, { 0x0a38, 0x0a3c } }, + { 0x0dda, 2, { 0x0dd9, 0x0dca } }, + { 0x0ddc, 2, { 0x0dd9, 0x0dcf } }, + { 0x0ddd, 3, { 0x0dd9, 0x0dcf, 0x0dca } }, + { 0x0dde, 2, { 0x0dd9, 0x0ddf } }, + { 0x1026, 2, { 0x1025, 0x102e } }, + { 0xfb1d, 2, { 0x05d9, 0x05b4 } }, +}; + +/* Category B: 2-3 code point sequence -> corrected replacement */ +/* 25 entries */ +struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[] = { + { 2, { 0x00a8, 0x030d }, 2, { 0x00a8, 0x0301 } }, + { 2, { 0x0306, 0x0307 }, 1, { 0x0310 } }, + { 2, { 0x0308, 0x030d }, 2, { 0x0308, 0x0301 } }, + { 2, { 0x0391, 0x030d }, 2, { 0x0391, 0x0301 } }, + { 2, { 0x0395, 0x030d }, 2, { 0x0395, 0x0301 } }, + { 2, { 0x0397, 0x030d }, 2, { 0x0397, 0x0301 } }, + { 2, { 0x0399, 0x030d }, 2, { 0x0399, 0x0301 } }, + { 2, { 0x039f, 0x030d }, 2, { 0x039f, 0x0301 } }, + { 2, { 0x03a5, 0x030d }, 2, { 0x03a5, 0x0301 } }, + { 2, { 0x03a9, 0x030d }, 2, { 0x03a9, 0x0301 } }, + { 2, { 0x03b1, 0x030d }, 2, { 0x03b1, 0x0301 } }, + { 2, { 0x03b5, 0x030d }, 2, { 0x03b5, 0x0301 } }, + { 2, { 0x03b7, 0x030d }, 2, { 0x03b7, 0x0301 } }, + { 2, { 0x03b9, 0x030d }, 2, { 0x03b9, 0x0301 } }, + { 2, { 0x03bf, 0x030d }, 2, { 0x03bf, 0x0301 } }, + { 2, { 0x03c5, 0x030d }, 2, { 0x03c5, 0x0301 } }, + { 2, { 0x03c9, 0x030d }, 2, { 0x03c9, 0x0301 } }, + { 2, { 0x03d2, 0x030d }, 2, { 0x03d2, 0x0301 } }, + { 2, { 0x09ac, 0x09bc }, 1, { 0x09b0 } }, + { 2, { 0x0a21, 0x0a3c }, 1, { 0x0a5c } }, + { 2, { 0x0b2f, 0x0b3c }, 1, { 0x0b5f } }, + { 2, { 0x0e4d, 0x0e32 }, 1, { 0x0e33 } }, + { 2, { 0x0ecd, 0x0eb2 }, 1, { 0x0eb3 } }, + { 3, { 0x0fb2, 0x0f80, 0x0f71 }, 1, { 0x0f77 } }, + { 3, { 0x0fb3, 0x0f80, 0x0f71 }, 1, { 0x0f79 } }, +}; diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c index 008fec186382b3..93d39481e47776 100644 --- a/fs/hfsplus/unicode.c +++ b/fs/hfsplus/unicode.c @@ -16,6 +16,7 @@ #include "hfsplus_fs.h" #include "hfsplus_raw.h" +#include "unicode.h" /* Fold the case of a unicode char, given the 16 bit value */ /* Returns folded char, or 0 if ignorable */ diff --git a/fs/hfsplus/unicode.h b/fs/hfsplus/unicode.h new file mode 100644 index 00000000000000..3957ebccc232b7 --- /dev/null +++ b/fs/hfsplus/unicode.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Unicode related declarations + */ + +#ifndef _LINUX_HFSPLUS_UNICODE_H +#define _LINUX_HFSPLUS_UNICODE_H + +/* + * struct hfsplus_ccc_range - one run of consecutive BMP code points that + * share the same Unicode canonical combining class + * @first: the first UTF-16 code point in the run + * @last: the last code point in the run (inclusive) + * @combining_class: canonical combining class (ccc) shared by @first..@last + */ +struct hfsplus_ccc_range { + u16 first; + u16 last; + u8 combining_class; +}; + +/* + * struct hfsplus_legacy_decomp - corrected canonical decomposition + * @uc: the code point (a UTF-16 code unit) to look up + * @len: number of valid code units in @repl + * @repl: corrected decomposition sequence (base letter + combining marks) + */ +struct hfsplus_legacy_decomp { + u16 uc; + u8 len; +#define HFSPLUS_LEGACY_DECOMP_MAX_LEN (3) + u16 repl[HFSPLUS_LEGACY_DECOMP_MAX_LEN]; +}; + +/* + * struct hfsplus_legacy_seq_fixup - corrected replacement for a short + * sequence of already-decomposed code units + * @match_len: number of valid code units in @match + * @match: decomposed sequence to look for in the buffer + * @repl_len: number of valid code units in @repl + * @repl: corrected sequence to substitute in place of @match + */ +struct hfsplus_legacy_seq_fixup { + u8 match_len; +#define HFSPLUS_LEGACY_SEQ_MAX_MATCH (3) + u16 match[HFSPLUS_LEGACY_SEQ_MAX_MATCH]; + u8 repl_len; +#define HFSPLUS_LEGACY_SEQ_MAX_REPL (2) + u16 repl[HFSPLUS_LEGACY_SEQ_MAX_REPL]; +}; + +extern u16 hfsplus_case_fold_table[]; +extern u16 hfsplus_decompose_table[]; +extern u16 hfsplus_compose_table[]; +extern struct hfsplus_ccc_range hfsplus_ccc_table[]; +extern struct hfsplus_legacy_decomp hfsplus_legacy_decomp_table[]; +extern struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[]; + +#endif /* _LINUX_HFSPLUS_UNICODE_H */ diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c index 7f6b2a3c69d9a4..32a64029d77bc8 100644 --- a/fs/hfsplus/unicode_test.c +++ b/fs/hfsplus/unicode_test.c @@ -10,6 +10,7 @@ #include #include #include "hfsplus_fs.h" +#include "unicode.h" struct test_mock_string_env { struct hfsplus_unistr str1; From bbe5ebcbc006ac3c7353759fc593fb7cb7de76a1 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Fri, 18 Sep 2026 17:16:06 -0700 Subject: [PATCH 0922/2286] hfsplus: canonicalize decomposed catalog names like fsck_hfs expects hfsplus_decompose_table (Apple Technote #1150) decomposes each source character entirely on its own. It never reorders the result, and it predates a handful of later corrections to the decomposition standard. Two concrete consequences, both of which macOS's own fsck_hfs treats as "Illegal name" (CatalogCheck.c: CheckCatalogName(), FixDecomps()): - When combining marks contributed by two different source characters end up adjacent, this driver stores them in input order rather than ascending Unicode combining-class order. - A small, fixed set of characters either aren't decomposed at all, or are decomposed into a sequence macOS corrected back in Mac OS X 10.2 ("Jaguar"). This is reliably reproducible: xfstests generic/339 exercises dirhash collisions by creating many files with randomized Unicode names, and the resulting HFS+ volume fails _check_generic_filesystem afterward with a long run of "Illegal name" reports from fsck.hfsplus. Fix hfsplus_asc2uni() to store names the way current macOS does, using the Unicode tables: - hfsplus_canonical_reorder() applies the Unicode Canonical Ordering Algorithm: each maximal run of nonzero-combining-class code units is stable-sorted into ascending class order. - decompose_unichar() falls back to hfsplus_legacy_decompose() for the characters missing from Apple Technote #1150's table. - hfsplus_fixup_legacy_sequences() substitutes any of fsck_hfs's known bad sequences with their corrected form. hfsplus_decompose_str() ties these together and is now shared by hfsplus_asc2uni(), hfsplus_hash_dentry() and hfsplus_compare_dentry(), so hashing and comparison always agree with what actually gets stored - otherwise two byte-for-byte different but canonically-equivalent spellings of a name could hash differently or fail to compare equal against the catalog entry a create() of either would produce. cc: John Paul Adrian Glaubitz cc: Yangtao Li cc: linux-fsdevel@vger.kernel.org Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260919001607.2777138-3-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- fs/hfsplus/tables.c | 99 +++++++++++ fs/hfsplus/unicode.c | 389 ++++++++++++++++++++++++++++--------------- fs/hfsplus/unicode.h | 4 + 3 files changed, 360 insertions(+), 132 deletions(-) diff --git a/fs/hfsplus/tables.c b/fs/hfsplus/tables.c index 3bbdc83debb524..2101201ab973f6 100644 --- a/fs/hfsplus/tables.c +++ b/fs/hfsplus/tables.c @@ -3641,3 +3641,102 @@ struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[] = { { 3, { 0x0fb2, 0x0f80, 0x0f71 }, 1, { 0x0f77 } }, { 3, { 0x0fb3, 0x0f80, 0x0f71 }, 1, { 0x0f79 } }, }; + +/* + * Look up the Unicode canonical combining class of a BMP code point. + * Returns 0 ("Not_Reordered") for any code point not listed in the table, + * which includes every ordinary base character. + */ +u8 hfsplus_combining_class(u16 c) +{ + int lo = 0, hi = ARRAY_SIZE(hfsplus_ccc_table) - 1; + + while (lo <= hi) { + int mid = (lo + hi) / 2; + const struct hfsplus_ccc_range *r = &hfsplus_ccc_table[mid]; + + if (c < r->first) + hi = mid - 1; + else if (c > r->last) + lo = mid + 1; + else + return r->combining_class; + } + + return 0; +} + +/* + * Look up the corrected canonical decomposition for a single BMP code + * point that Apple Technote #1150's own decomposition table (above) + * lacks. Returns NULL (and leaves *size alone) if @uc isn't one of these. + */ +const u16 *hfsplus_legacy_decompose(u16 uc, int *size) +{ + int lo = 0, hi = ARRAY_SIZE(hfsplus_legacy_decomp_table) - 1; + + while (lo <= hi) { + int mid = (lo + hi) / 2; + const struct hfsplus_legacy_decomp *e = + &hfsplus_legacy_decomp_table[mid]; + + if (uc < e->uc) + hi = mid - 1; + else if (uc > e->uc) + lo = mid + 1; + else { + *size = e->len; + return e->repl; + } + } + + return NULL; +} + +/* + * Scan an already decomposed and canonically-reordered code unit buffer + * for any of the short legacy sequences above and replace them in place + * with their corrected form. @len is updated to the buffer's new length. + */ +void hfsplus_fixup_legacy_sequences(u16 *buf, int *len) +{ + int i = 0; + + while (i < *len) { + unsigned int j; + bool matched = false; + + for (j = 0; j < ARRAY_SIZE(hfsplus_legacy_seq_fixups); j++) { + struct hfsplus_legacy_seq_fixup *f = + &hfsplus_legacy_seq_fixups[j]; + u16 *src, *dst; + size_t copy_len; + int k; + + if (i + f->match_len > *len) + continue; + for (k = 0; k < f->match_len; k++) + if (buf[i + k] != f->match[k]) + break; + if (k != f->match_len) + continue; + + dst = &buf[i + f->repl_len]; + src = &buf[i + f->match_len]; + copy_len = (*len - i - f->match_len) * sizeof(*buf); + memmove(dst, src, copy_len); + + dst = &buf[i]; + src = f->repl; + copy_len = f->repl_len * sizeof(*buf); + memcpy(dst, src, copy_len); + + *len += f->repl_len - f->match_len; + matched = true; + break; + } + + if (!matched) + i++; + } +} diff --git a/fs/hfsplus/unicode.c b/fs/hfsplus/unicode.c index 93d39481e47776..59b8788a69a9ed 100644 --- a/fs/hfsplus/unicode.c +++ b/fs/hfsplus/unicode.c @@ -442,45 +442,264 @@ static u16 *decompose_unichar(wchar_t uc, int *size, u16 *hangul_buffer) *size = hfsplus_try_decompose_hangul(uc, result); if (*size == 0) result = hfsplus_decompose_nonhangul(uc, size); + if (!result) { + /* + * Not every character with a canonical decomposition is in + * Apple Technote #1150's own table above; a small, fixed + * set was only added to the decomposition standard (or had + * its decomposition corrected) after that table was + * generated. hfsplus_legacy_decompose() covers those. + */ + const u16 *legacy = hfsplus_legacy_decompose(uc, size); + + if (legacy) + result = memcpy(hangul_buffer, legacy, + *size * sizeof(*legacy)); + } return result; } -int hfsplus_asc2uni(struct super_block *sb, - struct hfsplus_unistr *ustr, int max_unistr_len, - const char *astr, int len, int name_type) +/* + * Apply the Unicode Canonical Ordering Algorithm to a decomposed name held + * as plain host-order code units: within each maximal run of characters + * that have a nonzero combining class, stable-sort the run into ascending + * combining-class order. A character with combining class 0 always starts + * a new run and is never itself reordered. + * + * hfsplus_decompose_table decomposes each source character on its own; it + * says nothing about how the decompositions of two different source + * characters should be ordered relative to each other when both produce + * combining marks that end up adjacent. Without this pass, such a + * sequence can be stored in an order that macOS's own fsck_hfs + * (FixDecomps() in CatalogCheck.c) considers illegal, even though every + * individual character was decomposed correctly. + */ +static void hfsplus_canonical_reorder(u16 *ustr, int len) { - int size, dsize, decompose; - u16 *dstr, outlen = 0; - wchar_t c; - u16 dhangul[3]; + int i; + + for (i = 1; i < len; i++) { + u8 cls = hfsplus_combining_class(ustr[i]); + int j = i; + + if (!cls) + continue; + + while (j > 0) { + u8 prev_cls = hfsplus_combining_class(ustr[j - 1]); + u16 tmp; + + if (!prev_cls || prev_cls <= cls) + break; + + tmp = ustr[j]; + ustr[j] = ustr[j - 1]; + ustr[j - 1] = tmp; + j--; + } + } +} + +#define HFSPLUS_HANGUL_MAX_JAMO (3) /* L + V + optional T */ + +static_assert(HFSPLUS_HANGUL_MAX_JAMO >= HFSPLUS_LEGACY_DECOMP_MAX_LEN, + "hangul_buffer must fit the longest legacy decomposition too"); + +/* + * Decompose and canonically reorder an entire Linux name, as plain + * host-order code units. hfsplus_asc2uni(), hfsplus_hash_dentry() and + * hfsplus_compare_dentry() all go through this so that storage, hashing + * and comparison always agree on what a given name canonicalizes to. + * + * @out must hold at least @max_len entries, which must not exceed + * HFSPLUS_MAX_STRLEN. Returns the number of code units written. If + * @consumed is non-NULL, it is set to the number of input bytes actually + * consumed, which is less than @len when @out fills up first. + */ +static int hfsplus_decompose_str(struct super_block *sb, const char *astr, + int len, int max_len, int name_type, + u16 *out, int *consumed) +{ + int decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); + const char *start = astr; + int outlen = 0; + + while (outlen < max_len && len > 0) { + u16 *dstr; + u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO]; + int dsize, size; + wchar_t c; - decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); - while (outlen < max_unistr_len && len > 0) { size = asc2unichar(sb, astr, len, &c, name_type); - if (decompose) - dstr = decompose_unichar(c, &dsize, dhangul); - else - dstr = NULL; + dstr = decompose ? decompose_unichar(c, &dsize, dhangul) : NULL; if (dstr) { - if (outlen + dsize > max_unistr_len) + if (outlen + dsize > max_len) break; do { - ustr->unicode[outlen++] = cpu_to_be16(*dstr++); + out[outlen++] = *dstr++; } while (--dsize > 0); - } else - ustr->unicode[outlen++] = cpu_to_be16(c); + } else { + out[outlen++] = c; + } astr += size; len -= size; } + + hfsplus_canonical_reorder(out, outlen); + hfsplus_fixup_legacy_sequences(out, &outlen); + if (consumed) + *consumed = astr - start; + return outlen; +} + +int hfsplus_asc2uni(struct super_block *sb, + struct hfsplus_unistr *ustr, int max_unistr_len, + const char *astr, int len, int name_type) +{ + u16 buf[HFSPLUS_MAX_STRLEN]; + int outlen, i, consumed; + + if (max_unistr_len > HFSPLUS_MAX_STRLEN) + max_unistr_len = HFSPLUS_MAX_STRLEN; + + outlen = hfsplus_decompose_str(sb, astr, len, max_unistr_len, + name_type, buf, &consumed); + for (i = 0; i < outlen; i++) + ustr->unicode[i] = cpu_to_be16(buf[i]); ustr->length = cpu_to_be16(outlen); - if (len > 0) + + if (consumed < len) return -ENAMETOOLONG; return 0; } EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni); +/* + * Maximum length of a single maximal run of nonzero-combining-class code + * units that hfsplus_decompose_iter_next() below will canonically + * reorder. Every code unit with combining class 0 starts a new run, so + * this only bounds how many *consecutive* combining marks between two + * base characters get sorted - real text, and even deliberately + * adversarial "Zalgo" text, essentially never approaches this. Keeping + * it small means hfsplus_hash_dentry() and hfsplus_compare_dentry() only + * ever need a tiny amount of lookahead state, rather than buffering an + * entire (up to 255-unit) name. + */ +#define HFSPLUS_CCC_RUN_MAX 32 + +/* + * Iterator that produces the canonically-ordered, decomposed code units + * of a Linux name one at a time, without materializing the whole name. + * hfsplus_hash_dentry() and hfsplus_compare_dentry() use this so that + * hashing and comparison always agree with what hfsplus_asc2uni() would + * actually store, while still comparing lazily (stopping at the first + * difference) the way this code did before canonical reordering existed. + */ +struct hfsplus_decompose_iter { + struct super_block *sb; + const char *astr; + int len; + int name_type; + int decompose; + + u16 run[HFSPLUS_CCC_RUN_MAX]; + int run_len; + int run_pos; +}; + +static void hfsplus_decompose_iter_init(struct hfsplus_decompose_iter *it, + struct super_block *sb, + const char *astr, int len, + int name_type) +{ + it->sb = sb; + it->astr = astr; + it->len = len; + it->name_type = name_type; + it->decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); + it->run_len = 0; + it->run_pos = 0; +} + +/* + * Gather the next maximal run of nonzero-combining-class code units + * (starting with whatever character comes next, base or not) and + * canonically reorder just that run. A character is only included once + * we know the class of the first unit it produces, so decoding it is + * speculative until that's decided. + */ +static bool hfsplus_decompose_iter_refill(struct hfsplus_decompose_iter *it) +{ + it->run_pos = 0; + it->run_len = 0; + + while (it->len > 0) { + u16 *dstr; + u16 dhangul[HFSPLUS_HANGUL_MAX_JAMO]; + int dsize, size; + wchar_t c; + + size = asc2unichar(it->sb, it->astr, it->len, &c, + it->name_type); + + dstr = it->decompose ? + decompose_unichar(c, &dsize, dhangul) : NULL; + if (!dstr) { + dhangul[0] = c; + dstr = dhangul; + dsize = 1; + } + + if (it->run_len > 0 && !hfsplus_combining_class(dstr[0])) + break; + + if (it->run_len + dsize > HFSPLUS_CCC_RUN_MAX) + break; + + it->astr += size; + it->len -= size; + + do { + it->run[it->run_len++] = *dstr++; + } while (--dsize > 0); + } + + hfsplus_canonical_reorder(it->run, it->run_len); + hfsplus_fixup_legacy_sequences(it->run, &it->run_len); + return it->run_len > 0; +} + +/* Returns the next code unit, or a negative value once @it is exhausted. */ +static int hfsplus_decompose_iter_next(struct hfsplus_decompose_iter *it) +{ + if (it->run_pos >= it->run_len && !hfsplus_decompose_iter_refill(it)) + return -1; + return it->run[it->run_pos++]; +} + +/* + * Returns the next code unit that matters for comparison/hashing: folded + * if @casefold, with any character folding to 0 ("ignorable") skipped + * entirely. Returns a negative value once @it is exhausted. + */ +static int hfsplus_decompose_iter_next_folded(struct hfsplus_decompose_iter *it, + int casefold) +{ + int c; + + do { + c = hfsplus_decompose_iter_next(it); + if (c < 0) + return -1; + if (casefold) + c = case_fold(c); + } while (casefold && !c); + + return c; +} + /* * Hash a string to an integer as appropriate for the HFS+ filesystem. * Composed unicode characters are decomposed and case-folding is performed @@ -489,45 +708,17 @@ EXPORT_SYMBOL_IF_KUNIT(hfsplus_asc2uni); int hfsplus_hash_dentry(const struct dentry *dentry, struct qstr *str) { struct super_block *sb = dentry->d_sb; - const char *astr; - const u16 *dstr; - int casefold, decompose, size, len; + int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags); + struct hfsplus_decompose_iter it; unsigned long hash; - wchar_t c; - u16 c2; - u16 dhangul[3]; + int c; - casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags); - decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); - hash = init_name_hash(dentry); - astr = str->name; - len = str->len; - while (len > 0) { - int dsize; - size = asc2unichar(sb, astr, len, &c, HFS_REGULAR_NAME); - astr += size; - len -= size; + hfsplus_decompose_iter_init(&it, sb, str->name, str->len, + HFS_REGULAR_NAME); - if (decompose) - dstr = decompose_unichar(c, &dsize, dhangul); - else - dstr = NULL; - if (dstr) { - do { - c2 = *dstr++; - if (casefold) - c2 = case_fold(c2); - if (!casefold || c2) - hash = partial_name_hash(c2, hash); - } while (--dsize > 0); - } else { - c2 = c; - if (casefold) - c2 = case_fold(c2); - if (!casefold || c2) - hash = partial_name_hash(c2, hash); - } - } + hash = init_name_hash(dentry); + while ((c = hfsplus_decompose_iter_next_folded(&it, casefold)) >= 0) + hash = partial_name_hash(c, hash); str->hash = end_name_hash(hash); return 0; @@ -543,87 +734,21 @@ int hfsplus_compare_dentry(const struct dentry *dentry, unsigned int len, const char *str, const struct qstr *name) { struct super_block *sb = dentry->d_sb; - int casefold, decompose, size; - int dsize1, dsize2, len1, len2; - const u16 *dstr1, *dstr2; - const char *astr1, *astr2; - u16 c1, c2; - wchar_t c; - u16 dhangul_1[3], dhangul_2[3]; - - casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags); - decompose = !test_bit(HFSPLUS_SB_NODECOMPOSE, &HFSPLUS_SB(sb)->flags); - astr1 = str; - len1 = len; - astr2 = name->name; - len2 = name->len; - dsize1 = dsize2 = 0; - dstr1 = dstr2 = NULL; - - while (len1 > 0 && len2 > 0) { - if (!dsize1) { - size = asc2unichar(sb, astr1, len1, &c, - HFS_REGULAR_NAME); - astr1 += size; - len1 -= size; - - if (decompose) - dstr1 = decompose_unichar(c, &dsize1, - dhangul_1); - if (!decompose || !dstr1) { - c1 = c; - dstr1 = &c1; - dsize1 = 1; - } - } + int casefold = test_bit(HFSPLUS_SB_CASEFOLD, &HFSPLUS_SB(sb)->flags); + struct hfsplus_decompose_iter it1, it2; - if (!dsize2) { - size = asc2unichar(sb, astr2, len2, &c, - HFS_REGULAR_NAME); - astr2 += size; - len2 -= size; - - if (decompose) - dstr2 = decompose_unichar(c, &dsize2, - dhangul_2); - if (!decompose || !dstr2) { - c2 = c; - dstr2 = &c2; - dsize2 = 1; - } - } + hfsplus_decompose_iter_init(&it1, sb, str, len, HFS_REGULAR_NAME); + hfsplus_decompose_iter_init(&it2, sb, name->name, name->len, + HFS_REGULAR_NAME); - c1 = *dstr1; - c2 = *dstr2; - if (casefold) { - c1 = case_fold(c1); - if (!c1) { - dstr1++; - dsize1--; - continue; - } - c2 = case_fold(c2); - if (!c2) { - dstr2++; - dsize2--; - continue; - } - } - if (c1 < c2) - return -1; - else if (c1 > c2) - return 1; + while (1) { + int c1 = hfsplus_decompose_iter_next_folded(&it1, casefold); + int c2 = hfsplus_decompose_iter_next_folded(&it2, casefold); - dstr1++; - dsize1--; - dstr2++; - dsize2--; + if (c1 < 0 || c2 < 0) + return c1 == c2 ? 0 : (c1 < 0 ? -1 : 1); + if (c1 != c2) + return c1 < c2 ? -1 : 1; } - - if (len1 < len2) - return -1; - if (len1 > len2) - return 1; - return 0; } EXPORT_SYMBOL_IF_KUNIT(hfsplus_compare_dentry); diff --git a/fs/hfsplus/unicode.h b/fs/hfsplus/unicode.h index 3957ebccc232b7..31cc7691c91623 100644 --- a/fs/hfsplus/unicode.h +++ b/fs/hfsplus/unicode.h @@ -56,4 +56,8 @@ extern struct hfsplus_ccc_range hfsplus_ccc_table[]; extern struct hfsplus_legacy_decomp hfsplus_legacy_decomp_table[]; extern struct hfsplus_legacy_seq_fixup hfsplus_legacy_seq_fixups[]; +u8 hfsplus_combining_class(u16 c); +const u16 *hfsplus_legacy_decompose(u16 uc, int *size); +void hfsplus_fixup_legacy_sequences(u16 *buf, int *len); + #endif /* _LINUX_HFSPLUS_UNICODE_H */ From 28d4963409507b6b1ebaa9aa1a2ab007bc745958 Mon Sep 17 00:00:00 2001 From: Viacheslav Dubeyko Date: Fri, 18 Sep 2026 17:16:07 -0700 Subject: [PATCH 0923/2286] hfsplus: add KUnit coverage for canonical reordering and legacy fixups Add regression tests for checking that decomposed catalog names get canonically reordered and legacy-decomposition corrected before storage, matching what macOS's own fsck_hfs requires and previously flagged as "Illegal name" (reproducible via xfstests generic/339). Two small helpers, test_char2uni_utf8()/test_uni2char_utf8(), wrap the real utf8_to_utf32()/utf32_to_utf8() codec: the existing test_char2uni()/test_uni2char() stand-ins only handle one raw byte per character and can't represent the multi-byte combining marks (U+0301, U+0323, ...) these tests need. New cases: - hfsplus_asc2uni_combining_reorder_test: two independently-typed combining marks (U+0301 class 230, U+0323 class 220) end up stored in ascending combining-class order regardless of typed order, are left alone when already canonical, and keep their relative order when their classes are equal (the sort must be stable). - hfsplus_unicode_combining_reorder_roundtrip_test: a name stored with marks reordered still reads back as the same, valid UTF-8. - hfsplus_asc2uni_legacy_decomp_test: U+01F8 decomposes to "N" + U+0300 even though Apple Technote #1150's table has no entry for it. - hfsplus_asc2uni_legacy_seq_fixup_test: a Greek letter + U+030D becomes that letter + U+0301, and Bengali BA + NUKTA collapses to the single letter RA WITH MIDDLE DIAGONAL. - hfsplus_hash_dentry_combining_reorder_test: two names differing only in typed mark order hash identically, since both canonicalize to the same stored form. - hfsplus_compare_dentry_combining_reorder_test and hfsplus_compare_dentry_legacy_decomp_test: likewise, both compare equal - a precomposed U+01F8 and its already-decomposed "N" + U+0300 spelling must be found as the same catalog entry. All 34 cases in the hfsplus_unicode KUnit suite (27 existing + 7 new) pass under tools/testing/kunit/kunit.py. Signed-off-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260919001607.2777138-4-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko --- fs/hfsplus/unicode_test.c | 308 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) diff --git a/fs/hfsplus/unicode_test.c b/fs/hfsplus/unicode_test.c index 32a64029d77bc8..a683c6d5fc2215 100644 --- a/fs/hfsplus/unicode_test.c +++ b/fs/hfsplus/unicode_test.c @@ -948,6 +948,223 @@ static void hfsplus_asc2uni_decompose_test(struct kunit *test) free_mock_sb(mock_sb); } +/* + * Real UTF-8 <-> wchar_t conversion, mirroring what the "utf8" NLS table + * actually does (see char2uni()/uni2char() in fs/nls/nls_utf8.c). The + * test_char2uni()/test_uni2char() stand-ins above only handle one raw + * byte per character, which cannot represent the combining marks (e.g. + * U+0301, U+0323) the canonical-reordering tests below need. + */ +static int test_char2uni_utf8(const unsigned char *rawstring, int boundlen, + wchar_t *uni) +{ + unicode_t u; + int n = utf8_to_utf32(rawstring, boundlen, &u); + + if (n < 0 || u > MAX_WCHAR_T) { + *uni = 0x3f; /* ? */ + return -EINVAL; + } + *uni = (wchar_t)u; + return n; +} + +static int test_uni2char_utf8(wchar_t uni, unsigned char *out, int boundlen) +{ + int n = utf32_to_utf8(uni, out, boundlen); + + if (n < 0) { + *out = '?'; + return -EINVAL; + } + return n; +} + +/* + * Test that hfsplus_asc2uni() brings combining marks contributed by + * different source characters into Unicode canonical (combining-class) + * order, instead of just storing them in whatever order they were typed. + * + * U+0301 (COMBINING ACUTE ACCENT) has combining class 230; U+0323 + * (COMBINING DOT BELOW) has combining class 220. Since 220 < 230, + * canonical order requires the dot-below before the acute. A name + * created with them typed in the "wrong" order must still end up stored + * in canonical order - this is exactly what macOS's own fsck_hfs + * (FixDecomps() in CatalogCheck.c) requires and flags as "Illegal name" + * when it isn't true. + */ +static void hfsplus_asc2uni_combining_reorder_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct hfsplus_unistr ustr; + /* "e" + COMBINING ACUTE ACCENT (U+0301) + COMBINING DOT BELOW (U+0323) */ + static const char wrong_order[] = "e\xcc\x81\xcc\xa3"; + /* "e" + COMBINING DOT BELOW (U+0323) + COMBINING ACUTE ACCENT (U+0301) */ + static const char canonical_order[] = "e\xcc\xa3\xcc\x81"; + /* Two class-230 marks: GRAVE (U+0300) then ACUTE (U+0301) */ + static const char same_class[] = "e\xcc\x80\xcc\x81"; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni_utf8; + + /* Typed out of canonical order: must be reordered on storage. */ + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + wrong_order, strlen(wrong_order), + HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1])); + KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2])); + + /* Already in canonical order: must come out unchanged. */ + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + canonical_order, strlen(canonical_order), + HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0x0323, be16_to_cpu(ustr.unicode[1])); + KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2])); + + /* Two marks of equal combining class must keep their relative + * (input) order - the sort must be stable, not just "sorted". + */ + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + same_class, strlen(same_class), + HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 3, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 'e', be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1])); + KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[2])); + + free_mock_sb(mock_sb); +} + +/* + * Test that a name written with combining marks in non-canonical order + * still reads back as valid, correct UTF-8 once reordered - i.e. that + * hfsplus_asc2uni() and hfsplus_uni2asc_str() stay consistent with each + * other across the reordering. + * + * NODECOMPOSE is set so hfsplus_uni2asc_str() doesn't also recompose + * "e" + COMBINING DOT BELOW back into the precomposed U+1EB9 ("e"): that + * composition behavior is real (and already covered elsewhere), but it + * would obscure what this test is actually checking. + */ +static void hfsplus_unicode_combining_reorder_roundtrip_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct hfsplus_unistr ustr; + static const char wrong_order[] = "e\xcc\x81\xcc\xa3"; + static const char expected[] = "e\xcc\xa3\xcc\x81"; /* canonical order */ + char buf[32]; + int len = sizeof(buf); + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + set_bit(HFSPLUS_SB_NODECOMPOSE, &mock_sb->sb_info.flags); + mock_sb->nls.char2uni = test_char2uni_utf8; + mock_sb->nls.uni2char = test_uni2char_utf8; + + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + wrong_order, strlen(wrong_order), + HFS_REGULAR_NAME); + KUNIT_EXPECT_EQ(test, 0, result); + + result = hfsplus_uni2asc_str(&mock_sb->sb, &ustr, buf, &len); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, (int)strlen(expected), len); + KUNIT_EXPECT_MEMEQ(test, expected, buf, len); + + free_mock_sb(mock_sb); +} + +/* + * Test that hfsplus_asc2uni() decomposes a character using the corrected + * (post-2002/"Jaguar") canonical decomposition even when Apple Technote + * #1150's own decomposition table doesn't have an entry for it. + * + * U+01F8 (LATIN CAPITAL LETTER N WITH GRAVE) is exactly one of the + * characters macOS's own fsck_hfs (FixDecomps() in CatalogCheck.c) has + * flagged "Illegal name" for since 2002 when found stored undecomposed. + */ +static void hfsplus_asc2uni_legacy_decomp_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct hfsplus_unistr ustr; + static const char input[] = "\xc7\xb8"; /* U+01F8 */ + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni_utf8; + + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + input, strlen(input), HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 'N', be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0x0300, be16_to_cpu(ustr.unicode[1])); + + free_mock_sb(mock_sb); +} + +/* + * Test that hfsplus_asc2uni() corrects two more of fsck_hfs's known + * legacy decomposition sequences once combining marks from independently + * typed characters end up adjacent: + * + * - GREEK SMALL LETTER ALPHA (U+03B1) + COMBINING VERTICAL LINE ABOVE + * (U+030D) must become U+03B1 + COMBINING ACUTE ACCENT (U+0301). + * - BENGALI LETTER BA (U+09AC) + BENGALI SIGN NUKTA (U+09BC) must become + * the single character BENGALI LETTER RA WITH MIDDLE DIAGONAL (U+09B0). + */ +static void hfsplus_asc2uni_legacy_seq_fixup_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct hfsplus_unistr ustr; + static const char greek_input[] = "\xce\xb1\xcc\x8d"; /* U+03B1 U+030D */ + static const char bengali_input[] = "\xe0\xa6\xac\xe0\xa6\xbc"; /* U+09AC U+09BC */ + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + mock_sb->nls.char2uni = test_char2uni_utf8; + + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + greek_input, strlen(greek_input), + HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 2, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 0x03b1, be16_to_cpu(ustr.unicode[0])); + KUNIT_EXPECT_EQ(test, 0x0301, be16_to_cpu(ustr.unicode[1])); + + result = hfsplus_asc2uni(&mock_sb->sb, &ustr, HFSPLUS_MAX_STRLEN, + bengali_input, strlen(bengali_input), + HFS_REGULAR_NAME); + + KUNIT_EXPECT_EQ(test, 0, result); + KUNIT_EXPECT_EQ(test, 1, be16_to_cpu(ustr.length)); + KUNIT_EXPECT_EQ(test, 0x09b0, be16_to_cpu(ustr.unicode[0])); + + free_mock_sb(mock_sb); +} + /* Mock dentry for testing hfsplus_hash_dentry */ static struct dentry test_dentry; @@ -1231,6 +1448,37 @@ static void hfsplus_hash_dentry_edge_cases_test(struct kunit *test) free_mock_sb(mock_sb); } +/* + * Test that hfsplus_hash_dentry() hashes two names identically when they + * differ only in the (non-canonical) typed order of the same combining + * marks - both must canonicalize to the same stored form, so they must + * hash the same or dcache lookups would spuriously miss. + */ +static void hfsplus_hash_dentry_combining_reorder_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr str1, str2; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni_utf8; + + create_qstr(&str1, "e\xcc\x81\xcc\xa3"); /* acute, then dot-below */ + result = hfsplus_hash_dentry(&test_dentry, &str1); + KUNIT_EXPECT_EQ(test, 0, result); + + create_qstr(&str2, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */ + result = hfsplus_hash_dentry(&test_dentry, &str2); + KUNIT_EXPECT_EQ(test, 0, result); + + KUNIT_EXPECT_EQ(test, str1.hash, str2.hash); + + free_mock_sb(mock_sb); +} + /* Test hfsplus_compare_dentry basic functionality */ static void hfsplus_compare_dentry_basic_test(struct kunit *test) { @@ -1553,6 +1801,59 @@ static void hfsplus_compare_dentry_combined_flags_test(struct kunit *test) free_mock_sb(mock_sb); } +/* + * Test that hfsplus_compare_dentry() treats two names as equal when they + * differ only in the (non-canonical) typed order of the same combining + * marks, since both refer to the same canonically-ordered catalog entry. + */ +static void hfsplus_compare_dentry_combining_reorder_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni_utf8; + + create_qstr(&name, "e\xcc\xa3\xcc\x81"); /* dot-below, then acute */ + result = hfsplus_compare_dentry(&test_dentry, 5, "e\xcc\x81\xcc\xa3", + &name); /* acute, then dot-below */ + KUNIT_EXPECT_EQ(test, 0, result); + + free_mock_sb(mock_sb); +} + +/* + * Test that a precomposed character using one of fsck_hfs's known legacy + * decompositions compares equal to the already-decomposed form of the + * same character - i.e. that hfsplus_compare_dentry() applies the same + * legacy-decomposition correction as hfsplus_asc2uni() does on storage, + * so a lookup finds the entry regardless of which form was typed. + */ +static void hfsplus_compare_dentry_legacy_decomp_test(struct kunit *test) +{ + struct test_mock_sb *mock_sb; + struct qstr name; + int result; + + mock_sb = setup_mock_sb(); + KUNIT_ASSERT_NOT_NULL(test, mock_sb); + + setup_mock_dentry(&mock_sb->sb); + mock_sb->nls.char2uni = test_char2uni_utf8; + + /* "N" + COMBINING GRAVE ACCENT (U+0300), already decomposed */ + create_qstr(&name, "N\xcc\x80"); + /* U+01F8, precomposed */ + result = hfsplus_compare_dentry(&test_dentry, 2, "\xc7\xb8", &name); + KUNIT_EXPECT_EQ(test, 0, result); + + free_mock_sb(mock_sb); +} + static struct kunit_case hfsplus_unicode_test_cases[] = { KUNIT_CASE(hfsplus_strcasecmp_test), KUNIT_CASE(hfsplus_strcmp_test), @@ -1568,12 +1869,17 @@ static struct kunit_case hfsplus_unicode_test_cases[] = { KUNIT_CASE(hfsplus_asc2uni_buffer_limits_test), KUNIT_CASE(hfsplus_asc2uni_edge_cases_test), KUNIT_CASE(hfsplus_asc2uni_decompose_test), + KUNIT_CASE(hfsplus_asc2uni_combining_reorder_test), + KUNIT_CASE(hfsplus_unicode_combining_reorder_roundtrip_test), + KUNIT_CASE(hfsplus_asc2uni_legacy_decomp_test), + KUNIT_CASE(hfsplus_asc2uni_legacy_seq_fixup_test), KUNIT_CASE(hfsplus_hash_dentry_basic_test), KUNIT_CASE(hfsplus_hash_dentry_casefold_test), KUNIT_CASE(hfsplus_hash_dentry_special_chars_test), KUNIT_CASE(hfsplus_hash_dentry_decompose_test), KUNIT_CASE(hfsplus_hash_dentry_consistency_test), KUNIT_CASE(hfsplus_hash_dentry_edge_cases_test), + KUNIT_CASE(hfsplus_hash_dentry_combining_reorder_test), KUNIT_CASE(hfsplus_compare_dentry_basic_test), KUNIT_CASE(hfsplus_compare_dentry_casefold_test), KUNIT_CASE(hfsplus_compare_dentry_special_chars_test), @@ -1581,6 +1887,8 @@ static struct kunit_case hfsplus_unicode_test_cases[] = { KUNIT_CASE(hfsplus_compare_dentry_decompose_test), KUNIT_CASE(hfsplus_compare_dentry_edge_cases_test), KUNIT_CASE(hfsplus_compare_dentry_combined_flags_test), + KUNIT_CASE(hfsplus_compare_dentry_combining_reorder_test), + KUNIT_CASE(hfsplus_compare_dentry_legacy_decomp_test), {} }; From 391cac638eddae8f4a0a8dc8168a260f939eb3eb Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 25 Sep 2026 16:24:53 +0000 Subject: [PATCH 0924/2286] selftests/x86: Add tests for signal frame FPU portability Add a new selftest tools/testing/selftests/x86/sigframe_fpu_portability.c to verify signal frame portability and consistency when the xstate size is shrunk: - test_valid_shrunk_xstate_size: Verifies that the kernel correctly restores the xstate context from a signal frame where xstate_size has been manually shrunk to only cover active features, as long as the FP_XSTATE_MAGIC2 marker is correctly placed. This simulates migrating a process created on a host with fewer xstate features to a host with more features. - test_invalid_shrunk_xstate_size: Verifies that the kernel rejects (via SIGSEGV) a signal frame where xstate_size is smaller than required by the enabled features in the xfeatures mask. Signed-off-by: Andrei Vagin Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Chang S. Bae Link: https://patch.msgid.link/20260925162454.1403405-8-avagin@google.com --- tools/testing/selftests/x86/Makefile | 5 +- .../selftests/x86/sigframe_fpu_portability.c | 235 ++++++++++++++++++ tools/testing/selftests/x86/xstate.c | 12 - tools/testing/selftests/x86/xstate.h | 20 ++ 4 files changed, 259 insertions(+), 13 deletions(-) create mode 100644 tools/testing/selftests/x86/sigframe_fpu_portability.c diff --git a/tools/testing/selftests/x86/Makefile b/tools/testing/selftests/x86/Makefile index 434065215d127a..72071deda97882 100644 --- a/tools/testing/selftests/x86/Makefile +++ b/tools/testing/selftests/x86/Makefile @@ -19,7 +19,8 @@ TARGETS_C_32BIT_ONLY := entry_from_vm86 test_syscall_vdso unwind_vdso \ test_FCMOV test_FCOMI test_FISTTP \ vdso_restorer TARGETS_C_64BIT_ONLY := fsgsbase sysret_rip syscall_numbering \ - corrupt_xstate_header amx lam test_shadow_stack avx apx + corrupt_xstate_header amx lam test_shadow_stack avx apx \ + sigframe_fpu_portability # Some selftests require 32bit support enabled also on 64bit systems TARGETS_C_32BIT_NEEDED := ldt_gdt ptrace_syscall @@ -138,3 +139,5 @@ $(OUTPUT)/avx_64: CFLAGS += -mno-avx -mno-avx512f $(OUTPUT)/amx_64: EXTRA_FILES += xstate.c $(OUTPUT)/avx_64: EXTRA_FILES += xstate.c $(OUTPUT)/apx_64: EXTRA_FILES += xstate.c + +$(OUTPUT)/sigframe_fpu_portability_64: CFLAGS += -mno-avx -mno-avx512f diff --git a/tools/testing/selftests/x86/sigframe_fpu_portability.c b/tools/testing/selftests/x86/sigframe_fpu_portability.c new file mode 100644 index 00000000000000..8377de05203266 --- /dev/null +++ b/tools/testing/selftests/x86/sigframe_fpu_portability.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-only +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "helpers.h" +#include "xstate.h" + +#ifndef FP_XSTATE_MAGIC2_SIZE +#define FP_XSTATE_MAGIC2_SIZE sizeof(FP_XSTATE_MAGIC2) +#endif + +/* + * This test verifies the FPU portability and consistency of the signal frame. + * + * - test_valid_shrunk_xstate_size: + * Verifies that the kernel restores state from a frame with xstate_size + * shrunk to only include active features. + * + * - test_invalid_shrunk_xstate_size: + * Verifies that the kernel rejects a frame if xstate_size is too small for + * the features enabled in xfeatures. + */ + +#define SIGFRAME_XSTATE_HDR_OFFSET 512 +#define XSTATE_SSE_ONLY_SIZE (SIGFRAME_XSTATE_HDR_OFFSET + XSAVE_HDR_SIZE) +#define XFEATURE_MASK_FPSSE ((1 << XFEATURE_FP) | (1 << XFEATURE_SSE)) + +static uint32_t ymm_offset; +static uint32_t xstate_size_ymm; +static pid_t self_pid; + +/* + * Load %ymm0 from @v, invoke SYS_kill to deliver @sig, and store the + * restored %ymm0 state back into @v within a single inline assembly + * block so the compiler cannot clobber %xmm0/%ymm0 between steps. + */ +__attribute__((target("avx"))) +static void raise_with_ymm0(int sig, uint64_t *v) +{ + register long rax asm("rax") = SYS_kill; + register long rdi asm("rdi") = self_pid; + register long rsi asm("rsi") = sig; + + asm volatile ("vmovdqu %0, %%ymm0\n\t" + "syscall\n\t" + "vmovdqu %%ymm0, %0" + : "+m" (*(char (*)[32])v), "+r" (rax) + : "r" (rdi), "r" (rsi) + : "rcx", "r11", "ymm0", "memory"); +} + +/* + * Avoid using printf() in signal handlers as it is not + * async-signal-safe. + */ +#define SIGNAL_BUF_LEN 1024 +static char sig_err_buf[SIGNAL_BUF_LEN]; + +static void sig_print(const char *msg) +{ + int left = SIGNAL_BUF_LEN - strlen(sig_err_buf) - 1; + + strncat(sig_err_buf, msg, left); +} + +static void check_avx_support(void) +{ + uint32_t eax, ebx, ecx, edx; + struct xstate_info xstate; + + /* Check CPUID.01H:ECX.OSXSAVE[bit 27] before calling xgetbv to avoid #UD */ + __cpuid(1, eax, ebx, ecx, edx); + if (!(ecx & (1 << 27))) + ksft_exit_skip("OSXSAVE not enabled by OS\n"); + + /* Check XCR0[2] (YMM) is enabled by OS */ + if (!(xgetbv(0) & (1 << XFEATURE_YMM))) + ksft_exit_skip("AVX (YMM) not enabled in XCR0\n"); + + xstate = get_xstate_info(XFEATURE_YMM); + if (!xstate.size) + ksft_exit_skip("AVX not supported by hardware\n"); + + ymm_offset = xstate.xbuf_offset; + xstate_size_ymm = xstate.xbuf_offset + xstate.size; +} + +#define TEST_YMMH_VAL (0x5656565656565656UL) + +static void __handle_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp, bool valid_size) +{ + uint64_t xfeatures, *ymmh_p; + struct xsave_buffer *xbuf; + struct _fpx_sw_bytes *sw; + ucontext_t *uc = ucp; + void *fp; + + fp = uc->uc_mcontext.fpregs; + if (!fp) { + sig_print("fpregs is NULL\n"); + return; + } + + sw = get_fpx_sw_bytes(fp); + if (sw->magic1 != FP_XSTATE_MAGIC1) { + sig_print("magic1 is not valid\n"); + return; + } + + xbuf = (struct xsave_buffer *)fp; + + /* + * Both test cases shrink the frame to contain only AVX (FP + SSE + YMM). + * If valid_size is true, set xstate_size to match the enabled features. + * If valid_size is false, set xstate_size too small (SSE only), which + * the kernel must reject. + */ + if (valid_size) + sw->xstate_size = xstate_size_ymm; + else + sw->xstate_size = XSTATE_SSE_ONLY_SIZE; + + xfeatures = get_xstatebv(xbuf); + xfeatures &= XFEATURE_MASK_FPSSE | (1 << XFEATURE_YMM); + set_xstatebv(xbuf, xfeatures); + set_fpx_sw_bytes_features(fp, xfeatures); + + *(uint32_t *)(fp + sw->xstate_size) = FP_XSTATE_MAGIC2; + + if (valid_size) { + ymmh_p = (uint64_t *)(fp + ymm_offset); + ymmh_p[0] = TEST_YMMH_VAL; + ymmh_p[1] = TEST_YMMH_VAL + 1; + } + + /* clear everything after MAGIC2. */ + if (sw->xstate_size + FP_XSTATE_MAGIC2_SIZE < sw->extended_size) + memset(fp + sw->xstate_size + FP_XSTATE_MAGIC2_SIZE, 0, + sw->extended_size - sw->xstate_size - FP_XSTATE_MAGIC2_SIZE); +} + +static void handle_valid_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp) +{ + __handle_shrunk_xstate_size(sig, si, ucp, true); +} + +static void handle_invalid_shrunk_xstate_size(int sig, siginfo_t *si, void *ucp) +{ + __handle_shrunk_xstate_size(sig, si, ucp, false); +} + +static void test_valid_shrunk_xstate_size(void) +{ + uint64_t v[4]; + + sig_err_buf[0] = 0; + sethandler(SIGUSR1, handle_valid_shrunk_xstate_size, 0); + + v[0] = 0x1111111111111111ULL; + v[1] = 0x2222222222222222ULL; + v[2] = 0x3333333333333333ULL; + v[3] = 0x4444444444444444ULL; + raise_with_ymm0(SIGUSR1, v); + + if (sig_err_buf[0]) + ksft_test_result_fail("%s\n", sig_err_buf); + else if (v[2] == TEST_YMMH_VAL && v[3] == (TEST_YMMH_VAL + 1)) + ksft_test_result_pass("YMM state restored correctly from shrunk frame\n"); + else + ksft_test_result_fail( + "Got upper bits: 0x%lx 0x%lx (expected %lx %lx)\n", + v[2], v[3], TEST_YMMH_VAL, TEST_YMMH_VAL + 1); + + clearhandler(SIGUSR1); +} + +static sigjmp_buf segv_jmpbuf; + +static void handle_segv(int sig, siginfo_t *si, void *ucp) +{ + siglongjmp(segv_jmpbuf, 1); +} + +static void test_invalid_shrunk_xstate_size(void) +{ + uint64_t v[4]; + + sig_err_buf[0] = 0; + sethandler(SIGUSR1, handle_invalid_shrunk_xstate_size, 0); + sethandler(SIGSEGV, handle_segv, 0); + + if (sigsetjmp(segv_jmpbuf, 1) == 0) { + v[0] = 0x1111111111111111ULL; + v[1] = 0x2222222222222222ULL; + v[2] = 0x3333333333333333ULL; + v[3] = 0x4444444444444444ULL; + raise_with_ymm0(SIGUSR1, v); + sig_print("Inconsistent size was NOT rejected\n"); + } + + clearhandler(SIGUSR1); + clearhandler(SIGSEGV); + + if (sig_err_buf[0]) + ksft_test_result_fail("%s\n", sig_err_buf); + else + ksft_test_result_pass("Inconsistent size correctly rejected\n"); +} + +int main(void) +{ + ksft_print_header(); + ksft_set_plan(2); + + self_pid = getpid(); + + check_avx_support(); + + test_valid_shrunk_xstate_size(); + test_invalid_shrunk_xstate_size(); + + ksft_finished(); + return 0; +} diff --git a/tools/testing/selftests/x86/xstate.c b/tools/testing/selftests/x86/xstate.c index 97fe4bd8bc77de..0ab577157cd7d0 100644 --- a/tools/testing/selftests/x86/xstate.c +++ b/tools/testing/selftests/x86/xstate.c @@ -34,18 +34,6 @@ (1 << XFEATURE_XTILEDATA) | \ (1 << XFEATURE_APX)) -static inline uint64_t xgetbv(uint32_t index) -{ - uint32_t eax, edx; - - asm volatile("xgetbv" : "=a" (eax), "=d" (edx) : "c" (index)); - return eax + ((uint64_t)edx << 32); -} - -static inline uint64_t get_xstatebv(struct xsave_buffer *xbuf) -{ - return *(uint64_t *)(&xbuf->header); -} static struct xstate_info xstate; diff --git a/tools/testing/selftests/x86/xstate.h b/tools/testing/selftests/x86/xstate.h index 6ee816e7625ac7..eedf0cab7ccb7a 100644 --- a/tools/testing/selftests/x86/xstate.h +++ b/tools/testing/selftests/x86/xstate.h @@ -3,6 +3,8 @@ #define __SELFTESTS_X86_XSTATE_H #include +#include +#include #include "kselftest.h" @@ -94,6 +96,14 @@ static inline void xrstor(struct xsave_buffer *xbuf, uint64_t rfbm) : : "D" (xbuf), "a" (rfbm_lo), "d" (rfbm_hi)); } +static inline uint64_t xgetbv(uint32_t index) +{ + uint32_t eax, edx; + + asm volatile("xgetbv" : "=a" (eax), "=d" (edx) : "c" (index)); + return eax + ((uint64_t)edx << 32); +} + #define CPUID_LEAF_XSTATE 0xd #define CPUID_SUBLEAF_XSTATE_USER 0x0 @@ -160,6 +170,11 @@ static inline void set_xstatebv(struct xsave_buffer *xbuf, uint64_t bv) *(uint64_t *)(&xbuf->header) = bv; } +static inline uint64_t get_xstatebv(struct xsave_buffer *xbuf) +{ + return *(uint64_t *)(&xbuf->header); +} + /* See 'struct _fpx_sw_bytes' at sigcontext.h */ #define SW_BYTES_OFFSET 464 /* N.B. The struct's field name varies so read from the offset. */ @@ -175,6 +190,11 @@ static inline uint64_t get_fpx_sw_bytes_features(void *buffer) return *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET); } +static inline void set_fpx_sw_bytes_features(void *buffer, uint64_t features) +{ + *(uint64_t *)(buffer + SW_BYTES_BV_OFFSET) = features; +} + static inline void set_rand_data(struct xstate_info *xstate, struct xsave_buffer *xbuf) { int *ptr = (int *)&xbuf->bytes[xstate->xbuf_offset]; From d1706f9da9f40dffe8621cc0ee03b847f41f1d6d Mon Sep 17 00:00:00 2001 From: Joshua Crofts Date: Sat, 19 Sep 2026 10:24:28 +0200 Subject: [PATCH 0925/2286] PM: runtime: call pm_runtime_dont_use_autosuspend() on reinit() Add a pm_runtime_dont_use_autosuspend() call to pm_runtime_reinit() to ensure autosuspend is disabled on driver teardown. Suggested-by: Frank Li Signed-off-by: Joshua Crofts Link: https://patch.msgid.link/20260919-move-dont-use-autosuspend-v1-1-f6e2d1315c23@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index bb008dfe85a181..655c0b8af09545 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1890,6 +1890,9 @@ void pm_runtime_reinit(struct device *dev) if (dev->power.runtime_status == RPM_ACTIVE) pm_runtime_set_suspended(dev); + if (dev->power.use_autosuspend) + pm_runtime_dont_use_autosuspend(dev); + if (dev->power.irq_safe) { spin_lock_irq(&dev->power.lock); dev->power.irq_safe = 0; From f10f236b08076994df0a233729a6df01076eec1c Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 21 Sep 2026 16:36:41 +0200 Subject: [PATCH 0926/2286] thermal: intel: int340x: soc_slider: Simplify slider_def_*_set() Return errors directly to simplify slider_def_balance_set() and slider_def_offset_set(). Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260921143644.69381-2-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- .../processor_thermal_soc_slider.c | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c index 91f291627132aa..e161b95828e878 100644 --- a/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c +++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_soc_slider.c @@ -66,15 +66,16 @@ static int slider_def_balance_set(const char *arg, const struct kernel_param *kp guard(mutex)(&slider_param_lock); ret = kstrtou8(arg, 16, &slider_val); - if (!ret) { - if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] || - slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE]) - return -EINVAL; + if (ret) + return ret; - slider_balanced_param = slider_val; - } + if (slider_val <= slider_values[SOC_POWER_SLIDER_PERFORMANCE] || + slider_val >= slider_values[SOC_POWER_SLIDER_POWERSAVE]) + return -EINVAL; - return ret; + slider_balanced_param = slider_val; + + return 0; } static int slider_def_balance_get(char *buf, const struct kernel_param *kp) @@ -101,14 +102,15 @@ static int slider_def_offset_set(const char *arg, const struct kernel_param *kp) guard(mutex)(&slider_param_lock); ret = kstrtou8(arg, 16, &offset); - if (!ret) { - if (offset > SOC_SLIDER_VALUE_MAXIMUM) - return -EINVAL; + if (ret) + return ret; - slider_offset = offset; - } + if (offset > SOC_SLIDER_VALUE_MAXIMUM) + return -EINVAL; - return ret; + slider_offset = offset; + + return 0; } static int slider_def_offset_get(char *buf, const struct kernel_param *kp) From f58dacfbadb7be7c7dbb2b95e9341ad56710849e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 22 Sep 2026 08:10:52 +0000 Subject: [PATCH 0927/2286] PM: EM: Fix use-after-free of perf domain in netlink doit handlers The doit handlers dropped em_pd_list_mutex before dereferencing the perf domain, so a concurrent device unregister could free it while the reply was being built. Encode the reply from em_perf_domain_for_id() callbacks, which run under the mutex, and allocate the skb outside of it, like the dumpit path already does. This issue was found by an in-house static analysis tool. Fixes: 380ff27af25e ("PM: EM: Add dump to get-perf-domains in the EM YNL spec") Fixes: f2d2946eaa5c ("PM: EM: Implement em_nl_get_pd_table_doit()") Cc: All applicable Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Link: https://patch.msgid.link/20260922081052.321581-1-fanwu01@zju.edu.cn Signed-off-by: Rafael J. Wysocki --- kernel/power/em_netlink.c | 122 +++++++++++++++++++++++------------- kernel/power/em_netlink.h | 8 ++- kernel/power/energy_model.c | 8 ++- 3 files changed, 87 insertions(+), 51 deletions(-) diff --git a/kernel/power/em_netlink.c b/kernel/power/em_netlink.c index 4d4fd29bd2be9c..1f68d456a713c0 100644 --- a/kernel/power/em_netlink.c +++ b/kernel/power/em_netlink.c @@ -95,41 +95,62 @@ static int __em_nl_get_pd_for_dump(struct em_perf_domain *pd, void *data) return ret; } +struct em_nl_doit_ctx { + struct genl_info *info; + int cmd; + struct sk_buff *msg; +}; + +static int __em_nl_get_pd_doit_fill(struct em_perf_domain *pd, void *data) +{ + struct em_nl_doit_ctx *ctx = data; + void *hdr; + + hdr = genlmsg_put_reply(ctx->msg, ctx->info, &dev_energymodel_nl_family, 0, + ctx->cmd); + if (!hdr) + return -EMSGSIZE; + + if (__em_nl_get_pd(pd, ctx->msg)) { + genlmsg_cancel(ctx->msg, hdr); + return -EMSGSIZE; + } + + genlmsg_end(ctx->msg, hdr); + return 0; +} + int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, - struct genl_info *info) + struct genl_info *info) { - int id, ret = -EMSGSIZE, msg_sz = 0; - int cmd = info->genlhdr->cmd; - struct em_perf_domain *pd; + struct em_nl_doit_ctx ctx = { + .info = info, + .cmd = info->genlhdr->cmd, + }; struct sk_buff *msg; - void *hdr; + int id, ret, msg_sz = 0; if (!info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]) return -EINVAL; id = nla_get_u32(info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]); - pd = em_perf_domain_get_by_id(id); - if (!pd) - return -EINVAL; - __em_nl_get_pd_size(pd, &msg_sz); + /* Encode under em_pd_list_mutex, like the dumpit path. */ + ret = em_perf_domain_for_id(id, __em_nl_get_pd_size, &msg_sz); + if (ret) + return ret; + msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return -ENOMEM; - hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); - if (!hdr) - goto out_free_msg; - - ret = __em_nl_get_pd(pd, msg); + ctx.msg = msg; + ret = em_perf_domain_for_id(id, __em_nl_get_pd_doit_fill, &ctx); if (ret) - goto out_cancel_msg; - genlmsg_end(msg, hdr); + goto out_free_msg; return genlmsg_reply(msg, info); -out_cancel_msg: - genlmsg_cancel(msg, hdr); out_free_msg: nlmsg_free(msg); return ret; @@ -148,19 +169,6 @@ int dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff *skb, return for_each_em_perf_domain(__em_nl_get_pd_for_dump, &ctx); } -static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs) -{ - struct em_perf_domain *pd; - int id; - - if (!attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]) - return NULL; - - id = nla_get_u32(attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]); - pd = em_perf_domain_get_by_id(id); - return pd; -} - static int __em_nl_get_pd_table_size(const struct em_perf_domain *pd) { int id_sz, ps_sz; @@ -245,34 +253,58 @@ int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd) return -EMSGSIZE; } +static int __em_nl_get_pd_table_size_cb(struct em_perf_domain *pd, void *data) +{ + *(int *)data = __em_nl_get_pd_table_size(pd); + return 0; +} + +static int __em_nl_get_pd_table_doit_fill(struct em_perf_domain *pd, + void *data) +{ + struct em_nl_doit_ctx *ctx = data; + void *hdr; + + hdr = genlmsg_put_reply(ctx->msg, ctx->info, &dev_energymodel_nl_family, 0, + ctx->cmd); + if (!hdr) + return -EMSGSIZE; + + if (__em_nl_get_pd_table(ctx->msg, pd)) + return -EMSGSIZE; + + genlmsg_end(ctx->msg, hdr); + return 0; +} + int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb, - struct genl_info *info) + struct genl_info *info) { - int cmd = info->genlhdr->cmd; - int msg_sz, ret = -EMSGSIZE; - struct em_perf_domain *pd; + struct em_nl_doit_ctx ctx = { + .info = info, + .cmd = info->genlhdr->cmd, + }; struct sk_buff *msg; - void *hdr; + int id, ret, msg_sz; - pd = __em_nl_get_pd_table_id(info->attrs); - if (!pd) + if (!info->attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]) return -EINVAL; - msg_sz = __em_nl_get_pd_table_size(pd); + id = nla_get_u32(info->attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]); + + ret = em_perf_domain_for_id(id, __em_nl_get_pd_table_size_cb, &msg_sz); + if (ret) + return ret; msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return -ENOMEM; - hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); - if (!hdr) - goto out_free_msg; - - ret = __em_nl_get_pd_table(msg, pd); + ctx.msg = msg; + ret = em_perf_domain_for_id(id, __em_nl_get_pd_table_doit_fill, &ctx); if (ret) goto out_free_msg; - genlmsg_end(msg, hdr); return genlmsg_reply(msg, info); out_free_msg: diff --git a/kernel/power/em_netlink.h b/kernel/power/em_netlink.h index 583d7f1c39390d..bc98a3c278b9fe 100644 --- a/kernel/power/em_netlink.h +++ b/kernel/power/em_netlink.h @@ -12,7 +12,8 @@ #if defined(CONFIG_ENERGY_MODEL) && defined(CONFIG_NET) int for_each_em_perf_domain(int (*cb)(struct em_perf_domain*, void *), void *data); -struct em_perf_domain *em_perf_domain_get_by_id(int id); +int em_perf_domain_for_id(int id, int (*cb)(struct em_perf_domain *, void *), + void *data); void em_notify_pd_created(const struct em_perf_domain *pd); void em_notify_pd_deleted(const struct em_perf_domain *pd); void em_notify_pd_updated(const struct em_perf_domain *pd); @@ -24,9 +25,10 @@ int for_each_em_perf_domain(int (*cb)(struct em_perf_domain*, void *), return -EINVAL; } static inline -struct em_perf_domain *em_perf_domain_get_by_id(int id) +int em_perf_domain_for_id(int id, int (*cb)(struct em_perf_domain *, void *), + void *data) { - return NULL; + return -EINVAL; } static inline void em_notify_pd_created(const struct em_perf_domain *pd) {} diff --git a/kernel/power/energy_model.c b/kernel/power/energy_model.c index e610cf8e9a06c7..a76089e2ce8c44 100644 --- a/kernel/power/energy_model.c +++ b/kernel/power/energy_model.c @@ -1031,7 +1031,9 @@ int for_each_em_perf_domain(int (*cb)(struct em_perf_domain*, void *), return 0; } -struct em_perf_domain *em_perf_domain_get_by_id(int id) +/* Run @cb on the matching domain with em_pd_list_mutex held. */ +int em_perf_domain_for_id(int id, int (*cb)(struct em_perf_domain *, void *), + void *data) { struct em_perf_domain *pd; @@ -1040,9 +1042,9 @@ struct em_perf_domain *em_perf_domain_get_by_id(int id) list_for_each_entry(pd, &em_pd_list, node) { if (pd->id == id) - return pd; + return cb(pd, data); } - return NULL; + return -EINVAL; } #endif From f5f32c6c48d8951d068815402464f7b6ef1f3d0a Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Wed, 23 Sep 2026 22:29:13 +0200 Subject: [PATCH 0928/2286] thermal/cpufreq_cooling: Simplify cpufreq_set_cur_state() Return freq_qos_update_request() errors directly to simplify the code. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260923202911.90464-3-blum@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/thermal/cpufreq_cooling.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/cpufreq_cooling.c b/drivers/thermal/cpufreq_cooling.c index 768859a7aed059..e89036077befa7 100644 --- a/drivers/thermal/cpufreq_cooling.c +++ b/drivers/thermal/cpufreq_cooling.c @@ -488,12 +488,12 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev, frequency = get_state_freq(cpufreq_cdev, state); ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency); - if (ret >= 0) { - cpufreq_cdev->cpufreq_state = state; - ret = 0; - } + if (ret < 0) + return ret; + + cpufreq_cdev->cpufreq_state = state; - return ret; + return 0; } /** From f201d785cae6febe2be33235f541ef25b2a13240 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 22 Sep 2026 22:13:19 +0100 Subject: [PATCH 0929/2286] rust: pin-init: internal: pin_data: use HRTB to work around `trivial_bounds` for `Unpin` `#[pin_data]` generates a `Unpin` implementation for the user, which requires reasoning using field types. This is rejected by rustc as "trivial bounds", which can only be specified using the `#![feature(trivial_bounds)]`. Currently we use an extra lifetime in `__Unpin` to work around this; use HRTB to introduce a dummy lifetime can achieve the same, which the `Zeroable` implementation already uses. Unify the `Unpin` impl to also use the same HRTB trick that `Zeroable` uses. This produces slightly better error message and slightly shorter expansion. Link: https://patch.msgid.link/20260922211320.1390159-1-gary@kernel.org Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 35 +++++++++----------------- rust/pin-init/src/__internal.rs | 16 ------------ 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 8cd9bf139567ce..55c627499b35d2 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -5,10 +5,10 @@ use quote::{format_ident, quote, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, + punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, - WhereClause, }; use crate::{ @@ -231,21 +231,11 @@ fn generate_unpin_impl( generics: &Generics, fields: &[FieldInfo<'_>], ) -> TokenStream { - let (_, ty_generics, _) = generics.split_for_impl(); - let mut generics_with_pin_lt = generics.clone(); - generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); - generics_with_pin_lt.make_where_clause(); - let ( - impl_generics_with_pin_lt, - ty_generics_with_pin_lt, - Some(WhereClause { - where_token, - predicates, - }), - ) = generics_with_pin_lt.split_for_impl() - else { - unreachable!() - }; + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let predicates = whr + .map(|x| &x.predicates) + .unwrap_or(const { &Punctuated::new() }); + let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { let ident = f.member.as_ident(); let ty = &f.field.ty; @@ -260,19 +250,18 @@ fn generate_unpin_impl( dead_code, // The fields below are never used. non_snake_case // The warning will be emitted on the struct definition. )] - struct __Unpin #generics_with_pin_lt - #where_token - #predicates + struct __Unpin #generics #whr { - __phantom_pin: ::pin_init::__internal::PhantomInvariantLifetime<'__pin>, __phantom: ::pin_init::__internal::PhantomInvariant<#ident #ty_generics>, #(#pinned_fields),* } #[doc(hidden)] - impl #impl_generics_with_pin_lt ::core::marker::Unpin for #ident #ty_generics - #where_token - __Unpin #ty_generics_with_pin_lt: ::core::marker::Unpin, + impl #impl_generics ::core::marker::Unpin for #ident #ty_generics + where + // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` + // feature . + for<'__dummy> __Unpin #ty_generics: ::core::marker::Unpin, #predicates {} } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 8e9fd18b993f99..67a354fa29c47a 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -42,22 +42,6 @@ impl PhantomInvariant { } } -/// Zero-sized type used to mark a lifetime as invariant. -/// -/// This is a polyfill for the [unstable type] in the standard library of the same name. -/// -/// [unstable type]: https://doc.rust-lang.org/nightly/std/marker/struct.PhantomInvariantLifetime.html -#[repr(transparent)] -#[derive(Clone, Copy, Default)] -pub struct PhantomInvariantLifetime<'a>(PhantomInvariant<&'a ()>); - -impl PhantomInvariantLifetime<'_> { - #[inline(always)] - pub const fn new() -> Self { - Self(PhantomInvariant::new()) - } -} - /// Token type to signify successful initialization. /// /// Can only be constructed via the unsafe [`Self::new`] function. The initializer macros use this From ad5545276a22ccd6061829b9e697edff6c910531 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 19:20:14 +0100 Subject: [PATCH 0930/2286] rust: pin-init: internal: make `__init_data` and `__pin_data` safe Remove the unsafe markers of `__init_data` and `__pin_data`. These methods are for inference help and does not need to be unsafe. The `HasInitData` cannot be implemented outside pin-init, so the `unsafe trait` marker is not necessary. Link: https://patch.msgid.link/20260923-dev-pin-data-diag-v1-1-e4d7193569e5@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 3 +-- rust/pin-init/internal/src/pin_data.rs | 2 +- rust/pin-init/src/__internal.rs | 11 +++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 1d2db93dd33d63..1957be83a19692 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -312,8 +312,7 @@ fn expand( let field_check = make_field_check(&fields, init_kind, &path); Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. - // SAFETY: TODO - let data = unsafe { + let data = { use ::pin_init::__internal::#has_data_trait; // Can't use `<#path as #has_data_trait>::#get_data`, since the user is able to omit // generics (which need to be present with that syntax). diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 55c627499b35d2..9fdce1593c7f61 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -527,7 +527,7 @@ fn generate_the_pin_data( type PinData = __ThePinData #ty_generics; #[inline] - unsafe fn __pin_data() -> Self::PinData { + fn __pin_data() -> Self::PinData { __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant::new() } } } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 67a354fa29c47a..532ddcdc1c6a9b 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -65,12 +65,12 @@ impl InitOk { /// /// # Safety /// -/// Only the `init` module is allowed to use this trait. +/// `pin-init` relies on the correctness of the helper functions defined on `PinData`. +/// Thus, only the `#[pin_data]` can implement this trait. pub unsafe trait HasPinData { type PinData; - #[expect(clippy::missing_safety_doc)] - unsafe fn __pin_data() -> Self::PinData; + fn __pin_data() -> Self::PinData; } /// This trait is automatically implemented for every type. It aims to provide the same type @@ -82,8 +82,7 @@ pub unsafe trait HasPinData { pub unsafe trait HasInitData { type InitData; - #[expect(clippy::missing_safety_doc)] - unsafe fn __init_data() -> Self::InitData; + fn __init_data() -> Self::InitData; } pub struct AllData(PhantomInvariant); @@ -113,7 +112,7 @@ unsafe impl HasInitData for T { type InitData = AllData; #[inline] - unsafe fn __init_data() -> Self::InitData { + fn __init_data() -> Self::InitData { AllData(PhantomInvariant::new()) } } From c0243b93d0be35a4e8687b658991e3e6b2f1e9e7 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 19:20:15 +0100 Subject: [PATCH 0931/2286] rust: pin-init: internal: remove associated type of `HasInitData` There is only a single implementation, the associated type is not necessary. Remove it, and rename `AllData` to `InitData` to be a more meaningful name. The single implementation also means that "unsafe" is redundant, so remove it as well. Link: https://patch.msgid.link/20260923-dev-pin-data-diag-v1-2-e4d7193569e5@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/src/__internal.rs | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 532ddcdc1c6a9b..e5f75432fe8789 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -73,30 +73,31 @@ pub unsafe trait HasPinData { fn __pin_data() -> Self::PinData; } -/// This trait is automatically implemented for every type. It aims to provide the same type -/// inference help as `HasPinData`. +/// This trait is automatically implemented for every type. /// -/// # Safety -/// -/// Only the `init` module is allowed to use this trait. -pub unsafe trait HasInitData { - type InitData; - - fn __init_data() -> Self::InitData; +/// It aims to provide type inference help; `PATH::__init_data()` would be able to retrieve an +/// instance of `InitData>` without having to mention the generics explicitly. +pub trait HasInitData { + #[inline] + fn __init_data() -> InitData { + InitData(PhantomInvariant::new()) + } } -pub struct AllData(PhantomInvariant); +impl HasInitData for T {} + +pub struct InitData(PhantomInvariant); -impl Clone for AllData { +impl Clone for InitData { #[inline] fn clone(&self) -> Self { *self } } -impl Copy for AllData {} +impl Copy for InitData {} -impl AllData { +impl InitData { /// Type inference helper function. #[inline(always)] pub fn __make_closure(self, f: F) -> F @@ -107,16 +108,6 @@ impl AllData { } } -// SAFETY: TODO. -unsafe impl HasInitData for T { - type InitData = AllData; - - #[inline] - fn __init_data() -> Self::InitData { - AllData(PhantomInvariant::new()) - } -} - /// Stack initializer helper type. Use [`stack_pin_init`] instead of this primitive. /// /// # Invariants From c5155147cdf0053694f8bcdfcc616d254ae6842e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 19:20:16 +0100 Subject: [PATCH 0932/2286] rust: pin-init: internal: use `HasInitData` to provide inference help only Currently, type inference of the initialized type is done by use the function calling syntax `PATH::__pin_data` or `PATH::__init_data` to obtain the required init data type, without needing the user to explicitly mention all generics. Switch to always use `HasInitData::__init_data` to provide that inference, which will not produce method-not-found errors as it is always available. Change `__pin_data` to take `InitData` as the receiver, so for types that require `HasPinData`, `HasPinData::__pin_data(data)` can be used to obtain the needed type without having to mention the user-provided path again. The expanded code also uses the span of the type, so diagnostics will hint to user that the type is causing the issue. With this change, the error message when `HasPinData` is not implemented changes from error[E0599]: no associated function or constant named `__pin_data` found for struct `Foo` in the current scope --> tests/ui/compile-fail/init/missing_pin_data.rs:9:9 | 3 | struct Foo { | ---------- associated function or constant `__pin_data` not found for this struct ... 9 | pin_init!(Self { a: 42 }) | ^^^^^^^^^^^^^^^^^^^^^^^^^ associated function or constant not found in `Foo` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `__pin_data`, perhaps you need to implement it: candidate #1: `pin_init::__internal::HasPinData` to error[E0277]: the trait bound `Foo: pin_init::__internal::HasPinData` is not satisfied --> tests/ui/compile-fail/init/missing_pin_data.rs:9:19 | 9 | pin_init!(Self { a: 42 }) | ^^^^ unsatisfied trait bound | help: the trait `pin_init::__internal::HasPinData` is not implemented for `Foo` --> tests/ui/compile-fail/init/missing_pin_data.rs:3:1 | 3 | struct Foo { | ^^^^^^^^^^ Link: https://patch.msgid.link/20260923-dev-pin-data-diag-v1-3-e4d7193569e5@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 22 ++++++++++++---------- rust/pin-init/internal/src/pin_data.rs | 2 +- rust/pin-init/src/__internal.rs | 2 +- rust/pin-init/src/lib.rs | 3 ++- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 1957be83a19692..e98182c6544779 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -269,18 +269,17 @@ fn expand( }, |(_, err)| Box::new(err), ); - let (has_data_trait, get_data, init_from_closure) = if pinned { + let (get_pin_data, init_from_closure) = if pinned { ( - format_ident!("HasPinData"), - format_ident!("__pin_data"), + Some( + quote_spanned! { path.span().resolved_at(Span::mixed_site()) => + let data = ::pin_init::__internal::HasPinData::__pin_data(data); + }, + ), format_ident!("pin_init_from_closure"), ) } else { - ( - format_ident!("HasInitData"), - format_ident!("__init_data"), - format_ident!("init_from_closure"), - ) + (None, format_ident!("init_from_closure")) }; let init_kind = get_init_kind(rest, dcx); let zeroable_check = match init_kind { @@ -313,11 +312,14 @@ fn expand( Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. let data = { - use ::pin_init::__internal::#has_data_trait; + use ::pin_init::__internal::HasInitData; // Can't use `<#path as #has_data_trait>::#get_data`, since the user is able to omit // generics (which need to be present with that syntax). - #path::#get_data() + #path::__init_data() }; + + #get_pin_data + // Ensure that `data` really is of type `data` and help with type inference: let init = data.__make_closure::<_, #error>( move |slot| { diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 9fdce1593c7f61..982a38ceea9335 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -527,7 +527,7 @@ fn generate_the_pin_data( type PinData = __ThePinData #ty_generics; #[inline] - fn __pin_data() -> Self::PinData { + fn __pin_data(_: ::pin_init::__internal::InitData) -> Self::PinData { __ThePinData { __phantom: ::pin_init::__internal::PhantomInvariant::new() } } } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index e5f75432fe8789..914c4d1a06eb5b 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -70,7 +70,7 @@ impl InitOk { pub unsafe trait HasPinData { type PinData; - fn __pin_data() -> Self::PinData; + fn __pin_data(_: InitData) -> Self::PinData; } /// This trait is automatically implemented for every type. diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 4110590348c6ac..319485dc18ce82 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -920,7 +920,8 @@ macro_rules! assert_pinned { ($ty:ty, $field:ident, $field_ty:ty, inline) => { // SAFETY: This code is unreachable. let _ = move |ptr: *mut $ty| unsafe { - let data = <$ty as $crate::__internal::HasPinData>::__pin_data(); + let data = <$ty as $crate::__internal::HasInitData>::__init_data(); + let data = $crate::__internal::HasPinData::__pin_data(data); _ = data .$field(ptr) .init($crate::__internal::AlwaysFail::<$field_ty>::new()); From 6f78441c58c1012324da3189ea650cfeeb98966c Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 19:20:17 +0100 Subject: [PATCH 0933/2286] rust: pin-init: internal: add custom diagnostic when `#[pin_data]` is not implemented Produce a more helpful error message when `#[pin_data]` is omitted. The error message changes from error[E0277]: the trait bound `Foo: pin_init::__internal::HasPinData` is not satisfied --> tests/ui/compile-fail/init/missing_pin_data.rs:9:19 | 9 | pin_init!(Self { a: 42 }) | ^^^^ unsatisfied trait bound | help: the trait `pin_init::__internal::HasPinData` is not implemented for `Foo` --> tests/ui/compile-fail/init/missing_pin_data.rs:3:1 | 3 | struct Foo { | ^^^^^^^^^^ to error[E0277]: `Foo` cannot be used with `pin_init!` macro --> tests/ui/compile-fail/init/missing_pin_data.rs:9:19 | 9 | pin_init!(Self { a: 42 }) | ^^^^ unsatisfied trait bound | help: the trait `pin_init::__internal::HasPinData` is not implemented for `Foo` --> tests/ui/compile-fail/init/missing_pin_data.rs:3:1 | 3 | struct Foo { | ^^^^^^^^^^ = note: did you forget to add `#[pin_data]` attribute to the struct? Link: https://patch.msgid.link/20260923-dev-pin-data-diag-v1-4-e4d7193569e5@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/src/__internal.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 914c4d1a06eb5b..cba53b8c3c9495 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -67,6 +67,10 @@ impl InitOk { /// /// `pin-init` relies on the correctness of the helper functions defined on `PinData`. /// Thus, only the `#[pin_data]` can implement this trait. +#[diagnostic::on_unimplemented( + message = "`{Self}` cannot be used with `pin_init!` macro", + note = "did you forget to add `#[pin_data]` attribute to the struct?" +)] pub unsafe trait HasPinData { type PinData; From 666b3e7cfa3a5c6afbfb2b5dae017617dfc40863 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 19:20:18 +0100 Subject: [PATCH 0934/2286] rust: pin-init: add diagnostic attribute for `PinInit` and `Init` Give a slightly more useful error message when these traits are not implemented. Before this change: error[E0277]: the trait bound `impl pin_init::PinInit: Init` is not satisfied --> tests/ui/compile-fail/init/invalid_init.rs:19:16 | 19 | bar <- Bar::new(), | -------^^^^^^^^^^ | | | | | the trait `Init` is not implemented for `impl pin_init::PinInit` | required by a bound introduced by this call After this change: error[E0277]: `impl pin_init::PinInit` cannot be used to movably initialize `Bar` with error `_` --> tests/ui/compile-fail/init/invalid_init.rs:19:16 | 19 | bar <- Bar::new(), | -------^^^^^^^^^^ | | | | | the trait `Init` is not implemented for `impl pin_init::PinInit` | required by a bound introduced by this call | = note: if your type implements `PinInit` but not `Init`, you might be forgetting a `#[pin]` annotation on fields Ideally, we would generate differnet message when `PinInit` is implemented but not `Init`; that is not feasible with today's `diagnostics::on_unimplemented` attribute. However, the added note is worth it because in `init!()` is more rarely used compared to `pin_init!()`; in most cases `Init` being unimplemented is the result of missing `#[pin]`. Link: https://patch.msgid.link/20260923-dev-pin-data-diag-v1-5-e4d7193569e5@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index 319485dc18ce82..d1ed0561bb27f6 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -966,6 +966,9 @@ macro_rules! assert_pinned { #[cfg_attr(not(kernel), doc = "[`Arc`]: alloc::alloc::sync::Arc")] #[cfg_attr(not(kernel), doc = "[`Box`]: alloc::alloc::boxed::Box")] #[must_use = "An initializer must be used in order to create its value."] +#[diagnostic::on_unimplemented( + message = "`{Self}` cannot be used to initialize `{T}` with error `{E}`" +)] pub unsafe trait PinInit: Sized { /// Alias of [`PinInit::__init`]. /// @@ -1100,6 +1103,11 @@ where #[cfg_attr(not(kernel), doc = "[`Arc`]: alloc::alloc::sync::Arc")] #[cfg_attr(not(kernel), doc = "[`Box`]: alloc::alloc::boxed::Box")] #[must_use = "An initializer must be used in order to create its value."] +#[diagnostic::on_unimplemented( + message = "`{Self}` cannot be used to movably initialize `{T}` with error `{E}`", + note = "if your type implements `PinInit` but not `Init`, \ + you might be forgetting a `#[pin]` annotation on fields" +)] pub unsafe trait Init: PinInit { /// First initializes the value using `self` then calls the function `f` with the initialized /// value. From c0c06ce852e5b474c3c6131669e04f6718886185 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 24 Sep 2026 21:08:53 -0700 Subject: [PATCH 0935/2286] fscrypt: Move several items from fscrypt_private.h to .c files enum fscrypt_direction_t, struct fscrypt_symlink_data, and struct fscrypt_master_key_user are each used in only one file. Move them to their respective files. Link: https://patch.msgid.link/20260925040853.110410-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/crypto.c | 5 +++++ fs/crypto/fscrypt_private.h | 27 --------------------------- fs/crypto/hooks.c | 9 +++++++++ fs/crypto/keyring.c | 13 +++++++++++++ 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c index 5286a124b0d982..8f212e7f39f639 100644 --- a/fs/crypto/crypto.c +++ b/fs/crypto/crypto.c @@ -98,6 +98,11 @@ void fscrypt_generate_iv(union fscrypt_iv *iv, u64 index, iv->index = cpu_to_le64(index); } +typedef enum { + FS_DECRYPT = 0, + FS_ENCRYPT, +} fscrypt_direction_t; + /* Encrypt or decrypt a single "data unit" of file contents. */ static int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci, fscrypt_direction_t rw, u64 index, diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 74329e0953d147..e602c31b3844e7 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -219,15 +219,6 @@ fscrypt_policy_du_bits(const union fscrypt_policy *policy, BUG(); } -/* - * For encrypted symlinks, the ciphertext length is stored at the beginning - * of the string in little-endian format. - */ -struct fscrypt_symlink_data { - __le16 len; - char encrypted_path[]; -} __packed; - /** * struct fscrypt_prepared_key - a key prepared for actual encryption/decryption * @tfm: crypto API transform object @@ -321,11 +312,6 @@ struct fscrypt_inode_info { u8 ci_nonce[FSCRYPT_FILE_NONCE_SIZE]; }; -typedef enum { - FS_DECRYPT = 0, - FS_ENCRYPT, -} fscrypt_direction_t; - /* crypto.c */ extern struct kmem_cache *fscrypt_inode_info_cachep; int fscrypt_initialize(struct super_block *sb); @@ -473,19 +459,6 @@ fscrypt_is_key_prepared(const struct fscrypt_prepared_key *prep_key, /* keyring.c */ -/* - * fscrypt_master_key_user - a user's claim to a master key - */ -struct fscrypt_master_key_user { - struct list_head link; - kuid_t uid; - /* - * This 'struct key' contains no secret. It exists solely to charge the - * appropriate user's key quota. - */ - struct key *quota_key; -}; - /* * fscrypt_master_key_secret - secret key material of an in-use master key */ diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c index a7a8a3f581a046..149498bdab94e7 100644 --- a/fs/crypto/hooks.c +++ b/fs/crypto/hooks.c @@ -9,6 +9,15 @@ #include "fscrypt_private.h" +/* + * For encrypted symlinks, the ciphertext length is stored at the beginning + * of the string in little-endian format. + */ +struct fscrypt_symlink_data { + __le16 len; + char encrypted_path[]; +} __packed; + /** * fscrypt_file_open() - prepare to open a possibly-encrypted regular file * @inode: the inode being opened diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c index 76e28d1e00640d..874cc183ee6244 100644 --- a/fs/crypto/keyring.c +++ b/fs/crypto/keyring.c @@ -145,6 +145,19 @@ static inline bool valid_key_spec(const struct fscrypt_key_specifier *spec) return master_key_spec_len(spec) != 0; } +/* + * fscrypt_master_key_user - a user's claim to a master key + */ +struct fscrypt_master_key_user { + struct list_head link; + kuid_t uid; + /* + * This 'struct key' contains no secret. It exists solely to charge the + * appropriate user's key quota. + */ + struct key *quota_key; +}; + static int fscrypt_user_key_instantiate(struct key *key, struct key_preparsed_payload *prep) { From a63883d2c3d47ce6d41918c2453a4513b921eaaa Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 24 Sep 2026 21:15:27 -0700 Subject: [PATCH 0936/2286] MAINTAINERS: List the fscrypt branches The fscrypt entry in MAINTAINERS has a T: entry but it doesn't mention which branches are used. Update it to have a T: entry for each branch. Reviewed-by: Matthias Goergens Link: https://patch.msgid.link/20260925041527.112609-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..92664dc7682590 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10657,7 +10657,8 @@ M: Jaegeuk Kim L: linux-fscrypt@vger.kernel.org S: Supported Q: https://patchwork.kernel.org/project/linux-fscrypt/list/ -T: git https://git.kernel.org/pub/scm/fs/fscrypt/linux.git +T: git https://git.kernel.org/pub/scm/fs/fscrypt/linux.git for-next +T: git https://git.kernel.org/pub/scm/fs/fscrypt/linux.git for-current F: Documentation/filesystems/fscrypt.rst F: fs/crypto/ F: include/linux/fscrypt.h From b08e4ee274917074633a41b3598d5541cee2b914 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 24 Sep 2026 21:16:54 -0700 Subject: [PATCH 0937/2286] MAINTAINERS: List the fsverity branches The fsverity entry in MAINTAINERS has a T: entry but it doesn't mention which branches are used. Update it to have a T: entry for each branch. Reviewed-by: Matthias Goergens Link: https://patch.msgid.link/20260925041654.112935-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- MAINTAINERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..62928daca572ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10704,7 +10704,8 @@ M: Theodore Y. Ts'o L: fsverity@lists.linux.dev S: Supported Q: https://patchwork.kernel.org/project/fsverity/list/ -T: git https://git.kernel.org/pub/scm/fs/fsverity/linux.git +T: git https://git.kernel.org/pub/scm/fs/fsverity/linux.git for-next +T: git https://git.kernel.org/pub/scm/fs/fsverity/linux.git for-current F: Documentation/filesystems/fsverity.rst F: fs/verity/ F: include/linux/fsverity.h From 5fe4dd59664105a9ea1a9468aa7391806b80364a Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:50 +0100 Subject: [PATCH 0938/2286] kbuild: do not allocate .modinfo in vmlinux Commit 3e86e4d74c04 ("kbuild: keep .modinfo section in vmlinux.unstripped") placed MODULE_INFO() strings in the .modinfo section in vmlinux.unstripped, however it left the section allocatable, so it gets assigned an address and on arm64, arm and riscv, the section is tagged as PT_LOAD. This is useless as the data is ultimately stripped anyway. Doing this results in a lot of unnecessary work - each pass copies the whole file, 450 MiB with relocations for an x86 allmodconfig build and 250 MiB for an arm64 allmodconfig build. This adds ~0.7s on the serial tail of every build for arm64 and ~0.2s for x86 (the tail is single-threaded work done after parallel work has finished). Nothing requires .modinfo to exist at an address, so mark the output section (INFO) so that it's not allocated and use --dump-section to extract it as -O only emits allocated sections. The dump doesn't pick up the arch's OBJCOPYFLAGS any longer as they should not impact the output of this section. Image, bzImage, System.map and modules.builtin.modinfo are unchanged and the stripped vmlinux differs only in its program headers. The objcopy passes are on the serial tail of every build that links vmlinux, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 11.2s 11.1s -0.15s (-1%) x86 defconfig, touch mm/vma.c, clang 11.3s 11.1s -0.19s (-2%) x86 defconfig, clean, gcc 32.5s 32.3s -0.16s (-1%) x86 defconfig, clean, clang 34.5s 34.2s -0.26s (-1%) x86 allmodconfig, touch mm/vma.c, gcc 40.1s 39.7s -0.44s (-1%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20260923-build-speedup-v4-1-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- scripts/Makefile.vmlinux | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 26201821ea7ff0..a6730d34e8c673 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo : { *(.modinfo) } + .modinfo (INFO) : { *(.modinfo) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux index 6833b517cacb6f..95b523fe4f9eeb 100644 --- a/scripts/Makefile.vmlinux +++ b/scripts/Makefile.vmlinux @@ -89,11 +89,8 @@ remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel.*' remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*' -# To avoid warnings: "empty loadable segment detected at ..." from GNU objcopy, -# it is necessary to remove the PT_LOAD flag from the segment. quiet_cmd_strip_relocs = OBJCOPY $@ - cmd_strip_relocs = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \ - $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@ + cmd_strip_relocs = $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $< $@ targets += vmlinux vmlinux: vmlinux.unstripped FORCE @@ -103,10 +100,7 @@ vmlinux: vmlinux.unstripped FORCE # --------------------------------------------------------------------------- quiet_cmd_modules_builtin_modinfo = GEN $@ - cmd_modules_builtin_modinfo = $(cmd_objcopy); \ - chmod -x $@ - -OBJCOPYFLAGS_modules.builtin.modinfo := -j .modinfo -O binary + cmd_modules_builtin_modinfo = $(OBJCOPY) -O binary -j .modinfo --dump-section .modinfo=$@ $< /dev/null targets += modules.builtin.modinfo modules.builtin.modinfo: vmlinux.unstripped FORCE From 6f014bf46faf1645b6336f47390167de5c88e9d1 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:51 +0100 Subject: [PATCH 0939/2286] kallsyms: index symbols by token to speed up table compression The kallsyms program compresses symbols by figuring out the most commonly used substrings in all of the input symbols then uses special character codes to represent them. For instance, 0xf7 might end up representing "write_", then every single symbol that contains "write_" can use 0xf7 as a shorthand and save 5 bytes each time. 'Special' character codes are any byte value that is not used in any symbol, either due to being an invalid character, or not being present in any symbol (e.g. if no symbol contains 'z', then 'z' can be used as special character). It does this by first figuring out which special characters are available in insert_real_symbols_in_table(), then iterating through every available special character, counting how many times each pair of adjacent characters appear in symbols in build_initial_token_table(). These adjacent pairs are known as 'tokens'. Token counts are initially obtained by build_initial_token_table(), then optimize_result() calls find_best_token() to determine the token that appeared the most number of times and assigns it the next special character. Finally, optimize_result() calls compress_symbols() to replace every token in every symbol with its special character, which updates token_profit[] as it does so. This process is repeated for each remaining available special character, with tokens now perhaps containing previously assigned special characters (e.g. if 'wr' was assigned 0x80, then the token representing 'wri' would be '\x80i'). This compresses that token by 50% in each symbol it appears in (two bytes are now represented by one) and thus by repeatedly doing this kallsyms obtains good symbol compression. However, compress_symbols() is seriously inefficient - it iterates through EVERY symbol for EVERY special character assignment, i.e. ~256 * nr_symbols. Modern x86-64 kernels, for instance, have ~158,000 symbols, so millions of iterations are performed, most of which are entirely unnecessary (tokens don't appear in most symbols). In practice kallsyms spends half its runtime doing this, two or three times per vmlinux link step. Fix this by tracking which symbols each token appears in token_syms[], and only compress symbols which actually need to be updated. Each time a token is compressed that token can no longer appear in any symbol, so that token_syms[] entry can be freed. However new token_syms[] entries must be created for each new token containing the assigned special character, but this is bounded by the number of replacements in the symbol which is very small. In testing on an x86-64 platform using clang, each kallsyms invocation dropped from 0.59s to 0.33s with CONFIG_KALLSYMS_ALL set and from 0.38s to 0.22s without it set. The data was carefully checked and verified to be byte-for-byte identical for six symbol sets (two vmlinux passes, vmlinux.o, three userspace binaries) with all option combinations. As part of this change, additionally refactor the code to be a little easier to follow. kallsyms runs two or three times on the serial tail of every build that links vmlinux, no-op builds do not link and are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 11.1s 10.6s -0.53s (-5%) x86 defconfig, touch mm/vma.c, clang 11.1s 10.5s -0.64s (-6%) x86 defconfig, clean, gcc 32.3s 31.3s -1.0s (-3%) x86 defconfig, clean, clang 34.2s 33.3s -0.89s (-3%) x86 allmodconfig, touch mm/vma.c, gcc 39.7s 38.7s -1.0s (-3%) x86 allmodconfig, touch mm/vma.c, clang 40.3s 39.1s -1.3s (-3%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-2-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kallsyms.c | 143 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index 494852ade6d87a..bb05de2aa580d5 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -58,12 +58,50 @@ static unsigned int table_size, table_cnt; static int all_symbols; static int pc_relative; -static int token_profit[0x10000]; +/* A dynamic array of symbols, encoded by symbol index. */ +struct sym_arr { + unsigned int *sym_indexes; + unsigned int cnt, cap; +}; + +/* Every pair of bytes is a token. */ +#define NR_TOKENS 0x10000 + +static int token_profit[NR_TOKENS]; +static struct sym_arr token_syms[NR_TOKENS]; /* the table that holds the result of the compression */ static unsigned char best_table[256][2]; static unsigned char best_table_len[256]; +static unsigned int sym_arr_last(const struct sym_arr *arr) +{ + return arr->cnt ? arr->sym_indexes[arr->cnt - 1] : UINT_MAX; +} + +static void sym_arr_maybe_expand(struct sym_arr *arr) +{ + if (arr->cap > arr->cnt) + return; + + arr->cap = arr->cap ? arr->cap * 2 : 16; + arr->sym_indexes = xrealloc(arr->sym_indexes, + arr->cap * sizeof(*arr->sym_indexes)); +} + +static void sym_arr_add(struct sym_arr *arr, unsigned int sym_idx) +{ + sym_arr_maybe_expand(arr); + arr->sym_indexes[arr->cnt++] = sym_idx; +} + +static void sym_arr_free(struct sym_arr *arr) +{ + free(arr->sym_indexes); + arr->sym_indexes = NULL; + arr->cnt = 0; + arr->cap = 0; +} static void usage(void) { @@ -458,6 +496,15 @@ static void write_src(void) printf("\n"); } +static unsigned int token_index(unsigned char first, unsigned char second) +{ + return first + (second << 8); +} + +static unsigned int sym_token_index(const unsigned char *symbol, int first_idx) +{ + return token_index(symbol[first_idx], symbol[first_idx + 1]); +} /* table lookup compression functions */ @@ -467,7 +514,7 @@ static void learn_symbol(const unsigned char *symbol, int len) int i; for (i = 0; i < len - 1; i++) - token_profit[ symbol[i] + (symbol[i + 1] << 8) ]++; + token_profit[sym_token_index(symbol, i)]++; } /* decrease the count for all the possible tokens in a symbol */ @@ -476,16 +523,76 @@ static void forget_symbol(const unsigned char *symbol, int len) int i; for (i = 0; i < len - 1; i++) - token_profit[ symbol[i] + (symbol[i + 1] << 8) ]--; + token_profit[sym_token_index(symbol, i)]--; +} + +static void token_add_symbol(unsigned int token_idx, unsigned int sym_idx) +{ + struct sym_arr *arr = &token_syms[token_idx]; + + /* Symbol indexes kept in sorted order, check for duplicate. */ + if (sym_arr_last(arr) == sym_idx) + return; + + sym_arr_add(arr, sym_idx); +} + +static void symbol_index_all_tokens(const unsigned char *symbol, int len, + unsigned int sym_idx) +{ + int i; + + for (i = 0; i < len - 1; i++) { + const unsigned int token_idx = sym_token_index(symbol, i); + + token_add_symbol(token_idx, sym_idx); + } +} + +/* + * The symbol just got compressed. The only parts of the symbol that changed + * meaningfully are those containing the newly assigned compressed char, so + * index those. + */ +static void symbol_index_new_tokens(const unsigned char *symbol, int len, + unsigned int sym_idx, int compressed_chr) +{ + int i; + + for (i = 0; i < len - 1; i++) { + const unsigned int token_idx = sym_token_index(symbol, i); + + if (symbol[i] == compressed_chr || + symbol[i + 1] == compressed_chr) + token_add_symbol(token_idx, sym_idx); + } } -/* do the initial token count */ static void build_initial_token_table(void) { unsigned int i; for (i = 0; i < table_cnt; i++) learn_symbol(table[i]->sym, table[i]->len); + + /* + * The initial occurrence counts tell us exactly how much memory should + * be reserved for each token's symbol array. + */ + for (i = 0; i < ARRAY_SIZE(token_syms); i++) { + const int nr_syms = token_profit[i]; + + if (!nr_syms) + continue; + + token_syms[i].cap = nr_syms; + token_syms[i].sym_indexes = + xmalloc(nr_syms * sizeof(unsigned int)); + } + + /* For every symbol, index every token -> symbol it is present in. */ + for (i = 0; i < table_cnt; i++) + symbol_index_all_tokens(table[i]->sym, table[i]->len, i); } static unsigned char *find_token(unsigned char *str, int len, @@ -502,27 +609,31 @@ static unsigned char *find_token(unsigned char *str, int len, /* replace a given token in all the valid symbols. Use the sampled symbols * to update the counts */ -static void compress_symbols(const unsigned char *str, int idx) +static void compress_symbols(const unsigned char *str, int compressed_chr) { - unsigned int i, len, size; + const unsigned int token_idx = sym_token_index(str, 0); + struct sym_arr *arr = &token_syms[token_idx]; + unsigned int sym_idx, j, len, size; unsigned char *p1, *p2; - for (i = 0; i < table_cnt; i++) { + /* Iterate through all symbols this token is found in and compress. */ + for (j = 0; j < arr->cnt; j++) { + sym_idx = arr->sym_indexes[j]; - len = table[i]->len; - p1 = table[i]->sym; + len = table[sym_idx]->len; + p1 = table[sym_idx]->sym; /* find the token on the symbol */ p2 = find_token(p1, len, str); if (!p2) continue; /* decrease the counts for this symbol's tokens */ - forget_symbol(table[i]->sym, len); + forget_symbol(table[sym_idx]->sym, len); size = len; do { - *p2 = idx; + *p2 = compressed_chr; p2++; size -= (p2 - p1); memmove(p2, p2 + 1, size); @@ -536,11 +647,15 @@ static void compress_symbols(const unsigned char *str, int idx) } while (p2); - table[i]->len = len; + table[sym_idx]->len = len; /* increase the counts for this symbol's new tokens */ - learn_symbol(table[i]->sym, len); + learn_symbol(table[sym_idx]->sym, len); + symbol_index_new_tokens(table[sym_idx]->sym, len, sym_idx, + compressed_chr); } + + sym_arr_free(arr); /* No symbol contains this token any more. */ } /* search the token with the maximum profit */ @@ -551,7 +666,7 @@ static int find_best_token(void) bestprofit=-10000; best = 0; - for (i = 0; i < 0x10000; i++) { + for (i = 0; i < ARRAY_SIZE(token_profit); i++) { if (token_profit[i] > bestprofit) { best = i; bestprofit = token_profit[i]; From 83da9f3197e50d2e2c5b8b8d8358b6be6e867d7f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:52 +0100 Subject: [PATCH 0940/2286] kallsyms: output binary data to speed output and kallsyms assembly kallsyms generates an assembly file that consists mostly of .byte entries containing compressed names, token strings and name-sorted sequence numbers. For an x86-64 build with 158k symbols that is a 37 MiB .S file which takes 0.57s to assemble each of the two to three times it is built over a kernel build. Each time it is generated it also takes kallsyms a similar amount of time to output it. Avoid this overhead by instead outputting this data as binary and importing it into the assembly using the .incbin directive. Tables that are wider than a byte remain part of the assembly to ensure endianness and relative relocations are performed correctly. With this change, the output assembly file shrinks from 37 MiB to 9.8 MiB, with a 2.6 MiB binary data file alongside it, and the object remains identical. The generated binary file is deleted correctly on build clean along with all other ephemeral data. On an x86-64 system with CONFIG_KALLSYMS_ALL set: before after delta scripts/kallsyms 0.24s 0.18s 0.06s assemble 0.57s 0.16s 0.41s Per kallsyms invocation/assembly, for a total of 0.47s time saving upon invocation. An incremental build on the same system was reduced from 11.15s to 9.65s, indicating a total of 1.5 seconds saved over the build. The kallsyms runs and their assembly are on the serial tail of every build that links vmlinux, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 10.6s 9.7s -0.89s (-8%) x86 defconfig, touch mm/vma.c, clang 10.5s 9.3s -1.2s (-11%) x86 defconfig, clean, gcc 31.3s 30.3s -1.0s (-3%) x86 defconfig, clean, clang 33.3s 31.7s -1.6s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 38.7s 37.2s -1.4s (-4%) x86 allmodconfig, touch mm/vma.c, clang 39.1s 36.4s -2.6s (-7%) Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-3-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/kallsyms.c | 84 ++++++++++++++++++++++++++++++----------- scripts/link-vmlinux.sh | 2 +- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index bb05de2aa580d5..b615db767b1d73 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -5,7 +5,10 @@ * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * - * Usage: kallsyms [--all-symbols] in.map > out.S + * Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S + * + * The byte tables go to out.bin and are pulled into out.S with .incbin; + * wider tables stay assembler source for endianness and relocations. * * Table compression uses all the unused char codes on the symbols and * maps these to the most used substrings (tokens). For instance, it might @@ -105,7 +108,7 @@ static void sym_arr_free(struct sym_arr *arr) static void usage(void) { - fprintf(stderr, "Usage: kallsyms [--all-symbols] in.map > out.S\n"); + fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S\n"); exit(1); } @@ -322,6 +325,26 @@ static void output_label(const char *label) printf("%s:\n", label); } +static long bin_pos(FILE *file) +{ + const long pos = ftell(file); + + if (pos < 0) { + perror("kallsyms: ftell"); + exit(EXIT_FAILURE); + } + + return pos; +} + +static void write_incbin(const char *filename, long start, long end) +{ + if (start >= end) + return; + + printf("\t.incbin \"%s\", %ld, %ld\n", filename, start, end - start); +} + /* uncompress a compressed symbol. When this function is called, the best table * might still be compressed itself, so the function needs to be recursive */ static int expand_symbol(const unsigned char *data, int len, char *result) @@ -374,11 +397,12 @@ static void sort_symbols_by_name(void) qsort(table, table_cnt, sizeof(table[0]), compare_names); } -static void write_src(void) +static void write_src(FILE *out_bin_file, const char *out_bin_name) { - unsigned int i, k, off; + unsigned int i, off; unsigned int best_idx[256]; unsigned int *markers, markers_cnt; + long bin_start; char buf[KSYM_NAME_LEN]; printf("\t.section .rodata, \"a\"\n"); @@ -393,6 +417,7 @@ static void write_src(void) markers = xmalloc(sizeof(*markers) * markers_cnt); output_label("kallsyms_names"); + bin_start = bin_pos(out_bin_file); off = 0; for (i = 0; i < table_cnt; i++) { if ((i & 0xFF) == 0) @@ -416,26 +441,24 @@ static void write_src(void) /* Encode length with ULEB128. */ if (table[i]->len <= 0x7F) { /* Most symbols use a single byte for the length. */ - printf("\t.byte 0x%02x", table[i]->len); + fputc(table[i]->len, out_bin_file); off += table[i]->len + 1; } else { /* "Big" symbols use two bytes. */ - printf("\t.byte 0x%02x, 0x%02x", - (table[i]->len & 0x7F) | 0x80, - (table[i]->len >> 7) & 0x7F); + fputc((table[i]->len & 0x7F) | 0x80, out_bin_file); + fputc((table[i]->len >> 7) & 0x7F, out_bin_file); off += table[i]->len + 2; } - for (k = 0; k < table[i]->len; k++) - printf(", 0x%02x", table[i]->sym[k]); + fwrite(table[i]->sym, 1, table[i]->len, out_bin_file); /* * Now that we wrote out the compressed symbol name, restore the - * original name and print it in the comment. + * original name for the comments below. */ expand_symbol(table[i]->sym, table[i]->len, buf); strcpy((char *)table[i]->sym, buf); - printf("\t/* %s */\n", table[i]->sym); } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf(".size kallsyms_names, . - kallsyms_names\n"); printf("\n"); @@ -448,13 +471,15 @@ static void write_src(void) free(markers); output_label("kallsyms_token_table"); + bin_start = bin_pos(out_bin_file); off = 0; for (i = 0; i < 256; i++) { best_idx[i] = off; expand_symbol(best_table[i], best_table_len[i], buf); - printf("\t.asciz\t\"%s\"\n", buf); + fwrite(buf, 1, strlen(buf) + 1, out_bin_file); off += strlen(buf) + 1; } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf(".size kallsyms_token_table, . - kallsyms_token_table\n"); printf("\n"); @@ -487,12 +512,13 @@ static void write_src(void) sort_symbols_by_name(); output_label("kallsyms_seqs_of_names"); - for (i = 0; i < table_cnt; i++) - printf("\t.byte 0x%02x, 0x%02x, 0x%02x\t/* %s */\n", - (unsigned char)(table[i]->seq >> 16), - (unsigned char)(table[i]->seq >> 8), - (unsigned char)(table[i]->seq >> 0), - table[i]->sym); + bin_start = bin_pos(out_bin_file); + for (i = 0; i < table_cnt; i++) { + fputc(table[i]->seq >> 16, out_bin_file); + fputc(table[i]->seq >> 8, out_bin_file); + fputc(table[i]->seq >> 0, out_bin_file); + } + write_incbin(out_bin_name, bin_start, bin_pos(out_bin_file)); printf("\n"); } @@ -803,6 +829,9 @@ static void sort_symbols(void) int main(int argc, char **argv) { + const char *out_bin_name; + FILE *out_bin_file; + while (1) { static const struct option long_options[] = { {"all-symbols", no_argument, &all_symbols, 1}, @@ -818,14 +847,27 @@ int main(int argc, char **argv) usage(); } - if (optind >= argc) + if (optind + 2 != argc) usage(); + out_bin_name = argv[optind + 1]; + out_bin_file = fopen(out_bin_name, "w"); + if (!out_bin_file) { + perror(out_bin_name); + exit(EXIT_FAILURE); + } + read_map(argv[optind]); shrink_table(); sort_symbols(); optimize_token_table(); - write_src(); + write_src(out_bin_file, out_bin_name); + + /* Write errors are latched in the stream, so one check at the end covers them all. */ + if (ferror(out_bin_file) || fclose(out_bin_file)) { + perror(out_bin_name); + exit(EXIT_FAILURE); + } return 0; } diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index ab0b8125c8cbc6..e88604150d2c75 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -132,7 +132,7 @@ kallsyms() fi info KSYMS "${2}.S" - scripts/kallsyms ${kallsymopt} "${1}" > "${2}.S" + scripts/kallsyms ${kallsymopt} "${1}" "${2}.bin" > "${2}.S" info AS "${2}.o" ${CC} ${NOSTDINC_FLAGS} ${LINUXINCLUDE} ${KBUILD_CPPFLAGS} \ From 4719513c0fa2a39a2410448120a83cb1a416b194 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:53 +0100 Subject: [PATCH 0941/2286] sorttable: parse nm output correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sorttable reads a list of functions from vmlinux, provided by the output of nm, with the -S flag specified providing function sizes. It does so using fscanf(fp, "%16s %16s %c %*s\n", ...) reading address, size, type (a single character) and name. However, nm outputs no size for a symbol which has none, meaning that it misinterprets these entries - interpreting the type character as the size, and, if the name is a single character, ignores the newline and swallows the address of the next entry. Size-less single character names exist (e.g. the assemblers' loop counters left over as local absolute symbols): 0000000000000001 a i 000000000000000f a i 0000000000000050 a j 0000000000000052 a t 000000000000009f a i 0000000000000200 a i sorttable is looking for functions and these are not that, and with nm's output sorted, it just so happens to be that what's swallowed is never a function. However the next commit in this series stops sorting nm's output, so this bug can results in function names being lost. Fix the issue by using '%*[^\n]' in fscanf() rather than '%*s' such that the scan cannot move past the newline. Then, compare the address and size fields (which are always padded to the same width if output by nm) - if they differ, then this is a misread, so simply discard the entry. It's fine to discard these, as sorttable's raison d'être is to discover whether a call site is contained within a function and an empty function can't contain anything. Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-4-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/sorttable.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/sorttable.c b/scripts/sorttable.c index d7b50581c7329f..88e49a5c425127 100644 --- a/scripts/sorttable.c +++ b/scripts/sorttable.c @@ -322,10 +322,23 @@ static int parse_symbols(const char *fname) return -1; } - while (fscanf(fp, "%16s %16s %c %*s\n", addr_str, size_str, &type) == 3) { + while (fscanf(fp, "%16s %16s %c%*[^\n]", addr_str, size_str, &type) == 3) { uint64_t addr; uint64_t size; + /* + * nm outputs size-less entries with a missing 2nd value, which + * means fscanf() just misread its fields. + * + * These don't matter as a call site cannot be in an empty + * function, so just skip them. + * + * nm pads address and size to the same width, so if their + * widths differ, this is a misread. + */ + if (strlen(size_str) != strlen(addr_str)) + continue; + /* Only care about functions */ if (type != 't' && type != 'T' && type != 'W') continue; From c2970d8b52d132bf5152fba592205a2204d7be5a Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:54 +0100 Subject: [PATCH 0942/2286] kbuild: do not sort nm output where the order is irrelevant Unless instructed otherwise, nm sorts by name. There are places where this is unnecessary - an invocation from scripts/sorttable every vmlinux link, scripts/check-function-names.sh run after every vmlinux.o link, and the x86 VOFFSET and ZOFFSET listings between vmlinux and bzImage. Both GNU nm and llvm-nm accept the same '-p' parameter to disable sorting in these instances, so use that to prevent this unnecessary work. Each nm run on its own, x86-64, median of 5: GNU nm 2.47 llvm-nm 22 before after delta before after delta defconfig sorttable, nm -S vmlinux 0.080s 0.039s -0.041s 0.265s 0.113s -0.152s check-function-names.sh 0.072s 0.033s -0.039s 0.244s 0.102s -0.142s VOFFSET 0.077s 0.034s -0.043s 0.261s 0.113s -0.148s ZOFFSET 0.007s 0.005s -0.002s 0.005s 0.005s 0.000s TOTAL -0.125s -0.442s allmodconfig sorttable, nm -S vmlinux 0.156s 0.063s -0.093s 0.539s 0.223s -0.316s check-function-names.sh 0.149s 0.054s -0.095s 0.537s 0.217s -0.320s VOFFSET 0.146s 0.052s -0.094s 0.524s 0.221s -0.303s ZOFFSET 0.007s 0.005s -0.002s 0.006s 0.004s -0.002s TOTAL -0.284s -0.941s llvm-nm appears to be a lot slower than GNU nm, so these builds naturally improve the most. The four run one after another in the serial tail of every build that links vmlinux so impact kernel builds directly. In an allmodconfig the decompressor is built in parallel while the modules, so only the two before the vmlinux link contribute to build time. The build outputs remain unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta --------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.7s 9.5s -0.18s (-2%) x86 defconfig, touch mm/vma.c, clang 9.3s 9.0s -0.27s (-3%) x86 defconfig, clean, gcc 30.3s 30.1s -0.16s (-1%) x86 defconfig, clean, clang 31.7s 31.2s -0.55s (-2%) x86 allmodconfig, touch mm/vma.c, clang 36.4s 35.8s -0.67s (-2%) Assisted-by: LLM Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-5-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- arch/x86/boot/Makefile | 2 +- arch/x86/boot/compressed/Makefile | 2 +- scripts/check-function-names.sh | 3 ++- scripts/link-vmlinux.sh | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/arch/x86/boot/Makefile b/arch/x86/boot/Makefile index 3f9fb3698d6690..4a9bce32586d3b 100644 --- a/arch/x86/boot/Makefile +++ b/arch/x86/boot/Makefile @@ -74,7 +74,7 @@ SETUP_OBJS = $(addprefix $(obj)/,$(setup-y)) sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [a-zA-Z] \(startup_32\|efi.._stub_entry\|efi\(32\)\?_pe_entry\|input_data\|kernel_info\|_end\|_ehead\|_text\|_e\?data\|_e\?sbat\|z_.*\)$$/\#define ZO_\2 0x\1/p' quiet_cmd_zoffset = ZOFFSET $@ - cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@ + cmd_zoffset = $(NM) -p $< | sed -n $(sed-zoffset) > $@ targets += zoffset.h $(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index 06934f9691d6a9..6ec5e031db1d27 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -76,7 +76,7 @@ HOST_EXTRACFLAGS += -I$(srctree)/tools/include sed-voffset := -e 's/^\([0-9a-fA-F]*\) [ABbCDGRSTtVW] \(_text\|__start_rodata\|_sinittext\|__inittext_end\|__bss_start\|_end\)$$/\#define VO_\2 _AC(0x\1,UL)/p' quiet_cmd_voffset = VOFFSET $@ - cmd_voffset = $(NM) $< | sed -n $(sed-voffset) > $@ + cmd_voffset = $(NM) -p $< | sed -n $(sed-voffset) > $@ targets += ../voffset.h diff --git a/scripts/check-function-names.sh b/scripts/check-function-names.sh index 08071133e5a512..94883e690627da 100755 --- a/scripts/check-function-names.sh +++ b/scripts/check-function-names.sh @@ -13,7 +13,8 @@ if [ ! -f "$objfile" ]; then exit 1 fi -bad_symbols=$(${NM:-nm} "$objfile" | awk '$2 ~ /^[TtWw]$/ {print $3}' | grep -E '^(startup|exit|split|unlikely|hot|unknown)(\.|$)') +bad_symbols=$(${NM:-nm} -p "$objfile" | awk '$2 ~ /^[TtWw]$/ {print $3}' | + grep -E '^(startup|exit|split|unlikely|hot|unknown)(\.|$)') if [ -n "$bad_symbols" ]; then echo "$bad_symbols" | while read -r sym; do diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index e88604150d2c75..970ca10f8fa903 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -161,7 +161,7 @@ mksysmap() sorttable() { - ${NM} -S ${1} > .tmp_vmlinux.nm-sort + ${NM} -p -S ${1} > .tmp_vmlinux.nm-sort ${objtree}/scripts/sorttable -s .tmp_vmlinux.nm-sort ${1} } From 8050568e0a46f944117970c3969f9316125bc34d Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:55 +0100 Subject: [PATCH 0943/2286] kbuild: only emit vmlinux relocations when required A kernel build consists of more than one linking pass on vmlinux.o and vmlinux, at minimum two, and with CONFIG_KALLSYMS and BTF enabled on x86-64 for example there are 5 such stages. For architectures that build their own relocation tables (x86, riscv, mips, s390), vmlinux is linked with the --emit-relocs parameter specified. However, this is only required on the final vmlinux link. Symbol tables of trial links preceding it don't need it because they already check that System.map matches kallsyms symbols on each build. GNU ld is slow at emitting relocation tables, so this results in a reduction in build time. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.5s 9.1s -0.42s (-4%) x86 defconfig, clean, gcc 30.1s 29.5s -0.59s (-2%) x86 allmodconfig, touch mm/vma.c, gcc 37.2s 35.8s -1.4s (-4%) Note that this has little impact on LLVM ld which performs this operation more efficiently. Assisted-by: LLM Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-6-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 3 ++- scripts/link-vmlinux.sh | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f6745d723f6dd8..ab3ba78376f709 100644 --- a/Makefile +++ b/Makefile @@ -1265,8 +1265,9 @@ ifdef CONFIG_LD_ORPHAN_WARN LDFLAGS_vmlinux += --orphan-handling=$(CONFIG_LD_ORPHAN_WARN_LEVEL) endif +# --emit-relocs is added by scripts/link-vmlinux.sh, for the final link only. ifneq ($(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),) -LDFLAGS_vmlinux += --emit-relocs --discard-none +LDFLAGS_vmlinux += --discard-none endif # Align the architecture of userspace programs with the kernel diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index 970ca10f8fa903..ed5ff97c0667f5 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -96,6 +96,11 @@ vmlinux_link() ldflags="${ldflags} ${wl}--strip-debug" fi + # Only the final link actually requires the relocations. + if [ "${output}" = "${VMLINUX}" ] && is_enabled CONFIG_ARCH_VMLINUX_NEEDS_RELOCS; then + ldflags="${ldflags} ${wl}--emit-relocs" + fi + if [ -n "${generate_map}" ]; then ldflags="${ldflags} ${wl}-Map=vmlinux.map" fi From 5005f8043c092777b853663845da8facb5eb1385 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:56 +0100 Subject: [PATCH 0944/2286] elf-parse: add section flags, symbol binding and mapping helpers Extend elf-parse to be able to read the symbol table of vmlinux in kallsyms. This requires the ability to open ELF files in read-only mode, so provide elf_map_ro() to do so. It also requires accessors for section flags and symbol bindings, so provide these via shdr_flags() and sym_bind(). It's useful to be able to open an ELF file, map it, and then have easy access to its section headers, names and symbol table, so introduce struct elf_file to store this. In order to obtain this data also provide elf_open_ro() and elf_close(). Also, check for the file being an ELF file first in elf_parse(). This is the logical thing to check for first, but additionally prevents kallsyms from having to check this it self. Assisted-by: LLM Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-7-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/elf-parse.c | 103 ++++++++++++++++++++++++++++++++++++++------ scripts/elf-parse.h | 54 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/scripts/elf-parse.c b/scripts/elf-parse.c index 99869ff91a8c7f..ce1c2a670244eb 100644 --- a/scripts/elf-parse.c +++ b/scripts/elf-parse.c @@ -17,15 +17,21 @@ struct elf_funcs elf_parser; * Get the whole file as a programming convenience in order to avoid * malloc+lseek+read+free of many pieces. If successful, then mmap * avoids copying unused pieces; else just read the whole file. - * Open for both read and write. + * Open for both read and write if writable is true, otherwise open + * read-only. */ -static void *map_file(char const *fname, size_t *size) +static void *map_file(char const *fname, size_t *size, bool writable) { - int fd; + int fd, prot = PROT_READ, flags = MAP_PRIVATE; struct stat sb; void *addr = NULL; - fd = open(fname, O_RDWR); + if (writable) { + prot |= PROT_WRITE; + flags = MAP_SHARED; + } + + fd = open(fname, writable ? O_RDWR : O_RDONLY); if (fd < 0) { perror(fname); return NULL; @@ -39,7 +45,7 @@ static void *map_file(char const *fname, size_t *size) goto out; } - addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + addr = mmap(0, sb.st_size, prot, flags, fd, 0); if (addr == MAP_FAILED) { fprintf(stderr, "Could not mmap file: %s\n", fname); goto out; @@ -57,6 +63,12 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) Elf_Ehdr *ehdr = addr; uint16_t type; + if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || + ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { + fprintf(stderr, "unrecognized ELF file %s\n", fname); + return -1; + } + switch (ehdr->e32.e_ident[EI_DATA]) { case ELFDATA2LSB: elf_parser.r = rle; @@ -78,12 +90,6 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) return -1; } - if (memcmp(ELFMAG, ehdr->e32.e_ident, SELFMAG) != 0 || - ehdr->e32.e_ident[EI_VERSION] != EV_CURRENT) { - fprintf(stderr, "unrecognized ELF file %s\n", fname); - return -1; - } - type = elf_parser.r2(&ehdr->e32.e_type); if (!((1 << type) & types)) { fprintf(stderr, "Invalid ELF type file %s\n", fname); @@ -103,7 +109,9 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) elf_parser.shdr_name = shdr32_name; elf_parser.shdr_type = shdr32_type; elf_parser.shdr_entsize = shdr32_entsize; + elf_parser.shdr_flags = shdr32_flags; elf_parser.sym_type = sym32_type; + elf_parser.sym_bind = sym32_bind; elf_parser.sym_name = sym32_name; elf_parser.sym_value = sym32_value; elf_parser.sym_shndx = sym32_shndx; @@ -133,7 +141,9 @@ static int elf_parse(const char *fname, void *addr, uint32_t types) elf_parser.shdr_name = shdr64_name; elf_parser.shdr_type = shdr64_type; elf_parser.shdr_entsize = shdr64_entsize; + elf_parser.shdr_flags = shdr64_flags; elf_parser.sym_type = sym64_type; + elf_parser.sym_bind = sym64_bind; elf_parser.sym_name = sym64_name; elf_parser.sym_value = sym64_value; elf_parser.sym_shndx = sym64_shndx; @@ -174,12 +184,13 @@ int elf_map_long_size(void *addr) return ehdr->e32.e_ident[EI_CLASS] == ELFCLASS32 ? 4 : 8; } -void *elf_map(char const *fname, size_t *size, uint32_t types) +static void *__elf_map(char const *fname, size_t *size, uint32_t types, + bool writable) { void *addr; int ret; - addr = map_file(fname, size); + addr = map_file(fname, size, writable); if (!addr) return NULL; @@ -192,7 +203,73 @@ void *elf_map(char const *fname, size_t *size, uint32_t types) return addr; } +void *elf_map(char const *fname, size_t *size, uint32_t types) +{ + return __elf_map(fname, size, types, true); +} + +/* For tools that only read the file. */ +void *elf_map_ro(char const *fname, size_t *size, uint32_t types) +{ + return __elf_map(fname, size, types, false); +} + void elf_unmap(void *addr, size_t size) { munmap(addr, size); } + +/* + * Open an ELF file and map it read-only, locating its section headers, section + * names and symbol table which are populated in the elf out parameter. + * + * Returns 0 on success, otherwise -1. + */ +int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf) +{ + Elf_Ehdr *ehdr; + Elf_Shdr *first; + unsigned int shstrndx, i; + + memset(elf, 0, sizeof(*elf)); + elf->base = elf_map_ro(fname, &elf->size, types); + if (!elf->base) + return -1; + + ehdr = elf->base; + elf->shdrs = (const char *)elf->base + ehdr_shoff(ehdr); + elf->shentsize = ehdr_shentsize(ehdr); + first = elf_section(elf, 0); + + /* A count or index too large for the header lives in section 0. */ + elf->shnum = ehdr_shnum(ehdr); + if (elf->shnum == SHN_UNDEF) + elf->shnum = shdr_size(first); + shstrndx = ehdr_shstrndx(ehdr); + if (shstrndx == SHN_XINDEX) + shstrndx = shdr_link(first); + elf->shstrtab = (const char *)elf->base + + shdr_offset(elf_section(elf, shstrndx)); + + for (i = 0; i < elf->shnum && !elf->symtab; i++) + if (shdr_type(elf_section(elf, i)) == SHT_SYMTAB) + elf->symtab = elf_section(elf, i); + + if (!elf->symtab) { + fprintf(stderr, "%s: no symbol table\n", fname); + elf_close(elf); + return -1; + } + + elf->strtab = (const char *)elf->base + + shdr_offset(elf_section(elf, shdr_link(elf->symtab))); + elf->nr_syms = shdr_size(elf->symtab) / shdr_entsize(elf->symtab); + + return 0; +} + +void elf_close(struct elf_file *elf) +{ + elf_unmap(elf->base, elf->size); + elf->base = NULL; +} diff --git a/scripts/elf-parse.h b/scripts/elf-parse.h index f4411e03069dc5..c5ad754da25403 100644 --- a/scripts/elf-parse.h +++ b/scripts/elf-parse.h @@ -37,10 +37,12 @@ struct elf_funcs { uint64_t (*shdr_offset)(Elf_Shdr *shdr); uint64_t (*shdr_size)(Elf_Shdr *shdr); uint64_t (*shdr_entsize)(Elf_Shdr *shdr); + uint64_t (*shdr_flags)(Elf_Shdr *shdr); uint32_t (*shdr_link)(Elf_Shdr *shdr); uint32_t (*shdr_name)(Elf_Shdr *shdr); uint32_t (*shdr_type)(Elf_Shdr *shdr); uint8_t (*sym_type)(Elf_Sym *sym); + uint8_t (*sym_bind)(Elf_Sym *sym); uint32_t (*sym_name)(Elf_Sym *sym); uint64_t (*sym_value)(Elf_Sym *sym); uint16_t (*sym_shndx)(Elf_Sym *sym); @@ -143,6 +145,7 @@ SHDR_ADDR(addr) SHDR_ADDR(offset) SHDR_ADDR(size) SHDR_ADDR(entsize) +SHDR_ADDR(flags) SHDR_WORD(link) SHDR_WORD(name) @@ -211,6 +214,21 @@ static inline uint8_t sym_type(Elf_Sym *sym) return elf_parser.sym_type(sym); } +static inline uint8_t sym64_bind(Elf_Sym *sym) +{ + return ELF64_ST_BIND(sym->e64.st_info); +} + +static inline uint8_t sym32_bind(Elf_Sym *sym) +{ + return ELF32_ST_BIND(sym->e32.st_info); +} + +static inline uint8_t sym_bind(Elf_Sym *sym) +{ + return elf_parser.sym_bind(sym); +} + SYM_ADDR(value) SYM_WORD(name) SYM_HALF(shndx) @@ -298,8 +316,44 @@ static inline void w8le(uint64_t val, uint64_t *x) } void *elf_map(char const *fname, size_t *size, uint32_t types); +void *elf_map_ro(char const *fname, size_t *size, uint32_t types); void elf_unmap(void *addr, size_t size); int elf_map_machine(void *addr); int elf_map_long_size(void *addr); +/* A mapped file with its section headers, section names and symbol table. */ +struct elf_file { + void *base; + size_t size; + const char *shdrs; + unsigned int shnum, shentsize; + const char *shstrtab; + Elf_Shdr *symtab; + const char *strtab; + size_t nr_syms; +}; + +int elf_open_ro(char const *fname, uint32_t types, struct elf_file *elf); +void elf_close(struct elf_file *elf); + +static inline Elf_Shdr *elf_section(const struct elf_file *elf, + unsigned int index) +{ + return (Elf_Shdr *)(elf->shdrs + (size_t)index * elf->shentsize); +} + +static inline const char *elf_section_name(const struct elf_file *elf, + Elf_Shdr *shdr) +{ + return elf->shstrtab + shdr_name(shdr); +} + +static inline Elf_Sym *elf_symbol(const struct elf_file *elf, size_t index) +{ + const char *base = elf->base; + + return (Elf_Sym *)(base + shdr_offset(elf->symtab) + + index * shdr_entsize(elf->symtab)); +} + #endif /* _SCRIPTS_ELF_PARSE_H */ From 531980b4b17e916f5955bcc53a188827624f5a0f Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:17:57 +0100 Subject: [PATCH 0945/2286] kallsyms: reimplement mksysmap in C mksysmap is a sed script consisting of 30 patterns which link-vmlinux.sh uses to generate *.syms files, and which kallsyms is then called against to generate *.kallsyms files, with the final vmlinux build ultimately generating System.map. For an x86-64 allmodconfig build, this involves three nm runs over a 250 MiB file and half a million lines written and read each time - 0.5s per pass for llvm-nm, and 0.2s for GNU nm, with parsing on top of that. This is unnecessary, instead have kallsyms simply read the ELF file directly making use of the existing elf-parse library in scripts/. This changes kallsyms such that its input is no longer the output from nm, but rather an ELF file. However, if the input file is empty, it outputs an empty table, which retains the same behaviour on first pass that the build system expects. System.map is byte-identical to nm | mksysmap for GNU nm and llvm-nm on two x86 configurations each, and for llvm-nm on arm64, arm, s390 and loongarch defconfigs, so are the kallsyms tables of every pass. Relinking vmlinux, link steps included: before after allmodconfig clang 9.1s 7.9s allmodconfig gcc 7.8s 7.6s defconfig clang 3.7s 3.0s defconfig gcc 3.5s 3.4s Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 9.1s 8.6s -0.43s (-5%) x86 defconfig, touch mm/vma.c, clang 9.0s 8.0s -1.1s (-12%) x86 defconfig, clean, gcc 29.5s 28.9s -0.64s (-2%) x86 defconfig, clean, clang 31.2s 29.8s -1.4s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 35.8s 35.0s -0.77s (-2%) x86 allmodconfig, touch mm/vma.c, clang 35.8s 33.7s -2.0s (-6%) Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-8-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Makefile | 5 +- scripts/kallsyms-sysmap.c | 248 ++++++++++++++++++++++++++++++++++++++ scripts/kallsyms.c | 162 ++++++++++++++----------- scripts/kallsyms.h | 44 +++++++ scripts/link-vmlinux.sh | 15 +-- scripts/mksysmap | 94 --------------- 6 files changed, 395 insertions(+), 173 deletions(-) create mode 100644 scripts/kallsyms-sysmap.c create mode 100644 scripts/kallsyms.h delete mode 100755 scripts/mksysmap diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f09..38d58b0a37e645 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -3,7 +3,7 @@ # scripts contains sources for various helper programs used throughout # the kernel for the build process. -hostprogs-always-$(CONFIG_KALLSYMS) += kallsyms +hostprogs-always-y += kallsyms hostprogs-always-$(BUILD_C_RECORDMCOUNT) += recordmcount hostprogs-always-$(CONFIG_BUILDTIME_TABLE_SORT) += sorttable hostprogs-always-$(CONFIG_ASN1) += asn1_compiler @@ -13,6 +13,7 @@ hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen hostprogs-always-$(CONFIG_TRACEPOINTS) += tracepoint-update +kallsyms-objs := kallsyms.o kallsyms-sysmap.o elf-parse.o sorttable-objs := sorttable.o elf-parse.o tracepoint-update-objs := tracepoint-update.o elf-parse.o @@ -30,6 +31,8 @@ rustdoc_test_builder-rust := y rustdoc_test_gen-rust := y HOSTCFLAGS_tracepoint-update.o = -I$(srctree)/tools/include +HOSTCFLAGS_kallsyms.o = -I$(srctree)/tools/include +HOSTCFLAGS_kallsyms-sysmap.o = -I$(srctree)/tools/include HOSTCFLAGS_elf-parse.o = -I$(srctree)/tools/include HOSTCFLAGS_sorttable.o = -I$(srctree)/tools/include HOSTLDLIBS_sorttable = -lpthread diff --git a/scripts/kallsyms-sysmap.c b/scripts/kallsyms-sysmap.c new file mode 100644 index 00000000000000..a482968eba5112 --- /dev/null +++ b/scripts/kallsyms-sysmap.c @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Obtain symbols from vmlinux for usage by kallsyms. Replaces mksysmap. + * + * To retain compatibility, it provides the same output as nm, only faster. + */ + +#include +#include +#include +#include +#include + +#include "elf-parse.h" +#include "kallsyms.h" + +/* + * What mksysmap dropped from System.map, by name: symbols that are not needed + * there, or not suitable for kallsyms. + */ +static const char *const sysmap_omit_prefixes[] = { + /* local symbols for ARM, MIPS, etc. */ + "$", + /* local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. */ + ".L", + /* arm64 EFI stub namespace */ + "__efistub_", + /* arm64 local symbols in PIE namespace */ + "__pi_$", + "__pi_.L", + /* arm64 local symbols in non-VHE KVM namespace */ + "__kvm_nvhe_$", + "__kvm_nvhe_.L", + /* CFI type identifiers */ + "__kcfi_typeid_", + "__kvm_nvhe___kcfi_typeid_", + "__pi___kcfi_typeid_", + /* CRC from modversions */ + "__crc_", + /* EXPORT_SYMBOL (symbol name) */ + "__kstrtab_", + /* EXPORT_SYMBOL (namespace) */ + "__kstrtabns_", + /* MODULE_DEVICE_TABLE (symbol name) */ + "__mod_device_table__", +}; + +static const char *const sysmap_omit_suffixes[] = { + /* arm */ + "_from_arm", + "_from_thumb", + "_veneer", +}; + +static const char *const sysmap_omit_names[] = { + /* for LoongArch? */ + "L0", + /* ppc */ + "_SDA_BASE_", + "_SDA2_BASE_", +}; + +/* Symbols that contain the pattern anywhere are dropped. */ +static const char *const sysmap_omit_patterns[] = { + /* ppc stub */ + ".long_branch.", + ".plt_branch.", +}; + +/* __*Thunk_: the linker's range extension thunks on arm. */ +static bool is_range_thunk(const char *name) +{ + const char *p; + + if (!string_starts_with(name, "__")) + return false; + for (p = name + 2; isalnum((unsigned char)*p); p++) + ; + return p - name >= 7 && *p == '_' && strncmp(p - 5, "Thunk", 5) == 0; +} + +/* __UNIQUE_ID_modinfo_: the MODULE_INFO() strings of built-in code. */ +static bool is_modinfo_id(const char *name) +{ + static const char prefix[] = "__UNIQUE_ID_modinfo_"; + const char *p; + + if (!string_starts_with(name, prefix)) + return false; + for (p = name + strlen(prefix); isdigit((unsigned char)*p); p++) + ; + return !*p; +} + +static bool sysmap_omits(const char *name, char type) +{ + size_t i; + + /* Absolute, undefined and debugging symbols. */ + if (type == 'a' || type == 'N' || type == 'U' || type == 'w') + return true; + + for (i = 0; i < ARRAY_SIZE(sysmap_omit_prefixes); i++) + if (string_starts_with(name, sysmap_omit_prefixes[i])) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_suffixes); i++) + if (string_ends_with(name, sysmap_omit_suffixes[i])) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_names); i++) + if (strcmp(name, sysmap_omit_names[i]) == 0) + return true; + for (i = 0; i < ARRAY_SIZE(sysmap_omit_patterns); i++) + if (strstr(name, sysmap_omit_patterns[i])) + return true; + + return is_range_thunk(name) || is_modinfo_id(name); +} + +/* nm's letter for a symbol defined in a section, as BFD classifies it. */ +static char section_symbol_type(Elf_Shdr *shdr, const char *secname) +{ + static const char *const debug_prefixes[] = { + ".debug", ".zdebug", ".gnu.debuglto_.debug_", + ".gnu.linkonce.wi.", ".line", ".stab", + }; + const uint64_t flags = shdr_flags(shdr); + size_t i; + + if (flags & SHF_EXECINSTR) + return 't'; + if (flags & SHF_ALLOC) { + if (shdr_type(shdr) == SHT_NOBITS) + return 'b'; + return flags & SHF_WRITE ? 'd' : 'r'; + } + for (i = 0; i < ARRAY_SIZE(debug_prefixes); i++) + if (string_starts_with(secname, debug_prefixes[i])) + return 'N'; + if (shdr_type(shdr) != SHT_NOBITS && !(flags & SHF_WRITE)) + return 'n'; + return '?'; +} + +/* The letter nm prints for a symbol, or 0 for one it leaves out. */ +static char elf_symbol_type(const struct elf_file *elf, Elf_Sym *sym) +{ + unsigned int bind = sym_bind(sym), type = sym_type(sym); + unsigned int shndx = sym_shndx(sym); + Elf_Shdr *shdr; + char c; + + if (type == STT_SECTION || type == STT_FILE) + return 0; + if (shndx == SHN_COMMON) + return 'C'; + if (shndx == SHN_UNDEF) { + if (bind == STB_WEAK) + return type == STT_OBJECT ? 'v' : 'w'; + return 'U'; + } + if (type == STT_GNU_IFUNC) + return 'i'; + if (bind == STB_WEAK) + return type == STT_OBJECT ? 'V' : 'W'; + if (bind == STB_GNU_UNIQUE) + return 'u'; + if (bind != STB_GLOBAL && bind != STB_LOCAL) + return '?'; + + if (shndx == SHN_ABS) { + c = 'a'; + } else if (shndx < elf->shnum) { + shdr = elf_section(elf, shndx); + c = section_symbol_type(shdr, elf_section_name(elf, shdr)); + } else { + return '?'; + } + + return bind == STB_GLOBAL ? toupper(c) : c; +} + +/* nm -n order: by address, then by name. */ +static int compare_symbols(const void *a, const void *b) +{ + const struct sysmap_symbol *sa = a, *sb = b; + + if (sa->addr != sb->addr) + return sa->addr < sb->addr ? -1 : 1; + return strcmp(sa->name, sb->name); +} + +/* The symbols "nm -n | mksysmap" would list, in that order. */ +static struct sysmap_symbol *elf_read_symbols(const struct elf_file *elf, + size_t *nr_kept) +{ + struct sysmap_symbol *syms = xmalloc(elf->nr_syms * sizeof(*syms)); + size_t i, n = 0; + + for (i = 1; i < elf->nr_syms; i++) { + Elf_Sym *sym = elf_symbol(elf, i); + const char *name = elf->strtab + sym_name(sym); + char type = elf_symbol_type(elf, sym); + + if (!type || sysmap_omits(name, type)) + continue; + + syms[n].addr = sym_value(sym); + syms[n].name = name; + syms[n].type = type; + n++; + } + + qsort(syms, n, sizeof(*syms), compare_symbols); + *nr_kept = n; + return syms; +} + +struct sysmap *sysmap_read(const char *path) +{ + struct sysmap *map = xcalloc(1, sizeof(*map)); + + /* The names point into the mapping; keep it until the map is freed. */ + if (elf_open_ro(path, (1 << ET_EXEC) | (1 << ET_DYN), &map->elf)) + exit(EXIT_FAILURE); + map->syms = elf_read_symbols(&map->elf, &map->nr_syms); + map->addr_width = elf_map_long_size(map->elf.base) * 2; + + return map; +} + +void sysmap_write(const struct sysmap *map, FILE *out) +{ + size_t i; + + for (i = 0; i < map->nr_syms; i++) { + const struct sysmap_symbol *s = &map->syms[i]; + + fprintf(out, "%0*llx %c %s\n", map->addr_width, s->addr, s->type, + s->name); + } +} + +void sysmap_free(struct sysmap *map) +{ + free(map->syms); + elf_close(&map->elf); + free(map); +} diff --git a/scripts/kallsyms.c b/scripts/kallsyms.c index b615db767b1d73..ecb444078c1ca3 100644 --- a/scripts/kallsyms.c +++ b/scripts/kallsyms.c @@ -5,7 +5,12 @@ * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. * - * Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S + * Usage: kallsyms [--all-symbols] [--pc-relative] [--sysmap=out.map] in out.bin > out.S + * kallsyms --sysmap=out.map in + * + * in is vmlinux; an empty file stands for the first link, which has no + * symbols yet, and gives an empty table. --sysmap also writes the symbols + * in System.map format. * * The byte tables go to out.bin and are pulled into out.S with .incbin; * wider tables stay assembler source for endianness and relocations. @@ -21,7 +26,6 @@ * */ -#include #include #include #include @@ -29,10 +33,10 @@ #include #include #include - +#include #include -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#include "kallsyms.h" #define KSYM_NAME_LEN 512 @@ -108,11 +112,13 @@ static void sym_arr_free(struct sym_arr *arr) static void usage(void) { - fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] in.map out.bin > out.S\n"); + fprintf(stderr, "Usage: kallsyms [--all-symbols] [--pc-relative] [--sysmap=out.map]\n" + " in out.bin > out.S\n" + " kallsyms --sysmap=out.map vmlinux\n"); exit(1); } -static char *sym_name(const struct sym_entry *s) +static char *sym_entry_name(const struct sym_entry *s) { return (char *)s->sym + 1; } @@ -153,37 +159,12 @@ static void check_symbol_range(const char *sym, unsigned long long addr, } } -static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) +static struct sym_entry *add_symbol(unsigned long long addr, char type, + const char *name) { - char *name, type, *p; - unsigned long long addr; - size_t len; - ssize_t readlen; + size_t len = strlen(name); struct sym_entry *sym; - errno = 0; - readlen = getline(buf, buf_len, in); - if (readlen < 0) { - if (errno) { - perror("read_symbol"); - exit(EXIT_FAILURE); - } - return NULL; - } - - if ((*buf)[readlen - 1] == '\n') - (*buf)[readlen - 1] = 0; - - addr = strtoull(*buf, &p, 16); - - if (*buf == p || *p++ != ' ' || !isascii((type = *p++)) || *p++ != ' ') { - fprintf(stderr, "line format error\n"); - exit(EXIT_FAILURE); - } - - name = p; - len = strlen(name); - if (len >= KSYM_NAME_LEN) { fprintf(stderr, "Symbol %s too long for kallsyms (%zu >= %d).\n" "Please increase KSYM_NAME_LEN both in kernel and kallsyms.c\n", @@ -208,7 +189,7 @@ static struct sym_entry *read_symbol(FILE *in, char **buf, size_t *buf_len) sym->addr = addr; sym->len = len; sym->sym[0] = type; - strcpy(sym_name(sym), name); + strcpy(sym_entry_name(sym), name); return sym; } @@ -229,14 +210,9 @@ static int symbol_in_range(const struct sym_entry *s, return 0; } -static bool string_starts_with(const char *s, const char *prefix) -{ - return strncmp(s, prefix, strlen(prefix)) == 0; -} - static int symbol_valid(const struct sym_entry *s) { - const char *name = sym_name(s); + const char *name = sym_entry_name(s); /* if --all-symbols is not specified, then symbols outside the text * and inittext sections are discarded */ @@ -286,36 +262,55 @@ static void shrink_table(void) table_cnt = pos; } -static void read_map(const char *in) +static void add_table_entry(struct sym_entry *sym) { - FILE *fp; - struct sym_entry *sym; - char *buf = NULL; - size_t buflen = 0; + sym->seq = table_cnt; - fp = fopen(in, "r"); - if (!fp) { - perror(in); - exit(1); + if (table_cnt >= table_size) { + table_size = table_size ? table_size * 2 : 10000; + table = xrealloc(table, sizeof(*table) * table_size); } - while (!feof(fp)) { - sym = read_symbol(fp, &buf, &buflen); - if (!sym) - continue; + table[table_cnt++] = sym; +} - sym->seq = table_cnt; +static bool file_is_empty(const char *path) +{ + struct stat st; - if (table_cnt >= table_size) { - table_size += 10000; - table = xrealloc(table, sizeof(*table) * table_size); - } + if (stat(path, &st)) { + perror(path); + exit(EXIT_FAILURE); + } + + return st.st_size == 0; +} + +/* + * Read the symbols from vmlinux, writing System.map if asked to. The first + * link has no symbols yet: an empty file gives an empty table. + */ +static void read_elf(const char *path, FILE *sysmap_out) +{ + struct sysmap *map; + size_t i; + + if (file_is_empty(path)) + return; + + map = sysmap_read(path); + if (sysmap_out) + sysmap_write(map, sysmap_out); + + for (i = 0; i < map->nr_syms; i++) { + const struct sysmap_symbol *s = &map->syms[i]; + struct sym_entry *sym = add_symbol(s->addr, s->type, s->name); - table[table_cnt++] = sym; + if (sym) + add_table_entry(sym); } - free(buf); - fclose(fp); + sysmap_free(map); } static void output_label(const char *label) @@ -378,7 +373,7 @@ static int compare_names(const void *a, const void *b) const struct sym_entry *sa = *(const struct sym_entry **)a; const struct sym_entry *sb = *(const struct sym_entry **)b; - ret = strcmp(sym_name(sa), sym_name(sb)); + ret = strcmp(sym_entry_name(sa), sym_entry_name(sb)); if (!ret) { if (sa->addr > sb->addr) return 1; @@ -756,7 +751,7 @@ static void optimize_token_table(void) /* guess for "linker script provide" symbol */ static int may_be_linker_script_provide_symbol(const struct sym_entry *se) { - const char *symbol = sym_name(se); + const char *symbol = sym_entry_name(se); int len = se->len - 1; if (len < 8) @@ -813,8 +808,8 @@ static int compare_symbols(const void *a, const void *b) return wa - wb; /* sort by the number of prefix underscores */ - wa = strspn(sym_name(sa), "_"); - wb = strspn(sym_name(sb), "_"); + wa = strspn(sym_entry_name(sa), "_"); + wb = strspn(sym_entry_name(sb), "_"); if (wa != wb) return wa - wb; @@ -829,13 +824,14 @@ static void sort_symbols(void) int main(int argc, char **argv) { - const char *out_bin_name; - FILE *out_bin_file; + const char *in, *sysmap = NULL, *out_bin_name; + FILE *sysmap_out = NULL, *out_bin_file; while (1) { static const struct option long_options[] = { {"all-symbols", no_argument, &all_symbols, 1}, {"pc-relative", no_argument, &pc_relative, 1}, + {"sysmap", required_argument, NULL, 's'}, {}, }; @@ -843,13 +839,33 @@ int main(int argc, char **argv) if (c == -1) break; - if (c != 0) + if (c == 's') + sysmap = optarg; + else if (c != 0) usage(); } - if (optind + 2 != argc) + if (optind + 2 != argc && !(sysmap && optind + 1 == argc)) usage(); + in = argv[optind]; + if (sysmap) { + sysmap_out = fopen(sysmap, "w"); + if (!sysmap_out) { + perror(sysmap); + exit(EXIT_FAILURE); + } + } + + if (optind + 1 == argc) { + read_elf(in, sysmap_out); + if (ferror(sysmap_out) || fclose(sysmap_out)) { + perror(sysmap); + exit(EXIT_FAILURE); + } + return 0; + } + out_bin_name = argv[optind + 1]; out_bin_file = fopen(out_bin_name, "w"); if (!out_bin_file) { @@ -857,7 +873,11 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } - read_map(argv[optind]); + read_elf(in, sysmap_out); + if (sysmap_out && (ferror(sysmap_out) || fclose(sysmap_out))) { + perror(sysmap); + exit(EXIT_FAILURE); + } shrink_table(); sort_symbols(); optimize_token_table(); diff --git a/scripts/kallsyms.h b/scripts/kallsyms.h new file mode 100644 index 00000000000000..4b2801ba628593 --- /dev/null +++ b/scripts/kallsyms.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef KALLSYMS_H +#define KALLSYMS_H + +#include +#include +#include +#include + +#include +#include "elf-parse.h" + +static inline bool string_starts_with(const char *s, const char *prefix) +{ + return strncmp(s, prefix, strlen(prefix)) == 0; +} + +static inline bool string_ends_with(const char *s, const char *suffix) +{ + size_t len = strlen(s), suffix_len = strlen(suffix); + + return len >= suffix_len && strcmp(s + len - suffix_len, suffix) == 0; +} + +/* A symbol as nm lists it. */ +struct sysmap_symbol { + unsigned long long addr; + const char *name; + char type; +}; + +/* The symbols of an ELF file that System.map lists, in its order. */ +struct sysmap { + struct sysmap_symbol *syms; + size_t nr_syms; + int addr_width; /* hex digits of an address */ + struct elf_file elf; /* the mapping the names point into */ +}; + +struct sysmap *sysmap_read(const char *path); +void sysmap_write(const struct sysmap *map, FILE *out); +void sysmap_free(struct sysmap *map); + +#endif /* KALLSYMS_H */ diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh index ed5ff97c0667f5..c520741f85c2d4 100755 --- a/scripts/link-vmlinux.sh +++ b/scripts/link-vmlinux.sh @@ -123,11 +123,15 @@ kallsymso_changed() ! cmp -s "${kallsymso_prev}.sym" "${kallsymso}.sym" } -# Create ${2}.o file with all symbols from the ${1} object file +# Create ${2}.o with the kallsyms tables for ${1} (the vmlinux, or an empty +# listing for the first pass); list the symbols used in ${3} if given. kallsyms() { local kallsymopt; + if [ -n "${3:-}" ]; then + kallsymopt="--sysmap=${3}" + fi if is_enabled CONFIG_KALLSYMS_ALL; then kallsymopt="${kallsymopt} --all-symbols" fi @@ -150,18 +154,15 @@ kallsyms() # Perform kallsyms for the given temporary vmlinux. sysmap_and_kallsyms() { - mksysmap "${1}" "${1}.syms" - kallsyms "${1}.syms" "${1}.kallsyms" - + kallsyms "${1}" "${1}.kallsyms" "${1}.syms" kallsyms_sysmap=${1}.syms } # Create map file with all symbols from ${1} -# See mksymap for additional details mksysmap() { - info NM ${2} - ${NM} -n "${1}" | sed -f "${srctree}/scripts/mksysmap" > "${2}" + info SYSMAP ${2} + scripts/kallsyms --sysmap="${2}" "${1}" } sorttable() diff --git a/scripts/mksysmap b/scripts/mksysmap deleted file mode 100755 index 856b26ba2ac024..00000000000000 --- a/scripts/mksysmap +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sed -f -# SPDX-License-Identifier: GPL-2.0-only -# -# sed script to filter out symbols that are not needed for System.map, -# or not suitable for kallsyms. The input should be 'nm -n '. -# -# System.map is used by module-init tools and some debugging -# tools to retrieve the actual addresses of symbols in the kernel. -# -# readprofile starts reading symbols when _stext is found, and -# continue until it finds a symbol which is not either of 'T', 't', -# 'W' or 'w'. -# -# --------------------------------------------------------------------------- -# Ignored symbol types -# - -# a: local absolute symbols -# N: debugging symbols -# U: undefined global symbols -# w: local weak symbols -/ [aNUw] /d - -# --------------------------------------------------------------------------- -# Ignored prefixes -# (do not forget a space before each pattern) - -# local symbols for ARM, MIPS, etc. -/ \$/d - -# local labels, .LBB, .Ltmpxxx, .L__unnamed_xx, .LASANPC, etc. -/ \.L/d - -# arm64 EFI stub namespace -/ __efistub_/d - -# arm64 local symbols in PIE namespace -/ __pi_\$/d -/ __pi_\.L/d - -# arm64 local symbols in non-VHE KVM namespace -/ __kvm_nvhe_\$/d -/ __kvm_nvhe_\.L/d - -# lld arm/aarch64/mips thunks -/ __[[:alnum:]]*Thunk_/d - -# CFI type identifiers -/ __kcfi_typeid_/d -/ __kvm_nvhe___kcfi_typeid_/d -/ __pi___kcfi_typeid_/d - -# CRC from modversions -/ __crc_/d - -# EXPORT_SYMBOL (symbol name) -/ __kstrtab_/d - -# EXPORT_SYMBOL (namespace) -/ __kstrtabns_/d - -# MODULE_DEVICE_TABLE (symbol name) -/ __mod_device_table__/d - -# --------------------------------------------------------------------------- -# Ignored suffixes -# (do not forget '$' after each pattern) - -# arm -/_from_arm$/d -/_from_thumb$/d -/_veneer$/d - -# --------------------------------------------------------------------------- -# Ignored symbols (exact match) -# (do not forget a space before and '$' after each pattern) - -# for LoongArch? -/ L0$/d - -# ppc -/ _SDA_BASE_$/d -/ _SDA2_BASE_$/d - -# MODULE_INFO() -/ __UNIQUE_ID_modinfo_[0-9]*$/d - -# --------------------------------------------------------------------------- -# Ignored patterns -# (symbols that contain the pattern are ignored) - -# ppc stub -/\.long_branch\./d -/\.plt_branch\./d From f85147b291e355b34dfbe3c343c3682d488a8f07 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:00 +0100 Subject: [PATCH 0946/2286] kbuild: move the toolchain checks into scripts/Kconfig.toolchain init/Kconfig opens with two hundred lines identifying the compiler, assembler, linker and rustc and probing which options they support, none of it to do with init. Move them into their own file, sourced from the top of init/Kconfig, so that the next commit can add to them without growing init/Kconfig further. Also update MAINTAINERS to put the new file in the KCONFIG entry. No functional change intended. Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-11-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- MAINTAINERS | 1 + init/Kconfig | 197 +------------------------------------ scripts/Kconfig.toolchain | 198 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 196 deletions(-) create mode 100644 scripts/Kconfig.toolchain diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb0777052..46c4f7e3061c9b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14057,6 +14057,7 @@ Q: https://patchwork.kernel.org/project/linux-kbuild/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kbuild/linux.git F: Documentation/kbuild/kconfig* F: scripts/Kconfig.include +F: scripts/Kconfig.toolchain F: scripts/kconfig/ KCORE diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c522e..352601b44b5cab 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1,201 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -config CC_VERSION_TEXT - string - default "$(CC_VERSION_TEXT)" - help - This is used in unclear ways: - - - Re-run Kconfig when the compiler is updated - The 'default' property references the environment variable, - CC_VERSION_TEXT so it is recorded in include/config/auto.conf.cmd. - When the compiler is updated, Kconfig will be invoked. - - - Ensure full rebuild when the compiler is updated - include/linux/compiler-version.h contains this option in the comment - line so fixdep adds include/config/CC_VERSION_TEXT into the - auto-generated dependency. When the compiler is updated, syncconfig - will touch it and then every file will be rebuilt. - -config CC_IS_GCC - def_bool $(success,test "$(cc-name)" = GCC) - -config GCC_VERSION - int - default $(cc-version) if CC_IS_GCC - default 0 - -config CC_IS_CLANG - def_bool $(success,test "$(cc-name)" = Clang) - -config CLANG_VERSION - int - default $(cc-version) if CC_IS_CLANG - default 0 - -config AS_IS_GNU - def_bool $(success,test "$(as-name)" = GNU) - -config AS_IS_LLVM - def_bool $(success,test "$(as-name)" = LLVM) - -config AS_VERSION - int - # Use clang version if this is the integrated assembler - default CLANG_VERSION if AS_IS_LLVM - default $(as-version) - -config LD_IS_BFD - def_bool $(success,test "$(ld-name)" = BFD) - -config LD_VERSION - int - default $(ld-version) if LD_IS_BFD - default 0 - -config LD_IS_LLD - def_bool $(success,test "$(ld-name)" = LLD) - -config LLD_VERSION - int - default $(ld-version) if LD_IS_LLD - default 0 - -config RUSTC_VERSION - int - default $(rustc-version) - help - It does not depend on `RUST` since that one may need to use the version - in a `depends on`. - -config RUST_IS_AVAILABLE - def_bool $(success,$(srctree)/scripts/rust_is_available.sh) - help - This shows whether a suitable Rust toolchain is available (found). - - Please see Documentation/rust/quick-start.rst for instructions on how - to satisfy the build requirements of Rust support. - - In particular, the Makefile target 'rustavailable' is useful to check - why the Rust toolchain is not being detected. - -config RUSTC_LLVM_VERSION - int - default $(rustc-llvm-version) - -config RUSTC_LLVM_MAJOR_VERSION - int - default $(shell,expr $(rustc-llvm-version) / 10000) -config RUSTC_CLANG_LLVM_COMPATIBLE - bool - default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000) - help - This indicates whether Rust and Clang use LLVM of the same major - version. - - Operations involving handling LLVM IR or bitcode (e.g. cross-language - LTO) require the same LLVM major version to work properly. For best - compatibility it is recommended that the exact same LLVM is used. - -config ARCH_HAS_CC_CAN_LINK - bool - -config CC_CAN_LINK - bool - default ARCH_CC_CAN_LINK if ARCH_HAS_CC_CAN_LINK - default $(cc_can_link_user,$(m64-flag)) if 64BIT - default $(cc_can_link_user,$(m32-flag)) - -# Fixed in GCC 14, 13.3, 12.4 and 11.5 -# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 -config GCC_ASM_GOTO_OUTPUT_BROKEN - bool - depends on CC_IS_GCC - default y if GCC_VERSION < 110500 - default y if GCC_VERSION >= 120000 && GCC_VERSION < 120400 - default y if GCC_VERSION >= 130000 && GCC_VERSION < 130300 - -config CC_HAS_ASM_GOTO_OUTPUT - def_bool y - depends on !GCC_ASM_GOTO_OUTPUT_BROKEN - depends on $(success,echo 'int foo(int x) { asm goto ("": "=r"(x) ::: bar); return x; bar: return 0; }' | $(CC) -x c - -c -o /dev/null) - -config CC_HAS_ASM_GOTO_TIED_OUTPUT - depends on CC_HAS_ASM_GOTO_OUTPUT - # Detect buggy gcc and clang, fixed in gcc-11 clang-14. - def_bool $(success,echo 'int foo(int *x) { asm goto (".long (%l[bar]) - .": "+m"(*x) ::: bar); return *x; bar: return 0; }' | $CC -x c - -c -o /dev/null) - -config TOOLS_SUPPORT_RELR - def_bool $(success,env "CC=$(CC)" "LD=$(LD)" "NM=$(NM)" "OBJCOPY=$(OBJCOPY)" $(srctree)/scripts/tools-support-relr.sh) - -config CC_HAS_ASM_INLINE - def_bool $(success,echo 'void foo(void) { asm inline (""); }' | $(CC) -x c - -c -o /dev/null) - -config CC_HAS_ASSUME - bool - # clang needs to be at least 19.1.0 since the meaning of the assume - # attribute changed: - # https://github.com/llvm/llvm-project/commit/c44fa3e8a9a44c2e9a575768a3c185354b9f6c17 - default y if CC_IS_CLANG && CLANG_VERSION >= 190100 - # supported since gcc 13.1.0 - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106654 - default y if CC_IS_GCC && GCC_VERSION >= 130100 - -config CC_HAS_NO_PROFILE_FN_ATTR - def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) - -config CC_HAS_COUNTED_BY - bool - # clang needs to be at least 20.1.0 to avoid potential crashes - # when building structures that contain __counted_by - # https://github.com/ClangBuiltLinux/linux/issues/2114 - # https://github.com/llvm/llvm-project/commit/160fb1121cdf703c3ef5e61fb26c5659eb581489 - default y if CC_IS_CLANG && CLANG_VERSION >= 200100 - # supported since gcc 15.1.0 - # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 - default y if CC_IS_GCC && GCC_VERSION >= 150100 - -config CC_HAS_COUNTED_BY_PTR - bool - # supported since clang 22 - default y if CC_IS_CLANG && CLANG_VERSION >= 220100 - # supported since gcc 16.0.0 - default y if CC_IS_GCC && GCC_VERSION >= 160000 - -config CC_HAS_BROKEN_COUNTED_BY_REF - bool - # https://github.com/llvm/llvm-project/issues/182575 - default y if CC_IS_CLANG && CLANG_VERSION < 220100 - -config CC_HAS_ALLOC_TOKEN - def_bool $(cc-option,-falloc-token-max=123) - -config CC_HAS_MULTIDIMENSIONAL_NONSTRING - def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) - -config LD_CAN_USE_KEEP_IN_OVERLAY - # ld.lld prior to 21.0.0 did not support KEEP within an overlay description - # https://github.com/llvm/llvm-project/pull/130661 - def_bool LD_IS_BFD || LLD_VERSION >= 210000 - -config RUSTC_HAS_SPAN_FILE - def_bool RUSTC_VERSION >= 108800 - -config RUSTC_HAS_UNNECESSARY_TRANSMUTES - def_bool RUSTC_VERSION >= 108800 - -config RUSTC_HAS_FILE_WITH_NUL - def_bool RUSTC_VERSION >= 108900 - -config RUSTC_HAS_FILE_AS_C_STR - def_bool RUSTC_VERSION >= 109100 - -config RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS - def_bool RUSTC_VERSION >= 109800 - -config PAHOLE_VERSION - int - default "$(PAHOLE_VERSION)" +source "scripts/Kconfig.toolchain" config CONSTRUCTORS bool diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain new file mode 100644 index 00000000000000..fac36246451f62 --- /dev/null +++ b/scripts/Kconfig.toolchain @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: GPL-2.0-only +config CC_VERSION_TEXT + string + default "$(CC_VERSION_TEXT)" + help + This is used in unclear ways: + + - Re-run Kconfig when the compiler is updated + The 'default' property references the environment variable, + CC_VERSION_TEXT so it is recorded in include/config/auto.conf.cmd. + When the compiler is updated, Kconfig will be invoked. + + - Ensure full rebuild when the compiler is updated + include/linux/compiler-version.h contains this option in the comment + line so fixdep adds include/config/CC_VERSION_TEXT into the + auto-generated dependency. When the compiler is updated, syncconfig + will touch it and then every file will be rebuilt. + +config CC_IS_GCC + def_bool $(success,test "$(cc-name)" = GCC) + +config GCC_VERSION + int + default $(cc-version) if CC_IS_GCC + default 0 + +config CC_IS_CLANG + def_bool $(success,test "$(cc-name)" = Clang) + +config CLANG_VERSION + int + default $(cc-version) if CC_IS_CLANG + default 0 + +config AS_IS_GNU + def_bool $(success,test "$(as-name)" = GNU) + +config AS_IS_LLVM + def_bool $(success,test "$(as-name)" = LLVM) + +config AS_VERSION + int + # Use clang version if this is the integrated assembler + default CLANG_VERSION if AS_IS_LLVM + default $(as-version) + +config LD_IS_BFD + def_bool $(success,test "$(ld-name)" = BFD) + +config LD_VERSION + int + default $(ld-version) if LD_IS_BFD + default 0 + +config LD_IS_LLD + def_bool $(success,test "$(ld-name)" = LLD) + +config LLD_VERSION + int + default $(ld-version) if LD_IS_LLD + default 0 + +config RUSTC_VERSION + int + default $(rustc-version) + help + It does not depend on `RUST` since that one may need to use the version + in a `depends on`. + +config RUST_IS_AVAILABLE + def_bool $(success,$(srctree)/scripts/rust_is_available.sh) + help + This shows whether a suitable Rust toolchain is available (found). + + Please see Documentation/rust/quick-start.rst for instructions on how + to satisfy the build requirements of Rust support. + + In particular, the Makefile target 'rustavailable' is useful to check + why the Rust toolchain is not being detected. + +config RUSTC_LLVM_VERSION + int + default $(rustc-llvm-version) + +config RUSTC_LLVM_MAJOR_VERSION + int + default $(shell,expr $(rustc-llvm-version) / 10000) + +config RUSTC_CLANG_LLVM_COMPATIBLE + bool + default y if CC_IS_CLANG && RUSTC_LLVM_MAJOR_VERSION = $(shell,expr $(cc-version) / 10000) + help + This indicates whether Rust and Clang use LLVM of the same major + version. + + Operations involving handling LLVM IR or bitcode (e.g. cross-language + LTO) require the same LLVM major version to work properly. For best + compatibility it is recommended that the exact same LLVM is used. + +config ARCH_HAS_CC_CAN_LINK + bool + +config CC_CAN_LINK + bool + default ARCH_CC_CAN_LINK if ARCH_HAS_CC_CAN_LINK + default $(cc_can_link_user,$(m64-flag)) if 64BIT + default $(cc_can_link_user,$(m32-flag)) + +# Fixed in GCC 14, 13.3, 12.4 and 11.5 +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113921 +config GCC_ASM_GOTO_OUTPUT_BROKEN + bool + depends on CC_IS_GCC + default y if GCC_VERSION < 110500 + default y if GCC_VERSION >= 120000 && GCC_VERSION < 120400 + default y if GCC_VERSION >= 130000 && GCC_VERSION < 130300 + +config CC_HAS_ASM_GOTO_OUTPUT + def_bool y + depends on !GCC_ASM_GOTO_OUTPUT_BROKEN + depends on $(success,echo 'int foo(int x) { asm goto ("": "=r"(x) ::: bar); return x; bar: return 0; }' | $(CC) -x c - -c -o /dev/null) + +config CC_HAS_ASM_GOTO_TIED_OUTPUT + depends on CC_HAS_ASM_GOTO_OUTPUT + # Detect buggy gcc and clang, fixed in gcc-11 clang-14. + def_bool $(success,echo 'int foo(int *x) { asm goto (".long (%l[bar]) - .": "+m"(*x) ::: bar); return *x; bar: return 0; }' | $CC -x c - -c -o /dev/null) + +config TOOLS_SUPPORT_RELR + def_bool $(success,env "CC=$(CC)" "LD=$(LD)" "NM=$(NM)" "OBJCOPY=$(OBJCOPY)" $(srctree)/scripts/tools-support-relr.sh) + +config CC_HAS_ASM_INLINE + def_bool $(success,echo 'void foo(void) { asm inline (""); }' | $(CC) -x c - -c -o /dev/null) + +config CC_HAS_ASSUME + bool + # clang needs to be at least 19.1.0 since the meaning of the assume + # attribute changed: + # https://github.com/llvm/llvm-project/commit/c44fa3e8a9a44c2e9a575768a3c185354b9f6c17 + default y if CC_IS_CLANG && CLANG_VERSION >= 190100 + # supported since gcc 13.1.0 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106654 + default y if CC_IS_GCC && GCC_VERSION >= 130100 + +config CC_HAS_NO_PROFILE_FN_ATTR + def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) + +config CC_HAS_COUNTED_BY + bool + # clang needs to be at least 20.1.0 to avoid potential crashes + # when building structures that contain __counted_by + # https://github.com/ClangBuiltLinux/linux/issues/2114 + # https://github.com/llvm/llvm-project/commit/160fb1121cdf703c3ef5e61fb26c5659eb581489 + default y if CC_IS_CLANG && CLANG_VERSION >= 200100 + # supported since gcc 15.1.0 + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 + default y if CC_IS_GCC && GCC_VERSION >= 150100 + +config CC_HAS_COUNTED_BY_PTR + bool + # supported since clang 22 + default y if CC_IS_CLANG && CLANG_VERSION >= 220100 + # supported since gcc 16.0.0 + default y if CC_IS_GCC && GCC_VERSION >= 160000 + +config CC_HAS_BROKEN_COUNTED_BY_REF + bool + # https://github.com/llvm/llvm-project/issues/182575 + default y if CC_IS_CLANG && CLANG_VERSION < 220100 + +config CC_HAS_ALLOC_TOKEN + def_bool $(cc-option,-falloc-token-max=123) + +config CC_HAS_MULTIDIMENSIONAL_NONSTRING + def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) + +config LD_CAN_USE_KEEP_IN_OVERLAY + # ld.lld prior to 21.0.0 did not support KEEP within an overlay description + # https://github.com/llvm/llvm-project/pull/130661 + def_bool LD_IS_BFD || LLD_VERSION >= 210000 + +config RUSTC_HAS_SPAN_FILE + def_bool RUSTC_VERSION >= 108800 + +config RUSTC_HAS_UNNECESSARY_TRANSMUTES + def_bool RUSTC_VERSION >= 108800 + +config RUSTC_HAS_FILE_WITH_NUL + def_bool RUSTC_VERSION >= 108900 + +config RUSTC_HAS_FILE_AS_C_STR + def_bool RUSTC_VERSION >= 109100 + +config RUSTC_HAS_SUSPICIOUS_RUNTIME_SYMBOL_DEFINITIONS + def_bool RUSTC_VERSION >= 109800 + +config PAHOLE_VERSION + int + default "$(PAHOLE_VERSION)" From 454e00cf8ee5aff555ad5865b8c3dbf3731afcc9 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:01 +0100 Subject: [PATCH 0947/2286] kbuild: avoid re-running compiler and linker probes Each kernel make invocation begins with ~30 compiler and linker runs each of which performs duplicate probe for a number of compiler and linker options. This is useless work - the compiler and its version is known, so use these to determine which options are available, once. Using the newly introduced cc-option-str, track the result of each probe in CC_OPT_*, LD_OPT_* variables and have Makefile's simply append the symbol. Some care has to be taken around negative options, i.e. -Wno-xxx, as gcc accepts an unknown -Wno- without complaint and only complains later, so instead probe the positive form for these cases. Use CC_HAS_* For those probes that gate a Makefile conditional rather than a simple option, as per the established pattern for this. This change avoids a subtle issue with ppc32 (big endian) - clang doesn't accept -fno-stack-clash-protection on this sub-target, so keep this option in the Makefile. With gcc and clang, defconfig and allmodconfig, the recorded command lines are unchanged, a build with nothing to do rebuilds nothing and W=1 continues to function correctly. Doing this improves all builds, but has a particularly positive impact on no-op builds (builds where nothing has changed). Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, no-op make, gcc 0.78s 0.60s -0.18s (-23%) x86 defconfig, no-op make, clang 0.94s 0.66s -0.28s (-30%) x86 defconfig, touch mm/vma.c, gcc 8.6s 8.5s -0.16s (-2%) x86 defconfig, touch mm/vma.c, clang 8.0s 7.7s -0.24s (-3%) x86 defconfig, clean, clang 29.8s 29.4s -0.37s (-1%) x86 allmodconfig, no-op make, gcc 12.9s 12.7s -0.23s (-2%) x86 allmodconfig, no-op make, clang 14.2s 13.9s -0.29s (-2%) x86 allmodconfig, touch mm/vma.c, clang 32.2s 31.9s -0.30s (-1%) Assisted-by: LLM Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-12-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 23 +++++---- arch/arm64/kernel/pi/Makefile | 2 +- arch/riscv/kernel/pi/Makefile | 2 +- arch/x86/Kconfig | 17 ++++++ arch/x86/Makefile | 12 ++--- drivers/firmware/efi/libstub/Makefile | 2 +- scripts/Kconfig.toolchain | 74 +++++++++++++++++++++++++++ scripts/Makefile.warn | 28 +++++----- 8 files changed, 126 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index ab3ba78376f709..fab2bf3759a70c 100644 --- a/Makefile +++ b/Makefile @@ -946,8 +946,8 @@ KBUILD_RUSTFLAGS += -Coverflow-checks=$(if $(CONFIG_RUST_OVERFLOW_CHECKS),y,n) ifdef CONFIG_CC_IS_GCC # gcc-10 renamed --param=allow-store-data-races=0 to # -fno-allow-store-data-races. -KBUILD_CFLAGS += $(call cc-option,--param=allow-store-data-races=0) -KBUILD_CFLAGS += $(call cc-option,-fno-allow-store-data-races) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_ALLOW_STORE_DATA_RACES_PARAM) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_NO_ALLOW_STORE_DATA_RACES) endif ifdef CONFIG_READABLE_ASM @@ -1011,18 +1011,20 @@ endif endif # Explicitly clear padding bits during variable initialization -KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_ZERO_INIT_PADDING_BITS) # While VLAs have been removed, GCC produces unreachable stack probes # for the randomize_kstack_offset feature. Disable it for all compilers. +# Probed here rather than in Kconfig as clang only accepts it for some +# sub-targets, so the architecture's flags matter. KBUILD_CFLAGS += $(call cc-option, -fno-stack-clash-protection) # Get details on warnings generated due to GCC value tracking. -KBUILD_CFLAGS += $(call cc-option, -fdiagnostics-show-context=2) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_DIAGNOSTICS_SHOW_CONTEXT) # Show inlining notes for __attribute__((warning/error)) call chains. # GCC supports this unconditionally while Clang 23+ provides a flag. -KBUILD_CFLAGS += $(call cc-option, -fdiagnostics-show-inlining-chain) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_DIAGNOSTICS_SHOW_INLINING_CHAIN) # Clear used registers at func exit (to reduce data lifetime and ROP gadgets). ifdef CONFIG_ZERO_CALL_USED_REGS @@ -1033,7 +1035,7 @@ ifdef CONFIG_FUNCTION_TRACER ifdef CONFIG_FTRACE_MCOUNT_USE_CC CC_FLAGS_FTRACE += -mrecord-mcount ifdef CONFIG_HAVE_NOP_MCOUNT - ifeq ($(call cc-option-yn, -mnop-mcount),y) + ifdef CONFIG_CC_HAS_MNOP_MCOUNT CC_FLAGS_FTRACE += -mnop-mcount CC_FLAGS_USING += -DCC_USING_NOP_MCOUNT endif @@ -1051,8 +1053,7 @@ ifdef CONFIG_FTRACE_MCOUNT_USE_RECORDMCOUNT endif endif ifdef CONFIG_HAVE_FENTRY - # s390-linux-gnu-gcc did not support -mfentry until gcc-9. - ifeq ($(call cc-option-yn, -mfentry),y) + ifdef CONFIG_CC_HAS_MFENTRY CC_FLAGS_FTRACE += -mfentry CC_FLAGS_USING += -DCC_USING_FENTRY endif @@ -1160,7 +1161,7 @@ NOSTDINC_FLAGS += -nostdinc # the kernel uses only C99 flexible arrays for dynamically sized trailing # arrays. Enforce this for everything that may examine structure sizes and # perform bounds checking. -KBUILD_CFLAGS += $(call cc-option, -fstrict-flex-arrays=3) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_STRICT_FLEX_ARRAYS) # disable invalid "can't wrap" optimizations for signed / pointers KBUILD_CFLAGS += -fno-strict-overflow @@ -1243,11 +1244,11 @@ LDFLAGS_vmlinux += --build-id=sha1 # COMDAT-deduplicated sections. Use --force-group-allocation to resolve these # groups when linking modules. The option is available from ld.bfd 2.29 and # ld.lld 19.1.0. -KBUILD_LDFLAGS_MODULE += $(call ld-option,--force-group-allocation) +KBUILD_LDFLAGS_MODULE += $(CONFIG_LD_OPT_FORCE_GROUP_ALLOCATION) KBUILD_LDFLAGS += -z noexecstack ifeq ($(CONFIG_LD_IS_BFD),y) -KBUILD_LDFLAGS += $(call ld-option,--no-warn-rwx-segments) +KBUILD_LDFLAGS += $(CONFIG_LD_OPT_NO_WARN_RWX_SEGMENTS) endif ifeq ($(CONFIG_STRIP_ASM_SYMS),y) diff --git a/arch/arm64/kernel/pi/Makefile b/arch/arm64/kernel/pi/Makefile index be92d73c25b219..4a0cbedfdcd695 100644 --- a/arch/arm64/kernel/pi/Makefile +++ b/arch/arm64/kernel/pi/Makefile @@ -9,7 +9,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) -fpie \ -include $(srctree)/include/linux/hidden.h \ -D__DISABLE_EXPORTS -ffreestanding -D__NO_FORTIFY \ -fno-asynchronous-unwind-tables -fno-unwind-tables \ - $(call cc-option,-fno-addrsig) + $(CONFIG_CC_OPT_NO_ADDRSIG) # this code may run with the MMU off so disable unaligned accesses CFLAGS_map_range.o += -mstrict-align diff --git a/arch/riscv/kernel/pi/Makefile b/arch/riscv/kernel/pi/Makefile index bc098edac89813..4c8e52b3f17602 100644 --- a/arch/riscv/kernel/pi/Makefile +++ b/arch/riscv/kernel/pi/Makefile @@ -8,7 +8,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) -fpie \ -include $(srctree)/include/linux/hidden.h \ -D__DISABLE_EXPORTS -ffreestanding \ -fno-asynchronous-unwind-tables -fno-unwind-tables \ - $(call cc-option,-fno-addrsig) + $(CONFIG_CC_OPT_NO_ADDRSIG) # Disable LTO KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS)) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 15fd9ec5ecacb7..0fea2fc4ba1d5f 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2364,6 +2364,23 @@ config CC_HAS_KCFI_ARITY def_bool $(cc-option,-fsanitize=kcfi -fsanitize-kcfi-arity) depends on CC_IS_CLANG && !RUST +config CC_OPT_INDIRECT_BRANCH_CS_PREFIX + def_string "$(cc-option-str,-mindirect-branch-cs-prefix)" + +config CC_OPT_CF_PROTECTION_NONE + def_string "$(cc-option-str,-fcf-protection=none)" + +# with jump tables off: the compilers use NOTRACK for them, which kernel IBT +# does not allow +config CC_OPT_CF_PROTECTION_BRANCH + def_string "$(cc-option-str,-fcf-protection=branch -fno-jump-tables)" + +config CC_OPT_FALIGN_JUMPS + def_string "$(cc-option-str,-falign-jumps=1)" + +config CC_OPT_FALIGN_LOOPS + def_string "$(cc-option-str,-falign-loops=1)" + config FUNCTION_PADDING_CFI int default 59 if FUNCTION_ALIGNMENT_64B diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 598f178102ee4a..aac30ba51b2ff3 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -20,7 +20,7 @@ ifdef CONFIG_CC_IS_CLANG RETPOLINE_CFLAGS := -mretpoline-external-thunk RETPOLINE_VDSO_CFLAGS := -mretpoline endif -RETPOLINE_CFLAGS += $(call cc-option,-mindirect-branch-cs-prefix) +RETPOLINE_CFLAGS += $(CONFIG_CC_OPT_INDIRECT_BRANCH_CS_PREFIX) ifdef CONFIG_MITIGATION_RETHUNK RETHUNK_CFLAGS := -mfunction-return=thunk-extern @@ -52,7 +52,7 @@ REALMODE_CFLAGS := $(CC_FLAGS_DIALECT) -m16 -g -Os \ -DDISABLE_BRANCH_PROFILING -D__DISABLE_EXPORTS \ -Wall -Wstrict-prototypes -march=i386 -mregparm=3 \ -fno-strict-aliasing -fomit-frame-pointer -fno-pic \ - -mno-mmx -mno-sse $(call cc-option,-fcf-protection=none) + -mno-mmx -mno-sse $(CONFIG_CC_OPT_CF_PROTECTION_NONE) REALMODE_CFLAGS += -ffreestanding REALMODE_CFLAGS += -fno-stack-protector @@ -99,10 +99,10 @@ ifeq ($(CONFIG_X86_KERNEL_IBT),y) # # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104816 # -KBUILD_CFLAGS += $(call cc-option,-fcf-protection=branch -fno-jump-tables) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_CF_PROTECTION_BRANCH) KBUILD_RUSTFLAGS += -Zcf-protection=branch $(if $(call rustc-min-version,109300),-Cjump-tables=n,-Zno-jump-tables) else -KBUILD_CFLAGS += $(call cc-option,-fcf-protection=none) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_CF_PROTECTION_NONE) endif ifeq ($(CONFIG_X86_32),y) @@ -138,10 +138,10 @@ else KBUILD_CFLAGS += -m64 # Align jump targets to 1 byte, not the default 16 bytes: - KBUILD_CFLAGS += $(call cc-option,-falign-jumps=1) + KBUILD_CFLAGS += $(CONFIG_CC_OPT_FALIGN_JUMPS) # Pack loops tightly as well: - KBUILD_CFLAGS += $(call cc-option,-falign-loops=1) + KBUILD_CFLAGS += $(CONFIG_CC_OPT_FALIGN_LOOPS) # Don't autogenerate traditional x87 instructions KBUILD_CFLAGS += -mno-80387 diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 77a2b2d74f3f62..415f63105b481b 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -40,7 +40,7 @@ KBUILD_CFLAGS := $(subst $(CC_FLAGS_FTRACE),,$(cflags-y)) \ -D__NO_FORTIFY \ -ffreestanding \ -fno-stack-protector \ - $(call cc-option,-fno-addrsig) \ + $(CONFIG_CC_OPT_NO_ADDRSIG) \ -D__DISABLE_EXPORTS # diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index fac36246451f62..d708a175fe4866 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -144,6 +144,80 @@ config CC_HAS_ASSUME config CC_HAS_NO_PROFILE_FN_ATTR def_bool $(success,echo '__attribute__((no_profile_instrument_function)) int x();' | $(CC) -x c - -c -o /dev/null -Werror) +config CC_OPT_ZERO_INIT_PADDING_BITS + def_string "$(cc-option-str,-fzero-init-padding-bits=all)" + +config CC_OPT_NO_ADDRSIG + def_string "$(cc-option-str,-fno-addrsig)" + +config CC_OPT_DIAGNOSTICS_SHOW_CONTEXT + def_string "$(cc-option-str,-fdiagnostics-show-context=2)" + +config CC_OPT_DIAGNOSTICS_SHOW_INLINING_CHAIN + def_string "$(cc-option-str,-fdiagnostics-show-inlining-chain)" + +config CC_OPT_STRICT_FLEX_ARRAYS + def_string "$(cc-option-str,-fstrict-flex-arrays=3)" + +# gcc-10 renamed --param=allow-store-data-races=0 to -fno-allow-store-data-races +config CC_OPT_ALLOW_STORE_DATA_RACES_PARAM + def_string "$(cc-option-str,--param=allow-store-data-races=0)" if CC_IS_GCC + +config CC_OPT_NO_ALLOW_STORE_DATA_RACES + def_string "$(cc-option-str,-fno-allow-store-data-races)" if CC_IS_GCC + +config CC_HAS_MNOP_MCOUNT + def_bool $(cc-option,-mnop-mcount) + +# s390-linux-gnu-gcc did not support -mfentry until gcc-9. +config CC_HAS_MFENTRY + def_bool $(cc-option,-mfentry) + +config LD_OPT_FORCE_GROUP_ALLOCATION + def_string "$(ld-option-str,--force-group-allocation)" + +config LD_OPT_NO_WARN_RWX_SEGMENTS + def_string "$(ld-option-str,--no-warn-rwx-segments)" + +config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER + def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) + +config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF + def_string "-Wno-format-overflow-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-overflow-non-kprintf) + +config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF + def_string "-Wno-format-truncation-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-truncation-non-kprintf) + +config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE + def_string "-Wno-default-const-init-unsafe" if CC_IS_CLANG && $(cc-option,-Wdefault-const-init-unsafe) + +config CC_OPT_WNO_DANGLING_POINTER + def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) + +config CC_OPT_WVLA_LARGER_THAN + def_string "$(cc-option-str,-Wvla-larger-than=1)" + +config CC_HAS_WSTRINGOP_OVERFLOW + def_bool $(cc-option,-Wstringop-overflow) + +config CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION + def_string "-Wno-unterminated-string-initialization" if $(cc-option,-Wunterminated-string-initialization) + +config CC_OPT_WERROR_DESIGNATED_INIT + def_string "$(cc-option-str,-Werror=designated-init)" + +config CC_OPT_WENUM_CONVERSION + def_string "$(cc-option-str,-Wenum-conversion)" + +config CC_OPT_WNO_PACKED_NOT_ALIGNED + def_string "-Wno-packed-not-aligned" if $(cc-option,-Wpacked-not-aligned) + +config CC_OPT_WNO_FORMAT_OVERFLOW + def_string "-Wno-format-overflow" if $(cc-option,-Wformat-overflow) + +config CC_OPT_WNO_STRINGOP_TRUNCATION + def_string "-Wno-stringop-truncation" if $(cc-option,-Wstringop-truncation) + config CC_HAS_COUNTED_BY bool # clang needs to be at least 20.1.0 to avoid potential crashes diff --git a/scripts/Makefile.warn b/scripts/Makefile.warn index 35af7d6c6d1832..556fa249ca071a 100644 --- a/scripts/Makefile.warn +++ b/scripts/Makefile.warn @@ -17,7 +17,7 @@ KBUILD_CFLAGS += -Werror=strict-prototypes KBUILD_CFLAGS += -Wno-format-security KBUILD_CFLAGS += -Wno-trigraphs KBUILD_CFLAGS += -Wno-frame-address -KBUILD_CFLAGS += $(call cc-option, -Wno-address-of-packed-member) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER) KBUILD_CFLAGS += -Wmissing-declarations KBUILD_CFLAGS += -Wmissing-prototypes @@ -30,8 +30,8 @@ KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds ifdef CONFIG_CC_IS_CLANG # Clang checks for overflow/truncation with '%p', while GCC does not: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111219 -KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow-non-kprintf) -KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF) # Clang may emit a warning when a const variable, such as the dummy variables # in typecheck(), or const member of an aggregate type are not initialized, @@ -43,7 +43,7 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-format-truncation-non-kprintf) # disabled with this same switch, there should not be too much coverage lost # because -Wuninitialized will still flag when an uninitialized const variable # is used. -KBUILD_CFLAGS += $(call cc-option, -Wno-default-const-init-unsafe) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE) else # gcc inanely warns about local variables called 'main' @@ -54,7 +54,7 @@ endif KBUILD_CFLAGS += -Wno-type-limits # These result in bogus false positives -KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_DANGLING_POINTER) # Stack Variable Length Arrays (VLAs) must not be used in the kernel. # Function array parameters should, however, be usable, but -Wvla will @@ -62,7 +62,7 @@ KBUILD_CFLAGS += $(call cc-option, -Wno-dangling-pointer) # types, so depend on GCC for now to keep stack VLAs out of the tree. # https://github.com/llvm/llvm-project/issues/57098 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98217 -KBUILD_CFLAGS += $(call cc-option,-Wvla-larger-than=1) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WVLA_LARGER_THAN) # disable pointer signed / unsigned warnings in gcc 4.0 KBUILD_CFLAGS += -Wno-pointer-sign @@ -73,11 +73,11 @@ KBUILD_CFLAGS += -Wno-pointer-sign KBUILD_CFLAGS += -Wcast-function-type # Currently, disable -Wstringop-overflow for GCC 11, globally. -KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(call cc-option, -Wno-stringop-overflow) -KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(call cc-option, -Wstringop-overflow) +KBUILD_CFLAGS-$(CONFIG_CC_NO_STRINGOP_OVERFLOW) += $(if $(CONFIG_CC_HAS_WSTRINGOP_OVERFLOW),-Wno-stringop-overflow) +KBUILD_CFLAGS-$(CONFIG_CC_STRINGOP_OVERFLOW) += $(if $(CONFIG_CC_HAS_WSTRINGOP_OVERFLOW),-Wstringop-overflow) # Currently, disable -Wunterminated-string-initialization as broken -KBUILD_CFLAGS += $(call cc-option, -Wno-unterminated-string-initialization) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION) # The allocators already balk at large sizes, so silence the compiler # warnings for bounds checks involving those possible values. While @@ -100,10 +100,10 @@ KBUILD_CFLAGS += -Werror=date-time KBUILD_CFLAGS += -Werror=incompatible-pointer-types # Require designated initializers for all marked structures -KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WERROR_DESIGNATED_INIT) # Warn if there is an enum types mismatch -KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WENUM_CONVERSION) KBUILD_CFLAGS += -Wunused @@ -125,12 +125,12 @@ else # Suppress them by using -Wno... except for W=1. KBUILD_CFLAGS += -Wno-unused-but-set-variable KBUILD_CFLAGS += -Wno-unused-const-variable -KBUILD_CFLAGS += $(call cc-option, -Wno-packed-not-aligned) -KBUILD_CFLAGS += $(call cc-option, -Wno-format-overflow) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_PACKED_NOT_ALIGNED) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_FORMAT_OVERFLOW) ifdef CONFIG_CC_IS_GCC KBUILD_CFLAGS += -Wno-format-truncation endif -KBUILD_CFLAGS += $(call cc-option, -Wno-stringop-truncation) +KBUILD_CFLAGS += $(CONFIG_CC_OPT_WNO_STRINGOP_TRUNCATION) KBUILD_CFLAGS += -Wno-override-init # alias for -Wno-initializer-overrides in clang From 48ebe1cbbeb51879ba4d22775fee1654d94d1cc6 Mon Sep 17 00:00:00 2001 From: "Lorenzo Stoakes (ARM)" Date: Wed, 23 Sep 2026 18:18:11 +0100 Subject: [PATCH 0948/2286] kbuild: compress the kernel with pigz if available The gzip step of a kernel build is very lengthy, especially for larger builds such as allmodconfig. gzip itself cannot be run in parallel, however an alternative tool exists that can, providing the same feature set as gzip itself - pigz - which works as a drop-in replacement. On a 128-core Threadripper, gzip -9 of a 36 MiB x86-64 vmlinux.bin takes 1.6s, and with pigz it takes 0.09s, so the performance increase is significant. Therefore introduce the KPGZIP build variable which specifies the compressor to use for the kernel image and default it to pigz if available, or KGZIP otherwise (which itself defaults to gzip). Modules, packaging and the embedded kernel configuration (CONFIG_IKCONFIG) continue to use KGZIP which defaults to a single-threaded gzip instance. Module compression is run in parallel by make, so pigz offers no benefit there, packaging sits outside the build and the embedded configuration is too small to matter. pigz is executed using scripts/jobserver-exec, which reserves the job slots make has free - necessary, as otherwise pigz would just grab unbounded CPUs. The kernel image compression step is always performed at the end of the build so should typically have all job slots available. pigz always provides byte-for-byte identical output across runs regardless of thread count, but pigz and gzip are not guaranteed to generate the same binary output, so reproducible builds must use the same tools consistently. Also update Documentation/kbuild/kbuild.rst to describe KPGZIP and Documentation/process/changes.rst to list the optional pigz dependency. While there, add entries for the other compressor programs. Finally, update scripts/ver_linux to include the pigz version there. Every x86 build ends with the compression of vmlinux.bin, 36MB for defconfig and over 200MB for allmodconfig, no-op builds are unchanged. Whole build, 128-thread Threadripper 9980X, best of N runs: before after delta ------------------------------- x86 defconfig, touch mm/vma.c, gcc 7.7s 6.0s -1.7s (-22%) x86 defconfig, touch mm/vma.c, clang 6.9s 5.4s -1.5s (-21%) x86 defconfig, clean, gcc 28.2s 26.3s -1.8s (-6%) x86 defconfig, clean, clang 28.7s 27.2s -1.5s (-5%) x86 allmodconfig, touch mm/vma.c, gcc 23.7s 15.4s -8.3s (-35%) x86 allmodconfig, touch mm/vma.c, clang 22.5s 15.6s -7.0s (-31%) x86 allmodconfig, clean, gcc 294.1s 278.6s -15.5s (-5%) x86 allmodconfig, clean, clang 283.9s 266.8s -17.1s (-6%) Suggested-by: Kees Cook Link: https://zlib.net/pigz/ Assisted-by: LLM Reviewed-by: Manuel Ebner Reviewed-by: Kees Cook Signed-off-by: Lorenzo Stoakes (ARM) Link: https://patch.msgid.link/20260923-build-speedup-v4-22-73128809a4a4@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/kbuild.rst | 17 +++++++++++++++++ Documentation/process/changes.rst | 8 ++++++++ Makefile | 5 ++++- kernel/Makefile | 6 +++++- scripts/Makefile.lib | 4 ++-- scripts/jobserver-exec | 3 ++- scripts/ver_linux | 1 + tools/lib/python/jobserver.py | 13 +++++++++++++ 8 files changed, 52 insertions(+), 5 deletions(-) diff --git a/Documentation/kbuild/kbuild.rst b/Documentation/kbuild/kbuild.rst index 5a9013bacfb75c..e71a692a559ac2 100644 --- a/Documentation/kbuild/kbuild.rst +++ b/Documentation/kbuild/kbuild.rst @@ -110,6 +110,23 @@ HOSTLDLIBS ---------- Additional libraries to link against when building host programs. +KGZIP +----- +The gzip program used for compressed modules, packaging and the embedded +kernel configuration. Defaults to gzip. + +KPGZIP +------ +The gzip program used for the compressed kernel image. Defaults to pigz (a +parallel implementation of gzip), run under scripts/jobserver-exec so that it +uses the job slots make has free, if pigz is installed, otherwise to +``KGZIP``. Set ``KPGZIP=`` on the make command line to use another program. + +KBZIP2, KLZOP, LZMA, LZ4, XZ, ZSTD +---------------------------------- +The compressor programs for the other formats. Each defaults to the program +of the same name. + .. _userkbuildflags: USERCFLAGS diff --git a/Documentation/process/changes.rst b/Documentation/process/changes.rst index 0aa232b117b549..28f2ca63318e61 100644 --- a/Documentation/process/changes.rst +++ b/Documentation/process/changes.rst @@ -55,6 +55,7 @@ nfs-utils 1.0.5 showmount --version openssl & libcrypto 1.0.0 openssl version pahole 1.26 pahole --version pcmciautils 004 pccardctl -V +pigz (optional) 2.4 pigz --version PPP 2.4.0 pppd --version procps 3.2.0 ps --version Python 3.9.x python3 --version @@ -206,6 +207,13 @@ GNU AWK GNU AWK is needed if you want kernel builds to generate address range data for builtin modules (CONFIG_BUILTIN_MODULE_RANGES). +pigz (optional) +--------------- + +pigz is a parallel implementation of gzip. If it is installed the compressed +kernel image is produced with it rather than with gzip, see ``KPGZIP`` in +Documentation/kbuild/kbuild.rst. + System utilities **************** diff --git a/Makefile b/Makefile index fab2bf3759a70c..7f2d5cc6e5a2b0 100644 --- a/Makefile +++ b/Makefile @@ -568,6 +568,9 @@ LZMA = lzma LZ4 = lz4 XZ = xz ZSTD = zstd +# The kernel image is compressed with pigz, on the job slots make has free, +# if it is installed. Everything else uses KGZIP. +KPGZIP := $(if $(shell command -v pigz 2>/dev/null),$(PYTHON3) $(abs_srctree)/scripts/jobserver-exec pigz -p %PARALLELISM%,$(KGZIP)) TAR = tar CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ @@ -648,7 +651,7 @@ export RUSTC RUSTDOC RUSTFMT RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY BINDGEN LLVM_ export HOSTRUSTC KBUILD_HOSTRUSTFLAGS export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX -export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD TAR +export KGZIP KPGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD TAR export KBUILD_HOSTCXXFLAGS KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS KBUILD_PROCMACROLDFLAGS LDFLAGS_MODULE export KBUILD_USERCFLAGS KBUILD_USERLDFLAGS diff --git a/kernel/Makefile b/kernel/Makefile index 1e1a31673577d6..c64c82c96b400d 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -153,9 +153,13 @@ obj-$(CONFIG_SCF_TORTURE_TEST) += scftorture.o $(obj)/configs.o: $(obj)/config_data.gz +# .config is small, so it is compressed with gzip rather than the image compressor. +quiet_cmd_gzip_config = GZIP $@ + cmd_gzip_config = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ + targets += config_data config_data.gz $(obj)/config_data.gz: $(obj)/config_data FORCE - $(call if_changed,gzip) + $(call if_changed,gzip_config) filechk_cat = cat $< diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 0a4fdd8bd975de..19e96b30a122f7 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -333,11 +333,11 @@ quiet_cmd_ar = AR $@ quiet_cmd_objcopy = OBJCOPY $@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ -# Gzip +# Gzip, for the kernel image # --------------------------------------------------------------------------- quiet_cmd_gzip = GZIP $@ - cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@ + cmd_gzip = cat $(real-prereqs) | $(KPGZIP) -n -f -9 > $@ # Bzip2 # --------------------------------------------------------------------------- diff --git a/scripts/jobserver-exec b/scripts/jobserver-exec index 21b319e6c9a5f5..89119acb1b9e22 100755 --- a/scripts/jobserver-exec +++ b/scripts/jobserver-exec @@ -4,7 +4,8 @@ """ Determines how many parallel tasks "make" is expecting, as it is not exposed via any special variables, reserves them all, runs a subprocess -with PARALLELISM environment variable set, and releases the jobs back again. +with PARALLELISM environment variable set and any %PARALLELISM% in its +arguments replaced by the count, and releases the jobs back again. See: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver diff --git a/scripts/ver_linux b/scripts/ver_linux index 00bdaf30d590d5..e06d7c650710c7 100755 --- a/scripts/ver_linux +++ b/scripts/ver_linux @@ -57,6 +57,7 @@ BEGIN { printversion("pahole", version("pahole --version")) printversion("Pcmcia-cs", version("cardmgr -V")) printversion("pcmciautils", version("pccardctl -V")) + printversion("pigz", version("pigz --version")) printversion("PPP", version("pppd --version")) printversion("procps", version("ps --version")) printversion("Python", version("python3 -V")) diff --git a/tools/lib/python/jobserver.py b/tools/lib/python/jobserver.py index 0b1ffdf9f7a35e..7e94635bf7d94a 100755 --- a/tools/lib/python/jobserver.py +++ b/tools/lib/python/jobserver.py @@ -29,6 +29,10 @@ The end goal here is to keep the total number of build tasks under the limit established by the initial ``make -j$n_proc`` call. +A command that takes its thread count on the command line rather than from +the environment can use the ``%PARALLELISM%`` token in its arguments, which +is replaced by the count, or by 1 when there is no jobserver. + See: https://www.gnu.org/software/make/manual/html_node/POSIX-Jobserver.html#POSIX-Jobserver """ @@ -38,6 +42,8 @@ import subprocess import sys +PARALLELISM_TOKEN = "%PARALLELISM%" + def warn(text, *args): print(f'WARNING: {text}', *args, file = sys.stderr) @@ -182,6 +188,9 @@ def run(self, cmd, *args, **pwargs): Run a command setting PARALLELISM env variable to the number of available job slots (claim) + 1, e.g. it will reserve claim slots to do the actual build work, plus one to monitor its children. + + Any %PARALLELISM% in the command's arguments is replaced by the same + number, or by 1 when there is no jobserver. """ self.open() # Ensure that self.claim is set @@ -192,4 +201,8 @@ def run(self, cmd, *args, **pwargs): if self.claim: os.environ["PARALLELISM"] = str(self.claim) + if not isinstance(cmd, str): + parallelism = str(self.claim or 1) + cmd = [arg.replace(PARALLELISM_TOKEN, parallelism) for arg in cmd] + return subprocess.call(cmd, *args, **pwargs) From a17d099481bd11f0911993a8e69992269f92ad12 Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Tue, 15 Sep 2026 12:04:31 -0700 Subject: [PATCH 0949/2286] x86/resctrl: Update documented unit for the "activity" event The unit for the AET (Application Energy Telemetry) activity event[1] is nF, not F, see https://github.com/intel/Intel-PMT/blob/main/xml/CWF/OOBMSM/RMID-ENERGY/cwf_common.xml#L9 Fixes: a8848c4b43ad ("x86,fs/resctrl: Update documentation for telemetry events") Signed-off-by: Tony Luck Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Link: https://patch.msgid.link/20260915190431.18189-1-tony.luck@intel.com --- Documentation/filesystems/resctrl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst index dd4f80da419c84..b52795e03303f1 100644 --- a/Documentation/filesystems/resctrl.rst +++ b/Documentation/filesystems/resctrl.rst @@ -642,7 +642,7 @@ When monitoring is enabled all MON groups will also contain: during execution of instructions summed across all logical CPUs on a package for the current monitoring group. - "activity" also reports a floating point value (in Farads). This provides + "activity" also reports a floating point value (in nanofarads). This provides an estimate of work done independent of the frequency that the CPUs used for execution. From 67639ee98521c1592190c5c67047067a9c3a3fd5 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 25 Sep 2026 21:30:07 -0700 Subject: [PATCH 0950/2286] x86/fpu: Remove unnecessary checks for X86_FEATURE_FPU Since ab05214025ee ("x86/fpu: Remove MATH_EMULATION and related glue code"), X86_FEATURE_FPU is mandatory. If it's absent, the kernel halts execution in fpu__init_system_early_generic(). Therefore, remove unnecessary checks for X86_FEATURE_FPU that occur later in x86/fpu code. This makes struct swregs_state and the extern declarations of fpregs_soft_get and fpregs_soft_set all unused. So remove those too. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260924033405.57613-1-ebiggers@kernel.org --- arch/x86/include/asm/fpu/regset.h | 6 ++---- arch/x86/include/asm/fpu/sched.h | 1 - arch/x86/include/asm/fpu/types.h | 25 ------------------------- arch/x86/kernel/fpu/bugs.c | 4 ---- arch/x86/kernel/fpu/core.c | 13 +------------ arch/x86/kernel/fpu/init.c | 6 +----- arch/x86/kernel/fpu/regset.c | 6 ------ arch/x86/kernel/fpu/signal.c | 15 --------------- arch/x86/kernel/fpu/xstate.c | 5 ----- 9 files changed, 4 insertions(+), 77 deletions(-) diff --git a/arch/x86/include/asm/fpu/regset.h b/arch/x86/include/asm/fpu/regset.h index 697b77e96025a7..433720990f5de9 100644 --- a/arch/x86/include/asm/fpu/regset.h +++ b/arch/x86/include/asm/fpu/regset.h @@ -9,10 +9,8 @@ extern user_regset_active_fn regset_fpregs_active, regset_xregset_fpregs_active, ssp_active; -extern user_regset_get2_fn fpregs_get, xfpregs_get, fpregs_soft_get, - xstateregs_get, ssp_get; -extern user_regset_set_fn fpregs_set, xfpregs_set, fpregs_soft_set, - xstateregs_set, ssp_set; +extern user_regset_get2_fn fpregs_get, xfpregs_get, xstateregs_get, ssp_get; +extern user_regset_set_fn fpregs_set, xfpregs_set, xstateregs_set, ssp_set; /* * xstateregs_active == regset_fpregs_active. Please refer to the comment diff --git a/arch/x86/include/asm/fpu/sched.h b/arch/x86/include/asm/fpu/sched.h index 89004f4ca208da..48d74371a75a9c 100644 --- a/arch/x86/include/asm/fpu/sched.h +++ b/arch/x86/include/asm/fpu/sched.h @@ -32,7 +32,6 @@ extern void fpu_flush_thread(void); static inline void switch_fpu(struct task_struct *old, int cpu) { if (!test_tsk_thread_flag(old, TIF_NEED_FPU_LOAD) && - cpu_feature_enabled(X86_FEATURE_FPU) && !(old->flags & (PF_KTHREAD | PF_USER_WORKER))) { struct fpu *old_fpu = x86_task_fpu(old); diff --git a/arch/x86/include/asm/fpu/types.h b/arch/x86/include/asm/fpu/types.h index 93e99d2583d6f6..ea02832f7043c7 100644 --- a/arch/x86/include/asm/fpu/types.h +++ b/arch/x86/include/asm/fpu/types.h @@ -74,30 +74,6 @@ struct fxregs_state { /* Copy both mxcsr & mxcsr_flags with a single u64 memcpy: */ #define MXCSR_AND_FLAGS_SIZE sizeof(u64) -/* - * Software based FPU emulation state. This is arbitrary really, - * it matches the x87 format to make it easier to understand: - */ -struct swregs_state { - u32 cwd; - u32 swd; - u32 twd; - u32 fip; - u32 fcs; - u32 foo; - u32 fos; - /* 8*10 bytes for each FP-reg = 80 bytes: */ - u32 st_space[20]; - u8 ftop; - u8 changed; - u8 lookahead; - u8 no_update; - u8 rm; - u8 alimit; - struct math_emu_info *info; - u32 entry_eip; -}; - /* * List of XSAVE features Linux knows about: */ @@ -369,7 +345,6 @@ struct xregs_state { union fpregs_state { struct fregs_state fsave; struct fxregs_state fxsave; - struct swregs_state soft; struct xregs_state xsave; u8 __padding[PAGE_SIZE]; }; diff --git a/arch/x86/kernel/fpu/bugs.c b/arch/x86/kernel/fpu/bugs.c index edbafc5940e33b..4b84cd0a9d24f0 100644 --- a/arch/x86/kernel/fpu/bugs.c +++ b/arch/x86/kernel/fpu/bugs.c @@ -29,10 +29,6 @@ void __init fpu__init_check_bugs(void) { s32 fdiv_bug; - /* kernel_fpu_begin/end() relies on patched alternative instructions. */ - if (!boot_cpu_has(X86_FEATURE_FPU)) - return; - kernel_fpu_begin(); /* diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c index d1aeecd57f5edb..67d4db38c4cbbd 100644 --- a/arch/x86/kernel/fpu/core.c +++ b/arch/x86/kernel/fpu/core.c @@ -491,7 +491,7 @@ void kernel_fpu_begin_mask(unsigned int kfpu_mask) if (likely(kfpu_mask & KFPU_MXCSR) && boot_cpu_has(X86_FEATURE_XMM)) ldmxcsr(MXCSR_DEFAULT); - if (unlikely(kfpu_mask & KFPU_387) && boot_cpu_has(X86_FEATURE_FPU)) + if (unlikely(kfpu_mask & KFPU_387)) asm volatile ("fninit"); } EXPORT_SYMBOL_GPL(kernel_fpu_begin_mask); @@ -672,9 +672,6 @@ int fpu_clone(struct task_struct *dst, u64 clone_flags, bool minimal, fpstate_reset(dst_fpu); - if (!cpu_feature_enabled(X86_FEATURE_FPU)) - return 0; - /* * Enforce reload for user space tasks and prevent kernel threads * from trying to save the FPU registers on context switch. @@ -833,11 +830,6 @@ void fpu__clear_user_states(struct fpu *fpu) WARN_ON_FPU(fpu != x86_task_fpu(current)); fpregs_lock(); - if (!cpu_feature_enabled(X86_FEATURE_FPU)) { - fpu_reset_fpstate_regs(); - fpregs_unlock(); - return; - } /* * Ensure that current's supervisor states are loaded into their @@ -874,9 +866,6 @@ void fpu_flush_thread(void) */ void switch_fpu_return(void) { - if (!cpu_feature_enabled(X86_FEATURE_FPU)) - return; - fpregs_restore_userregs(); } EXPORT_SYMBOL_FOR_KVM(switch_fpu_return); diff --git a/arch/x86/kernel/fpu/init.c b/arch/x86/kernel/fpu/init.c index 0d33c217b71c1a..1a92ec433f9d34 100644 --- a/arch/x86/kernel/fpu/init.c +++ b/arch/x86/kernel/fpu/init.c @@ -31,8 +31,6 @@ static void fpu__init_cpu_generic(void) cr0 = read_cr0(); cr0 &= ~(X86_CR0_TS|X86_CR0_EM); /* clear TS and EM */ - if (!boot_cpu_has(X86_FEATURE_FPU)) - cr0 |= X86_CR0_EM; write_cr0(cr0); /* Flush out any pending x87 state: */ @@ -184,9 +182,7 @@ static void __init fpu__init_system_xstate_size_legacy(void) * Note that the size configuration might be overwritten later * during fpu__init_system_xstate(). */ - if (!cpu_feature_enabled(X86_FEATURE_FPU)) { - size = sizeof(struct swregs_state); - } else if (cpu_feature_enabled(X86_FEATURE_FXSR)) { + if (cpu_feature_enabled(X86_FEATURE_FXSR)) { size = sizeof(struct fxregs_state); fpu_user_cfg.legacy_features = XFEATURE_MASK_FPSSE; } else { diff --git a/arch/x86/kernel/fpu/regset.c b/arch/x86/kernel/fpu/regset.c index 0986c2200adc56..f96affd834a146 100644 --- a/arch/x86/kernel/fpu/regset.c +++ b/arch/x86/kernel/fpu/regset.c @@ -407,9 +407,6 @@ int fpregs_get(struct task_struct *target, const struct user_regset *regset, sync_fpstate(fpu); - if (!cpu_feature_enabled(X86_FEATURE_FPU)) - return fpregs_soft_get(target, regset, to); - if (!cpu_feature_enabled(X86_FEATURE_FXSR)) { return membuf_write(&to, &fpu->fpstate->regs.fsave, sizeof(struct fregs_state)); @@ -441,9 +438,6 @@ int fpregs_set(struct task_struct *target, const struct user_regset *regset, if (pos != 0 || count != sizeof(struct user_i387_ia32_struct)) return -EINVAL; - if (!cpu_feature_enabled(X86_FEATURE_FPU)) - return fpregs_soft_set(target, regset, pos, count, kbuf, ubuf); - ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &env, 0, -1); if (ret) return ret; diff --git a/arch/x86/kernel/fpu/signal.c b/arch/x86/kernel/fpu/signal.c index 594ba36dec734f..1f721ac842833a 100644 --- a/arch/x86/kernel/fpu/signal.c +++ b/arch/x86/kernel/fpu/signal.c @@ -212,14 +212,6 @@ bool copy_fpstate_to_sigframe(void __user *buf, void __user *buf_fx, int size, u ia32_fxstate &= (IS_ENABLED(CONFIG_X86_32) || IS_ENABLED(CONFIG_IA32_EMULATION)); - if (!cpu_feature_enabled(X86_FEATURE_FPU)) { - struct user_i387_ia32_struct fp; - - fpregs_soft_get(current, NULL, (struct membuf){.p = &fp, - .left = sizeof(fp)}); - return !copy_to_user(buf, &fp, sizeof(fp)); - } - if (!access_ok(buf, size)) return false; @@ -499,13 +491,6 @@ bool fpu__restore_sig(void __user *buf, int ia32_frame) if (!access_ok(buf, size)) goto out; - if (!IS_ENABLED(CONFIG_X86_64) && !cpu_feature_enabled(X86_FEATURE_FPU)) { - success = !fpregs_soft_set(current, NULL, 0, - sizeof(struct user_i387_ia32_struct), - NULL, buf); - goto out; - } - if (use_xsave()) { struct _fpx_sw_bytes fx_sw_user; diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index a2b6ede343fd1c..561eec713dfc1c 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -811,11 +811,6 @@ void __init fpu__init_system_xstate(unsigned int legacy_size) int err; int i; - if (!boot_cpu_has(X86_FEATURE_FPU)) { - pr_info("x86/fpu: No FPU detected\n"); - return; - } - if (!boot_cpu_has(X86_FEATURE_XSAVE)) { pr_info("x86/fpu: x87 FPU will use %s\n", boot_cpu_has(X86_FEATURE_FXSR) ? "FXSAVE" : "FSAVE"); From 00c81514ad49110574e4b9bdbc448551004161f2 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:15:58 -0500 Subject: [PATCH 0951/2286] tools/nolibc: fix readdir_r() with 64-bit directory offsets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit readdir_r() stores the result of _sys_lseek() in an int. Directory offsets are opaque cookies which can use all 64 bits: ext4, for instance, gives 64-bit processes 63-bit hashes, and 0x7fffffffffffffff as the offset after the last entry. Truncated to an int, such an offset is negative about half the time, and readdir_r() takes it for an error. Since commit 4ada5679f18d ("tools/nolibc/dirent: avoid errno in readdir_r"), readdir_r() fails at the first entry whose offset has bit 31 set, which on ext4 is usually one of the first few, and returns the truncated offset as the error number. Before that, only -1 counted as an error, which the last entry always hits: readdir_r() then returned errno, usually 0, without filling in the entry, so the caller got the previous entry a second time and never saw the last one. 32-bit processes get 31-bit hashes from ext4 and are not affected. Keep the offset in an off_t. Fixes: 665fa8dea90d ("tools/nolibc: add support for directory access") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/dirent.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/dirent.h b/tools/include/nolibc/dirent.h index 2dbf4052b85a0b..19857369de2a6f 100644 --- a/tools/include/nolibc/dirent.h +++ b/tools/include/nolibc/dirent.h @@ -81,6 +81,7 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) struct linux_dirent64 *ldir = (void *)buf; intptr_t i = (intptr_t)dirp; int fd, ret; + off_t off; if (i >= 0) return EBADF; @@ -100,9 +101,9 @@ int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) * readdir() can only return one entry at a time. * Make sure the non-returned ones are not skipped. */ - ret = _sys_lseek(fd, ldir->d_off, SEEK_SET); - if (ret < 0) - return -ret; + off = _sys_lseek(fd, ldir->d_off, SEEK_SET); + if (off < 0) + return -off; entry->d_ino = ldir->d_ino; /* the destination should always be big enough */ From 929ea5b3481fdbbb419f3986703280fb2b819504 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:15:59 -0500 Subject: [PATCH 0952/2286] tools/nolibc: fix FD_SET(), FD_CLR() and FD_ISSET() on 64-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since commit feaf75658783 ("nolibc: fix fd_set type"), an fd_set is an array of unsigned long, but the FD_* macros still build their masks as an int or an unsigned int: 1 << n in FD_SET(), 1U << n in FD_CLR() and FD_ISSET(), where n is the fd modulo 64 on 64-bit architectures. So: - FD_SET() of an fd with n == 31 sets the bits for n = 31-63, as 1 << 31 is negative and gets sign-extended. - For n >= 32 the shifts are undefined. x86-64, for instance, masks the shift count, so FD_SET(40) and FD_ISSET(40) use the bit of fd 8. - FD_CLR() zero-extends ~(1U << n), so it also clears the bits for n = 32-63, whichever fd it is given. select() on fd 40, for instance, fails with EBADF, or watches fd 8 instead if that one is open. Build the masks from 1UL. Fixes: feaf75658783 ("nolibc: fix fd_set type") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-3-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/sys/select.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/sys/select.h b/tools/include/nolibc/sys/select.h index 6d65d9ef3d6af4..f299c79939f593 100644 --- a/tools/include/nolibc/sys/select.h +++ b/tools/include/nolibc/sys/select.h @@ -26,7 +26,7 @@ typedef struct { int __fd = (fd); \ if (__fd >= 0) \ __set->fds[__fd / FD_SETIDXMASK] &= \ - ~(1U << (__fd & FD_SETBITMASK)); \ + ~(1UL << (__fd & FD_SETBITMASK)); \ } while (0) #define FD_SET(fd, set) do { \ @@ -34,7 +34,7 @@ typedef struct { int __fd = (fd); \ if (__fd >= 0) \ __set->fds[__fd / FD_SETIDXMASK] |= \ - 1 << (__fd & FD_SETBITMASK); \ + 1UL << (__fd & FD_SETBITMASK); \ } while (0) #define FD_ISSET(fd, set) ({ \ @@ -43,7 +43,7 @@ typedef struct { int __r = 0; \ if (__fd >= 0) \ __r = !!(__set->fds[__fd / FD_SETIDXMASK] & \ -1U << (__fd & FD_SETBITMASK)); \ +1UL << (__fd & FD_SETBITMASK)); \ __r; \ }) From 6a8fa40b03f7f111eba4ece966ec4762728378ad Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Fri, 25 Sep 2026 16:16:00 -0500 Subject: [PATCH 0953/2286] selftests/nolibc: test the FD_* macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only test that uses the FD_* macros passes fd 1, so nothing noticed that they used the wrong bits for fds 31-63 of each word on 64-bit architectures. Check that FD_SET() and FD_CLR() change exactly one fd, for every fd below FD_SETSIZE. Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260925211600.119116-4-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/testing/selftests/nolibc/nolibc-test.c | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 37c11a7fce238a..1995d67dcca6ba 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1841,6 +1841,26 @@ int test_time_types(void) return 0; } +int test_fd_set(void) +{ + fd_set set, clr; + int fd, i; + + /* FD_SET() and FD_CLR() must change exactly one fd */ + for (fd = 0; fd < FD_SETSIZE; fd++) { + FD_ZERO(&set); + FD_SET(fd, &set); + memset(&clr, 0xff, sizeof(clr)); + FD_CLR(fd, &clr); + for (i = 0; i < FD_SETSIZE; i++) + if (!!FD_ISSET(i, &set) != (i == fd) || + !!FD_ISSET(i, &clr) != (i != fd)) + return 1; + } + + return 0; +} + int test_malloc(void) { size_t sz_array1, sz_array2, sz_array3; @@ -2022,6 +2042,7 @@ int run_stdlib(int min, int max) CASE_TEST(memchr_foobar6_o); EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break; CASE_TEST(memchr_foobar3_b); EXPECT_STRZR(1, memchr("foobar", 'b', 3)); break; CASE_TEST(time_types); EXPECT_ZR(is_nolibc, test_time_types()); break; + CASE_TEST(fd_set); EXPECT_ZR(1, test_fd_set()); break; CASE_TEST(makedev); EXPECT_EQ(1, makedev(0x12, 0x34), 0x1234); break; CASE_TEST(major); EXPECT_EQ(1, major(0x1234), 0x12); break; CASE_TEST(minor); EXPECT_EQ(1, minor(0x1234), 0x34); break; From 0e1c44b472e1ec21efdad1df21b10e5c568b65b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 7 Apr 2026 13:44:12 +0200 Subject: [PATCH 0954/2286] tools/nolibc: add 64-bit parisc support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the existing parisc support to also handle 64-bit mode. Some peculiarities: * The nonstandard ftruncate64 system call requires a workaround. * The linker defaults to main() as entry point, which needs to be switched to _start() explicitly. * The linker defaults to dynamic linking, which needs to be disabled explicitly. * With the default -Os, gcc does not use 64-bit multiplication instructions, requiring libgcc. Use -O2 instead. * There is no qemu-user available. * CONFIG_COMPAT needs to be disabled to avoid build errors due to a missing 32-bit vDSO compiler. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260914-nolibc-parisc64-v3-1-f14b320117c1@weissschuh.net --- tools/include/nolibc/arch-parisc.h | 26 ++++++++++++++++--- tools/include/nolibc/stdlib.h | 2 +- .../testing/selftests/nolibc/Makefile.nolibc | 5 ++++ tools/testing/selftests/nolibc/run-tests.sh | 10 ++++--- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/tools/include/nolibc/arch-parisc.h b/tools/include/nolibc/arch-parisc.h index ca219cbbabb09f..bf57ba16055ca7 100644 --- a/tools/include/nolibc/arch-parisc.h +++ b/tools/include/nolibc/arch-parisc.h @@ -7,9 +7,7 @@ #ifndef _NOLIBC_ARCH_PARISC_H #define _NOLIBC_ARCH_PARISC_H -#if defined(__LP64__) -#error 64-bit not supported -#endif +#include #include "compiler.h" #include "crt.h" @@ -167,9 +165,15 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_protector _start(void) { __asm__ volatile ( +#ifdef __LP64__ + ".import __gp\n" /* Set up the dp register */ + "ldil L%__gp, %dp\n" + "ldo R%__gp(%dp), %dp\n" +#else ".import $global$\n" /* Set up the dp register */ "ldil L%$global$, %dp\n" "ldo R%$global$(%dp), %dp\n" +#endif "b _start_c\n" /* Call _start_c, the load below is executed first */ @@ -182,4 +186,20 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +#ifdef __LP64__ +/* + * This override is normally only meant for 32-bit architectures. + * But parisc 64-bit has __NR_ftruncate64, so this path is taken. + * Add an unused second argument to satisfy the calling convention. + */ +static __attribute__((unused)) +int _sys_ftruncate64(int fd, uint64_t length, uint64_t unused) +{ + (void)unused; + + return __nolibc_syscall2(__NR_ftruncate64, fd, length); +} +#define _sys_ftruncate64 _sys_ftruncate64 +#endif + #endif /* _NOLIBC_ARCH_PARISC_H */ diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 1816c2368b6803..6c3c620a04cf0d 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -230,7 +230,7 @@ int _nolibc_u64toa_base(uint64_t in, char *buffer, unsigned int base, uint64_t r /* Generate least significant digit first */ do { -#if defined(__SIZEOF_INT128__) && !defined(__mips__) && !defined(__sparc__) +#if defined(__SIZEOF_INT128__) && !defined(__mips__) && !defined(__sparc__) && !defined(__hppa__) q = ((unsigned __int128)in * recip) >> 64; #else uint64_t p = (uint32_t)in * (recip >> 32); diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index d8b71cbfc1735c..3ca473ec35ef60 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -65,6 +65,7 @@ ARCH_sparc32 = sparc ARCH_sparc64 = sparc ARCH_sh4 = sh ARCH_parisc32 = parisc +ARCH_parisc64 = parisc64 ARCH := $(or $(ARCH_$(XARCH)),$(XARCH)) # kernel image names by architecture @@ -113,6 +114,7 @@ EXTRACONFIG_sparc32 = -e CONFIG_TMPFS EXTRACONFIG_m68k = -e CONFIG_BLK_DEV_INITRD EXTRACONFIG_sh4 = -e CONFIG_BLK_DEV_INITRD -e CONFIG_CMDLINE_FROM_BOOTLOADER EXTRACONFIG_alpha = -e CONFIG_BLK_DEV_INITRD +EXTRACONFIG_parisc64 = -d CONFIG_COMPAT EXTRACONFIG = $(EXTRACONFIG_$(XARCH)) # optional tests to run (default = all) @@ -134,6 +136,7 @@ QEMU_ARCH_loongarch = loongarch64 QEMU_ARCH_sparc32 = sparc QEMU_ARCH_openrisc = or1k QEMU_ARCH_parisc32 = hppa +QEMU_ARCH_parisc64 = hppa QEMU_ARCH = $(or $(QEMU_ARCH_$(XARCH)),$(XARCH)) QEMU_ARCH_USER_ppc64le = ppc64le @@ -175,6 +178,7 @@ QEMU_ARGS_m68k = -M virt -append "console=ttyGF0,115200 panic=-1 $(TEST:%= QEMU_ARGS_sh4 = -M r2d -serial file:/dev/stdout -append "console=ttySC1,115200 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_openrisc = -M virt -m 512M -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_parisc32 = -M B160L -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" +QEMU_ARGS_parisc64 = -M C3700 -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS_alpha = -M clipper -append "console=ttyS0 panic=-1 $(TEST:%=NOLIBC_TEST=%)" QEMU_ARGS = -m 1G $(QEMU_ARGS_$(XARCH)) $(QEMU_ARGS_BIOS) $(QEMU_ARGS_EXTRA) @@ -193,6 +197,7 @@ CFLAGS_x32 = -mx32 CFLAGS_arm = -marm CFLAGS_armthumb = -mthumb -march=armv6t2 CFLAGS_parisc32 = -mfast-indirect-calls +CFLAGS_parisc64 = -Wl,--no-dynamic-linker -Wl,--entry=_start -O2 CFLAGS_ppc = -m32 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64 = -m64 -mbig-endian -mno-vsx $(call cc-option,-mmultiple) CFLAGS_ppc64le = -m64 -mlittle-endian -mno-vsx $(call cc-option,-mabi=elfv2) diff --git a/tools/testing/selftests/nolibc/run-tests.sh b/tools/testing/selftests/nolibc/run-tests.sh index af2a6f510d1db2..2844fed26df177 100755 --- a/tools/testing/selftests/nolibc/run-tests.sh +++ b/tools/testing/selftests/nolibc/run-tests.sh @@ -29,7 +29,7 @@ all_archs=( sparc32 sparc64 m68k sh4 - parisc32 + parisc32 parisc64 alpha hexagon ) @@ -122,6 +122,7 @@ crosstool_arch() { sparc*) echo sparc64;; x32*) echo x86_64;; parisc32) echo hppa;; + parisc64) echo hppa64;; *) echo "$1";; esac } @@ -206,11 +207,14 @@ test_arch() { exit 1 esac printf '%-15s' "$arch:" - if [ "$llvm" = "1" ] && [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" -o "$arch" = "parisc32" -o "$arch" = "alpha" ]; then + if [ "$llvm" = "1" ] && \ + [ "$arch" = "m68k" -o "$arch" = "sh4" -o "$arch" = "openrisc" \ + -o "$arch" == "parisc32" -o "$arch" == "parisc64" -o "$arch" = "alpha" \ + ]; then echo "Unsupported configuration" return fi - if [ "$test_mode" = "user" ] && [ "$arch" = "x32" ]; then + if [ "$test_mode" = "user" ] && [ "$arch" = "x32" -o "$arch" = "parisc64" ]; then echo "Unsupported configuration" return fi From 7cee2fb210a198667d3d4200705215e6f315cab0 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:46 +0530 Subject: [PATCH 0955/2286] bitmap: bitmap_parse(): reject non-hex character before 8 digits bitmap_parse() is documented to return -EINVAL for illegal characters, and it does so everywhere except when the illegal character directly precedes a chunk of exactly eight hex digits. bitmap_get_x32_reverse() parses a chunk from right to left. After it has consumed eight digits it looks at one more character: a hex digit means the chunk is wider than 32 bits and -EOVERFLOW is returned. Any other character is silently consumed as if it were a separator, and the remaining input is parsed as the next chunk. As a result: "x12345678" -> 0x12345678, returns 0 (expected -EINVAL) "1g12345678" -> 0x1_12345678, returns 0 (expected -EINVAL) "0x0000000f" -> 0xf, returns 0, while "0xf" returns -EINVAL bitmap_parse() backs cpumask_parse() and cpumask_parse_user(), so the same inconsistency is visible when writing masks such as /proc/irq/*/smp_affinity or the network queue rps_cpus/xps_cpus files. __bitmap_parse(), which this function replaced in commit 2d6261583be0 ("lib: rework bitmap_parse()"), rejected all three strings with -EINVAL, and a "0x" prefix is accepted today only when exactly eight digits follow it, so no reliably working input is lost. When the loop falls through, *end is known to be a non-separator character inside the buffer, so return -EOVERFLOW if it is a hex digit and -EINVAL otherwise. Fixes: 2d6261583be0 ("lib: rework bitmap_parse()") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/bitmap-str.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/bitmap-str.c b/lib/bitmap-str.c index dd9aa0635fa561..e02c5bc80951c5 100644 --- a/lib/bitmap-str.c +++ b/lib/bitmap-str.c @@ -414,8 +414,12 @@ static const char *bitmap_get_x32_reverse(const char *start, goto out; } - if (hex_to_bin(*end--) >= 0) - return ERR_PTR(-EOVERFLOW); + /* + * Eight digits have been consumed and the next character is not a + * separator: another hex digit means the chunk does not fit in 32 + * bits, anything else is an illegal character. + */ + return ERR_PTR(hex_to_bin(*end) >= 0 ? -EOVERFLOW : -EINVAL); out: *num = ret; return end; From bf7e3cb42c8b5d52fa4232c11bae40e9a2509f95 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:47 +0530 Subject: [PATCH 0956/2286] bitmap: test bitmap_parse() with an illegal character before 8 hex digits Add bitmap_parse() cases where an illegal character directly precedes a chunk of eight hex digits. These were accepted before the previous patch and must return -EINVAL. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/test_bitmap.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/test_bitmap.c b/lib/test_bitmap.c index 56bd23059b26ab..35bb18f6e32667 100644 --- a/lib/test_bitmap.c +++ b/lib/test_bitmap.c @@ -643,6 +643,10 @@ static const struct test_bitmap_parselist parse_tests[] __initconst = { {-EOVERFLOW, "badf00d,deadbeef,1,0", NULL, 90, 0}, {-EOVERFLOW, "fbadf00d,deadbeef,1,0", NULL, 95, 0}, {-EOVERFLOW, "badf00d,deadbeef,1,0", NULL, 100, 0}, + + {-EINVAL, "x12345678", NULL, 32, 0}, + {-EINVAL, "1g12345678", NULL, 64, 0}, + {-EINVAL, "0x0000000f", NULL, 64, 0}, #undef step }; From c55d102257fa31271c6f11fea595b78c48fd7aa2 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:48 +0530 Subject: [PATCH 0957/2286] bitmap: bitmap_parselist(): reject trailing characters after group size bitmap_parse_region() checks that every region ends at a separator (',', whitespace, '\n' or '\0'), except in the "range:used/group" form: after parsing the group size it returns the pointer to the following character without looking at it. The caller then starts a new region at that character. Before 'N' and 'all' were accepted as region starts this happened to be harmless, because any other character would fail to parse as a new region. Now text directly following the group size is silently parsed as another region: "0-7:1/2N" -> bits 0,2,4,6,7 (nbits = 8), returns 0 "0-7:1/2all" -> all bits set, returns 0 while "0-7N", "5N" or "0-7:1/2x" are correctly rejected with -EINVAL. bitmap_parselist() is documented to return -EINVAL for an invalid character. It parses cpu lists from sysfs, cgroup cpuset files and boot parameters such as isolcpus= and nohz_full=, so a typo there is accepted and yields a different mask than intended. Check for the end of the region after the group size as well, like the other forms do. Fixes: 2c4885d24e64 ("lib: bitmap: support "N" as an alias for size of bitmap") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/bitmap-str.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/bitmap-str.c b/lib/bitmap-str.c index e02c5bc80951c5..b5896686465743 100644 --- a/lib/bitmap-str.c +++ b/lib/bitmap-str.c @@ -299,7 +299,14 @@ static const char *bitmap_parse_region(const char *str, struct region *r) if (*str != '/') return ERR_PTR(-EINVAL); - return bitmap_getnum(str + 1, &r->group_len, lastbit); + str = bitmap_getnum(str + 1, &r->group_len, lastbit); + if (IS_ERR(str)) + return str; + + if (!end_of_region(*str)) + return ERR_PTR(-EINVAL); + + return end_of_str(*str) ? NULL : str; no_end: r->end = r->start; From f2c318d812f9b108c4808342ea135e2788c5fd6e Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:49 +0530 Subject: [PATCH 0958/2286] bitmap: test bitmap_parselist() with text after the group size Add bitmap_parselist() cases where 'N' or 'all' directly follows the group size of a "range:used/group" region. These were accepted before "bitmap: bitmap_parselist(): reject trailing characters after group size" and must return -EINVAL. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/test_bitmap.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/test_bitmap.c b/lib/test_bitmap.c index 35bb18f6e32667..f316a82840f50e 100644 --- a/lib/test_bitmap.c +++ b/lib/test_bitmap.c @@ -528,6 +528,9 @@ static const struct test_bitmap_parselist parselist_tests[] __initconst = { {-EINVAL, "a-31:10/1", NULL, 8, 0}, {-EINVAL, "0-31:a/1", NULL, 8, 0}, {-EINVAL, "0-\n", NULL, 8, 0}, + {-EINVAL, "0-7:1/2N", NULL, 8, 0}, + {-EINVAL, "0-7:1/2all", NULL, 8, 0}, + {-EINVAL, "all:1/2N", NULL, 8, 0}, }; From d075c4dc0b3c8ed91a4f67ebdf3384aa59fd4fbb Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:50 +0530 Subject: [PATCH 0959/2286] bitmap: bitmap_parselist(): don't wrap around on a huge group size In the "range:used/group" form bitmap_set_region() walks the range with for (start = r->start; start <= r->end; start += r->group_len) where all values are unsigned int. bitmap_getnum() accepts any group size up to UINT_MAX, and bitmap_check_region() only requires it to be non-zero and at least the used size. If start + group_len exceeds UINT_MAX, start wraps around to a small value that is still <= end, and bits below the start of the range get set: "1-1:1/4294967295" -> bits 0,1 (expected bit 1) "15-15:1/4294967281" -> bits 0,15 (expected bit 15) The loop still terminates, because start keeps decreasing until it wraps past zero again, but the resulting mask contains bits outside the requested range. This needs a group size within 'start' of UINT_MAX, so it is unlikely to be hit by accident; all bits set stay below the end of the range, so there is no out-of-bounds write. A group larger than the range simply means that the range contains a single group, so stop the walk when the next group would start beyond UINT_MAX. Fixes: 0a5ce0831d04 ("lib/bitmap.c: make bitmap_parselist() thread-safe and much faster") Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/bitmap-str.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/bitmap-str.c b/lib/bitmap-str.c index b5896686465743..cafd6388892f06 100644 --- a/lib/bitmap-str.c +++ b/lib/bitmap-str.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "kstrtox.h" @@ -186,10 +187,12 @@ struct region { static void bitmap_set_region(const struct region *r, unsigned long *bitmap) { - unsigned int start; + unsigned int start = r->start; - for (start = r->start; start <= r->end; start += r->group_len) + do { bitmap_set(bitmap, start, min(r->end - start + 1, r->off)); + } while (!check_add_overflow(start, r->group_len, &start) && + start <= r->end); } static int bitmap_check_region(const struct region *r) From 452a6d5b5e0556848a8c28428c8f86874ea4ee02 Mon Sep 17 00:00:00 2001 From: Shashank Mohan Jain Date: Sat, 26 Sep 2026 06:56:51 +0530 Subject: [PATCH 0960/2286] bitmap: test bitmap_parselist() with a group size close to UINT_MAX Add bitmap_parselist() cases where the group size makes start + group_len overflow an unsigned int. Before "bitmap: bitmap_parselist(): don't wrap around on a huge group size" the region walk wrapped around and also set bit 0. Assisted-by: LLM Signed-off-by: Shashank Mohan Jain Signed-off-by: Yury Norov --- lib/test_bitmap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/test_bitmap.c b/lib/test_bitmap.c index f316a82840f50e..ba09d057f47f42 100644 --- a/lib/test_bitmap.c +++ b/lib/test_bitmap.c @@ -502,6 +502,8 @@ static const struct test_bitmap_parselist parselist_tests[] __initconst = { {0, "0-N:1/3,1-N:1/3,2-N:1/3", &exp1[8 * step], 32, 0}, {0, "0-31:1/3,1-31:1/3,2-31:1/3", &exp1[8 * step], 32, 0}, {0, "1-10:8/12,8-31:24/29,0-31:0/3", &exp1[9 * step], 32, 0}, + {0, "1-1:1/4294967295", &exp1[1 * step], 32, 0}, + {0, "15-15:1/4294967281", &exp1[13 * step], 32, 0}, {0, "all", &exp1[8 * step], 32, 0}, {0, "0, 1, all, ", &exp1[8 * step], 32, 0}, From 6d1f56090e31545b2e70de223b8779852ef22a12 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 16 Sep 2026 14:41:31 -0300 Subject: [PATCH 0961/2286] docs: translations: pt_BR: point refs at the translated documents Several pt_BR documents referenced the English version of documents that are already translated, in some cases rendering an English title in the middle of a Portuguese sentence. Add the missing pt_BR anchors, repoint the references at the translations, and fix the broken paths and stray punctuation found along the way. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916174136.48011-2-danielmaraboo@gmail.com> --- .../pt_BR/process/adding-syscalls.rst | 2 +- .../code-of-conduct-interpretation.rst | 2 ++ .../pt_BR/process/code-of-conduct.rst | 4 +-- .../pt_BR/process/coding-style.rst | 2 ++ .../translations/pt_BR/process/cve.rst | 4 +-- .../pt_BR/process/development-process.rst | 2 ++ .../pt_BR/process/maintainer-handbooks.rst | 4 +-- .../pt_BR/process/maintainer-kvm-x86.rst | 4 +-- .../process/maintainer-soc-clean-dts.rst | 5 ++-- .../pt_BR/process/management-style.rst | 7 +++--- .../pt_BR/process/security-bugs.rst | 25 +++++++++++-------- .../pt_BR/process/submit-checklist.rst | 4 +-- .../pt_BR/process/submitting-patches.rst | 2 ++ 13 files changed, 40 insertions(+), 27 deletions(-) diff --git a/Documentation/translations/pt_BR/process/adding-syscalls.rst b/Documentation/translations/pt_BR/process/adding-syscalls.rst index 37b0dd0e2df58b..99607ee5f94987 100644 --- a/Documentation/translations/pt_BR/process/adding-syscalls.rst +++ b/Documentation/translations/pt_BR/process/adding-syscalls.rst @@ -7,7 +7,7 @@ Adicionando uma Nova Chamada de Sistema Este documento descreve o que está envolvido na adição de uma nova chamada de sistema (system call) ao kernel Linux, indo além dos conselhos normais de submissão em -:ref:`Documentation/process/submitting-patches.rst `. +:ref:`Documentation/translations/pt_BR/process/submitting-patches.rst `. Alternativas às Chamadas de Sistema diff --git a/Documentation/translations/pt_BR/process/code-of-conduct-interpretation.rst b/Documentation/translations/pt_BR/process/code-of-conduct-interpretation.rst index 866c9f7e7a12d1..c40886e3d3ceeb 100644 --- a/Documentation/translations/pt_BR/process/code-of-conduct-interpretation.rst +++ b/Documentation/translations/pt_BR/process/code-of-conduct-interpretation.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_code_of_conduct_interpretation: + Interpretação do Código de Conduta do Kernel Linux ================================================== diff --git a/Documentation/translations/pt_BR/process/code-of-conduct.rst b/Documentation/translations/pt_BR/process/code-of-conduct.rst index 1ab171bf21cffd..8a3da70b7e6bf6 100644 --- a/Documentation/translations/pt_BR/process/code-of-conduct.rst +++ b/Documentation/translations/pt_BR/process/code-of-conduct.rst @@ -84,5 +84,5 @@ disponível em https://www.contributor-covenant.org/version/1/4/code-of-conduct. Interpretação ============= -Consulte o documento :ref:`code_of_conduct_interpretation` para entender como a -comunidade do kernel Linux interpretará este documento. +Consulte o documento :ref:`pt_BR_code_of_conduct_interpretation` para +entender como a comunidade do kernel Linux interpretará este documento. diff --git a/Documentation/translations/pt_BR/process/coding-style.rst b/Documentation/translations/pt_BR/process/coding-style.rst index 26dcd1246de6e1..ac9bf0a716bca5 100644 --- a/Documentation/translations/pt_BR/process/coding-style.rst +++ b/Documentation/translations/pt_BR/process/coding-style.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_codingstyle: + Estilo de codificação do kernel Linux ===================================== diff --git a/Documentation/translations/pt_BR/process/cve.rst b/Documentation/translations/pt_BR/process/cve.rst index 18f4b11be3690b..83977eda34c47a 100644 --- a/Documentation/translations/pt_BR/process/cve.rst +++ b/Documentation/translations/pt_BR/process/cve.rst @@ -18,7 +18,7 @@ essas atribuições. A equipe de desenvolvedores do kernel Linux tem a capacidade de atribuir CVEs para possíveis problemas de segurança do kernel Linux. Essa atribuição é independente do processo normal de relato de bugs de segurança do kernel -Linux, descrito em :ref:`securitybugs`. +Linux, descrito em :ref:`pt_BR_securitybugs`. Uma lista de todos os CVEs atribuídos ao kernel Linux pode ser encontrada nos arquivos da lista de discussão linux-cve, como visto em @@ -51,7 +51,7 @@ ele é SOMENTE para atribuição de CVEs a correções que já estejam em árvor kernel lançadas. Se você acredita ter encontrado um problema de segurança ainda não corrigido, por favor siga o processo normal de relato de bugs de segurança do kernel -Linux, descrito em :ref:`securitybugs`. +Linux, descrito em :ref:`pt_BR_securitybugs`. Nenhum CVE será atribuído automaticamente para problemas de segurança ainda não corrigidos no kernel Linux; a atribuição só acontecerá automaticamente diff --git a/Documentation/translations/pt_BR/process/development-process.rst b/Documentation/translations/pt_BR/process/development-process.rst index d303ab92b2d35a..e1441cec8b1acc 100644 --- a/Documentation/translations/pt_BR/process/development-process.rst +++ b/Documentation/translations/pt_BR/process/development-process.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_development_process_main: + Guia para o Processo de Desenvolvimento do Kernel ================================================= diff --git a/Documentation/translations/pt_BR/process/maintainer-handbooks.rst b/Documentation/translations/pt_BR/process/maintainer-handbooks.rst index b7aab788ffdbf2..1307306501ab1c 100644 --- a/Documentation/translations/pt_BR/process/maintainer-handbooks.rst +++ b/Documentation/translations/pt_BR/process/maintainer-handbooks.rst @@ -5,8 +5,8 @@ Notas sobre o processo de desenvolvimento de subsistemas e mantenedores O propósito deste documento é fornecer informações específicas de subsistemas que são suplementares ao manual geral do processo de -desenvolvimento. -:ref:`Documentation/process `. +desenvolvimento +:ref:`Documentation/translations/pt_BR/process `. Para desenvolvedores, veja abaixo todos os guias específicos de subsistemas conhecidos. Se o subsistema para o qual você está diff --git a/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst b/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst index d9fd272fb866d6..ef133d32c517cb 100644 --- a/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst +++ b/Documentation/translations/pt_BR/process/maintainer-kvm-x86.rst @@ -431,5 +431,5 @@ Bugs que podem ser explorados pelo convidado (guest) para atacar o hospedeiro (host) (kernel ou espaço do usuário), ou que podem ser explorados por uma VM aninhada (nested) contra o *seu* próprio hospedeiro (L2 atacando L1), são de interesse particular para o KVM. Por favor, siga o protocolo em -:ref:`securitybugs` se você suspeitar que um bug possa levar a um escape, -vazamento de dados, etc. +:ref:`pt_BR_securitybugs` se você suspeitar que um bug possa levar a um +escape, vazamento de dados, etc. diff --git a/Documentation/translations/pt_BR/process/maintainer-soc-clean-dts.rst b/Documentation/translations/pt_BR/process/maintainer-soc-clean-dts.rst index a7e7bf0f106f45..7256c71534a791 100644 --- a/Documentation/translations/pt_BR/process/maintainer-soc-clean-dts.rst +++ b/Documentation/translations/pt_BR/process/maintainer-soc-clean-dts.rst @@ -8,8 +8,9 @@ Visão Geral ----------- As plataformas SoC ou subarquiteturas devem seguir todas as regras de -Documentation/process/maintainer-soc.rst. Este documento, referenciado em -MAINTAINERS, impõe requisitos adicionais listados abaixo. +Documentation/translations/pt_BR/process/maintainer-soc.rst. Este +documento, referenciado em MAINTAINERS, impõe requisitos adicionais +listados abaixo. Conformidade Estrita com DT Schema de DTS e dtc ----------------------------------------------- diff --git a/Documentation/translations/pt_BR/process/management-style.rst b/Documentation/translations/pt_BR/process/management-style.rst index b92f8705c30fd1..c40b19b904f64d 100644 --- a/Documentation/translations/pt_BR/process/management-style.rst +++ b/Documentation/translations/pt_BR/process/management-style.rst @@ -5,9 +5,10 @@ Estilo de gerenciamento do kernel Linux Este é um documento curto descrevendo o estilo de gerenciamento preferido (ou inventado, dependendo de quem você perguntar) para o kernel do Linux. Ele se -destina a espelhar o documento :ref:`process/coding-style.rst ` em -algum grau, e foi escrito principalmente para evitar responder [#f1]_ as mesmas -(ou semelhantes) perguntas repetidamente. +destina a espelhar o documento +:ref:`Documentation/translations/pt_BR/process/coding-style.rst ` +em algum grau, e foi escrito principalmente para evitar responder [#f1]_ as +mesmas (ou semelhantes) perguntas repetidamente. Estilo de gerenciamento é muito pessoal e muito mais difícil de quantificar do que simples regras de estilo de codificação, então este documento pode ou não ter diff --git a/Documentation/translations/pt_BR/process/security-bugs.rst b/Documentation/translations/pt_BR/process/security-bugs.rst index 72c771869566bd..321575657b2824 100644 --- a/Documentation/translations/pt_BR/process/security-bugs.rst +++ b/Documentation/translations/pt_BR/process/security-bugs.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_securitybugs: + Falhas de segurança =================== @@ -59,11 +61,11 @@ Além disso, as seguintes informações são altamente desejáveis: mantenedores, mesmo que a correção acabe não sendo a correta, pois ajuda a entender o bug. Ao propor uma correção testada, por favor, formate-a sempre de uma maneira que possa ser mesclada imediatamente (consulte - Documentation/process/submitting-patches.rst). Isso evitará algumas trocas - de mensagens caso ela seja aceita, e você receberá o crédito por - encontrar e corrigir o problema. Observe que, neste caso, apenas uma tag - ``Signed-off-by:`` é necessária, sem ``Reported-by:`` quando o relator e - o autor forem a mesma pessoa. + Documentation/translations/pt_BR/process/submitting-patches.rst). Isso + evitará algumas trocas de mensagens caso ela seja aceita, e você receberá + o crédito por encontrar e corrigir o problema. Observe que, neste caso, + apenas uma tag ``Signed-off-by:`` é necessária, sem ``Reported-by:`` + quando o relator e o autor forem a mesma pessoa. * **mitigações**: com muita frequência, durante a análise de um bug, surgem algumas maneiras de mitigar o problema. É útil compartilhá-las, @@ -228,8 +230,8 @@ a tornar esses relatórios desnecessariamente difíceis de lidar: Se a correção não puder ser testada porque depende de hardware raro ou de protocolos de rede quase extintos, é provável que o problema não seja um bug de segurança. Em qualquer caso, se uma correção for proposta, ela deve - aderir a Documentation/process/submitting-patches.rst e incluir uma tag - 'Fixes:' designando o commit que introduziu o bug. + aderir a Documentation/translations/pt_BR/process/submitting-patches.rst + e incluir uma tag 'Fixes:' designando o commit que introduziu o bug. A falha em considerar estes pontos expõe seu relatório ao risco de ser ignorado. @@ -280,7 +282,7 @@ entender e corrigir a vulnerabilidade de segurança. Por favor, envie e-mails em **texto simples** sem anexos, sempre que possível. É muito mais difícil ter uma discussão com citações de contexto sobre um problema complexo se todos os detalhes estiverem ocultos em anexos. Pense nisso -como uma :doc:`regular path submission ` +como uma :doc:`submissão pelo caminho normal ` (mesmo que você ainda não tenha um patch): descreva o problema e o impacto, liste as etapas de reprodução e siga com uma proposta de correção, tudo em texto simples. Relatórios formatados em Markdown, HTML e RST são @@ -288,8 +290,9 @@ particularmente malvistos, pois são bastante difíceis de ler por humanos e incentivam o uso de visualizadores dedicados, às vezes online, o que por definição não é aceitável para um relatório de segurança confidencial. Note que alguns clientes de e-mail tendem a corromper a formatação de texto simples -por padrão; por favor, consulte Documentation/process/email-clients.rst para -mais informações. +por padrão; por favor, consulte +Documentation/translations/pt_BR/process/email-clients.rst para mais +informações. Divulgação e informações sob embargo ------------------------------------ @@ -359,7 +362,7 @@ A equipe de segurança não atribui CVEs, nem os exigimos para relatórios ou correções, pois isso pode complicar desnecessariamente o processo e adiar o tratamento do bug. Se um relator desejar que um identificador CVE seja atribuído para um problema confirmado, ele pode entrar em contato com a -:doc:`kernel CVE assignment team<../../../process/cve>` para obter um. +:doc:`equipe de atribuição de CVEs do kernel ` para obter um. Acordo de não divulgação ------------------------ diff --git a/Documentation/translations/pt_BR/process/submit-checklist.rst b/Documentation/translations/pt_BR/process/submit-checklist.rst index 003fc956832df6..3e0ee38b9a72de 100644 --- a/Documentation/translations/pt_BR/process/submit-checklist.rst +++ b/Documentation/translations/pt_BR/process/submit-checklist.rst @@ -8,7 +8,7 @@ Aqui estão algumas coisas básicas que os desenvolvedores devem fazer se quiserem ver suas submissões de patches de kernel aceitas mais rapidamente. Estas diretrizes vão além da documentação fornecida em -:ref:`Documentation/process/submitting-patches.rst ` +:ref:`Documentation/translations/pt_BR/process/submitting-patches.rst ` e em outros locais sobre o envio de patches para o kernel Linux. Revise seu código @@ -19,7 +19,7 @@ Revise seu código os que você usa de forma indireta. 2) Verifique o estilo geral do seu patch conforme detalhado em - :ref:`Documentation//process/coding-style.rst `. + :ref:`Documentation/translations/pt_BR/process/coding-style.rst `. 3) Todas as barreiras de memória {por exemplo, ``barrier()``, ``rmb()``, ``wmb()``} precisam de um comentário no código-fonte que explique a diff --git a/Documentation/translations/pt_BR/process/submitting-patches.rst b/Documentation/translations/pt_BR/process/submitting-patches.rst index 8cf1aae52005d0..aad926c17fb454 100644 --- a/Documentation/translations/pt_BR/process/submitting-patches.rst +++ b/Documentation/translations/pt_BR/process/submitting-patches.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_submittingpatches: + Enviando patches: o guia essencial para colocar o seu código no kernel ====================================================================== From bfff9a61c7a604605d30937436f46f4b1af17afa Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 16 Sep 2026 14:41:32 -0300 Subject: [PATCH 0962/2286] docs: translations: pt_BR: add admin-guide index Add a Brazilian Portuguese index for admin-guide, mirroring the section layout of the original. No document has been translated yet, so every entry is listed under a Todolist marker instead of a toctree, the same way the zh_CN translation tracks its pending files; entries move into a toctree as they get translated. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916174136.48011-3-danielmaraboo@gmail.com> --- .../translations/pt_BR/admin-guide/index.rst | 186 ++++++++++++++++++ Documentation/translations/pt_BR/index.rst | 10 + 2 files changed, 196 insertions(+) create mode 100644 Documentation/translations/pt_BR/admin-guide/index.rst diff --git a/Documentation/translations/pt_BR/admin-guide/index.rst b/Documentation/translations/pt_BR/admin-guide/index.rst new file mode 100644 index 00000000000000..a5ad5baa3aeff9 --- /dev/null +++ b/Documentation/translations/pt_BR/admin-guide/index.rst @@ -0,0 +1,186 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. raw:: latex + + \renewcommand\thesection* + \renewcommand\thesubsection* + +================================================== +Guia do usuário e do administrador do kernel Linux +================================================== + +A seguir está uma coleção de documentos voltados ao usuário que foram +adicionados ao kernel ao longo do tempo. Ainda há pouca ordem ou organização +geral aqui --- este material não foi escrito para ser um documento único e +coerente! Com sorte, as coisas melhorarão rapidamente com o tempo. + +Guias gerais para a administração do kernel +------------------------------------------- + +Esta seção inicial contém informações gerais, incluindo o arquivo README que +descreve o kernel como um todo, documentação sobre os parâmetros do kernel, +etc. + +Todolist: + +* README +* devices +* features + +Uma grande parte da interface administrativa do kernel são os sistemas de +arquivos virtuais /proc e sysfs; estes documentos descrevem como interagir +com eles. + +Todolist: + +* sysfs-rules +* sysctl/index +* cputopology +* abi + +Documentação relacionada à segurança: + +Todolist: + +* hw-vuln/index +* LSM/index +* perf-security + +Inicializando o kernel +---------------------- + +Todolist: + +* bootconfig +* kernel-parameters +* efi-stub +* initrd + +Rastreando e identificando problemas +------------------------------------ + +Aqui está um conjunto de documentos voltados a usuários que estão tentando +rastrear problemas e bugs em particular. + +Todolist: + +* reporting-issues +* reporting-regressions +* quickly-build-trimmed-linux +* verify-bugs-and-bisect-regressions +* bug-hunting +* bug-bisect +* tainted-kernels +* ramoops +* dynamic-debug-howto +* init +* kdump/index +* perf/index +* pstore-blk +* clearing-warn-once +* kernel-per-CPU-kthreads +* lockup-watchdogs +* RAS/index +* sysrq + +Subsistemas centrais do kernel +------------------------------ + +Estes documentos descrevem interfaces de administração dos subsistemas +centrais do kernel, que provavelmente são de interesse em quase qualquer +sistema. + +Todolist: + +* cgroup-v2 +* cgroup-v1/index +* cpu-isolation +* cpu-load +* mm/index +* module-signing +* namespaces/index +* numastat +* pm/index +* syscall-user-dispatch + +Suporte a formatos binários não nativos. Observe que alguns destes +documentos são ... antigos ... + +Todolist: + +* binfmt-misc +* java +* mono + +Administração da camada de blocos e de sistemas de arquivos +----------------------------------------------------------- + +Todolist: + +* bcache +* binderfs +* blockdev/index +* cifs/index +* device-mapper/index +* ext4 +* filesystem-monitoring +* nfs/index +* iostats +* jfs +* md +* ufs +* xfs + +Guias específicos de dispositivos +--------------------------------- + +Como configurar o seu hardware dentro do sistema Linux. + +Todolist: + +* acpi/index +* aoe/index +* auxdisplay/index +* braille-console +* btmrvl +* dell_rbu +* edid +* gpio/index +* hw_random +* laptops/index +* lcd-panel-cgram +* media/index +* nvme-multipath +* parport +* pnp +* rapidio +* rtc +* serial-console +* svga +* thermal/index +* thunderbolt +* vga-softcursor +* video-output + +Análise de carga de trabalho +---------------------------- + +Este é o início de uma seção com informações de interesse para +desenvolvedores de aplicações e integradores de sistemas que fazem análise +do kernel Linux para aplicações críticas de segurança. Documentos que dão +suporte à análise das interações do kernel com as aplicações, e às +expectativas dos principais subsistemas do kernel, serão encontrados aqui. + +Todolist: + +* workload-tracing + +Todo o resto +------------ + +Alguns documentos difíceis de categorizar e geralmente obsoletos. + +Todolist: + +* ldm +* unicode diff --git a/Documentation/translations/pt_BR/index.rst b/Documentation/translations/pt_BR/index.rst index 462267e92f2f2a..5557c5e4e25012 100644 --- a/Documentation/translations/pt_BR/index.rst +++ b/Documentation/translations/pt_BR/index.rst @@ -67,3 +67,13 @@ kernel e sobre como ver seu trabalho integrado. :maxdepth: 1 process/index + +Guias do usuário e do administrador +=================================== + +Os guias para usuários e administradores do kernel Linux. + +.. toctree:: + :maxdepth: 1 + + admin-guide/index From ef7eb3a467ec47add95301dc88416520a39f46f5 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 16 Sep 2026 14:41:33 -0300 Subject: [PATCH 0963/2286] docs: translations: pt_BR: translate admin-guide/README.rst Translate the kernel README into Brazilian Portuguese and move it from the Todolist into the toctree of the admin-guide index. References to changes.rst and applying-patches.rst point at the pt_BR translations, since both are already translated. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916174136.48011-4-danielmaraboo@gmail.com> --- .../translations/pt_BR/admin-guide/README.rst | 382 ++++++++++++++++++ .../translations/pt_BR/admin-guide/index.rst | 6 +- 2 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/pt_BR/admin-guide/README.rst diff --git a/Documentation/translations/pt_BR/admin-guide/README.rst b/Documentation/translations/pt_BR/admin-guide/README.rst new file mode 100644 index 00000000000000..1843f89b12ccfa --- /dev/null +++ b/Documentation/translations/pt_BR/admin-guide/README.rst @@ -0,0 +1,382 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _pt_BR_readme: + +Versão 6.x do kernel Linux +=============================================== + +Estas são as notas de lançamento da versão 6 do Linux. Leia-as com atenção, +pois elas dizem do que se trata tudo isso, explicam como instalar o kernel e +o que fazer se algo der errado. + +O que é o Linux? +---------------- + + O Linux é um clone do sistema operacional Unix, escrito do zero por Linus + Torvalds com a ajuda de uma equipe pouco organizada de hackers espalhados + pela Internet. Ele busca a conformidade com o POSIX e com a Single UNIX + Specification. + + Possui todos os recursos que você esperaria de um Unix moderno e completo, + incluindo multitarefa real, memória virtual, bibliotecas compartilhadas, + carregamento sob demanda (demand loading), executáveis compartilhados com + cópia-na-escrita (copy-on-write), gerenciamento de memória adequado e rede + multipilha, incluindo IPv4 e IPv6. + + É distribuído sob a GNU General Public License v2 --- veja o arquivo + COPYING que o acompanha para mais detalhes. + +Em qual hardware ele roda? +-------------------------- + + Embora tenha sido originalmente desenvolvido primeiro para PCs de 32 bits + baseados em x86 (386 ou superior), hoje o Linux também roda (pelo menos) + nas arquiteturas Compaq Alpha AXP, Sun SPARC e UltraSPARC, Motorola 68000, + PowerPC, PowerPC64, ARM, Hitachi SuperH, Cell, IBM S/390, MIPS, HP PA-RISC, + Intel IA-64, DEC VAX, AMD x86-64 Xtensa e ARC. + + O Linux é facilmente portável para a maioria das arquiteturas de propósito + geral de 32 ou 64 bits, desde que possuam uma unidade de gerenciamento de + memória paginada (PMMU) e um port do compilador C da GNU (gcc), parte da + GNU Compiler Collection (GCC). O Linux também já foi portado para diversas + arquiteturas sem PMMU, embora a funcionalidade fique, obviamente, um tanto + limitada. + O Linux também foi portado para si mesmo. Você pode agora executar o kernel + como uma aplicação de espaço de usuário --- isso se chama UserMode Linux + (UML). + +Documentação +------------ + + - Há muita documentação disponível, tanto em formato eletrônico na Internet + quanto em livros, tanto específica do Linux quanto referente a questões + gerais do UNIX. Eu recomendaria procurar nos subdiretórios de documentação + de qualquer site FTP do Linux pelos livros do LDP (Linux Documentation + Project). Este README não pretende ser a documentação do sistema: existem + fontes muito melhores disponíveis. + + - Existem vários arquivos README no subdiretório Documentation/: eles + normalmente contêm notas de instalação específicas do kernel para alguns + drivers, por exemplo. Por favor, leia o arquivo + Documentation/translations/pt_BR/process/changes.rst, pois ele contém + informações sobre os problemas que podem resultar da atualização do seu + kernel. + +Instalando o código-fonte do kernel +----------------------------------- + + - Se você instalar as fontes completas, coloque o tarball do kernel em um + diretório no qual você tenha permissões (por exemplo, o seu diretório + pessoal) e o descompacte:: + + xz -cd linux-6.x.tar.xz | tar xvf - + + Substitua "X" pelo número da versão do kernel mais recente. + + NÃO use a área /usr/src/linux! Essa área possui um conjunto (geralmente + incompleto) de cabeçalhos do kernel que são usados pelos arquivos de + cabeçalho da biblioteca. Eles devem corresponder à biblioteca, e não ser + bagunçados por qualquer que seja o kernel-do-dia. + + - Você também pode atualizar entre versões 6.x aplicando patches. Os patches + são distribuídos no formato xz. Para instalar aplicando patches, obtenha + todos os arquivos de patch mais novos, entre no diretório de nível + superior do código-fonte do kernel (linux-6.x) e execute:: + + xz -cd ../patch-6.x.xz | patch -p1 + + Substitua "x" para todas as versões maiores que a versão "x" da sua árvore + de fontes atual, **em ordem**, e deve dar tudo certo. Talvez você queira + remover os arquivos de backup (algum-nome-de-arquivo~ ou + algum-nome-de-arquivo.orig) e certificar-se de que não há patches que + falharam (algum-nome-de-arquivo# ou algum-nome-de-arquivo.rej). Se + houver, ou você ou eu cometemos um erro. + + Diferentemente dos patches para os kernels 6.x, os patches para os kernels + 6.x.y (também conhecidos como kernels -stable) não são incrementais; em vez + disso, eles se aplicam diretamente ao kernel 6.x base. Por exemplo, se o + seu kernel base é o 6.0 e você quer aplicar o patch 6.0.3, você não deve + aplicar antes os patches 6.0.1 e 6.0.2. Da mesma forma, se você está + rodando a versão 6.0.2 do kernel e quer saltar para a 6.0.3, você deve + primeiro reverter o patch 6.0.2 (ou seja, patch -R) **antes** de aplicar o + patch 6.0.3. Você pode ler mais sobre isso em + Documentation/translations/pt_BR/process/applying-patches.rst. + + Como alternativa, o script patch-kernel pode ser usado para automatizar + esse processo. Ele determina a versão atual do kernel e aplica quaisquer + patches encontrados:: + + linux/scripts/patch-kernel linux + + O primeiro argumento no comando acima é a localização do código-fonte do + kernel. Os patches são aplicados a partir do diretório atual, mas um + diretório alternativo pode ser especificado como segundo argumento. + + - Certifique-se de que não há arquivos .o e dependências obsoletos espalhados + por aí:: + + cd linux + make mrproper + + Agora você deve ter as fontes instaladas corretamente. + +Requisitos de software +---------------------- + + Compilar e executar os kernels 6.x exige versões atualizadas de vários + pacotes de software. Consulte + Documentation/translations/pt_BR/process/changes.rst para os números de + versão mínimos exigidos e como obter atualizações desses pacotes. Esteja + ciente de que usar versões excessivamente antigas desses pacotes pode + causar erros indiretos, muito difíceis de rastrear; portanto, não presuma + que basta atualizar os pacotes quando problemas óbvios surgirem durante a + compilação ou a execução. + +Diretório de compilação do kernel +--------------------------------- + + Ao compilar o kernel, todos os arquivos de saída serão, por padrão, + armazenados junto com o código-fonte do kernel. + Usar a opção ``make O=output/dir`` permite especificar um local alternativo + para os arquivos de saída (incluindo o .config). + Exemplo:: + + código-fonte do kernel: /usr/src/linux-6.x + diretório de compilação: /home/nome/build/kernel + + Para configurar e compilar o kernel, use:: + + cd /usr/src/linux-6.x + make O=/home/nome/build/kernel menuconfig + make O=/home/nome/build/kernel + sudo make O=/home/nome/build/kernel modules_install install + + Observe: se a opção ``O=output/dir`` for usada, ela deve ser usada em todas + as invocações do make. + +Configurando o kernel +--------------------- + + Não pule esta etapa, mesmo que você esteja atualizando apenas uma versão + menor. Novas opções de configuração são adicionadas a cada lançamento, e + problemas estranhos aparecerão se os arquivos de configuração não estiverem + preparados como esperado. Se você quiser levar a sua configuração existente + para uma nova versão com o mínimo de trabalho, use ``make oldconfig``, que + perguntará apenas as respostas para as novas questões. + + - Comandos alternativos de configuração são:: + + "make config" Interface em texto puro. + + "make menuconfig" Menus coloridos, listas de seleção e diálogos, em + modo texto. + + "make nconfig" Menus coloridos aprimorados, em modo texto. + + "make xconfig" Ferramenta de configuração baseada em Qt. + + "make gconfig" Ferramenta de configuração baseada em GTK. + + "make oldconfig" Assume o padrão para todas as questões com base no + conteúdo do seu arquivo ./.config existente e + pergunta sobre os novos símbolos de configuração. + + "make olddefconfig" + Como o anterior, mas define os novos símbolos com + seus valores padrão, sem perguntar. + + "make defconfig" Cria um arquivo ./.config usando os valores padrão + dos símbolos, vindos de + arch/$ARCH/configs/defconfig ou de + arch/$ARCH/configs/${PLATFORM}_defconfig, + dependendo da arquitetura. + + "make ${PLATFORM}_defconfig" + Cria um arquivo ./.config usando os valores padrão + dos símbolos vindos de + arch/$ARCH/configs/${PLATFORM}_defconfig. + Use "make help" para obter uma lista de todas as + plataformas disponíveis para a sua arquitetura. + + "make allyesconfig" + Cria um arquivo ./.config definindo os valores dos + símbolos como 'y' sempre que possível. + + "make allmodconfig" + Cria um arquivo ./.config definindo os valores dos + símbolos como 'm' sempre que possível. + + "make allnoconfig" Cria um arquivo ./.config definindo os valores dos + símbolos como 'n' sempre que possível. + + "make randconfig" Cria um arquivo ./.config definindo os valores dos + símbolos aleatoriamente. + + "make localmodconfig" Cria uma configuração baseada na configuração + atual e nos módulos carregados (lsmod). Desativa + qualquer opção de módulo que não seja necessária + para os módulos carregados. + + Para criar um localmodconfig para outra máquina, + armazene o lsmod daquela máquina em um arquivo e + o passe como parâmetro LSMOD. + + Além disso, você pode preservar módulos em certas + pastas ou arquivos kconfig especificando seus + caminhos no parâmetro LMC_KEEP. + + alvo$ lsmod > /tmp/mylsmod + alvo$ scp /tmp/mylsmod host:/tmp + + host$ make LSMOD=/tmp/mylsmod \ + LMC_KEEP="drivers/usb:drivers/gpu:fs" \ + localmodconfig + + O acima também funciona em compilação cruzada. + + "make localyesconfig" Semelhante ao localmodconfig, exceto que converte + todas as opções de módulo em opções embutidas + (=y). Você também pode preservar módulos com o + LMC_KEEP. + + "make kvm_guest.config" Habilita opções adicionais para suporte a + kernel convidado do kvm. + + "make xen.config" Habilita opções adicionais para suporte a kernel + convidado dom0 do xen. + + "make tinyconfig" Configura o menor kernel possível. + + Você pode encontrar mais informações sobre o uso das ferramentas de + configuração do kernel Linux em Documentation/kbuild/kconfig.rst. + + - NOTAS sobre o ``make config``: + + - Ter drivers desnecessários deixará o kernel maior e, em algumas + circunstâncias, pode levar a problemas: sondar uma placa controladora + inexistente pode confundir as suas outras controladoras. + + - Um kernel com emulação matemática compilada ainda usará o + coprocessador, se houver um presente: a emulação matemática + simplesmente nunca será usada nesse caso. O kernel ficará um pouco + maior, mas funcionará em máquinas diferentes, independentemente de + terem ou não um coprocessador matemático. + + - Os detalhes de configuração de "kernel hacking" normalmente resultam em + um kernel maior ou mais lento (ou ambos), e podem até tornar o kernel + menos estável, ao configurar algumas rotinas para tentar ativamente + quebrar código ruim e encontrar problemas no kernel (kmalloc()). + Portanto, você provavelmente deve responder 'n' às questões sobre + recursos de "development", "experimental" ou "debugging". + +Compilando o kernel +------------------- + + - Certifique-se de ter pelo menos o gcc 8.1 disponível. + Para mais informações, consulte + Documentation/translations/pt_BR/process/changes.rst. + + - Execute um ``make`` para criar uma imagem compactada do kernel. Também é + possível executar ``make install`` se você tiver o lilo instalado ou se a + sua distribuição possuir um script de instalação reconhecido pelo + instalador do kernel. A maioria das distribuições populares terá um script + de instalação reconhecido. Talvez você queira verificar antes a + configuração da sua distribuição. + + Para fazer a instalação de fato, você precisa ser root, mas nada da + compilação normal deve exigir isso. Não tome o nome de root em vão. + + - Se você configurou qualquer parte do kernel como ``modules``, também terá + que executar ``make modules_install``. + + - Saída detalhada (verbose) da compilação do kernel: + + Normalmente, o sistema de compilação do kernel roda em um modo bastante + silencioso (mas não totalmente). No entanto, às vezes você ou outros + desenvolvedores do kernel precisam ver os comandos de compilação, de + ligação (link) ou outros exatamente como são executados. Para isso, use o + modo de compilação "verbose". Isso é feito passando ``V=1`` ao comando + ``make``, por exemplo:: + + make V=1 all + + Para que o sistema de compilação também informe o motivo da recompilação + de cada alvo, use ``V=2``. O padrão é ``V=0``. + + - Mantenha um kernel de backup à mão, caso algo dê errado. Isso é + especialmente verdadeiro para as versões de desenvolvimento, já que cada + novo lançamento contém código novo que não foi depurado. Certifique-se de + manter também um backup dos módulos correspondentes àquele kernel. Se + você estiver instalando um novo kernel com o mesmo número de versão do seu + kernel em funcionamento, faça um backup do seu diretório de módulos antes + de executar um ``make modules_install``. + + Como alternativa, antes de compilar, use a opção de configuração do kernel + "LOCALVERSION" para acrescentar um sufixo único à versão normal do kernel. + A LOCALVERSION pode ser definida no menu "General Setup". + + - Para inicializar o seu novo kernel, você precisará copiar a imagem do + kernel (por exemplo, .../linux/arch/x86/boot/bzImage após a compilação) + para o local onde o seu kernel inicializável habitual se encontra. + + - Inicializar um kernel diretamente de um dispositivo de armazenamento, sem + a ajuda de um gerenciador de inicialização como o LILO ou o GRUB, não é + mais suportado na BIOS (sistemas não EFI). Em sistemas UEFI/EFI, no + entanto, você pode usar o EFISTUB, que permite à placa-mãe inicializar + diretamente no kernel. Em estações de trabalho e desktops modernos, + geralmente recomenda-se usar um gerenciador de inicialização, pois podem + surgir dificuldades com múltiplos kernels e com o secure boot. + Para mais detalhes sobre o EFISTUB, veja + "Documentation/admin-guide/efi-stub.rst". + + - É importante observar que, desde 2016, o LILO (LInux LOader) não está mais + em desenvolvimento ativo, embora, por ter sido extremamente popular, + apareça com frequência na documentação. Alternativas populares incluem + GRUB2, rEFInd, Syslinux, systemd-boot ou EFISTUB. Por diversas razões, não + é recomendável usar software que não esteja mais em desenvolvimento ativo. + + - É provável que a sua distribuição inclua um script de instalação e que + executar ``make install`` seja tudo o que é necessário. Caso não seja + assim, você terá que identificar o seu gerenciador de inicialização e + consultar a documentação dele, ou configurar a sua EFI. + +Instruções legadas do LILO +-------------------------- + + + - Se você usa o LILO, as imagens do kernel são especificadas no arquivo + /etc/lilo.conf. O arquivo de imagem do kernel geralmente é /vmlinuz, + /boot/vmlinuz, /bzImage ou /boot/bzImage. Para usar o novo kernel, salve + uma cópia da imagem antiga e copie a nova imagem por cima da antiga. + Então, você DEVE EXECUTAR O LILO NOVAMENTE para atualizar o mapa de + carregamento! Se não fizer isso, não conseguirá inicializar a nova imagem + do kernel. + + - Reinstalar o LILO geralmente é uma questão de executar /sbin/lilo. Talvez + você queira editar o /etc/lilo.conf para especificar uma entrada para a + sua imagem antiga do kernel (digamos, /vmlinux.old), caso a nova não + funcione. Veja a documentação do LILO para mais informações. + + - Após reinstalar o LILO, deve estar tudo pronto. Desligue o sistema, + reinicie e aproveite! + + - Se algum dia você precisar alterar o dispositivo raiz padrão, o modo de + vídeo, etc. na imagem do kernel, use as opções de inicialização do seu + gerenciador de inicialização onde for apropriado. Não é necessário + recompilar o kernel para alterar esses parâmetros. + + - Reinicie com o novo kernel e aproveite. + + +Se algo der errado +------------------ + +Se você tiver problemas que pareçam ser causados por bugs do kernel, por +favor, siga as instruções em +'Documentation/admin-guide/reporting-issues.rst'. + +Dicas sobre como entender os relatórios de bugs do kernel estão em +'Documentation/admin-guide/bug-hunting.rst'. Mais sobre depuração do kernel +com o gdb está em +'Documentation/process/debugging/gdb-kernel-debugging.rst' e +'Documentation/process/debugging/kgdb.rst'. diff --git a/Documentation/translations/pt_BR/admin-guide/index.rst b/Documentation/translations/pt_BR/admin-guide/index.rst index a5ad5baa3aeff9..716f6c1f7de81e 100644 --- a/Documentation/translations/pt_BR/admin-guide/index.rst +++ b/Documentation/translations/pt_BR/admin-guide/index.rst @@ -21,9 +21,13 @@ Esta seção inicial contém informações gerais, incluindo o arquivo README qu descreve o kernel como um todo, documentação sobre os parâmetros do kernel, etc. +.. toctree:: + :maxdepth: 1 + + README + Todolist: -* README * devices * features From a3c3209b0785bbb350e637d34e5a05d6f79a1d08 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 16 Sep 2026 14:41:34 -0300 Subject: [PATCH 0964/2286] docs: translations: pt_BR: translate admin-guide/devices.rst Translate the Linux Device List document into Brazilian Portuguese and move it from the Todolist into the toctree of the admin-guide index. The device registry itself is pulled in with a literal include of the original devices.txt, so the numbers stay in a single place. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916174136.48011-5-danielmaraboo@gmail.com> --- .../pt_BR/admin-guide/devices.rst | 278 ++++++++++++++++++ .../translations/pt_BR/admin-guide/index.rst | 2 +- 2 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 Documentation/translations/pt_BR/admin-guide/devices.rst diff --git a/Documentation/translations/pt_BR/admin-guide/devices.rst b/Documentation/translations/pt_BR/admin-guide/devices.rst new file mode 100644 index 00000000000000..614a32c33968e2 --- /dev/null +++ b/Documentation/translations/pt_BR/admin-guide/devices.rst @@ -0,0 +1,278 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. _pt_BR_admin_devices: + +Dispositivos alocados no Linux (versão 4.x+) +============================================ + +Esta lista é a Linux Device List, o registro oficial dos números de +dispositivo alocados e dos nós do diretório ``/dev`` para o sistema +operacional Linux. + +A versão deste documento em lanana.org não é mais mantida. Esta versão, no +kernel Linux mainline, é o documento mestre. Atualizações devem ser enviadas +como patches aos mantenedores do kernel (veja o documento +:ref:`Documentation/translations/pt_BR/process/submitting-patches.rst `). +Explore especificamente as seções intituladas "CHAR and MISC DRIVERS" e +"BLOCK LAYER" no arquivo MAINTAINERS para encontrar os mantenedores certos +a envolver para dispositivos de caractere e de bloco. + +Este documento é incluído por referência no Filesystem Hierarchy Standard +(FHS). O FHS está disponível em https://www.pathname.com/fhs/. + +Alocações marcadas com (68k/Amiga) aplicam-se somente ao Linux/68k na +plataforma Amiga. Alocações marcadas com (68k/Atari) aplicam-se somente ao +Linux/68k na plataforma Atari. + +Este documento está em domínio público. Os autores solicitam, no entanto, +que versões semanticamente alteradas não sejam distribuídas sem permissão +dos autores, presumindo-se que os autores possam ser contatados sem um +esforço desarrazoado. + + +.. attention:: + + AUTORES DE DRIVERS DE DISPOSITIVO, POR FAVOR LEIAM ISTO + + O Linux agora possui amplo suporte à alocação dinâmica de numeração de + dispositivos e pode usar ``sysfs`` e ``udev`` (``systemd``) para lidar com + as necessidades de nomenclatura. Ainda existem algumas exceções na área de + dispositivos seriais e de inicialização. Antes de pedir um número de + dispositivo, certifique-se de que você realmente precisa de um. + + Para ter um número maior (major) alocado, ou um número menor (minor) nas + situações em que isso se aplica (por exemplo, busmice), por favor envie um + patch aos autores conforme indicado acima. + + Mantenha a descrição do dispositivo *no mesmo formato desta lista*. A razão + para isso é que essa é a única maneira que encontramos de garantir que + temos todas as informações necessárias para publicar o seu dispositivo e + evitar conflitos. + + Por fim, às vezes temos que bancar a "polícia do espaço de nomes". Por + favor, não se ofenda. Frequentemente recebemos submissões de nomes em + ``/dev`` que fatalmente causariam conflitos no futuro. Estamos tentando + evitar chegar a uma situação em que teríamos que sofrer uma mudança + incompatível para a frente. Portanto, por favor, consulte-nos **antes** de + tornar públicos, de qualquer forma, os nomes e números do seu dispositivo + --- pelo menos até o ponto em que seria minimamente difícil alterá-los. + + Sua cooperação é apreciada. + +.. include:: ../../../admin-guide/devices.txt + :literal: + +Entradas adicionais do diretório ``/dev/`` +------------------------------------------ + +Esta seção detalha as entradas adicionais que devem ou podem existir no +diretório /dev. É preferível que os links simbólicos usem a mesma forma +(absoluta ou relativa) indicada aqui. Os links são classificados como +"físicos" (hard) ou "simbólicos", dependendo do tipo de link preferido; se +possível, o tipo de link indicado deve ser usado. + +Links obrigatórios +++++++++++++++++++ + +Estes links devem existir em todos os sistemas: + +=============== =============== =============== ============================== +/dev/fd /proc/self/fd simbólico Descritores de arquivo +/dev/stdin fd/0 simbólico Descritor de arquivo da stdin +/dev/stdout fd/1 simbólico Descritor de arquivo da stdout +/dev/stderr fd/2 simbólico Descritor de arquivo da stderr +/dev/nfsd socksys simbólico Exigido pelo iBCS-2 +/dev/X0R null simbólico Exigido pelo iBCS-2 +=============== =============== =============== ============================== + +Observação: ``/dev/X0R`` é --. + +Links recomendados +++++++++++++++++++ + +Recomenda-se que estes links existam em todos os sistemas: + + +=============== =============== =============== ========================== +/dev/core /proc/kcore simbólico Compatibilidade retroativa +/dev/ramdisk ram0 simbólico Compatibilidade retroativa +/dev/ftape qft0 simbólico Compatibilidade retroativa +/dev/bttv0 video0 simbólico Compatibilidade retroativa +/dev/radio radio0 simbólico Compatibilidade retroativa +/dev/i2o* /dev/i2o/* simbólico Compatibilidade retroativa +=============== =============== =============== ========================== + +Os nomes alternativos ``/dev/scd?``, sugeridos anteriormente para os +``/dev/sr?`` de CD-ROM e outras unidades ópticas (que usam comandos SCSI), +foram removidos na versão 174 do ``udev``, lançada em 2011. + +Links definidos localmente +++++++++++++++++++++++++++ + +Os links a seguir podem ser estabelecidos localmente para se adequar à +configuração do sistema. Isto é meramente uma tabulação da prática +existente, e não constitui uma recomendação. No entanto, se existirem, eles +devem ter os seguintes usos. + +=============== ================= =============== ============================== +/dev/mouse porta de mouse simbólico Dispositivo de mouse atual +/dev/tape unidade de fita simbólico Dispositivo de fita atual +/dev/cdrom unidade de CD-ROM simbólico Dispositivo de CD-ROM atual +/dev/scanner scanner simbólico Scanner atual +/dev/modem porta de modem simbólico Dispositivo de discagem atual +/dev/root dispositivo raiz simbólico Sistema de arquivos raiz atual +/dev/swap área de swap simbólico Dispositivo de swap atual +=============== ================= =============== ============================== + +O ``/dev/modem`` não deve ser usado para um modem que suporte tanto discagem +de entrada quanto de saída, pois isso tende a causar problemas com arquivos +de trava (lock). Se existir, o ``/dev/modem`` deve apontar para o +dispositivo TTY primário apropriado (o uso dos dispositivos alternativos de +saída é obsoleto). + +Para dispositivos SCSI, o ``/dev/tape`` e o ``/dev/cdrom`` devem apontar +para os dispositivos *cozidos* (*cooked*) (``/dev/st*`` e ``/dev/sr*``, +respectivamente), enquanto o ``/dev/scanner`` deve apontar para o +dispositivo SCSI genérico apropriado (``/dev/sg*``). + +O ``/dev/mouse`` pode apontar para um dispositivo TTY serial primário, um +dispositivo de mouse em hardware, ou um socket para um programa de driver de +mouse (por exemplo, ``/dev/gpmdata``). + +Sockets e pipes ++++++++++++++++ + +Sockets e pipes nomeados não transitórios podem existir em /dev. As entradas +comuns são: + +=============== ======== ============================= +/dev/printer socket socket local do lpd +/dev/log socket socket local do syslog +/dev/gpmdata socket multiplexador de mouse do gpm +=============== ======== ============================= + +Pontos de montagem +++++++++++++++++++ + +Os nomes a seguir são reservados para a montagem de sistemas de arquivos +especiais sob /dev. Esses sistemas de arquivos especiais fornecem interfaces +do kernel que não podem ser fornecidas com nós de dispositivo padrão. + +=============== ======== ================================================== +/dev/pts devpts Sistema de arquivos de escravos PTY +/dev/shm tmpfs Acesso de manutenção à memória compartilhada POSIX +=============== ======== ================================================== + +Dispositivos de terminal +------------------------ + +Dispositivos de terminal, ou TTY, são uma classe especial de dispositivos de +caractere. Um dispositivo de terminal é qualquer dispositivo que possa atuar +como terminal de controle de uma sessão; isso inclui consoles virtuais, +portas seriais e pseudoterminais (PTYs). + +Todos os dispositivos de terminal compartilham um conjunto comum de +capacidades conhecidas como disciplinas de linha (line disciplines); estas +incluem a disciplina de linha de terminal comum, bem como os modos SLIP e +PPP. + +Todos os dispositivos de terminal são nomeados de forma semelhante; esta +seção explica a nomenclatura e o uso dos vários tipos de TTY. Observe que as +convenções de nomenclatura incluem várias verrugas históricas; algumas delas +são específicas do Linux, algumas foram herdadas de outros sistemas, e +algumas refletem o Linux tendo superado uma convenção emprestada. + +Um sinal de cerquilha (``#``) em um nome de dispositivo é usado aqui para +indicar um número decimal sem zeros à esquerda. + +Consoles virtuais e o dispositivo de console +++++++++++++++++++++++++++++++++++++++++++++ + +Consoles virtuais são telas de terminal em tela cheia no monitor de vídeo do +sistema. Consoles virtuais são nomeados ``/dev/tty#``, com a numeração +começando em ``/dev/tty1``; o ``/dev/tty0`` é o console virtual atual. +O ``/dev/tty0`` é o dispositivo que deve ser usado para acessar a placa de +vídeo do sistema naquelas arquiteturas para as quais os dispositivos de +frame buffer (``/dev/fb*``) não são aplicáveis. Não use o ``/dev/console`` +para esse fim. + +O dispositivo de console, ``/dev/console``, é o dispositivo para o qual as +mensagens do sistema devem ser enviadas, e no qual os logins devem ser +permitidos em modo monousuário. A partir do Linux 2.1.71, o ``/dev/console`` +é gerenciado pelo kernel; para versões anteriores, ele deve ser um link +simbólico para o ``/dev/tty0``, para um console virtual específico como o +``/dev/tty1``, ou para um dispositivo primário de porta serial (``tty*``, +não ``cu*``), dependendo da configuração do sistema. + +Portas seriais +++++++++++++++ + +Portas seriais são portas seriais RS-232 e qualquer dispositivo que simule +uma, seja em hardware (como modems internos) ou em software (como o driver +ISDN). No Linux, cada porta serial possui dois nomes de dispositivo: o +primário, ou de chamada de entrada (callin), e o alternativo, ou de chamada +de saída (callout). Cada tipo de dispositivo é indicado por uma letra +diferente. Para qualquer letra X, os nomes dos dispositivos são +``/dev/ttyX#`` e ``/dev/cux#``, respectivamente; por razões históricas, o +``/dev/ttyS#`` e o ``/dev/ttyC#`` correspondem ao ``/dev/cua#`` e ao +``/dev/cub#``. No futuro, deve-se esperar que múltiplas letras sejam usadas; +todas as letras serão maiúsculas para o dispositivo "tty" (por exemplo, +``/dev/ttyDP#``) e minúsculas para o dispositivo "cu" (por exemplo, +``/dev/cudp#``). + +Os nomes ``/dev/ttyQ#`` e ``/dev/cuq#`` são reservados para uso local. + +Os dispositivos alternativos provêem exclusão baseada no kernel e padrões um +tanto diferentes dos dispositivos primários. Seu principal propósito é +permitir o uso de portas seriais com programas sem suporte inerente a portas +seriais, ou com suporte defeituoso. Seu uso é obsoleto, e eles podem ser +removidos em uma versão futura do Linux. + +A arbitragem de portas seriais é provida pelo uso de arquivos de trava com +os nomes ``/var/lock/LCK..ttyX#``. O conteúdo do arquivo de trava deve ser o +PID do processo que está travando, como um número ASCII. + +É prática comum instalar links como /dev/modem, que apontam para portas +seriais. Para garantir o travamento adequado na presença desses links, +recomenda-se que o software persiga os links simbólicos e trave todos os +nomes possíveis; adicionalmente, recomenda-se que um arquivo de trava seja +instalado com o dispositivo alternativo correspondente. Para evitar +impasses (deadlocks), recomenda-se que as travas sejam adquiridas na +seguinte ordem, e liberadas na ordem inversa: + + 1. O nome do link simbólico, se houver (``/var/lock/LCK..modem``) + 2. O nome "tty" (``/var/lock/LCK..ttyS2``) + 3. O nome do dispositivo alternativo (``/var/lock/LCK..cua2``) + +No caso de links simbólicos aninhados, os arquivos de trava devem ser +instalados na ordem em que os links simbólicos são resolvidos. + +Sob nenhuma circunstância uma aplicação deve manter uma trava enquanto +espera que outra seja liberada. Além disso, aplicações que tentam criar +arquivos de trava para os nomes de dispositivo alternativos correspondentes +devem levar em conta a possibilidade de serem usadas em um TTY que não seja +de porta serial, para o qual nenhum dispositivo alternativo existiria. + +Pseudoterminais (PTYs) +++++++++++++++++++++++ + +Pseudoterminais, ou PTYs, são usados para criar sessões de login ou para +prover outras capacidades que exijam uma disciplina de linha TTY (incluindo +capacidade de SLIP ou PPP) a processos arbitrários geradores de dados. Cada +PTY tem um lado mestre, nomeado ``/dev/pty[p-za-e][0-9a-f]``, e um lado +escravo, nomeado ``/dev/tty[p-za-e][0-9a-f]``. O kernel arbitra o uso dos +PTYs permitindo que cada lado mestre seja aberto apenas uma vez. + +Uma vez que o lado mestre tenha sido aberto, o dispositivo escravo +correspondente pode ser usado da mesma maneira que qualquer dispositivo TTY. +Os dispositivos mestre e escravo são conectados pelo kernel, gerando o +equivalente a um pipe bidirecional com capacidades de TTY. + +Versões recentes dos kernels Linux e da GNU libc contêm suporte ao esquema +de nomenclatura System V/Unix98 para PTYs, que atribui um dispositivo comum, +``/dev/ptmx``, a todos os mestres (abri-lo lhe dará automaticamente um PTY +não atribuído anteriormente) e um subdiretório, ``/dev/pts``, para os +escravos; os escravos são nomeados com inteiros decimais (``/dev/pts/#`` em +nossa notação). Isso remove o problema de esgotar o espaço de nomes e +permite que o kernel crie automaticamente os nós de dispositivo para os +escravos sob demanda, usando o sistema de arquivos "devpts". diff --git a/Documentation/translations/pt_BR/admin-guide/index.rst b/Documentation/translations/pt_BR/admin-guide/index.rst index 716f6c1f7de81e..471b76f00e006b 100644 --- a/Documentation/translations/pt_BR/admin-guide/index.rst +++ b/Documentation/translations/pt_BR/admin-guide/index.rst @@ -25,10 +25,10 @@ etc. :maxdepth: 1 README + devices Todolist: -* devices * features Uma grande parte da interface administrativa do kernel são os sistemas de From 105c5d8775026712869e205c2ed025187566f9f8 Mon Sep 17 00:00:00 2001 From: Daniel Pereira Date: Wed, 16 Sep 2026 14:41:35 -0300 Subject: [PATCH 0965/2286] docs: translations: pt_BR: point howto.rst at the translated documents The reading list in howto.rst referenced the English version of documents that are already translated, so a reader of the translation was sent back to the originals. Add the four missing pt_BR anchors and repoint the nine references that have a translation available; the remaining three have no pt_BR counterpart and are left alone. Signed-off-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916174136.48011-6-danielmaraboo@gmail.com> --- .../pt_BR/process/applying-patches.rst | 2 ++ .../translations/pt_BR/process/howto.rst | 19 ++++++++++--------- .../pt_BR/process/kernel-docs.rst | 2 ++ .../pt_BR/process/license-rules.rst | 2 ++ .../pt_BR/process/management-style.rst | 2 ++ 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Documentation/translations/pt_BR/process/applying-patches.rst b/Documentation/translations/pt_BR/process/applying-patches.rst index 37a8d804d0c720..143603b991f777 100644 --- a/Documentation/translations/pt_BR/process/applying-patches.rst +++ b/Documentation/translations/pt_BR/process/applying-patches.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_applying_patches: + Aplicando Patches ao Kernel Linux +++++++++++++++++++++++++++++++++ diff --git a/Documentation/translations/pt_BR/process/howto.rst b/Documentation/translations/pt_BR/process/howto.rst index bcedee7273fd87..54ea7fad8d7534 100644 --- a/Documentation/translations/pt_BR/process/howto.rst +++ b/Documentation/translations/pt_BR/process/howto.rst @@ -67,7 +67,8 @@ Questões Legais O código-fonte do kernel Linux é lançado sob a GPL. Por favor, veja o arquivo COPYING no diretório principal da árvore de fontes. As regras de licenciamento do kernel Linux e como usar os identificadores `SPDX `_ no -código-fonte estão descritas em :ref:`Documentation/process/license-rules.rst `. +código-fonte estão descritas em +:ref:`Documentation/translations/pt_BR/process/license-rules.rst `. Se você tiver mais perguntas sobre a licença, por favor, entre em contato com um advogado e não pergunte na lista de discussão do kernel Linux. As pessoas nas listas de discussão não são advogados e você não deve confiar em suas @@ -94,7 +95,7 @@ a lista linux-api@vger.kernel.org. Aqui está uma lista de arquivos que estão na árvore de fontes do kernel e que são de leitura obrigatória: - :ref:`Documentation/admin-guide/README.rst ` + :ref:`Documentation/translations/pt_BR/admin-guide/README.rst ` Este arquivo fornece um breve histórico sobre o kernel Linux e descreve o que é necessário fazer para configurar e compilar o kernel. Pessoas que são novas no kernel devem começar por aqui. @@ -104,14 +105,14 @@ que são de leitura obrigatória: software que são necessários para compilar e executar o kernel com sucesso. - :ref:`Documentation/process/coding-style.rst ` + :ref:`Documentation/translations/pt_BR/process/coding-style.rst ` Este documento descreve o estilo de codificação do kernel Linux e parte da fundamentação por trás dele. Espera-se que todo código novo siga as diretrizes deste documento. A maioria dos mantenedores apenas aceitará patches se essas regras forem seguidas, e muitas pessoas apenas revisarão o código se ele estiver no estilo adequado. - :ref:`Documentation/process/submitting-patches.rst ` + :ref:`Documentation/translations/pt_BR/process/submitting-patches.rst ` Este arquivo descreve em detalhes explícitos como criar e enviar um patch com sucesso, incluindo (mas não limitado a): @@ -144,12 +145,12 @@ Outras excelentes descrições de como criar patches adequadamente são: do Linux e é muito importante para pessoas que estão migrando para o Linux vindas do desenvolvimento em outros Sistemas Operacionais. - :ref:`Documentation/process/security-bugs.rst ` + :ref:`Documentation/translations/pt_BR/process/security-bugs.rst ` Se você acredita ter encontrado um problema de segurança no kernel Linux, por favor, siga os passos descritos neste documento para ajudar a notificar os desenvolvedores do kernel e auxiliar na resolução do problema. - :ref:`Documentation/process/management-style.rst ` + :ref:`Documentation/translations/pt_BR/process/management-style.rst ` Este documento descreve como os mantenedores do kernel Linux operam e o ethos compartilhado por trás de suas metodologias. Esta é uma leitura importante para qualquer pessoa nova no desenvolvimento do kernel (ou @@ -162,12 +163,12 @@ Outras excelentes descrições de como criar patches adequadamente são: versões estáveis (stable) do kernel e o que fazer se você desejar que uma alteração seja incluída em um desses lançamentos. - :ref:`Documentation/process/kernel-docs.rst ` + :ref:`Documentation/translations/pt_BR/process/kernel-docs.rst ` Uma lista de documentação externa que pertence ao desenvolvimento do kernel. Por favor, consulte esta lista caso não encontre o que está procurando dentro da documentação interna do kernel. - :ref:`Documentation/process/applying-patches.rst ` + :ref:`Documentation/translations/pt_BR/process/applying-patches.rst ` Uma boa introdução descrevendo exatamente o que é um patch e como aplicá-lo aos diferentes ramos (branches) de desenvolvimento do kernel. @@ -435,7 +436,7 @@ individualmente, em vez de escrever tudo no topo do e-mail. Se você adicionar patches ao seu e-mail, certifique-se de que sejam texto puro legível, conforme declarado em -:ref:`Documentation/process/submitting-patches.rst `. +:ref:`Documentation/translations/pt_BR/process/submitting-patches.rst `. Os desenvolvedores do kernel não querem lidar com anexos ou patches compactados; eles podem querer comentar linhas individuais do seu patch, o que só funciona dessa forma. Certifique-se de usar um programa de diff --git a/Documentation/translations/pt_BR/process/kernel-docs.rst b/Documentation/translations/pt_BR/process/kernel-docs.rst index 3c8d80ffa567f8..6235b1e7bde0de 100644 --- a/Documentation/translations/pt_BR/process/kernel-docs.rst +++ b/Documentation/translations/pt_BR/process/kernel-docs.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_kernel_docs: + Índice de Documentação Adicional do Kernel ========================================== diff --git a/Documentation/translations/pt_BR/process/license-rules.rst b/Documentation/translations/pt_BR/process/license-rules.rst index 1e395dfea8759c..7a801b2a4f4e2e 100644 --- a/Documentation/translations/pt_BR/process/license-rules.rst +++ b/Documentation/translations/pt_BR/process/license-rules.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_kernel_licensing: + Regras de licenciamento do kernel Linux ======================================= diff --git a/Documentation/translations/pt_BR/process/management-style.rst b/Documentation/translations/pt_BR/process/management-style.rst index c40b19b904f64d..9d067e04650a15 100644 --- a/Documentation/translations/pt_BR/process/management-style.rst +++ b/Documentation/translations/pt_BR/process/management-style.rst @@ -1,5 +1,7 @@ .. SPDX-License-Identifier: GPL-2.0 +.. _pt_BR_managementstyle: + Estilo de gerenciamento do kernel Linux ======================================= From f59db7fb11af62c4c0a686d80f99bf03a305176f Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 16 Sep 2026 11:41:11 -0700 Subject: [PATCH 0966/2286] docs: Makefile: Add a testdocs target for the documentation unit tests tools/unittests/run.py discovers and runs the unit tests there (which are primarily documentation tools), but nothing in the build calls it, so the tests are only run by whoever remembers that the script is there. Give it a target, next to the other checks that need no Sphinx. The name ends in "docs", so it matches the "%docs" pattern already in no-dot-config-targets and runs without a configured tree, and it sets PYTHONPYCACHEPREFIX as the other Python targets here do, to keep __pycache__ directories out of the source tree. $ make testdocs ... Ran 121 tests with "make dochelp" listing it between refcheckdocs and cleandocs. Tested with GNU Make 4.4.1 and Python 3.14.7, on a tree with no .config, where it ran 121 tests and left no __pycache__ behind. Assisted-by: LLM Signed-off-by: Kees Cook Tested-by: Randy Dunlap Acked-by: Randy Dunlap Tested-by: Nicolas Schier Reviewed-by: Nicolas Schier Reviewed-by: Breno Leitao Signed-off-by: Jonathan Corbet Message-ID: <20260916184107.i.183-kees@kernel.org> --- Documentation/Makefile | 5 +++++ Makefile | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/Makefile b/Documentation/Makefile index 377a449656c817..ee852afe9bb998 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -74,6 +74,10 @@ htmldocs-redirects: $(srctree)/Documentation/.renames.txt refcheckdocs: $(Q)cd $(srctree); tools/docs/documentation-file-ref-check +testdocs: + $(Q)PYTHONPYCACHEPREFIX="$(PYTHONPYCACHEPREFIX)" \ + $(PYTHON3) $(srctree)/tools/unittests/run.py + cleandocs: $(Q)rm -rf $(BUILDDIR) @@ -95,6 +99,7 @@ dochelp: @echo ' (will connect to external hosts)' @echo ' refcheckdocs - check for references to non-existing files under' @echo ' Documentation' + @echo ' testdocs - run the unit tests of the documentation tools' @echo ' cleandocs - clean all generated files' @echo @echo ' make SPHINXDIRS="s1 s2" [target] Generate only docs of folder s1, s2' diff --git a/Makefile b/Makefile index 4ad67b737af7ad..d08f4325780676 100644 --- a/Makefile +++ b/Makefile @@ -2002,7 +2002,7 @@ $(help-board-dirs): help-%: # --------------------------------------------------------------------------- DOC_TARGETS := xmldocs latexdocs pdfdocs htmldocs epubdocs cleandocs \ linkcheckdocs dochelp refcheckdocs texinfodocs infodocs mandocs \ - htmldocs-redirects + htmldocs-redirects testdocs PHONY += $(DOC_TARGETS) $(DOC_TARGETS): From 1b62fa92122be81d9b229816f3d456919624284e Mon Sep 17 00:00:00 2001 From: Lucas Adryell Ramalho Date: Mon, 31 Aug 2026 20:26:30 -0300 Subject: [PATCH 0967/2286] docs: translations: pt_BR: translate stable-api-nonsense.rst Translate Documentation/process/stable-api-nonsense.rst into Brazilian Portuguese. Assisted-by: LLM Acked-by: Daniel Pereira Signed-off-by: Lucas Adryell Ramalho Signed-off-by: Jonathan Corbet Message-ID: <20260831-ptbr-stable-api-nonsense-v4-1-0ef5e58504cc@gmail.com> --- .../translations/pt_BR/process/index.rst | 1 + .../pt_BR/process/stable-api-nonsense.rst | 208 ++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/stable-api-nonsense.rst diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 2c63d82bff67fd..36d9d9b8cf9be3 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -62,6 +62,7 @@ Estas são as regras pelas quais tentamos viver na comunidade do kernel Modelos de Maturidade para Contribuição no Kernel Linux Declaração de Aplicação do Kernel Linux Declaração sobre Drivers do Kernel + A interface de drivers do kernel Linux Estilo de gerenciamento do kernel Linux Assistentes de código O manual da árvore tip diff --git a/Documentation/translations/pt_BR/process/stable-api-nonsense.rst b/Documentation/translations/pt_BR/process/stable-api-nonsense.rst new file mode 100644 index 00000000000000..c5d0e643eb3231 --- /dev/null +++ b/Documentation/translations/pt_BR/process/stable-api-nonsense.rst @@ -0,0 +1,208 @@ +.. SPDX-License-Identifier: GPL-2.0 + +A interface de drivers do kernel Linux +======================================= + +(todas as suas perguntas respondidas e mais algumas) + +Greg Kroah-Hartman + +Este texto foi escrito para tentar explicar por que o Linux **não possui uma +interface binária do kernel nem uma interface estável do kernel**. + +.. note:: + + Observe que este artigo descreve as interfaces **internas do kernel**, e não + as interfaces entre o kernel e o espaço de usuário. + + A interface entre o kernel e o espaço de usuário é aquela utilizada pelos + aplicativos: a interface de chamadas de sistema (syscalls). Essa + interface é **muito** estável ao longo do tempo e não será quebrada. Tenho + programas antigos, compilados em uma versão do kernel anterior à 0.9 e alguma + coisa, que ainda funcionam perfeitamente na versão mais recente do kernel + 2.6. Essa é a interface cuja estabilidade os usuários e desenvolvedores de + aplicativos podem considerar garantida. + + +Resumo executivo +---------------- + +Você acha que quer uma interface estável do kernel, mas, na verdade, não quer, +e nem sabe disso. O que você realmente quer é um driver que continue +funcionando de maneira estável, e isso só é possível se o seu driver estiver +na árvore principal do kernel. Você também obtém muitos outros benefícios se +o seu driver fizer parte da árvore principal do kernel. São esses benefícios +que ajudaram a tornar o Linux um sistema operacional tão robusto, estável e +maduro — justamente a razão pela qual você o está usando. + + +Introdução +---------- + +Apenas quem escreve drivers para o kernel precisa se preocupar com as mudanças +nas interfaces internas do kernel. Para a grande maioria das pessoas, essas +interfaces nem sequer são visíveis e tampouco são motivo de preocupação. + +Antes de mais nada, não abordarei **nenhuma** questão jurídica relacionada a +código-fonte fechado, código-fonte oculto, blobs binários, wrappers de +código-fonte ou qualquer outro termo usado para descrever drivers do kernel +cujo código-fonte não seja disponibilizado sob a GPL. Consulte um advogado +caso tenha alguma dúvida jurídica. Sou programador e, portanto, descreverei +aqui apenas as questões técnicas (isso não significa que as questões +jurídicas sejam pouco importantes; elas são reais e você precisa estar sempre +ciente delas). + +Portanto, há dois tópicos principais: interfaces binárias do kernel e +interfaces estáveis de código-fonte do kernel. Ambos dependem um do outro, +mas discutiremos primeiro a parte referente às interfaces binárias para +deixá-la de lado. + + +Interface binária do kernel +--------------------------- + +Supondo que tivéssemos uma interface estável de código-fonte para o kernel, +uma interface binária surgiria naturalmente também, certo? Errado. Considere +os seguintes fatos sobre o kernel Linux: + + - Dependendo da versão do compilador C utilizada, diferentes estruturas de + dados do kernel terão diferentes alinhamentos e poderão até mesmo incluir + funções de maneiras distintas (por exemplo, tornando determinadas funções + inline ou não). A organização das funções individuais não é tão importante, + mas as diferenças no preenchimento das estruturas de dados são muito + importantes. + - Dependendo das opções selecionadas durante a compilação do kernel, uma + grande variedade de comportamentos pode ser assumida pelo kernel: + + - diferentes estruturas podem conter campos diferentes; + - algumas funções podem nem sequer ser implementadas (por exemplo, + determinados bloqueios são completamente eliminados durante a + compilação em kernels sem SMP); + - a memória dentro do kernel pode ser alinhada de maneiras diferentes, + dependendo das opções de compilação. + - O Linux é executado em uma grande variedade de arquiteturas de + processadores. Não há como drivers binários compilados para uma arquitetura + funcionarem corretamente em outra. + +Vários desses problemas podem ser contornados simplesmente compilando o módulo +para uma configuração específica e exata do kernel, utilizando exatamente o +mesmo compilador C empregado na compilação do kernel. Isso é suficiente caso +você queira fornecer um módulo para uma determinada versão de uma distribuição +Linux específica. Porém, multiplique essa única compilação pelo número de +distribuições Linux existentes e pelo número de versões suportadas de cada +distribuição e você rapidamente terá um pesadelo de diferentes opções de +compilação em diferentes versões. Além disso, cada versão de uma distribuição +Linux contém vários kernels, cada um ajustado para diferentes tipos de +hardware (diferentes tipos de processadores e diferentes opções). Portanto, +mesmo para uma única versão, você precisará criar várias versões do seu módulo. + +Acredite em mim: com o tempo, você enlouquecerá se tentar oferecer suporte a +esse tipo de distribuição. Aprendi isso da maneira mais difícil há muito +tempo... + +Interfaces estáveis de código-fonte do kernel +---------------------------------------------- + +Esse é um tópico um pouco mais "volátil" se você conversar com alguém que está +tentando manter atualizado, ao longo do tempo, um driver do kernel Linux que +não está na árvore principal do kernel. + +O desenvolvimento do kernel Linux é contínuo e ocorre em ritmo acelerado, +sem desacelerar. Por isso, os desenvolvedores do kernel encontram bugs nas +interfaces existentes ou descobrem maneiras melhores de fazer as coisas. +Quando isso acontece, eles corrigem as interfaces atuais para que funcionem +melhor. Nesse processo, nomes de funções podem mudar, estruturas podem crescer +ou diminuir e parâmetros de funções podem ser reformulados. Quando isso +acontece, todos os locais dentro do kernel que utilizam essa interface são +corrigidos ao mesmo tempo, garantindo que tudo continue funcionando +corretamente. + +Como exemplos específicos disso, as interfaces USB internas do kernel +passaram por pelo menos três reformulações diferentes ao longo da existência +desse subsistema. Essas reformulações foram feitas para resolver diversos +problemas: + + - Uma mudança de um modelo síncrono de fluxos de dados para um modelo + assíncrono. Isso reduziu a complexidade de vários drivers e aumentou a + taxa de transferência de todos os drivers USB, de modo que atualmente + executamos quase todos os dispositivos USB na maior velocidade possível. + - Foi feita uma mudança na maneira como os pacotes de dados eram alocados + pelos drivers USB a partir do núcleo USB, de modo que todos os drivers + passaram a precisar fornecer mais informações ao núcleo USB, corrigindo + diversos deadlocks documentados. + +Isso contrasta fortemente com vários sistemas operacionais de código fechado, +que tiveram de manter suas interfaces USB antigas ao longo do tempo. Isso +permite que novos desenvolvedores utilizem acidentalmente interfaces antigas +e façam as coisas de maneira inadequada, prejudicando a estabilidade do +sistema operacional. + +Em ambos os casos, todos os desenvolvedores concordaram que essas eram +mudanças importantes que precisavam ser feitas, e elas foram realizadas com +relativamente pouco esforço. Se o Linux tivesse de garantir a preservação de +uma interface de código-fonte estável, uma nova interface teria de ser criada, +enquanto a interface antiga e defeituosa teria de continuar sendo mantida ao +longo do tempo, resultando em trabalho adicional para os desenvolvedores USB. +Como todos os desenvolvedores USB do Linux realizam esse trabalho em seu +próprio tempo, pedir que programadores façam trabalho extra, sem nenhum +benefício e gratuitamente, não é uma possibilidade. + +Questões de segurança também são muito importantes para o Linux. Quando um +problema de segurança é encontrado, ele é corrigido em um período muito curto. +Em diversas ocasiões, isso fez com que interfaces internas do kernel fossem +reformuladas para impedir que o problema de segurança ocorresse. Quando isso +acontece, todos os drivers que utilizam essas interfaces também são corrigidos +ao mesmo tempo, garantindo que o problema de segurança seja resolvido e não +possa reaparecer acidentalmente no futuro. Se as interfaces internas não +pudessem ser alteradas, não seria possível corrigir esse tipo de problema de +segurança e garantir que ele não voltasse a ocorrer. + +As interfaces do kernel são aprimoradas ao longo do tempo. Se ninguém estiver +utilizando uma determinada interface, ela é removida. Isso garante que o +kernel permaneça o menor possível e que todas as interfaces existentes possam +ser testadas da melhor maneira possível (é praticamente impossível testar +adequadamente a validade de interfaces que não são utilizadas). + + +O que fazer +----------- + +Então, se você possui um driver do kernel Linux que não está na árvore +principal do kernel, o que você, como desenvolvedor, deve fazer? Distribuir +um driver binário para cada versão diferente do kernel em cada distribuição +é um pesadelo, e tentar acompanhar uma interface do kernel que está em +constante mudança também é uma tarefa difícil. + +Simples: coloque seu driver na árvore principal do kernel (lembre-se de que +estamos falando aqui de drivers distribuídos sob uma licença compatível com +a GPL; se seu código não se enquadra nessa categoria, boa sorte, você está +por conta própria aqui, seu parasita). Se seu driver estiver na árvore e uma +interface do kernel mudar, ele será corrigido pela própria pessoa que realizou +a alteração no kernel. Isso garante que seu driver continue sempre compilável +e funcionando ao longo do tempo, exigindo muito pouco esforço de sua parte. + +Os excelentes efeitos colaterais de ter seu driver na árvore principal do +kernel são: + + - A qualidade do driver aumentará, enquanto os custos de manutenção + (para o desenvolvedor original) diminuirão. + - Outros desenvolvedores adicionarão funcionalidades ao seu driver. + - Outras pessoas encontrarão e corrigirão bugs no seu driver. + - Outras pessoas encontrarão oportunidades de otimização no seu driver. + - Outras pessoas atualizarão o driver para você quando mudanças em + interfaces externas exigirem isso. + - O driver será automaticamente distribuído por todas as distribuições + Linux, sem que seja necessário pedir às distribuições que o adicionem. + +Como o Linux oferece suporte, "pronto para uso", a um número maior de +dispositivos diferentes do que qualquer outro sistema operacional, e oferece +suporte a esses dispositivos em mais arquiteturas de processadores diferentes +do que qualquer outro sistema operacional, esse modelo comprovado de +desenvolvimento deve estar fazendo alguma coisa certa :) + + +------ + +Agradecimentos a Randy Dunlap, Andrew Morton, David Brownell, Hanna Linder, +Robert Love e Nishanth Aravamudan pela revisão e pelos comentários sobre +este documento. From e7134ee23ae1a34766ae0bc34a7d0c37ad986588 Mon Sep 17 00:00:00 2001 From: Lucas Adryell Ramalho Date: Wed, 16 Sep 2026 10:46:34 -0300 Subject: [PATCH 0968/2286] docs: translations: pt_BR: translate maintainer-devicetree.rst Translate maintainer-devicetree.rst to brazilian portuguese. Assisted-by: LLM Signed-off-by: Lucas Adryell Ramalho Reviewed-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260916134637.216062-1-lucasadramalho@gmail.com> --- .../translations/pt_BR/process/index.rst | 1 + .../pt_BR/process/maintainer-devicetree.rst | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/maintainer-devicetree.rst diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 36d9d9b8cf9be3..4b765f5d38251d 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -96,6 +96,7 @@ mantenedores de subsistemas. Processo do subsistema SoC Conformidade de DTS para SoC Processo do subsistema KVM x86 + Subsistema de Devicetree e Open Firmware Outros materiais ---------------- diff --git a/Documentation/translations/pt_BR/process/maintainer-devicetree.rst b/Documentation/translations/pt_BR/process/maintainer-devicetree.rst new file mode 100644 index 00000000000000..be26d89defdff6 --- /dev/null +++ b/Documentation/translations/pt_BR/process/maintainer-devicetree.rst @@ -0,0 +1,76 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================================= +Subsistema de Devicetree e Open Firmware +========================================= + +Outros documentos sobre o processo +---------------------------------- + +Consulte os documentos em Documentation/devicetree/bindings/ para saber como +escrever bindings de Devicetree corretamente e como enviar patches. + +Revisão e tratamento de patches +------------------------------- + +Os patches sob responsabilidade dos mantenedores de Devicetree são processados +de formas distintas, conforme o tipo de patch: + +1. Código central de drivers OF, por exemplo, drivers/of/: + os patches são revisados e aplicados pelos mantenedores de DT. + +2. Bindings de Devicetree: + os patches são revisados pelos mantenedores de DT, mas devem ser aplicados + pelos mantenedores do subsistema, exceto em alguns casos. Consulte também + *Para mantenedores do kernel* em + Documentation/devicetree/bindings/submitting-patches.rst. + +3. DTS e drivers: + os mantenedores de DT podem fazer comentários, mas, em geral, não se espera + uma revisão. Os DTS devem passar nas verificações de esquema + (dtbs_check) ou, ao menos, não gerar novos avisos. + +Patchwork +~~~~~~~~~ + +Os mantenedores de Devicetree revisam patches usando o Patchwork; portanto, o +status atual de um patch pode ser consultado por lá. Em submissões típicas de +drivers, o Patchwork recebe toda a série de patches, mas normalmente apenas +alguns patches são bindings de Devicetree e, assim, revisados pelos +mantenedores de DT. + +Explicação dos status do Patchwork: + + - **New**: ainda não processado pelo conjunto de ferramentas de automação. + - **Needs ACK**: aguardando revisão dos mantenedores de DT. + - **Handled Elsewhere**: patch não relacionado a DT; não será revisado aqui. + - **RFC**: o patch provavelmente foi ignorado por ser um RFC incompleto. + - **Changes Requested**: o patch foi revisado e os mantenedores de DT esperam + alterações. + - **Accepted**: o patch foi revisado e aplicado pelos mantenedores de DT em + sua árvore. + - **Not Applicable**: o patch foi revisado e provavelmente está em boas + condições, com uma tag *Reviewed-by* ou *Acked-by* fornecida, mas os + mantenedores de DT esperam que outra pessoa o aplique. + +Nova revisão e pings de patches +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Devido ao alto volume de e-mails, os mantenedores de Devicetree não leem todas +as mensagens que recebem; em vez disso, eles dependem do Patchwork durante o +processo de revisão. Além disso, muitas vezes deixam de lado patches que já +foram revisados. + +Como resultado, os mantenedores podem não perceber: + +1. Perguntas sobre patches já revisados. +2. Pings, por exemplo, quando um patch foi revisado pelos mantenedores de DT, + mas ainda não foi aplicado pelos mantenedores do subsistema. + +Esses casos podem ser tratados das seguintes formas: + +1. Enviando um ping aos mantenedores de DT no canal de IRC. +2. Removendo a tag *Acked-by* ou *Reviewed-by* do mantenedor de DT ao enviar + uma nova versão da série de patches, junto com uma explicação no changelog + do patch sobre o motivo da remoção da tag e o que se espera dos mantenedores + de DT. From 7acf376911146139ba4072d8917387cea1a62b0f Mon Sep 17 00:00:00 2001 From: Lucas Adryell Ramalho Date: Fri, 18 Sep 2026 01:28:34 -0300 Subject: [PATCH 0969/2286] docs: translations: pt_BR: add debugging index Add a Brazilian Portuguese index for process/debugging, mirroring the section layout of the original. No debugging documents have been translated yet, so every entry is listed under a Todolist marker instead of a toctree. Signed-off-by: Lucas Adryell Ramalho Reviewed-by: Daniel Pereira Signed-off-by: Jonathan Corbet Message-ID: <20260918042835.156030-1-lucasadramalho@gmail.com> --- .../pt_BR/process/debugging/index.rst | 73 +++++++++++++++++++ .../translations/pt_BR/process/index.rst | 1 + 2 files changed, 74 insertions(+) create mode 100644 Documentation/translations/pt_BR/process/debugging/index.rst diff --git a/Documentation/translations/pt_BR/process/debugging/index.rst b/Documentation/translations/pt_BR/process/debugging/index.rst new file mode 100644 index 00000000000000..b223fae34622f6 --- /dev/null +++ b/Documentation/translations/pt_BR/process/debugging/index.rst @@ -0,0 +1,73 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======================================================= +Dicas de depuração para desenvolvedores do Kernel Linux +======================================================= + +Guias gerais +------------ + +Todolist: + +* driver_development_debugging_guide +* gdb-kernel-debugging +* kgdb +* userspace_debugging_guide + +Guias específicos de subsistemas +-------------------------------- + +Todolist: + +* media_specific_debugging_guide + +Dicas gerais de depuração +------------------------- + +Dependendo do problema, um conjunto diferente de ferramentas está disponível +para rastrear o problema ou até mesmo para perceber se há algum problema em +primeiro lugar. + +Como primeiro passo, você precisa descobrir que tipo de problema você deseja +depurar. Dependendo da resposta, sua metodologia e escolha de ferramentas podem +variar. + +Preciso depurar com acesso limitado? +------------------------------------ + +Você possui acesso limitado à máquina ou não consegue parar a execução em +andamento? + +Nesse caso, sua capacidade de depuração depende do suporte de depuração +embutido no kernel fornecido pela distribuição. +O :doc:`/process/debugging/userspace_debugging_guide` fornece uma breve visão +geral sobre uma variedade de ferramentas de depuração possíveis nessa situação. +Você pode verificar a capacidade do seu kernel, na maioria dos casos, olhando o +arquivo de configuração dentro do diretório /boot. + +Eu tenho acesso root ao sistema? +-------------------------------- + +Você consegue facilmente substituir o módulo em questão ou instalar um novo +kernel? + +Nesse caso, sua gama de ferramentas disponíveis é muito maior. Você +pode encontrar as ferramentas +no :doc:`/process/debugging/driver_development_debugging_guide`. + +A temporização é um fator? +-------------------------- + +É importante entender se o problema que você deseja depurar se manifesta +de forma consistente (ou seja, para um determinado conjunto de entradas, você +sempre obtém a mesma saída incorreta) ou de forma inconsistente. Se ele se +manifestar de forma inconsistente, algum fator de temporização pode estar em +jogo. Se a inserção de atrasos no código alterar o comportamento, é bastante +provável que a temporização seja um fator determinante. + +Quando a temporização altera o resultado da execução do código, o uso de um +simples printk() para fins de depuração pode não funcionar; uma alternativa +semelhante é usar trace_printk(), que registra as mensagens de depuração no +arquivo de rastreamento, em vez de no log do kernel. + +**Copyright** ©2024 : Collabora diff --git a/Documentation/translations/pt_BR/process/index.rst b/Documentation/translations/pt_BR/process/index.rst index 4b765f5d38251d..d072f7e46767e2 100644 --- a/Documentation/translations/pt_BR/process/index.rst +++ b/Documentation/translations/pt_BR/process/index.rst @@ -78,6 +78,7 @@ gerenciamento de bugs e vulnerabilidades. .. toctree:: :maxdepth: 1 + Dicas de depuração para desenvolvedores do Kernel Linux Falhas de segurança Problemas de hardware sob embargo CVEs From c6a6c48034b99bcb1a9cb016d5aa601e68a3eb91 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 20 Sep 2026 21:46:37 -0700 Subject: [PATCH 0970/2286] ABI: sysfs-bus-pci: use literal blocks for commands Use literal blocks for console I/O to separate it from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260921044637.91089-1-rdunlap@infradead.org> --- Documentation/ABI/testing/sysfs-bus-pci | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci index 55ea1db749a180..14a8667156123d 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -486,19 +486,19 @@ Description: These files provide an interface to PCIe Resizable BAR support. A file is created for each BAR resource (N) supported by the PCIe Resizable BAR extended capability of the device. Reading - each file exposes the bitmap of available resource sizes: + each file exposes the bitmap of available resource sizes:: - # cat resource1_resize - 00000000000001c0 + # cat resource1_resize + 00000000000001c0 The bitmap represents supported resource sizes for the BAR, where bit0 = 1MB, bit1 = 2MB, bit2 = 4MB, etc. In the above example the device supports 64MB, 128MB, and 256MB BAR sizes. When writing the file, the user provides the bit position of - the desired resource size, for example: + the desired resource size, for example:: - # echo 7 > resource1_resize + # echo 7 > resource1_resize This indicates to set the size value corresponding to bit 7, 128MB. The resulting size is 2 ^ (bit# + 20). This definition @@ -600,7 +600,7 @@ Description: As all DOE devices must support the DOE discovery feature, if DOE is supported you will at least see the doe_discovery - file, with this contents: + file, with this contents:: # cat doe_features/doe_discovery 0001:00 @@ -608,7 +608,7 @@ Description: If the device supports other features you will see other files as well. For example if CMA/SPDM and secure CMA/SPDM are supported the doe_features directory will look like - this: + this:: # ls doe_features 0001:01 0001:02 doe_discovery From d03ceacf648c1c53ee78339ed5c45932a444dfce Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 20 Sep 2026 21:47:45 -0700 Subject: [PATCH 0971/2286] Documentation/ABI: IFS: use literal blocks for commands Use literal blocks for console I/O to separate it from the surrounding text. Otherwise the lines run together, without any clear break before or after the commands. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260921044746.94316-1-rdunlap@infradead.org> --- Documentation/ABI/testing/sysfs-platform-intel-ifs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-platform-intel-ifs b/Documentation/ABI/testing/sysfs-platform-intel-ifs index 41b4d5b1e21c6d..11cd2f412d8172 100644 --- a/Documentation/ABI/testing/sysfs-platform-intel-ifs +++ b/Documentation/ABI/testing/sysfs-platform-intel-ifs @@ -10,8 +10,10 @@ Description: Write to trigger IFS test for one online core. Note that the test is per core. The cpu# can be for any thread on the core. Running on one thread completes the test for the core containing that thread. - Example: to test the core containing cpu5: echo 5 > - /sys/devices/virtual/misc/intel_ifs_/run_test + Example: to test the core containing cpu5:: + + echo 5 > /sys/devices/virtual/misc/intel_ifs_/run_test + Devices: all What: /sys/devices/virtual/misc/intel_ifs_/status From cfb2e42aed6e71fcf05bb42e6d2639f4d8dac909 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 20 Sep 2026 21:49:12 -0700 Subject: [PATCH 0972/2286] docs: ABI: TMR manager: use literal blocks for console I/O Use literal blocks so that the console commands and their output are not joined together on one line. Signed-off-by: Randy Dunlap Reviewed-by: Michal Simek Signed-off-by: Jonathan Corbet Message-ID: <20260921044912.97843-1-rdunlap@infradead.org> --- .../ABI/testing/sysfs-driver-xilinx-tmr-manager | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-xilinx-tmr-manager b/Documentation/ABI/testing/sysfs-driver-xilinx-tmr-manager index 57b9b68a73ee9a..8d07c982d125f9 100644 --- a/Documentation/ABI/testing/sysfs-driver-xilinx-tmr-manager +++ b/Documentation/ABI/testing/sysfs-driver-xilinx-tmr-manager @@ -3,14 +3,16 @@ Date: Nov 2022 Contact: appana.durga.kedareswara.rao@amd.com Description: This control file provides the fault detection count. This file cannot be written. - Example: - # cat /sys/devices/platform/amba_pl/44a10000.tmr_manager/errcnt - 1 + Example:: + + # cat /sys/devices/platform/amba_pl/44a10000.tmr_manager/errcnt + 1 What: /sys/devices/platform/amba_pl//dis_block_break Date: Nov 2022 Contact: appana.durga.kedareswara.rao@amd.com -Description: Write any value to it, This control file enables the break signal. +Description: Write any value to it. This control file enables the break signal. This file is write only. - Example: - # echo > /sys/devices/platform/amba_pl/44a10000.tmr_manager/dis_block_break + Example:: + + # echo > /sys/devices/platform/amba_pl/44a10000.tmr_manager/dis_block_break From 80c2913d0a06c9b144be72f0b186734ce63c4044 Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Wed, 23 Sep 2026 16:39:02 +0800 Subject: [PATCH 0973/2286] sched/doc: add a preemption model overview The scheduler documentation describes the individual scheduling classes and various tuning knobs, but there is nothing that describes the preemption models themselves. The only place where they are documented is the kernel-parameters entry for "preempt=", which explains the boot time parameter but not the models it selects. Add a short overview of the models, what separates them, which of them a given kernel offers, and how a scheduling request is honoured under each. Assisted-by: LLM Reviewed-by: Sebastian Andrzej Siewior Suggested-by: Sebastian Andrzej Siewior Signed-off-by: Quchaosheng Signed-off-by: Jonathan Corbet Message-ID: <20260923083903.616271-1-quchaosheng000406@163.com> --- Documentation/scheduler/index.rst | 1 + Documentation/scheduler/sched-preemption.rst | 85 ++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 Documentation/scheduler/sched-preemption.rst diff --git a/Documentation/scheduler/index.rst b/Documentation/scheduler/index.rst index 17ce8d76befc1b..d6d75421756ac5 100644 --- a/Documentation/scheduler/index.rst +++ b/Documentation/scheduler/index.rst @@ -23,5 +23,6 @@ Scheduler sched-stats sched-ext sched-debug + sched-preemption text_files diff --git a/Documentation/scheduler/sched-preemption.rst b/Documentation/scheduler/sched-preemption.rst new file mode 100644 index 00000000000000..bfa22559c29b19 --- /dev/null +++ b/Documentation/scheduler/sched-preemption.rst @@ -0,0 +1,85 @@ +===================== +Scheduler preemption +===================== + +The kernel can be built to run kernel code either uninterruptibly, or with +varying degrees of preemptibility. These are the *preemption models*. +They apply to kernel code only: a task running in user space is always +preempted by the scheduler, whichever model is selected. + +When CONFIG_PREEMPT_DYNAMIC is enabled the preemption model can additionally +be selected at boot time with the ``preempt=`` command line parameter, without +rebuilding the kernel. See +Documentation/admin-guide/kernel-parameters.txt for the parameter itself. +The active model can also be read and changed through +/sys/kernel/debug/sched/preempt, which exists for debugging purposes and may +change. + +The models selectable at runtime are: + + ========= ==================================================== + none No preemption of kernel code other than at explicit + ``cond_resched()`` / blocking points. + voluntary As ``none``, plus ``might_sleep()`` sites. + full Any section that is not explicitly preempt disabled + may be preempted at any time. + lazy As ``full``, except that the scheduling request is + delayed until the return to user space or the next + tick, whichever comes first. This delay does not + apply to real-time tasks. + ========= ==================================================== + +Not every model is available on every kernel. An architecture that provides +lazy-preempt support offers only ``full`` and ``lazy``, and ``none`` and +``voluntary`` are rejected there. A PREEMPT_RT kernel always offers ``full``, +and ``lazy`` as well if the architecture provides it. + +The model that is actually active is reported in the boot log:: + + Dynamic Preempt: full + +Honouring a scheduling request +============================== + +A wakeup makes a task runnable, and the scheduler then decides whether it +should run. If there is an idle CPU which is suitable then the task is moved +there. Otherwise the scheduler has to decide which task to preempt, and the +task that has to leave the CPU gets a reschedule flag. In the tracing output +this is the ``need-resched`` column, ``N`` for TIF_NEED_RESCHED and ``l`` for +TIF_NEED_RESCHED_LAZY. + +The kernel then has to honour that request. A task executing in user space +can always be preempted; a task executing in kernel space can only be +preempted where it is safe to do so, and how that is decided is what +separates the models: + +``none`` + Code paths with long loops contain explicit ``cond_resched()`` calls, + which perform the scheduling. + +``voluntary`` + As ``none``, and functions which are known to be able to block gain + a ``cond_resched()``-style scheduling point as well, through + ``might_sleep()``. + +``full`` + Explicit preemption points are no longer involved. The kernel tracks + whether it may be preempted and schedules once the request can be + honoured. + +``lazy`` + As ``full``, with one difference: the request the fair scheduler sets + is TIF_NEED_RESCHED_LAZY, which is not honoured immediately even where + it could be. It is delayed until the task returns to user space, so + that the in-kernel work runs to completion and every lock has been + dropped before the CPU is given up. If the task has not scheduled on + its own by then, the request is turned into a full TIF_NEED_RESCHED on + the next HZ tick; this is visible as ``B`` in the ``need-resched`` + column. + +Real-time tasks always use TIF_NEED_RESCHED, so the delay above does not +apply to them. ``lazy`` is therefore not a replacement for PREEMPT_RT: it +keeps most of the responsiveness of ``full`` for SCHED_NORMAL tasks while +letting them run to completion, which avoids leaving a lock behind that +another task will immediately ask for, and avoids pushing cache-hot data out +that would have to be pulled back in. From 358c2e2c229d20f5a978ad0bb1367a11fcacb4dc Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Wed, 23 Sep 2026 16:39:03 +0800 Subject: [PATCH 0974/2286] docs: kernel-parameters: fix a truncated sentence for preempt=lazy The preempt=lazy paragraph trails off: "One preemption is when the task returns to user space." reads as if a preemption and a preemption point were the same thing, and the sentence does not say what it is describing. Say which point it means. Fixes: f66e4a996582 ("sched/core: Update kernel boot parameters for LAZY preempt.") Assisted-by: LLM Suggested-by: Sebastian Andrzej Siewior Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Quchaosheng Signed-off-by: Jonathan Corbet Message-ID: <20260923083903.616271-2-quchaosheng000406@163.com> --- Documentation/admin-guide/kernel-parameters.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 4864e37005bee0..4b715d5dd8ce27 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -5436,8 +5436,8 @@ Kernel parameters lazy - Scheduler controlled. Similar to full but instead of preempting the task immediately, the task gets one HZ tick time to yield itself before the - preemption will be forced. One preemption is when the - task returns to user space. + preemption will be forced. One such preemption + point is when the task returns to user space. print-fatal-signals= [KNL] debug: print fatal signals From 182bc9844e7e58d9bd9211b8f27fead92251c8fb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 22 Sep 2026 19:49:18 -0700 Subject: [PATCH 0975/2286] docs/ABI: testing: aspeed-uart-routing: use literal block for console commands Use a literal block for console I/O so that the lines aren't merged together in the produced output. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260923024918.840039-1-rdunlap@infradead.org> --- Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing b/Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing index 910df0e5815abf..b0b713c855175f 100644 --- a/Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing +++ b/Documentation/ABI/testing/sysfs-driver-aspeed-uart-routing @@ -8,9 +8,10 @@ Description: Selects the RX source of the UARTx device. selected option marked by brackets "[]". The list of available options depends on the selected file. - e.g. - cat /sys/bus/platform/drivers/aspeed-uart-routing/\*.uart_routing/uart1 - [io1] io2 io3 io4 uart2 uart3 uart4 io6 + E.g.:: + + cat /sys/bus/platform/drivers/aspeed-uart-routing/\*.uart_routing/uart1 + [io1] io2 io3 io4 uart2 uart3 uart4 io6 In this case, UART1 gets its input from IO1 (physical serial port 1). From 7b3bac2d0e530255f57eefc939450e8919f61421 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 22 Sep 2026 19:49:46 -0700 Subject: [PATCH 0976/2286] ABI: sysfs-bus-usb: use literal block for console commands Use a literal block for console I/O so that the lines aren't merged together in the produced output. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260923024946.842697-1-rdunlap@infradead.org> --- Documentation/ABI/testing/sysfs-bus-usb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index baf4bf6d32ee37..5213117fd957e6 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -83,7 +83,8 @@ Description: idVendor idProduct. After successfully removing an ID, the driver will no longer support the device. This is useful to ensure auto probing won't - match the driver to the device. For example: + match the driver to the device. For example:: + # echo "046d c315" > /sys/bus/usb/drivers/foo/remove_id Reading from this file will list the dynamically added From af10d9c17a4836830d834bab67ce1e802e836eec Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 22 Sep 2026 19:49:56 -0700 Subject: [PATCH 0977/2286] docs: ABI: sysfs-class-firmware-attributes: use literal blocks for console commands Use a literal block for console I/O so that the lines aren't merged together in the produced output. Also correct some punctuation. Signed-off-by: Randy Dunlap Reviewed-by: Mark Pearson Signed-off-by: Jonathan Corbet Message-ID: <20260923024957.845198-1-rdunlap@infradead.org> --- .../testing/sysfs-class-firmware-attributes | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 2713efa509b465..40e35fe97a7d82 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -397,22 +397,25 @@ Description: unlimited attributes. Read the attribute to check what save mode is enabled (single or bulk). - E.g: - # cat /sys/class/firmware-attributes/thinklmi/attributes/save_settings - single + E.g:: + + # cat /sys/class/firmware-attributes/thinklmi/attributes/save_settings + single Write the attribute with 'bulk' to enable bulk save mode. - Write the attribute with 'single' to enable saving, after every attribute set. + Write the attribute with 'single' to enable saving after every attribute set. The default setting is single mode. - E.g: - # echo bulk > /sys/class/firmware-attributes/thinklmi/attributes/save_settings + E.g:: + + # echo bulk > /sys/class/firmware-attributes/thinklmi/attributes/save_settings When in bulk mode write 'save' to trigger a save of all currently modified attributes. - Note, once a save has been triggered, in bulk mode, attributes can no longer be set and + Note, once a save has been triggered in bulk mode, attributes can no longer be set and will return a permissions error. This is to prevent users hitting the 48+ save limitation (which requires entering the BIOS to clear the error condition) - E.g: - # echo save > /sys/class/firmware-attributes/thinklmi/attributes/save_settings + E.g:: + + # echo save > /sys/class/firmware-attributes/thinklmi/attributes/save_settings What: /sys/class/firmware-attributes/*/attributes/debug_cmd Date: July 2021 From 4f041d7b65b8007403cce30ecf69ad42d48ec315 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Tue, 22 Sep 2026 19:50:18 -0700 Subject: [PATCH 0978/2286] docs: ABI: sysfs-driver-xdata: use literal blocks for console commands Use a literal block for console I/O so that the lines aren't merged together in the produced output. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20260923025018.848769-1-rdunlap@infradead.org> --- Documentation/ABI/testing/sysfs-driver-xdata | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-driver-xdata b/Documentation/ABI/testing/sysfs-driver-xdata index f574e8e6dca21d..5bcdcba3a5c95f 100644 --- a/Documentation/ABI/testing/sysfs-driver-xdata +++ b/Documentation/ABI/testing/sysfs-driver-xdata @@ -9,15 +9,19 @@ Description: Allows the user to enable the PCIe traffic generator which Write y/1/on to enable, n/0/off to disable - Usage e.g. + Usage e.g.:: + echo 1 > /sys/class/misc/dw-xdata-pcie./write - or + + or:: + echo 0 > /sys/class/misc/dw-xdata-pcie./write The user can read the current PCIe link throughput generated through this generator in MB/s. - Usage e.g. + Usage e.g.:: + cat /sys/class/misc/dw-xdata-pcie./write 204 @@ -34,15 +38,19 @@ Description: Allows the user to enable the PCIe traffic generator which Write y/1/on to enable, n/0/off to disable - Usage e.g. + Usage e.g.:: + echo 1 > /sys/class/misc/dw-xdata-pcie./read - or + + or:: + echo 0 > /sys/class/misc/dw-xdata-pcie./read The user can read the current PCIe link throughput generated through this generator in MB/s. - Usage e.g. + Usage e.g.:: + cat /sys/class/misc/dw-xdata-pcie./read 199 From 7606d01b963fc3e4e19dd53ca6c5fdd97f7bc1d7 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 26 Sep 2026 10:45:45 -0700 Subject: [PATCH 0979/2286] x86/fpu: Remove additional remnants of math emulation Remove math_emu.h and the declaration of math_emulate(). They are no longer used since support for math emulation was removed. Signed-off-by: Eric Biggers Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260926174545.55533-1-ebiggers@kernel.org --- arch/x86/include/asm/math_emu.h | 15 --------------- arch/x86/include/asm/processor.h | 2 +- arch/x86/include/asm/traps.h | 2 -- 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 arch/x86/include/asm/math_emu.h diff --git a/arch/x86/include/asm/math_emu.h b/arch/x86/include/asm/math_emu.h deleted file mode 100644 index 3c42743083ed9f..00000000000000 --- a/arch/x86/include/asm/math_emu.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef _ASM_X86_MATH_EMU_H -#define _ASM_X86_MATH_EMU_H - -#include - -/* This structure matches the layout of the data saved to the stack - following a device-not-present interrupt, part of it saved - automatically by the 80386/80486. - */ -struct math_emu_info { - long ___orig_eip; - struct pt_regs *regs; -}; -#endif /* _ASM_X86_MATH_EMU_H */ diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h index ec9db0dfa0dffc..77488ff5eb8c7c 100644 --- a/arch/x86/include/asm/processor.h +++ b/arch/x86/include/asm/processor.h @@ -10,7 +10,7 @@ struct mm_struct; struct io_bitmap; struct vm86; -#include +#include #include #include #include diff --git a/arch/x86/include/asm/traps.h b/arch/x86/include/asm/traps.h index 3f24cc472ce9be..e13f1025c62e0a 100644 --- a/arch/x86/include/asm/traps.h +++ b/arch/x86/include/asm/traps.h @@ -37,8 +37,6 @@ static inline int get_si_code(unsigned long condition) return TRAP_BRKPT; } -void math_emulate(struct math_emu_info *); - bool fault_in_kernel_space(unsigned long address); #ifdef CONFIG_VMAP_STACK From 94d8ed941d63132fee54204b273ba1fd7dcfc760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:12 +0200 Subject: [PATCH 0980/2286] selftests/nolibc: make the broken MIPS N32 argument detection reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Another test is about to be added which needs to make use of this. Move the definition into a reusable variable. As a side-effect, the test is now properly marked as skipped. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-1-e9af6e47b37a@weissschuh.net --- tools/testing/selftests/nolibc/nolibc-test.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 1995d67dcca6ba..c344675ad0c94d 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1525,12 +1525,6 @@ int test_large_file(void) ssize_t written; off_t off; -#if defined(__mips__) && defined(_ABIN32) - /* https://lore.kernel.org/qemu-devel/fed03914-a95a-4522-a432-f129264cb2ac@t-8ch.de/ */ - if (getpid() != 1) - return 0; -#endif - if (large_seek < UINT32_MAX) { errno = EOVERFLOW; return -1; @@ -1651,6 +1645,7 @@ int run_syscall(int min, int max) void *p1, *p2; int has_gettid = 1; int has_brk; + int has_working_64bit_syscall_arg = 1; /* indicates whether or not /proc is mounted */ proc = stat("/proc", &stat_buf) == 0; @@ -1666,6 +1661,11 @@ int run_syscall(int min, int max) /* on musl setting brk()/sbrk() always fails */ has_brk = brk(0) == 0; +#if defined(__mips__) && defined(_ABIN32) + /* https://lore.kernel.org/qemu-devel/fed03914-a95a-4522-a432-f129264cb2ac@t-8ch.de/ */ + has_working_64bit_syscall_arg = getpid() == 1; +#endif + for (test = min; test >= 0 && test <= max; test++) { int llen = 0; /* line length */ @@ -1781,7 +1781,7 @@ int run_syscall(int min, int max) CASE_TEST(_syscall_noargs); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_getpid), getpid()); break; CASE_TEST(_syscall_args); EXPECT_SYSEQ(is_nolibc, _syscall(__NR_statx, 0, NULL, 0, 0, NULL), -EFAULT); break; CASE_TEST(namespace); EXPECT_SYSZR(euid0 && proc, test_namespace()); break; - CASE_TEST(largefile); EXPECT_SYSZR(1, test_large_file()); break; + CASE_TEST(largefile); EXPECT_SYSZR(has_working_64bit_syscall_arg, test_large_file()); break; case __LINE__: return ret; /* must be last */ /* note: do not set any defaults so as to permit holes above */ From 74136192e3f6f48670a0bccc7bdc5005b13d94f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:13 +0200 Subject: [PATCH 0981/2286] tools/nolibc: make 64-bit systemcall argument padding generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several 32-bit architectures pass a 64-bit systemcall argument as two 32-bit values requiring alignment to an even register pair. This case is currently relevant for the ftruncate64() wrapper and handled by those architectures providing custom wrappers. With the introduction of pread() and pwrite() wrappers, the same pattern would lead to a lot of repeated code. Add a #define, so that the generic code can do the padding where necessary, without any per-architecture duplication. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-2-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-arm.h | 7 +------ tools/include/nolibc/arch-hexagon.h | 7 +------ tools/include/nolibc/arch-mips.h | 7 +------ tools/include/nolibc/arch-powerpc.h | 7 +------ tools/include/nolibc/unistd.h | 4 ++++ 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index b88686fb19c435..b5b3456c75cb02 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -196,11 +196,6 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif /* _NOLIBC_ARCH_ARM_H */ diff --git a/tools/include/nolibc/arch-hexagon.h b/tools/include/nolibc/arch-hexagon.h index 0767054882f3e5..68f73529a6ddb7 100644 --- a/tools/include/nolibc/arch-hexagon.h +++ b/tools/include/nolibc/arch-hexagon.h @@ -154,11 +154,6 @@ _start(void) } #endif /* NOLIBC_NO_RUNTIME */ -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif /* _NOLIBC_ARCH_HEXAGON_H */ diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 26ad413cec62b7..21b9959a08e2a9 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -286,12 +286,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #endif /* NOLIBC_NO_RUNTIME */ #if defined(_ABIO32) -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif #endif /* _NOLIBC_ARCH_MIPS_H */ diff --git a/tools/include/nolibc/arch-powerpc.h b/tools/include/nolibc/arch-powerpc.h index dbe2e5205aaafa..a4711638cb66fa 100644 --- a/tools/include/nolibc/arch-powerpc.h +++ b/tools/include/nolibc/arch-powerpc.h @@ -222,12 +222,7 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote #endif /* NOLIBC_NO_RUNTIME */ #if !defined(__powerpc64__) -static __attribute__((unused)) -int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) -{ - return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); -} -#define _sys_ftruncate64 _sys_ftruncate64 +#define __NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR #endif #endif /* _NOLIBC_ARCH_POWERPC_H */ diff --git a/tools/include/nolibc/unistd.h b/tools/include/nolibc/unistd.h index a264a20da13dd1..939d4e05a55d35 100644 --- a/tools/include/nolibc/unistd.h +++ b/tools/include/nolibc/unistd.h @@ -52,7 +52,11 @@ int access(const char *path, int amode) static __attribute__((unused)) int _sys_ftruncate64(int fd, uint32_t length0, uint32_t length1) { +#if defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall4(__NR_ftruncate64, fd, 0, length0, length1); +#else return __nolibc_syscall3(__NR_ftruncate64, fd, length0, length1); +#endif } #define _sys_ftruncate64 _sys_ftruncate64 #endif From ddcf77b0350b2eaca4ba2f9db7c4569045c0c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:14 +0200 Subject: [PATCH 0982/2286] tools/nolibc: move __NOLIBC_LLARGPART() to compiler.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pread() and pwrite() support will require the usage of the macro from the arch-*.h headers. sys.h can not be used from those. Move the macro to a more generic header. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-3-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/compiler.h | 9 +++++++++ tools/include/nolibc/sys.h | 10 ---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/include/nolibc/compiler.h b/tools/include/nolibc/compiler.h index f2d7a81d0d7c6a..375c45ce81c836 100644 --- a/tools/include/nolibc/compiler.h +++ b/tools/include/nolibc/compiler.h @@ -90,4 +90,13 @@ # define __nolibc_no_sanitize_undefined #endif +/* + * Helper for 32-bit machines where a 64-bit syscall arg needs to be split into + * two 32-bit parts while making sure the order of the low/high parts are correct + * for the endianness: + * __NOLIBC_LLARGPART(x, 0), __NOLIBC_LLARGPART(x, 1) + */ +#define __NOLIBC_LLARGPART(_arg, _part) \ + (((union { long long ll; long l[2]; }) { .ll = _arg }).l[_part]) + #endif /* _NOLIBC_COMPILER_H */ diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 548f94d96ed203..7f06d27da612f8 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -71,16 +71,6 @@ static __inline__ int __nolibc_enosys(const char *syscall, ...) #endif -/* - * Helper for 32-bit machines where a 64-bit syscall arg needs to be split into - * two 32-bit parts while making sure the order of the low/high parts are correct - * for the endianness: - * __NOLIBC_LLARGPART(x, 0), __NOLIBC_LLARGPART(x, 1) - */ -#define __NOLIBC_LLARGPART(_arg, _part) \ - (((union { long long ll; long l[2]; }) { .ll = _arg }).l[_part]) - - /* Functions in this file only describe syscalls. They're declared static so * that the compiler usually decides to inline them while still being allowed * to pass a pointer to one of their instances. Each syscall exists in two From abafa291e52ac95c46ba0744b64e4d0d3adae4b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:15 +0200 Subject: [PATCH 0983/2286] tools/nolibc: introduce __NOLIBC_BITS_PER_SYSCALL_ARG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ILP32 architectures, like x32 and n32, support 64-bit system call arguments, although they are nominally 32-bit architectures. Add a new define which provides the size of system call arguments so generic libc code can use the best system call for those. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-4-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-mips.h | 1 + tools/include/nolibc/arch-x86.h | 2 ++ tools/include/nolibc/arch.h | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/tools/include/nolibc/arch-mips.h b/tools/include/nolibc/arch-mips.h index 21b9959a08e2a9..c0da0cc309ce14 100644 --- a/tools/include/nolibc/arch-mips.h +++ b/tools/include/nolibc/arch-mips.h @@ -72,6 +72,7 @@ #define _NOLIBC_SYSCALL_STACK_UNRESERVE #define _NOLIBC_SYSCALL_REG register long long +#define __NOLIBC_BITS_PER_SYSCALL_ARG 64 #endif /* _ABIO32 */ diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index 577e9b682eb37e..0638f931674936 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -386,5 +386,7 @@ __asm__ ( ".popsection\n" ); +#define __NOLIBC_BITS_PER_SYSCALL_ARG 64 + #endif /* !defined(__x86_64__) */ #endif /* _NOLIBC_ARCH_X86_H */ diff --git a/tools/include/nolibc/arch.h b/tools/include/nolibc/arch.h index 24e0ec4fb2db17..79ed5900f2a79f 100644 --- a/tools/include/nolibc/arch.h +++ b/tools/include/nolibc/arch.h @@ -40,4 +40,9 @@ #error Unsupported Architecture #endif + +#ifndef __NOLIBC_BITS_PER_SYSCALL_ARG +#define __NOLIBC_BITS_PER_SYSCALL_ARG __BITS_PER_LONG +#endif + #endif /* _NOLIBC_ARCH_H */ From da27722051fdf0775d8c5b3e33dc21bcf605dbd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sun, 27 Sep 2026 10:49:16 +0200 Subject: [PATCH 0984/2286] tools/nolibc: add support for pread() and pwrite() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are standard function to interact with a file descriptor without touching its internal file offset. Signed-off-by: Thomas Weißschuh Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260927-nolibc-pread-v1-5-e9af6e47b37a@weissschuh.net --- tools/include/nolibc/arch-sh.h | 18 +++++++ tools/include/nolibc/sys.h | 50 ++++++++++++++++++++ tools/testing/selftests/nolibc/nolibc-test.c | 46 ++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/tools/include/nolibc/arch-sh.h b/tools/include/nolibc/arch-sh.h index a32378fd621fda..996cfbe1bee327 100644 --- a/tools/include/nolibc/arch-sh.h +++ b/tools/include/nolibc/arch-sh.h @@ -10,6 +10,8 @@ #include "compiler.h" #include "crt.h" +#include + /* * Syscalls for SuperH: * - registers are 32bit wide @@ -161,4 +163,20 @@ void __attribute__((weak, noreturn)) __nolibc_entrypoint __nolibc_no_stack_prote } #endif /* NOLIBC_NO_RUNTIME */ +static __attribute__((unused)) +ssize_t _sys_pread(int fd, void *buf, size_t count, off_t offset) +{ + return __nolibc_syscall6(__NR_pread64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +} +#define _sys_pread _sys_pread + +static __attribute__((unused)) +ssize_t _sys_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return __nolibc_syscall6(__NR_pwrite64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +} +#define _sys_pwrite _sys_pwrite + #endif /* _NOLIBC_ARCH_SH_H */ diff --git a/tools/include/nolibc/sys.h b/tools/include/nolibc/sys.h index 7f06d27da612f8..b63fa6c618c570 100644 --- a/tools/include/nolibc/sys.h +++ b/tools/include/nolibc/sys.h @@ -745,6 +745,56 @@ int pivot_root(const char *new, const char *old) } +/* + * ssize_t pread(int fd, void *buf, size_t count, off_t offset); + * ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); + */ + +#ifndef _sys_pread +static __attribute__((unused)) +ssize_t _sys_pread(int fd, void *buf, size_t count, off_t offset) +{ +#if __NOLIBC_BITS_PER_SYSCALL_ARG == 64 + return __nolibc_syscall4(__NR_pread64, fd, buf, count, offset); +#elif defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall6(__NR_pread64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#else + return __nolibc_syscall5(__NR_pread64, fd, buf, count, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#endif +} +#endif /* _sys_pread */ + +static __attribute__((unused)) +ssize_t pread(int fd, void *buf, size_t count, off_t offset) +{ + return __sysret(_sys_pread(fd, buf, count, offset)); +} + +#ifndef _sys_pwrite +static __attribute__((unused)) +ssize_t _sys_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ +#if __NOLIBC_BITS_PER_SYSCALL_ARG == 64 + return __nolibc_syscall4(__NR_pwrite64, fd, buf, count, offset); +#elif defined(__NOLIBC_PAD_64BIT_SYSCALL_ARGUMENT_PAIR) + return __nolibc_syscall6(__NR_pwrite64, fd, buf, count, 0, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#else + return __nolibc_syscall5(__NR_pwrite64, fd, buf, count, + __NOLIBC_LLARGPART(offset, 0), __NOLIBC_LLARGPART(offset, 1)); +#endif +} +#endif /* _sys_pwrite */ + +static __attribute__((unused)) +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return __sysret(_sys_pwrite(fd, buf, count, offset)); +} + + /* * ssize_t read(int fd, void *buf, size_t count); */ diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index c344675ad0c94d..f3d0d27cdbe178 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1369,6 +1369,51 @@ int test_pipe(void) return !!memcmp(buf, msg, len); } +int test_pwrite_pread(void) +{ + const off_t offset = (0x12ULL << 32) | 0x34; + ssize_t ret; + char buf[2]; + int fd, err; + off_t offs; + + fd = open("/tmp", O_TMPFILE | O_RDWR, 0644); + if (fd == -1) + return __LINE__; + + /* Write into the middle of the file. */ + ret = pwrite(fd, "abc", 3, offset); + if (ret != 3) { + err = __LINE__; + goto out; + } + + /* Make sure the file offset points somewhere else before pread(). */ + offs = lseek(fd, 0, SEEK_END); + if (offs != offset + 3) { + err = __LINE__; + goto out; + } + + ret = pread(fd, buf, 2, offset + 1); + if (ret != 2) { + err = __LINE__; + goto out; + } + + if (buf[0] != 'b' || buf[1] != 'c') { + err = __LINE__; + goto out; + } + + err = 0; + +out: + close(fd); + + return err; +} + int test_rlimit(void) { struct rlimit rlim = { @@ -1746,6 +1791,7 @@ int run_syscall(int min, int max) CASE_TEST(poll_stdout); EXPECT_SYSNE(1, ({ struct pollfd fds = { 1, POLLOUT, 0}; poll(&fds, 1, 0); }), -1); break; CASE_TEST(poll_fault); EXPECT_SYSER(1, poll(NULL, 1, 0), -1, EFAULT); break; CASE_TEST(prctl); EXPECT_SYSER(1, prctl(PR_SET_NAME, (unsigned long)NULL, 0, 0, 0), -1, EFAULT); break; + CASE_TEST(pwrite_pread); EXPECT_SYSZR(has_working_64bit_syscall_arg, test_pwrite_pread()); break; CASE_TEST(read_badf); EXPECT_SYSER(1, read(-1, &tmp, 1), -1, EBADF); break; CASE_TEST(rlimit); EXPECT_SYSZR(1, test_rlimit()); break; CASE_TEST(rmdir_blah); EXPECT_SYSER(1, rmdir("/blah"), -1, ENOENT); break; From f136640782e13480ca0f52759483f476d93a1b61 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 31 Jul 2026 11:44:00 +0200 Subject: [PATCH 0985/2286] m68k: Use generic BASE_BAUD in asm/serial.h Include asm-generic/serial.h and use the generic BASE_BAUD definition instead of redefining it. Signed-off-by: Thorsten Blum Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260731094402.741645-3-thorsten.blum@linux.dev Signed-off-by: Geert Uytterhoeven --- arch/m68k/include/asm/serial.h | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/arch/m68k/include/asm/serial.h b/arch/m68k/include/asm/serial.h index 3f76bc7b9edb2f..e215b48975d59d 100644 --- a/arch/m68k/include/asm/serial.h +++ b/arch/m68k/include/asm/serial.h @@ -7,15 +7,7 @@ * */ - -/* - * This assumes you have a 1.8432 MHz clock for your UART. - * - * It'd be nice if someone built a serial card with a 24.576 MHz - * clock, since the 16550A is capable of handling a top speed of 1.5 - * megabits/second; but this requires the faster clock. - */ -#define BASE_BAUD ( 1843200 / 16 ) +#include /* Standard COM flags (except for COM4, because of the 8514 problem) */ #ifdef CONFIG_SERIAL_8250_DETECT_IRQ From daf59a78072d36e449b3f78c70c971f00f87fe29 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 31 Jul 2026 11:44:01 +0200 Subject: [PATCH 0986/2286] m68k: Add include guard to asm/serial.h Add an include guard and remove the stale comment while at it. Signed-off-by: Thorsten Blum Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260731094402.741645-4-thorsten.blum@linux.dev Signed-off-by: Geert Uytterhoeven --- arch/m68k/include/asm/serial.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/arch/m68k/include/asm/serial.h b/arch/m68k/include/asm/serial.h index e215b48975d59d..866ef25a74a207 100644 --- a/arch/m68k/include/asm/serial.h +++ b/arch/m68k/include/asm/serial.h @@ -1,11 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* - * include/asm-m68k/serial.h - * - * currently this seems useful only for a Q40, - * it's an almost exact copy of ../asm-alpha/serial.h - * - */ +#ifndef _ASM_M68K_SERIAL_H +#define _ASM_M68K_SERIAL_H #include @@ -26,3 +21,5 @@ { 0, BASE_BAUD, 0x3E8, 4, STD_COM_FLAGS }, /* ttyS2 */ \ { 0, BASE_BAUD, 0x2E8, 3, STD_COM4_FLAGS }, /* ttyS3 */ #endif + +#endif /* _ASM_M68K_SERIAL_H */ From dd22510518598ad0b0341168075b77b647954ff0 Mon Sep 17 00:00:00 2001 From: Miro Kropacek Date: Thu, 20 Aug 2026 10:28:38 +1200 Subject: [PATCH 0987/2286] m68k: Add support for SVEthLANa The SuperVidel FPGA contains an OpenCore ethernet package. Add platform device data and other necessary definitions to support this device on Atari. The ethoc module must be enabled separately by the user. Signed-off-by: Miro Kropacek Reviewed-by: Michael Schmitz Signed-off-by: Michael Schmitz Link: https://lists.debian.org/debian-68k/2026/08/msg00000.html Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260819222838.21256-6-schmitzmic@gmail.com Signed-off-by: Geert Uytterhoeven --- arch/m68k/Kconfig.devices | 12 +++++ arch/m68k/atari/config.c | 75 +++++++++++++++++++++++++++++++ arch/m68k/include/asm/atariints.h | 2 +- arch/m68k/include/asm/irq.h | 6 +-- 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/arch/m68k/Kconfig.devices b/arch/m68k/Kconfig.devices index e6e3efac184074..3f809a02438b26 100644 --- a/arch/m68k/Kconfig.devices +++ b/arch/m68k/Kconfig.devices @@ -67,6 +67,18 @@ config ATARI_ETHERNAT To compile the actual ethernet driver, choose Y or M for the SMC91X option in the network device section; the module will be called smc91x. +config ATARI_SVETHLANA + bool "Atari SuperVidel SVEthLANa Ethernet support" + depends on ATARI + help + Say Y to include support for the SVEthLANa network adapter built + into the SuperVidel FPGA. SuperVidel firmware version 10 or newer + is required (older firmware lacks the Ethernet DMA). + + To compile the actual ethernet driver, choose Y or M for the ETHOC + option in the network device section; the module will be called + ethoc. + config ATARI_ETHERNEC bool "Atari EtherNEC Ethernet support" depends on ATARI_ROM_ISA diff --git a/arch/m68k/atari/config.c b/arch/m68k/atari/config.c index ee2d061efb2a85..bdafc86241b760 100644 --- a/arch/m68k/atari/config.c +++ b/arch/m68k/atari/config.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -748,6 +749,62 @@ static struct platform_device *atari_ethernat_devices[] __initdata = { }; #endif /* CONFIG_ATARI_ETHERNAT */ +#ifdef CONFIG_ATARI_SVETHLANA +/* + * SVEthLANa: OpenCores 10/100 Mbps Ethernet MAC in the SuperVidel FPGA, + * handled by the ethoc driver. Requires SuperVidel firmware version 10 + * or newer (Ethernet DMA). + * + * The MAC can only DMA within the SuperVidel DDR RAM, so the packet + * buffers are carved out of the top of the DDR; the framebuffer lives + * at the bottom. + */ + +#define ATARI_SVETHLANA_PHYS_ADDR 0x80012000 +#define ATARI_SVETHLANA_IRQ 141 /* vector 0xc5 */ + +#define ATARI_SV_VERSION_PHYS_ADDR 0x8001007c +#define ATARI_SV_DDR_END 0xa8000000 +/* 128 packet buffers of 1536 bytes each, the maximum ethoc supports */ +#define SVETHLANA_BUF_SIZE (128 * 1536) + +static struct resource svethlana_resources[] = { + [0] = { + .name = "ethoc-regs", + .start = ATARI_SVETHLANA_PHYS_ADDR, + .end = ATARI_SVETHLANA_PHYS_ADDR + 0x7ff, + .flags = IORESOURCE_MEM, + }, + [1] = { + .name = "ethoc-buf", + .start = ATARI_SV_DDR_END - SVETHLANA_BUF_SIZE, + .end = ATARI_SV_DDR_END - 1, + .flags = IORESOURCE_MEM, + }, + [2] = { + .name = "ethoc-irq", + .start = ATARI_SVETHLANA_IRQ, + .end = ATARI_SVETHLANA_IRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct ethoc_platform_data svethlana_platform_data = { + .phy_id = -1, + .big_endian = true, +}; + +static struct platform_device svethlana_device = { + .name = "ethoc", + .id = -1, + .num_resources = ARRAY_SIZE(svethlana_resources), + .resource = svethlana_resources, + .dev = { + .platform_data = &svethlana_platform_data, + }, +}; +#endif /* CONFIG_ATARI_SVETHLANA */ + #ifdef CONFIG_ATARI_ETHERNEC /* * EtherNEC: RTL8019 (NE2000 compatible) Ethernet chipset, @@ -892,6 +949,24 @@ static int __init atari_platform_init(void) } #endif +#ifdef CONFIG_ATARI_SVETHLANA + { + int error; + void __iomem *sv_version; + + sv_version = ioremap(ATARI_SV_VERSION_PHYS_ADDR, 4); + if (sv_version) { + if (hwreg_present(sv_version) && + (__raw_readl(sv_version) & 0x3ff) >= 10) { + error = platform_device_register(&svethlana_device); + if (error && !rv) + rv = error; + } + iounmap(sv_version); + } + } +#endif + #ifdef CONFIG_ATARI_ETHERNEC { int error; diff --git a/arch/m68k/include/asm/atariints.h b/arch/m68k/include/asm/atariints.h index 6321c44956206f..cb15361d14e91e 100644 --- a/arch/m68k/include/asm/atariints.h +++ b/arch/m68k/include/asm/atariints.h @@ -32,7 +32,7 @@ #define VME_SOURCE_BASE 56 #define VME_MAX_SOURCES 16 -#define NUM_ATARI_SOURCES 141 +#define NUM_ATARI_SOURCES 142 /* convert vector number to int source number */ #define IRQ_VECTOR_TO_SOURCE(v) ((v) - ((v) < 0x20 ? 0x18 : (0x40-8))) diff --git a/arch/m68k/include/asm/irq.h b/arch/m68k/include/asm/irq.h index 2263e92d418ab7..53cbe6db46919a 100644 --- a/arch/m68k/include/asm/irq.h +++ b/arch/m68k/include/asm/irq.h @@ -10,8 +10,8 @@ * different m68k hosts compiled into the kernel. * Currently the Atari has 72 and the Amiga 24, but if both are * supported in the kernel it is better to make room for 72. - * With EtherNAT add-on card on Atari, the highest interrupt - * number is 140 so NR_IRQS needs to be 141. + * With the SVEthLANa adapter in the SuperVidel on Atari, the highest + * interrupt number is 141 so NR_IRQS needs to be 142. */ #if defined(CONFIG_COLDFIRE) #define NR_IRQS 256 @@ -19,7 +19,7 @@ defined(CONFIG_SUN3X) || defined(CONFIG_VIRT) #define NR_IRQS 200 #elif defined(CONFIG_ATARI) -#define NR_IRQS 141 +#define NR_IRQS 142 #elif defined(CONFIG_MAC) #define NR_IRQS 72 #elif defined(CONFIG_Q40) From cf83dfb2f22b5517c9033b1357cca412a4ae0fe3 Mon Sep 17 00:00:00 2001 From: Paolo Pisati Date: Wed, 26 Aug 2026 18:09:11 +0200 Subject: [PATCH 0988/2286] m68k: defconfig: Enable PATA_CSWARP Enable the new CS-Lab Warp PATA driver in the Amiga and multi defconfigs. Acked-by: Geert Uytterhoeven Assisted-by: Claude_Code:claude-opus-5 Signed-off-by: Paolo Pisati Reviewed-by: Damien Le Moal Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260826160911.460165-3-p.pisati@gmail.com Signed-off-by: Geert Uytterhoeven --- arch/m68k/configs/amiga_defconfig | 1 + arch/m68k/configs/multi_defconfig | 1 + 2 files changed, 2 insertions(+) diff --git a/arch/m68k/configs/amiga_defconfig b/arch/m68k/configs/amiga_defconfig index 8b7efe20dd1285..794bad30f5f06d 100644 --- a/arch/m68k/configs/amiga_defconfig +++ b/arch/m68k/configs/amiga_defconfig @@ -284,6 +284,7 @@ CONFIG_ATA=y # CONFIG_ATA_VERBOSE_ERROR is not set # CONFIG_ATA_BMDMA is not set CONFIG_PATA_GAYLE=y +CONFIG_PATA_CSWARP=y CONFIG_PATA_BUDDHA=y CONFIG_MD=y CONFIG_MD_LINEAR=m diff --git a/arch/m68k/configs/multi_defconfig b/arch/m68k/configs/multi_defconfig index 2cbd6bf20a6873..3a9aa52d00e442 100644 --- a/arch/m68k/configs/multi_defconfig +++ b/arch/m68k/configs/multi_defconfig @@ -310,6 +310,7 @@ CONFIG_ATA=y # CONFIG_ATA_BMDMA is not set CONFIG_PATA_FALCON=y CONFIG_PATA_GAYLE=y +CONFIG_PATA_CSWARP=y CONFIG_PATA_BUDDHA=y CONFIG_PATA_PLATFORM=y CONFIG_MD=y From 6cdbe63badcb524c88f2c8b3cee08f2ffc099a1b Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 28 Aug 2026 18:49:40 +0900 Subject: [PATCH 0989/2286] m68k: apollo: Fix interrupt setup The user interrupt vector needs to be installed before the interrupts are unmasked otherwise the timer tick isn't handled and boot up gets stuck in calibrate_delay(). Assisted-by: Claude:claude-fable-5 # Wrote Apollo DN3000 emulator using QEMU Fixes: 0aa78106c661cf6c ("[PATCH] m68k: convert apollo irq code") Signed-off-by: Daniel Palmer Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260828094940.3882602-1-daniel@0x0f.com Signed-off-by: Geert Uytterhoeven --- arch/m68k/apollo/dn_ints.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/m68k/apollo/dn_ints.c b/arch/m68k/apollo/dn_ints.c index ba96a92f8f18f0..89bfb5aaad9df8 100644 --- a/arch/m68k/apollo/dn_ints.c +++ b/arch/m68k/apollo/dn_ints.c @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -11,10 +12,13 @@ static unsigned int apollo_irq_startup(struct irq_data *data) { unsigned int irq = data->irq; + m68k_irq_startup(data); + if (irq < 8) *(volatile unsigned char *)(pica+1) &= ~(1 << irq); else *(volatile unsigned char *)(picb+1) &= ~(1 << (irq - 8)); + return 0; } @@ -26,6 +30,8 @@ static void apollo_irq_shutdown(struct irq_data *data) *(volatile unsigned char *)(pica+1) |= (1 << irq); else *(volatile unsigned char *)(picb+1) |= (1 << (irq - 8)); + + m68k_irq_shutdown(data); } static void apollo_irq_eoi(struct irq_data *data) From 87a981f18ffba758badf3dfc9c3efcd7beca19bb Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Fri, 18 Sep 2026 22:19:25 +0800 Subject: [PATCH 0990/2286] zorro: Use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit to the assign_bit API. Signed-off-by: Peng Fan Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260918141926.3023230-1-peng.fan@oss.nxp.com Signed-off-by: Geert Uytterhoeven --- drivers/zorro/zorro.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/zorro/zorro.c b/drivers/zorro/zorro.c index 45495c423c79d7..cc3177cc681c45 100644 --- a/drivers/zorro/zorro.c +++ b/drivers/zorro/zorro.c @@ -103,10 +103,7 @@ static void __init mark_region(unsigned long start, unsigned long end, while (start < end) { u32 chunk = start>>Z2RAM_CHUNKSHIFT; - if (flag) - set_bit(chunk, zorro_unused_z2ram); - else - clear_bit(chunk, zorro_unused_z2ram); + assign_bit(chunk, zorro_unused_z2ram, flag); start += Z2RAM_CHUNKSIZE; } } From 6fcfce65d445ba6436ffea16ed36b7557cad5968 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Mon, 31 Aug 2026 11:44:34 +0200 Subject: [PATCH 0991/2286] m68k: defconfig: Update defconfigs for v7.3-rc1 - Enable modular build of the new xor_gen, raid P/Q generation, and bitmap, IDA and Maple Tree region allocation tests. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/2b621309c18a776a41d8daaa4b67e652482b3719.1788169336.git.geert@linux-m68k.org --- arch/m68k/configs/amiga_defconfig | 3 +++ arch/m68k/configs/apollo_defconfig | 3 +++ arch/m68k/configs/atari_defconfig | 3 +++ arch/m68k/configs/bvme6000_defconfig | 3 +++ arch/m68k/configs/hp300_defconfig | 3 +++ arch/m68k/configs/mac_defconfig | 3 +++ arch/m68k/configs/multi_defconfig | 3 +++ arch/m68k/configs/mvme147_defconfig | 3 +++ arch/m68k/configs/mvme16x_defconfig | 3 +++ arch/m68k/configs/q40_defconfig | 3 +++ arch/m68k/configs/sun3_defconfig | 3 +++ arch/m68k/configs/sun3x_defconfig | 3 +++ 12 files changed, 36 insertions(+) diff --git a/arch/m68k/configs/amiga_defconfig b/arch/m68k/configs/amiga_defconfig index 794bad30f5f06d..fefcaa5ad44c32 100644 --- a/arch/m68k/configs/amiga_defconfig +++ b/arch/m68k/configs/amiga_defconfig @@ -552,6 +552,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -579,6 +581,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/apollo_defconfig b/arch/m68k/configs/apollo_defconfig index c7f7705806f97c..84d4a6ed1707c6 100644 --- a/arch/m68k/configs/apollo_defconfig +++ b/arch/m68k/configs/apollo_defconfig @@ -506,6 +506,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -533,6 +535,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/atari_defconfig b/arch/m68k/configs/atari_defconfig index 2fb213161baec9..22d2fc0e26a8d1 100644 --- a/arch/m68k/configs/atari_defconfig +++ b/arch/m68k/configs/atari_defconfig @@ -529,6 +529,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -556,6 +558,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/bvme6000_defconfig b/arch/m68k/configs/bvme6000_defconfig index 6730242d4d4861..0f79c6382d3548 100644 --- a/arch/m68k/configs/bvme6000_defconfig +++ b/arch/m68k/configs/bvme6000_defconfig @@ -498,6 +498,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -525,6 +527,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/hp300_defconfig b/arch/m68k/configs/hp300_defconfig index 2b3ff26ac8fdff..2bf412a1713c75 100644 --- a/arch/m68k/configs/hp300_defconfig +++ b/arch/m68k/configs/hp300_defconfig @@ -508,6 +508,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -535,6 +537,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/mac_defconfig b/arch/m68k/configs/mac_defconfig index 61f32ef2547e05..5496f1b6053191 100644 --- a/arch/m68k/configs/mac_defconfig +++ b/arch/m68k/configs/mac_defconfig @@ -527,6 +527,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -554,6 +556,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/multi_defconfig b/arch/m68k/configs/multi_defconfig index 3a9aa52d00e442..506406be9c8e2e 100644 --- a/arch/m68k/configs/multi_defconfig +++ b/arch/m68k/configs/multi_defconfig @@ -615,6 +615,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -642,6 +644,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/mvme147_defconfig b/arch/m68k/configs/mvme147_defconfig index 019f8b8070be8d..bbce43b8f7aa4c 100644 --- a/arch/m68k/configs/mvme147_defconfig +++ b/arch/m68k/configs/mvme147_defconfig @@ -498,6 +498,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -525,6 +527,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/mvme16x_defconfig b/arch/m68k/configs/mvme16x_defconfig index 38916328742582..a63d09dc8dbd39 100644 --- a/arch/m68k/configs/mvme16x_defconfig +++ b/arch/m68k/configs/mvme16x_defconfig @@ -499,6 +499,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -526,6 +528,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/q40_defconfig b/arch/m68k/configs/q40_defconfig index cbc41443cffa6e..d6b7cbab170048 100644 --- a/arch/m68k/configs/q40_defconfig +++ b/arch/m68k/configs/q40_defconfig @@ -517,6 +517,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -544,6 +546,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/sun3_defconfig b/arch/m68k/configs/sun3_defconfig index 273e840dc8d6a0..bee5cdd2063757 100644 --- a/arch/m68k/configs/sun3_defconfig +++ b/arch/m68k/configs/sun3_defconfig @@ -496,6 +496,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -522,6 +524,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m diff --git a/arch/m68k/configs/sun3x_defconfig b/arch/m68k/configs/sun3x_defconfig index 843d72924bcb20..ef2b76a064391d 100644 --- a/arch/m68k/configs/sun3x_defconfig +++ b/arch/m68k/configs/sun3x_defconfig @@ -496,6 +496,8 @@ CONFIG_CRYPTO_USER_API_AEAD=m CONFIG_PRIME_NUMBERS=m CONFIG_CRC_ENABLE_ALL_FOR_KUNIT=m CONFIG_CRC_BENCHMARK=y +CONFIG_XOR_BENCHMARK=y +CONFIG_RAID6_PQ_KUNIT_BENCHMARK=y CONFIG_XZ_DEC_TEST=m # CONFIG_SECTION_MISMATCH_WARN_ONLY is not set CONFIG_MAGIC_SYSRQ=y @@ -523,6 +525,7 @@ CONFIG_TEST_VMALLOC=m CONFIG_TEST_WORKQUEUE=m CONFIG_TEST_BPF=m CONFIG_FIND_BIT_BENCHMARK=m +CONFIG_REGION_ALLOC_BENCHMARK=m CONFIG_TEST_FIRMWARE=m CONFIG_TEST_SYSCTL=m CONFIG_LINEAR_RANGES_TEST=m From af32c3cb72529b14bfb2685bbdaa2b1514496b58 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Sun, 27 Sep 2026 15:05:50 +0200 Subject: [PATCH 0992/2286] m68k: defconfig: Enable ATARI_SVETHLANA and ETHOC Enable support for the SVEthLANa network adapter built into the SuperVidel CT60 FPGA extension, which is compatible with OpenCores 10/100 Mbps Ethernet. SVEthLANa works fine with phylib's Generic PHY fallback, so there is no need to enable any PHY drivers. Signed-off-by: Geert Uytterhoeven Link: https://patch.msgid.link/42db3dfd79c69a95475b3b187437efcc162dfbff.1790514278.git.geert@linux-m68k.org --- arch/m68k/configs/atari_defconfig | 2 ++ arch/m68k/configs/multi_defconfig | 2 ++ 2 files changed, 4 insertions(+) diff --git a/arch/m68k/configs/atari_defconfig b/arch/m68k/configs/atari_defconfig index 22d2fc0e26a8d1..22d8a85816015a 100644 --- a/arch/m68k/configs/atari_defconfig +++ b/arch/m68k/configs/atari_defconfig @@ -25,6 +25,7 @@ CONFIG_NFBLOCK=y CONFIG_NFCON=y CONFIG_NFETH=y CONFIG_ATARI_ETHERNAT=y +CONFIG_ATARI_SVETHLANA=y CONFIG_ATARI_ETHERNEC=y CONFIG_ATARI_DSP56K=m CONFIG_MODULES=y @@ -328,6 +329,7 @@ CONFIG_TUN=m CONFIG_VETH=m CONFIG_ATARILANCE=y CONFIG_NE2000=y +CONFIG_ETHOC=y CONFIG_SMC91X=y CONFIG_PPP=m CONFIG_PPP_BSDCOMP=m diff --git a/arch/m68k/configs/multi_defconfig b/arch/m68k/configs/multi_defconfig index 506406be9c8e2e..a8f593b4fc6a73 100644 --- a/arch/m68k/configs/multi_defconfig +++ b/arch/m68k/configs/multi_defconfig @@ -38,6 +38,7 @@ CONFIG_NFBLOCK=y CONFIG_NFCON=y CONFIG_NFETH=y CONFIG_ATARI_ETHERNAT=y +CONFIG_ATARI_SVETHLANA=y CONFIG_ATARI_ETHERNEC=y CONFIG_ATARI_DSP56K=m CONFIG_AMIGA_BUILTIN_SERIAL=y @@ -383,6 +384,7 @@ CONFIG_MAC8390=y CONFIG_NE2000=y CONFIG_APNE=y CONFIG_ZORRO8390=y +CONFIG_ETHOC=y CONFIG_SMC91X=y CONFIG_PLIP=m CONFIG_PPP=m From a98db7418797bc27bef53c861b97ac748f598414 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 27 Sep 2026 12:12:27 -0400 Subject: [PATCH 0993/2286] kbuild: keep .modinfo when vmlinux is linked with --gc-sections Builds with CONFIG_LD_DEAD_CODE_DATA_ELIMINATION=y, such as arm allmodconfig, now fail while extracting modules.builtin.modinfo: arm-linux-gnueabihf-objcopy: vmlinux.unstripped: can't dump section '.modinfo' - it does not exist: file format not recognized /bin/sh: 1: cannot open modules.builtin.modinfo: No such file or, with LLVM: llvm-objcopy: error: 'vmlinux.unstripped': section '.modinfo' not found Nothing references the MODULE_INFO() strings, so --gc-sections discards every .modinfo input section and vmlinux.unstripped ends up with no .modinfo data. This is not new: ever since modules.builtin.modinfo started being extracted from vmlinux.unstripped, these builds have silently produced an empty file, losing the modinfo of every built-in module. An arm multi_v7_defconfig build with LD_DEAD_CODE_DATA_ELIMINATION on current mainline yields a 0 byte modules.builtin.modinfo. The switch to --dump-section only turned that into a build error. Wrap the input section in KEEP() so that the linker retains it. The output section is still (INFO), so nothing is allocated for it. With the same arm config, modules.builtin.modinfo now has 14119 entries, and on x86_64 defconfig, where nothing is garbage collected, it is byte-identical before and after this change. Found by KernelCI builds of the linus-next tree. Fixes: 39cfd5b12160 ("kbuild: extract modules.builtin.modinfo from vmlinux.unstripped") Fixes: 5fe4dd596641 ("kbuild: do not allocate .modinfo in vmlinux") Assisted-by: LLM Signed-off-by: Sasha Levin Link: https://patch.msgid.link/20260927161227.1179863-1-sashal@kernel.org Signed-off-by: Nathan Chancellor --- include/asm-generic/vmlinux.lds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index a6730d34e8c673..7a3e9f00d6283f 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -855,7 +855,7 @@ KLP_SYMID #define MODINFO \ - .modinfo (INFO) : { *(.modinfo) } + .modinfo (INFO) : { KEEP(*(.modinfo)) } #ifdef CONFIG_GENERIC_BUG #define BUG_TABLE \ From c337b60bdadc819c39f802d8e6418c320c580e0e Mon Sep 17 00:00:00 2001 From: Ovidiu Panait Date: Wed, 16 Sep 2026 12:00:03 +0000 Subject: [PATCH 0994/2286] thermal/drivers/renesas/rzg3e: Remove redundant pm_runtime_mark_last_busy() calls pm_runtime_put_autosuspend() already calls pm_runtime_mark_last_busy() internally, so the explicit calls are not needed. Remove them. Signed-off-by: Ovidiu Panait Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260916120003.87018-6-ovidiu.panait.rb@renesas.com --- drivers/thermal/renesas/rzg3e_thermal.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index ffe2b872503149..a1ceff79e83e6d 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -262,7 +262,6 @@ static int rzg3e_thermal_get_temp(struct thermal_zone_device *tz, int *temp) *temp, *temp / 1000, abs(*temp) % 1000, code); out: - pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); return ret; } @@ -313,7 +312,6 @@ static int rzg3e_thermal_set_trips(struct thermal_zone_device *tz, /* Unmask compare IRQ */ writel(TSU_SIER_CMPIE, priv->base + TSU_SIER); - pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); return 0; @@ -485,7 +483,6 @@ static int rzg3e_thermal_probe(struct platform_device *pdev) if (ret) dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); - pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev); dev_info(dev, "RZ/G3E thermal sensor registered\n"); From eca1247443d41f695c389e4109c2d7986f8fd95e Mon Sep 17 00:00:00 2001 From: Priyansh Jain Date: Fri, 8 May 2026 15:37:00 +0530 Subject: [PATCH 0995/2286] thermal/drivers/qcom/tsens: Widen temperature limits to match hardware range MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TSENS v2 software driver currently clamps trip_min_temp and trip_max_temp to -40°C and 120°C respectively. However, the TSENS v2 hardware temperature threshold registers support a wider programmable range from -204°C to +204°C. On newer chipsets using TSENS v2, devices may legitimately operate beyond the existing software limits (for example, up to 130°C). When a trip temperature is programmed outside the software clamped range, it is constrained to 120°C on the upper end or -40°C on the lower end. If the actual temperature continues to exceed this clamped limit, the threshold is immediately violated again, which can result in a continuous interrupt storm. Expand the TSENS v2 software trip temperature limits to match the full hardware supported range (-204°C to +204°C). This avoids repeated threshold reprogramming and ensures correct trip handling on TSENS v2 based platforms. Signed-off-by: Priyansh Jain Signed-off-by: Daniel Lezcano Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260508100700.772985-3-priyansh.jain@oss.qualcomm.com --- drivers/thermal/qcom/tsens-v2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c index 2ee117aa91baea..843dd29fad2e38 100644 --- a/drivers/thermal/qcom/tsens-v2.c +++ b/drivers/thermal/qcom/tsens-v2.c @@ -54,8 +54,8 @@ static struct tsens_features tsens_v2_feat = { .adc = 0, .srot_split = 1, .max_sensors = 16, - .trip_min_temp = -40000, - .trip_max_temp = 120000, + .trip_min_temp = -204000, + .trip_max_temp = 204000, }; static struct tsens_features ipq8074_feat = { From 6b7f5abb48cab4ab500ca9fdfcb28aeb0e4d4971 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:15:59 +0100 Subject: [PATCH 0996/2286] dt-bindings: thermal: r9a08g045-tsu: Document RZ/G3L TSU Add the renesas,r9a08g046-tsu compatible string for the RZ/G3L Thermal Sensor Unit (a.k.a TSU), which is similar to the RZ/G3S TSU but uses different calibration constants. TSU is connected to a dedicated ADC IP (a.k.a ADC1) on RZ/G3L. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Reviewed-by: Claudiu Beznea Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260925061617.127349-2-biju.das.jz@bp.renesas.com --- .../devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml b/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml index 573e2b9d37524b..20fc1a8000e33e 100644 --- a/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml +++ b/Documentation/devicetree/bindings/thermal/renesas,r9a08g045-tsu.yaml @@ -17,7 +17,9 @@ $ref: thermal-sensor.yaml# properties: compatible: - const: renesas,r9a08g045-tsu + enum: + - renesas,r9a08g045-tsu + - renesas,r9a08g046-tsu reg: maxItems: 1 From 91e5a33675e56dbc982703655cdf4637f5e76f23 Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:00 +0100 Subject: [PATCH 0997/2286] thermal/drivers/renesas/rzg3s: Convert calibration defaults to per-SoC match data Move the default (software) calibration values SW_CALIB0_VAL and SW_CALIB1_VAL, used when FUSE values are missing, into a new struct rzg3s_thermal_info retrieved via of_device_get_match_data(). This allows different SoCs sharing this driver to provide their own default calibration constants, which is needed as RZ/G3L uses different values than RZ/G3S. No functional change intended for RZ/G3S. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Jonathan Cameron Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20260925061617.127349-3-biju.das.jz@bp.renesas.com --- drivers/thermal/renesas/rzg3s_thermal.c | 32 ++++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index 7ced8f76a0ec39..146b73a1b751dc 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,10 +28,6 @@ #define TSU_READ_STEPS 8 -/* Default calibration values, if FUSE values are missing. */ -#define SW_CALIB0_VAL 1297 -#define SW_CALIB1_VAL 751 - #define MCELSIUS(temp) ((temp) * MILLIDEGREE_PER_DEGREE) /** @@ -55,6 +52,16 @@ struct rzg3s_thermal_priv { u16 calib1; }; +/** + * struct rzg3s_thermal_info - RZ/G3S thermal information + * @calib0: calibration value if FUSE value is missing + * @calib1: calibration value if FUSE value is missing + */ +struct rzg3s_thermal_info { + u16 calib0; + u16 calib1; +}; + static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz); @@ -149,7 +156,8 @@ static const struct thermal_zone_device_ops rzg3s_tz_of_ops = { .change_mode = rzg3s_thermal_change_mode, }; -static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) +static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv, + const struct rzg3s_thermal_info *info) { struct device *dev = priv->dev; u32 val; @@ -163,13 +171,13 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) if (val & OTPTSUTRIM_EN_MASK) priv->calib0 = FIELD_GET(OTPTSUTRIM_MASK, val); else - priv->calib0 = SW_CALIB0_VAL; + priv->calib0 = info->calib0; val = readl(priv->base + OTPTSUTRIM_REG(1)); if (val & OTPTSUTRIM_EN_MASK) priv->calib1 = FIELD_GET(OTPTSUTRIM_MASK, val); else - priv->calib1 = SW_CALIB1_VAL; + priv->calib1 = info->calib1; pm_runtime_put_autosuspend(dev); @@ -178,6 +186,7 @@ static int rzg3s_thermal_read_calib(struct rzg3s_thermal_priv *priv) static int rzg3s_thermal_probe(struct platform_device *pdev) { + const struct rzg3s_thermal_info *info = device_get_match_data(&pdev->dev); struct rzg3s_thermal_priv *priv; struct device *dev = &pdev->dev; int ret; @@ -208,7 +217,7 @@ static int rzg3s_thermal_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Failed to enable runtime PM!\n"); - ret = rzg3s_thermal_read_calib(priv); + ret = rzg3s_thermal_read_calib(priv, info); if (ret) return dev_err_probe(dev, ret, "Failed to read calibration data!\n"); @@ -251,8 +260,13 @@ static const struct dev_pm_ops rzg3s_thermal_pm_ops = { SYSTEM_SLEEP_PM_OPS(rzg3s_thermal_suspend, rzg3s_thermal_resume) }; +static const struct rzg3s_thermal_info rzg3s_info = { + .calib0 = 1297, + .calib1 = 751, +}; + static const struct of_device_id rzg3s_thermal_dt_ids[] = { - { .compatible = "renesas,r9a08g045-tsu" }, + { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids); From e856ca3013b3074fd07cb81c90b1de332f69153c Mon Sep 17 00:00:00 2001 From: Biju Das Date: Fri, 25 Sep 2026 07:16:01 +0100 Subject: [PATCH 0998/2286] thermal/drivers/renesas/rzg3s: Add RZ/G3L TSU support Add the renesas,r9a08g046-tsu compatible string and its default (software) calibration constants for RZ/G3L, used when FUSE calibration values are missing. Signed-off-by: Biju Das Signed-off-by: Daniel Lezcano Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20260925061617.127349-4-biju.das.jz@bp.renesas.com --- drivers/thermal/renesas/rzg3s_thermal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thermal/renesas/rzg3s_thermal.c b/drivers/thermal/renesas/rzg3s_thermal.c index 146b73a1b751dc..a8716da7fcd2f4 100644 --- a/drivers/thermal/renesas/rzg3s_thermal.c +++ b/drivers/thermal/renesas/rzg3s_thermal.c @@ -265,8 +265,14 @@ static const struct rzg3s_thermal_info rzg3s_info = { .calib1 = 751, }; +static const struct rzg3s_thermal_info rzg3l_info = { + .calib0 = 1322, + .calib1 = 772, +}; + static const struct of_device_id rzg3s_thermal_dt_ids[] = { { .compatible = "renesas,r9a08g045-tsu", .data = &rzg3s_info }, + { .compatible = "renesas,r9a08g046-tsu", .data = &rzg3l_info }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rzg3s_thermal_dt_ids); From c7243accb48a7987b2e80751f712c3f4fe2b3714 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 9 Aug 2026 23:32:25 +0200 Subject: [PATCH 0999/2286] x86/cpu: Constify struct x86_cpu_id 'struct x86_cpu_id' is not modified in this compilation unit. Constifying this structure moves some data to a read-only section, so increases overall security. It is only used in cpu_has_old_microcode() which is an __init function. So, using __initconst is safe and will save about 6 kB of memory at runtime. On a x86_64, with allmodconfig: text data bss dec hex filename 55725 28774 512 85011 14c13 arch/x86/kernel/cpu/common.o.before 61464 22918 512 84894 14b9e arch/x86/kernel/cpu/common.o.after [ bp: Massage commit message. ] Signed-off-by: Christophe JAILLET Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/f08d3a0e7aefe3bad66251927cbd03b8cbf9df73.1786311119.git.christophe.jaillet@wanadoo.fr --- arch/x86/kernel/cpu/common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index c7352827f491d5..593ca8ce3894cd 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -1427,7 +1427,7 @@ static bool __init vulnerable_to_its(u64 x86_arch_cap_msr) return false; } -static struct x86_cpu_id cpu_latest_microcode[] = { +static const struct x86_cpu_id cpu_latest_microcode[] __initconst = { #include "microcode/intel-ucode-defs.h" {} }; From 313b652837d0541684680a7d07a95c5560441d4f Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 22 Sep 2026 18:51:40 +0200 Subject: [PATCH 1000/2286] sched/core: Drop mutex locks before proxy rescheduling find_proxy_task() can call proxy_resched_idle() from return expressions inside mutex wait_lock and blocked task lock guard scopes. Return expressions are evaluated before scoped guards are released, so proxy_resched_idle() invokes scheduling-class callbacks while both raw spinlocks are held. Route these cases through a common label outside the guard scopes. The common label is required because a direct return would evaluate proxy_resched_idle() before guard cleanup and run the class callbacks under the proxy-exec locks again. This is a preparatory change to support proxy execution with sched_ext. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: K Prateek Nayak Link: https://patch.msgid.link/20260922165445.943315-2-arighi@nvidia.com --- kernel/sched/core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index ee9b443f760d7e..f34e73cde87d3a 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6980,7 +6980,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) if (!READ_ONCE(owner->on_rq) || owner->se.sched_delayed) { /* XXX Don't handle blocked owners/delayed dequeue yet */ if (curr_in_chain) - return proxy_resched_idle(rq); + goto resched_idle; __clear_task_blocked_on(p, NULL); goto deactivate; } @@ -6992,7 +6992,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) * and leave that CPU to sort things out. */ if (curr_in_chain) - return proxy_resched_idle(rq); + goto resched_idle; goto migrate_task; } @@ -7005,7 +7005,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) * case we should end up back in find_proxy_task(), this time * hopefully with all relevant tasks already enqueued. */ - return proxy_resched_idle(rq); + goto resched_idle; } /* @@ -7042,7 +7042,7 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) * So schedule rq->idle so that ttwu_runnable() can get the rq * lock and mark owner as running. */ - return proxy_resched_idle(rq); + goto resched_idle; } /* * OK, now we're absolutely sure @owner is on this @@ -7062,6 +7062,8 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) } return owner; +resched_idle: + return proxy_resched_idle(rq); deactivate: proxy_deactivate(rq, p); return NULL; From 8f8c0417e97382c3473ef56b0cd472badb40e4a0 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 22 Sep 2026 18:51:41 +0200 Subject: [PATCH 1001/2286] sched/core: Dequeue waking proxy donors before reset proxy_needs_return() resets an active donor while holding blocked_lock. proxy_reset_donor() invokes scheduling-class callbacks, adding an unnecessary raw-spinlock nesting. For an EXT donor, resetting rq->donor first calls put_prev_task_scx() while SCX_TASK_QUEUED and is_blocked are still set. That path reenqueues the retained donor through scx_do_enqueue_task(), only for the following block_task() to dequeue it again. Split block_task() so the waking donor is first dequeued from its scheduling class while it is still rq->donor. This lets dequeue_task_scx() end the donor's running session and clear SCX_TASK_QUEUED before proxy_reset_donor() invokes put_prev_task_scx(). Keep the generic on_rq state set until donor references are replaced, then complete the runqueue removal. This follows the normal sleep ordering, avoids transiently re-enqueuing the waking donor, and moves the scheduling-class callbacks outside blocked_lock. This is a preparatory change to support proxy execution with sched_ext. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260922165445.943315-3-arighi@nvidia.com --- kernel/sched/core.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f34e73cde87d3a..07cc17141afedc 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -2252,7 +2252,8 @@ void deactivate_task(struct rq *rq, struct task_struct *p, int flags) dequeue_task(rq, p, flags); } -static void block_task(struct rq *rq, struct task_struct *p, unsigned long task_state) +static bool dequeue_block_task(struct rq *rq, struct task_struct *p, + unsigned long task_state) { int flags = DEQUEUE_NOCLOCK; @@ -2273,9 +2274,15 @@ static void block_task(struct rq *rq, struct task_struct *p, unsigned long task_ * * Where __schedule() and ttwu() have matching control dependencies. * - * After this, schedule() must not care about p->state any more. + * Once the caller invokes __block_task(), schedule() must not care about + * p->state any more. */ - if (dequeue_task(rq, p, DEQUEUE_SLEEP | flags)) + return dequeue_task(rq, p, DEQUEUE_SLEEP | flags); +} + +static void block_task(struct rq *rq, struct task_struct *p, unsigned long task_state) +{ + if (dequeue_block_task(rq, p, task_state)) __block_task(rq, p); } @@ -3781,6 +3788,8 @@ static inline void proxy_reset_donor(struct rq *rq) */ static inline bool proxy_needs_return(struct rq *rq, struct task_struct *p) { + bool dequeued; + /* * Typically per __set_task_cpu(), task_cpu(p) == p->wake_cpu. * @@ -3803,12 +3812,23 @@ static inline bool proxy_needs_return(struct rq *rq, struct task_struct *p) /* If already current, don't need to return migrate */ if (task_current(rq, p)) return false; - - /* If we're return migrating the rq->donor, switch it out for idle */ - if (task_current_donor(rq, p)) - proxy_reset_donor(rq); } - block_task(rq, p, TASK_WAKING); + + dequeued = dequeue_block_task(rq, p, TASK_WAKING); + + /* + * Dequeue @p from its scheduling class before resetting rq->donor. + * In particular, sched_ext needs to end the donor's running session + * and clear SCX_TASK_QUEUED before put_prev_task_scx() is called by + * proxy_reset_donor(); otherwise it would reenqueue the blocked donor. + * + * Keep on_rq set until all donor references have been replaced. + */ + if (task_current_donor(rq, p)) + proxy_reset_donor(rq); + + if (dequeued) + __block_task(rq, p); return true; } #else /* !CONFIG_SCHED_PROXY_EXEC */ From a49653d0abebee32bf3ecf650fae9f9dd8298135 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 22 Sep 2026 18:51:42 +0200 Subject: [PATCH 1002/2286] sched/core: Mark wakeups completed through ttwu_runnable() ttwu_runnable() completes a wakeup for a task which remains queued without calling activate_task() or its scheduling-class enqueue callback. Scheduling classes currently cannot distinguish this path from wakeups which performed a full activation. Add WF_TTWU_RQ to identify wakeups completed through ttwu_runnable(). A later sched_ext change uses it to reconsider retained proxy donors which wake without another ops.enqueue() call. No functional change. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260922165445.943315-4-arighi@nvidia.com --- kernel/sched/core.c | 2 +- kernel/sched/sched.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 07cc17141afedc..6b42f916a36edf 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3919,7 +3919,7 @@ static int ttwu_runnable(struct task_struct *p, int wake_flags) * When on_rq && !on_cpu the task is preempted, see if * it should preempt the task that is current now. */ - wakeup_preempt(rq, p, wake_flags); + wakeup_preempt(rq, p, wake_flags | WF_TTWU_RQ); } ttwu_do_wakeup(p); return 1; diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index b98084e1f5b08d..a0f391fc4294e4 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2545,6 +2545,7 @@ static inline int task_on_rq_migrating(struct task_struct *p) #define WF_MIGRATED 0x20 /* Internal use, task got migrated */ #define WF_CURRENT_CPU 0x40 /* Prefer to move the wakee to the current CPU. */ #define WF_RQ_SELECTED 0x80 /* ->select_task_rq() was called */ +#define WF_TTWU_RQ 0x100 /* Wakeup completed through ttwu_runnable() */ static_assert(WF_EXEC == SD_BALANCE_EXEC); static_assert(WF_FORK == SD_BALANCE_FORK); From 57c75e3ae38c40acb5882a57c36f2d5234e066d0 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 22 Sep 2026 18:51:43 +0200 Subject: [PATCH 1003/2286] sched: Add helper to block retained proxy donors Scheduler ownership changes may need to turn a mutex-blocked task retained on the runqueue for proxy execution back into a normally blocked task. Add sched_proxy_block_task() to perform that transition while holding p->pi_lock and the task's runqueue lock. Reset an active donor to the physical current task before blocking it. If DELAY_DEQUEUE keeps a FAIR donor queued, the immediately following sched_change_begin() completes the dequeue through switching_from_fair() before recording its queued state. This is a preparatory change to support proxy execution with sched_ext. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Acked-by: John Stultz Link: https://patch.msgid.link/20260922165445.943315-5-arighi@nvidia.com --- kernel/sched/core.c | 28 ++++++++++++++++++++++++++++ kernel/sched/sched.h | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 6b42f916a36edf..35374e0240c318 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -6834,6 +6834,34 @@ static void proxy_deactivate(struct rq *rq, struct task_struct *donor) block_task(rq, donor, state); } +/* + * Remove a retained proxy donor before changing its scheduler ownership. + * The caller holds p->pi_lock, so p cannot wake and migrate if block_task() + * drops it from the runqueue. If DELAY_DEQUEUE keeps a blocked fair task + * queued, switching_from_fair() completes the dequeue in the immediately + * following sched_change_begin(). + */ +void sched_proxy_block_task(struct rq *rq, struct task_struct *p) +{ + unsigned long state = READ_ONCE(p->__state); + + lockdep_assert_held(&p->pi_lock); + lockdep_assert_rq_held(rq); + + if (!p->is_blocked || !task_on_rq_queued(p)) + return; + if (WARN_ON_ONCE(state == TASK_RUNNING)) + return; + + if (task_current_donor(rq, p)) + proxy_reset_donor(rq); + + if (!p->se.sched_delayed) + block_task(rq, p, state); + + WARN_ON_ONCE(task_on_rq_queued(p) && !p->se.sched_delayed); +} + static inline void proxy_release_rq_lock(struct rq *rq, struct rq_flags *rf) __releases(__rq_lockp(rq)) { diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index a0f391fc4294e4..d41d23a5632b33 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2516,6 +2516,12 @@ static inline bool task_is_blocked(struct task_struct *p) return !!p->blocked_on; } +#ifdef CONFIG_SCHED_PROXY_EXEC +void sched_proxy_block_task(struct rq *rq, struct task_struct *p); +#else +static inline void sched_proxy_block_task(struct rq *rq, struct task_struct *p) {} +#endif + static inline int task_on_cpu(struct rq *rq, struct task_struct *p) { return p->on_cpu; From be100c77178ef2299a080eb698720832a870248b Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 22 Sep 2026 18:51:44 +0200 Subject: [PATCH 1004/2286] sched: Add sched_ext hooks for proxy execution Proxy execution splits the scheduling context (the donor) from the execution context (the lock owner). sched_ext needs to observe that split at three points in __schedule(): - whether a blocked EXT task can be retained on the runqueue as a donor, - when a donor's scheduling context starts driving a lock owner, - after proxy resolution, when deferred reenqueue work that was blocked by an active proxy relationship can be retried. Introduce scx_allow_proxy_exec(), scx_proxy_donor_start() and scx_proxy_reenqueue_retry(), and add their call sites in __schedule(). The implementations are empty here and are filled in by the sched_ext changes that follow, so that all the sched core changes needed by proxy execution stay together in the preparatory patches. SCHED_PROXY_EXEC still depends on !SCHED_CLASS_EXT, so the new hooks are inert: they are compiled out with CONFIG_SCHED_CLASS_EXT=n and unreachable otherwise. This is a preparatory change to support proxy execution with sched_ext. No functional change. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260922165445.943315-6-arighi@nvidia.com --- kernel/sched/core.c | 12 +++++++----- kernel/sched/ext/ext.c | 13 +++++++++++++ kernel/sched/ext/ext.h | 7 +++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 35374e0240c318..acc89310d48837 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -7243,13 +7243,12 @@ static void __sched notrace __schedule(int sched_mode) } } else if (!preempt && prev_state) { /* - * We pass task_is_blocked() as the should_block arg - * in order to keep mutex-blocked tasks on the runqueue - * for slection with proxy-exec (without proxy-exec - * task_is_blocked() will always be false). + * Keep mutex-blocked tasks on the runqueue for proxy execution + * only when their scheduling class allows it. Without proxy + * execution, task_is_blocked() always returns false. */ try_to_block_task(rq, prev, &prev_state, - !task_is_blocked(prev)); + !task_is_blocked(prev) || !scx_allow_proxy_exec(prev)); switch_count = &prev->nvcsw; } @@ -7270,6 +7269,7 @@ static void __sched notrace __schedule(int sched_mode) } if (next == rq->idle) { zap_balance_callbacks(rq); + scx_proxy_reenqueue_retry(rq, next); goto keep_resched; } } @@ -7290,6 +7290,8 @@ static void __sched notrace __schedule(int sched_mode) donor->sched_class->put_prev_task(rq, donor, donor); donor->sched_class->set_next_task(rq, donor, SNT_PICK); } + scx_proxy_donor_start(rq); + scx_proxy_reenqueue_retry(rq, next); } else { rq_set_donor(rq, next); } diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c index 53275bc7c0299a..3f79156467799d 100644 --- a/kernel/sched/ext/ext.c +++ b/kernel/sched/ext/ext.c @@ -24,6 +24,11 @@ DEFINE_RAW_SPINLOCK(scx_sched_lock); +bool scx_allow_proxy_exec(const struct task_struct *p) +{ + return true; +} + /* * NOTE: sched_ext is in the process of growing multiple scheduler support and * scx_root usage is in a transitional state. Naked dereferences are safe if the @@ -1079,6 +1084,10 @@ static void schedule_deferred_locked(struct rq *rq) schedule_deferred(rq); } +void scx_proxy_reenqueue_retry(struct rq *rq, struct task_struct *next) +{ +} + void schedule_dsq_reenq(struct scx_sched *sch, struct scx_dispatch_q *dsq, u64 reenq_flags, struct rq *locked_rq) { @@ -3065,6 +3074,10 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, enum snt_e t } } +void scx_proxy_donor_start(struct rq *rq) +{ +} + static enum scx_cpu_preempt_reason preempt_reason_from_class(const struct sched_class *class) { diff --git a/kernel/sched/ext/ext.h b/kernel/sched/ext/ext.h index 0b7fc46aee08c8..3cfbfeb1bf9d12 100644 --- a/kernel/sched/ext/ext.h +++ b/kernel/sched/ext/ext.h @@ -20,6 +20,9 @@ void scx_rq_deactivate(struct rq *rq); int scx_check_setscheduler(struct task_struct *p, int policy); bool task_should_scx(int policy); bool scx_allow_ttwu_queue(const struct task_struct *p); +bool scx_allow_proxy_exec(const struct task_struct *p); +void scx_proxy_donor_start(struct rq *rq); +void scx_proxy_reenqueue_retry(struct rq *rq, struct task_struct *next); void init_sched_ext_class(void); static inline u32 scx_cpuperf_target(s32 cpu) @@ -54,6 +57,10 @@ static inline void scx_rq_deactivate(struct rq *rq) {} static inline int scx_check_setscheduler(struct task_struct *p, int policy) { return 0; } static inline bool task_on_scx(const struct task_struct *p) { return false; } static inline bool scx_allow_ttwu_queue(const struct task_struct *p) { return true; } +static inline bool scx_allow_proxy_exec(const struct task_struct *p) { return true; } +static inline void scx_proxy_donor_start(struct rq *rq) {} +static inline void scx_proxy_reenqueue_retry(struct rq *rq, + struct task_struct *next) {} static inline void init_sched_ext_class(void) {} #endif /* CONFIG_SCHED_CLASS_EXT */ From a8d0854a76a876ea474e17322acb680716a09da2 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:16 +0530 Subject: [PATCH 1005/2286] sched/cputime: Add kcpustat_field_total helper Provide a new helper function which sums up a given type of cpustat over a specified cpumask. This allows the caller's code to be simpler and avoids duplication. For example, subsequent patch in the steal governor use this exact same pattern when calculating steal time. Number of cpus can be derived from cpumask_weight() where necessary. Suggested-by: Yury Norov Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Yury Norov Reviewed-by: Mete Durlu Acked-by: Frederic Weisbecker Link: https://patch.msgid.link/20260928053728.797539-2-sshegde@linux.ibm.com --- arch/s390/kernel/hiperdispatch.c | 10 +++------- fs/proc/uptime.c | 6 +----- include/linux/kernel_stat.h | 11 +++++++++++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/arch/s390/kernel/hiperdispatch.c b/arch/s390/kernel/hiperdispatch.c index 2172065222661b..c21496f0a141a0 100644 --- a/arch/s390/kernel/hiperdispatch.c +++ b/arch/s390/kernel/hiperdispatch.c @@ -207,16 +207,12 @@ static unsigned long hd_calculate_steal_percentage(void) { unsigned long time_delta, steal_delta, steal, percentage; static ktime_t prev; - int cpus, cpu; + int cpus; ktime_t now; - cpus = 0; - steal = 0; percentage = 0; - for_each_cpu(cpu, &hd_vmvl_cpumask) { - steal += kcpustat_cpu(cpu).cpustat[CPUTIME_STEAL]; - cpus++; - } + steal = kcpustat_field_total(CPUTIME_STEAL, &hd_vmvl_cpumask); + cpus = cpumask_weight(&hd_vmvl_cpumask); /* * If there is no vertical medium and low CPUs steal time * is 0 as vertical high CPUs shouldn't experience steal time. diff --git a/fs/proc/uptime.c b/fs/proc/uptime.c index 433aa947cd574d..53143c66cbe1b1 100644 --- a/fs/proc/uptime.c +++ b/fs/proc/uptime.c @@ -15,12 +15,8 @@ static int uptime_proc_show(struct seq_file *m, void *v) struct timespec64 idle; u64 idle_nsec; u32 rem; - int i; - - idle_nsec = 0; - for_each_possible_cpu(i) - idle_nsec += kcpustat_field(CPUTIME_IDLE, i); + idle_nsec = kcpustat_field_total(CPUTIME_IDLE, cpu_possible_mask); ktime_get_boottime_ts64(&uptime); timens_add_boottime(&uptime); diff --git a/include/linux/kernel_stat.h b/include/linux/kernel_stat.h index 9ca6c2259dfea6..c1e85550bf1256 100644 --- a/include/linux/kernel_stat.h +++ b/include/linux/kernel_stat.h @@ -196,6 +196,17 @@ static inline void kcpustat_cpu_fetch(struct kernel_cpustat *dst, int cpu) } #endif /* !CONFIG_VIRT_CPU_ACCOUNTING_GEN */ +static inline u64 kcpustat_field_total(enum cpu_usage_stat usage, const struct cpumask *cpus) +{ + u64 total = 0; + int cpu; + + for_each_cpu(cpu, cpus) + total += kcpustat_field(usage, cpu); + + return total; +} + extern void account_user_time(struct task_struct *, u64); extern void account_guest_time(struct task_struct *, u64); extern void account_system_time(struct task_struct *, int, u64); From cfb463b7172dca2f29b2221b71b54890f268e3fb Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:17 +0530 Subject: [PATCH 1006/2286] cpumask: Introduce cpumask_intersects_and Introduce bitmap_intersects_and() to determine whether the intersection of three bitmaps is non-empty. Unlike cpumask_first_and_and(), this returns immediately when an intersecting word is found and does not calculate the first matching bit. Add cpumask_intersects_and() as the corresponding cpumask wrapper. A subsequent patch uses the helper to determine whether a task has a CPU that is present in its affinity mask, the preferred CPU mask, and task possible CPU mask. Suggested-by: Yury Norov Signed-off-by: Shrikanth Hegde Reviwed-by: Yury Norov Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Yury Norov Link: https://patch.msgid.link/20260928053728.797539-3-sshegde@linux.ibm.com --- include/linux/bitmap.h | 14 ++++++++++++++ include/linux/cpumask.h | 18 ++++++++++++++++++ lib/bitmap.c | 17 +++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 7df1573a409c3c..adafbcf2016bf7 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -52,6 +52,7 @@ struct device; * bitmap_complement(dst, src, nbits) *dst = ~(*src) * bitmap_equal(src1, src2, nbits) Are *src1 and *src2 equal? * bitmap_intersects(src1, src2, nbits) Do *src1 and *src2 overlap? + * bitmap_intersects_and(src1, src2, src3, nbits) Do *src1, *src2 and *src3 overlap? * bitmap_subset(src1, src2, nbits) Is *src1 a subset of *src2? * bitmap_empty(src, nbits) Are all bits zero in *src? * bitmap_full(src, nbits) Are all bits set in *src? @@ -181,6 +182,9 @@ void __bitmap_replace(unsigned long *dst, const unsigned long *mask, unsigned int nbits); bool __bitmap_intersects(const unsigned long *bitmap1, const unsigned long *bitmap2, unsigned int nbits); +bool __bitmap_intersects_and(const unsigned long *bitmap1, + const unsigned long *bitmap2, + const unsigned long *bitmap3, unsigned int nbits); bool __bitmap_subset(const unsigned long *bitmap1, const unsigned long *bitmap2, unsigned int nbits); unsigned int __bitmap_weight(const unsigned long *bitmap, unsigned int nbits); @@ -445,6 +449,16 @@ bool bitmap_intersects(const unsigned long *src1, const unsigned long *src2, uns return __bitmap_intersects(src1, src2, nbits); } +static __always_inline +bool bitmap_intersects_and(const unsigned long *src1, const unsigned long *src2, + const unsigned long *src3, unsigned int nbits) +{ + if (small_const_nbits(nbits)) + return ((*src1 & *src2 & *src3) & BITMAP_LAST_WORD_MASK(nbits)) != 0; + else + return __bitmap_intersects_and(src1, src2, src3, nbits); +} + static __always_inline bool bitmap_subset(const unsigned long *src1, const unsigned long *src2, unsigned int nbits) { diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h index 4c8bb6953107d1..7c8f16797f94fe 100644 --- a/include/linux/cpumask.h +++ b/include/linux/cpumask.h @@ -824,6 +824,24 @@ bool cpumask_intersects(const struct cpumask *src1p, const struct cpumask *src2p small_cpumask_bits); } +/** + * cpumask_intersects_and - (*src1p & *src2p & *src3p) != 0 + * @src1p: the first input + * @src2p: the second input + * @src3p: the third input + * + * Return: true if AND of the three cpumasks is non-empty, + * otherwise false + */ +static __always_inline +bool cpumask_intersects_and(const struct cpumask *src1p, + const struct cpumask *src2p, + const struct cpumask *src3p) +{ + return bitmap_intersects_and(cpumask_bits(src1p), cpumask_bits(src2p), + cpumask_bits(src3p), small_cpumask_bits); +} + /** * cpumask_subset - (*src1p & ~*src2p) == 0 * @src1p: the first input diff --git a/lib/bitmap.c b/lib/bitmap.c index ed685127a1073b..d1cb8a507c6017 100644 --- a/lib/bitmap.c +++ b/lib/bitmap.c @@ -308,6 +308,23 @@ bool __bitmap_intersects(const unsigned long *bitmap1, } EXPORT_SYMBOL(__bitmap_intersects); +bool __bitmap_intersects_and(const unsigned long *bitmap1, + const unsigned long *bitmap2, + const unsigned long *bitmap3, unsigned int bits) +{ + unsigned int k, lim = bits / BITS_PER_LONG; + + for (k = 0; k < lim; ++k) + if (bitmap1[k] & bitmap2[k] & bitmap3[k]) + return true; + + if (bits % BITS_PER_LONG) + if ((bitmap1[k] & bitmap2[k] & bitmap3[k]) & BITMAP_LAST_WORD_MASK(bits)) + return true; + return false; +} +EXPORT_SYMBOL(__bitmap_intersects_and); + bool __bitmap_subset(const unsigned long *bitmap1, const unsigned long *bitmap2, unsigned int bits) { From 06a49ef784acf91cee78d50c5307d400f86ccafb Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:18 +0530 Subject: [PATCH 1007/2286] sched/docs: Document cpu_preferred_mask and Preferred CPU concept Add documentation for new CPU state called preferred CPU state and corresponding cpumask called cpu_preferred_mask. This could help users in understanding what it is and how to use it. Document the role of scheduler and driver for this feature to work. Details regarding the driver documentation will be added in later patches under Documentation/driver-api/steal-governor.rst. Newly added file could be used for other paravirt usecase documentation. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-4-sshegde@linux.ibm.com --- Documentation/scheduler/index.rst | 1 + Documentation/scheduler/sched-paravirt.rst | 67 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 Documentation/scheduler/sched-paravirt.rst diff --git a/Documentation/scheduler/index.rst b/Documentation/scheduler/index.rst index 17ce8d76befc1b..a43647b9706d97 100644 --- a/Documentation/scheduler/index.rst +++ b/Documentation/scheduler/index.rst @@ -23,5 +23,6 @@ Scheduler sched-stats sched-ext sched-debug + sched-paravirt text_files diff --git a/Documentation/scheduler/sched-paravirt.rst b/Documentation/scheduler/sched-paravirt.rst new file mode 100644 index 00000000000000..311cdbf2722b56 --- /dev/null +++ b/Documentation/scheduler/sched-paravirt.rst @@ -0,0 +1,67 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. _sched-paravirt: + +Preferred CPUs +============== + +In paravirtualized environments CPU overcommit is a common scenario. +i.e. the sum of virtual CPUs (vCPUs) of all VMs is greater than number of +physical CPUs (pCPUs). Under such conditions when all or many VMs have +high utilization, hypervisor won't be able to satisfy the CPU requirement +and has to context switch within or across VMs. The hypervisor needs to +preempt one vCPU to run another. This is called vCPU preemption. +This is more expensive compared to task context switch within a vCPU, since +hypervisor lacks vCPU context and could preempt a critical section which +slows forward progress. + +In such cases it is better that combined vCPU demand from all VMs is reduced +by not using some of the vCPUs in each VM. vCPUs where workload can be safely +scheduled which won't increase any contention for pCPU are called +"Preferred CPUs". + +One of the main design constructs is that preferred CPUs are always +a subset of active CPUs. In most cases preferred CPUs will be same as +active CPUs. When there is pCPU contention, Preferred CPUs will reduce +based on the steal time. When the pCPU contention goes away as indicated +by steal time, Preferred CPUs could become same as active CPUs again. +The policy decisions are to be taken by driver. +For example, steal_governor. Look at its documentation for more +details. (``drivers/virt/steal_governor.c``) + +Scheduling decisions such as wakeup, pushing the task etc, need this +CPU state info. This is maintained in ``cpu_preferred_mask``. +vCPUs which are not in ``cpu_preferred_mask`` should be treated as vCPUs which +should not be used at this moment provided it doesn't break user affinity. + +This is achieved by: + +1. Selecting a preferred CPU at wakeup using fallback mechanism. +2. Pushing the task away from non-preferred CPU at tick. +3. Selecting only preferred CPUs for load balance. + +``/sys/devices/system/cpu/preferred`` prints the current ``cpu_preferred_mask`` +in cpulist format. + +Notes: + +1. This feature is available under ``CONFIG_PREFERRED_CPU``. Driver which + makes decisions should enable it. For example, steal_governor driver + (``CONFIG_STEAL_GOVERNOR``). On enabling the driver, CPU preferred state + can change based on steal time. Without the driver, preferred CPUs is + same as active CPUs. + +2. This feature works for the FAIR class only. + +3. A pinned task, which can't be moved to preferred CPUs will continue + to run based on its affinity. But no load balancing happens if it is affined + only on non-preferred CPUs. + +4. Decision to change the preferred CPU state is driven by the kernel. + Hence it shouldn't break user affinities. One of the main reasons why + CPU hotplug or Isolated cpuset partitions was not a solution. + +5. This feature works best only when all the Guest VMs enable the feature as + it is a co-operative scheme. If a specific VM doesn't enable this feature + it may end up with more CPUs than others, still should lead to better + performance when seen from system view. + Users who enable this driver must ensure it is enabled in all Guest VMs. From 518b32bd5bb373aa334e4b73d359689071d50b5c Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:19 +0530 Subject: [PATCH 1008/2286] cpumask: Introduce cpu_preferred_mask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provide the preferred CPU infrastructure. Define get/set macros which could be used to get/set CPU state as preferred. CONFIG_PREFERRED_CPU will be selected by the driver which handles steal time values. It is going to set/clear preferred CPU state. This driver will be called steal_governor and it is introduced in subsequent patches. It periodically computes the steal ratio and decides on preferred CPU state. A CPU is set to preferred when it becomes active. Later it may be marked as non-preferred depending on steal ratio by the steal_governor. Always maintain design construct of preferred is subset of active. i.e. preferred ⊆ active ⊆ online ⊆ present ⊆ possible With CONFIG_PREFERRED_CPU=n, ensure set_cpu_preferred is a nop and get method returns the active state in that case. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-5-sshegde@linux.ibm.com --- include/linux/cpumask.h | 24 ++++++++++++++++++++++++ kernel/Kconfig.preempt | 4 ++++ kernel/cpu.c | 6 ++++++ kernel/sched/core.c | 5 +++++ 4 files changed, 39 insertions(+) diff --git a/include/linux/cpumask.h b/include/linux/cpumask.h index 7c8f16797f94fe..bf89bb3f30f664 100644 --- a/include/linux/cpumask.h +++ b/include/linux/cpumask.h @@ -121,12 +121,20 @@ extern struct cpumask __cpu_enabled_mask; extern struct cpumask __cpu_present_mask; extern struct cpumask __cpu_active_mask; extern struct cpumask __cpu_dying_mask; + +#ifdef CONFIG_PREFERRED_CPU +extern struct cpumask __cpu_preferred_mask; +#else +#define __cpu_preferred_mask __cpu_active_mask +#endif + #define cpu_possible_mask ((const struct cpumask *)&__cpu_possible_mask) #define cpu_online_mask ((const struct cpumask *)&__cpu_online_mask) #define cpu_enabled_mask ((const struct cpumask *)&__cpu_enabled_mask) #define cpu_present_mask ((const struct cpumask *)&__cpu_present_mask) #define cpu_active_mask ((const struct cpumask *)&__cpu_active_mask) #define cpu_dying_mask ((const struct cpumask *)&__cpu_dying_mask) +#define cpu_preferred_mask ((const struct cpumask *)&__cpu_preferred_mask) extern atomic_t __num_online_cpus; extern unsigned int __num_possible_cpus; @@ -1181,6 +1189,12 @@ void init_cpu_possible(const struct cpumask *src); #define set_cpu_active(cpu, active) assign_cpu((cpu), &__cpu_active_mask, (active)) #define set_cpu_dying(cpu, dying) assign_cpu((cpu), &__cpu_dying_mask, (dying)) +#ifdef CONFIG_PREFERRED_CPU +#define set_cpu_preferred(cpu, preferred) assign_cpu((cpu), &__cpu_preferred_mask, (preferred)) +#else +#define set_cpu_preferred(cpu, preferred) do { } while (0) +#endif + void set_cpu_online(unsigned int cpu, bool online); void set_cpu_possible(unsigned int cpu, bool possible); @@ -1275,6 +1289,11 @@ static __always_inline bool cpu_dying(unsigned int cpu) return cpumask_test_cpu(cpu, cpu_dying_mask); } +static __always_inline bool cpu_preferred(unsigned int cpu) +{ + return cpumask_test_cpu(cpu, cpu_preferred_mask); +} + #else #define num_online_cpus() 1U @@ -1313,6 +1332,11 @@ static __always_inline bool cpu_dying(unsigned int cpu) return false; } +static __always_inline bool cpu_preferred(unsigned int cpu) +{ + return cpu == 0; +} + #endif /* NR_CPUS > 1 */ #define cpu_is_offline(cpu) unlikely(!cpu_online(cpu)) diff --git a/kernel/Kconfig.preempt b/kernel/Kconfig.preempt index 985aea617cfe72..edc067a0c422ae 100644 --- a/kernel/Kconfig.preempt +++ b/kernel/Kconfig.preempt @@ -194,3 +194,7 @@ config SCHED_CLASS_EXT For more information: Documentation/scheduler/sched-ext.rst https://github.com/sched-ext/scx + +config PREFERRED_CPU + bool + depends on SMP && PARAVIRT diff --git a/kernel/cpu.c b/kernel/cpu.c index b3c8553d7bd6ca..376d297a6292b6 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c @@ -3103,6 +3103,11 @@ EXPORT_SYMBOL(__cpu_dying_mask); atomic_t __num_online_cpus __read_mostly; EXPORT_SYMBOL(__num_online_cpus); +#ifdef CONFIG_PREFERRED_CPU +struct cpumask __cpu_preferred_mask __read_mostly; +EXPORT_SYMBOL_GPL(__cpu_preferred_mask); +#endif + void init_cpu_present(const struct cpumask *src) { cpumask_copy(&__cpu_present_mask, src); @@ -3160,6 +3165,7 @@ void __init boot_cpu_init(void) /* Mark the boot cpu "present", "online" etc for SMP and UP case */ set_cpu_online(cpu, true); set_cpu_active(cpu, true); + set_cpu_preferred(cpu, true); set_cpu_present(cpu, true); set_cpu_possible(cpu, true); diff --git a/kernel/sched/core.c b/kernel/sched/core.c index acc89310d48837..d4edee72d793b2 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -8632,6 +8632,9 @@ int sched_cpu_activate(unsigned int cpu) */ sched_set_rq_online(rq, cpu); + /* preferred is subset of active and follows its state */ + set_cpu_preferred(cpu, true); + return 0; } @@ -8645,6 +8648,8 @@ int sched_cpu_deactivate(unsigned int cpu) if (ret) return ret; + set_cpu_preferred(cpu, false); + /* * Remove CPU from nohz.idle_cpus_mask to prevent participating in * load balancing when not active From 62082451655747dae25496d126e58f985df29f50 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:20 +0530 Subject: [PATCH 1009/2286] sysfs: Add preferred CPU file Add a "preferred" file in /sys/devices/system/cpu/ when kernel is built with CONFIG_PREFERRED_CPU=y. This would help - Users to quickly check which CPUs are marked as preferred. - Userspace daemons such as irqbalance to use this mask to route irqs into preferred CPUs. For example: cat /sys/devices/system/cpu/online 0-719 cat /sys/devices/system/cpu/preferred 0-599 <<< Implies 0-599 are preferred for workloads and 600-719 should be avoided at this moment. cat /sys/devices/system/cpu/preferred 0-719 <<< All CPUs are usable. There is no preference. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-6-sshegde@linux.ibm.com --- Documentation/ABI/testing/sysfs-devices-system-cpu | 14 ++++++++++++++ drivers/base/cpu.c | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 82d10d556cc89f..8f250b8693b844 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -806,3 +806,17 @@ Date: Nov 2022 Contact: Linux kernel mailing list Description: (RO) the list of CPUs that can be brought online. + +What: /sys/devices/system/cpu/preferred +Date: Sep 2026 +Contact: Linux kernel mailing list +Description: + (RO) the list of preferred CPUs applicable in + paravirtualized environments. + + The steal governor driver dynamically adjusts this mask + based on observed steal time. Scheduling tasks on + CPUs outside of this list may lead to performance + degradations due to underlying physical CPU contention. + + See Documentation/scheduler/sched-paravirt.rst for more details. diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index 69e52fed424159..747915ff974fb1 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -391,6 +391,15 @@ static int cpu_uevent(const struct device *dev, struct kobj_uevent_env *env) } #endif +#ifdef CONFIG_PREFERRED_CPU +static ssize_t preferred_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%*pbl\n", cpumask_pr_args(cpu_preferred_mask)); +} +static DEVICE_ATTR_RO(preferred); +#endif + const struct bus_type cpu_subsys = { .name = "cpu", .dev_name = "cpu", @@ -531,6 +540,9 @@ static struct attribute *cpu_root_attrs[] = { #endif #ifdef CONFIG_GENERIC_CPU_AUTOPROBE &dev_attr_modalias.attr, +#endif +#ifdef CONFIG_PREFERRED_CPU + &dev_attr_preferred.attr, #endif NULL }; From d8a3da0de8433d494db1ff5ec541559c764da4ab Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:21 +0530 Subject: [PATCH 1010/2286] sched/core: Try to use a preferred CPU in is_cpu_allowed When possible, try to choose a preferred CPU. This is essential to maintain user affinities when preferred CPUs change. A task pinned on a non-preferred CPU should continue to run there, since this is a non-user triggered event. If a CPU is non-preferred and the task can run on other CPUs which are currently preferred, then choose a preferred CPU instead. This is decided by checking if cpus_ptr, cpu_preferred_mask and task possible CPUs intersect or not. If yes, then the task has other preferred CPUs. This takes care of tasks with architecture-specific CPU masks (e.g., 32-bit tasks on arm64). The push task mechanism uses a stopper thread which calls select_fallback_rq() and uses this mechanism to pick a preferred CPU. This takes care of the wakeup path for FAIR tasks too. is_cpu_allowed() is called to ensure wakeups happen on preferred CPUs. With that, additional checks in available_idle_cpu() are not necessary. Ignore the preferred CPU state if a task's affinity is changing and its new mask no longer includes the CPU it is currently running on. This ensures migration_cpu_stop() does not abort, preventing the task from being stranded outside its allowed affinity. For the majority of cases, this would still keep select_fallback_rq() as O(N). cpumask_intersects_and(), which is O(N), is called only if !cpu_preferred. The task running there is expected to move out. Subsequently, it should run on a preferred CPU. This becomes O(N**2) only for tasks pinned solely to non-preferred CPUs. That is a rare case. Overhead is minimal when the CPU is preferred. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-7-sshegde@linux.ibm.com --- kernel/sched/core.c | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index d4edee72d793b2..d095fb1be6e9e0 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -2511,6 +2511,24 @@ static inline bool rq_has_pinned_tasks(struct rq *rq) return rq->nr_pinned; } +static inline bool task_can_migrate_to_preferred(struct task_struct *p, int cpu) +{ + /* No need to migrate from a preferred CPU */ + if (cpu_preferred(cpu)) + return false; + + /* Only FAIR tasks honor preferred CPU state */ + if (unlikely(p->sched_class != &fair_sched_class)) + return false; + + /* Ignore preferred state if task affinity is changing */ + if (unlikely(!cpumask_test_cpu(task_cpu(p), p->cpus_ptr))) + return false; + + return cpumask_intersects_and(p->cpus_ptr, cpu_preferred_mask, + task_cpu_possible_mask(p)); +} + /* * Per-CPU kthreads are allowed to run on !active && online CPUs, see * __set_cpus_allowed_ptr() and select_fallback_rq(). @@ -2526,8 +2544,12 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu) return cpu_online(cpu); /* Non kernel threads are not allowed during either online or offline. */ - if (!(p->flags & PF_KTHREAD)) + if (!(p->flags & PF_KTHREAD)) { + /* Try to use preferred CPU if task's affinity allows */ + if (task_can_migrate_to_preferred(p, cpu)) + return false; return cpu_active(cpu); + } /* KTHREAD_IS_PER_CPU is always allowed. */ if (kthread_is_per_cpu(p)) @@ -2537,7 +2559,11 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu) if (cpu_dying(cpu)) return false; - /* But are allowed during online. */ + /* Try to keep unbound kthreads on a preferred CPU if possible. */ + if (task_can_migrate_to_preferred(p, cpu)) + return false; + + /* Otherwise, they are allowed to run on online CPU. */ return cpu_online(cpu); } From 4ee29b029058a8f06fbb91425e1d9351e8015b53 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:22 +0530 Subject: [PATCH 1011/2286] sched/fair: Load balance only among preferred CPUs When a CPU is marked as non-preferred, any load pulled towards it is pointless since the task will be pushed out again in the next tick. So, consider only preferred CPUs for load balancing. This ensures load balancing does not fight against the push task mechanism which happens at the tick. Also, this stops active balancing from happening on a non-preferred CPU pulling the load. This also means there is no load balancing if a task is pinned only to non-preferred CPUs. They will continue to run where they were previously running before the CPUs were marked as non-preferred. Bail out early for NEWIDLE balancing, as load balancing is done only on preferred CPUs. Note that idle balancing is allowed to go through, since that naturally updates nohz.next_balance when all the idle CPUs are non-preferred. Also, optimization in find_new_ilb() is skipped. The steal governor driver, which is introduced in later patches, updates the preferred CPUs state in descending order. find_new_ilb() checks for idle CPUs in ascending order. Hence, in most common scenarios, the idle CPU found by find_new_ilb() will already be a preferred CPU. When all idle CPUs are non-preferred, the first idle CPU has to be chosen anyway. All of this is naturally handled in find_new_ilb() currently. Adding additional complexity to it for rare edge cases is not necessary. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Yury Norov Link: https://patch.msgid.link/20260928053728.797539-8-sshegde@linux.ibm.com --- kernel/sched/fair.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 03206e15e6fe4e..1c687c3c70f116 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -13467,7 +13467,7 @@ static int sched_balance_rq(int this_cpu, struct rq *this_rq, }; bool need_unlock = false; - cpumask_and(cpus, sched_domain_span(sd), cpu_active_mask); + cpumask_and(cpus, sched_domain_span(sd), cpu_preferred_mask); schedstat_inc(sd->lb_count[idle]); @@ -14582,10 +14582,8 @@ static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf) */ this_rq->idle_stamp = rq_clock(this_rq); - /* - * Do not pull tasks towards !active CPUs... - */ - if (!cpu_active(this_cpu)) + /* Do not pull tasks towards !preferred CPUs */ + if (!cpu_preferred(this_cpu)) return 0; /* From 74699f56ebcfe0f401ee6ce4c5a72822101bb222 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:23 +0530 Subject: [PATCH 1012/2286] sched/core: Push current task from non preferred CPU Actively push out the current running task on a non-preferred CPU (NPC). Since the task is currently running, a stopper thread must be queued to push the task out. However, if the task is pinned only to non-preferred CPUs, it will continue running there. This helps to maintain userspace affinities, unlike CPU hotplug or isolated cpusets. The implementation follows the structure of __balance_push_cpu_stop(), but is kept separate to handle the preferred-CPU specific conditions and pending-work state under CONFIG_PREFERRED_CPU. Add the npc_push_work_pending flag to protect the work buffer. For now, only the currently running task is pushed out. This keeps the code simpler. In the future, an optimization may be added to move all queued tasks on the runqueue. This works only for the FAIR scheduling class. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-9-sshegde@linux.ibm.com --- kernel/sched/core.c | 83 ++++++++++++++++++++++++++++++++++++++++++++ kernel/sched/sched.h | 9 +++++ 2 files changed, 92 insertions(+) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index d095fb1be6e9e0..0e40055c236771 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -5829,6 +5829,9 @@ void sched_tick(void) unsigned long hw_pressure; u64 resched_latency; + if (!cpu_preferred(cpu)) + sched_push_current_non_preferred_cpu(rq); + if (housekeeping_cpu(cpu, HK_TYPE_KERNEL_NOISE)) arch_scale_freq_tick(); @@ -11253,3 +11256,83 @@ void sched_change_end(struct sched_change_ctx *ctx) p->sched_class->prio_changed(rq, p, ctx->prio); } } + +#ifdef CONFIG_PREFERRED_CPU +static DEFINE_PER_CPU(struct cpu_stop_work, npc_push_task_work); + +static int sched_non_preferred_cpu_push_stop(void *arg) +{ + struct task_struct *p = arg; + struct rq *rq = this_rq(); + struct rq_flags rf; + int cpu; + + if (cpu_preferred(rq->cpu)) { + scoped_guard(rq_lock_irqsave, rq) + rq->npc_push_work_pending = false; + put_task_struct(p); + return 0; + } + + scoped_guard (raw_spinlock_irq, &p->pi_lock) { + /* + * select_fallback_rq() may acquire the rq lock in case of + * fallback. So call it before grabbing rq lock. If the task + * migrates to another CPU before the rq lock is acquired, + * subsequent validation of task's current rq will help to + * safely bail out. + */ + cpu = select_fallback_rq(rq->cpu, p); + rq_lock(rq, &rf); + rq->npc_push_work_pending = false; + update_rq_clock(rq); + context_unsafe_alias(rq); + + if (task_rq(p) == rq && task_on_rq_queued(p)) + rq = __migrate_task(rq, &rf, p, cpu); + rq_unlock(rq, &rf); + } + + put_task_struct(p); + return 0; +} + +/* + * Push the current task running on non-preferred CPU(npc). + * Using this non preferred CPU will lead to more contention + * in the host. So it is better not to use this CPU. + * + * Since task is running, call a stopper to push the task out. This is + * similar to how task moves during hotplug. In select_fallback_rq() a + * preferred CPU will be chosen and henceforth task shouldn't come back to + * this CPU again. + * + * Works for FAIR class only. + * + * If task is affined only on non-preferred CPUs, no point in moving it out. + */ +void sched_push_current_non_preferred_cpu(struct rq *rq) +{ + struct task_struct *push_task = rq->curr; + + scoped_guard(rq_lock, rq) { + /* Push the task if its explicit affinity allows */ + if (!task_can_migrate_to_preferred(push_task, rq->cpu)) + return; + + /* There is already a stopper thread. Don't race with it. */ + if (rq->npc_push_work_pending) + return; + + if (is_migration_disabled(push_task)) + return; + + rq->npc_push_work_pending = true; + } + + /* sched_tick runs with interrupts disabled. */ + get_task_struct(push_task); + stop_one_cpu_nowait(rq->cpu, sched_non_preferred_cpu_push_stop, + push_task, this_cpu_ptr(&npc_push_task_work)); +} +#endif diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h index d41d23a5632b33..7d2ec527b8a255 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -1326,6 +1326,9 @@ struct rq { #ifdef CONFIG_PARAVIRT_TIME_ACCOUNTING u64 prev_steal_time_rq; #endif +#ifdef CONFIG_PREFERRED_CPU + bool npc_push_work_pending; +#endif /* calc_load related fields */ unsigned long calc_load_update; @@ -4299,4 +4302,10 @@ DEFINE_CLASS_IS_UNCONDITIONAL(sched_change) #include "ext/ext.h" +#ifdef CONFIG_PREFERRED_CPU +void sched_push_current_non_preferred_cpu(struct rq *rq); +#else /* !CONFIG_PREFERRED_CPU */ +static inline void sched_push_current_non_preferred_cpu(struct rq *rq) { } +#endif + #endif /* _KERNEL_SCHED_SCHED_H */ From 68957caaa9c0e127bc87fee64e45be843f6c5c91 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:24 +0530 Subject: [PATCH 1013/2286] sched/debug: Add migration stats due to non preferred CPUs Add a new per-task stat, - nr_migrations_cpu_non_preferred: number of push migrations while the CPU is non-preferred. Since this new stat is per-task, it changes only /proc//sched. It doesn't update /proc/schedstat. Hence increasing the schedstat version is not necessary. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-10-sshegde@linux.ibm.com --- include/linux/sched.h | 1 + kernel/sched/core.c | 9 +++++++-- kernel/sched/debug.c | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index d7cc77181ef98e..48639b0c75d08f 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -554,6 +554,7 @@ struct sched_statistics { u64 nr_failed_migrations_running; u64 nr_failed_migrations_hot; u64 nr_forced_migrations; + u64 nr_migrations_cpu_non_preferred; u64 nr_wakeups; u64 nr_wakeups_sync; diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0e40055c236771..0bb86a43a5920c 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -11288,8 +11288,13 @@ static int sched_non_preferred_cpu_push_stop(void *arg) update_rq_clock(rq); context_unsafe_alias(rq); - if (task_rq(p) == rq && task_on_rq_queued(p)) - rq = __migrate_task(rq, &rf, p, cpu); + if (task_rq(p) == rq && task_on_rq_queued(p)) { + struct rq *dest_rq = __migrate_task(rq, &rf, p, cpu); + + if (rq != dest_rq) + schedstat_inc(p->stats.nr_migrations_cpu_non_preferred); + rq = dest_rq; + } rq_unlock(rq, &rf); } diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c index fda10b3fa249a9..e6a3b516c703ba 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -1440,6 +1440,7 @@ void proc_sched_show_task(struct task_struct *p, struct pid_namespace *ns, P_SCHEDSTAT(nr_failed_migrations_running); P_SCHEDSTAT(nr_failed_migrations_hot); P_SCHEDSTAT(nr_forced_migrations); + P_SCHEDSTAT(nr_migrations_cpu_non_preferred); P_SCHEDSTAT(nr_wakeups); P_SCHEDSTAT(nr_wakeups_sync); P_SCHEDSTAT(nr_wakeups_migrate); From 9a8e740ee9f69ec857ffa0c76cf1a01d1cb7360f Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:25 +0530 Subject: [PATCH 1014/2286] virt: Introduce steal governor driver Introduce a new driver in virt named steal_governor. This driver will compute the steal time and drive the policy decisions regarding the preferred CPU state. Note that this driver is strictly intended for actual guests. Hence block it on Xen dom0. More details can be found in Documentation/driver-api/steal-governor.rst. A new kconfig called STEAL_GOVERNOR is introduced in subsequent patches, which enables this driver. This driver will select CONFIG_PREFERRED_CPU. This makes configs driven by user preference/configuration. When the driver is disabled, preferred CPUs remain the same as active CPUs. The file layout of the driver is kept simple for now. The code is in drivers/virt/steal_governor.c, and the configs are part of drivers/virt/Kconfig. The main structure of the steal governor contains: - work, delay: Deferred periodic work function variables. - steal, time: Used to calculate deltas during periodic work. - interval_ms, high_threshold, low_threshold: Tuning knobs for the steal governor. While there, add MAINTAINERS entry for this new driver. Suggested-by: Yury Norov Suggested-by: K Prateek Nayak Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-11-sshegde@linux.ibm.com --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/steal-governor.rst | 151 ++++++++++++++++++++ MAINTAINERS | 9 ++ drivers/virt/steal_governor.c | 78 ++++++++++ 4 files changed, 239 insertions(+) create mode 100644 Documentation/driver-api/steal-governor.rst create mode 100644 drivers/virt/steal_governor.c diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index 6601a258690f83..26b7638a327d81 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -139,6 +139,7 @@ Subsystem-specific APIs sm501 soundwire/index spi + steal-governor surface_aggregator/index switchtec sync_file diff --git a/Documentation/driver-api/steal-governor.rst b/Documentation/driver-api/steal-governor.rst new file mode 100644 index 00000000000000..3817eedb38d787 --- /dev/null +++ b/Documentation/driver-api/steal-governor.rst @@ -0,0 +1,151 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Steal Governor +============== + +:Author: Shrikanth Hegde + +Introduction +============ + +The steal governor is aimed at mitigating the Noisy Neighbour problem +which occurs in paravirtualized environments with CPU overcommit. +The performance of a workload running in one VM gets degraded by +the activity of other VMs on the same host. As a result, all VMs +collectively make slower forward progress. + +In such systems, high utilization in all VMs causes the hypervisor to +frequently preempt vCPUs. This vCPU preemption is expensive. +To mitigate this, the kernel aims to restrict workloads to a subset of +Preferred CPUs to reduce physical CPU contention. +A detailed explanation of Preferred CPUs is available in +``Documentation/scheduler/sched-paravirt.rst``. + +The steal governor selects ``CONFIG_PREFERRED_CPU=y`` which enables the +scheduler core infrastructure to move the tasks to Preferred CPUs where +possible. The driver controls the policy decisions regarding the state of +preferred CPUs. That is, this driver decides which CPUs are preferred +and which CPUs are non-preferred. + +The driver code is available at ``drivers/virt/steal_governor.c``. + +Core idea +========= + +steal time is an indication available today in Guest which shows contention +for underlying physical CPU. Use it as a hint in the guest to fold the +workload to a reduced set of vCPUs. When there is contention, steal time +will show up in all the guests. When each guest honors the hint and folds +the workload to a smaller set of vCPUs (Preferred CPUs), it reduces the +contention and thereby reduces vCPU preemption. +This is achieved without any cross-guest communication. + +Steal governor driver effectively does: + +1. Periodically computes the steal ratio using accumulated steal time + across possible CPUs, normalized by the number of active CPUs. + +2. If steal ratio is greater than high threshold, reduce the number of + preferred CPUs by 1 core. Ensure at least one core is left always. + Skip changing the state of offline CPUs in that core. + +3. If steal ratio is less than or equal to low threshold, increase the + number of preferred CPUs by 1 core. If preferred is same as active, + nothing to be done. Skip changing the state of offline CPUs. + This helps to handle cases where few CPUs are offline in a core and + those offline CPUs will not be marked as preferred. + +4. Ensure preferred CPUs is always subset of active CPUs. + On feature disable it is same as active CPUs. + +This feature works best only when all the VMs enable the feature as +it is a co-operative scheme. If a specific VM doesn't enable this feature +it may end up with more CPUs than others, still should lead to better +performance when seen from system view. Those who enable this driver must +ensure it is enabled in all VMs. + +Note that this driver is strictly intended for actual guests; for example, +loading this module in a privileged VM like Xen Dom0 is blocked. + +Workload considerations +======================= + +The steal governor is useful for workloads where vCPU preemption has +costs beyond the lost CPU time, such as lock-holder preemption, critical +sections, communicating threads, and cache or TLB disruption. + +Pure CPU-time workloads with independent workers may not benefit and +could see a small regression due to additional guest scheduling overhead. + +Module Parameters +================= + +interval_ms +----------- + +How often steal governor checks for steal time. +Default: 1000 i.e. 1 second. Value should be in between 100ms to 100sec. + +This controls how fast steal governor driver reacts to changes to the +contention of physical CPUs. Since it does a fair amount of work, setting +too low may have overhead. Setting it too high might render it ineffective. + +low_threshold +------------- + +lower threshold value in percentage * 100. +Default: 200, i.e. 2% steal is considered as low threshold. +Can't be higher than high_threshold. + +This determines what values should be considered as nil/no steal values. +When steal governor sees steal ratio is less than or equal to this value, +it will increase the preferred CPUs by 1 core. +Using zero might cause oscillations. + +high_threshold +-------------- + +higher threshold value in percentage * 100 +Default: 500, i.e. 5% steal is considered as high threshold. +Can't be lower than low_threshold. Must be less than 10000. + +This determines what values should be considered as high steal values. +When steal governor sees steal ratio is higher than this value, it will +reduce the preferred CPUs by 1 core. + +Limitations of default values +----------------------------- + +Because of the vast diversity in VM configurations and different +architectures, the default thresholds may not be optimal for all systems. +Users may need to tune these parameters based on the system under +test to achieve the best results. + +The governor sums the steal time across all possible CPUs, which ensures +the accumulated steal time remains a monotonically increasing value. +However, to calculate the effective steal ratio, it divides this sum +by the number of active CPUs. Because only active CPUs contribute to +the steal time delta, this prevents threshold dilution on sparsely +populated systems. + +The driver reduces/increases preferred CPUs by core-level. This could provide +faster convergence for hypervisors such as powerVM. But on KVM and Xen +convergence could be slower depending on the configuration. +Using a smaller interval_ms could help one to expedite it. + +Reasons for CONFIG_STEAL_GOVERNOR=m +=================================== + +Selecting this driver makes CONFIG_PREFERRED_CPU=y. That makes configs +driven by user preference. Though one can have CONFIG_STEAL_GOVERNOR=y, +It is recommended to build CONFIG_STEAL_GOVERNOR=m due to below reasons: + +1. Doing periodic work has additional overheads. Enabling this driver + in systems where steal time cannot happen is of no use. There is no + benefit with additional overheads in such systems. + +2. This works well when all VMs work in a co-operative manner. When an + administrative user enables it in one VM, he/she will likely enable + it in all VMs. + +3. User can tweak the module parameters by reloading the module. diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..28003d36b3f7ae 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26223,6 +26223,15 @@ F: rust/helpers/jump_label.c F: rust/kernel/generated_arch_static_branch_asm.rs.S F: rust/kernel/jump_label.rs +STEAL GOVERNOR DRIVER +M: Shrikanth Hegde +R: Yury Norov +L: linux-kernel@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git sched/core +F: Documentation/driver-api/steal-governor.rst +F: drivers/virt/steal_governor.c + STI AUDIO (ASoC) DRIVERS M: Arnaud Pouliquen L: linux-sound@vger.kernel.org diff --git a/drivers/virt/steal_governor.c b/drivers/virt/steal_governor.c new file mode 100644 index 00000000000000..2320cbfa4b47b4 --- /dev/null +++ b/drivers/virt/steal_governor.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Steal time governor driver periodically computes steal time. + * Based on the thresholds it either reduce/increase the preferred + * CPUs which can be used by the workload to avoid vCPU preemption + * to an extent possible in paravirtualized environment. + * + * Available with CONFIG_STEAL_GOVERNOR + * + * Copyright (C) 2026 IBM + * Author: Shrikanth Hegde + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_XEN +#include +#endif + +#if !IS_ENABLED(CONFIG_PREFERRED_CPU) +#error "Steal Governor requires CONFIG_PREFERRED_CPU" +#endif + +struct steal_governor { + ktime_t time; + u64 steal; + unsigned long delay; + unsigned int interval_ms; + unsigned int high_threshold; + unsigned int low_threshold; + struct delayed_work work; +}; + +static struct steal_governor sg_ctx; + +static void restore_preferred_to_active(void) +{ + int cpu; + + guard(cpus_read_lock)(); + for_each_cpu(cpu, cpu_active_mask) + set_cpu_preferred(cpu, true); +} + +static int __init steal_governor_init(void) +{ +#ifdef CONFIG_XEN + if (xen_initial_domain()) { + pr_err("Cannot load in Xen Dom0 (Host OS). Driver is for guests only.\n"); + return -ENODEV; + } +#endif + + pr_info("enabled\n"); + return 0; +} + +static void __exit steal_governor_exit(void) +{ + restore_preferred_to_active(); + pr_info("disabled\n"); +} + +module_init(steal_governor_init); +module_exit(steal_governor_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("IBM Corporation"); +MODULE_DESCRIPTION("Virtualization Steal Time Governor"); From 4b9302d494fffee7318bd9cdd2021198b6a7badd Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:26 +0530 Subject: [PATCH 1015/2286] virt/steal_governor: Add control knobs for handling steal values These are the knobs to control the steal_governor. interval_ms: How often steal governor checks for steal time. (Default: 1000 i.e 1 second) This controls how fast steal governor driver reacts to changes to the contention of physical CPUs. Can be set between 100 to 100000. i.e. 100ms to 100seconds. 100ms is kept as minimum to ensure few meaningful steal values accumulate even with HZ=100. low_threshold: lower threshold value in percentage * 100. (Default: 200, i.e 2% steal is considered as low threshold) This determines what values should be considered as nil/no steal values. When steal governor see steal ratio is below or equal to this value, it will increase the preferred CPUs by 1 core. Having value as zero might cause oscillations high_threshold: higher threshold value in percentage * 100 (Default: 500, i.e 5% steal is considered as high threshold) This determines what values should be considered as high steal values. When steal governor sees steal ratio is higher than this value, it will reduce the preferred CPUs by 1 core. module_param_cb methods are used to do the validation checks. This helps to ensure one configures sane values. Since low and high are dependent, that check is done at module init. Notes: - Parameters values can't be changed at runtime. One has to unload the module and change it. Hence recommended to build it as module. - Default values may not work well for all configurations. Tune it according to the system under test. Documentation is available at: Documentation/driver-api/steal-governor.rst Suggested-by: Yury Norov Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-12-sshegde@linux.ibm.com --- drivers/virt/steal_governor.c | 73 ++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/drivers/virt/steal_governor.c b/drivers/virt/steal_governor.c index 2320cbfa4b47b4..27f53ea16498b4 100644 --- a/drivers/virt/steal_governor.c +++ b/drivers/virt/steal_governor.c @@ -40,7 +40,11 @@ struct steal_governor { struct delayed_work work; }; -static struct steal_governor sg_ctx; +static struct steal_governor sg_ctx = { + .interval_ms = 1000, /* 1 second */ + .high_threshold = 500, /* 5% */ + .low_threshold = 200, /* 2% */ +}; static void restore_preferred_to_active(void) { @@ -51,6 +55,62 @@ static void restore_preferred_to_active(void) set_cpu_preferred(cpu, true); } +static int param_set_interval_ms(const char *val, const struct kernel_param *kp) +{ + unsigned int interval; + int ret; + + ret = kstrtouint(val, 0, &interval); + if (ret) + return ret; + + if (interval < 100 || interval > 100000) { + pr_err("interval_ms must be between 100 and 100000\n"); + return -EINVAL; + } + + return param_set_uint(val, kp); +} + +static const struct kernel_param_ops interval_ms_ops = { + .set = param_set_interval_ms, + .get = param_get_uint, +}; + +module_param_cb(interval_ms, &interval_ms_ops, &sg_ctx.interval_ms, 0444); +MODULE_PARM_DESC(interval_ms, + "Sampling frequency in milliseconds. default: 1000"); + +static int param_set_high_threshold(const char *val, const struct kernel_param *kp) +{ + unsigned int threshold; + int ret; + + ret = kstrtouint(val, 0, &threshold); + if (ret) + return ret; + + if (threshold >= 100 * 100) { + pr_err("high_threshold (%u) can't be more than 99.99%%\n", threshold); + return -EINVAL; + } + + return param_set_uint(val, kp); +} + +static const struct kernel_param_ops high_threshold_ops = { + .set = param_set_high_threshold, + .get = param_get_uint, +}; + +module_param_cb(high_threshold, &high_threshold_ops, &sg_ctx.high_threshold, 0444); +MODULE_PARM_DESC(high_threshold, + "High steal threshold. default: 500 i.e 5%. Must be > low_threshold"); + +module_param_named(low_threshold, sg_ctx.low_threshold, uint, 0444); +MODULE_PARM_DESC(low_threshold, + "Low steal threshold. default: 200 i.e 2%. Must be < high_threshold"); + static int __init steal_governor_init(void) { #ifdef CONFIG_XEN @@ -60,7 +120,16 @@ static int __init steal_governor_init(void) } #endif - pr_info("enabled\n"); + if (sg_ctx.low_threshold >= sg_ctx.high_threshold) { + pr_err("low_threshold (%u) must be less than high_threshold (%u)\n", + sg_ctx.low_threshold, sg_ctx.high_threshold); + return -EINVAL; + } + + sg_ctx.delay = msecs_to_jiffies(sg_ctx.interval_ms); + pr_info("enabled. interval: %ums, high_threshold: %u, low_threshold: %u\n", + sg_ctx.interval_ms, sg_ctx.high_threshold, sg_ctx.low_threshold); + return 0; } From 27d47ebce4d6490ce093031a48cc2d81bf9997a3 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:27 +0530 Subject: [PATCH 1016/2286] virt/steal_governor: Implement steal_governor policy loop Schedule work at regular intervals to implement the steal_governor policy loop, which monitors steal time and takes action on the state of preferred CPUs. The interval is determined by the interval_ms parameter. schedule_delayed_work() is used since interval_ms is on the order of milliseconds and the work does not need to happen instantly. Periodic policy loop essentially does: - Gets the total/delta steal values and cpus to use steal_ratio. - Calculate the steal_ratio as below. steal_ratio = (delta_steal * 100*100)/(delta_ns * num_cpus()) It is calculated this way to consider the fractional values of steal time. I.e 10 means 0.1% steal time. A few tricks such as divide by 10,000 are used to avoid possible overflow. - If steal ratio is higher than high threshold, call the method to reduce the preferred CPUs. - If steal ratio is lower or equal to low threshold, call the method to increase the preferred CPUs. - If the steal ratio falls in between, no action is taken. - Ensures design constraints always met. 1. At least one core/CPU must be there in preferred mask. 2. preferred CPUs is subset of active CPUs. If not met, then restore preferred CPUs to active and stop requeue of the work. Driver is effectively non-functional after that. Note that design checks are always performed. This helps avoid placing driver-specific design constraints inside the core CPU hotplug mechanism. User may offline specific set of CPUs that could leave the preferred mask as empty. With the design check performed always, driver gracefully shuts down upon detecting that edge case. In order to help the above loop, a few helper functions have been added. 1. get_system_steal_time() - steal governor takes global view of steal time instead of individual vCPU. Collect the steal values across the vCPUs of interest. - Sum up steal time values across possible CPUs. This helps to keep it a monotonically increasing number and avoids spikes due to CPU hotplug. 2. decrease_preferred_cpus() - Called when there is high steal time. It needs to decide which CPUs to mark as non-preferred. - Get first housekeeping CPU and its core mask. Mark it as protected core. This helps to keep at least one core as preferred. (kernel ensures at least one housekeeping CPU stays active.) - Find the last CPU outside of this protected core mask. i.e target CPU - Based on that target CPU, get its sibling and mark them as non-preferred. 3. increase_preferred_cpus() - Called when there is low steal time. It needs to decide which CPUs to mark as preferred and set that state. - Get the first active non-preferred CPUs. This likely is the last set of CPUs being marked as non-preferred. - get the siblings of that CPU and mark them as preferred. 4. get_system_cpus() - informs how many CPUs needs to be considered for steal_ratio calculations. - Since only active CPUs effectively contribute to steal time delta, returns number of active CPUs. This also helps to avoid dilution of thresholds in sparsely populated systems. Notes: 1. Using core instead of individual CPUs performs better as SMT is quite common and some hypervisor such as powerVM does core scheduling. 2. This doesn't do any NUMA splicing to keep the code simpler and minimal overhead. Current code expects CPUs spread uniformly across NUMA nodes. Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-13-sshegde@linux.ibm.com --- drivers/virt/steal_governor.c | 149 ++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/drivers/virt/steal_governor.c b/drivers/virt/steal_governor.c index 27f53ea16498b4..6e31f9923dea4d 100644 --- a/drivers/virt/steal_governor.c +++ b/drivers/virt/steal_governor.c @@ -13,13 +13,18 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include #include +#include #include #include +#include #include +#include +#include #include #include #ifdef CONFIG_XEN @@ -111,6 +116,145 @@ module_param_named(low_threshold, sg_ctx.low_threshold, uint, 0444); MODULE_PARM_DESC(low_threshold, "Low steal threshold. default: 200 i.e 2%. Must be < high_threshold"); +/* Return collective steal time across system. */ +static u64 get_system_steal_time(void) +{ + return kcpustat_field_total(CPUTIME_STEAL, cpu_possible_mask); +} + +/* Return number of CPUs to consider for steal ratio. */ +static unsigned int get_system_cpus(void) +{ + return num_active_cpus(); +} + +/* + * Called when the steal governor detects high physical CPU contention. + * It finds the last active core in the preferred mask and mark those + * CPUs as non-preferred. + * + * Must ensure: + * - at least one core is always kept as preferred + * - preferred is always subset of active. + */ +static void decrease_preferred_cpus(void) +{ + const struct cpumask *first_hk_core; + int target_cpu = nr_cpu_ids; + int cpu; + + guard(cpus_read_lock)(); + cpu = cpumask_first_and(housekeeping_cpumask(HK_TYPE_KERNEL_NOISE), + cpu_preferred_mask); + if (cpu >= nr_cpu_ids) + return; + + /* Always leave first housekeeping core as preferred. */ + first_hk_core = topology_sibling_cpumask(cpu); + cpu = cpumask_last(cpu_preferred_mask); + if (cpu >= nr_cpu_ids) + return; + + /* Find the last CPU which doesn't belong to that first hk_core. */ + if (!cpumask_test_cpu(cpu, first_hk_core)) { + target_cpu = cpu; + } else { + for_each_cpu_andnot(cpu, cpu_preferred_mask, first_hk_core) + target_cpu = cpu; + } + + /* Only the first housekeeping core remains */ + if (target_cpu >= nr_cpu_ids) + return; + + for_each_cpu_and(cpu, topology_sibling_cpumask(target_cpu), + cpu_preferred_mask) + set_cpu_preferred(cpu, false); +} + +/* + * Called when the steal governor detects no/low physical CPU contention. + * It finds the first active core outside of preferred mask and mark + * those CPUs as preferred. + * + * Must ensure preferred is subset of active. + */ +static void increase_preferred_cpus(void) +{ + int first_cpu, cpu; + + guard(cpus_read_lock)(); + first_cpu = cpumask_first_andnot(cpu_active_mask, cpu_preferred_mask); + + /* All CPUs are preferred. Nothing to increase further */ + if (first_cpu >= nr_cpu_ids) + return; + + for_each_cpu_and(cpu, topology_sibling_cpumask(first_cpu), + cpu_active_mask) + set_cpu_preferred(cpu, true); +} + +static bool preferred_cpus_valid(void) +{ + if (cpumask_empty(cpu_preferred_mask)) { + pr_err("empty preferred mask. stopping\n"); + return false; + } + + if (!cpumask_subset(cpu_preferred_mask, cpu_active_mask)) { + pr_err("preferred: %*pbl is not subset of active: %*pbl, stopping\n", + cpumask_pr_args(cpu_preferred_mask), + cpumask_pr_args(cpu_active_mask)); + return false; + } + + return true; +} + +static void steal_governor_loop(struct work_struct *work) +{ + u64 curr_steal, delta_steal, delta_ns, steal_ratio; + ktime_t now; + + now = ktime_get(); + delta_ns = ktime_to_ns(ktime_sub(now, sg_ctx.time)); + + if (unlikely(delta_ns < NSEC_PER_MSEC)) { + pr_err_ratelimited("work scheduled too soon delta_ns: %llu\n", delta_ns); + goto requeue_work; + } + + curr_steal = get_system_steal_time(); + delta_steal = curr_steal > sg_ctx.steal ? curr_steal - sg_ctx.steal : 0; + sg_ctx.steal = curr_steal; + sg_ctx.time = now; + + /* + * steal_ratio = (delta_steal * 100*100)/(delta_ns * num_cpus()) + * To avoid possible overflow, divide the denominator early. + * Note minimum interval is 100ms. + */ + delta_ns = max_t(u64, div_u64(delta_ns * get_system_cpus(), 10000), 1); + steal_ratio = div64_u64(delta_steal, delta_ns); + + if (steal_ratio > sg_ctx.high_threshold) + decrease_preferred_cpus(); + else if (steal_ratio <= sg_ctx.low_threshold) + increase_preferred_cpus(); + /* + * else: steal ratio is within bounds. Still do design checks so that + * module restores to active if CPU hotplug breaks those assumptions. + */ + if (!preferred_cpus_valid()) { + restore_preferred_to_active(); + return; + } + +requeue_work: + schedule_delayed_work(&sg_ctx.work, sg_ctx.delay); +} + static int __init steal_governor_init(void) { #ifdef CONFIG_XEN @@ -127,6 +271,10 @@ static int __init steal_governor_init(void) } sg_ctx.delay = msecs_to_jiffies(sg_ctx.interval_ms); + INIT_DELAYED_WORK(&sg_ctx.work, steal_governor_loop); + sg_ctx.steal = get_system_steal_time(); + sg_ctx.time = ktime_get(); + schedule_delayed_work(&sg_ctx.work, sg_ctx.delay); pr_info("enabled. interval: %ums, high_threshold: %u, low_threshold: %u\n", sg_ctx.interval_ms, sg_ctx.high_threshold, sg_ctx.low_threshold); @@ -135,6 +283,7 @@ static int __init steal_governor_init(void) static void __exit steal_governor_exit(void) { + disable_delayed_work_sync(&sg_ctx.work); restore_preferred_to_active(); pr_info("disabled\n"); } From 1fb28c664a19df8d45a6afa04d28d102b04ea680 Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Mon, 28 Sep 2026 11:07:28 +0530 Subject: [PATCH 1017/2286] virt/steal_governor: Enable the driver Provide a config option to enable the steal_governor driver. Since the feature targets paravirtualized environments and requires SMP, enforce those dependencies. The driver selects CONFIG_PREFERRED_CPU for the core scheduler mechanisms to work. It is recommended to build the driver as a module (m) instead of built-in (y) due to the following reasons: - Module parameters are read-only after initialization. Building as a module allows updating these parameters by simply reloading the module. Default module parameters cannot work in all configurations. - The driver can be completely disabled by unloading the module. - This feature works best when all VMs operate in a cooperative manner. Requiring an explicit module load ensures intentional deployment across all VMs by the system administrator. Suggested-by: Yury Norov Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260928053728.797539-14-sshegde@linux.ibm.com --- drivers/virt/Kconfig | 17 +++++++++++++++++ drivers/virt/Makefile | 1 + 2 files changed, 18 insertions(+) diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig index 52eb7e4ba71f65..eeb84e578ddf96 100644 --- a/drivers/virt/Kconfig +++ b/drivers/virt/Kconfig @@ -41,6 +41,23 @@ config FSL_HV_MANAGER 4) A kernel interface for receiving callbacks when a managed partition shuts down. +config STEAL_GOVERNOR + tristate "Dynamic vCPU management based on steal time" + depends on PARAVIRT && SMP + select PREFERRED_CPU + default m + help + This driver helps to reduce the steal time in paravirtualized + environments, thereby reducing vCPU preemption costs. + + By default preferred CPUs will be same as active CPUs. Depending + on the steal time when steal_governor driver is enabled, + preferred CPUs could become subset of active CPUs. + More details are at: Documentation/driver-api/steal-governor.rst + + It is recommended to build it as module and load the module + to enable it. + source "drivers/virt/vboxguest/Kconfig" source "drivers/virt/nitro_enclaves/Kconfig" diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile index f29901bd782058..05fb075ef5b856 100644 --- a/drivers/virt/Makefile +++ b/drivers/virt/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o obj-$(CONFIG_VMGENID) += vmgenid.o +obj-$(CONFIG_STEAL_GOVERNOR) += steal_governor.o obj-y += vboxguest/ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/ From 01c3d218e8fa5ac4393e7959701cd6c2e90c4a81 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 15:06:13 +0100 Subject: [PATCH 1018/2286] rust: pin-init: internal: handle item and expr diagnostics differently Currently `DiagCtxt` is used to allow error recovery in the pin-init macros, by generating both a `compile_error!()` macro item and continue expansion, and then merge token streams together. This works very well for items, however for expressions, this will produce invalid expression and result in a confusing error message. error: macro expansion ignores `::` and any tokens following --> tests/ui/compile-fail/zeroable/invalid_spread.rs:15:9 | 13 | let _ = init!(Foo { | _____________- 14 | | a: 0, 15 | | ..MyZeroable::init_zeroed() | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 16 | | }); | |______- caused by the macro expansion here | = note: the usage of `init!` is likely invalid in expression context Fix this by wrap the concatenated diagnostics in a block, with the generated expression in its tail position. This results in the desired error message. error: expected nothing or `..Zeroable::init_zeroed()`. --> tests/ui/compile-fail/zeroable/invalid_spread.rs:15:9 | 15 | ..MyZeroable::init_zeroed() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Reviewed-by: Benno Lossin Link: https://patch.msgid.link/20260923140618.1978181-1-gary@kernel.org Signed-off-by: Gary Guo --- rust/pin-init/internal/src/diagnostics.rs | 55 +++++++++++++++++++---- rust/pin-init/internal/src/lib.rs | 12 ++--- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/rust/pin-init/internal/src/diagnostics.rs b/rust/pin-init/internal/src/diagnostics.rs index c7d9b3e624fc8a..c42f1095ab1092 100644 --- a/rust/pin-init/internal/src/diagnostics.rs +++ b/rust/pin-init/internal/src/diagnostics.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use proc_macro2::TokenStream; -use quote::quote_spanned; +use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, Error}; pub(crate) struct DiagCtxt(TokenStream); @@ -29,16 +29,55 @@ impl DiagCtxt { )); } - pub(crate) fn with( - fun: impl FnOnce(&mut DiagCtxt) -> Result, + fn with( + f: impl FnOnce(&mut DiagCtxt) -> Result, + merge_diag: impl FnOnce(TokenStream, TokenStream) -> TokenStream, + convert_diag: impl FnOnce(TokenStream) -> TokenStream, ) -> TokenStream { let mut dcx = Self(TokenStream::new()); - match fun(&mut dcx) { - Ok(mut stream) => { - stream.extend(dcx.0); - stream + match f(&mut dcx) { + Ok(stream) => { + if dcx.0.is_empty() { + stream + } else { + merge_diag(stream, dcx.0) + } } - Err(ErrorGuaranteed(())) => dcx.0, + Err(ErrorGuaranteed(())) => convert_diag(dcx.0), } } + + pub(crate) fn for_item( + f: impl FnOnce(&mut DiagCtxt) -> Result, + ) -> TokenStream { + Self::with( + f, + |mut out, diag| { + out.extend(diag); + out + }, + std::convert::identity, + ) + } + + pub(crate) fn for_expr( + f: impl FnOnce(&mut DiagCtxt) -> Result, + ) -> TokenStream { + Self::with( + f, + |out, diag| { + // Diagnostics that we generate are always items. + // So for expressions create a block to place diagnostics in item position. + quote!({ + #diag + #out + }) + }, + |diag| { + quote!({ + #diag + }) + }, + ) + } } diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs index c488019d6250ef..0410024ba1e6c9 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -25,31 +25,31 @@ mod zeroable; pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args); let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| pin_data::pin_data(args, input, dcx)).into() + DiagCtxt::for_item(|dcx| pin_data::pin_data(args, input, dcx)).into() } #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { let args = parse_macro_input!(args); let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| pinned_drop::pinned_drop(args, input, dcx)).into() + DiagCtxt::for_item(|dcx| pinned_drop::pinned_drop(args, input, dcx)).into() } #[proc_macro_derive(Zeroable)] pub fn derive_zeroable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| zeroable::derive(input, dcx)).into() + DiagCtxt::for_item(|dcx| zeroable::derive(input, dcx)).into() } #[proc_macro_derive(MaybeZeroable)] pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| zeroable::maybe_derive(input, dcx)).into() + DiagCtxt::for_item(|dcx| zeroable::maybe_derive(input, dcx)).into() } #[proc_macro] pub fn init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| { + DiagCtxt::for_expr(|dcx| { init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx) }) .into() @@ -58,7 +58,7 @@ pub fn init(input: TokenStream) -> TokenStream { #[proc_macro] pub fn pin_init(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input); - DiagCtxt::with(|dcx| { + DiagCtxt::for_expr(|dcx| { init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx) }) .into() From 73c1f157907cf23ef90e105f48c40fae75ee1008 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 15:06:14 +0100 Subject: [PATCH 1019/2286] rust: pin-init: internal: make `DiagCtxt` available inside parser Currently, there is a split between `syn::Error` and pin-init's custom `DiagCtxt`. Parsing has no access to `DiagCtxt` and therefore cannot report diagnostics without failing the parse. Bridge the gap by making the `DiagCtxt` available inside parsers using `thread_local!`. `parse_macro_input!` returns error as token stream directly, so it is no longer suitable when the parsing is moved inside `DiagCtxt` closure. Add a `From for ErrorGuaranteed` implementation so `?` can be used to add a `syn::Error` into the current diagnostic context and obtain a `ErrorGuaranteed`. Clean up the handler of using `<-` inside tuple expression as a demonstration of the usefulness of having `DiagCtxt` access inside `Parse`. Reviewed-by: Benno Lossin Link: https://patch.msgid.link/20260923140618.1978181-2-gary@kernel.org Signed-off-by: Gary Guo --- rust/pin-init/internal/src/diagnostics.rs | 84 ++++++++++++++++++----- rust/pin-init/internal/src/init.rs | 41 ++++------- rust/pin-init/internal/src/lib.rs | 32 +++++---- 3 files changed, 96 insertions(+), 61 deletions(-) diff --git a/rust/pin-init/internal/src/diagnostics.rs b/rust/pin-init/internal/src/diagnostics.rs index c42f1095ab1092..efdcf45f40cf78 100644 --- a/rust/pin-init/internal/src/diagnostics.rs +++ b/rust/pin-init/internal/src/diagnostics.rs @@ -1,32 +1,68 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT +use std::cell::RefCell; use std::fmt::Display; +use std::marker::PhantomData; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, Error}; -pub(crate) struct DiagCtxt(TokenStream); +pub(crate) struct DiagCtxt(PhantomData<*mut ()>); pub(crate) struct ErrorGuaranteed(()); +struct DiagCtxtData { + diag: TokenStream, +} + +thread_local! { + static DIAGNOSTICS: RefCell> = const { RefCell::new(None) }; +} + +// Allows `syn::Error` to be emitted into the current diagnostic context with just `?`. +impl From for ErrorGuaranteed { + fn from(error: syn::Error) -> Self { + DIAGNOSTICS.with_borrow_mut(|data| { + data.as_mut() + .unwrap() + .diag + .extend(error.into_compile_error()); + }); + Self(()) + } +} + impl DiagCtxt { - pub(crate) fn error(&mut self, span: impl Spanned, msg: impl Display) -> ErrorGuaranteed { - let error = Error::new(span.span(), msg); - self.0.extend(error.into_compile_error()); - ErrorGuaranteed(()) + pub(crate) fn error(&self, span: impl Spanned, msg: impl Display) -> ErrorGuaranteed { + Error::new(span.span(), msg).into() } - pub(crate) fn warn(&mut self, span: impl Spanned, msg: impl Display) { + pub(crate) fn warn(&self, span: impl Spanned, msg: impl Display) { // Have the message start on a new line for visual clarity. let msg = format!("\n{}", msg); - self.0.extend(quote_spanned!(span.span() => - // Approximate using deprecated warning while `proc_macro_diagnostic` is unstable. - const _: () = { - #[deprecated = #msg] - const fn warn() {} - warn(); - }; - )); + DIAGNOSTICS.with_borrow_mut(|data| { + data.as_mut() + .unwrap() + .diag + .extend(quote_spanned!(span.span() => + // Approximate using deprecated warning while `proc_macro_diagnostic` is + // unstable. + const _: () = { + #[deprecated = #msg] + const fn warn() {} + warn(); + }; + )) + }); + } + + /// Execute the provided function with the current diagnostic context. + pub(crate) fn current(f: impl FnOnce(&DiagCtxt) -> R) -> R { + DIAGNOSTICS.with_borrow(|data| { + assert!(data.is_some(), "No active `DiagCtxt`"); + }); + + f(&DiagCtxt(PhantomData)) } fn with( @@ -34,16 +70,26 @@ impl DiagCtxt { merge_diag: impl FnOnce(TokenStream, TokenStream) -> TokenStream, convert_diag: impl FnOnce(TokenStream) -> TokenStream, ) -> TokenStream { - let mut dcx = Self(TokenStream::new()); - match f(&mut dcx) { + DIAGNOSTICS.with_borrow_mut(|data| { + assert!(data.is_none(), "`DiagCtxt` cannot be nested"); + *data = Some(DiagCtxtData { + diag: TokenStream::new(), + }); + }); + + let result = f(&mut DiagCtxt(PhantomData)); + + let data = DIAGNOSTICS.with_borrow_mut(|data| data.take().unwrap()); + + match result { Ok(stream) => { - if dcx.0.is_empty() { + if data.diag.is_empty() { stream } else { - merge_diag(stream, dcx.0) + merge_diag(stream, data.diag) } } - Err(ErrorGuaranteed(())) => convert_diag(dcx.0), + Err(ErrorGuaranteed(())) => convert_diag(data.diag), } } diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index e98182c6544779..c5b5a0ce5c4e41 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -44,9 +44,6 @@ pub(crate) enum InitExprKind { struct InitTupleField { attrs: Vec, - /// `<-` is not valid in constructor syntax; it is parsed anyway so that it can be rejected - /// with a proper diagnostic instead of a parse error. - left_arrow_token: Option, value: Expr, } @@ -84,20 +81,6 @@ impl InitExprTuple { rest: None, } } - - fn validate(&self, dcx: &mut DiagCtxt) -> Result<(), ErrorGuaranteed> { - let mut result = Ok(()); - for field in &self.fields { - if let Some(left_arrow_token) = &field.left_arrow_token { - result = Err(dcx.error( - left_arrow_token, - "`<-` is not supported in tuple constructor syntax; name the fields by index \ - instead, e.g. `Type { 0 <- initializer, 1: value }`", - )); - } - } - result - } } struct This { @@ -153,8 +136,6 @@ pub(crate) fn expand_with_cfg( ) -> Result { let initializer = match initializer.kind { InitExprKind::Tuple(expr) => { - expr.validate(dcx)?; - let mut initializer = Initializer { attrs: initializer.attrs, this: initializer.this, @@ -579,9 +560,20 @@ impl InitExprTuple { let paren_token = parenthesized!(content in input); let mut fields = Punctuated::new(); while !content.is_empty() { + let attrs = content.call(Attribute::parse_outer)?; + + if let Some(left_arrow_token) = content.parse::>()? { + DiagCtxt::current(|dcx| { + dcx.error( + left_arrow_token, + "`<-` is not supported in tuple constructor syntax; name the fields by \ + index instead, e.g. `Type { 0 <- initializer, 1: value }`", + ) + }); + } + fields.push_value(InitTupleField { - attrs: content.call(Attribute::parse_outer)?, - left_arrow_token: content.parse()?, + attrs, value: content.parse()?, }); if content.is_empty() { @@ -758,13 +750,8 @@ impl ToTokens for InitExprTuple { impl ToTokens for InitTupleField { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { - attrs, - left_arrow_token, - value, - } = self; + let Self { attrs, value } = self; tokens.append_all(attrs); - left_arrow_token.to_tokens(tokens); value.to_tokens(tokens); } } diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs index 0410024ba1e6c9..07b3d33d228248 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -10,7 +10,6 @@ #![allow(missing_docs)] use proc_macro::TokenStream; -use syn::parse_macro_input; use crate::diagnostics::DiagCtxt; @@ -23,43 +22,46 @@ mod zeroable; #[proc_macro_attribute] pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args); - let input = parse_macro_input!(input); - DiagCtxt::for_item(|dcx| pin_data::pin_data(args, input, dcx)).into() + DiagCtxt::for_item(|dcx| pin_data::pin_data(syn::parse(args)?, syn::parse(input)?, dcx)).into() } #[proc_macro_attribute] pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args); - let input = parse_macro_input!(input); - DiagCtxt::for_item(|dcx| pinned_drop::pinned_drop(args, input, dcx)).into() + DiagCtxt::for_item(|dcx| pinned_drop::pinned_drop(syn::parse(args)?, syn::parse(input)?, dcx)) + .into() } #[proc_macro_derive(Zeroable)] pub fn derive_zeroable(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); - DiagCtxt::for_item(|dcx| zeroable::derive(input, dcx)).into() + DiagCtxt::for_item(|dcx| zeroable::derive(syn::parse(input)?, dcx)).into() } #[proc_macro_derive(MaybeZeroable)] pub fn maybe_derive_zeroable(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); - DiagCtxt::for_item(|dcx| zeroable::maybe_derive(input, dcx)).into() + DiagCtxt::for_item(|dcx| zeroable::maybe_derive(syn::parse(input)?, dcx)).into() } #[proc_macro] pub fn init(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); DiagCtxt::for_expr(|dcx| { - init::expand_with_cfg(input, Some("::core::convert::Infallible"), false, dcx) + init::expand_with_cfg( + syn::parse(input)?, + Some("::core::convert::Infallible"), + false, + dcx, + ) }) .into() } #[proc_macro] pub fn pin_init(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input); DiagCtxt::for_expr(|dcx| { - init::expand_with_cfg(input, Some("::core::convert::Infallible"), true, dcx) + init::expand_with_cfg( + syn::parse(input)?, + Some("::core::convert::Infallible"), + true, + dcx, + ) }) .into() } From 95593ed3616194c9498cc190cd1a3693ff4bca37 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 23 Sep 2026 15:06:15 +0100 Subject: [PATCH 1020/2286] rust: pin-init: internal: improve diagnostics robustness against panicking Currently, if proc macro panicked, the diagnostics clean up is not executed, and further invocation will cause the "DiagCtxt cannot be nested" error. While we should aim to have no panics inside proc macros, producing a sensible diagnostics message even when macro panicked is very useful for developing. Thus, catch proc macro panics and convert them to errors, and emit them together with all diagnostics accumulated so far. Ideally we would like panic location w/ line numbers being available as well; however this is not currently implementable without overriding the global panic hook. Reviewed-by: Benno Lossin Link: https://patch.msgid.link/20260923140618.1978181-3-gary@kernel.org Signed-off-by: Gary Guo --- rust/pin-init/internal/src/diagnostics.rs | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/rust/pin-init/internal/src/diagnostics.rs b/rust/pin-init/internal/src/diagnostics.rs index efdcf45f40cf78..e88e520e326b66 100644 --- a/rust/pin-init/internal/src/diagnostics.rs +++ b/rust/pin-init/internal/src/diagnostics.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::fmt::Display; use std::marker::PhantomData; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, Error}; @@ -77,7 +77,27 @@ impl DiagCtxt { }); }); - let result = f(&mut DiagCtxt(PhantomData)); + let mut dcx = DiagCtxt(PhantomData); + let result = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut dcx))) { + Ok(result) => result, + Err(payload) => { + // Robustness against panicking in macros. + // + // Ensure that any error messages are still emitted when this happens. + let message = if let Some(&s) = payload.downcast_ref::<&'static str>() { + s + } else if let Some(s) = payload.downcast_ref::() { + s.as_str() + } else { + "Box" + }; + + Err(dcx.error( + Span::mixed_site(), + format!("proc macro panicked: {message}"), + )) + } + }; let data = DIAGNOSTICS.with_borrow_mut(|data| data.take().unwrap()); From ec27f3b700010d2ebae1cde2df0246197689d159 Mon Sep 17 00:00:00 2001 From: Yilin Chen <1479826151@qq.com> Date: Mon, 14 Sep 2026 14:09:00 +0000 Subject: [PATCH 1021/2286] rust: configfs: require thread-safe callback data The Rust configfs abstractions do not fully constrain callback data for cross-thread use. Add the missing `Send` and `Sync` requirements. Specifically, make the following changes: 1. Require `Data: Send` when implementing `Send` for `Subsystem`, since the subsystem stores its data by value. 2. Make `GroupOperations` a `Sync` supertrait because `make_group` and `drop_item` receive `&self` from foreign threads. Require `Child: Send` because configfs may release child groups on an arbitrary thread. 3. Require `AttributeOperations::Data: Sync` because its callbacks receive `&Data` from foreign threads. 4. Update the safety comments in FFI callbacks that call `get_group_data` to cite these bounds as justification for sharing the returned references with the callback thread. 5. Remove redundant `Child: 'static` bounds from `GroupOperationsVTable` and `new_with_child_ctor`. Fixes: 446cafc295bf ("rust: configfs: introduce rust support for configfs") Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Should.20add.20Data.3A.20Send.2FSync.20bounds.20in.20configfs.3A.3ASubsystem.3F/with/623719979 Assisted-by: Gpt-5.6 Sol Signed-off-by: Yilin Chen <1479826151@qq.com> Link: https://msgid.link/tencent_5449081195EDDF5726C709CF648DD4044A08@qq.com [ Andreas: Add Send/Sync bounds for `Group`, add `Send` bounds to `GroupOperationsVTable`, `ItemOperationsVTable` and `ItemType` macro implementation. ] Signed-off-by: Andreas Hindborg --- rust/kernel/configfs.rs | 63 ++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs index 7722035ebc020e..ec295ab965da17 100644 --- a/rust/kernel/configfs.rs +++ b/rust/kernel/configfs.rs @@ -135,8 +135,9 @@ pub struct Subsystem { // SAFETY: We do not provide any operations on `Subsystem`. unsafe impl Sync for Subsystem {} -// SAFETY: Ownership of `Subsystem` can safely be transferred to other threads. -unsafe impl Send for Subsystem {} +// SAFETY: Ownership of `Subsystem` can safely be transferred to other threads +// if its data can be transferred as well. +unsafe impl Send for Subsystem {} impl Subsystem { /// Create an initializer for a [`Subsystem`]. @@ -249,6 +250,13 @@ pub struct Group { data: Data, } +// SAFETY: We do not provide any operations on `Group`. +unsafe impl Sync for Group {} + +// SAFETY: Ownership of `Group` can safely be transferred to other threads if +// its data can be transferred as well. +unsafe impl Send for Group {} + impl Group { /// Create an initializer for a new group. /// @@ -325,7 +333,9 @@ struct GroupOperationsVTable(PhantomData<(Parent, Child)>); impl GroupOperationsVTable where Parent: GroupOperations, - Child: 'static, + // We transfer `Arc>` across a thread boundary in `make_group` + // and `drop_item`. + Arc>: Send, { /// # Safety /// @@ -344,8 +354,9 @@ where this: *mut bindings::config_group, name: *const kernel::ffi::c_char, ) -> *mut bindings::config_group { - // SAFETY: By function safety requirements of this function, this call - // is safe. + // SAFETY: By function safety requirements, `this` points to a configfs + // group containing `Parent`. The `GroupOperations` bound guarantees + // that `Parent: Sync`, so it is safe to share it with this thread. let parent_data = unsafe { get_group_data(this) }; let group_init = match Parent::make_group( @@ -390,8 +401,9 @@ where this: *mut bindings::config_group, item: *mut bindings::config_item, ) { - // SAFETY: By function safety requirements of this function, this call - // is safe. + // SAFETY: By function safety requirements, `this` points to a configfs + // group containing `Parent`. The `GroupOperations` bound guarantees + // that `Parent: Sync`, so it is safe to share it with this thread. let parent_data = unsafe { get_group_data(this) }; // SAFETY: By function safety requirements, `item` is embedded in a @@ -403,7 +415,9 @@ where if Parent::HAS_DROP_ITEM { // SAFETY: We called `into_raw` to produce `r_child_group_ptr` in - // `make_group`. + // `make_group`. This function may be executing on a different + // thread than `into_raw`. As `Arc>: Send` this + // ownership transfer is safe. let arc: Arc> = unsafe { Arc::from_raw(r_child_group_ptr.cast_mut()) }; Parent::drop_item(parent_data, arc.as_arc_borrow()); @@ -434,6 +448,8 @@ struct ItemOperationsVTable(PhantomData<(Container, Data)>); impl ItemOperationsVTable, Data> where Data: 'static, + // We transfer `Arc>` across a thread boundary in `release`. + Arc>: Send, { /// # Safety /// @@ -450,8 +466,9 @@ where // embedded within a `Group`. let r_group_ptr = unsafe { Group::::container_of(c_group_ptr) }; - // SAFETY: We called `into_raw` on `r_group_ptr` in - // `make_group`. + // SAFETY: We called `into_raw` on `r_group_ptr` in `make_group`. This + // function may be running on a different thread than the thread that + // called `into_raw`. As `Arc>: Send`, this is safe. let pin_self: Arc> = unsafe { Arc::from_raw(r_group_ptr.cast_mut()) }; drop(pin_self); } @@ -483,12 +500,12 @@ impl ItemOperationsVTable, Data> { /// /// Implement this trait on structs that embed a [`Subsystem`] or a [`Group`]. #[vtable] -pub trait GroupOperations { +pub trait GroupOperations: Sync { /// The child data object type. /// /// This group will create subgroups (subdirectories) backed by this kind of /// object. - type Child: 'static; + type Child: 'static + Send; /// Creates a new subgroup. /// @@ -555,8 +572,10 @@ where // `config_group`. unsafe { container_of!(item, bindings::config_group, cg_item) }; - // SAFETY: The function safety requirements for this function satisfy - // the conditions for this call. + // SAFETY: By function safety requirements, `c_group` points to a + // configfs group containing `Data`. The `AttributeOperations` bound + // guarantees that `Data: Sync`, so it is safe to share it with this + // thread. let data: &Data = unsafe { get_group_data(c_group) }; // SAFETY: By function safety requirements, `page` is writable for `PAGE_SIZE`. @@ -589,8 +608,10 @@ where // `config_group`. unsafe { container_of!(item, bindings::config_group, cg_item) }; - // SAFETY: The function safety requirements for this function satisfy - // the conditions for this call. + // SAFETY: By function safety requirements, `c_group` points to a + // configfs group containing `Data`. The `AttributeOperations` bound + // guarantees that `Data: Sync`, so it is safe to share it with this + // thread. let data: &Data = unsafe { get_group_data(c_group) }; let ret = O::store( @@ -643,7 +664,7 @@ where pub trait AttributeOperations { /// The type of the object that contains the field that is backing the /// attribute for this operation. - type Data; + type Data: Sync; /// Renders the value of an attribute. /// @@ -749,7 +770,8 @@ macro_rules! impl_item_type { ) -> Self where Data: GroupOperations, - Child: 'static, + Arc>: Send, + Arc>: Send, { Self { item_type: Opaque::new(bindings::config_item_type { @@ -767,7 +789,10 @@ macro_rules! impl_item_type { pub const fn new( owner: &'static ThisModule, attributes: &'static AttributeList, - ) -> Self { + ) -> Self + where + Arc>: Send, + { Self { item_type: Opaque::new(bindings::config_item_type { ct_owner: owner.as_ptr(), From 4ebfdb2c8b28eb8ef85b4e4ce65e55bd5a4df8d5 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 25 Sep 2026 08:25:29 +0900 Subject: [PATCH 1022/2286] rust: bindings: set HZ to CONFIG_HZ include/uapi/asm-generic/param.h defines HZ as __USER_HZ and include/asm-generic/param.h then sets it to CONFIG_HZ. bindgen keeps the first definition of a macro and ignores a later #undef, so the generated binding is __USER_HZ. Redefine HZ in rust/bindings/lib.rs, the same way compat_ptr_ioctl is redefined there. Nothing in the tree reads bindings::HZ today, so this prevents accidental misuse rather than fixing a bug. Suggested-by: Alice Ryhl Reviewed-by: Gary Guo Signed-off-by: FUJITA Tomonori Link: https://msgid.link/20260924232530.446588-2-tomo@flapping.org Signed-off-by: Andreas Hindborg --- rust/bindings/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 812f8e5a08d536..c17e6c2b775fd0 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -81,3 +81,8 @@ pub const compat_ptr_ioctl: Option< None } }; + +// `bindgen` keeps the first definition of a macro and ignores a later `#undef`. +// The generated `HZ` is therefore `__USER_HZ` from +// `include/uapi/asm-generic/param.h`, not `CONFIG_HZ`. +pub const HZ: u32 = CONFIG_HZ; From a9aba476d42df2537403f97f185a2b1979d0dd97 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 25 Sep 2026 08:25:30 +0900 Subject: [PATCH 1023/2286] rust: time: add Delta::to_jiffies_timeout() for timeout conversion The boundaries of __msecs_to_jiffies() and nsecs_to_jiffies64() depend on which HZ branch is compiled. With CONFIG_HZ_300 nsecs_to_jiffies64() overflows after 64.99 years, which is less than the 292 years a Delta can hold. With HZ=1000 a jiffy is a millisecond, so __msecs_to_jiffies() returns its argument unchanged and never caps it at MAX_JIFFY_OFFSET. Compute the conversion in Rust with mul_u64_add_u64_div_u64() instead. It returns (a * b + c) / d, computing a * b internally in 128 bits, so ceil(nanos * HZ / NSEC_PER_SEC) needs no input clamp and rounds once. The bound then follows from the arithmetic: with HZ <= NSEC_PER_SEC, which a static_assert() checks, the result is at most the nanosecond count. Unless the result saturates, the value is rounded up, so the timeout is never shorter than the requested span. It saturates at zero jiffies for a negative span, i.e. an immediate timeout, and at MAX_JIFFY_OFFSET, the upper bound the kernel uses for a jiffies span. Since MAX_JIFFY_OFFSET is derived from long, only 32 bit can reach it, and a saturated timeout there is finite, so it can be shorter than the requested span. Reviewed-by: Gary Guo Signed-off-by: FUJITA Tomonori Link: https://msgid.link/20260924232530.446588-3-tomo@flapping.org Signed-off-by: Andreas Hindborg --- rust/helpers/helpers.c | 1 + rust/helpers/math.c | 8 +++ rust/kernel/Kconfig.test | 10 ++++ rust/kernel/time.rs | 105 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 rust/helpers/math.c diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 440fb7638e3cb1..08374b163774b9 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -73,6 +73,7 @@ #include "kunit.c" #include "list.c" #include "maple_tree.c" +#include "math.c" #include "mm.c" #include "mutex.c" #include "net/genetlink.c" diff --git a/rust/helpers/math.c b/rust/helpers/math.c new file mode 100644 index 00000000000000..e2ee29bcce0fae --- /dev/null +++ b/rust/helpers/math.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +__rust_helper u64 rust_helper_mul_u64_add_u64_div_u64(u64 a, u64 b, u64 c, u64 d) +{ + return mul_u64_add_u64_div_u64(a, b, c, d); +} diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test index e6a5c7a795f0fc..0087749995d2b8 100644 --- a/rust/kernel/Kconfig.test +++ b/rust/kernel/Kconfig.test @@ -83,4 +83,14 @@ config RUST_BITFIELD_KUNIT_TEST If unsure, say N. +config RUST_TIME_KUNIT_TEST + bool "KUnit tests for the Rust time API" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + help + This option enables KUnit tests for the Rust time API. + These are only for development and testing, not for regular + kernel use cases. + + If unsure, say N. + endif diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs index 60717f17e9bab1..5ae377e70dd880 100644 --- a/rust/kernel/time.rs +++ b/rust/kernel/time.rs @@ -39,6 +39,10 @@ pub const NSEC_PER_MSEC: i64 = bindings::NSEC_PER_MSEC as i64; /// The number of nanoseconds per second. pub const NSEC_PER_SEC: i64 = bindings::NSEC_PER_SEC as i64; +/// The C side `MAX_JIFFY_OFFSET`, i.e. `((LONG_MAX >> 1) - 1)`. It is the upper +/// bound the kernel uses for a jiffies span, not a wait-forever value. +const MAX_JIFFY_OFFSET: isize = (isize::MAX >> 1) - 1; + /// The time unit of Linux kernel. One jiffy equals (1/HZ) second. pub type Jiffies = crate::ffi::c_ulong; @@ -554,6 +558,62 @@ impl Delta { } } + /// Convert this span to a [`Delta`] suitable for use as a timeout. + /// + /// Unless the result saturates, the value is rounded up to the next whole + /// jiffy, so the resulting timeout is never shorter than `self`. + /// + /// A negative span saturates at zero jiffies, i.e. an immediate timeout. + /// + /// A span that does not fit saturates at the kernel's [`MAX_JIFFY_OFFSET`], + /// the upper bound for a jiffies span. That is a finite timeout, so a + /// saturated result can be shorter than the requested span. It is derived + /// from `long`, so only 32 bit can reach it, at about 12 days with `HZ=1000`. + /// + /// # Examples + /// + /// ``` + /// use kernel::time::Delta; + /// + /// // A negative span is an immediate timeout. + /// assert_eq!(Delta::from_millis(-1).to_jiffies_timeout().as_jiffies(), 0); + /// + /// // A span shorter than a jiffy still waits, i.e. the timeout is never + /// // shorter than the span. + /// assert!(Delta::from_nanos(1).to_jiffies_timeout().as_jiffies() >= 1); + /// ``` + /// + /// [`MAX_JIFFY_OFFSET`]: srctree/include/linux/jiffies.h + #[inline] + pub fn to_jiffies_timeout(self) -> Delta { + const HZ: u64 = bindings::HZ as u64; + + // The quotient `(nsecs * HZ + NSEC_PER_SEC - 1) / NSEC_PER_SEC` has to fit in + // `u64`; `nsecs * HZ` does not. With `HZ <= NSEC_PER_SEC` the numerator is at + // most `(nsecs + 1) * NSEC_PER_SEC - 1`, so the quotient is at most `nsecs`. + crate::static_assert!(HZ <= NSEC_PER_SEC as u64); + + // CAST: `i64::max()` makes the value non-negative, so the cast keeps it. + let nsecs = i64::max(self.as_nanos(), 0) as u64; + + // SAFETY: `mul_u64_add_u64_div_u64()` must not be called with a zero divisor, + // and its result must fit in `u64`. `NSEC_PER_SEC` is a non-zero constant, and + // the assertion above bounds the quotient by `nsecs`. + let jiffies = unsafe { + bindings::mul_u64_add_u64_div_u64( + nsecs, + HZ, + (NSEC_PER_SEC - 1) as u64, + NSEC_PER_SEC as u64, + ) + }; + + // CAST: `jiffies` is clamped to `MAX_JIFFY_OFFSET`, which is `<= isize::MAX`. + let jiffies = u64::min(jiffies, MAX_JIFFY_OFFSET as u64) as isize; + + Delta::::from_jiffies(jiffies) + } + /// Return `self % dividend` where `dividend` is in nanoseconds. /// /// The kernel doesn't have any emulation for `s64 % s64` on 32 bit platforms, so this is @@ -580,3 +640,48 @@ impl Delta { } } } + +#[cfg(CONFIG_RUST_TIME_KUNIT_TEST)] +#[macros::kunit_tests(rust_kernel_time)] +mod tests { + use super::*; + + #[test] + fn to_jiffies_timeout_converts() { + const HZ: isize = bindings::HZ as isize; + + // One second is exactly `HZ` jiffies, and the round-up must not add one. + assert_eq!(Delta::from_secs(1).to_jiffies_timeout().as_jiffies(), HZ); + + // One nanosecond more has to round up to the next whole jiffy. + assert_eq!( + Delta::from_nanos(NSEC_PER_SEC + 1) + .to_jiffies_timeout() + .as_jiffies(), + HZ + 1 + ); + } + + #[test] + fn to_jiffies_timeout_saturates() { + // The result never exceeds `MAX_JIFFY_OFFSET`. On 32 bit with `HZ=1000` this + // span is 2147483647 jiffies, so the clamp is what keeps it in range; on 64 + // bit it fits and the check holds for every possible return value. + let clamped = Delta::from_millis(i64::from(i32::MAX)).to_jiffies_timeout(); + assert!(clamped.as_jiffies() <= MAX_JIFFY_OFFSET); + + // `MAX_JIFFY_OFFSET` is derived from `long`, so only 32 bit can reach it. On + // 64 bit `i64::MAX` nanoseconds is about 292 years, which is 9223372036855 + // jiffies with `HZ=1000`, far below the limit. + #[cfg(not(CONFIG_64BIT))] + { + // An overlong span is clamped to `MAX_JIFFY_OFFSET`. + let overlong = Delta::from_nanos(i64::MAX).to_jiffies_timeout(); + assert_eq!(overlong.as_jiffies(), MAX_JIFFY_OFFSET); + } + + // A negative span is an immediate timeout, however long it is. + let negative = Delta::from_nanos(i64::MIN).to_jiffies_timeout(); + assert_eq!(negative.as_jiffies(), 0); + } +} From d48b2d347105436365280a3697a265aa4d37e96c Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Mon, 28 Sep 2026 08:36:00 -0700 Subject: [PATCH 1024/2286] virt: tdx-guest: Remove unused and confusing function argument The tsm_report_ops->report_new() function takes a void* argument for implementations to use. But, TDX does not use the argument. It relies entirely on the 'struct tsm_report'. Despite that, the TDX code passes 'data' around needlessly from tdx_report_new()=>tdx_report_new_locked() where it is completely unused and ignored. This is not just a cleanup or bike-shedding rename. The variable is a real liability because there are 'quote_data' variables and even a tdx_quote_buf->data[] that can get literally referred to as "data". Remove the unused argument. Signed-off-by: Dave Hansen --- drivers/virt/coco/tdx-guest/tdx-guest.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index d0303e31e81610..a21bd0376b74ad 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -265,7 +265,7 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou return (i == timeout) ? -ETIMEDOUT : 0; } -static int tdx_report_new_locked(struct tsm_report *report, void *data) +static int tdx_report_new_locked(struct tsm_report *report) { u8 *buf; struct tdx_quote_buf *quote_buf = quote_data; @@ -333,10 +333,10 @@ static int tdx_report_new_locked(struct tsm_report *report, void *data) return ret; } -static int tdx_report_new(struct tsm_report *report, void *data) +static int tdx_report_new(struct tsm_report *report, void *unused) { scoped_cond_guard(mutex_intr, return -EINTR, "e_lock) - return tdx_report_new_locked(report, data); + return tdx_report_new_locked(report); } static bool tdx_report_attr_visible(int n) From a49e2d257594931772ab8f0024708c3076f3aa1d Mon Sep 17 00:00:00 2001 From: Nikolay Borisov Date: Fri, 25 Sep 2026 16:18:08 +0300 Subject: [PATCH 1025/2286] x86/tdx: Restrict attestation exports to the tdx-guest driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are few remaining attestation related functions which are exported via EXPORT_SYMBOL_GPL, yet they are solely used by the 'tdx-guest' driver. Let's just limit their visibility by using the namespaced EXPORT_SYMBOL_FOR_MODULES. Suggested-by: Michal Koutný Signed-off-by: Nikolay Borisov Signed-off-by: Dave Hansen Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Xiaoyao Li Link: https://patch.msgid.link/20260925131808.2415177-1-nik.borisov@suse.com --- arch/x86/coco/tdx/tdx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index f904a636d449ba..a38e444018402e 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -139,7 +139,7 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport) return 0; } -EXPORT_SYMBOL_GPL(tdx_mcall_get_report0); +EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_get_report0, "tdx-guest"); /** * tdx_mcall_extend_rtmr() - Wrapper to extend RTMR registers using @@ -175,7 +175,7 @@ int tdx_mcall_extend_rtmr(u8 index, u8 *data) return 0; } -EXPORT_SYMBOL_GPL(tdx_mcall_extend_rtmr); +EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_extend_rtmr, "tdx-guest"); /** * tdx_hcall_get_quote() - Wrapper to request TD Quote using GetQuote @@ -196,7 +196,7 @@ u64 tdx_hcall_get_quote(u8 *buf, size_t size) /* Since buf is a shared memory, set the shared (decrypted) bits */ return _tdx_hypercall(TDVMCALL_GET_QUOTE, cc_mkdec(virt_to_phys(buf)), size, 0, 0); } -EXPORT_SYMBOL_GPL(tdx_hcall_get_quote); +EXPORT_SYMBOL_FOR_MODULES(tdx_hcall_get_quote, "tdx-guest"); static void __noreturn tdx_panic(const char *msg) { From 4f6d1a9ea172c22e6c67e2ec06fb0f20e07e7225 Mon Sep 17 00:00:00 2001 From: "Rob Herring (Arm)" Date: Fri, 12 Jun 2026 16:50:09 -0500 Subject: [PATCH 1026/2286] thermal: of: Match trip property helper types The thermal-zone binding defines "temperature" as a signed int32 value and "hysteresis" as an unsigned int32 value. Using helpers with matching types avoids dt_property_check mismatches and preserves the signed interpretation needed for trips below zero. Read "temperature" with the signed helper and keep "hysteresis" on the unsigned helper using separate typed temporaries before storing them in the trip structure. Assisted-by: Codex:gpt-5-5 Signed-off-by: Rob Herring (Arm) Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20260612215009.1884665-1-robh@kernel.org --- drivers/thermal/thermal_of.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae33..deaae7a995c503 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -63,22 +63,23 @@ static int thermal_of_get_trip_type(struct device_node *np, static int thermal_of_populate_trip(struct device_node *np, struct thermal_trip *trip) { - int prop; + u32 hysteresis; + s32 temperature; int ret; - ret = of_property_read_u32(np, "temperature", &prop); + ret = of_property_read_s32(np, "temperature", &temperature); if (ret < 0) { pr_err("missing temperature property\n"); return ret; } - trip->temperature = prop; + trip->temperature = temperature; - ret = of_property_read_u32(np, "hysteresis", &prop); + ret = of_property_read_u32(np, "hysteresis", &hysteresis); if (ret < 0) { pr_err("missing hysteresis property\n"); return ret; } - trip->hysteresis = prop; + trip->hysteresis = hysteresis; ret = thermal_of_get_trip_type(np, &trip->type); if (ret < 0) { From ddb39deb06e0f816bba0cbcc3a658661df308f8d Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:16 +0000 Subject: [PATCH 1027/2286] dt-bindings: thermal: Add Google GS101 TMU Document the Thermal Management Unit (TMU) found on the Google GS101 SoC. The GS101 TMU utilizes a hybrid control model shared between the Application Processor (AP) and the ACPM (Alive Clock and Power Manager) firmware. This hybrid ACPM TMU architecture is also present on other Samsung Exynos SoCs (e.g., AutoV920, Exynos850). While the TMU is a standard memory-mapped IP block, on this platform the AP's direct register access is restricted to the interrupt pending (INTPEND) registers for event identification. High-level functional tasks, such as sensor initialization, threshold programming, and temperature reads, are delegated to the ACPM firmware. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260603-acpm-tmu-v8-1-0f1810a356e6@linaro.org --- .../thermal/google,gs101-tmu-top.yaml | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml diff --git a/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml new file mode 100644 index 00000000000000..75560ebca48d46 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thermal/google,gs101-tmu-top.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Samsung Exynos ACPM Thermal Management Unit (TMU) + +maintainers: + - Tudor Ambarus + +description: + The Samsung Exynos ACPM TMU is a thermal sensor block found on Exynos + based platforms (such as Google GS101 and Exynos850). It supports + both direct register-level access and firmware-mediated management + via the ACPM (Alive Clock and Power Manager) firmware. + + On these platforms, the hardware is managed in a hybrid fashion. The + Application Processor (AP) maintains direct memory-mapped access + exclusively to the interrupt pending registers to identify thermal + events. All other functional aspects - including sensor + initialization, threshold configuration, and temperature acquisition + - are handled by the ACPM firmware. The AP coordinates these + operations through the ACPM IPC protocol. + +properties: + compatible: + const: google,gs101-tmu-top + + reg: + maxItems: 1 + + clocks: + items: + - description: APB peripheral clock (PCLK) for TMU register access. + + interrupts: + maxItems: 1 + + "#thermal-sensor-cells": + const: 1 + + samsung,acpm-ipc: + $ref: /schemas/types.yaml#/definitions/phandle + description: Phandle to the ACPM IPC node. + +required: + - compatible + - reg + - clocks + - interrupts + - "#thermal-sensor-cells" + - samsung,acpm-ipc + +additionalProperties: false + +examples: + - | + #include + #include + + thermal-sensor@100a0000 { + compatible = "google,gs101-tmu-top"; + reg = <0x100a0000 0x800>; + clocks = <&cmu_misc CLK_GOUT_MISC_TMU_TOP_PCLK>; + interrupts = ; + #thermal-sensor-cells = <1>; + samsung,acpm-ipc = <&acpm_ipc>; + }; From 0ffdb6c1ea44ddb40794b9c20a39b8fb30551875 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 25 Sep 2026 09:51:39 +0200 Subject: [PATCH 1028/2286] x86/tdx: Share tdx_panic() with the EFI stub Move the implementation of tdx_panic() into the source file that is shared with the decompressor and the EFI stub. Use memcpy() and strnlen() instead of strtomem_pad(), as the latter does not exist in the early boot code. Avoid fortify instrumentation in the decompressor/EFI stub by defining __NO_FORTIFY before including tdx-shared.c Note that __tdx_hypercall() may call __tdx_hypercall_failed() if the hypercall returns with an error (while it should never return to begin with). __tdx_hypercall_failed() calls the decompressor's error() routine, which prints a message and then loops forever. When called from the EFI stub, this error() call may attempt to use port I/O to the default serial port rather than the TDX hypercalls which the decompressor uses normally to print diagnostics to the console, but this is fine: given that this situation only occurs after a catastrophic error, and a subsequent spurious return from tdx_panic(), whether error() uses port I/O or not is rather moot at that point, as long as it never returns. Signed-off-by: Ard Biesheuvel Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Kiryl Shutsemau (Meta) Link: https://patch.msgid.link/20260925075137.44298-6-ardb@kernel.org --- arch/x86/boot/compressed/tdx-shared.c | 2 ++ arch/x86/coco/tdx/tdx-shared.c | 31 ++++++++++++++++++++++++ arch/x86/coco/tdx/tdx.c | 35 --------------------------- arch/x86/include/asm/shared/tdx.h | 1 + 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/arch/x86/boot/compressed/tdx-shared.c b/arch/x86/boot/compressed/tdx-shared.c index 5ac43762fe13c1..dc38047647ccec 100644 --- a/arch/x86/boot/compressed/tdx-shared.c +++ b/arch/x86/boot/compressed/tdx-shared.c @@ -1,2 +1,4 @@ +#define __NO_FORTIFY + #include "error.h" #include "../../coco/tdx/tdx-shared.c" diff --git a/arch/x86/coco/tdx/tdx-shared.c b/arch/x86/coco/tdx/tdx-shared.c index 1655aa56a0a512..29661d8dfe8eaf 100644 --- a/arch/x86/coco/tdx/tdx-shared.c +++ b/arch/x86/coco/tdx/tdx-shared.c @@ -89,3 +89,34 @@ noinstr u64 __tdx_hypercall(struct tdx_module_args *args) /* TDVMCALL leaf return code is in R10 */ return args->r10; } + +void __noreturn tdx_panic(const char *msg) +{ + struct tdx_module_args args = { + .r10 = TDX_HYPERCALL_STANDARD, + .r11 = TDVMCALL_REPORT_FATAL_ERROR, + .r12 = 0, /* Error code: 0 is Panic */ + }; + /* Define register order according to the GHCI */ + struct { u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; } message = {}; + + /* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ + memcpy(&message, msg, strnlen(msg, sizeof(message))); + + args.r8 = message.r8; + args.r9 = message.r9; + args.r14 = message.r14; + args.r15 = message.r15; + args.rdi = message.rdi; + args.rsi = message.rsi; + args.rbx = message.rbx; + args.rdx = message.rdx; + + /* + * This hypercall should never return and it is not safe + * to keep the guest running. Call it forever if it + * happens to return. + */ + while (1) + __tdx_hypercall(&args); +} diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index f904a636d449ba..a9a16d0fb5c2e2 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -198,41 +198,6 @@ u64 tdx_hcall_get_quote(u8 *buf, size_t size) } EXPORT_SYMBOL_GPL(tdx_hcall_get_quote); -static void __noreturn tdx_panic(const char *msg) -{ - struct tdx_module_args args = { - .r10 = TDX_HYPERCALL_STANDARD, - .r11 = TDVMCALL_REPORT_FATAL_ERROR, - .r12 = 0, /* Error code: 0 is Panic */ - }; - union { - /* Define register order according to the GHCI */ - struct { u64 r14, r15, rbx, rdi, rsi, r8, r9, rdx; }; - - char bytes[64] __nonstring; - } message; - - /* VMM assumes '\0' in byte 65, if the message took all 64 bytes */ - strtomem_pad(message.bytes, msg, '\0'); - - args.r8 = message.r8; - args.r9 = message.r9; - args.r14 = message.r14; - args.r15 = message.r15; - args.rdi = message.rdi; - args.rsi = message.rsi; - args.rbx = message.rbx; - args.rdx = message.rdx; - - /* - * This hypercall should never return and it is not safe - * to keep the guest running. Call it forever if it - * happens to return. - */ - while (1) - __tdx_hypercall(&args); -} - /* * The kernel cannot handle #VEs when accessing normal kernel memory. Ensure * that no #VE will be delivered for accesses to TD-private memory. diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h index f20e91d7ac35be..e5785258e54740 100644 --- a/arch/x86/include/asm/shared/tdx.h +++ b/arch/x86/include/asm/shared/tdx.h @@ -171,6 +171,7 @@ static inline u64 _tdx_hypercall(u64 fn, u64 r12, u64 r13, u64 r14, u64 r15) return __tdx_hypercall(&args); } +void __noreturn tdx_panic(const char *msg); /* Called from __tdx_hypercall() for unrecoverable failure */ void __noreturn __tdx_hypercall_failed(void); From a8bdcf944504980635c2069b4a4cfcf677b09bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Thu, 17 Sep 2026 16:39:45 +0200 Subject: [PATCH 1029/2286] audit: separate file and process capability storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File capabilities need permitted and inheritable sets, an effective flag, and a root UID. Process capabilities need four full capability sets but neither a file effective flag nor a root UID. Give file capabilities their own type instead of storing both representations in audit_cap_data. This reduces each preallocated audit_names entry by 16 bytes on the tested 64-bit configurations, saving 80 bytes per audit context. The BPRM capability auxiliary object also shrinks from 144 to 112 bytes. Retain all logged fields, including file root IDs and process ambient capabilities; record formats and collection behavior are unchanged. Signed-off-by: Christian Göttsche Signed-off-by: Paul Moore --- kernel/audit.h | 15 +++++++++------ kernel/auditsc.c | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/kernel/audit.h b/kernel/audit.h index 3176da46484320..7640d2c0fba450 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -53,15 +53,18 @@ struct audit_entry { struct audit_krule rule; }; +struct audit_file_caps { + kernel_cap_t permitted; + kernel_cap_t inheritable; + unsigned int fE; /* effective bit of file cap */ + kuid_t rootid; +}; + struct audit_cap_data { kernel_cap_t permitted; kernel_cap_t inheritable; - union { - unsigned int fE; /* effective bit of file cap */ - kernel_cap_t effective; /* effective set of process */ - }; + kernel_cap_t effective; kernel_cap_t ambient; - kuid_t rootid; }; /* When fs/namei.c:getname() is called, we store the pointer in name and bump @@ -83,7 +86,7 @@ struct audit_names { kgid_t gid; dev_t rdev; struct lsm_prop oprop; - struct audit_cap_data fcap; + struct audit_file_caps fcap; unsigned int fcap_ver; unsigned char type; /* record type */ /* diff --git a/kernel/auditsc.c b/kernel/auditsc.c index ee7e53d2cd5231..464736499c8341 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -107,7 +107,7 @@ struct audit_aux_data_pids { struct audit_aux_data_bprm_fcaps { struct audit_aux_data d; - struct audit_cap_data fcap; + struct audit_file_caps fcap; unsigned int fcap_ver; struct audit_cap_data old_pcap; struct audit_cap_data new_pcap; From cf086537af4e3b04f6012dae3766a51ed1afc61c Mon Sep 17 00:00:00 2001 From: Sohil Mehta Date: Mon, 28 Sep 2026 15:26:07 -0700 Subject: [PATCH 1030/2286] x86/microcode/intel: Refresh old_microcode defines with the May 2026 release Update the minimum expected revisions of Intel microcode based on the microcode-20260512 (May 2026) release. Note, the three new entries are for INTEL_PANTHERLAKE_L steppings. Signed-off-by: Sohil Mehta Signed-off-by: Dave Hansen Link: https://patch.msgid.link/20260928222607.3593973-1-sohil.mehta@intel.com --- .../kernel/cpu/microcode/intel-ucode-defs.h | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/arch/x86/kernel/cpu/microcode/intel-ucode-defs.h b/arch/x86/kernel/cpu/microcode/intel-ucode-defs.h index af8b1d8b26f689..7f463c85c5f84c 100644 --- a/arch/x86/kernel/cpu/microcode/intel-ucode-defs.h +++ b/arch/x86/kernel/cpu/microcode/intel-ucode-defs.h @@ -136,35 +136,35 @@ { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x5f, .steppings = 0x0002, .platform_mask = 0x01, .driver_data = 0x3e }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x66, .steppings = 0x0008, .platform_mask = 0x80, .driver_data = 0x2a }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x6a, .steppings = 0x0020, .platform_mask = 0x87, .driver_data = 0xc0002f0 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x6a, .steppings = 0x0040, .platform_mask = 0x87, .driver_data = 0xd000410 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x6c, .steppings = 0x0002, .platform_mask = 0x10, .driver_data = 0x10002e0 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x6a, .steppings = 0x0040, .platform_mask = 0x87, .driver_data = 0xd000421 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x6c, .steppings = 0x0002, .platform_mask = 0x10, .driver_data = 0x10002f1 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x7a, .steppings = 0x0002, .platform_mask = 0x01, .driver_data = 0x42 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x7a, .steppings = 0x0100, .platform_mask = 0x01, .driver_data = 0x26 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x7e, .steppings = 0x0020, .platform_mask = 0x80, .driver_data = 0xca }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x7e, .steppings = 0x0020, .platform_mask = 0x80, .driver_data = 0xcc }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8a, .steppings = 0x0002, .platform_mask = 0x10, .driver_data = 0x33 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8c, .steppings = 0x0002, .platform_mask = 0x80, .driver_data = 0xbc }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8c, .steppings = 0x0004, .platform_mask = 0xc2, .driver_data = 0x3c }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8d, .steppings = 0x0002, .platform_mask = 0xc2, .driver_data = 0x56 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8c, .steppings = 0x0002, .platform_mask = 0x80, .driver_data = 0xbe }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8c, .steppings = 0x0004, .platform_mask = 0xc2, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8d, .steppings = 0x0002, .platform_mask = 0xc2, .driver_data = 0x58 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8e, .steppings = 0x0200, .platform_mask = 0x10, .driver_data = 0xf6 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8e, .steppings = 0x0200, .platform_mask = 0xc0, .driver_data = 0xf6 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8e, .steppings = 0x0400, .platform_mask = 0xc0, .driver_data = 0xf6 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8e, .steppings = 0x0800, .platform_mask = 0xd0, .driver_data = 0xf6 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8e, .steppings = 0x1000, .platform_mask = 0x94, .driver_data = 0x100 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0010, .platform_mask = 0x10, .driver_data = 0x2c000410 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0010, .platform_mask = 0x87, .driver_data = 0x2b000650 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0020, .platform_mask = 0x10, .driver_data = 0x2c000410 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0020, .platform_mask = 0x87, .driver_data = 0x2b000650 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0040, .platform_mask = 0x10, .driver_data = 0x2c000410 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0040, .platform_mask = 0x87, .driver_data = 0x2b000650 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0080, .platform_mask = 0x87, .driver_data = 0x2b000650 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0100, .platform_mask = 0x10, .driver_data = 0x2c000410 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0100, .platform_mask = 0x87, .driver_data = 0x2b000650 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0010, .platform_mask = 0x10, .driver_data = 0x2c000421 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0010, .platform_mask = 0x87, .driver_data = 0x2b000670 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0020, .platform_mask = 0x10, .driver_data = 0x2c000421 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0020, .platform_mask = 0x87, .driver_data = 0x2b000670 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0040, .platform_mask = 0x10, .driver_data = 0x2c000421 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0040, .platform_mask = 0x87, .driver_data = 0x2b000670 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0080, .platform_mask = 0x87, .driver_data = 0x2b000670 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0100, .platform_mask = 0x10, .driver_data = 0x2c000421 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x8f, .steppings = 0x0100, .platform_mask = 0x87, .driver_data = 0x2b000670 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x96, .steppings = 0x0002, .platform_mask = 0x01, .driver_data = 0x1a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x97, .steppings = 0x0004, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x97, .steppings = 0x0020, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0008, .platform_mask = 0x80, .driver_data = 0x43a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0010, .platform_mask = 0x40, .driver_data = 0xb }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0010, .platform_mask = 0x80, .driver_data = 0x43a }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x97, .steppings = 0x0004, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x97, .steppings = 0x0020, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0008, .platform_mask = 0x80, .driver_data = 0x43b }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0010, .platform_mask = 0x40, .driver_data = 0xc }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9a, .steppings = 0x0010, .platform_mask = 0x80, .driver_data = 0x43b }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9c, .steppings = 0x0001, .platform_mask = 0x01, .driver_data = 0x24000026 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9e, .steppings = 0x0200, .platform_mask = 0x2a, .driver_data = 0xf8 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0x9e, .steppings = 0x0400, .platform_mask = 0x22, .driver_data = 0xfa }, @@ -176,30 +176,33 @@ { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xa5, .steppings = 0x0020, .platform_mask = 0x22, .driver_data = 0x100 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xa6, .steppings = 0x0001, .platform_mask = 0x80, .driver_data = 0x102 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xa6, .steppings = 0x0002, .platform_mask = 0x80, .driver_data = 0x100 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xa7, .steppings = 0x0002, .platform_mask = 0x02, .driver_data = 0x64 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xaa, .steppings = 0x0010, .platform_mask = 0xe6, .driver_data = 0x25 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xad, .steppings = 0x0002, .platform_mask = 0x20, .driver_data = 0xa000124 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xad, .steppings = 0x0002, .platform_mask = 0x95, .driver_data = 0x10003f0 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xae, .steppings = 0x0002, .platform_mask = 0x97, .driver_data = 0x1000273 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xaf, .steppings = 0x0008, .platform_mask = 0x01, .driver_data = 0x3000382 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb5, .steppings = 0x0001, .platform_mask = 0x80, .driver_data = 0xa }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb7, .steppings = 0x0002, .platform_mask = 0x32, .driver_data = 0x132 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb7, .steppings = 0x0010, .platform_mask = 0x32, .driver_data = 0x132 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0004, .platform_mask = 0xe0, .driver_data = 0x6133 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0008, .platform_mask = 0xe0, .driver_data = 0x6133 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0100, .platform_mask = 0xe0, .driver_data = 0x6133 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbd, .steppings = 0x0002, .platform_mask = 0x80, .driver_data = 0x125 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbe, .steppings = 0x0001, .platform_mask = 0x19, .driver_data = 0x1e }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0004, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0020, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0040, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0080, .platform_mask = 0x07, .driver_data = 0x3d }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc5, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x11a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc6, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x11a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc6, .steppings = 0x0010, .platform_mask = 0x82, .driver_data = 0x11a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xca, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x11a }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcf, .steppings = 0x0002, .platform_mask = 0x87, .driver_data = 0x210002c0 }, -{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcf, .steppings = 0x0004, .platform_mask = 0x87, .driver_data = 0x210002c0 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xa7, .steppings = 0x0002, .platform_mask = 0x02, .driver_data = 0x65 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xaa, .steppings = 0x0010, .platform_mask = 0xe6, .driver_data = 0x28 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xad, .steppings = 0x0002, .platform_mask = 0x20, .driver_data = 0xa000142 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xad, .steppings = 0x0002, .platform_mask = 0x95, .driver_data = 0x1000423 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xae, .steppings = 0x0002, .platform_mask = 0x97, .driver_data = 0x1000307 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xaf, .steppings = 0x0008, .platform_mask = 0x01, .driver_data = 0x30003a3 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb5, .steppings = 0x0001, .platform_mask = 0x80, .driver_data = 0xd }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb7, .steppings = 0x0002, .platform_mask = 0x32, .driver_data = 0x133 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xb7, .steppings = 0x0010, .platform_mask = 0x32, .driver_data = 0x133 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0004, .platform_mask = 0xe0, .driver_data = 0x6134 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0008, .platform_mask = 0xe0, .driver_data = 0x6134 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xba, .steppings = 0x0100, .platform_mask = 0xe0, .driver_data = 0x6134 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbd, .steppings = 0x0002, .platform_mask = 0x80, .driver_data = 0x126 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbe, .steppings = 0x0001, .platform_mask = 0x19, .driver_data = 0x21 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0004, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0020, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0040, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xbf, .steppings = 0x0080, .platform_mask = 0x07, .driver_data = 0x3e }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc5, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x121 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc6, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x121 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xc6, .steppings = 0x0010, .platform_mask = 0x82, .driver_data = 0x121 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xca, .steppings = 0x0004, .platform_mask = 0x82, .driver_data = 0x121 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcc, .steppings = 0x0002, .platform_mask = 0x90, .driver_data = 0x11b }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcc, .steppings = 0x0004, .platform_mask = 0x90, .driver_data = 0x11b }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcc, .steppings = 0x0008, .platform_mask = 0x90, .driver_data = 0x11b }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcf, .steppings = 0x0002, .platform_mask = 0x87, .driver_data = 0x210002e0 }, +{ .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0x6, .model = 0xcf, .steppings = 0x0004, .platform_mask = 0x87, .driver_data = 0x210002e0 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0xf, .model = 0x00, .steppings = 0x0080, .platform_mask = 0x01, .driver_data = 0x12 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0xf, .model = 0x00, .steppings = 0x0080, .platform_mask = 0x02, .driver_data = 0x8 }, { .flags = X86_CPU_ID_FLAG_ENTRY_VALID, .vendor = X86_VENDOR_INTEL, .family = 0xf, .model = 0x00, .steppings = 0x0400, .platform_mask = 0x01, .driver_data = 0x13 }, From a9bc562daaf2596e6eab5f1a5e3a5880c4b2a1ee Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 25 Sep 2026 09:51:40 +0200 Subject: [PATCH 1031/2286] x86/boot: Move unaccepted memory handling out of the decompressor arch_accept_memory() is an arch-specific hook that is required by the EFI stub when processing memory that the firmware reports to the OS as EFI_UNACCEPTED_MEMORY. This hook is called after ExitBootServices() has been called, as before that point, the EFI memory map may get updated behind the back of the running EFI stub, making it difficult to get a stable view on it while iterating over the entries. Currently, the x86 version of this hook is implemented in its decompressor rather than in the EFI stub itself, in a manner that is problematic: when an error occurs, it calls the decompressor's error() routine, but without having gone through the decompressor initialization code. This means it will resort to direct port I/O rather than the hypercall based interface that TDX guests would use otherwise. Conceptually, code that is only called from the EFI stub, and never by the decompressor when doing legacy boot, belongs in the EFI stub and not in the decompressor. So move it into the x86-specific EFI stub code, replacing the error() on the TDX path with tdx_panic(), and dropping the error() when no CC support is detected - the kernel can decide what to do in this case after it has booted. Signed-off-by: Ard Biesheuvel Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Kiryl Shutsemau (Meta) Link: https://patch.msgid.link/20260925075137.44298-7-ardb@kernel.org --- arch/x86/boot/compressed/mem.c | 42 ------------------------- arch/x86/boot/compressed/sev.h | 2 -- arch/x86/include/asm/sev.h | 2 ++ drivers/firmware/efi/libstub/x86-stub.c | 39 +++++++++++++++++++++++ 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/arch/x86/boot/compressed/mem.c b/arch/x86/boot/compressed/mem.c index 0e9f84ab4bdcd1..1721af3a80399b 100644 --- a/arch/x86/boot/compressed/mem.c +++ b/arch/x86/boot/compressed/mem.c @@ -2,48 +2,6 @@ #include "error.h" #include "misc.h" -#include "tdx.h" -#include "sev.h" -#include - -/* - * accept_memory() and process_unaccepted_memory() called from EFI stub which - * runs before decompressor and its early_tdx_detect(). - * - * Enumerate TDX directly from the early users. - */ -static bool early_is_tdx_guest(void) -{ - static bool once; - static bool is_tdx; - - if (!IS_ENABLED(CONFIG_INTEL_TDX_GUEST)) - return false; - - if (!once) { - u32 eax, sig[3]; - - cpuid_count(TDX_CPUID_LEAF_ID, 0, &eax, - &sig[0], &sig[2], &sig[1]); - is_tdx = !memcmp(TDX_IDENT, sig, sizeof(sig)); - once = true; - } - - return is_tdx; -} - -void arch_accept_memory(phys_addr_t start, phys_addr_t end) -{ - /* Platform-specific memory-acceptance call goes here */ - if (early_is_tdx_guest()) { - if (!tdx_accept_memory(start, end)) - panic("TDX: Failed to accept memory\n"); - } else if (early_is_sevsnp_guest()) { - snp_accept_memory(start, end); - } else { - error("Cannot accept memory: unknown platform\n"); - } -} bool init_unaccepted_memory(void) { diff --git a/arch/x86/boot/compressed/sev.h b/arch/x86/boot/compressed/sev.h index 22637b416b46fc..62e50c2e71edb1 100644 --- a/arch/x86/boot/compressed/sev.h +++ b/arch/x86/boot/compressed/sev.h @@ -14,7 +14,6 @@ void snp_accept_memory(phys_addr_t start, phys_addr_t end); u64 sev_get_status(void); -bool early_is_sevsnp_guest(void); static inline u64 sev_es_rd_ghcb_msr(void) { @@ -37,7 +36,6 @@ static inline void sev_es_wr_ghcb_msr(u64 val) static inline void snp_accept_memory(phys_addr_t start, phys_addr_t end) { } static inline u64 sev_get_status(void) { return 0; } -static inline bool early_is_sevsnp_guest(void) { return false; } #endif diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h index 9e7a077c445dcc..4b5db43cc0b198 100644 --- a/arch/x86/include/asm/sev.h +++ b/arch/x86/include/asm/sev.h @@ -517,6 +517,7 @@ void snp_accept_memory(phys_addr_t start, phys_addr_t end); u64 snp_get_unsupported_features(u64 status); u64 sev_get_status(void); void sev_show_status(void); +bool early_is_sevsnp_guest(void); int prepare_pte_enc(struct pte_enc_desc *d); void set_pte_enc_mask(pte_t *kpte, unsigned long pfn, pgprot_t new_prot); void snp_kexec_finish(void); @@ -626,6 +627,7 @@ static inline void snp_accept_memory(phys_addr_t start, phys_addr_t end) { } static inline u64 snp_get_unsupported_features(u64 status) { return 0; } static inline u64 sev_get_status(void) { return 0; } static inline void sev_show_status(void) { } +static inline bool early_is_sevsnp_guest(void) { return false; } static inline int prepare_pte_enc(struct pte_enc_desc *d) { return 0; } static inline void set_pte_enc_mask(pte_t *kpte, unsigned long pfn, pgprot_t new_prot) { } static inline void snp_kexec_finish(void) { } diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index cef32e2c82d8fd..80556a7e755210 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include "efistub.h" #include "x86-stub.h" @@ -1068,3 +1070,40 @@ void efi64_stub_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg, struct boot_params *boot_params); #endif #endif + +#ifdef CONFIG_UNACCEPTED_MEMORY +/* + * process_unaccepted_memory() is called after ExitBootServices(), and so these + * memory acceptance routines cannot rely on EFI protocols for detecting the + * presence of TDX or SEV-SNP, or emit any kind of output if any error + * conditions are detected. + */ +static bool early_is_tdx_guest(void) +{ + static bool once; + static bool is_tdx; + + if (!IS_ENABLED(CONFIG_INTEL_TDX_GUEST)) + return false; + + if (!once) { + u32 eax = TDX_CPUID_LEAF_ID, sig[3] = {}; + + native_cpuid(&eax, &sig[0], &sig[2], &sig[1]); + is_tdx = !memcmp(TDX_IDENT, sig, sizeof(sig)); + once = true; + } + + return is_tdx; +} + +void arch_accept_memory(phys_addr_t start, phys_addr_t end) +{ + if (early_is_tdx_guest()) { + if (!tdx_accept_memory(start, end)) + tdx_panic("Failed to accept memory"); + } else if (early_is_sevsnp_guest()) { + snp_accept_memory(start, end); + } +} +#endif From dc2a1671d983d26b8ea5a4c5de9c5a028707f40b Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 25 Sep 2026 09:51:41 +0200 Subject: [PATCH 1032/2286] x86/boot: Drop unused implementation of panic() The decompressor has its own implementation of panic(), which is based on the vsnprintf() routine provided by the EFI stub. The decompressor's panic() has no remaining users, and is implemented in terms of the EFI stub's snprintf() implementation, which is going away (and relying on the EFI stub from code that does not execute in the context of the EFI boot services is a bad idea in general). So drop it. Signed-off-by: Ard Biesheuvel Signed-off-by: Borislav Petkov (AMD) Acked-by: Kiryl Shutsemau (Meta) Link: https://patch.msgid.link/20260925075137.44298-8-ardb@kernel.org --- arch/x86/boot/compressed/error.c | 19 ------------------- arch/x86/boot/compressed/error.h | 1 - 2 files changed, 20 deletions(-) diff --git a/arch/x86/boot/compressed/error.c b/arch/x86/boot/compressed/error.c index 19a8251de506a5..ce5ed7d8265e6d 100644 --- a/arch/x86/boot/compressed/error.c +++ b/arch/x86/boot/compressed/error.c @@ -22,22 +22,3 @@ void error(char *m) while (1) asm("hlt"); } - -/* EFI libstub provides vsnprintf() */ -#ifdef CONFIG_EFI_STUB -void panic(const char *fmt, ...) -{ - static char buf[1024]; - va_list args; - int len; - - va_start(args, fmt); - len = vsnprintf(buf, sizeof(buf), fmt, args); - va_end(args); - - if (len && buf[len - 1] == '\n') - buf[len - 1] = '\0'; - - error(buf); -} -#endif diff --git a/arch/x86/boot/compressed/error.h b/arch/x86/boot/compressed/error.h index 31f9e080d61a80..87062dea9a20da 100644 --- a/arch/x86/boot/compressed/error.h +++ b/arch/x86/boot/compressed/error.h @@ -6,6 +6,5 @@ void warn(const char *m); void error(char *m) __noreturn; -void panic(const char *fmt, ...) __noreturn __cold; #endif /* BOOT_COMPRESSED_ERROR_H */ From 2ea0119f72dba597aa8a98cbdb72c564bfc5cb38 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Sun, 15 Feb 2026 21:36:04 +0100 Subject: [PATCH 1033/2286] rust: hrtimer: document handle based design rationale Add implementation notes explaining why the hrtimer abstraction uses a handle based approach. Reviewed-by: Alice Ryhl Reviewed-by: Boqun Feng Reviewed-by: FUJITA Tomonori Link: https://msgid.link/20260215-hrtimer-docs-v1-1-bff6a6c0d923@kernel.org Signed-off-by: Andreas Hindborg --- rust/kernel/time/hrtimer.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rust/kernel/time/hrtimer.rs b/rust/kernel/time/hrtimer.rs index 87aafbee0ce958..c0180b8ca5d31f 100644 --- a/rust/kernel/time/hrtimer.rs +++ b/rust/kernel/time/hrtimer.rs @@ -402,6 +402,17 @@ //! ``` //! //! [`Arc`]: kernel::sync::Arc +// Implementation details +// +// The reasoning for adopting a handle based approach: +// - If we explicitly drop the target of a timer callback in the timer callback, we +// may get a dangling reference. +// - If the callback owns the last reference to the target, target may be dropped +// in non-sleepable context when the callback is finished. +// - When dropping an object that is the target of an armed timer, we may drop +// fields accessed by the timer callback before we cancel the timer (drop order). +// +// By using a handle, we can make the handle own the callback target and avoid these problems. use super::{ClockId, Delta, Instant}; use crate::{prelude::*, types::Opaque}; From e84c9b64df615e658e47561b835c1364dc48f0c0 Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Mon, 8 Jun 2026 14:32:00 +0200 Subject: [PATCH 1034/2286] rust: configfs: fix data offset calculation for subsystem callbacks `get_group_data()` chooses between `Group::::container_of()` and `Subsystem::::container_of()` based on whether `this` represents the root group of a configfs subsystem. It detects this by checking `(*this).cg_subsys.is_null()`, but `link_group()` in `fs/configfs/dir.c` unconditionally sets `cg_subsys` for every `config_group` attached anywhere in a registered subsystem, including the subsystem's own `su_group`. The only `config_group` with a NULL `cg_subsys` is the configfs root, on which userspace cannot trigger callbacks. The check is therefore always false at runtime, and the `Subsystem` branch is dead. Subsystem-level callbacks reach the `Group` branch and may read `data` at the wrong offset. Thus change the `is_root` check to correctly identify whether a group is a root group by comparing `this` to `subsys.su_group`. Cc: stable@vger.kernel.org Fixes: 446cafc295bf ("rust: configfs: introduce rust support for configfs") Link: https://msgid.link/20260608-configfs-fix-offset-v1-1-7f01b8fb9e5f@kernel.org [ Andreas: Fix clippy warning on 1.85.0 by moving safety comment. ] Signed-off-by: Andreas Hindborg --- rust/kernel/configfs.rs | 43 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/rust/kernel/configfs.rs b/rust/kernel/configfs.rs index ec295ab965da17..0f506d3a3cf545 100644 --- a/rust/kernel/configfs.rs +++ b/rust/kernel/configfs.rs @@ -305,26 +305,39 @@ unsafe impl HasGroup for Group { /// /// `this` must be a valid pointer. /// -/// If `this` does not represent the root group of a configfs subsystem, -/// `this` must be a pointer to a `bindings::config_group` embedded in a -/// `Group`. +/// If `this` is the `su_group` field of a `bindings::configfs_subsystem`, that +/// `configfs_subsystem` must be embedded in a `Subsystem`. /// -/// Otherwise, `this` must be a pointer to a `bindings::config_group` that -/// is embedded in a `bindings::configfs_subsystem` that is embedded in a -/// `Subsystem`. +/// Otherwise, `this` must be a pointer to a `bindings::config_group` embedded +/// in a `Group`. unsafe fn get_group_data<'a, Parent>(this: *mut bindings::config_group) -> &'a Parent { // SAFETY: `this` is a valid pointer. - let is_root = unsafe { (*this).cg_subsys.is_null() }; + let subsys = unsafe { (*this).cg_subsys }; + + // `link_group()` in `fs/configfs/dir.c` assigns `cg_subsys` for every + // `config_group` attached anywhere in a registered subsystem, including + // the subsystem's own `su_group` (which gets a pointer to itself). The + // only `config_group` with a NULL `cg_subsys` is the configfs root, and + // userspace cannot trigger callbacks on it. The group is therefore the + // subsystem's `su_group` iff it equals `&cg_subsys->su_group`. + let is_root = !subsys.is_null() + && core::ptr::eq( + this.cast_const(), + // SAFETY: For every `config_group` the configfs core dispatches a + // callback on, `cg_subsys` was set by `link_group()` at registration time + // and points to a valid `configfs_subsystem` that outlives the callback. + unsafe { &raw const (*subsys).su_group }, + ); - if !is_root { - // SAFETY: By C API contact,`this` was returned from a call to - // `make_group`. The pointer is known to be embedded within a - // `Group`. - unsafe { &(*Group::::container_of(this)).data } - } else { - // SAFETY: By C API contract, `this` is a pointer to the - // `bindings::config_group` field within a `Subsystem`. + if is_root { + // SAFETY: By the above, `this` is the `su_group` field of a + // `configfs_subsystem` that, by function safety requirements, is + // embedded in a `Subsystem`. unsafe { &(*Subsystem::container_of(this)).data } + } else { + // SAFETY: By function safety requirements, `this` is a + // `config_group` embedded in a `Group`. + unsafe { &(*Group::::container_of(this)).data } } } From 6faad04a002603d800134398fcd042b403b32d35 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Tue, 29 Sep 2026 15:38:14 +0900 Subject: [PATCH 1035/2286] nilfs2: clear dirty flag on bdev buffers on log write failure Since the commit referenced in the Fixes tag stopped directly calling inode_attach_wb(), a dirty flag is set on buffers (with mark_buffer_dirty()) allocated to the backing device for segment summaries and the super root block during log writes. While this dirty flag is cleared upon a successful log write, it remains uncleared if the log write fails. Consequently, when a log write fails, these backing device buffers are left in an inconsistent state where their uptodate flag and the dirty flag of the containing page/folio are cleared, but the buffer's dirty flag remains as stale garbage. Particularly when the block size is smaller than the page size, if other buffers on the same page/folio become dirty, this buffer unexpectedly becomes a target for writeback again. As a result, if the block is later reused for other data or metadata, that contents risks being corrupted. Fix this issue by calling clear_buffer_dirty() for the backing device buffers when aborting log writes in nilfs_abort_logs(). Fixes: 68142cb628f7 ("nilfs2: do not call inode_attach_wb() directly") Cc: stable@vger.kernel.org Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/segment.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c index 829573cb613168..2f0e59847d026c 100644 --- a/fs/nilfs2/segment.c +++ b/fs/nilfs2/segment.c @@ -1829,6 +1829,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err) list_for_each_entry(segbuf, logs, sb_list) { list_for_each_entry(bh, &segbuf->sb_segsum_buffers, b_assoc_buffers) { + clear_buffer_dirty(bh); clear_buffer_uptodate(bh); if (bh->b_folio != bd_folio) { if (bd_folio) @@ -1840,6 +1841,7 @@ static void nilfs_abort_logs(struct list_head *logs, int err) list_for_each_entry(bh, &segbuf->sb_payload_buffers, b_assoc_buffers) { if (bh == segbuf->sb_super_root) { + clear_buffer_dirty(bh); clear_buffer_uptodate(bh); if (bh->b_folio != bd_folio) { folio_end_writeback(bd_folio); From 75990fb534e858ac61ae7651c4695e0905a04443 Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Wed, 16 Sep 2026 07:15:30 -0700 Subject: [PATCH 1036/2286] tick/nohz: Remove redundant local_irq_save()/restore() tick_nohz_update_jiffies() is only reached through tick_irq_enter() from hard interrupt entry, where local interrupts are already disabled. tick_do_update_jiffies64() requires interrupts to be disabled. The local_irq_save()/local_irq_restore() pair around it therefore provides no additional interrupt exclusion. Remove it and assert the caller invariant instead. On x86-64, this avoids the redundant RFLAGS save, CLI, saved-IF test and conditional branch on the tick-stopped path. On a 252-CPU host running a Thrift RPC service, tick_irq_enter() ran 165,116 times/s out of 212-244k interrupts/s. Only calls with TS_FLAG_STOPPED reach the optimized path, so this is an upper bound on its execution rate. Signed-off-by: Usama Arif Signed-off-by: Thomas Gleixner Reviewed-by: Frederic Weisbecker Link: https://patch.msgid.link/20260916141530.3423310-1-usama.arif@linux.dev --- kernel/time/tick-sched.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 6c3fea38671397..c8f2c4a503b08f 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -738,14 +738,11 @@ bool tick_nohz_tick_stopped_cpu(int cpu) */ static void tick_nohz_update_jiffies(ktime_t now) { - unsigned long flags; + /* Reached only from irq_enter_rcu(), i.e. hard interrupt entry. */ + lockdep_assert_irqs_disabled(); __this_cpu_write(tick_cpu_sched.idle_waketime, now); - - local_irq_save(flags); tick_do_update_jiffies64(now); - local_irq_restore(flags); - touch_softlockup_watchdog_sched(); } From d305927765cf6025bc11db9d96c06ea663230ddd Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 18 Sep 2026 06:34:08 -0700 Subject: [PATCH 1037/2286] tick/nohz: Avoid unused timekeeping_max_deferment() calls tick_nohz_next_event() limits a CPU's sleep interval to the maximum deferment supported by the current clocksource when that CPU owns the do_timer() duty. If the duty is unassigned, the limit also applies when the CPU's TS_FLAG_DO_TIMER_LAST flag is set. After the early timer checks, the function currently reads the maximum deferment unconditionally. It then replaces the result with KTIME_MAX unless one of the two conditions above applies. timekeeping_max_deferment() performs a seqcount-protected read of the shared timekeeper and follows its clocksource pointer. Check the do_timer state first and avoid this work when the result would be discarded. This leaves the resulting expiry unchanged and reduces accesses to timekeeper data that is modified regularly. On x86-64 this removes 18-20 dynamically executed instructions, including the call, from the common non-owner path when the seqcount does not retry. Signed-off-by: Usama Arif Signed-off-by: Thomas Gleixner Reviewed-by: Rik van Riel Link: https://patch.msgid.link/20260918133408.2834751-1-usama.arif@linux.dev --- kernel/time/tick-sched.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index c8f2c4a503b08f..a7893a079a83f3 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -816,7 +816,7 @@ u64 get_jiffies_update(unsigned long *basej) */ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) { - u64 basemono, next_tick, delta, expires; + u64 basemono, next_tick, expires; unsigned long basejiff; int tick_cpu; @@ -856,8 +856,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * If the tick is due in the next period, keep it ticking or * force prod the timer. */ - delta = next_tick - basemono; - if (delta <= (u64)TICK_NSEC) { + if (next_tick - basemono <= (u64)TICK_NSEC) { /* * We've not stopped the tick yet, and there's a timer in the * next period, so no point in stopping it either, bail. @@ -873,17 +872,19 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) * the sleep time to the timekeeping 'max_deferment' value. * Otherwise we can sleep as long as we want. */ - delta = timekeeping_max_deferment(); tick_cpu = READ_ONCE(tick_do_timer_cpu); if (tick_cpu != cpu && - (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST))) - delta = KTIME_MAX; - - /* Calculate the next expiry time */ - if (delta < (KTIME_MAX - basemono)) - expires = basemono + delta; - else + (tick_cpu != TICK_DO_TIMER_NONE || !tick_sched_flag_test(ts, TS_FLAG_DO_TIMER_LAST))) { expires = KTIME_MAX; + } else { + expires = timekeeping_max_deferment(); + + /* Calculate the next expiry time */ + if (expires < (KTIME_MAX - basemono)) + expires += basemono; + else + expires = KTIME_MAX; + } ts->timer_expires = min_t(u64, expires, next_tick); From d166a1cd901615280be050929e5a5c9e4d300a62 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:17 -0700 Subject: [PATCH 1038/2286] hrtimer: Mark data-racy accesses to hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion. Therefore, mark data-racy accesses to the hrtimer_sleeper ->task field using READ_ONCE() (using a new hrtimer_sleeper_task_get() access function) and WRITE_ONCE() (using a new hrtimer_sleeper_task_set() access function). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-1-paulmck@kernel.org --- include/linux/hrtimer.h | 8 ++++++++ kernel/time/hrtimer.c | 14 +++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 29072d89e5cb38..d44549c0a2c118 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -320,6 +320,14 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires, const enum hrtimer_mode mode, clockid_t clock_id); extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); +static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl) +{ + return READ_ONCE(sl->task); +} +static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t) +{ + WRITE_ONCE(sl->task, t); +} /* Soft interrupt function to run the hrtimer queues: */ extern void hrtimer_run_queues(void); diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index cbf1693c86b387..7b44a35478d486 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2323,9 +2323,9 @@ void hrtimer_run_queues(void) static enum hrtimer_restart hrtimer_wakeup(struct hrtimer *timer) { struct hrtimer_sleeper *t = container_of(timer, struct hrtimer_sleeper, timer); - struct task_struct *task = t->task; + struct task_struct *task = hrtimer_sleeper_task_get(t); - t->task = NULL; + hrtimer_sleeper_task_set(t, NULL); if (task) wake_up_process(task); @@ -2354,7 +2354,7 @@ void hrtimer_sleeper_start_expires(struct hrtimer_sleeper *sl, enum hrtimer_mode /* If already expired, clear the task pointer and set current state to running */ if (!hrtimer_start_expires_user(&sl->timer, mode)) { - sl->task = NULL; + hrtimer_sleeper_task_set(sl, NULL); __set_current_state(TASK_RUNNING); } } @@ -2388,7 +2388,7 @@ static void __hrtimer_setup_sleeper(struct hrtimer_sleeper *sl, clockid_t clock_ } __hrtimer_setup(&sl->timer, hrtimer_wakeup, clock_id, mode); - sl->task = current; + hrtimer_sleeper_task_set(sl, current); } /** @@ -2432,17 +2432,17 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod set_current_state(TASK_INTERRUPTIBLE|TASK_FREEZABLE); hrtimer_sleeper_start_expires(t, mode); - if (likely(t->task)) + if (likely(hrtimer_sleeper_task_get(t))) schedule(); hrtimer_cancel(&t->timer); mode = HRTIMER_MODE_ABS; - } while (t->task && !signal_pending(current)); + } while (hrtimer_sleeper_task_get(t) && !signal_pending(current)); __set_current_state(TASK_RUNNING); - if (!t->task) + if (!hrtimer_sleeper_task_get(t)) return 0; restart = ¤t->restart_block; From cefc1a24ace53c9b26f5adee1f60a439cc0d53a4 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:18 -0700 Subject: [PATCH 1039/2286] aio: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Christian Brauner (Amutable) Acked-by: Jan Kara Link: https://patch.msgid.link/20260919001428.3133388-2-paulmck@kernel.org --- fs/aio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/aio.c b/fs/aio.c index d78acc69f48701..8130d9e637ea4b 100644 --- a/fs/aio.c +++ b/fs/aio.c @@ -1402,7 +1402,7 @@ static long read_events(struct kioctx *ctx, long min_nr, long nr, w.min_nr = min_nr - ret; ret2 = prepare_to_wait_event(&ctx->wait, &w.w, TASK_INTERRUPTIBLE); - if (!ret2 && !t.task) + if (!ret2 && !hrtimer_sleeper_task_get(&t)) ret2 = -ETIME; if (aio_read_events(ctx, min_nr, nr, event, &ret) || ret2) From 66b29025b6761d9e0a8bd4d1b813b1f2c3c1a28e Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:19 -0700 Subject: [PATCH 1040/2286] wait: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the __wait_event_hrtimeout() macro. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-3-paulmck@kernel.org --- include/linux/wait.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/wait.h b/include/linux/wait.h index 7e215330199cfe..c2af98b0074dcb 100644 --- a/include/linux/wait.h +++ b/include/linux/wait.h @@ -556,7 +556,7 @@ do { \ } \ \ __ret = ___wait_event(wq_head, condition, state, 0, 0, \ - if (!__t.task) { \ + if (!hrtimer_sleeper_task_get(&__t)) { \ __ret = -ETIME; \ break; \ } \ From 851c30277918e1f1aad12394235d87c00afec464 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:20 -0700 Subject: [PATCH 1041/2286] io-uring/rw: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the io_hybrid_iopoll_delay() function. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-4-paulmck@kernel.org --- io_uring/rw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_uring/rw.c b/io_uring/rw.c index 432820f862514c..d9b1e096f07baf 100644 --- a/io_uring/rw.c +++ b/io_uring/rw.c @@ -1295,7 +1295,7 @@ static u64 io_hybrid_iopoll_delay(struct io_ring_ctx *ctx, struct io_kiocb *req) set_current_state(TASK_INTERRUPTIBLE); hrtimer_sleeper_start_expires(&timer, mode); - if (timer.task) + if (hrtimer_sleeper_task_get(&timer)) io_schedule(); hrtimer_cancel(&timer.timer); From fa3d486086d6948a6cf09b6d94cb1f56df2b1a6b Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:21 -0700 Subject: [PATCH 1042/2286] futex: Use accessor for hrtimer_sleeper ->task field in waitwake.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to kernel/futex/waitwake.c. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: André Almeida Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-5-paulmck@kernel.org --- kernel/futex/waitwake.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c index d4483d15d30a5d..cf18309e577030 100644 --- a/kernel/futex/waitwake.c +++ b/kernel/futex/waitwake.c @@ -383,7 +383,7 @@ void futex_do_wait(struct futex_q *q, struct hrtimer_sleeper *timeout) * flagged for rescheduling. Only call schedule if there * is no timeout, or if it has yet to expire. */ - if (!timeout || timeout->task) + if (!timeout || hrtimer_sleeper_task_get(timeout)) schedule(); } __set_current_state(TASK_RUNNING); @@ -539,7 +539,7 @@ int futex_wait_multiple_setup(struct futex_vector *vs, int count, int *woken) static void futex_sleep_multiple(struct futex_vector *vs, unsigned int count, struct hrtimer_sleeper *to) { - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return; for (; count; count--, vs++) { @@ -590,7 +590,7 @@ int futex_wait_multiple(struct futex_vector *vs, unsigned int count, if (ret >= 0) return ret; - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return -ETIMEDOUT; else if (signal_pending(current)) return -ERESTARTSYS; @@ -725,7 +725,7 @@ int __futex_wait(u32 __user *uaddr, unsigned int flags, u32 val, if (!futex_unqueue(&q)) return 0; - if (to && !to->task) + if (to && !hrtimer_sleeper_task_get(to)) return -ETIMEDOUT; /* From 9b7bcd671e9c979f16c48589f4b9b6c684ab052f Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:22 -0700 Subject: [PATCH 1043/2286] timers: Use accessor for hrtimer_sleeper ->task field in sleep_timeout.c The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to schedule_hrtimeout_range_clock(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-6-paulmck@kernel.org --- kernel/time/sleep_timeout.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/time/sleep_timeout.c b/kernel/time/sleep_timeout.c index 3c90574bd904f1..ad8c415851ae19 100644 --- a/kernel/time/sleep_timeout.c +++ b/kernel/time/sleep_timeout.c @@ -212,7 +212,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta, hrtimer_set_expires_range_ns(&t.timer, *expires, delta); hrtimer_sleeper_start_expires(&t, mode); - if (likely(t.task)) + if (likely(hrtimer_sleeper_task_get(&t))) schedule(); hrtimer_cancel(&t.timer); @@ -220,7 +220,7 @@ int __sched schedule_hrtimeout_range_clock(ktime_t *expires, u64 delta, __set_current_state(TASK_RUNNING); - return !t.task ? 0 : -EINTR; + return !hrtimer_sleeper_task_get(&t) ? 0 : -EINTR; } EXPORT_SYMBOL_GPL(schedule_hrtimeout_range_clock); From 3990d196954a06cf9fb1bb4efd142d57c30a8e21 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:23 -0700 Subject: [PATCH 1044/2286] net: pktgen: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to the pktgen spin() function. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-7-paulmck@kernel.org --- net/core/pktgen.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/core/pktgen.c b/net/core/pktgen.c index 7f81aed466723b..784d8398fb7abe 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -2346,11 +2346,11 @@ static void spin(struct pktgen_dev *pkt_dev, ktime_t spin_until) set_current_state(TASK_INTERRUPTIBLE); hrtimer_sleeper_start_expires(&t, HRTIMER_MODE_ABS); - if (likely(t.task)) + if (likely(hrtimer_sleeper_task_get(&t))) schedule(); hrtimer_cancel(&t.timer); - } while (t.task && pkt_dev->running && !signal_pending(current)); + } while (hrtimer_sleeper_task_get(&t) && pkt_dev->running && !signal_pending(current)); __set_current_state(TASK_RUNNING); end_time = ktime_get(); } From 9cd2f4e304d921459a9348323aff518aee7fdc06 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:24 -0700 Subject: [PATCH 1045/2286] rtmutex: Use accessor for hrtimer_sleeper ->task field The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to rt_mutex_slowlock_block(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-8-paulmck@kernel.org --- kernel/locking/rtmutex.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/locking/rtmutex.c b/kernel/locking/rtmutex.c index 4728631ae71940..5a9534c715b845 100644 --- a/kernel/locking/rtmutex.c +++ b/kernel/locking/rtmutex.c @@ -1644,7 +1644,7 @@ static int __sched rt_mutex_slowlock_block(struct rt_mutex_base *lock, break; } - if (timeout && !timeout->task) { + if (timeout && !hrtimer_sleeper_task_get(timeout)) { ret = -ETIMEDOUT; break; } From 7eed1771a6e84101c7cdf8b7d487f01b6420ba8c Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:25 -0700 Subject: [PATCH 1046/2286] futex: Use accessor for hrtimer_sleeper ->task field in requeue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hrtimer_sleeper structure's ->task field is used as a flag to indicate that the associated hrtimer has expired. This means that the hrtimer handler can be storing to this field while other code is loading from it to check for expiry. Note that additional races appear for hrtimers that can be restarted, which could be argued to be a user error. However, that is no reason to let the compiler introduce additional confusion, and to this end, the hrtimer_sleeper_task_get() was introduced, use of which also has the benefit of avoiding open-code access to hrtimer_sleeper innards. Therefore, apply this accessor to handle_early_requeue_pi_wakeup(). KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Reviewed-by: André Almeida Reviewed-by: Dmitry Ilvokhin Link: https://patch.msgid.link/20260919001428.3133388-9-paulmck@kernel.org --- kernel/futex/requeue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index b3f4a4bccb1263..842d852302ddfb 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -744,7 +744,7 @@ int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb, /* Handle spurious wakeups gracefully */ ret = -EWOULDBLOCK; - if (timeout && !timeout->task) + if (timeout && !hrtimer_sleeper_task_get(timeout)) ret = -ETIMEDOUT; else if (signal_pending(current)) ret = -ERESTARTNOINTR; From 15f84398330ce1c0aa3e1e3edd0c915324bed26b Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:26 -0700 Subject: [PATCH 1047/2286] hrtimer: Update hrtimer_resolution only if value changes Currently, the hrtimer_switch_to_hres() function unconditionally updates hrtimer_resolution to HIGH_RES_NSEC. This causes KCSAN false positives in kernels built with CONFIG_KCSAN_STRICT=y. It also causes unnecessary cache-line traffic on the many code paths that load hrtimer_resolution, which is especially regrettable because the value of hrtimer_resolution normally changes only once during the lifetime of a given kernel. Therefore, update the value of hrtimer_resolution to HIGH_RES_NSEC only if it does not already have that value. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-10-paulmck@kernel.org --- kernel/time/hrtimer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 7b44a35478d486..2054064609935b 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -780,7 +780,8 @@ static void hrtimer_switch_to_hres(void) return; } base->hres_active = true; - hrtimer_resolution = HIGH_RES_NSEC; + if (hrtimer_resolution != HIGH_RES_NSEC) + hrtimer_resolution = HIGH_RES_NSEC; tick_setup_sched_timer(true); /* "Retrigger" the interrupt to get things going */ From ad80926d780340ab08799cdb38be010e0dce8ec0 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:27 -0700 Subject: [PATCH 1048/2286] hrtimer: Mark the hrtimer_sleeper structure's ->task field __private The hrtimer_sleeper structure's ->task field is now used only by the hrtimer_sleeper_task_get() and hrtimer_sleeper_task_set() functions, and there is no reason for it to be directly accessed anywhere else. Therefore, mark this field __private and use ACCESS_PRIVATE() in hrtimer_sleeper_task_get() and hrtimer_sleeper_task_set(). Suggested-by: Thomas Gleixner Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-11-paulmck@kernel.org --- include/linux/hrtimer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index d44549c0a2c118..cad8482337cb7b 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -72,7 +72,7 @@ enum hrtimer_mode { */ struct hrtimer_sleeper { struct hrtimer timer; - struct task_struct *task; + struct task_struct *__private task; }; static inline void hrtimer_set_expires(struct hrtimer *timer, ktime_t time) @@ -322,11 +322,11 @@ extern int schedule_hrtimeout_range_clock(ktime_t *expires, extern int schedule_hrtimeout(ktime_t *expires, const enum hrtimer_mode mode); static inline struct task_struct *hrtimer_sleeper_task_get(struct hrtimer_sleeper *sl) { - return READ_ONCE(sl->task); + return READ_ONCE(ACCESS_PRIVATE(sl, task)); } static inline void hrtimer_sleeper_task_set(struct hrtimer_sleeper *sl, struct task_struct *t) { - WRITE_ONCE(sl->task, t); + WRITE_ONCE(ACCESS_PRIVATE(sl, task), t); } /* Soft interrupt function to run the hrtimer queues: */ From 5dffe33bfdafcc768d090c55479f515585509ffa Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:14:28 -0700 Subject: [PATCH 1049/2286] hrtimer: Apply READ_ONCE() to lockless base->running loads Updates to base->running are protected by the hrtimer base lock, but some loads are lockless. Therefore, prevent compiler mischief by applying READ_ONCE() to the lockless loads. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919001428.3133388-12-paulmck@kernel.org --- kernel/time/hrtimer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index 2054064609935b..17dd38a6cee7a8 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -2004,7 +2004,7 @@ bool hrtimer_active(const struct hrtimer *timer) base = READ_ONCE(timer->base); seq = raw_read_seqcount_begin(&base->seq); - if (timer->is_queued || base->running == timer) + if (timer->is_queued || READ_ONCE(base->running) == timer) return true; } while (read_seqcount_retry(&base->seq, seq) || base != READ_ONCE(timer->base)); @@ -2041,7 +2041,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc lockdep_assert_held(&cpu_base->lock); debug_hrtimer_deactivate(timer); - base->running = timer; + WRITE_ONCE(base->running, timer); /* * Separate the ->running assignment from the ->is_queued assignment. @@ -2100,7 +2100,7 @@ static void __run_hrtimer(struct hrtimer_cpu_base *cpu_base, struct hrtimer_cloc raw_write_seqcount_barrier(&base->seq); WARN_ON_ONCE(base->running != timer); - base->running = NULL; + WRITE_ONCE(base->running, NULL); } static void __hrtimer_run_queues(struct hrtimer_cpu_base *cpu_base, ktime_t now, From d190aa209416f6d6d7bdaffffaaab2af5d3ccdec Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Fri, 5 Jun 2026 12:56:31 +0200 Subject: [PATCH 1050/2286] rust: alloc: allow different error types in `KBox::pin_slice` Previously, `KBox::pin_slice` required the initializer error type to match the return error type via `E: From`. This prevented using infallible initializers like `new_mutex!` inside `pin_slice`, because `Infallible` does not implement `From`. Introduce a separate type parameter `E2` for the initializer error type and require `E: From` and `E: From` instead. This allows the initializer to return a different error type that can be converted into the final error type, enabling use of infallible pin initializers in fallible allocation contexts. Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/.E2.9C.94.20Constructing.20Mutex.20from.20PinInit.3CT.2C.20Error.3E Co-developed-by: Benno Lossin Signed-off-by: Benno Lossin Signed-off-by: Andreas Hindborg Link: https://patch.msgid.link/20260605-pin-slice-init-v2-1-b197e84040ba@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/alloc/kbox.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rust/kernel/alloc/kbox.rs b/rust/kernel/alloc/kbox.rs index c63d6acdbb6f90..2c929eb1c3d65f 100644 --- a/rust/kernel/alloc/kbox.rs +++ b/rust/kernel/alloc/kbox.rs @@ -345,7 +345,7 @@ where /// } /// /// // Allocate a boxed slice of 10 `Example`s. - /// let s = KBox::pin_slice( + /// let s = KBox::pin_slice::<_, _, Error, _>( /// | _i | Example::new(), /// 10, /// GFP_KERNEL @@ -355,15 +355,16 @@ where /// assert_eq!(s[3].d.lock().a, 20); /// # Ok::<(), Error>(()) /// ``` - pub fn pin_slice( + pub fn pin_slice( mut init: Func, len: usize, flags: Flags, ) -> Result>, E> where Func: FnMut(usize) -> Item, - Item: PinInit, + Item: PinInit, E: From, + E: From, { let mut buffer = super::Vec::::with_capacity(len, flags)?; for i in 0..len { From 6e8339118040f24ebcc21db22c9c1b7398b7e39e Mon Sep 17 00:00:00 2001 From: Andreas Hindborg Date: Tue, 29 Sep 2026 17:18:24 +0200 Subject: [PATCH 1051/2286] rust: alloc: add `NumaNode::id()` accessor `NumaNode` wraps an `i32` but offers no way to read it back. Add a trivial `id()` getter so callers that need to pass the node id to a C binding can recover the underlying `c_int` without reaching into the private field. Reviewed-by: Alice Ryhl Reviewed-by: Gary Guo Signed-off-by: Andreas Hindborg Link: https://patch.msgid.link/20260929-numa-node-id-v2-1-fdd2c0a49412@kernel.org Signed-off-by: Danilo Krummrich --- rust/kernel/alloc.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/kernel/alloc.rs b/rust/kernel/alloc.rs index 21067bde686078..68d80f3fa6c865 100644 --- a/rust/kernel/alloc.rs +++ b/rust/kernel/alloc.rs @@ -130,6 +130,12 @@ impl NumaNode { } Ok(Self(node)) } + + /// Returns the raw NUMA node identifier as an `i32`. + #[inline] + pub fn id(&self) -> i32 { + self.0 + } } /// Specify necessary constant to pass the information to Allocator that the caller doesn't care From 1159ad0a6aaa414dc726a33ccdcb702752d1b8eb Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:25:22 -0700 Subject: [PATCH 1052/2286] timers: Mark racy updates to hlist_node::pprev field hlist_unhashed_lockless() locklessly samples the hlist_node structure's pprev field, but detach_timer(), hlist_move_list(), and hlist_splice_init() all use plain C-language stores to update this field, despite the "The READ_ONCE() is paired with the various WRITE_ONCE() in hlist helpers that are defined below" in the hlist_unhashed_lockless() header comment. Therefore use WRITE_ONCE() for these hlist_node::pprev updates. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919002523.3133928-1-paulmck@kernel.org --- include/linux/list.h | 6 +++--- kernel/time/timer.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/linux/list.h b/include/linux/list.h index 77fb62f79928ac..e3753695e76c3c 100644 --- a/include/linux/list.h +++ b/include/linux/list.h @@ -1172,7 +1172,7 @@ static inline void hlist_move_list(struct hlist_head *old, { new->first = old->first; if (new->first) - new->first->pprev = &new->first; + WRITE_ONCE(new->first->pprev, &new->first); old->first = NULL; } @@ -1189,10 +1189,10 @@ static inline void hlist_splice_init(struct hlist_head *from, struct hlist_head *to) { if (to->first) - to->first->pprev = &last->next; + WRITE_ONCE(to->first->pprev, &last->next); last->next = to->first; to->first = from->first; - from->first->pprev = &to->first; + WRITE_ONCE(from->first->pprev, &to->first); from->first = NULL; } diff --git a/kernel/time/timer.c b/kernel/time/timer.c index ae9abf14688e01..42afdcb229d8bd 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -890,7 +890,7 @@ static inline void detach_timer(struct timer_list *timer, bool clear_pending) __hlist_del(entry); if (clear_pending) - entry->pprev = NULL; + WRITE_ONCE(entry->pprev, NULL); entry->next = LIST_POISON2; } From bb41ece16463b76b4d73fc47e6648fd823236765 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 18 Sep 2026 17:25:23 -0700 Subject: [PATCH 1053/2286] timers/migration: Mark racy updates to tmigr_event::ignore field The tmigr_event structure's ignore field is sometimes accessed locklessly, but the __tmigr_cpu_activate(), tmigr_cpu_new_timer(), and __tmigr_cpu_deactivate() functions do not mark accesses to this field. Therefore, use READ_ONCE() for the tmigr_cpu_new_timer() function's lockless load and WRITE_ONCE() for the __tmigr_cpu_activate() and __tmigr_cpu_deactivate() functions' stores. KCSAN located this issue. Signed-off-by: Paul E. McKenney Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260919002523.3133928-2-paulmck@kernel.org --- kernel/time/timer_migration.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/time/timer_migration.c b/kernel/time/timer_migration.c index 059d43355e650f..f920e73fff5112 100644 --- a/kernel/time/timer_migration.c +++ b/kernel/time/timer_migration.c @@ -715,7 +715,7 @@ static void __tmigr_cpu_activate(struct tmigr_cpu *tmc) trace_tmigr_cpu_active(tmc); - tmc->cpuevt.ignore = true; + WRITE_ONCE(tmc->cpuevt.ignore, true); WRITE_ONCE(tmc->wakeup, KTIME_MAX); walk_groups(&tmigr_active_up, &data, tmc); @@ -1258,7 +1258,7 @@ u64 tmigr_cpu_new_timer(u64 nextexp) ret = READ_ONCE(tmc->wakeup); if (nextexp != KTIME_MAX) { if (nextexp != tmc->cpuevt.nextevt.expires || - tmc->cpuevt.ignore) { + READ_ONCE(tmc->cpuevt.ignore)) { ret = tmigr_new_timer(tmc, nextexp); /* * Make sure the reevaluation of timers in idle path @@ -1362,7 +1362,7 @@ static u64 __tmigr_cpu_deactivate(struct tmigr_cpu *tmc, u64 nextexp) * or CPU goes offline. */ if (nextexp != KTIME_MAX) - tmc->cpuevt.ignore = false; + WRITE_ONCE(tmc->cpuevt.ignore, false); walk_groups(&tmigr_inactive_up, &data, tmc); return data.firstexp; From bc5b66c300b87544e6861b8dff8c45958603cba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 3 Sep 2026 09:11:47 +0200 Subject: [PATCH 1054/2286] timekeeping: Use READ_ONCE/WRITE_ONCE() for ktime_sec to prevent tearing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timekeeper update path uses a bulk memcpy() to synchronize the timekeeper structure, which is not guaranteed to be atomic. This allows for torn reads in ktime_get_seconds() which bypasses the sequence counter protection for performance. To prevent reading a torn ktime_sec value, enforce atomic-like access by using WRITE_ONCE() for the critical field before the bulk memcpy() in timekeeping_update_from_shadow(). Correspondingly, use READ_ONCE() in ktime_get_seconds() to ensure a fresh, consistent load from memory. The same was done for xtime_sec and ktime_get_real_seconds() in commit d7fc133bf91f ("timekeeping: Use READ_ONCE/WRITE_ONCE() for xtime_sec to prevent tearing"). Signed-off-by: Thomas Weißschuh (Schneider Electric) Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260903-timekeeping-ktime_sec-v1-1-b33017536aa9@linutronix.de --- kernel/time/timekeeping.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c index ea2e6e55f37bfd..d54c4d303db628 100644 --- a/kernel/time/timekeeping.c +++ b/kernel/time/timekeeping.c @@ -861,8 +861,10 @@ static void timekeeping_update_from_shadow(struct tk_data *tkd, unsigned int act * * Write xtime_sec first so that even if the memcpy() tears the store * data integrity is provided for ktime_get_real_seconds(). + * The same goes for ktime_sec and ktime_get_seconds(). */ WRITE_ONCE(tkd->timekeeper.xtime_sec, tk->xtime_sec); + WRITE_ONCE(tkd->timekeeper.ktime_sec, tk->ktime_sec); memcpy(&tkd->timekeeper, tk, sizeof(*tk)); write_seqcount_end(&tkd->seq); } @@ -1169,7 +1171,7 @@ time64_t ktime_get_seconds(void) struct timekeeper *tk = &tk_core.timekeeper; WARN_ON(timekeeping_suspended); - return tk->ktime_sec; + return READ_ONCE(tk->ktime_sec); } EXPORT_SYMBOL_GPL(ktime_get_seconds); From 2927f7ca78448781e11a4623d347cd653c28adf6 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 25 Sep 2026 18:57:01 +0200 Subject: [PATCH 1055/2286] time: Prevent time64_to_tm() day truncation on 32-bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit time64_to_tm() accepts a 64-bit seconds value, but stores the quotient in long. On a 32-bit kernel the day count therefore wraps when it reaches 2^31, even though the corresponding year remains representable in struct tm. This is reachable when formatting externally supplied timestamps. For example, NILFS recovery copies the little-endian 64-bit ss_create field from an on-disk segment summary into time64_t. Its sysfs attributes then print that value with %ptTs, whose formatter calls time64_to_tm(). A corrupted image can consequently produce an architecture-dependent printed date. Keep the day quotient in s64 while leaving the seconds-within-day remainder as long. Use div_s64_rem() for the weekday calculation so 32-bit builds do not require compiler runtime division helpers. Fixes: e6c2682a1da3 ("time: Add time64_to_tm()") Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260925165702.51677-2-kmehltretter@gmail.com --- kernel/time/timeconv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/time/timeconv.c b/kernel/time/timeconv.c index 59b922c826e771..aed3af950fa0b2 100644 --- a/kernel/time/timeconv.c +++ b/kernel/time/timeconv.c @@ -49,8 +49,9 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result) u32 u32tmp, day_of_century, year_of_century, day_of_year, month, day; u64 u64tmp, udays, century, year; bool is_Jan_or_Feb, is_leap_year; - long days, rem; int remainder; + long rem; + s64 days; days = div_s64_rem(totalsecs, SECS_PER_DAY, &remainder); rem = remainder; @@ -70,7 +71,8 @@ void time64_to_tm(time64_t totalsecs, int offset, struct tm *result) result->tm_sec = rem % 60; /* January 1, 1970 was a Thursday. */ - result->tm_wday = (4 + days) % 7; + div_s64_rem(days + 4, 7, &remainder); + result->tm_wday = remainder; if (result->tm_wday < 0) result->tm_wday += 7; From 3945c4a3ea151f777b4b098ebeecd2f85c20244f Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 25 Sep 2026 18:57:02 +0200 Subject: [PATCH 1056/2286] time/kunit: Add time64_to_tm() case beyond 32-bit day count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing date range test covers +/-80,000 years around the epoch, far below the day count at which a 32-bit long wraps (2^31 days, roughly 5.88 million years). Add a case at exactly 2^31 days after the epoch. Without the preceding fix a 32-bit kernel returns tm_year=-5879541 and wrong month, day, day-of-year and weekday; with it the result matches 64-bit kernels. Signed-off-by: Karl Mehltretter Signed-off-by: Thomas Gleixner Assisted-by: LLM Reviewed-by: Thomas Weißschuh Link: https://patch.msgid.link/20260925165702.51677-3-kmehltretter@gmail.com --- kernel/time/time_test.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/kernel/time/time_test.c b/kernel/time/time_test.c index 1b99180da28818..8b718767b3bafd 100644 --- a/kernel/time/time_test.c +++ b/kernel/time/time_test.c @@ -87,8 +87,24 @@ static void time64_to_tm_test_date_range(struct kunit *test) } } +static void time64_to_tm_test_wide_day_count(struct kunit *test) +{ + /* 2^31 days: the first count that does not fit in a 32-bit long. */ + time64_t timestamp = (1LL << 31) * 86400; + struct tm result; + + time64_to_tm(timestamp, 0, &result); + + KUNIT_EXPECT_EQ(test, result.tm_year, 5879680); + KUNIT_EXPECT_EQ(test, result.tm_mon, 6); + KUNIT_EXPECT_EQ(test, result.tm_mday, 12); + KUNIT_EXPECT_EQ(test, result.tm_yday, 193); + KUNIT_EXPECT_EQ(test, result.tm_wday, 6); +} + static struct kunit_case time_test_cases[] = { KUNIT_CASE_SLOW(time64_to_tm_test_date_range), + KUNIT_CASE(time64_to_tm_test_wide_day_count), {} }; From a252cb93e1568d9f0754b9b2ac4829d896daa14e Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Thu, 17 Sep 2026 04:52:31 +0300 Subject: [PATCH 1057/2286] selftests: timers: Count what tick is worth in the drift estimate raw_skew measures the drift between CLOCK_MONOTONIC and CLOCK_MONOTONIC_RAW and compares it against the adjustment adjtimex() reports, but for the latter it reads only tx.freq. The kernel takes the tick value into the adjustment as well, worth a microsecond of the tick length per unit, a hundred ppm each, and a time synchronisation daemon does put the coarse part of its correction there. Where it does, the two numbers cannot meet: # Estimating clock drift: -51.724(est) 48.277(act) [FAILED] On the machine measured, chronyd held freq at 48.278 ppm with tick at 9999, so the correction really applied is -51.722 ppm, which is what the clocks show. The skip for an externally adjusted clock does not catch this: the clock is steady, the offset is zero, and neither freq nor tick moves during the run. Count what tick is worth alongside freq. On the same machine: # Estimating clock drift: -51.724(est) -51.723(act) [OK] Signed-off-by: Eva Kurchatova Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260917015237.2462954-1-eva.kurchatova@virtuozzo.com --- tools/testing/selftests/timers/raw_skew.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/testing/selftests/timers/raw_skew.c b/tools/testing/selftests/timers/raw_skew.c index 0c87a8fb0d7f9e..dfca3e2d50d2ea 100644 --- a/tools/testing/selftests/timers/raw_skew.c +++ b/tools/testing/selftests/timers/raw_skew.c @@ -91,6 +91,7 @@ int main(int argc, char **argv) { struct timespec mon, raw, start, end; long long delta1, delta2, interval, eppm, ppm; + long tick_nom; struct timex tx1, tx2; setbuf(stdout, NULL); @@ -129,6 +130,17 @@ int main(int argc, char **argv) /* Avg the two actual freq samples adjtimex gave us */ ppm = (long long)(tx1.freq + tx2.freq) * 1000 / 2; ppm = shift_right(ppm, 16); + + /* + * The tick value holds the coarse part of the adjustment, a + * microsecond of the tick length per unit, and time sync daemons + * do put part of their correction there. What it is worth has to + * be counted in as well, or a clock disciplined through it looks + * off by a hundred ppm a unit against what the two clocks show. + */ + tick_nom = USEC_PER_SEC / sysconf(_SC_CLK_TCK); + ppm += (long long)(tx1.tick + tx2.tick - 2 * tick_nom) * + (USEC_PER_SEC / tick_nom) * 1000 / 2; printf(" %lld.%i(act)", ppm/1000, abs((int)(ppm%1000))); if (llabs(eppm - ppm) > 1000) { From b03638013add0609d35aaa8291548dea92cb9484 Mon Sep 17 00:00:00 2001 From: Eva Kurchatova Date: Thu, 17 Sep 2026 04:52:32 +0300 Subject: [PATCH 1058/2286] selftests: timers: Measure the CPU timers on the clock they count Commit b00385b8d081 ("selftests/posix_timers: Use CLOCK_THREAD_CPUTIME_ID for ITIMER_PROF measurements") gave ITIMER_PROF the clock it counts, as measuring a CPU timer against the wall says nothing on a machine where the task does not have a CPU to itself. The other CPU timers are still measured against CLOCK_REALTIME and fail the same way. With four busy loops on the two CPUs of a test machine: not ok 2 ITIMER_VIRTUAL not ok 5 timer_create() per CLOCK_THREAD_CPUTIME_ID not ok 6 timer_create() per CLOCK_PROCESS_CPUTIME_ID ITIMER_VIRTUAL counts the time the task spends in userspace, so take CLOCK_THREAD_CPUTIME_ID for it as well; the loop it runs stays in userspace, so what that clock adds for system time is far inside the half a second the check allows. check_timer_create() arms its timer on the clock it is given, so measure on that one. The test then passes whole on that machine, 19 of 19, busy or idle. Signed-off-by: Eva Kurchatova Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260917015237.2462954-2-eva.kurchatova@virtuozzo.com --- tools/testing/selftests/timers/posix_timers.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c index a92d4b957747ea..52b7289e1450c6 100644 --- a/tools/testing/selftests/timers/posix_timers.c +++ b/tools/testing/selftests/timers/posix_timers.c @@ -113,9 +113,10 @@ static void check_itimer(int which, const char *name) done = 0; - if (which == ITIMER_VIRTUAL) + if (which == ITIMER_VIRTUAL) { + clock_id = CLOCK_THREAD_CPUTIME_ID; signal(SIGVTALRM, sig_handler); - else if (which == ITIMER_PROF) { + } else if (which == ITIMER_PROF) { clock_id = CLOCK_THREAD_CPUTIME_ID; signal(SIGPROF, sig_handler); } @@ -148,7 +149,7 @@ static void check_timer_create(int which) struct itimerspec val = { .it_value.tv_sec = DELAY, }; - int clock_id = CLOCK_REALTIME; + int clock_id = which; timer_t id; done = 0; From 348f54c435bf3177baa78201952810b255d44b30 Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Tue, 25 Aug 2026 14:46:05 +0800 Subject: [PATCH 1059/2286] selftests/timers: clocksource-switch: Fix unchecked open()/read() get_clocksources() and get_cur_clocksource() open and read the clocksource sysfs files without checking the return values and without NUL-terminating the buffer. If open() fails, read() is called on fd -1 and returns -1, which is stored in a size_t as SIZE_MAX. The token-scanning loop in get_clocksources() then walks past the uninitialized stack buffer, reading and writing out of bounds. Even on success the buffer is not NUL-terminated, so a fully read buffer makes the inner scan run past the data, and get_cur_clocksource() lets change_clocksource() call strlen() on a non-terminated buffer. Check the open()/read() results, use ssize_t for the read length, NUL-terminate the buffer, and close the fd in get_cur_clocksource(). Fixes: 7290ce1423c3 ("selftests/timers: Add clocksource-switch test from timetest suite") Signed-off-by: Qingshuang Fu Signed-off-by: Thomas Gleixner Acked-by: John Stultz Link: https://patch.msgid.link/20260825064605.472889-1-fffsqian@163.com --- .../selftests/timers/clocksource-switch.c | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/testing/selftests/timers/clocksource-switch.c b/tools/testing/selftests/timers/clocksource-switch.c index db62a764c29e65..2e86f56d953ebb 100644 --- a/tools/testing/selftests/timers/clocksource-switch.c +++ b/tools/testing/selftests/timers/clocksource-switch.c @@ -40,16 +40,23 @@ int get_clocksources(char list[][30]) { int fd, i; - size_t size; + ssize_t size; char buf[512]; char *head, *tmp; fd = open("/sys/devices/system/clocksource/clocksource0/available_clocksource", O_RDONLY); + if (fd < 0) + return 0; - size = read(fd, buf, 512); + size = read(fd, buf, sizeof(buf) - 1); close(fd); + if (size <= 0) + return 0; + + buf[size] = '\0'; + for (i = 0; i < 10; i++) list[i][0] = '\0'; @@ -74,11 +81,21 @@ int get_clocksources(char list[][30]) int get_cur_clocksource(char *buf, size_t size) { + ssize_t len; int fd; fd = open("/sys/devices/system/clocksource/clocksource0/current_clocksource", O_RDONLY); + if (fd < 0) + return -1; + + len = read(fd, buf, size - 1); + + close(fd); + + if (len <= 0) + return -1; - size = read(fd, buf, size); + buf[len] = '\0'; return 0; } From 14e2c1f5431a03478ed4bbf2e72298c769f375ac Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Mon, 28 Sep 2026 19:26:57 +0200 Subject: [PATCH 1060/2286] kbuild: Mark usr_gen_init_cpio as no-dot-config-target Let usr/gen_init_cpio be built also if there is no kernel configuration. Askar Safin reported that gen_init_cpio may be of use before any kernel configuration is available. Add usr_gen_init_cpio to list of targets that do not require a .config file. Reported-by: Askar Safin Closes: https://lore.kernel.org/all/20260220191150.244006-1-safinaskar@gmail.com Fixes: f8e05c10631e ("kbuild: Add top-level target for building gen_init_cpio") Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260928-move-gen_init_cpio-to-scripts-v4-1-f437a33c40ac@kernel.org Signed-off-by: Nathan Chancellor --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index f6745d723f6dd8..c52fffe8042f33 100644 --- a/Makefile +++ b/Makefile @@ -298,6 +298,7 @@ no-dot-config-targets := $(clean-targets) \ %asm-generic kernelversion %src-pkg dt_binding_check \ dt_style_selftest \ outputmakefile rustavailable rustfmt rustfmtcheck \ + usr_gen_init_cpio \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name From 33bef82263082e81406ef5662de2900e24038a78 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Mon, 28 Sep 2026 19:26:58 +0200 Subject: [PATCH 1061/2286] kbuild: Move gen_init_cpio and gen_initramfs.sh to scripts/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_init_cpio and gen_initramfs.sh are part of kbuild and required for all kernel builds w/ CONFIG_BLK_DEV_INITRD. Move both to scripts/ to be more clear about their importance. Link: https://lore.kernel.org/all/aSdrCFkUQup3qb-q@derry.ads.avm.de/ Reviewed-by: Thomas Weißschuh Reviewed-by: Nathan Chancellor Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260928-move-gen_init_cpio-to-scripts-v4-2-f437a33c40ac@kernel.org Signed-off-by: Nathan Chancellor --- .../early-userspace/early_userspace_support.rst | 16 ++++++++-------- .../filesystems/ramfs-rootfs-initramfs.rst | 10 +++++----- MAINTAINERS | 1 + Makefile | 10 +++++----- scripts/.gitignore | 1 + scripts/Makefile | 3 +++ scripts/Makefile.package | 2 +- {usr => scripts}/gen_init_cpio.c | 0 {usr => scripts}/gen_initramfs.sh | 4 ++-- scripts/remove-stale-files | 2 ++ .../sbom/tests/cmd_graph/test_savedcmd_parser.py | 2 +- tools/testing/selftests/kho/vmtest.sh | 2 +- tools/testing/selftests/liveupdate/vmtest.sh | 4 ++-- tools/testing/selftests/nolibc/Makefile.nolibc | 2 +- usr/.gitignore | 4 +++- usr/Kconfig | 2 +- usr/Makefile | 4 +--- 17 files changed, 38 insertions(+), 31 deletions(-) rename {usr => scripts}/gen_init_cpio.c (100%) rename {usr => scripts}/gen_initramfs.sh (97%) diff --git a/Documentation/driver-api/early-userspace/early_userspace_support.rst b/Documentation/driver-api/early-userspace/early_userspace_support.rst index 60d1e1bc9413bc..1070f82e35788f 100644 --- a/Documentation/driver-api/early-userspace/early_userspace_support.rst +++ b/Documentation/driver-api/early-userspace/early_userspace_support.rst @@ -55,9 +55,9 @@ user root (0). INITRAMFS_ROOT_GID can be set to a group ID that needs to be mapped to group root (0). A source file must be directives in the format required by the -usr/gen_init_cpio utility (run 'usr/gen_init_cpio -h' to get the +scripts/gen_init_cpio utility (run 'scripts/gen_init_cpio -h' to get the file format). The directives in the file will be passed directly to -usr/gen_init_cpio. +scripts/gen_init_cpio. When a combination of directories and files are specified then the initramfs image will be an aggregate of all of them. In this way a user @@ -69,17 +69,17 @@ early userspace image can be built by an unprivileged user. As a technical note, when directories and files are specified, the entire CONFIG_INITRAMFS_SOURCE is passed to -usr/gen_initramfs.sh. This means that CONFIG_INITRAMFS_SOURCE +scripts/gen_initramfs.sh. This means that CONFIG_INITRAMFS_SOURCE can really be interpreted as any legal argument to gen_initramfs.sh. If a directory is specified as an argument then the contents are scanned, uid/gid translation is performed, and -usr/gen_init_cpio file directives are output. If a file is -specified as an argument to usr/gen_initramfs.sh then the +scripts/gen_init_cpio file directives are output. If a file is +specified as an argument to scripts/gen_initramfs.sh then the contents of the file are simply copied to the output. All of the output directives from directory scanning and file contents copying are -processed by usr/gen_init_cpio. +processed by scripts/gen_init_cpio. -See also 'usr/gen_initramfs.sh -h'. +See also 'scripts/gen_initramfs.sh -h'. Where's this all leading? ========================= @@ -142,7 +142,7 @@ b) some device and filesystem drivers built as modules and stored in an c) using initramfs. The call to prepare_namespace() must be skipped. This means that a binary must do all the work. Said binary can be stored - into initramfs either via modifying usr/gen_init_cpio.c or via the new + into initramfs either via modifying scripts/gen_init_cpio.c or via the new initrd format, an cpio archive. It must be called "/init". This binary is responsible to do all the things prepare_namespace() would do. diff --git a/Documentation/filesystems/ramfs-rootfs-initramfs.rst b/Documentation/filesystems/ramfs-rootfs-initramfs.rst index 165117a721ceee..4263fe06115de1 100644 --- a/Documentation/filesystems/ramfs-rootfs-initramfs.rst +++ b/Documentation/filesystems/ramfs-rootfs-initramfs.rst @@ -155,7 +155,7 @@ specification such as the following example:: dir /mnt 755 0 0 file /init initramfs/init.sh 755 0 0 -Run "usr/gen_init_cpio" (after the kernel build) to get a usage message +Run "scripts/gen_init_cpio" (after the kernel build) to get a usage message documenting the above file format. One advantage of the configuration file is that root access is not required to @@ -167,9 +167,9 @@ Documentation/driver-api/early-userspace/early_userspace_support.rst for more de The kernel does not depend on external cpio tools. If you specify a directory instead of a configuration file, the kernel's build infrastructure creates a configuration file from that directory (usr/Makefile calls -usr/gen_initramfs.sh), and proceeds to package up that directory -using the config file (by feeding it to usr/gen_init_cpio, which is created -from usr/gen_init_cpio.c). The kernel's build-time cpio creation code is +scripts/gen_initramfs.sh), and proceeds to package up that directory +using the config file (by feeding it to scripts/gen_init_cpio, which is created +from scripts/gen_init_cpio.c). The kernel's build-time cpio creation code is entirely self-contained, and the kernel's boot-time extractor is also (obviously) self-contained. @@ -313,7 +313,7 @@ the above threads) is: 2) The cpio archive format chosen by the kernel is simpler and cleaner (and thus easier to create and parse) than any of the (literally dozens of) various tar archive formats. The complete initramfs archive format is - explained in buffer-format.rst, created in usr/gen_init_cpio.c, and + explained in buffer-format.rst, created in scripts/gen_init_cpio.c, and extracted in init/initramfs.c. All three together come to less than 26k total of human-readable text. diff --git a/MAINTAINERS b/MAINTAINERS index 6215fcb0777052..7704147dd97527 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14138,6 +14138,7 @@ F: scripts/basic/ F: scripts/clang-tools/ F: scripts/container F: scripts/dummy-tools/ +F: scripts/gen_init* F: scripts/include/ F: scripts/install.sh F: scripts/mk* diff --git a/Makefile b/Makefile index c52fffe8042f33..c496d16391035e 100644 --- a/Makefile +++ b/Makefile @@ -298,7 +298,7 @@ no-dot-config-targets := $(clean-targets) \ %asm-generic kernelversion %src-pkg dt_binding_check \ dt_style_selftest \ outputmakefile rustavailable rustfmt rustfmtcheck \ - usr_gen_init_cpio \ + scripts_gen_init_cpio \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name @@ -1532,9 +1532,9 @@ ifdef CONFIG_HEADERS_INSTALL prepare: headers endif -PHONY += usr_gen_init_cpio -usr_gen_init_cpio: scripts_basic - $(Q)$(MAKE) $(build)=usr usr/gen_init_cpio +PHONY += scripts_gen_init_cpio +scripts_gen_init_cpio: scripts_basic + $(Q)$(MAKE) $(build)=scripts scripts/gen_init_cpio PHONY += scripts_unifdef scripts_unifdef: scripts_basic @@ -1835,7 +1835,7 @@ distclean: mrproper # Packaging of the kernel to various formats # --------------------------------------------------------------------------- -modules-cpio-pkg: usr_gen_init_cpio +modules-cpio-pkg: scripts_gen_init_cpio %src-pkg: FORCE $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.package $@ diff --git a/scripts/.gitignore b/scripts/.gitignore index 4215c2208f7e41..70045654391074 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only /asn1_compiler +/gen_init_cpio /gen_packed_field_checks /generate_rust_target /insert-sys-cert diff --git a/scripts/Makefile b/scripts/Makefile index 3434a82a119f09..3d50805b0885a8 100644 --- a/scripts/Makefile +++ b/scripts/Makefile @@ -53,6 +53,9 @@ ifdef CONFIG_BUILDTIME_MCOUNT_SORT HOSTCFLAGS_sorttable.o += -DMCOUNT_SORT_ENABLED endif +hostprogs += gen_init_cpio +always-$(CONFIG_BLK_DEV_INITRD) += gen_init_cpio + # The following programs are only built on demand hostprogs += unifdef gen_packed_field_checks diff --git a/scripts/Makefile.package b/scripts/Makefile.package index 6d36786ba31cef..948ad5b679d110 100644 --- a/scripts/Makefile.package +++ b/scripts/Makefile.package @@ -198,7 +198,7 @@ tar%-pkg: linux-$(KERNELRELEASE)-$(ARCH).tar.% FORCE $(Q)$(MAKE) -f $(srctree)/Makefile INSTALL_MOD_PATH=$@/$(INSTALL_MOD_PATH) modules_install quiet_cmd_cpio = CPIO $@ - cmd_cpio = $(CONFIG_SHELL) $(srctree)/usr/gen_initramfs.sh -o $@ $< + cmd_cpio = $(CONFIG_SHELL) $(srctree)/scripts/gen_initramfs.sh -o $@ $< modules-$(KERNELRELEASE)-$(ARCH).cpio: .tmp_modules_cpio $(call cmd,cpio) diff --git a/usr/gen_init_cpio.c b/scripts/gen_init_cpio.c similarity index 100% rename from usr/gen_init_cpio.c rename to scripts/gen_init_cpio.c diff --git a/usr/gen_initramfs.sh b/scripts/gen_initramfs.sh similarity index 97% rename from usr/gen_initramfs.sh rename to scripts/gen_initramfs.sh index 7eba2fddf0ef2e..9c6168a9338d44 100755 --- a/usr/gen_initramfs.sh +++ b/scripts/gen_initramfs.sh @@ -6,7 +6,7 @@ # # Generate a cpio packed initramfs. It uses gen_init_cpio to generate # the cpio archive. -# This script assumes that gen_init_cpio is located in usr/ directory +# This script assumes that gen_init_cpio is located in scripts/ directory # error out on errors set -e @@ -247,4 +247,4 @@ done # If output_file is set we will generate cpio archive # we are careful to delete tmp files -usr/gen_init_cpio $output $timestamp $cpio_list +scripts/gen_init_cpio $output $timestamp $cpio_list diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files index 6e39fa8540df2f..e46337e4bfb8f8 100755 --- a/scripts/remove-stale-files +++ b/scripts/remove-stale-files @@ -26,3 +26,5 @@ rm -f scripts/selinux/genheaders/genheaders rm -f *.spec rm -f lib/test_fortify.log + +rm -f usr/gen_init_cpio diff --git a/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py index d7776f072e0342..93787a1cd1566d 100644 --- a/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py +++ b/scripts/sbom/tests/cmd_graph/test_savedcmd_parser.py @@ -338,7 +338,7 @@ def test_xen_hypercalls(self): # gen_initramfs.sh command tests def test_gen_initramfs(self): - cmd = "sh ../usr/gen_initramfs.sh -o usr/initramfs_data.cpio -l usr/.initramfs_data.cpio.d ../usr/default_cpio_list" + cmd = "sh ../scripts/gen_initramfs.sh -o usr/initramfs_data.cpio -l usr/.initramfs_data.cpio.d ../usr/default_cpio_list" expected = "../usr/default_cpio_list" self._assert_parsing(cmd, expected) diff --git a/tools/testing/selftests/kho/vmtest.sh b/tools/testing/selftests/kho/vmtest.sh index 0014bd76e88de6..1e86ffcc3a79e6 100755 --- a/tools/testing/selftests/kho/vmtest.sh +++ b/tools/testing/selftests/kho/vmtest.sh @@ -100,7 +100,7 @@ file /init $tmp_dir/init 0755 0 0 file /kernel $kernel 0644 0 0 EOF - "$build_dir/usr/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd" + "$build_dir/scripts/gen_init_cpio" "$tmp_dir/cpio_list" > "$initrd" } function run_qemu() { diff --git a/tools/testing/selftests/liveupdate/vmtest.sh b/tools/testing/selftests/liveupdate/vmtest.sh index 64fd2ab55b765f..672a9e9d685a55 100755 --- a/tools/testing/selftests/liveupdate/vmtest.sh +++ b/tools/testing/selftests/liveupdate/vmtest.sh @@ -152,7 +152,7 @@ file /test_binary $workspace_dir/test_binary 0755 0 0 EOF # Generate inner_initrd.cpio - "$build_dir/usr/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio" + "$build_dir/scripts/gen_init_cpio" "$workspace_dir/cpio_list_inner" > "$workspace_dir/inner_initrd.cpio" cat > "$workspace_dir/cpio_list" < "$initrd" + "$build_dir/scripts/gen_init_cpio" "$workspace_dir/cpio_list" > "$initrd" } function run_qemu() { diff --git a/tools/testing/selftests/nolibc/Makefile.nolibc b/tools/testing/selftests/nolibc/Makefile.nolibc index f70c8dfca01869..380200b152e4de 100644 --- a/tools/testing/selftests/nolibc/Makefile.nolibc +++ b/tools/testing/selftests/nolibc/Makefile.nolibc @@ -295,7 +295,7 @@ run-user: nolibc-test $(Q)$(REPORT) $(CURDIR)/run.out initramfs.cpio: kernel nolibc-test - $(QUIET_GEN)echo 'file /init nolibc-test 755 0 0' | $(objtree)/usr/gen_init_cpio - > initramfs.cpio + $(QUIET_GEN)echo 'file /init nolibc-test 755 0 0' | $(objtree)/scripts/gen_init_cpio - > initramfs.cpio initramfs: nolibc-test $(QUIET_MKDIR)mkdir -p initramfs diff --git a/usr/.gitignore b/usr/.gitignore index 8996e7a889024f..13bf7a38355ce5 100644 --- a/usr/.gitignore +++ b/usr/.gitignore @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only -/gen_init_cpio /initramfs_data.cpio /initramfs_inc_data + +# Keep around until cleanup in scripts/remove-stale-files +/gen_init_cpio diff --git a/usr/Kconfig b/usr/Kconfig index 9279a2893ab0ee..f87dd4bac41838 100644 --- a/usr/Kconfig +++ b/usr/Kconfig @@ -13,7 +13,7 @@ config INITRAMFS_SOURCE to be used as an initramfs image. Directories should contain a filesystem layout to be included in the initramfs image. Files should contain entries according to the format described by the - "usr/gen_init_cpio" program in the kernel tree. + "scripts/gen_init_cpio" program in the kernel tree. When multiple directories and files are specified then the initramfs image will be the aggregate of all of them. diff --git a/usr/Makefile b/usr/Makefile index e8f42478a0b765..5b97ee66c00a02 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -16,8 +16,6 @@ obj-$(CONFIG_BLK_DEV_INITRD) := initramfs_data.o $(obj)/initramfs_data.o: $(obj)/initramfs_inc_data -hostprogs := gen_init_cpio - ramfs-input := $(CONFIG_INITRAMFS_SOURCE) cpio-data := @@ -72,7 +70,7 @@ quiet_cmd_initfs = GEN $@ # 2) There are changes in which files are included (added or deleted) # 3) If gen_init_cpio are newer than initramfs_data.cpio # 4) Arguments to gen_initramfs.sh changes -$(obj)/initramfs_data.cpio: $(src)/gen_initramfs.sh $(obj)/gen_init_cpio $(deps_initramfs) FORCE +$(obj)/initramfs_data.cpio: $(srctree)/scripts/gen_initramfs.sh scripts/gen_init_cpio $(deps_initramfs) FORCE $(call if_changed,initfs) endif From 8b1534b6c47b6f85d3eaa1f169d2e298db19de24 Mon Sep 17 00:00:00 2001 From: Qiliang Yuan Date: Mon, 28 Sep 2026 20:54:40 +0800 Subject: [PATCH 1062/2286] quota: fix shrinker scan budget accounting in dqcache_shrink_scan() The dquot cache shrinker's count_objects() callback, dqcache_shrink_count(), reports the number of unused dquot objects via the DQST_FREE_DQUOTS percpu counter (through vfs_pressure_ratio()). do_shrink_slab() reads this count exactly once per invocation and derives a one-shot scan budget (total_scan) from it, then repeatedly calls scan_objects() in fixed-size batches until that budget is exhausted. dqcache_shrink_scan() never updates sc->nr_scanned, even though include/linux/shrinker.h documents that "the callee should track its actual progress" in that field. The function already decrements sc->nr_to_scan in place as it walks free_dquots, one per dquot destroyed, and its while loop exits immediately without touching sc->nr_to_scan at all when free_dquots is already empty. Because sc->nr_scanned defaults to sc->nr_to_scan before every call, do_shrink_slab() always believes a full batch was examined regardless of what actually happened, so it has no way to tell "there was nothing to do this pass" from "a full batch was scanned and none of it was freeable", and keeps calling scan_objects() until the original, one-shot total_scan budget computed from the percpu count is drained, even though free_dquots emptied out long before that budget was used up. Compute sc->nr_scanned from the difference between the nr_to_scan value on entry and the value left behind by the reclaim loop, and return SHRINK_STOP once that difference comes back as zero, since that only happens when free_dquots was already empty and no further calls in this reclaim pass can make progress. Tested on a loop-mounted ext4 image with the quota feature enabled: chown()ing several hundred files to distinct uid:gid pairs to populate allocated dquots, then triggering "echo 2 > /proc/sys/vm/drop_caches" to let them drop to zero references and become reclaimable, while using an ad hoc kprobe pair on dqcache_shrink_scan() (this shrinker has no dedicated tracepoints) to record sc->nr_to_scan on entry and the return value on exit: total scan_objects() calls with calls freed == 0 before this patch 12 5 (42%) after this patch 5 0 The last call in the "after" run returns SHRINK_STOP (0xffffffffffffffff) instead of a fifth zero-progress attempt. This is the same missing sc->nr_scanned accounting already fixed in the analogous ext4 extents_status shrinker and the jbd2 checkpoint shrinker; dqcache_shrink_scan() was found by auditing every shrinker in the tree whose count_objects() reads a percpu_counter (the precondition for count_objects() to report a stale, still-positive value after the reclaimable list has actually drained), while ruling out the much larger set of shrinkers built on the exact list_lru atomic counters, which cannot observe this kind of staleness. Signed-off-by: Qiliang Yuan Link: https://patch.msgid.link/20260928-fix-dquot-shrink-scan-nr-scanned-v1-1-789c661c9dd6@gmail.com Signed-off-by: Jan Kara --- fs/quota/dquot.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c index 204afc5e984bcc..ae631a28d31963 100644 --- a/fs/quota/dquot.c +++ b/fs/quota/dquot.c @@ -809,6 +809,7 @@ static unsigned long dqcache_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) { struct dquot *dquot; + unsigned long orig_nr_to_scan = sc->nr_to_scan; unsigned long freed = 0; spin_lock(&dq_list_lock); @@ -822,6 +823,21 @@ dqcache_shrink_scan(struct shrinker *shrink, struct shrink_control *sc) freed++; } spin_unlock(&dq_list_lock); + + sc->nr_scanned = orig_nr_to_scan - sc->nr_to_scan; + + /* + * DQST_FREE_DQUOTS is a percpu counter, so count_objects() can + * report a stale/approximate value that is still positive even + * though free_dquots is actually empty by the time we get here. + * When that happens sc->nr_scanned comes back 0 and we have + * nothing further to offer this reclaim pass, so tell + * do_shrink_slab() to stop calling us instead of letting it burn + * through its one-shot scan budget against an empty list. + */ + if (sc->nr_scanned == 0) + return SHRINK_STOP; + return freed; } From cd548f83af6f3590d16ac20067831ea51d9fbd3b Mon Sep 17 00:00:00 2001 From: Quchaosheng Date: Mon, 28 Sep 2026 10:36:38 +0800 Subject: [PATCH 1063/2286] Documentation: sched-preemption: Add a SPDX license identifier The file was added without a license identifier. Add one, matching the other files in Documentation/scheduler/. Suggested-by: Jonathan Corbet Assisted-by: LLM Signed-off-by: Quchaosheng Message-ID: <20260928023638.1397038-1-quchaosheng000406@163.com> Signed-off-by: Jonathan Corbet --- Documentation/scheduler/sched-preemption.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/scheduler/sched-preemption.rst b/Documentation/scheduler/sched-preemption.rst index bfa22559c29b19..b48e53587a7afa 100644 --- a/Documentation/scheduler/sched-preemption.rst +++ b/Documentation/scheduler/sched-preemption.rst @@ -1,3 +1,5 @@ +.. SPDX-License-Identifier: GPL-2.0 + ===================== Scheduler preemption ===================== From c88c76e56b710ab5ad28cae63937fd0ca0231d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Sun, 27 Sep 2026 10:23:15 +0300 Subject: [PATCH 1064/2286] docs: timers: hpet: Modernize file references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current HPET documentation contains outdated 'file:' prefixes for source tree references, which breaks proper rendering and hyperlink generation under the modern Sphinx-based documentation ecosystem. Fix this by modernizing the paths using the standard literal backticks formatting. Assisted-by: LLM Signed-off-by: Habil Eren Türker Signed-off-by: Jonathan Corbet Message-ID: <20260927072425.7892-2-habilerenturker@hotmail.com> --- Documentation/timers/hpet.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/timers/hpet.rst b/Documentation/timers/hpet.rst index c9d05d3caaca97..8bb51d9719979e 100644 --- a/Documentation/timers/hpet.rst +++ b/Documentation/timers/hpet.rst @@ -23,8 +23,8 @@ The driver supports detection of HPET driver allocation and initialization of the HPET before the driver module_init routine is called. This enables platform code which uses timer 0 or 1 as the main timer to intercept HPET initialization. An example of this initialization can be found in -arch/x86/kernel/hpet.c. +``arch/x86/kernel/hpet.c``. The driver provides a userspace API which resembles the API found in the RTC driver framework. An example user space program is provided in -file:samples/timers/hpet_example.c +``samples/timers/hpet_example.c`` From b5375eb154041e18be6f93f39e939b1a5e1283bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Habil=20Eren=20T=C3=BCrker?= Date: Sun, 27 Sep 2026 10:23:16 +0300 Subject: [PATCH 1065/2286] docs: timers: hpet: Document userspace API and ioctl commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a dedicated 'Userspace API and ioctl Commands' section to clearly document the supported ioctl interfaces extrapolated from include/uapi/linux/hpet.h, including the layout of struct hpet_info. Assisted-by: LLM Signed-off-by: Habil Eren Türker [jc: fixed some trailing-whitespace gripes] Signed-off-by: Jonathan Corbet Message-ID: <20260927072425.7892-3-habilerenturker@hotmail.com> --- Documentation/timers/hpet.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Documentation/timers/hpet.rst b/Documentation/timers/hpet.rst index 8bb51d9719979e..254d0bb2b802fe 100644 --- a/Documentation/timers/hpet.rst +++ b/Documentation/timers/hpet.rst @@ -28,3 +28,32 @@ initialization. An example of this initialization can be found in The driver provides a userspace API which resembles the API found in the RTC driver framework. An example user space program is provided in ``samples/timers/hpet_example.c`` + +Userspace API and ioctl Commands +================================ + +* ``HPET_IE_ON`` / ``HPET_IE_OFF`` + Enables or disables the interrupt generation for the timer channel. + +* ``HPET_INFO`` + It is used to query the capabilities and current status of the HPET. + When the ioctl call is made, the kernel populates the ``struct hpet_info`` + structure and returns it to userspace: + + .. code-block:: c + + struct hpet_info { + unsigned long hi_ireqfreq; /* Hz */ + unsigned long hi_flags; /* information */ + unsigned short hi_hpet; + unsigned short hi_timer; + }; + +* ``HPET_EPI`` / ``HPET_DPI`` + Enables or disables periodic interrupts. When activated, the timer + triggers continuously at the specified frequency. + +* ``HPET_IRQFREQ`` + Sets the interrupt frequency for the periodic timer. This request + takes an unsigned long argument specifying the desired frequency in Hz. + From 2d72a4c09867a8f9131afc2e705a728f9fba2417 Mon Sep 17 00:00:00 2001 From: Omokefe Emmanuel Onanaroghene Date: Sat, 26 Sep 2026 00:40:54 +0100 Subject: [PATCH 1066/2286] docs: kdoc: parse context_lock_struct() as struct declaration kernel-doc fails to parse struct declarations wrapped in context_lock_struct(), e.g. struct debugfs_cancellation: include/linux/debugfs.h:245 ... error: Cannot parse struct or union! Add a struct transform that expands context_lock_struct() to a plain struct declaration, and apply the struct transforms to the whole declaration before split_struct_proto(), matching what dump_var() and dump_function() already do for their prototypes. The member-only transform in dump_struct() is then redundant. Add a regression test for the debugfs_cancellation declaration. Fixes: 6e530e2e3119 ("debugfs: Make debugfs_cancellation a context lock struct") Signed-off-by: Omokefe Emmanuel Onanaroghene Tested-by: Randy Dunlap Reviewed-by: Aleksandr Loktionov Signed-off-by: Jonathan Corbet Message-ID: <20260925234054.128477-1-emmaonana18@gmail.com> --- tools/lib/python/kdoc/kdoc_parser.py | 11 +++++++---- tools/lib/python/kdoc/xforms_lists.py | 1 + tools/unittests/test_kdoc_parser.py | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/tools/lib/python/kdoc/kdoc_parser.py b/tools/lib/python/kdoc/kdoc_parser.py index d9ad1ddc87dd13..80d4245acd1725 100644 --- a/tools/lib/python/kdoc/kdoc_parser.py +++ b/tools/lib/python/kdoc/kdoc_parser.py @@ -831,6 +831,13 @@ def dump_struct(self, ln, proto, source): # source = source proto = trim_private_members(proto) + + # + # Expand macro-based declarations, like context_lock_struct(), + # into a plain struct/union declaration before splitting it, + # as dump_var() and dump_function() already do. + # + proto = self.xforms.apply("struct", proto) struct_parts = self.split_struct_proto(proto) if not struct_parts: self.emit_msg(ln, f"{proto} error: Cannot parse struct or union!") @@ -841,10 +848,6 @@ def dump_struct(self, ln, proto, source): self.emit_msg(ln, f"expecting prototype for {decl_type} {self.entry.identifier}. " f"Prototype was for {decl_type} {declaration_name} instead") return - # - # Go through the list of members applying all of our transformations. - # - members = self.xforms.apply("struct", members) # # Deal with embedded struct and union members, and drop enums entirely. diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py index 423e2bab766703..15f6f21c7a9edc 100644 --- a/tools/lib/python/kdoc/xforms_lists.py +++ b/tools/lib/python/kdoc/xforms_lists.py @@ -62,6 +62,7 @@ class CTransforms: (CMatch("struct_group_attr"), r"struct { \3+ };"), (CMatch("struct_group_tagged"), r"struct { \3+ };"), (CMatch("__struct_group"), r"struct { \4+ };"), + (CMatch("context_lock_struct"), r"struct \1"), ] #: Transforms for function prototypes. diff --git a/tools/unittests/test_kdoc_parser.py b/tools/unittests/test_kdoc_parser.py index c4a76ed13dbc7d..4baa78ca02b3f2 100755 --- a/tools/unittests/test_kdoc_parser.py +++ b/tools/unittests/test_kdoc_parser.py @@ -418,6 +418,32 @@ def test_with_default_expected(self): """ self.run_test(self.SOURCE, [self.DEFAULT.copy()], self.EXPORTS) +class TestContextLockStruct(GenerateKdocItem): + def test_context_lock_struct_declaration(self): + source = """ + /** + * struct debugfs_cancellation - cancellation data + * @list: internal, for keeping track + * @cancel: callback to call + * @cancel_data: extra data for the callback to call + */ + context_lock_struct(debugfs_cancellation) { + struct list_head list; + void (*cancel)(struct dentry *, void *); + void *cancel_data; + }; + """ + + kernel_doc = KernelDoc(self.config, "test.c", self.xforms) + patcher = patch('builtins.open', new_callable=mock_open, + read_data=dedent(source)) + with patcher: + _, entries = kernel_doc.parse_kdoc() + + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0].name, "debugfs_cancellation") + self.assertEqual(entries[0].warnings, []) + # # Class and logic to create dynamic tests from YAML # From 7131aaa74bcc7c9da1f1d1c8724da75c775557a0 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Wed, 30 Sep 2026 03:26:54 +0900 Subject: [PATCH 1067/2286] nilfs2: ignore non-fatal signals during synchronous log write wait Based on a syzbot report, Wang Jianjian pointed out that following a successful directory operation in nilfs_rename(), a synchronous write requested within nilfs_transaction_commit(), triggered by a sync flag set by nilfs_commit_chunk(), could be interrupted by a user signal. This interruption leaves in-memory metadata updates intact while returning an error, causing inconsistency with the VFS dentry cache and ultimately triggering a kernel warning. According to "signal(7)", local disks are not classified as "slow devices" (such as terminals, FIFOs, or pipes) and their I/O operations on disk devices are not interrupted by signals. In kernel space, waiting for such disk I/O should therefore not be aborted by ordinary (non-fatal) signals. Suppress this issue by modifying nilfs_segctor_sync(), which waits for the log writer thread to complete synchronous writes, to catch only fatal signals via fatal_signal_pending() and return -EINTR rather than -ERESTARTSYS. To complement this change and prevent the wait loop from busy-looping when schedule() returns immediately due to a pending non-fatal signal, switch the wait state from TASK_INTERRUPTIBLE to TASK_KILLABLE. This serves as an effective interim stabilization measure without structural changes until a comprehensive fix for metadata consistency is implemented. Reported-by: syzbot+f6c7e1f1809f235eeb90@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=f6c7e1f1809f235eeb90 Fixes: 9ff05123e3bf ("nilfs2: segment constructor") Tested-by: syzbot+f6c7e1f1809f235eeb90@syzkaller.appspotmail.com Cc: Wang Jianjian Cc: stable@vger.kernel.org Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/ioctl.c | 2 +- fs/nilfs2/recovery.c | 2 +- fs/nilfs2/segment.c | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index 01a04080ef7045..b2b67d6a1f097a 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -965,10 +965,10 @@ static int nilfs_ioctl_clean_segments(struct inode *inode, struct file *filp, * Return: 0 on success, or one of the following negative error codes on * failure: * * %-EFAULT - Failure during execution of requested operation. + * * %-EINTR - Interrupted. * * %-EIO - I/O error. * * %-ENOMEM - Insufficient memory available. * * %-ENOSPC - No space left on device (only in a panic state). - * * %-ERESTARTSYS - Interrupted. * * %-EROFS - Read only filesystem. */ static int nilfs_ioctl_sync(struct inode *inode, struct file *filp, diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c index 9a70c28ec94c18..abe4101f7550c0 100644 --- a/fs/nilfs2/recovery.c +++ b/fs/nilfs2/recovery.c @@ -790,11 +790,11 @@ static void nilfs_abort_roll_forward(struct the_nilfs *nilfs) * * Return: 0 on success, or one of the following negative error codes on * failure: + * * %-EINTR - Interrupted. * * %-EINVAL - Inconsistent filesystem state. * * %-EIO - I/O error. * * %-ENOMEM - Insufficient memory available. * * %-ENOSPC - No space left on device (only in a panic state). - * * %-ERESTARTSYS - Interrupted. */ int nilfs_salvage_orphan_logs(struct the_nilfs *nilfs, struct super_block *sb, diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c index 2f0e59847d026c..af0c3560444f5f 100644 --- a/fs/nilfs2/segment.c +++ b/fs/nilfs2/segment.c @@ -2259,7 +2259,7 @@ static int nilfs_segctor_sync(struct nilfs_sc_info *sci) wake_up(&sci->sc_wait_daemon); for (;;) { - set_current_state(TASK_INTERRUPTIBLE); + set_current_state(TASK_KILLABLE); /* * Synchronize only while the log writer thread is alive. @@ -2273,11 +2273,11 @@ static int nilfs_segctor_sync(struct nilfs_sc_info *sci) err = wait_req.err; break; } - if (!signal_pending(current)) { + if (!fatal_signal_pending(current)) { schedule(); continue; } - err = -ERESTARTSYS; + err = -EINTR; break; } finish_wait(&sci->sc_wait_request, &wait_req.wq); @@ -2311,10 +2311,10 @@ static void nilfs_segctor_wakeup(struct nilfs_sc_info *sci, int err, bool force) * * Return: 0 on success, or one of the following negative error codes on * failure: + * * %-EINTR - Interrupted. * * %-EIO - I/O error (including metadata corruption). * * %-ENOMEM - Insufficient memory available. * * %-ENOSPC - No space left on device (only in a panic state). - * * %-ERESTARTSYS - Interrupted. * * %-EROFS - Read only filesystem. */ int nilfs_construct_segment(struct super_block *sb) @@ -2341,10 +2341,10 @@ int nilfs_construct_segment(struct super_block *sb) * * Return: 0 on success, or one of the following negative error codes on * failure: + * * %-EINTR - Interrupted. * * %-EIO - I/O error (including metadata corruption). * * %-ENOMEM - Insufficient memory available. * * %-ENOSPC - No space left on device (only in a panic state). - * * %-ERESTARTSYS - Interrupted. * * %-EROFS - Read only filesystem. */ int nilfs_construct_dsync_segment(struct super_block *sb, struct inode *inode, From 881f19c2ffbc73f351b257e09c71e6059939b770 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Tue, 29 Sep 2026 02:06:37 +0900 Subject: [PATCH 1068/2286] lsm: remove redundant NULL check in security_init() Since commit 5137e583ba26 ("lsm: cleanup the debug and console output in lsm_init.c"), the NULL check for lsm_order_legacy in security_init() has no effect. So remove the check. No functional change. Signed-off-by: Sang-Heon Jeon Signed-off-by: Paul Moore --- security/lsm_init.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/lsm_init.c b/security/lsm_init.c index dbda7771013b8f..8d5ecea99c46b8 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -431,8 +431,7 @@ int __init security_init(void) } if (lsm_order_cmdline) { - if (lsm_order_legacy) - lsm_order_legacy = NULL; + lsm_order_legacy = NULL; lsm_order_parse(lsm_order_cmdline, "cmdline"); } else lsm_order_parse(lsm_order_builtin, "builtin"); From edb2b6d8ea88659679787231253e1acefca63f28 Mon Sep 17 00:00:00 2001 From: "Pawel Zalewski (The Capable Hub)" Date: Fri, 7 Aug 2026 13:38:33 +0100 Subject: [PATCH 1069/2286] x86/platform/olpc: Use named initializers for acpi_device_id Use a named initializer for the acpi_device_id fields which makes the code more readable and consistent with how lists are initialized in the rest of the kernel code base. Also drop explicitly setting fields to 0 where it is redundant. While at it - unify the list terminator to have a single space between the brackets and no trailing comma. Signed-off-by: Pawel Zalewski (The Capable Hub) Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260807-acpi-platform-v1-1-1507662916ed@thegoodpenguin.co.uk --- arch/x86/platform/olpc/olpc-xo15-sci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/platform/olpc/olpc-xo15-sci.c b/arch/x86/platform/olpc/olpc-xo15-sci.c index 75ed6ceb9df33d..a13dd6b8bc6ee5 100644 --- a/arch/x86/platform/olpc/olpc-xo15-sci.c +++ b/arch/x86/platform/olpc/olpc-xo15-sci.c @@ -210,8 +210,8 @@ static int xo15_sci_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(xo15_sci_pm, NULL, xo15_sci_resume); static const struct acpi_device_id xo15_sci_device_ids[] = { - {"XO15EC", 0}, - {"", 0}, + { .id = "XO15EC" }, + { } }; static struct platform_driver xo15_sci_drv = { From dbb9962ec1f997cfb128b337989250a5e4d8c88e Mon Sep 17 00:00:00 2001 From: "Borislav Petkov (AMD)" Date: Fri, 18 Sep 2026 22:43:31 -0700 Subject: [PATCH 1070/2286] MAINTAINERS: Orphan drivers/edac/i3200_edac.c It hasn't received any care and testing for a looong time now, and it doesn't look like it would anymore. Orphan it. Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260919054656.GGaq4h0KqIx5E0tne3@fat_crate.local --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2fabe0b5a49c01..1d68d83f3120dc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9447,6 +9447,11 @@ L: linux-edac@vger.kernel.org S: Orphan F: drivers/edac/i3000_edac.c +EDAC-I3200 +L: linux-edac@vger.kernel.org +S: Orphan +F: drivers/edac/i3200_edac.c + EDAC-I5000 L: linux-edac@vger.kernel.org S: Maintained From 2c6a75adb15f8fbeb7a8cc6b1f35beb21db20762 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Thu, 17 Sep 2026 20:18:01 -0700 Subject: [PATCH 1071/2286] x86/um: Remove unused header arch/x86/um/asm/required-features.h only existed to satisfy dependencies on the non-UML x86 header of the same name, which was removed in 8f97566c8a81 ("x86/cpufeatures: Remove {disabled,required}-features.h"). Remove it. Signed-off-by: Ethan Nelson-Moore Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260918031807.449816-1-enelsonmoore@gmail.com --- arch/x86/um/asm/required-features.h | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 arch/x86/um/asm/required-features.h diff --git a/arch/x86/um/asm/required-features.h b/arch/x86/um/asm/required-features.h deleted file mode 100644 index dfb967b2d2f375..00000000000000 --- a/arch/x86/um/asm/required-features.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __UM_REQUIRED_FEATURES_H -#define __UM_REQUIRED_FEATURES_H - -/* - * Nothing to see, just need something for the i386 and x86_64 asm - * headers to include. - */ - -#endif From 2fc0d80b9d961a9f43c7c3fa840079e4cd629e47 Mon Sep 17 00:00:00 2001 From: Maximilian Immanuel Brandtner Date: Thu, 24 Sep 2026 12:47:31 +0200 Subject: [PATCH 1072/2286] s390/sclp: Introduce dedicated sclp-vt220 event buffer type Introduce sclp-vt220 event buffer type to replace the byte-manipulation behavior in the receiver function with well defined primitives. Signed-off-by: Maximilian Immanuel Brandtner Reviewed-by: Peter Oberparleiter Reviewed-by: Hendrik Brueckner Signed-off-by: Heiko Carstens --- drivers/s390/char/sclp_vt220.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c index 4e2bf8186a5dca..cd5e5dba460d72 100644 --- a/drivers/s390/char/sclp_vt220.c +++ b/drivers/s390/char/sclp_vt220.c @@ -52,6 +52,12 @@ struct sclp_vt220_sccb { struct evbuf_header evbuf; }; +struct sclp_vt220_evbuf { + struct evbuf_header header; + char type; + char data[]; +} __packed; + #define SCLP_VT220_MAX_CHARS_PER_BUFFER (PAGE_SIZE - \ sizeof(struct sclp_vt220_request) - \ sizeof(struct sclp_vt220_sccb)) @@ -519,25 +525,25 @@ static void sclp_vt220_handle_input(const char *buffer, unsigned int count) /* * Called by the SCLP to report incoming event buffers. */ -static void -sclp_vt220_receiver_fn(struct evbuf_header *evbuf) +static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf) { - char *buffer; + struct sclp_vt220_evbuf *buffer; unsigned int count; - buffer = (char *) ((addr_t) evbuf + sizeof(struct evbuf_header)); - count = evbuf->length - sizeof(struct evbuf_header); + if (evbuf->length < offsetof(struct sclp_vt220_evbuf, data)) + return; + + buffer = (struct sclp_vt220_evbuf *)evbuf; + count = evbuf->length - offsetof(struct sclp_vt220_evbuf, data); - switch (*buffer) { + switch (buffer->type) { case SCLP_VT220_SESSION_ENDED: case SCLP_VT220_SESSION_STARTED: sclp_vt220_reset_session(); break; case SCLP_VT220_SESSION_DATA: /* Send input to line discipline */ - buffer++; - count--; - sclp_vt220_handle_input(buffer, count); + sclp_vt220_handle_input(buffer->data, count); tty_flip_buffer_push(&sclp_vt220_port); break; } From 0287e5076147ba6a9b0d242e1666e1619fdecdb0 Mon Sep 17 00:00:00 2001 From: Maximilian Immanuel Brandtner Date: Thu, 24 Sep 2026 12:47:32 +0200 Subject: [PATCH 1073/2286] s390/sclp: Implement resize for sclp-vt220 console Add support for sclp vt220 resize events to enable host-initiated terminal resizing when using the sclp-vt220 console. This allows QEMU to dynamically resize the console. On older kernel versions, these events are safely ignored as the kernel drops events with undefined tty codes. Signed-off-by: Maximilian Immanuel Brandtner Reviewed-by: Hendrik Brueckner Reviewed-by: Peter Oberparleiter Signed-off-by: Heiko Carstens --- drivers/s390/char/sclp_vt220.c | 63 +++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/drivers/s390/char/sclp_vt220.c b/drivers/s390/char/sclp_vt220.c index cd5e5dba460d72..b95af0974624d0 100644 --- a/drivers/s390/char/sclp_vt220.c +++ b/drivers/s390/char/sclp_vt220.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "sclp.h" @@ -58,6 +59,11 @@ struct sclp_vt220_evbuf { char data[]; } __packed; +struct sclp_vt220_resize_data_t { + u16 rows; + u16 cols; +} __packed; + #define SCLP_VT220_MAX_CHARS_PER_BUFFER (PAGE_SIZE - \ sizeof(struct sclp_vt220_request) - \ sizeof(struct sclp_vt220_sccb)) @@ -95,6 +101,15 @@ static int __initdata sclp_vt220_init_count; * another buffer */ static int sclp_vt220_flush_later; +/* Work struct required for scheduling resize */ +static struct work_struct sclp_vt220_resize_work; + +/* Current vt220 terminal winsize */ +static struct winsize sclp_vt220_winsize = { + .ws_row = 24, + .ws_col = 80, +}; + static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf); static int __sclp_vt220_emit(struct sclp_vt220_request *request); static void sclp_vt220_emit_current(void); @@ -474,6 +489,7 @@ sclp_vt220_write(struct tty_struct *tty, const u8 *buf, size_t count) #define SCLP_VT220_SESSION_ENDED 0x01 #define SCLP_VT220_SESSION_STARTED 0x80 #define SCLP_VT220_SESSION_DATA 0x00 +#define SCLP_VT220_SESSION_RESIZE 0x08 #ifdef CONFIG_MAGIC_SYSRQ @@ -522,6 +538,38 @@ static void sclp_vt220_handle_input(const char *buffer, unsigned int count) #endif +static void sclp_vt220_resize(struct work_struct *work) +{ + struct tty_struct *tty; + struct winsize ws; + + spin_lock_irq(&sclp_vt220_lock); + ws = sclp_vt220_winsize; + spin_unlock_irq(&sclp_vt220_lock); + + tty = tty_port_tty_get(&sclp_vt220_port); + if (!tty) + return; + + tty_do_resize(tty, &ws); + tty_kref_put(tty); +} + +static void sclp_vt220_resize_sched(void *buffer, unsigned int count) +{ + struct sclp_vt220_resize_data_t *data = buffer; + unsigned long flags; + + if (count < sizeof(*data)) + return; + + spin_lock_irqsave(&sclp_vt220_lock, flags); + sclp_vt220_winsize.ws_row = data->rows; + sclp_vt220_winsize.ws_col = data->cols; + schedule_work(&sclp_vt220_resize_work); + spin_unlock_irqrestore(&sclp_vt220_lock, flags); +} + /* * Called by the SCLP to report incoming event buffers. */ @@ -546,6 +594,9 @@ static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf) sclp_vt220_handle_input(buffer->data, count); tty_flip_buffer_push(&sclp_vt220_port); break; + case SCLP_VT220_SESSION_RESIZE: + sclp_vt220_resize_sched(buffer->data, count); + break; } } @@ -557,10 +608,12 @@ sclp_vt220_open(struct tty_struct *tty, struct file *filp) { if (tty->count == 1) { tty_port_tty_set(&sclp_vt220_port, tty); - if (!tty->winsize.ws_row && !tty->winsize.ws_col) { - tty->winsize.ws_row = 24; - tty->winsize.ws_col = 80; - } + + mutex_lock(&tty->winsize_mutex); + spin_lock_irq(&sclp_vt220_lock); + tty->winsize = sclp_vt220_winsize; + spin_unlock_irq(&sclp_vt220_lock); + mutex_unlock(&tty->winsize_mutex); } return 0; } @@ -753,6 +806,8 @@ static int __init sclp_vt220_tty_init(void) rc = tty_register_driver(driver); if (rc) goto out_init; + + INIT_WORK(&sclp_vt220_resize_work, sclp_vt220_resize); rc = sclp_register(&sclp_vt220_register_input); if (rc) goto out_reg; From 2b97e984b18a003256ed7c4aa20bf4b6661e4750 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:57:55 +0200 Subject: [PATCH 1074/2286] s390/percpu: Fix comment typo This is all preempt_disable() / preempt_enable() pairs and not preempt_disable() / preempt_disable() pairs. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index feb58acf87d69f..31012abe8292ba 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -63,7 +63,7 @@ /* * Macros to be used for percpu code section based on atomic instructions. * - * Avoid the need to use preempt_disable() / preempt_disable() pairs and the + * Avoid the need to use preempt_disable() / preempt_enable() pairs and the * conditional preempt_schedule_notrace() function calls which come with * this. The idea is that this_cpu operations based on atomic instructions are * guarded with mviy instructions: From 965869bd2a4f2df44c84fc44f2d2038d14b45978 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:57:56 +0200 Subject: [PATCH 1075/2286] s390/percpu: Add sanity check to GEN_MVIY macro Emit an error if the GEN_MVIY macro fails to parse the passed register string. This will make it more obvious if something similar which was fixed with [1] happens again. [1] commit 101782f8945a ("s390/percpu: Fix MVIY_PERCPU() with older binutils") Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 31012abe8292ba..9888b38dedab22 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -100,12 +100,17 @@ */ #define MVIY_PERCPU(disp, dispalt, reg) \ - ".macro GEN_MVIY disp reg\n" \ + ".macro GEN_MVIY disp, reg\n" \ + ".set .Lreg,255\n" \ ".irp rs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15\n" \ " .ifc \\reg,%%r\\rs\n" \ - " mviy \\disp(%%r0),\\rs\n" \ + " .set .Lreg,\\rs\n" \ " .endif\n" \ ".endr\n" \ + ".if .Lreg == 255\n" \ + " .error \"Illegal register number\"\n" \ + ".endif\n" \ + "mviy \\disp(%%r0),.Lreg\n" \ ".endm\n" \ ALTERNATIVE("GEN_MVIY " disp ", " reg "\n", \ "GEN_MVIY " dispalt ", " reg "\n", \ From ee30fcd726fb3f1fed370a0be446a8b44f8a09b7 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:57:57 +0200 Subject: [PATCH 1076/2286] s390/lowcore: Remove _AC() from LOWCORE_ALT_ADDRESS Remove the _AC() conversion from LOWCORE_ALT_ADDRESS. This changes the type of LOWCORE_ALT_ADDRESS from unsigned long to int in C code, however looking at the current users this should not cause any changes. This allows to use __stringify(LOWCORE_ALT_ADDRESS) for inline assemblies, which otherwise wouldn't work because of the UL suffix. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/lowcore.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/include/asm/lowcore.h b/arch/s390/include/asm/lowcore.h index 5cef215d30e7da..f0364a9f8a19e1 100644 --- a/arch/s390/include/asm/lowcore.h +++ b/arch/s390/include/asm/lowcore.h @@ -21,7 +21,7 @@ #define LC_ORDER 1 #define LC_PAGES 2 -#define LOWCORE_ALT_ADDRESS _AC(0x70000, UL) +#define LOWCORE_ALT_ADDRESS 0x70000 #ifndef __ASSEMBLER__ From d6fe39764bd690949ae640c49164cca07b99d7c9 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:57:58 +0200 Subject: [PATCH 1077/2286] s390/percpu: Let MVIY_PERCPU() calculate alternative displacement Let MVIY_PERCPU() calculate the alternative lowcore displacement instead of passing the same value(s) again and again in all percpu inline assemblies. This shortens the inline assemblies and makes them a bit more readable. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 68 +++++++++++++++------------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 9888b38dedab22..3bc448c4c2dd70 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -99,7 +99,9 @@ * MVIY_ALT(...) <- end of percpu code section */ -#define MVIY_PERCPU(disp, dispalt, reg) \ +#define LC_ALT_ADDR __stringify(LOWCORE_ALT_ADDRESS) + +#define MVIY_PERCPU(disp, reg) \ ".macro GEN_MVIY disp, reg\n" \ ".set .Lreg,255\n" \ ".irp rs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15\n" \ @@ -112,19 +114,19 @@ ".endif\n" \ "mviy \\disp(%%r0),.Lreg\n" \ ".endm\n" \ - ALTERNATIVE("GEN_MVIY " disp ", " reg "\n", \ - "GEN_MVIY " dispalt ", " reg "\n", \ + ALTERNATIVE("GEN_MVIY " disp " , " reg "\n", \ + "GEN_MVIY " disp "+" LC_ALT_ADDR " , " reg "\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) \ ".purgem GEN_MVIY\n" -#define MVIY_ALT(disp, dispalt) \ - ALTERNATIVE(" mviy " disp "(%%r0),0\n", \ - " mviy " dispalt "(%%r0),0\n", \ +#define MVIY_ALT(disp) \ + ALTERNATIVE(" mviy " disp "(%%r0),0\n", \ + " mviy " disp "+" LC_ALT_ADDR "(%%r0),0\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) -#define AG_ALT(disp, dispalt, reg) \ - ALTERNATIVE(" ag " reg ", " disp "(%%r0)\n", \ - " ag " reg ", " dispalt "(%%r0)\n", \ +#define AG_ALT(disp, reg) \ + ALTERNATIVE(" ag " reg ", " disp "(%%r0)\n", \ + " ag " reg ", " disp "+" LC_ALT_ADDR "(%%r0)\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) #ifndef MARCH_HAS_Z196_FEATURES @@ -153,33 +155,29 @@ do { \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ asm volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ + AG_ALT("%[disppcpo]", "%[ptr__]") \ op2 " 0(%[ptr__]),%[val__]\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "i" ((szcast)val__), \ [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } else { \ asm volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]", "%[ptr__]") \ + AG_ALT("%[disppcpo]", "%[ptr__]") \ op1 " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } \ @@ -199,18 +197,16 @@ do { \ lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ + AG_ALT("%[disppcpo]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ old__ + val__; \ @@ -230,18 +226,16 @@ do { \ lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ + AG_ALT("%[disppcpo]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) @@ -263,16 +257,14 @@ do { \ lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ + AG_ALT("%[disppcpo]","%[ptr__]") \ op " %[res__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [res__] "=&d" (res__), [ptr__] "+&a" (ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (*ptr__), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ @@ -294,17 +286,15 @@ do { \ lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[dispaltpcpr]", "%[ptr__]")\ - AG_ALT("%[disppcpo]", "%[dispaltpcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ + AG_ALT("%[disppcpo]","%[ptr__]") \ op " %[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]", "%[dispaltpcpr]") \ + MVIY_ALT("%[disppcpr]") \ : [ptr__] "+&a" (ptr__), "=m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ [disppcpr] "i" (lc_pcpr), \ [disppcpo] "i" (lc_pcpo), \ - [dispaltpcpr] "i" (lc_pcpr + LOWCORE_ALT_ADDRESS), \ - [dispaltpcpo] "i" (lc_pcpo + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) From 301e3a0776511ed60cca1f7d4b14b6b44827c6f4 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:57:59 +0200 Subject: [PATCH 1078/2286] s390/percpu/lowcore: Add and use LC_PERCPU lowcore offset defines Define LC_PERCPU_OFFSET and LC_PERCPU_REGISTER offset defines and use them in percpu code. This shortens the percpu inline assemblies and makes them a bit more readable. Note that because of header dependencies it is not possible to use asm-offsets. However to avoid adding more dependencies to asm-offsets it seems to be preferable to use the current approach in any way. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/entry-percpu.h | 2 +- arch/s390/include/asm/lowcore.h | 3 ++ arch/s390/include/asm/percpu.h | 47 ++++++++++------------------ 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/arch/s390/include/asm/entry-percpu.h b/arch/s390/include/asm/entry-percpu.h index 4ef47265cfce84..89c9e44f6f833a 100644 --- a/arch/s390/include/asm/entry-percpu.h +++ b/arch/s390/include/asm/entry-percpu.h @@ -49,7 +49,7 @@ static __always_inline bool percpu_code_check(struct pt_regs *regs) } if ((insn & 0xff0f) != 0xe300) return true; - disp = offsetof(struct lowcore, percpu_offset); + disp = LC_PERCPU_OFFSET; if (machine_has_relocated_lowcore()) disp += LOWCORE_ALT_ADDRESS; insn = (disp & 0xff000) >> 4 | (disp & 0x00fff) << 16 | 0x8; diff --git a/arch/s390/include/asm/lowcore.h b/arch/s390/include/asm/lowcore.h index f0364a9f8a19e1..f4517bbe405be9 100644 --- a/arch/s390/include/asm/lowcore.h +++ b/arch/s390/include/asm/lowcore.h @@ -222,6 +222,9 @@ struct lowcore { __u8 pad_0x1900[0x2000-0x1900]; /* 0x1900 */ } __packed __aligned(8192); +#define LC_PERCPU_OFFSET offsetof(struct lowcore, percpu_offset) +#define LC_PERCPU_REGISTER offsetof(struct lowcore, percpu_register) + static __always_inline struct lowcore *get_lowcore(void) { struct lowcore *lc; diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 3bc448c4c2dd70..770fe4be65dcf2 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -14,17 +14,16 @@ #define arch_raw_cpu_ptr(_ptr) \ ({ \ - unsigned long lc_percpu, tcp_ptr__; \ + unsigned long tcp_ptr__; \ \ tcp_ptr__ = (__force unsigned long)(_ptr); \ - lc_percpu = offsetof(struct lowcore, percpu_offset); \ asm_inline volatile( \ ALTERNATIVE("ag %[__ptr__],%[offzero](%%r0)\n", \ "ag %[__ptr__],%[offalt](%%r0)\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) \ : [__ptr__] "+d" (tcp_ptr__) \ - : [offzero] "i" (lc_percpu), \ - [offalt] "i" (lc_percpu + LOWCORE_ALT_ADDRESS), \ + : [offzero] "i" (LC_PERCPU_OFFSET), \ + [offalt] "i" (LC_PERCPU_OFFSET + LOWCORE_ALT_ADDRESS), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ (TYPEOF_UNQUAL(*(_ptr)) __force __kernel *)tcp_ptr__; \ @@ -144,13 +143,10 @@ #define arch_this_cpu_add(pcp, val, op1, op2, szcast) \ do { \ - unsigned long lc_pcpr, lc_pcpo; \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ - lc_pcpr = offsetof(struct lowcore, percpu_register); \ - lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ @@ -162,8 +158,8 @@ do { \ : [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "i" ((szcast)val__), \ - [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } else { \ @@ -176,8 +172,8 @@ do { \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } \ @@ -188,13 +184,10 @@ do { \ #define arch_this_cpu_add_return(pcp, val, op) \ ({ \ - unsigned long lc_pcpr, lc_pcpo; \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ - lc_pcpr = offsetof(struct lowcore, percpu_register); \ - lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ @@ -205,8 +198,8 @@ do { \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ old__ + val__; \ @@ -217,13 +210,10 @@ do { \ #define arch_this_cpu_to_op(pcp, val, op) \ do { \ - unsigned long lc_pcpr, lc_pcpo; \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ - lc_pcpr = offsetof(struct lowcore, percpu_register); \ - lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ @@ -234,8 +224,8 @@ do { \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) @@ -249,12 +239,10 @@ do { \ #define arch_this_cpu_read(pcp, op) \ ({ \ - unsigned long lc_pcpr, lc_pcpo, res__; \ typedef typeof(pcp) pcp_op_T__; \ + unsigned long res__; \ pcp_op_T__ *ptr__; \ \ - lc_pcpr = offsetof(struct lowcore, percpu_register); \ - lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ @@ -263,8 +251,8 @@ do { \ MVIY_ALT("%[disppcpr]") \ : [res__] "=&d" (res__), [ptr__] "+&a" (ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + : [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (*ptr__), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ @@ -278,12 +266,9 @@ do { \ #define arch_this_cpu_write(pcp, val, op) \ do { \ - unsigned long lc_pcpr, lc_pcpo; \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ *ptr__, val__ = (val); \ \ - lc_pcpr = offsetof(struct lowcore, percpu_register); \ - lc_pcpo = offsetof(struct lowcore, percpu_offset); \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ @@ -293,8 +278,8 @@ do { \ : [ptr__] "+&a" (ptr__), "=m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (lc_pcpr), \ - [disppcpo] "i" (lc_pcpo), \ + [disppcpr] "i" (LC_PERCPU_REGISTER), \ + [disppcpo] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) From 02a8ff0f894f6960d5d5fb47169140a1ae5c024d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:00 +0200 Subject: [PATCH 1079/2286] s390/percpu: Rename inline assembly symbolic names Rename percpu inline assembly symbolic names so they match (more) what is specified with input operands. A trivial change which is supposed to make the code a bit more readable. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 770fe4be65dcf2..0df47ec2e6c786 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -151,29 +151,29 @@ do { \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ asm volatile( \ - MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ - AG_ALT("%[disppcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[lcreg]","%[ptr__]") \ + AG_ALT("%[lcoff]", "%[ptr__]") \ op2 " 0(%[ptr__]),%[val__]\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "i" ((szcast)val__), \ - [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } else { \ asm volatile( \ - MVIY_PERCPU("%[disppcpr]", "%[ptr__]") \ - AG_ALT("%[disppcpo]", "%[ptr__]") \ + MVIY_PERCPU("%[lcreg]", "%[ptr__]") \ + AG_ALT("%[lcoff]", "%[ptr__]") \ op1 " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } \ @@ -190,16 +190,16 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ - AG_ALT("%[disppcpo]","%[ptr__]") \ + MVIY_PERCPU("%[lcreg]","%[ptr__]") \ + AG_ALT("%[lcoff]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ old__ + val__; \ @@ -216,16 +216,16 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ - AG_ALT("%[disppcpo]","%[ptr__]") \ + MVIY_PERCPU("%[lcreg]","%[ptr__]") \ + AG_ALT("%[lcoff]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) @@ -245,14 +245,14 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ - AG_ALT("%[disppcpo]","%[ptr__]") \ + MVIY_PERCPU("%[lcreg]","%[ptr__]") \ + AG_ALT("%[lcoff]","%[ptr__]") \ op " %[res__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [res__] "=&d" (res__), [ptr__] "+&a" (ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + : [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (*ptr__), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ @@ -271,15 +271,15 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[disppcpr]","%[ptr__]") \ - AG_ALT("%[disppcpo]","%[ptr__]") \ + MVIY_PERCPU("%[lcreg]","%[ptr__]") \ + AG_ALT("%[lcoff]","%[ptr__]") \ op " %[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[disppcpr]") \ + MVIY_ALT("%[lcreg]") \ : [ptr__] "+&a" (ptr__), "=m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ - [disppcpr] "i" (LC_PERCPU_REGISTER), \ - [disppcpo] "i" (LC_PERCPU_OFFSET), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } while (0) From da9af84e66c3ace4f1a95421c1152f9df4e6abc5 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:01 +0200 Subject: [PATCH 1080/2286] s390/percpu: Use __PCPU_BEGIN() and __PCPU_END() for inline assemblies Replace the use of MVIY_PERCPU(), AG_ALT(), and MVIY_ALT() within all percpu inline assemblies with __PCPU_BEGIN() and __PCPU_END(), since the begin and end of each inline assembly is the same. Also use consistent names: e.g. __PCPU_MVIY() and __PCPU_AG() instead of the previous MVIY_PERCPU() and AG_ALT(). Furthermore extract DEFINE_GR_NUM and UNDEF_GR_NUM which are used to convert register strings to register numbers. All of this should make the code more readable. No functional change. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 85 +++++++++++++++++----------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 0df47ec2e6c786..78c3aaa138f73f 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -92,42 +92,49 @@ * * Inline assemblies making use of this typically have a code sequence like: * - * MVIY_PERCPU(...) <- start of percpu code section - * AG_ALT(...) <- add percpu offset; must be the second instruction - * atomic_op <- atomic op - * MVIY_ALT(...) <- end of percpu code section + * __PCPU_BEGIN(...) <- start of percpu code section + * atomic_op <- atomic op + * __PCPU_END(...) <- end of percpu code section */ #define LC_ALT_ADDR __stringify(LOWCORE_ALT_ADDRESS) -#define MVIY_PERCPU(disp, reg) \ - ".macro GEN_MVIY disp, reg\n" \ - ".set .Lreg,255\n" \ - ".irp rs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15\n" \ - " .ifc \\reg,%%r\\rs\n" \ - " .set .Lreg,\\rs\n" \ +#define DEFINE_GR_NUM \ + ".macro _GR_NUM opd, gr\n" \ + ".set \\opd,255\n" \ + ".irp rs,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15\n" \ + " .ifc \\gr,%%r\\rs\n" \ + " \\opd = \\rs\n" \ " .endif\n" \ ".endr\n" \ - ".if .Lreg == 255\n" \ + ".if \\opd == 255\n" \ " .error \"Illegal register number\"\n" \ ".endif\n" \ - "mviy \\disp(%%r0),.Lreg\n" \ - ".endm\n" \ - ALTERNATIVE("GEN_MVIY " disp " , " reg "\n", \ - "GEN_MVIY " disp "+" LC_ALT_ADDR " , " reg "\n", \ - ALT_FEATURE(MFEATURE_LOWCORE)) \ - ".purgem GEN_MVIY\n" - -#define MVIY_ALT(disp) \ - ALTERNATIVE(" mviy " disp "(%%r0),0\n", \ - " mviy " disp "+" LC_ALT_ADDR "(%%r0),0\n", \ + ".endm\n" + +#define UNDEF_GR_NUM \ + ".purgem _GR_NUM\n" + +#define __PCPU_MVIY(lcreg, imm) \ + ALTERNATIVE(" mviy " lcreg "(%%r0)," imm "\n", \ + " mviy " lcreg "+" LC_ALT_ADDR "(%%r0)," imm "\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) -#define AG_ALT(disp, reg) \ - ALTERNATIVE(" ag " reg ", " disp "(%%r0)\n", \ - " ag " reg ", " disp "+" LC_ALT_ADDR "(%%r0)\n", \ +#define __PCPU_AG(reg, lcoff) \ + ALTERNATIVE(" ag " reg ", " lcoff "(%%r0)\n", \ + " ag " reg ", " lcoff "+" LC_ALT_ADDR "(%%r0)\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) +#define __PCPU_BEGIN(lcreg, lcoff, reg) \ + DEFINE_GR_NUM \ + "_GR_NUM .Lreg, " reg "\n" \ + UNDEF_GR_NUM \ + __PCPU_MVIY(lcreg, ".Lreg") \ + __PCPU_AG(reg, lcoff) + +#define __PCPU_END(lcreg) \ + __PCPU_MVIY(lcreg, "0") + #ifndef MARCH_HAS_Z196_FEATURES #define this_cpu_add_4(pcp, val) arch_this_cpu_to_op_simple(pcp, val, +) @@ -151,10 +158,9 @@ do { \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ asm volatile( \ - MVIY_PERCPU("%[lcreg]","%[ptr__]") \ - AG_ALT("%[lcoff]", "%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op2 " 0(%[ptr__]),%[val__]\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "i" ((szcast)val__), \ @@ -164,10 +170,9 @@ do { \ : "cc"); \ } else { \ asm volatile( \ - MVIY_PERCPU("%[lcreg]", "%[ptr__]") \ - AG_ALT("%[lcoff]", "%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op1 " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ @@ -190,10 +195,9 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[lcreg]","%[ptr__]") \ - AG_ALT("%[lcoff]","%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ @@ -216,10 +220,9 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[lcreg]","%[ptr__]") \ - AG_ALT("%[lcoff]","%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op " %[old__],%[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [old__] "=&d" (old__), \ [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ @@ -245,10 +248,9 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[lcreg]","%[ptr__]") \ - AG_ALT("%[lcoff]","%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op " %[res__],0(%[ptr__])\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [res__] "=&d" (res__), [ptr__] "+&a" (ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [lcreg] "i" (LC_PERCPU_REGISTER), \ @@ -271,10 +273,9 @@ do { \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - MVIY_PERCPU("%[lcreg]","%[ptr__]") \ - AG_ALT("%[lcoff]","%[ptr__]") \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ op " %[val__],0(%[ptr__])\n" \ - MVIY_ALT("%[lcreg]") \ + __PCPU_END("%[lcreg]") \ : [ptr__] "+&a" (ptr__), "=m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ : [val__] "d" (val__), \ From e8b55c9982df6dc5f28c427e4debabb4da3a5e64 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:02 +0200 Subject: [PATCH 1081/2286] s390/percpu: Use percpu code section for this_cpu_cmpxchg128() Convert this_cpu_cmpxchg128() to make use of percpu code section functionality. This is just for the sake of completeness, since currently there is no in-kernel user of this_cpu_cmpxchg128(). Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 78c3aaa138f73f..0eae65937c0da7 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -312,15 +312,23 @@ do { \ #define this_cpu_cmpxchg128(pcp, oval, nval) \ ({ \ typedef typeof(pcp) pcp_op_T__; \ - u128 old__, new__, ret__; \ + u128 old__ = (oval), new__ = (nval); \ pcp_op_T__ *ptr__; \ - old__ = oval; \ - new__ = nval; \ - preempt_disable_notrace(); \ - ptr__ = raw_cpu_ptr(&(pcp)); \ - ret__ = cmpxchg128((void *)ptr__, old__, new__); \ - preempt_enable_notrace(); \ - ret__; \ + \ + ptr__ = PERCPU_PTR(&(pcp)); \ + asm_inline volatile( \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ + " cdsg %[old__],%[new__],0(%[ptr__])\n" \ + __PCPU_END("%[lcreg]") \ + : [old__] "+&d" (old__), [ptr__] "+&a" (ptr__), \ + "+m" (*ptr__), \ + "=m" (((struct lowcore *)0)->percpu_register) \ + : [new__] "d" (new__), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ + "m" (((struct lowcore *)0)->percpu_offset) \ + : "memory", "cc"); \ + old__; \ }) #define arch_this_cpu_xchg(pcp, nval) \ From d83b5cf355ca762ef19aa3a424af44e3e275ff52 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:03 +0200 Subject: [PATCH 1082/2286] s390/percpu: Use percpu code section for this_cpu_xchg() Convert this_cpu_xchg() to make use of percpu code section functionality in order to get rid of preempt_disable() and preempt_enable() calls. Consider only four and eight byte usages, since there are no compare and swap instructions for one and two bytes available. For one and two byte use the previous simple implementation. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 0eae65937c0da7..237bf8c8084381 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -331,7 +331,7 @@ do { \ old__; \ }) -#define arch_this_cpu_xchg(pcp, nval) \ +#define arch_this_cpu_xchg_simple(pcp, nval) \ ({ \ typeof(pcp) *ptr__; \ typeof(pcp) ret__; \ @@ -342,10 +342,34 @@ do { \ ret__; \ }) -#define this_cpu_xchg_1(pcp, nval) arch_this_cpu_xchg(pcp, nval) -#define this_cpu_xchg_2(pcp, nval) arch_this_cpu_xchg(pcp, nval) -#define this_cpu_xchg_4(pcp, nval) arch_this_cpu_xchg(pcp, nval) -#define this_cpu_xchg_8(pcp, nval) arch_this_cpu_xchg(pcp, nval) +#define arch_this_cpu_xchg(pcp, nval, ldop, csop) \ +({ \ + typedef typeof(pcp) pcp_op_T__; \ + pcp_op_T__ old__, new__ = (nval); \ + pcp_op_T__ *ptr__; \ + \ + ptr__ = PERCPU_PTR(&(pcp)); \ + asm_inline volatile( \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ + " " ldop " %[old__],0(%[ptr__])\n" \ + "0: " csop " %[old__],%[new__],0(%[ptr__])\n"\ + " jnz 0b\n" \ + __PCPU_END("%[lcreg]") \ + : [old__] "=&d" (old__), \ + [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + "=m" (((struct lowcore *)0)->percpu_register) \ + : [new__] "d" (new__), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ + "m" (((struct lowcore *)0)->percpu_offset) \ + : "memory", "cc"); \ + old__; \ +}) + +#define this_cpu_xchg_1(pcp, nval) arch_this_cpu_xchg_simple(pcp, nval) +#define this_cpu_xchg_2(pcp, nval) arch_this_cpu_xchg_simple(pcp, nval) +#define this_cpu_xchg_4(pcp, nval) arch_this_cpu_xchg(pcp, nval, "l", "cs") +#define this_cpu_xchg_8(pcp, nval) arch_this_cpu_xchg(pcp, nval, "lg", "csg") #include From 45b0097302d87a42189abe0b33d69fe18f6c7cb0 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:04 +0200 Subject: [PATCH 1083/2286] s390/percpu: Use percpu code section for this_cpu_cmpxchg() Convert this_cpu_cmpxchg() to make use of percpu code section functionality in order to get rid of preempt_disable() and preempt_enable() calls. Consider only four and eight byte usages, since there are no compare and swap instructions for one and two bytes available. For one and two bytes use the previous simple implementation. Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 237bf8c8084381..46368131b932b0 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -290,7 +290,7 @@ do { \ #define this_cpu_write_4(pcp, val) arch_this_cpu_write(pcp, val, "st") #define this_cpu_write_8(pcp, val) arch_this_cpu_write(pcp, val, "stg") -#define arch_this_cpu_cmpxchg(pcp, oval, nval) \ +#define arch_this_cpu_cmpxchg_simple(pcp, oval, nval) \ ({ \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ ret__; \ @@ -302,10 +302,32 @@ do { \ ret__; \ }) -#define this_cpu_cmpxchg_1(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval) -#define this_cpu_cmpxchg_2(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval) -#define this_cpu_cmpxchg_4(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval) -#define this_cpu_cmpxchg_8(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval) +#define arch_this_cpu_cmpxchg(pcp, oval, nval, op) \ +({ \ + typedef typeof(pcp) pcp_op_T__; \ + pcp_op_T__ old__ = (oval), new__ = (nval); \ + pcp_op_T__ *ptr__; \ + \ + ptr__ = PERCPU_PTR(&(pcp)); \ + asm_inline volatile( \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ + op " %[old__],%[new__],0(%[ptr__])\n" \ + __PCPU_END("%[lcreg]") \ + : [old__] "+&d" (old__), \ + [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + "=m" (((struct lowcore *)0)->percpu_register) \ + : [new__] "d" (new__), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ + "m" (((struct lowcore *)0)->percpu_offset) \ + : "memory", "cc"); \ + old__; \ +}) + +#define this_cpu_cmpxchg_1(pcp, oval, nval) arch_this_cpu_cmpxchg_simple(pcp, oval, nval) +#define this_cpu_cmpxchg_2(pcp, oval, nval) arch_this_cpu_cmpxchg_simple(pcp, oval, nval) +#define this_cpu_cmpxchg_4(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval, "cs") +#define this_cpu_cmpxchg_8(pcp, oval, nval) arch_this_cpu_cmpxchg(pcp, oval, nval, "csg") #define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n) From 7849fb83e4cf814710003e4c83e161f97cf7ab1c Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 21 Sep 2026 17:58:05 +0200 Subject: [PATCH 1084/2286] s390/percpu: Rework to simplify percpu_entry() and percpu_exit() The percpu code section functionality uses a rather complex method to figure out if the register, which contains the address of the current cpu's percpu variable, needs to be adjusted. If an interrupt happens within a percpu code section (indicated by a lowcore field), the instruction at the interrupted location is checked. If it is not a specific AG instruction, the register needs to be updated. This mechanism needs to take kprobes into account, and enforces a specific instruction ordering. Mark Rutland provided a different solution for arm64 [1] which comes without such limitations, but requires to use one more instruction, and two more registers. Given that this simplifies percpu_entry() and percpu_exit() it seems to be worth to go that route. Change s390 to implement a similar approach. This requires to encode three register numbers into the "percpu_register" field, which is used to indicate if a percpu code section is executed. The used mviy instruction can write only one byte, which allows to encode only two register numbers. Use a register pair for the inline assemblies, and only encode the even register number of the register pair to work around this. The generated code changes like this for e.g. a simple this_cpu_inc(): Old: c0 20 00 00 00 00 larl %r2,c6 <-- load address of percpu var b9 04 00 32 lgr %r3,%r2 <-- pointless copy of address eb 03 03 c0 00 52 mviy 960,3 <-- start of percpu code section - gpr 3 contains percpu var address e3 30 03 b8 00 08 ag %r3,952 <-- add percpu offset eb 01 30 00 00 7a agsi 0(%r3),1 <-- atomic inc eb 00 03 c0 00 52 mviy 960,0 <-- end of percpu code section New: c0 10 00 00 00 00 larl %r1,c6 <-- load address of percpu var eb 21 03 c0 00 52 mviy 960,33 <-- start of percpu code section 33 == 0x21: - gpr 1 contains percpu var address - gpr 2 used for percpu offset - gpr 2+1 == 3 used for current cpu's percpu var address e3 20 03 b8 00 04 lg %r2,952 <-- load percpu offset 41 32 10 00 la %r3,0(%r2,%r1) <-- generate current cpu's percpu var address eb 01 30 00 00 7a agsi 0(%r3),1 <-- atomic inc eb 00 03 c0 00 52 mviy 960,0 <-- end of percpu code section In the above "new" code example 33 (0x21) is used as indicator value to mark that a percpu code section is executed. This value implies that registers 2 and 3 will (only) hold the percpu offset and the current cpu's percpu var address. Those registers will be updated by percpu_exit() if the process was migrated to a different cpu to contain the percpu offset and percpu var address of the new cpu. Note that because of the pointless lgr instruction in the "old" code example the two code sequences have identical size, and it looks like only one more register is used. However this is because of suboptimal gcc code generation. [1] https://lore.kernel.org/all/20260908151741.394589-1-mark.rutland@arm.com/ Acked-by: Alexander Gordeev Tested-by: Mete Durlu Signed-off-by: Heiko Carstens --- arch/s390/include/asm/entry-percpu.h | 71 +++-------- arch/s390/include/asm/percpu.h | 177 +++++++++++++++++---------- arch/s390/kernel/irq.c | 10 +- arch/s390/kernel/nmi.c | 4 +- arch/s390/kernel/traps.c | 4 +- 5 files changed, 138 insertions(+), 128 deletions(-) diff --git a/arch/s390/include/asm/entry-percpu.h b/arch/s390/include/asm/entry-percpu.h index 89c9e44f6f833a..f6f79ec4bee3bc 100644 --- a/arch/s390/include/asm/entry-percpu.h +++ b/arch/s390/include/asm/entry-percpu.h @@ -2,79 +2,42 @@ #ifndef ARCH_S390_ENTRY_PERCPU_H #define ARCH_S390_ENTRY_PERCPU_H -#include +#include #include #include #include -#include static __always_inline void percpu_entry(struct pt_regs *regs) { struct lowcore *lc = get_lowcore(); - if (user_mode(regs)) - return; regs->cpu = lc->cpu_nr; regs->percpu_register = lc->percpu_register; lc->percpu_register = 0; } -static __always_inline bool percpu_code_check(struct pt_regs *regs) -{ - unsigned int insn, disp; - struct kprobe *p; - - if (likely(user_mode(regs) || !regs->percpu_register)) - return false; - /* - * Within a percpu code section - check if the percpu base register - * needs to be updated. This is the case if the PSW does not point to - * the ADD instruction within the section. - * - AG %rx,percpu_offset_in_lowcore(%r0,%r0) - * which adds the percpu offset to the percpu base register. - */ - lockdep_assert_preemption_disabled(); -again: - insn = READ_ONCE(*(u16 *)psw_bits(regs->psw).ia); - if (unlikely(insn == BREAKPOINT_INSTRUCTION)) { - p = get_kprobe((void *)psw_bits(regs->psw).ia); - /* - * If the kprobe is concurrently removed on a different CPU - * it might not be found anymore. However text must have - * been restored - try again. - */ - if (!p) - goto again; - insn = p->opcode; - } - if ((insn & 0xff0f) != 0xe300) - return true; - disp = LC_PERCPU_OFFSET; - if (machine_has_relocated_lowcore()) - disp += LOWCORE_ALT_ADDRESS; - insn = (disp & 0xff000) >> 4 | (disp & 0x00fff) << 16 | 0x8; - if (*(u32 *)(psw_bits(regs->psw).ia + 2) != insn) - return true; - return false; -} - -static __always_inline void percpu_exit(struct pt_regs *regs, bool needs_fixup) +static __always_inline void percpu_exit(struct pt_regs *regs) { + unsigned char regval, regpcp, regoff, regptr; struct lowcore *lc = get_lowcore(); - unsigned char reg; - if (user_mode(regs)) - return; - reg = regs->percpu_register; - lc->percpu_register = reg; - if (likely(!needs_fixup)) + if (!regs->percpu_register) return; - /* Check if process has been migrated to a different CPU. */ + regval = regs->percpu_register; + lc->percpu_register = regval; + /* Migrated to a different CPU? */ if (regs->cpu == lc->cpu_nr) return; - /* Fixup percpu base register */ - regs->gprs[reg] -= __per_cpu_offset[regs->cpu]; - regs->gprs[reg] += lc->percpu_offset; + regpcp = FIELD_GET(PCPU_REG_PCP, regval); + regoff = FIELD_GET(PCPU_REG_OFF, regval); + regptr = regoff + 1; + /* + * Update register 'regoff' with the current CPU's percpu offset, + * and recalculate and update current CPU's percpu variable address + * contained in register 'regptr'. + */ + regs->gprs[regoff] = lc->percpu_offset; + regs->gprs[regptr] = regs->gprs[regpcp] + regs->gprs[regoff]; } #endif diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 46368131b932b0..c3995bd8d1de81 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -67,28 +67,32 @@ * this. The idea is that this_cpu operations based on atomic instructions are * guarded with mviy instructions: * - * - The first mviy instruction writes the register number, which contains the - * percpu address variable to lowcore. This also indicates that a percpu - * code section is executed. + * - The first mviy instruction writes the register number of the percpu address + * variable and the even register number of a register pair (which encodes two + * registers: the even register for the percpu offset and the odd register for + * the percpu pointer) to lowcore. This also indicates that a percpu code + * section is executed. * - * - The first mviy instruction following the mviy instruction must be the ag - * instruction which adds the percpu offset to the percpu address register. + * - The mviy instruction is followed by the lg instruction which loads the + * percpu offset into the even register of the pair and the la instruction + * which adds the percpu offset and the percpu address into the odd register + * of the pair (the percpu pointer register). * * - Afterwards the atomic percpu operation follows. * * - Then a second mviy instruction writes a zero to lowcore, which indicates * the end of the percpu code section. * - * - In case of an interrupt/exception/nmi the register number which was - * written to lowcore is copied to the exception frame (pt_regs), and a zero - * is written to lowcore. + * - In case of an interrupt/exception/nmi the encoded register numbers which + * were written to lowcore are copied to the exception frame (pt_regs), and a + * zero is written to lowcore. * * - On return to the previous context it is checked if a percpu code section - * was executed (saved register number not zero), and if the process was - * migrated to a different cpu. If the percpu offset was already added to - * the percpu address register (instruction address does _not_ point to the - * ag instruction) the content of the percpu address register is adjusted so - * it points to percpu variable of the new cpu. + * was executed (saved register value not zero), and if the process was + * migrated to a different cpu. The content of the percpu offset register + * (even register of the pair) is reloaded with the current cpu's percpu + * offset and the percpu pointer register (odd register of the pair) is + * recalculated so it points to the percpu variable of the new cpu. * * Inline assemblies making use of this typically have a code sequence like: * @@ -115,22 +119,53 @@ #define UNDEF_GR_NUM \ ".purgem _GR_NUM\n" +#define PCPU_REG_PCP_SHIFT 0 +#define PCPU_REG_PCP GENMASK(3, 0) +#define PCPU_REG_OFF_SHIFT 4 +#define PCPU_REG_OFF GENMASK(7, 4) + #define __PCPU_MVIY(lcreg, imm) \ ALTERNATIVE(" mviy " lcreg "(%%r0)," imm "\n", \ " mviy " lcreg "+" LC_ALT_ADDR "(%%r0)," imm "\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) -#define __PCPU_AG(reg, lcoff) \ - ALTERNATIVE(" ag " reg ", " lcoff "(%%r0)\n", \ - " ag " reg ", " lcoff "+" LC_ALT_ADDR "(%%r0)\n", \ +#define __PCPU_LG(regoff, lcoff) \ + ALTERNATIVE(" lg " regoff ", " lcoff "(%%r0)\n", \ + " lg " regoff ", " lcoff "+" LC_ALT_ADDR "(%%r0)\n", \ ALT_FEATURE(MFEATURE_LOWCORE)) -#define __PCPU_BEGIN(lcreg, lcoff, reg) \ +#define __PCPU_LA(regptr, regoff, regpcp) \ + " la " regptr ",0(" regoff "," regpcp ")\n" + +#define __PCPU_CALC_REGVAL(regpcp, regoff) \ + "(" regpcp " << " __stringify(PCPU_REG_PCP_SHIFT) ") |" \ + "(" regoff " << " __stringify(PCPU_REG_OFF_SHIFT) ")" + +#define __PCPU_CHECK_REGS(regpcp, regoff, regptr) \ + ".if " regoff " & 1\n" \ + " .error \"Percpu offset register must be even\"\n" \ + ".endif\n" \ + ".if (" regpcp " == 0) || (" regoff " == 0)\n" \ + " .error \"Percpu address and offset register must be non-zero\"\n"\ + ".endif\n" \ + ".if " regptr " != " regoff " + 1\n" \ + " .error \"Percpu pointer and offset register must be a pair\"\n" \ + ".endif\n" \ + ".if (" regpcp " == " regoff ") || (" regpcp " == " regptr ")\n" \ + " .error \"Percpu registers must be distinct\"\n" \ + ".endif\n" + +#define __PCPU_BEGIN(lcreg, lcoff, regpcp, regoff, regptr) \ DEFINE_GR_NUM \ - "_GR_NUM .Lreg, " reg "\n" \ + "_GR_NUM .Lregpcp, " regpcp "\n" \ + "_GR_NUM .Lregoff, " regoff "\n" \ + "_GR_NUM .Lregptr, " regptr "\n" \ UNDEF_GR_NUM \ - __PCPU_MVIY(lcreg, ".Lreg") \ - __PCPU_AG(reg, lcoff) + __PCPU_CHECK_REGS(".Lregpcp", ".Lregoff", ".Lregptr") \ + ".set .Lregval, " __PCPU_CALC_REGVAL(".Lregpcp", ".Lregoff") "\n" \ + __PCPU_MVIY(lcreg, ".Lregval") \ + __PCPU_LG(regoff, lcoff) \ + __PCPU_LA(regptr, regoff, regpcp) #define __PCPU_END(lcreg) \ __PCPU_MVIY(lcreg, "0") @@ -151,6 +186,7 @@ #define arch_this_cpu_add(pcp, val, op1, op2, szcast) \ do { \ typedef typeof(pcp) pcp_op_T__; \ + union register_pair rp__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ @@ -158,25 +194,27 @@ do { \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ asm volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op2 " 0(%[ptr__]),%[val__]\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op2 " 0(%N[pair__]),%[val__]\n" \ __PCPU_END("%[lcreg]") \ - : [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [pair__] "=&a" (rp__.pair), "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "i" ((szcast)val__), \ + : [val__] "i" ((szcast)val__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } else { \ asm volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op1 " %[old__],%[val__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op1 " %[old__],%[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), \ - [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), \ + : [val__] "d" (val__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -190,18 +228,20 @@ do { \ #define arch_this_cpu_add_return(pcp, val, op) \ ({ \ typedef typeof(pcp) pcp_op_T__; \ + union register_pair rp__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op " %[old__],%[val__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[old__],%[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), \ - [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), \ + : [val__] "d" (val__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -215,18 +255,20 @@ do { \ #define arch_this_cpu_to_op(pcp, val, op) \ do { \ typedef typeof(pcp) pcp_op_T__; \ + union register_pair rp__; \ pcp_op_T__ val__ = (val); \ pcp_op_T__ old__, *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op " %[old__],%[val__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[old__],%[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), \ - [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), \ + : [val__] "d" (val__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -243,17 +285,20 @@ do { \ #define arch_this_cpu_read(pcp, op) \ ({ \ typedef typeof(pcp) pcp_op_T__; \ + union register_pair rp__; \ unsigned long res__; \ pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op " %[res__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[res__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [res__] "=&d" (res__), [ptr__] "+&a" (ptr__), \ + : [res__] "=&d" (res__), [pair__] "=&a" (rp__.pair), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [lcreg] "i" (LC_PERCPU_REGISTER), \ + : [ptr__] "a" (ptr__), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (*ptr__), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -269,16 +314,18 @@ do { \ #define arch_this_cpu_write(pcp, val, op) \ do { \ typedef typeof(pcp) pcp_op_T__; \ + union register_pair rp__; \ pcp_op_T__ *ptr__, val__ = (val); \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op " %[val__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [ptr__] "+&a" (ptr__), "=m" (*ptr__), \ + : [pair__] "=&a" (rp__.pair), "=m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), \ + : [val__] "d" (val__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -306,17 +353,19 @@ do { \ ({ \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ old__ = (oval), new__ = (nval); \ + union register_pair rp__; \ pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - op " %[old__],%[new__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[old__],%[new__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "+&d" (old__), \ - [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [old__] "+&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [new__] "d" (new__), \ + : [new__] "d" (new__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -335,17 +384,19 @@ do { \ ({ \ typedef typeof(pcp) pcp_op_T__; \ u128 old__ = (oval), new__ = (nval); \ + union register_pair rp__; \ pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - " cdsg %[old__],%[new__],0(%[ptr__])\n" \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + " cdsg %[old__],%[new__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "+&d" (old__), [ptr__] "+&a" (ptr__), \ + : [old__] "+&d" (old__), [pair__] "=&a" (rp__.pair), \ "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [new__] "d" (new__), \ + : [new__] "d" (new__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ @@ -368,19 +419,21 @@ do { \ ({ \ typedef typeof(pcp) pcp_op_T__; \ pcp_op_T__ old__, new__ = (nval); \ + union register_pair rp__; \ pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ - __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]") \ - " " ldop " %[old__],0(%[ptr__])\n" \ - "0: " csop " %[old__],%[new__],0(%[ptr__])\n"\ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + " " ldop " %[old__],0(%N[pair__])\n" \ + "0: " csop " %[old__],%[new__],0(%N[pair__])\n"\ " jnz 0b\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), \ - [ptr__] "+&a" (ptr__), "+m" (*ptr__), \ + : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [new__] "d" (new__), \ + : [new__] "d" (new__), [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c index c923496aa7b480..8f21bea349a659 100644 --- a/arch/s390/kernel/irq.c +++ b/arch/s390/kernel/irq.c @@ -144,9 +144,9 @@ static int irq_pending(struct pt_regs *regs) void noinstr do_io_irq(struct pt_regs *regs) { - bool from_idle, percpu_needs_fixup; struct pt_regs *old_regs; irqentry_state_t state; + bool from_idle; percpu_entry(regs); state = irqentry_enter(regs); @@ -174,21 +174,20 @@ void noinstr do_io_irq(struct pt_regs *regs) do_irq_async(regs, IO_INTERRUPT); } while (machine_is_lpar() && irq_pending(regs)); - percpu_needs_fixup = percpu_code_check(regs); irq_exit_rcu(); set_irq_regs(old_regs); irqentry_exit(regs, state); if (from_idle) regs->psw.mask &= ~(PSW_MASK_EXT | PSW_MASK_IO | PSW_MASK_WAIT); - percpu_exit(regs, percpu_needs_fixup); + percpu_exit(regs); } void noinstr do_ext_irq(struct pt_regs *regs) { - bool from_idle, percpu_needs_fixup; struct pt_regs *old_regs; irqentry_state_t state; + bool from_idle; percpu_entry(regs); state = irqentry_enter(regs); @@ -214,14 +213,13 @@ void noinstr do_ext_irq(struct pt_regs *regs) do_irq_async(regs, EXT_INTERRUPT); - percpu_needs_fixup = percpu_code_check(regs); irq_exit_rcu(); set_irq_regs(old_regs); irqentry_exit(regs, state); if (from_idle) regs->psw.mask &= ~(PSW_MASK_EXT | PSW_MASK_IO | PSW_MASK_WAIT); - percpu_exit(regs, percpu_needs_fixup); + percpu_exit(regs); } static void show_msi_interrupt(struct seq_file *p, int irq) diff --git a/arch/s390/kernel/nmi.c b/arch/s390/kernel/nmi.c index 17297a8b63d951..56a15edc65d71c 100644 --- a/arch/s390/kernel/nmi.c +++ b/arch/s390/kernel/nmi.c @@ -361,7 +361,6 @@ NOKPROBE_SYMBOL(s390_backup_mcck_info); */ void notrace s390_do_machine_check(struct pt_regs *regs) { - bool percpu_needs_fixup; static int ipd_count; static DEFINE_SPINLOCK(ipd_lock); static unsigned long long last_ipd; @@ -495,9 +494,8 @@ void notrace s390_do_machine_check(struct pt_regs *regs) if (mcck_pending) schedule_mcck_handler(); - percpu_needs_fixup = percpu_code_check(regs); irqentry_nmi_exit(regs, irq_state); - percpu_exit(regs, percpu_needs_fixup); + percpu_exit(regs); } NOKPROBE_SYMBOL(s390_do_machine_check); diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index b6ba4465f59dea..dd051046170bbf 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -338,7 +338,6 @@ static void (*pgm_check_table[128])(struct pt_regs *regs); void noinstr __do_pgm_check(struct pt_regs *regs, unsigned long flags) { struct lowcore *lc = get_lowcore(); - bool percpu_needs_fixup; irqentry_state_t state; struct pgm_stat *stat; unsigned int trapnr; @@ -400,9 +399,8 @@ void noinstr __do_pgm_check(struct pt_regs *regs, unsigned long flags) pgm_check_table[trapnr](regs); out: local_irq_disable(); - percpu_needs_fixup = percpu_code_check(regs); irqentry_exit(regs, state); - percpu_exit(regs, percpu_needs_fixup); + percpu_exit(regs); } static int pgm_check_stat_show(struct seq_file *p, void *v) From a7378b68f816103c68406e6fde71a1afab7224bd Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 16:35:17 +0800 Subject: [PATCH 1085/2286] isofs: bound empty directory blocks in isofs_read_level3_size() isofs_read_level3_size() walks the sections of a level-3 (multi-extent) directory record and, on a zero length byte, moves on to the next block with no limit. A crafted image can put a long run of empty blocks between two sections of a multi-extent file, and the walk reads every one of them before it gives up, all the way to the end of the device if it has to. Real discs do have trailing empty directory blocks, written by tools such as Easy CD Creator and Nero, but always at the end of the directory, never between two sections of a multi-extent record, so they do not exercise this path [1]. Jan Kara suggested treating an empty block like a section: count it towards the existing 100-section limit rather than adding a separate one [2]. Do that: both the section count and the empty-block count are checked against their combined total, so neither one alone can reach 100 while the other keeps growing. Only a zero length byte at the start of a block counts as an empty block; the same byte later in a block still just ends that block's records, as it does today, and is not counted. Hui Peng's earlier patch for this function added its own, separate limit on the number of empty blocks [3]; this uses the combined limit Jan suggested instead. Suggested-by: Jan Kara Signed-off-by: Matthias Goergens [1] https://lore.kernel.org/all/20260925043804.2091174-1-matthias.goergens@gmail.com/ [2] https://lore.kernel.org/all/cmlro2xzle2aa7ebflvxhbcv74mea7p6qvxhin6egpdvyzyuxh@s45tpgxdal3n/ [3] https://lore.kernel.org/all/20260919222553.3792320-1-benquike@gmail.com/ Link: https://patch.msgid.link/20260926083517.556576-1-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/inode.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index 184350d2e6ad54..e884618c0c5357 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -1175,6 +1175,7 @@ static int isofs_read_level3_size(struct inode *inode) struct buffer_head *bh = NULL; unsigned long block, offset, block_saved, offset_saved; int i = 0; + int empty_blocks = 0; int more_entries = 0; struct iso_inode_info *ei = ISOFS_I(inode); @@ -1202,9 +1203,14 @@ static int isofs_read_level3_size(struct inode *inode) /* * If we are at the end of a block (or at its zero-padded - * tail), move on to the next block. + * tail), move on to the next block. A zero length byte at + * the start of a block means the whole block is empty; + * count that towards the same limit as sections below, or a + * chain of empty blocks could be walked without bound. */ if (offset >= bufsize || de->length[0] == 0) { + if (offset == 0 && ++empty_blocks + i > 100) + goto out_toomany; brelse(bh); bh = NULL; ++block; @@ -1233,7 +1239,7 @@ static int isofs_read_level3_size(struct inode *inode) more_entries = de->flags[-high_sierra] & 0x80; i++; - if (i > 100) + if (i + empty_blocks > 100) goto out_toomany; } while (more_entries); out: @@ -1245,7 +1251,7 @@ static int isofs_read_level3_size(struct inode *inode) return -EIO; out_toomany: - printk(KERN_INFO "%s: More than 100 file sections ?!?, aborting...\n" + printk(KERN_INFO "%s: More than 100 file sections/empty blocks ?!?, aborting...\n" "isofs_read_level3_size: inode=%llu\n", __func__, inode->i_ino); goto out; From 7b5ba47d845f35b04937748a191a1772ffc1f288 Mon Sep 17 00:00:00 2001 From: Lorenzo Stoakes Date: Tue, 13 Jan 2026 19:29:49 +0000 Subject: [PATCH 1086/2286] rust: bindings: fix VM_MAYSHARE value Commit 2b6a3f061f11 ("mm: declare VMA flags by bit") added a number of overrides to the rust bindings helper as the change performed in the commit caused rust bindgen to not be able to automatically determine values of VMA flags. However a typo was introduced leaving VM_MAYSHARE to be incorrectly interpreted as VM_MAYEXEC in rust code. This patch fixes the typo. Fixes: 2b6a3f061f11 ("mm: declare VMA flags by bit") Reported-by: Chris Mason Closes: https://lore.kernel.org/all/20260113185142.254821-1-clm@meta.com/ Cc: stable@vger.kernel.org Signed-off-by: Lorenzo Stoakes Reviewed-by: Alice Ryhl Reviewed-by: wang lian Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260113192949.689834-1-lorenzo.stoakes@oracle.com [ Reworded to improve title and to fix typo. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/bindings/bindings_helper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 4b31aa7f432f59..c706dda5b5ca76 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -138,7 +138,7 @@ const vm_flags_t RUST_CONST_HELPER_VM_SHARED = VM_SHARED; const vm_flags_t RUST_CONST_HELPER_VM_MAYREAD = VM_MAYREAD; const vm_flags_t RUST_CONST_HELPER_VM_MAYWRITE = VM_MAYWRITE; const vm_flags_t RUST_CONST_HELPER_VM_MAYEXEC = VM_MAYEXEC; -const vm_flags_t RUST_CONST_HELPER_VM_MAYSHARE = VM_MAYEXEC; +const vm_flags_t RUST_CONST_HELPER_VM_MAYSHARE = VM_MAYSHARE; const vm_flags_t RUST_CONST_HELPER_VM_PFNMAP = VM_PFNMAP; const vm_flags_t RUST_CONST_HELPER_VM_IO = VM_IO; const vm_flags_t RUST_CONST_HELPER_VM_DONTCOPY = VM_DONTCOPY; From 6f3adf358c8808973dc44aa333bdc0b06ae5f0c5 Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Tue, 1 Sep 2026 18:48:02 +0100 Subject: [PATCH 1087/2286] firmware: Move firmware attributes class helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the firmware attributes class helper from drivers/platform/x86 to drivers/firmware and expose its class declaration through a public Linux header. The helper is not x86-specific. Keeping it in drivers/firmware lets coreboot firmware drivers use the standard firmware-attributes ABI without living under platform/x86. Replace the affected drivers' relative helper includes directly with the new public header. Suggested-by: Derek J. Clark Reviewed-by: Mark Pearson Reviewed-by: Derek J. Clark Tested-by: Oliver Lin Signed-off-by: Sean Rhodes Acked-by: Ilpo Järvinen Link: https://lore.kernel.org/r/5682c2228fa4a784d3953664b56a06e0dd9ccdef.1788284852.git.sean@starlabs.systems Signed-off-by: Tzung-Bi Shih --- MAINTAINERS | 8 ++++++++ drivers/firmware/Kconfig | 3 +++ drivers/firmware/Makefile | 1 + .../x86 => firmware}/firmware_attributes_class.c | 2 +- drivers/platform/x86/Kconfig | 3 --- drivers/platform/x86/Makefile | 2 -- drivers/platform/x86/asus-armoury.c | 2 +- drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 2 +- drivers/platform/x86/hp/hp-bioscfg/bioscfg.c | 2 +- drivers/platform/x86/lenovo/think-lmi.c | 2 +- drivers/platform/x86/lenovo/wmi-other.c | 2 +- drivers/platform/x86/samsung-galaxybook.c | 2 +- .../linux/firmware_attributes.h | 6 +++--- 13 files changed, 22 insertions(+), 15 deletions(-) rename drivers/{platform/x86 => firmware}/firmware_attributes_class.c (94%) rename drivers/platform/x86/firmware_attributes_class.h => include/linux/firmware_attributes.h (60%) diff --git a/MAINTAINERS b/MAINTAINERS index 8ba5c6b0d54336..c22cb3d73b4d35 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10194,6 +10194,14 @@ F: include/linux/firewire.h F: include/uapi/linux/firewire*.h F: tools/firewire/ +FIRMWARE ATTRIBUTES CLASS +M: Hans de Goede +M: Ilpo Järvinen +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/firmware/firmware_attributes_class.c +F: include/linux/firmware_attributes.h + FIRMWARE FRAMEWORK FOR ARMV8-A M: Sudeep Holla L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index e07a7ce5c3cc83..b7d4df89cfb1c7 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -8,6 +8,9 @@ menu "Firmware Drivers" source "drivers/firmware/arm_scmi/Kconfig" +config FW_ATTR_CLASS + tristate + config ARM_SCPI_PROTOCOL tristate "ARM System Control and Power Interface (SCPI) Message Protocol" depends on ARM || ARM64 || COMPILE_TEST diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index a37008dfcde5b7..ce925e8ee526f4 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -17,6 +17,7 @@ obj-$(CONFIG_FIRMWARE_MEMMAP) += memmap.o obj-$(CONFIG_MTK_ADSP_IPC) += mtk-adsp-ipc.o obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o obj-$(CONFIG_FW_CFG_SYSFS) += qemu_fw_cfg.o +obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o obj-$(CONFIG_SYSFB) += sysfb.o obj-$(CONFIG_SYSFB_SIMPLEFB) += sysfb_simplefb.o obj-$(CONFIG_TH1520_AON_PROTOCOL) += thead,th1520-aon.o diff --git a/drivers/platform/x86/firmware_attributes_class.c b/drivers/firmware/firmware_attributes_class.c similarity index 94% rename from drivers/platform/x86/firmware_attributes_class.c rename to drivers/firmware/firmware_attributes_class.c index 736e96c186d9dc..503ecc288086bb 100644 --- a/drivers/platform/x86/firmware_attributes_class.c +++ b/drivers/firmware/firmware_attributes_class.c @@ -3,7 +3,7 @@ /* Firmware attributes class helper module */ #include -#include "firmware_attributes_class.h" +#include const struct class firmware_attributes_class = { .name = "firmware-attributes", diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 957034f39e4e7a..4851bf45157dc0 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -925,9 +925,6 @@ config DASHARO_ACPI source "drivers/platform/x86/x86-android-tablets/Kconfig" -config FW_ATTR_CLASS - tristate - config INTEL_IMR bool "Intel Isolated Memory Region support" depends on X86_INTEL_QUARK && IOSF_MBI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 872ac3842391f7..cab20a4145e30c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -63,8 +63,6 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_HP) += hp/ # Hewlett Packard Enterprise obj-$(CONFIG_UV_SYSFS) += uv_sysfs.o -obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o - # IBM Thinkpad (before 2005) obj-$(CONFIG_IBM_RTL) += ibm_rtl.o obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o diff --git a/drivers/platform/x86/asus-armoury.c b/drivers/platform/x86/asus-armoury.c index 93d9665717afa5..fb382de5b57b55 100644 --- a/drivers/platform/x86/asus-armoury.c +++ b/drivers/platform/x86/asus-armoury.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,6 @@ #include #include "asus-armoury.h" -#include "firmware_attributes_class.h" #define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index ab46a023cc34d0..ae89670f73b4ba 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -10,13 +10,13 @@ #include #include #include +#include #include #include #include #include #include #include "dell-wmi-sysman.h" -#include "../../firmware_attributes_class.h" #define MAX_TYPES 4 #include diff --git a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c index 309634c1cc20ee..d5a2d0963bbf30 100644 --- a/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c +++ b/drivers/platform/x86/hp/hp-bioscfg/bioscfg.c @@ -7,6 +7,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -14,7 +15,6 @@ #include #include #include "bioscfg.h" -#include "../../firmware_attributes_class.h" #include #include diff --git a/drivers/platform/x86/lenovo/think-lmi.c b/drivers/platform/x86/lenovo/think-lmi.c index a0e3fa766e37b5..ebfc6182986e47 100644 --- a/drivers/platform/x86/lenovo/think-lmi.c +++ b/drivers/platform/x86/lenovo/think-lmi.c @@ -14,13 +14,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include "../firmware_attributes_class.h" #include "think-lmi.h" static bool debug_support; diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index fbb32bf404f245..b175971bb308ec 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include "wmi-capdata.h" #include "wmi-events.h" #include "wmi-helpers.h" -#include "../firmware_attributes_class.h" #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c index 99f72b204a51c7..88ef11004ad068 100644 --- a/drivers/platform/x86/samsung-galaxybook.c +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,7 +29,6 @@ #include #include #include -#include "firmware_attributes_class.h" #define DRIVER_NAME "samsung-galaxybook" diff --git a/drivers/platform/x86/firmware_attributes_class.h b/include/linux/firmware_attributes.h similarity index 60% rename from drivers/platform/x86/firmware_attributes_class.h rename to include/linux/firmware_attributes.h index d27abe54fcf981..d4026ba1fcc29a 100644 --- a/drivers/platform/x86/firmware_attributes_class.h +++ b/include/linux/firmware_attributes.h @@ -2,11 +2,11 @@ /* Firmware attributes class helper module */ -#ifndef FW_ATTR_CLASS_H -#define FW_ATTR_CLASS_H +#ifndef _LINUX_FIRMWARE_ATTRIBUTES_H +#define _LINUX_FIRMWARE_ATTRIBUTES_H #include extern const struct class firmware_attributes_class; -#endif /* FW_ATTR_CLASS_H */ +#endif /* _LINUX_FIRMWARE_ATTRIBUTES_H */ From f2e05ce9763feb1dd7da15afd2cc61df227ee85a Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Tue, 1 Sep 2026 18:48:05 +0100 Subject: [PATCH 1088/2286] firmware: coreboot: Add CFR firmware attributes driver coreboot can publish a CFR tree in its coreboot table to describe firmware setup options. Add a firmware-attributes driver for that table entry under the coreboot firmware driver directory. The driver exposes runtime EFI variable-backed boolean, enum and integer settings through the firmware-attributes ABI. It only registers settings with readable runtime EFI variables, skips suppressed, volatile, dependency-gated and non-runtime options, and keeps read-only or inactive options read-only in sysfs. For settings with an APM_CNT runtime-apply record, write the EFI variable and ask firmware to apply the setting immediately with an I/O port SMI command. If firmware reports a failure, restore the old EFI variable value. Settings without a runtime apply hook keep the EFI variable update and set pending_reboot. Depend on HAS_IOPORT because APM_CNT uses legacy I/O ports. Tested on a Star Labs Byte with firmware 26.07: runtime APM_CNT apply was verified with power_led, and reboot-needed behavior was verified with debug_level. Link: https://doc.coreboot.org/drivers/cfr.html Link: https://doc.coreboot.org/drivers/cfr_internal.html Signed-off-by: Sean Rhodes Link: https://lore.kernel.org/r/73204be94586622903da7fa90757fb091e8a7c2c.1788284852.git.sean@starlabs.systems Signed-off-by: Tzung-Bi Shih --- MAINTAINERS | 6 + drivers/firmware/coreboot/Kconfig | 15 + drivers/firmware/coreboot/Makefile | 1 + drivers/firmware/coreboot/coreboot-cfr.c | 1204 ++++++++++++++++++++++ 4 files changed, 1226 insertions(+) create mode 100644 drivers/firmware/coreboot/coreboot-cfr.c diff --git a/MAINTAINERS b/MAINTAINERS index c22cb3d73b4d35..3dd5c138ecde8a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6716,6 +6716,12 @@ F: tools/testing/selftests/cgroup/test_hugetlb_memcg.c F: tools/testing/selftests/cgroup/test_kmem.c F: tools/testing/selftests/cgroup/test_memcontrol.c +COREBOOT CFR FIRMWARE ATTRIBUTES DRIVER +M: Sean Rhodes +L: chrome-platform@lists.linux.dev +S: Maintained +F: drivers/firmware/coreboot/coreboot-cfr.c + COREBOOT FIRMWARE DRIVERS M: Tzung-Bi Shih R: Brian Norris diff --git a/drivers/firmware/coreboot/Kconfig b/drivers/firmware/coreboot/Kconfig index d273bd836245eb..acfc7d1872281d 100644 --- a/drivers/firmware/coreboot/Kconfig +++ b/drivers/firmware/coreboot/Kconfig @@ -49,6 +49,21 @@ config COREBOOT_TABLE device tree node /firmware/coreboot. If unsure say N. +config COREBOOT_CFR + tristate "coreboot CFR firmware attributes" + depends on EFI + depends on COREBOOT_TABLE + depends on HAS_IOPORT + select CRC32 + select FW_ATTR_CLASS + help + This driver parses the coreboot CFR table and exposes runtime EFI + variable-backed options through the firmware-attributes sysfs + interface. + + If you have a system running coreboot with CFR runtime + configuration support, say Y or M here. + config COREBOOT_MEMCONSOLE_COMMON tristate depends on GOOGLE_MEMCONSOLE_X86_LEGACY || COREBOOT_MEMCONSOLE diff --git a/drivers/firmware/coreboot/Makefile b/drivers/firmware/coreboot/Makefile index e681f274417abb..a08d167a95a83f 100644 --- a/drivers/firmware/coreboot/Makefile +++ b/drivers/firmware/coreboot/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_GOOGLE_SMI) += gsmi.o obj-$(CONFIG_COREBOOT_TABLE) += coreboot_table.o +obj-$(CONFIG_COREBOOT_CFR) += coreboot-cfr.o obj-$(CONFIG_COREBOOT_FRAMEBUFFER) += framebuffer-coreboot.o obj-$(CONFIG_COREBOOT_MEMCONSOLE_COMMON) += memconsole.o obj-$(CONFIG_COREBOOT_MEMCONSOLE) += memconsole-coreboot.o diff --git a/drivers/firmware/coreboot/coreboot-cfr.c b/drivers/firmware/coreboot/coreboot-cfr.c new file mode 100644 index 00000000000000..2d9ea5d213d824 --- /dev/null +++ b/drivers/firmware/coreboot/coreboot-cfr.c @@ -0,0 +1,1204 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * coreboot CFR firmware attributes driver. + * + * Parses LB_TAG_CFR_ROOT records from the coreboot table and exposes + * runtime EFI variable-backed options through the firmware-attributes class. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coreboot_table.h" + +#define DRIVER_NAME "coreboot-cfr" + +#define LB_TAG_CFR_ROOT 0x47 +#define CFR_VERSION 0 + +enum cfr_tags { + CFR_TAG_OPTION_FORM = 1, + CFR_TAG_ENUM_VALUE = 2, + CFR_TAG_OPTION_ENUM = 3, + CFR_TAG_OPTION_NUMBER = 4, + CFR_TAG_OPTION_BOOL = 5, + CFR_TAG_VARCHAR_OPT_NAME = 7, + CFR_TAG_VARCHAR_UI_NAME = 8, + CFR_TAG_RUNTIME_APPLY = 13, +}; + +enum cfr_option_flags { + CFR_OPTFLAG_READONLY = BIT(0), + CFR_OPTFLAG_INACTIVE = BIT(1), + CFR_OPTFLAG_SUPPRESS = BIT(2), + CFR_OPTFLAG_VOLATILE = BIT(3), + CFR_OPTFLAG_RUNTIME = BIT(4), +}; + +enum cfr_runtime_apply_method { + CFR_RUNTIME_APPLY_NONE = 0, + CFR_RUNTIME_APPLY_APM_CNT = 1, +}; + +struct lb_cfr { + u32 tag; + u32 size; + u32 version; + u32 checksum; +} __packed; + +struct lb_cfr_varbinary { + u32 tag; + u32 size; + u32 data_length; +} __packed; + +struct lb_cfr_enum_value { + u32 tag; + u32 size; + u32 value; +} __packed; + +struct lb_cfr_runtime_apply { + u32 tag; + u32 size; + u32 method; + u32 id; +} __packed; + +struct lb_cfr_numeric_option { + u32 tag; + u32 size; + cb_u64 object_id; + cb_u64 dependency_id; + u32 flags; + u32 default_value; + u32 min; + u32 max; + u32 step; + u32 display_flags; +} __packed; + +struct lb_cfr_option_form { + u32 tag; + u32 size; + cb_u64 object_id; + cb_u64 dependency_id; + u32 flags; +} __packed; + +#define COREBOOT_CFR_OPT_SKIP_FLAGS \ + (CFR_OPTFLAG_SUPPRESS | CFR_OPTFLAG_VOLATILE) + +#define COREBOOT_CFR_OPT_READ_ONLY_FLAGS \ + (CFR_OPTFLAG_READONLY | CFR_OPTFLAG_INACTIVE) + +#define COREBOOT_CFR_EFI_ATTRS \ + (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | \ + EFI_VARIABLE_RUNTIME_ACCESS) + +#define COREBOOT_CFR_APM_CNT_PORT 0xb2 +#define COREBOOT_CFR_APM_STS_PORT 0xb3 +#define COREBOOT_CFR_APM_APPLY_CMD 0xe3 +#define COREBOOT_CFR_MAX_FORM_DEPTH 16 + +static efi_guid_t coreboot_cfr_guid = EFI_GUID(0xceae4c1d, 0x335b, 0x4685, + 0xa4, 0xa0, 0xfc, 0x4a, + 0x94, 0xee, 0xa0, 0x85); + +enum coreboot_cfr_setting_type { + COREBOOT_CFR_SETTING_ENUM, + COREBOOT_CFR_SETTING_NUMBER, + COREBOOT_CFR_SETTING_BOOL, +}; + +struct coreboot_cfr_enum { + char *label; + u32 value; +}; + +struct coreboot_cfr_setting { + struct kobject kobj; + struct list_head node; + struct coreboot_cfr_drvdata *drvdata; + enum coreboot_cfr_setting_type type; + char *name; + char *display_name; + struct coreboot_cfr_enum *values; + unsigned int n_values; + u32 default_value; + u32 min; + u32 max; + u32 step; + u32 runtime_apply_method; + u32 runtime_apply_id; + bool read_only; +}; + +struct coreboot_cfr_drvdata { + struct device *class_dev; + struct kset *attrs_kset; + struct list_head settings; + /* Serializes EFI variable writes and the matching runtime apply hook. */ + struct mutex lock; + bool efi_writes_supported; + bool pending_reboot; +}; + +struct coreboot_cfr_iterator { + const u8 *cursor; + size_t remaining; +}; + +static struct coreboot_cfr_setting *to_coreboot_cfr_setting(struct kobject *kobj) +{ + return container_of(kobj, struct coreboot_cfr_setting, kobj); +} + +static bool coreboot_cfr_string_is_valid_name(const char *name) +{ + return name && name[0] && !strchr(name, '/') && + strcmp(name, "pending_reboot") != 0; +} + +static bool coreboot_cfr_string_is_valid_label(const char *label) +{ + return label && !strchr(label, ';') && !strchr(label, '\n'); +} + +static char *coreboot_cfr_string_dup(const struct lb_cfr_varbinary *str) +{ + const char *data = (const char *)(str + 1); + size_t len = str->data_length; + + if (len && !data[len - 1]) + len--; + + return kmemdup_nul(data, len, GFP_KERNEL); +} + +static const struct coreboot_table_entry * +coreboot_cfr_next_entry(struct coreboot_cfr_iterator *iterator) +{ + const struct coreboot_table_entry *entry; + + if (!iterator->remaining) + return NULL; + + if (iterator->remaining < sizeof(*entry)) + return ERR_PTR(-EINVAL); + + entry = (const struct coreboot_table_entry *)iterator->cursor; + if (entry->size < sizeof(*entry) || entry->size > iterator->remaining) + return ERR_PTR(-EINVAL); + + iterator->cursor += entry->size; + iterator->remaining -= entry->size; + + return entry; +} + +static const struct coreboot_table_entry * +coreboot_cfr_child_entry(const void *base, size_t len, u32 tag) +{ + struct coreboot_cfr_iterator iterator = { + .cursor = base, + .remaining = len, + }; + const struct coreboot_table_entry *entry; + + for (;;) { + entry = coreboot_cfr_next_entry(&iterator); + if (IS_ERR_OR_NULL(entry)) + return entry; + + if (entry->tag == tag) + return entry; + } +} + +static const struct lb_cfr_varbinary * +coreboot_cfr_child_string(const void *base, size_t len, u32 tag) +{ + const struct lb_cfr_varbinary *str; + const struct coreboot_table_entry *entry; + + entry = coreboot_cfr_child_entry(base, len, tag); + if (IS_ERR(entry)) + return ERR_CAST(entry); + if (!entry) + return NULL; + + if (entry->size < sizeof(*str)) + return ERR_PTR(-EINVAL); + + str = (const struct lb_cfr_varbinary *)entry; + if (str->data_length > entry->size - sizeof(*str)) + return ERR_PTR(-EINVAL); + + return str; +} + +static const struct lb_cfr_runtime_apply * +coreboot_cfr_child_runtime_apply(const void *base, size_t len) +{ + const struct lb_cfr_runtime_apply *runtime_apply; + const struct coreboot_table_entry *entry; + + entry = coreboot_cfr_child_entry(base, len, CFR_TAG_RUNTIME_APPLY); + if (IS_ERR(entry)) + return ERR_CAST(entry); + if (!entry) + return NULL; + + if (entry->size < sizeof(*runtime_apply)) + return ERR_PTR(-EINVAL); + + runtime_apply = (const struct lb_cfr_runtime_apply *)entry; + if (runtime_apply->method == CFR_RUNTIME_APPLY_APM_CNT && + runtime_apply->id > U8_MAX) + return ERR_PTR(-EINVAL); + + return runtime_apply; +} + +static efi_char16_t *coreboot_cfr_efi_name(const char *name) +{ + size_t len, i; + + len = strlen(name); + if (len >= EFI_VAR_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + efi_char16_t *efi_name __free(kfree) = + kcalloc(len + 1, sizeof(*efi_name), GFP_KERNEL); + if (!efi_name) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < len; i++) { + if (!isascii(name[i])) + return ERR_PTR(-EINVAL); + efi_name[i] = name[i]; + } + + return no_free_ptr(efi_name); +} + +/* Caller must hold the efivar lock. */ +static int coreboot_cfr_read_efi_value(efi_char16_t *efi_name, u32 *value, + u32 *attrs) +{ + unsigned long size = sizeof(__le32); + efi_status_t status; + __le32 data; + u32 attr; + + status = efivar_get_variable(efi_name, &coreboot_cfr_guid, &attr, + &size, &data); + if (status != EFI_SUCCESS) + return efi_status_to_err(status); + + if (size != sizeof(data)) + return -EINVAL; + + if (!(attr & EFI_VARIABLE_RUNTIME_ACCESS)) + return -EOPNOTSUPP; + + *value = le32_to_cpu(data); + if (attrs) + *attrs = attr; + + return 0; +} + +static int coreboot_cfr_read_value(const struct coreboot_cfr_setting *setting, + u32 *value, u32 *attrs) +{ + efi_char16_t *efi_name __free(kfree) = + coreboot_cfr_efi_name(setting->name); + int ret; + + if (IS_ERR(efi_name)) + return PTR_ERR(no_free_ptr(efi_name)); + + ret = efivar_lock(); + if (ret) + return ret; + + ret = coreboot_cfr_read_efi_value(efi_name, value, attrs); + efivar_unlock(); + + return ret; +} + +/* Caller must hold the efivar lock. */ +static int coreboot_cfr_write_efi_value(efi_char16_t *efi_name, u32 value, + u32 attrs) +{ + efi_status_t status; + __le32 data; + + if (!efivar_supports_writes()) + return -EROFS; + + data = cpu_to_le32(value); + status = efivar_set_variable_locked(efi_name, &coreboot_cfr_guid, attrs, + sizeof(data), &data, false); + if (status != EFI_SUCCESS) + return efi_status_to_err(status); + + return 0; +} + +static int coreboot_cfr_apply_runtime(struct coreboot_cfr_setting *setting) +{ + u8 status; + + if (setting->runtime_apply_method != CFR_RUNTIME_APPLY_APM_CNT) + return -EOPNOTSUPP; + + outb((u8)setting->runtime_apply_id, COREBOOT_CFR_APM_STS_PORT); + outb(COREBOOT_CFR_APM_APPLY_CMD, COREBOOT_CFR_APM_CNT_PORT); + status = inb(COREBOOT_CFR_APM_STS_PORT); + if (status) + return -EIO; + + return 0; +} + +static int coreboot_cfr_write_value(struct coreboot_cfr_setting *setting, + u32 value) +{ + efi_char16_t *efi_name; + u32 attrs; + u32 old; + int restore_ret; + int ret; + + if (setting->read_only) + return -EACCES; + + efi_name = coreboot_cfr_efi_name(setting->name); + if (IS_ERR(efi_name)) + return PTR_ERR(efi_name); + + mutex_lock(&setting->drvdata->lock); + + ret = efivar_lock(); + if (ret) + goto out_unlock_mutex; + + ret = coreboot_cfr_read_efi_value(efi_name, &old, &attrs); + if (ret) + goto out_unlock_efi; + + if ((attrs & COREBOOT_CFR_EFI_ATTRS) != COREBOOT_CFR_EFI_ATTRS) { + ret = -EOPNOTSUPP; + goto out_unlock_efi; + } + + if (old == value) + goto out_unlock_efi; + + ret = coreboot_cfr_write_efi_value(efi_name, value, attrs); + if (ret) + goto out_unlock_efi; + + ret = coreboot_cfr_apply_runtime(setting); + if (ret == -EOPNOTSUPP) { + /* EFI changed; firmware will consume it after reboot. */ + setting->drvdata->pending_reboot = true; + ret = 0; + } else if (ret) { + restore_ret = coreboot_cfr_write_efi_value(efi_name, old, attrs); + if (restore_ret) { + setting->drvdata->pending_reboot = true; + ret = restore_ret; + } else { + goto out_unlock_efi; + } + } + + efivar_unlock(); + kobject_uevent(&setting->drvdata->class_dev->kobj, KOBJ_CHANGE); + mutex_unlock(&setting->drvdata->lock); + kfree(efi_name); + return ret; + +out_unlock_efi: + efivar_unlock(); +out_unlock_mutex: + mutex_unlock(&setting->drvdata->lock); + kfree(efi_name); + return ret; +} + +static const char * +coreboot_cfr_label_from_value(const struct coreboot_cfr_setting *setting, + u32 value) +{ + unsigned int i; + + for (i = 0; i < setting->n_values; i++) { + if (setting->values[i].value == value) + return setting->values[i].label; + } + + return NULL; +} + +static int coreboot_cfr_parse_value(struct coreboot_cfr_setting *setting, + const char *label, u32 *value_out) +{ + unsigned int i; + + for (i = 0; i < setting->n_values; i++) { + if (!sysfs_streq(label, setting->values[i].label)) + continue; + + *value_out = setting->values[i].value; + return 0; + } + + return kstrtou32(label, 0, value_out); +} + +static bool coreboot_cfr_value_is_valid(struct coreboot_cfr_setting *setting, + u32 value) +{ + u32 delta; + + if (setting->type != COREBOOT_CFR_SETTING_NUMBER) { + /* Enum and bool values are valid only if they have a label. */ + return coreboot_cfr_label_from_value(setting, value); + } + + if (value < setting->min || value > setting->max) + return false; + + if (!setting->step) + return true; + + delta = value - setting->min; + return delta % setting->step == 0; +} + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + if (setting->type == COREBOOT_CFR_SETTING_NUMBER) + return sysfs_emit(buf, "integer\n"); + + return sysfs_emit(buf, "enumeration\n"); +} + +static ssize_t display_name_language_code_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "en_US.UTF-8\n"); +} + +static ssize_t display_name_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + return sysfs_emit(buf, "%s\n", setting->display_name); +} + +static ssize_t possible_values_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + ssize_t len = 0; + unsigned int i; + + for (i = 0; i < setting->n_values; i++) { + len += sysfs_emit_at(buf, len, "%s%s", i ? ";" : "", + setting->values[i].label); + } + + len += sysfs_emit_at(buf, len, "\n"); + return len; +} + +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + return sysfs_emit(buf, "%u\n", setting->min); +} + +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + return sysfs_emit(buf, "%u\n", setting->max); +} + +static ssize_t scalar_increment_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + return sysfs_emit(buf, "%u\n", setting->step); +} + +static ssize_t default_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + const char *label; + + if (setting->type == COREBOOT_CFR_SETTING_NUMBER) + return sysfs_emit(buf, "%u\n", setting->default_value); + + label = coreboot_cfr_label_from_value(setting, setting->default_value); + if (!label) + return sysfs_emit(buf, "%u\n", setting->default_value); + + return sysfs_emit(buf, "%s\n", label); +} + +static ssize_t current_value_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + const char *label; + u32 value; + int ret; + + ret = coreboot_cfr_read_value(setting, &value, NULL); + if (ret) + return ret; + + if (setting->type == COREBOOT_CFR_SETTING_NUMBER) + return sysfs_emit(buf, "%u\n", value); + + label = coreboot_cfr_label_from_value(setting, value); + if (!label) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", label); +} + +static ssize_t current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + u32 value; + int ret; + + ret = coreboot_cfr_parse_value(setting, buf, &value); + if (ret) + return ret; + + if (!coreboot_cfr_value_is_valid(setting, value)) + return -EINVAL; + + ret = coreboot_cfr_write_value(setting, value); + if (ret) + return ret; + + return count; +} + +static struct kobj_attribute type_attr = __ATTR_RO(type); +static struct kobj_attribute display_name_language_code_attr = + __ATTR_RO(display_name_language_code); +static struct kobj_attribute display_name_attr = __ATTR_RO(display_name); +static struct kobj_attribute possible_values_attr = __ATTR_RO(possible_values); +static struct kobj_attribute min_value_attr = __ATTR_RO(min_value); +static struct kobj_attribute max_value_attr = __ATTR_RO(max_value); +static struct kobj_attribute scalar_increment_attr = __ATTR_RO(scalar_increment); +static struct kobj_attribute default_value_attr = __ATTR_RO(default_value); +static struct kobj_attribute current_value_attr = __ATTR_RW(current_value); + +static struct attribute *coreboot_cfr_setting_attrs[] = { + &type_attr.attr, + &display_name_language_code_attr.attr, + &display_name_attr.attr, + &possible_values_attr.attr, + &min_value_attr.attr, + &max_value_attr.attr, + &scalar_increment_attr.attr, + &default_value_attr.attr, + ¤t_value_attr.attr, + NULL, +}; + +static umode_t coreboot_cfr_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct coreboot_cfr_setting *setting = to_coreboot_cfr_setting(kobj); + + if (setting->type == COREBOOT_CFR_SETTING_NUMBER && + attr == &possible_values_attr.attr) + return 0; + + if (setting->type != COREBOOT_CFR_SETTING_NUMBER && + (attr == &min_value_attr.attr || attr == &max_value_attr.attr || + attr == &scalar_increment_attr.attr)) + return 0; + + if (setting->read_only && attr == ¤t_value_attr.attr) + return 0444; + + return attr->mode; +} + +static const struct attribute_group coreboot_cfr_setting_group = { + .attrs = coreboot_cfr_setting_attrs, + .is_visible = coreboot_cfr_attr_is_visible, +}; + +static void coreboot_cfr_free_setting(struct coreboot_cfr_setting *setting) +{ + unsigned int i; + + for (i = 0; i < setting->n_values; i++) + kfree(setting->values[i].label); + + kfree(setting->values); + kfree(setting->display_name); + kfree(setting->name); + kfree(setting); +} + +static void coreboot_cfr_setting_release(struct kobject *kobj) +{ + coreboot_cfr_free_setting(to_coreboot_cfr_setting(kobj)); +} + +static const struct kobj_type coreboot_cfr_setting_ktype = { + .release = coreboot_cfr_setting_release, + .sysfs_ops = &kobj_sysfs_ops, +}; + +static ssize_t pending_reboot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct device *dev = kobj_to_dev(kobj->parent); + struct coreboot_cfr_drvdata *data; + + data = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", data->pending_reboot); +} + +static struct kobj_attribute pending_reboot_attr = __ATTR_RO(pending_reboot); + +static int coreboot_cfr_copy_bool_values(struct coreboot_cfr_setting *setting) +{ + static const struct coreboot_cfr_enum bool_values[] = { + { .label = "Disabled", .value = 0 }, + { .label = "Enabled", .value = 1 }, + }; + unsigned int i; + + setting->values = kcalloc(ARRAY_SIZE(bool_values), sizeof(*setting->values), + GFP_KERNEL); + if (!setting->values) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(bool_values); i++) { + setting->values[i].label = kstrdup(bool_values[i].label, + GFP_KERNEL); + if (!setting->values[i].label) + return -ENOMEM; + setting->values[i].value = bool_values[i].value; + setting->n_values++; + } + + return 0; +} + +static int coreboot_cfr_count_enum_values(const void *base, size_t len) +{ + struct coreboot_cfr_iterator iterator = { + .cursor = base, + .remaining = len, + }; + const struct coreboot_table_entry *entry; + int count = 0; + + for (;;) { + entry = coreboot_cfr_next_entry(&iterator); + if (IS_ERR(entry)) + return PTR_ERR(entry); + if (!entry) + return count; + + if (entry->tag == CFR_TAG_ENUM_VALUE) + count++; + } +} + +static int coreboot_cfr_copy_enum_values(struct coreboot_cfr_setting *setting, + const void *base, size_t len) +{ + struct coreboot_cfr_iterator iterator = { + .cursor = base, + .remaining = len, + }; + const struct lb_cfr_enum_value *enum_value; + const struct lb_cfr_varbinary *label; + const struct coreboot_table_entry *entry; + struct coreboot_cfr_enum *value; + int count; + + count = coreboot_cfr_count_enum_values(base, len); + if (count <= 0) + return count ?: -EINVAL; + + setting->values = kcalloc(count, sizeof(*setting->values), GFP_KERNEL); + if (!setting->values) + return -ENOMEM; + + for (;;) { + entry = coreboot_cfr_next_entry(&iterator); + if (IS_ERR(entry)) + return PTR_ERR(entry); + if (!entry) + return 0; + + if (entry->tag != CFR_TAG_ENUM_VALUE) + continue; + + if (entry->size < sizeof(*enum_value)) + return -EINVAL; + + enum_value = (const struct lb_cfr_enum_value *)entry; + label = coreboot_cfr_child_string(enum_value + 1, + enum_value->size - sizeof(*enum_value), + CFR_TAG_VARCHAR_UI_NAME); + if (IS_ERR(label)) + return PTR_ERR(label); + if (!label) + return -EINVAL; + + value = &setting->values[setting->n_values]; + value->label = coreboot_cfr_string_dup(label); + if (!value->label) + return -ENOMEM; + if (!coreboot_cfr_string_is_valid_label(value->label)) { + kfree(value->label); + value->label = NULL; + return -EINVAL; + } + + value->value = enum_value->value; + setting->n_values++; + } +} + +static bool +coreboot_cfr_possible_values_fit(const struct coreboot_cfr_setting *setting) +{ + size_t len = 1; /* Trailing newline. */ + size_t label_len; + unsigned int i; + + for (i = 0; i < setting->n_values; i++) { + if (len >= PAGE_SIZE) + return false; + + if (i) + len++; + + label_len = strlen(setting->values[i].label); + if (label_len >= PAGE_SIZE - len) + return false; + + len += label_len; + } + + return true; +} + +static int coreboot_cfr_setting_is_usable(struct coreboot_cfr_setting *setting) +{ + u32 value; + int ret; + + ret = coreboot_cfr_read_value(setting, &value, NULL); + if (ret) + return ret; + + if (!coreboot_cfr_value_is_valid(setting, value)) + return -EINVAL; + + return 0; +} + +static int coreboot_cfr_register_setting(struct coreboot_cfr_drvdata *data, + struct coreboot_cfr_setting *setting) +{ + int ret; + + ret = kobject_init_and_add(&setting->kobj, &coreboot_cfr_setting_ktype, + &data->attrs_kset->kobj, "%s", setting->name); + if (ret) + goto err_put_kobj; + + ret = sysfs_create_group(&setting->kobj, &coreboot_cfr_setting_group); + if (ret) + goto err_put_kobj; + + list_add_tail(&setting->node, &data->settings); + return 0; + +err_put_kobj: + kobject_put(&setting->kobj); + return ret; +} + +static int coreboot_cfr_add_numeric_option(struct coreboot_cfr_drvdata *data, + const struct lb_cfr_numeric_option *option, + bool parent_read_only) +{ + const struct lb_cfr_varbinary *name; + const struct lb_cfr_varbinary *display_name; + const struct lb_cfr_runtime_apply *runtime_apply; + const void *child_base = option + 1; + struct coreboot_cfr_setting *setting; + size_t child_len = option->size - sizeof(*option); + int ret; + + if (!(option->flags & CFR_OPTFLAG_RUNTIME)) + return 0; + + if (option->flags & COREBOOT_CFR_OPT_SKIP_FLAGS) + return 0; + + if (option->dependency_id) + return 0; + + setting = kzalloc_obj(*setting, GFP_KERNEL); + if (!setting) + return -ENOMEM; + + INIT_LIST_HEAD(&setting->node); + setting->drvdata = data; + setting->default_value = option->default_value; + setting->min = option->min; + setting->max = option->max; + setting->step = option->step ?: 1; + setting->read_only = + (option->flags & COREBOOT_CFR_OPT_READ_ONLY_FLAGS) || + !data->efi_writes_supported || parent_read_only; + + runtime_apply = coreboot_cfr_child_runtime_apply(child_base, child_len); + if (IS_ERR(runtime_apply)) { + ret = PTR_ERR(runtime_apply); + goto err_put_setting; + } + + if (runtime_apply && runtime_apply->method == CFR_RUNTIME_APPLY_APM_CNT) { + setting->runtime_apply_method = runtime_apply->method; + setting->runtime_apply_id = runtime_apply->id; + } + + name = coreboot_cfr_child_string(child_base, child_len, + CFR_TAG_VARCHAR_OPT_NAME); + if (IS_ERR(name)) { + ret = PTR_ERR(name); + goto err_put_setting; + } + if (!name) { + ret = -EINVAL; + goto err_put_setting; + } + + setting->name = coreboot_cfr_string_dup(name); + if (!setting->name) { + ret = -ENOMEM; + goto err_put_setting; + } + + if (!coreboot_cfr_string_is_valid_name(setting->name)) { + ret = -EINVAL; + goto err_put_setting; + } + + display_name = coreboot_cfr_child_string(child_base, child_len, + CFR_TAG_VARCHAR_UI_NAME); + if (IS_ERR(display_name)) { + ret = PTR_ERR(display_name); + goto err_put_setting; + } + if (display_name) + setting->display_name = coreboot_cfr_string_dup(display_name); + else + setting->display_name = kstrdup(setting->name, GFP_KERNEL); + if (!setting->display_name) { + ret = -ENOMEM; + goto err_put_setting; + } + + switch (option->tag) { + case CFR_TAG_OPTION_BOOL: + setting->type = COREBOOT_CFR_SETTING_BOOL; + setting->min = 0; + setting->max = 1; + setting->step = 1; + ret = coreboot_cfr_copy_bool_values(setting); + break; + case CFR_TAG_OPTION_ENUM: + setting->type = COREBOOT_CFR_SETTING_ENUM; + ret = coreboot_cfr_copy_enum_values(setting, child_base, + child_len); + break; + case CFR_TAG_OPTION_NUMBER: + setting->type = COREBOOT_CFR_SETTING_NUMBER; + if (setting->max < setting->min) + ret = -EINVAL; + else + ret = 0; + break; + default: + ret = -EINVAL; + break; + } + + if (ret) + goto err_put_setting; + /* possible_values must be returned completely in one sysfs read. */ + if (setting->type != COREBOOT_CFR_SETTING_NUMBER && + !coreboot_cfr_possible_values_fit(setting)) { + ret = 0; + goto err_put_setting; + } + + ret = coreboot_cfr_setting_is_usable(setting); + if (ret) { + /* + * CFR may describe options without a matching, readable runtime + * EFI variable. Skip those, but propagate transient EFI failures. + */ + if (ret == -ENOENT || ret == -EINVAL || ret == -EOPNOTSUPP || + ret == -ENAMETOOLONG) + ret = 0; + goto err_put_setting; + } + + /* The kobject release callback owns setting after this call. */ + return coreboot_cfr_register_setting(data, setting); + +err_put_setting: + coreboot_cfr_free_setting(setting); + return ret; +} + +static int coreboot_cfr_parse_records(struct coreboot_cfr_drvdata *data, + const void *base, size_t len, + unsigned int depth, + bool parent_read_only) +{ + struct coreboot_cfr_iterator iterator = { + .cursor = base, + .remaining = len, + }; + const struct lb_cfr_option_form *form; + const struct lb_cfr_numeric_option *option; + const struct coreboot_table_entry *entry; + const void *child_base; + size_t child_len; + int ret; + + for (;;) { + entry = coreboot_cfr_next_entry(&iterator); + if (IS_ERR(entry)) + return PTR_ERR(entry); + if (!entry) + return 0; + + switch (entry->tag) { + case CFR_TAG_OPTION_FORM: + if (entry->size < sizeof(struct lb_cfr_option_form)) + return -EINVAL; + + form = (const struct lb_cfr_option_form *)entry; + if (form->dependency_id || + form->flags & CFR_OPTFLAG_SUPPRESS) + break; + + if (depth >= COREBOOT_CFR_MAX_FORM_DEPTH) + return -E2BIG; + + child_base = form + 1; + child_len = entry->size - sizeof(struct lb_cfr_option_form); + ret = coreboot_cfr_parse_records(data, child_base, + child_len, depth + 1, + parent_read_only || + (form->flags & + COREBOOT_CFR_OPT_READ_ONLY_FLAGS)); + if (ret) + return ret; + break; + case CFR_TAG_OPTION_ENUM: + case CFR_TAG_OPTION_NUMBER: + case CFR_TAG_OPTION_BOOL: + option = (const struct lb_cfr_numeric_option *)entry; + if (entry->size < sizeof(*option)) + return -EINVAL; + ret = coreboot_cfr_add_numeric_option(data, option, + parent_read_only); + if (ret) + return ret; + break; + default: + /* Ignore unsupported, child-only and future record types. */ + break; + } + } +} + +static void coreboot_cfr_unregister_settings(struct coreboot_cfr_drvdata *data) +{ + struct coreboot_cfr_setting *setting, *tmp; + + list_for_each_entry_safe(setting, tmp, &data->settings, node) { + sysfs_remove_group(&setting->kobj, &coreboot_cfr_setting_group); + list_del(&setting->node); + kobject_put(&setting->kobj); + } +} + +static int coreboot_cfr_probe(struct coreboot_device *dev) +{ + const struct lb_cfr *root = (const struct lb_cfr *)dev->raw; + struct coreboot_cfr_drvdata *data; + size_t payload_len; + int ret; + + if (dev->entry.size < sizeof(*root)) + return -EINVAL; + + if (root->tag != LB_TAG_CFR_ROOT || root->version != CFR_VERSION) + return -EINVAL; + + if (root->size < sizeof(*root) || root->size > dev->entry.size) + return -EINVAL; + + payload_len = root->size - sizeof(*root); + if (crc32_be(0, root + 1, payload_len) != root->checksum) + return -EBADMSG; + + if (!efivar_is_available()) + return -EPROBE_DEFER; + + data = devm_kzalloc(&dev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ret = efivar_lock(); + if (ret) + return ret; + data->efi_writes_supported = efivar_supports_writes(); + efivar_unlock(); + + INIT_LIST_HEAD(&data->settings); + ret = devm_mutex_init(&dev->dev, &data->lock); + if (ret) + return ret; + + dev_set_drvdata(&dev->dev, data); + + data->class_dev = device_create(&firmware_attributes_class, NULL, + MKDEV(0, 0), NULL, DRIVER_NAME); + if (IS_ERR(data->class_dev)) + return PTR_ERR(data->class_dev); + dev_set_drvdata(data->class_dev, data); + + data->attrs_kset = kset_create_and_add("attributes", NULL, + &data->class_dev->kobj); + if (!data->attrs_kset) { + ret = -ENOMEM; + goto err_unregister_dev; + } + + ret = sysfs_create_file(&data->attrs_kset->kobj, + &pending_reboot_attr.attr); + if (ret) + goto err_unregister_attrs; + + ret = coreboot_cfr_parse_records(data, root + 1, payload_len, 0, false); + if (ret) + goto err_unregister_settings; + + if (list_empty(&data->settings)) { + ret = -ENODEV; + goto err_unregister_settings; + } + + return 0; + +err_unregister_settings: + coreboot_cfr_unregister_settings(data); + sysfs_remove_file(&data->attrs_kset->kobj, &pending_reboot_attr.attr); +err_unregister_attrs: + kset_unregister(data->attrs_kset); +err_unregister_dev: + device_unregister(data->class_dev); + return ret; +} + +static void coreboot_cfr_remove(struct coreboot_device *dev) +{ + struct coreboot_cfr_drvdata *data = dev_get_drvdata(&dev->dev); + + coreboot_cfr_unregister_settings(data); + sysfs_remove_file(&data->attrs_kset->kobj, &pending_reboot_attr.attr); + kset_unregister(data->attrs_kset); + device_unregister(data->class_dev); +} + +static const struct coreboot_device_id coreboot_cfr_ids[] = { + { .tag = LB_TAG_CFR_ROOT }, + { } +}; +MODULE_DEVICE_TABLE(coreboot, coreboot_cfr_ids); + +static struct coreboot_driver coreboot_cfr_driver = { + .probe = coreboot_cfr_probe, + .remove = coreboot_cfr_remove, + .drv = { + .name = DRIVER_NAME, + }, + .id_table = coreboot_cfr_ids, +}; +module_coreboot_driver(coreboot_cfr_driver); + +MODULE_AUTHOR("Sean Rhodes "); +MODULE_DESCRIPTION("coreboot CFR firmware attributes driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("EFIVAR"); From 4c91a2f4805c3b44849526330c4600d4bd2de176 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:49 -0700 Subject: [PATCH 1089/2286] PM: runtime: Correct pm_runtime_autosuspend_expiration() doc The "adjusted to be nonzero" comment may be a relic from when this API previously used jiffies, although I'm not quite sure about that either. In any case, it doesn't seem correct today. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.1.Ifc8b2d1742729cc82f46220d85c6382fdee1fc8b@changeid Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 655c0b8af09545..bb366a295a73a8 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -169,7 +169,7 @@ static void pm_runtime_cancel_pending(struct device *dev) * Compute the autosuspend-delay expiration time based on the device's * power.last_busy time. If the delay has already expired or is disabled * (negative) or the power.use_autosuspend flag isn't set, return 0. - * Otherwise return the expiration time in nanoseconds (adjusted to be nonzero). + * Otherwise return the expiration time in nanoseconds. * * This function may be called either with or without dev->power.lock held. * Either way it can be racy, since power.last_busy may be updated at any time. From f667dd6713d6c5b1140d7e8d73b776f898dfb654 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:50 -0700 Subject: [PATCH 1090/2286] PM: runtime: More kerneldoc formatting In taking another pass at these docs, I found some more inconsistencies. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.2.Ia1cae8a40a24662df11553c225f4438eab65b7db@changeid Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index bb366a295a73a8..304a2b12342082 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1254,12 +1254,11 @@ static int pm_runtime_get_conditional(struct device *dev, bool ign_usage_count) } /** - * pm_runtime_get_if_active - Bump up runtime PM usage counter if the device is - * in active state + * pm_runtime_get_if_active - Conditionally bump up runtime PM usage counter. * @dev: Target device. * * Increment the runtime PM usage counter of @dev if its runtime PM status is - * already %RPM_ACTIVE + * already %RPM_ACTIVE. * * Return: * * %-EINVAL: Runtime PM is disabled for @dev. The usage counter is not incremented. @@ -1607,7 +1606,6 @@ static void pm_runtime_set_suspended_action(void *data) /** * devm_pm_runtime_set_active_enabled - set_active version of devm_pm_runtime_enable. - * * @dev: Device to handle. */ int devm_pm_runtime_set_active_enabled(struct device *dev) @@ -1634,11 +1632,10 @@ static void pm_runtime_disable_action(void *data) /** * devm_pm_runtime_enable - devres-enabled version of pm_runtime_enable. + * @dev: Device to handle. * * NOTE: this will also handle calling pm_runtime_dont_use_autosuspend() for * you at driver exit time if needed. - * - * @dev: Device to handle. */ int devm_pm_runtime_enable(struct device *dev) { @@ -1655,7 +1652,6 @@ static void pm_runtime_put_noidle_action(void *data) /** * devm_pm_runtime_get_noresume - devres-enabled version of pm_runtime_get_noresume. - * * @dev: Device to handle. */ int devm_pm_runtime_get_noresume(struct device *dev) From 437b4ed6cb5848027fe9b9c8d0da495eef7f8de3 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:51 -0700 Subject: [PATCH 1091/2286] PM: runtime: Misc improvements to runtime_pm.rst There are several small errors and omissions, as well as new updates (pm_runtime_resume_and_get(), devm_pm_runtime_enable()) we should incorporate. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.3.I383681b22c12d7caee976cb91aa90d1a94d4a591@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 39fdeeda7a1e92..59b634fd6829fc 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -229,6 +229,8 @@ It is safe to execute the following helper functions from interrupt context: - pm_request_resume() - pm_runtime_get_noresume() - pm_runtime_get() +- pm_runtime_get_if_active() +- pm_runtime_get_if_in_use() - pm_runtime_put_noidle() - pm_runtime_put() - pm_runtime_put_autosuspend() @@ -238,6 +240,9 @@ It is safe to execute the following helper functions from interrupt context: - pm_runtime_set_active() - pm_runtime_set_suspended() - pm_runtime_suspended() +- pm_runtime_active() +- pm_runtime_status_suspended() +- pm_runtime_enabled() - pm_runtime_mark_last_busy() - pm_runtime_autosuspend_expiration() @@ -249,6 +254,7 @@ functions may also be used in interrupt context: - pm_runtime_autosuspend() - pm_runtime_resume() - pm_runtime_get_sync() +- pm_runtime_resume_and_get() - pm_runtime_put_sync() - pm_runtime_put_sync_suspend() - pm_runtime_put_sync_autosuspend() @@ -258,7 +264,7 @@ functions may also be used in interrupt context: Initially, the runtime PM is disabled for all devices, which means that the majority of the runtime PM helper functions described in Section 4 will return --EAGAIN until pm_runtime_enable() is called for the device. +-EACCES until pm_runtime_enable() is called for the device. In addition to that, the initial runtime PM status of all devices is 'suspended', but it need not reflect the actual physical state of the device. @@ -287,7 +293,7 @@ enabled earlier by calling pm_runtime_enable(). Note, if the device may execute pm_runtime calls during the probe (such as if it is registered with a subsystem that may call back in) then the -pm_runtime_get_sync() call paired with a pm_runtime_put() call will be +pm_runtime_resume_and_get() call paired with a pm_runtime_put() call will be appropriate to ensure that the device is not put back to sleep during the probe. This can happen with systems such as the network device layer. @@ -315,7 +321,10 @@ removal of their drivers. Drivers in ->remove() callback should undo the runtime PM changes done in ->probe(). Usually this means calling pm_runtime_disable(), -pm_runtime_dont_use_autosuspend() etc. +pm_runtime_dont_use_autosuspend() etc. Alternatively, drivers can use +devm_pm_runtime_enable() during probe, which automatically takes care of +calling pm_runtime_disable() and pm_runtime_dont_use_autosuspend() upon driver +detachment. The user space can effectively disallow the driver of the device to power manage it at run time by changing the value of its /sys/devices/.../power/control @@ -426,7 +435,7 @@ out the following operations: Subsystems may wish to conserve code space by using the set of generic power management callbacks provided by the PM core, defined in -driver/base/power/generic_ops.c: +drivers/base/power/generic_ops.c: .. kernel-doc:: drivers/base/power/generic_ops.c :export: @@ -441,8 +450,8 @@ subsystem-level dev_pm_ops structure. Device drivers that wish to use the same function as a system suspend, freeze, poweroff and runtime suspend callback, and similarly for system resume, thaw, restore, and runtime resume, can achieve similar behaviour with the help of the -DEFINE_RUNTIME_DEV_PM_OPS() defined in include/linux/pm_runtime.h (possibly setting its -last argument to NULL). +DEFINE_RUNTIME_DEV_PM_OPS() macro defined in include/linux/pm_runtime.h +(possibly setting its last argument to NULL). 8. "No-Callback" Devices ======================== @@ -505,7 +514,7 @@ registration the length should be controlled by user space, using the In order to use autosuspend, subsystems or drivers must call pm_runtime_use_autosuspend() (preferably before registering the device), and -thereafter they should use the various `*_autosuspend()` helper functions +thereafter they should use the various \*_autosuspend() helper functions instead of the non-autosuspend counterparts:: Instead of: pm_runtime_suspend use: pm_runtime_autosuspend; @@ -515,7 +524,7 @@ instead of the non-autosuspend counterparts:: Drivers may also continue to use the non-autosuspend helper functions; they will behave normally, which means sometimes taking the autosuspend delay into -account (see pm_runtime_idle). The autosuspend variants of the functions also +account (see pm_runtime_idle()). The autosuspend variants of the functions also call pm_runtime_mark_last_busy(). Under some circumstances a driver or subsystem may want to prevent a device @@ -558,7 +567,7 @@ Here is a schematic pseudo-code example:: int foo_runtime_suspend(struct device *dev) { - struct foo_priv foo = container_of(dev, ...); + struct foo_priv *foo = container_of(dev, ...); int ret = 0; lock(&foo->private_lock); @@ -574,7 +583,7 @@ Here is a schematic pseudo-code example:: int foo_runtime_resume(struct device *dev) { - struct foo_priv foo = container_of(dev, ...); + struct foo_priv *foo = container_of(dev, ...); lock(&foo->private_lock); /* ... resume the device ... */ From 86122467eea539ac13d40b6938abd1fbb40fb640 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:52 -0700 Subject: [PATCH 1092/2286] PM: runtime: Add "Section" hyperlinks Add reStructuredText targets for each section, and use them throughout to make the generated HTML more navigable. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.4.I7666a5802ea59359afce4ff7d0b1af668e5f5292@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 48 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 59b634fd6829fc..8e93766bdf9967 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -8,6 +8,8 @@ Runtime Power Management Framework for I/O Devices (C) 2014 Intel Corp., Rafael J. Wysocki +.. _Section 1: + 1. Introduction =============== @@ -37,6 +39,8 @@ The runtime PM callbacks present in 'struct dev_pm_ops', the device runtime PM fields of 'struct dev_pm_info' and the core helper functions provided for runtime PM are described below. +.. _Section 2: + 2. Device Runtime PM Callbacks ============================== @@ -80,8 +84,8 @@ the PM core that it is safe to run the ->runtime_suspend(), ->runtime_resume() and ->runtime_idle() callbacks for the given device in atomic context with interrupts disabled. This implies that the callback routines in question must not block or sleep, but it also means that the synchronous helper functions -listed at the end of Section 4 may be used for that device within an interrupt -handler or generally in an atomic context. +listed at the end of `Section 4`_ may be used for that device within an +interrupt handler or generally in an atomic context. The subsystem-level suspend callback, if present, is _entirely_ _responsible_ for handling the suspend of the device as appropriate, which may, but need not @@ -105,9 +109,9 @@ knows what to do to handle the device). * If the suspend callback returns an error code different from -EBUSY and -EAGAIN, the PM core regards this as a fatal error and will refuse to run - the helper functions described in Section 4 for the device until its status - is directly set to either 'active', or 'suspended' (the PM core provides - special helper functions for this purpose). + the helper functions described in `Section 4`_ for the device until its + status is directly set to either 'active', or 'suspended' (the PM core + provides special helper functions for this purpose). In particular, if the driver requires remote wakeup capability (i.e. hardware mechanism allowing the device to request a change of its power state, such as @@ -132,10 +136,10 @@ what to do to handle the device). 'active'. * If the resume callback returns an error code, the PM core regards this as a - fatal error and will refuse to run the helper functions described in Section - 4 for the device, until its status is directly set to either 'active', or - 'suspended' (by means of special helper functions provided by the PM core - for this purpose). + fatal error and will refuse to run the helper functions described in + `Section 4`_ for the device, until its status is directly set to either + 'active', or 'suspended' (by means of special helper functions provided by + the PM core for this purpose). The idle callback (a subsystem-level one, if present, or the driver one) is executed by the PM core whenever the device appears to be idle, which is @@ -158,9 +162,9 @@ call to pm_runtime_autosuspend(). To prevent this (for example, if the callback routine has started a delayed suspend), the routine must return a non-zero value. Negative error return codes are ignored by the PM core. -The helper functions provided by the PM core, described in Section 4, guarantee -that the following constraints are met with respect to runtime PM callbacks for -one device: +The helper functions provided by the PM core, described in `Section 4`_, +guarantee that the following constraints are met with respect to runtime PM +callbacks for one device: (1) The callbacks are mutually exclusive (e.g. it is forbidden to execute ->runtime_suspend() in parallel with ->runtime_resume() or with another @@ -200,6 +204,8 @@ rules: scheduled requests to execute the other callbacks for the same device, except for scheduled autosuspends. +.. _Section 3: + 3. Runtime PM Device Fields =========================== @@ -210,6 +216,8 @@ state. .. kernel-doc:: include/linux/pm.h :identifiers: dev_pm_info +.. _Section 4: + 4. Runtime PM Device Helper Functions ===================================== @@ -259,12 +267,14 @@ functions may also be used in interrupt context: - pm_runtime_put_sync_suspend() - pm_runtime_put_sync_autosuspend() +.. _Section 5: + 5. Runtime PM Initialization, Device Probing and Removal ======================================================== Initially, the runtime PM is disabled for all devices, which means that the -majority of the runtime PM helper functions described in Section 4 will return --EACCES until pm_runtime_enable() is called for the device. +majority of the runtime PM helper functions described in `Section 4`_ will +return -EACCES until pm_runtime_enable() is called for the device. In addition to that, the initial runtime PM status of all devices is 'suspended', but it need not reflect the actual physical state of the device. @@ -287,7 +297,7 @@ pm_runtime_set_suspended(). If the default initial runtime PM status of the device (i.e. 'suspended') reflects the actual state of the device, its bus type's or its driver's ->probe() callback will likely need to wake it up using one of the PM core's -helper functions described in Section 4. In that case, pm_runtime_resume() +helper functions described in `Section 4`_. In that case, pm_runtime_resume() should be used. Of course, for this purpose the device's runtime PM has to be enabled earlier by calling pm_runtime_enable(). @@ -338,6 +348,8 @@ value of /sys/devices/.../power/control to "auto" to allow the driver to power manage the device at run time, the driver may confuse it by using pm_runtime_forbid() this way. +.. _Section 6: + 6. Runtime PM and System Sleep ============================== @@ -430,6 +442,8 @@ out the following operations: callback and right after executing the subsystem-level .complete() callback for it, respectively. +.. _Section 7: + 7. Generic subsystem callbacks ============================== @@ -453,6 +467,8 @@ restore, and runtime resume, can achieve similar behaviour with the help of the DEFINE_RUNTIME_DEV_PM_OPS() macro defined in include/linux/pm_runtime.h (possibly setting its last argument to NULL). +.. _Section 8: + 8. "No-Callback" Devices ======================== @@ -489,6 +505,8 @@ in subsystems/drivers, the PM core allows runtime PM callbacks to be unassigned. More precisely, if a callback pointer is NULL, the PM core will act as though there was a callback and it returned 0. +.. _Section 9: + 9. Autosuspend, or automatically-delayed suspends ================================================= From aedc51762a0d3d4a50d1542b59aafcd6060c4b39 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:53 -0700 Subject: [PATCH 1093/2286] PM: runtime: Clarify ->runtime_idle() callback return value handling This doc says "Negative error return codes are ignored by the PM core" for ->runtime_idle(). This is misleading: any non-zero return value (including negative error codes such as -EBUSY or -EAGAIN) tells the PM core to abort automatic suspension of the device, which is commonly used by subsystems to prevent immediate runtime suspend. Clarify that unlike ->runtime_suspend() and ->runtime_resume(), the PM core does not treat negative return values from ->runtime_idle() as a fatal device error, and that any non-zero value stops the PM core from suspending the device. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.5.If6e26acac7770c65446dd0b81f891948abb38973@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 8e93766bdf9967..6fe20ad8d3d9d9 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -158,9 +158,14 @@ suspending the device are satisfied) and to queue up a suspend request for the device in that case. If there is no idle callback, or if the callback returns 0, then the PM core will attempt to carry out a runtime suspend of the device, also respecting devices configured for autosuspend. In essence this means a -call to pm_runtime_autosuspend(). To prevent this (for example, if the callback -routine has started a delayed suspend), the routine must return a non-zero -value. Negative error return codes are ignored by the PM core. +call to pm_runtime_autosuspend(). + +To prevent this suspension (for example, if the callback routine has scheduled +a delayed suspend or determined the device cannot be idle), the routine must +return a non-zero value (typically -EBUSY or -EAGAIN). Unlike +->runtime_suspend() and ->runtime_resume(), the PM core does not treat negative +return codes from ->runtime_idle() as a fatal device error; any non-zero value +simply stops the PM core from suspending the device. The helper functions provided by the PM core, described in `Section 4`_, guarantee that the following constraints are met with respect to runtime PM From dd2f2b9b1082c1e4283cb5e0aea0bc8ebd966c81 Mon Sep 17 00:00:00 2001 From: Brian Norris Date: Tue, 29 Sep 2026 19:49:54 -0700 Subject: [PATCH 1094/2286] PM: runtime: Clarify driver callback expectations and structure Section 2 Section 2 describes the three runtime callbacks, but: 1) it's fairly dense to read (150+ lines); and 2) it glosses over a big point -- that it's uncommon for drivers to implement ->runtime_idle() Add a note up-front to help direct the reader about #2, and add headings to try to break up the text a bit. Signed-off-by: Brian Norris Link: https://patch.msgid.link/20260929194956.v3.6.If19ee35d3b80d115f264484b21fb003a962762fa@changeid Signed-off-by: Rafael J. Wysocki --- Documentation/power/runtime_pm.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/power/runtime_pm.rst b/Documentation/power/runtime_pm.rst index 6fe20ad8d3d9d9..68903d93a3e7c1 100644 --- a/Documentation/power/runtime_pm.rst +++ b/Documentation/power/runtime_pm.rst @@ -54,6 +54,14 @@ There are three device runtime PM callbacks defined in 'struct dev_pm_ops':: ... }; +Most device drivers only need to implement ->runtime_suspend() and +->runtime_resume(). The ->runtime_idle() callback is optional and rarely +implemented by peripheral device drivers, as the PM core automatically handles +suspension and autosuspend when ->runtime_idle() is omitted (or returns 0). + +Subsystem and Driver Callbacks +------------------------------ + The ->runtime_suspend(), ->runtime_resume() and ->runtime_idle() callbacks are executed by the PM core for the device's subsystem that may be either of the following: @@ -87,6 +95,9 @@ not block or sleep, but it also means that the synchronous helper functions listed at the end of `Section 4`_ may be used for that device within an interrupt handler or generally in an atomic context. +Callback Semantics +------------------ + The subsystem-level suspend callback, if present, is _entirely_ _responsible_ for handling the suspend of the device as appropriate, which may, but need not include executing the device driver's own ->runtime_suspend() callback (from the @@ -167,6 +178,9 @@ return a non-zero value (typically -EBUSY or -EAGAIN). Unlike return codes from ->runtime_idle() as a fatal device error; any non-zero value simply stops the PM core from suspending the device. +Core Guarantees and Synchronization Rules +----------------------------------------- + The helper functions provided by the PM core, described in `Section 4`_, guarantee that the following constraints are met with respect to runtime PM callbacks for one device: From 8688f0b29d021cad8188b11d5664b4a1f595691b Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:44 +0200 Subject: [PATCH 1095/2286] kconfig: tests: Reset all KCONFIG_* env variables by default Reset all environment variables with names starting with 'KCONFIG_' from the user-provided environment. This fixes the 'warn_changed_input' test on systems with KCONFIG_WARN_CHANGED_INPUT=1 and ensures a cleaner test environment. Fixes: 645323a7f4e5 ("kconfig: add optional warnings for changed input values") Signed-off-by: Nicolas Schier Reviewed-by: Julian Braha Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-1-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 2263bd2a3b3ac9..6fb391ac8fa9e1 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -57,9 +57,12 @@ def _run_conf(self, mode, dot_config=None, out_file='.config', # Override 'srctree' environment to make the test as the top directory extra_env['srctree'] = self._test_dir - # Clear KCONFIG_DEFCONFIG_LIST to keep unit tests from being affected - # by the user's environment. - extra_env['KCONFIG_DEFCONFIG_LIST'] = '' + # Clear all KCONFIG_* environment variables + env = { + varname: os.environ[varname] + for varname in os.environ.keys() + if not varname.startswith("KCONFIG_") + } # Run Kconfig in a temporary directory. # This directory is automatically removed when done. @@ -75,7 +78,7 @@ def _run_conf(self, mode, dot_config=None, out_file='.config', stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=temp_dir, - env=dict(os.environ, **extra_env)) + env=dict(env, **extra_env)) # If input key sequence is given, feed it to stdin. if in_keys: From 09bd92ba15bd00d937deea38a10a439458393043 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:45 +0200 Subject: [PATCH 1096/2286] kconfig: tests: Provide defconfig outfile for savedefconfig Let kconfig test framework's 'savedefconfig' forward the requested output 'defconfig' filename to 'conf'. The original implementation was calling 'conf' with '--savedefconfig' but missed the mandatory filename for the defconfig output file. While at it, honour the 'dot_config' argument which points to the input config file. Fixes: 022a4bf6b59d ("kconfig: tests: add framework for Kconfig unit testing") Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-2-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 6fb391ac8fa9e1..1f25de43128d34 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -235,7 +235,8 @@ def savedefconfig(self, dot_config): dot_config: .config file for input returncode: exit status of the Kconfig executable """ - return self._run_conf('--savedefconfig', out_file='defconfig') + return self._run_conf('--savedefconfig={}'.format(out_file), + dot_config=dot_config, out_file='defconfig') def listnewconfig(self, dot_config=None): """Run listnewconfig. From ecc9c2b15a04b9bdbbc61eab738d685a2fb0dcd8 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:46 +0200 Subject: [PATCH 1097/2286] kconfig: tests: warn_changed_input: Simplify by reusing the extra env Define the extra environment once and re-use it for the respective _run_conf() calls to increase readability. Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-3-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- .../kconfig/tests/warn_changed_input/__init__.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py index 4c3bca6af846e0..a5f07d4a882c4b 100644 --- a/scripts/kconfig/tests/warn_changed_input/__init__.py +++ b/scripts/kconfig/tests/warn_changed_input/__init__.py @@ -8,26 +8,24 @@ def test(conf): + warn_changed_input = { + "KCONFIG_WARN_CHANGED_INPUT": "1", + } + assert conf.olddefconfig('config') == 0 assert 'user-provided values changed by Kconfig' not in conf.stderr assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }) == 0 + extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_config') assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }, silent=True) == 0 + extra_env=warn_changed_input, silent=True) == 0 assert conf.stderr_contains('expected_stderr') assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', out_file='defconfig', - extra_env={ - 'KCONFIG_WARN_CHANGED_INPUT': '1', - }) == 0 + extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_defconfig') From 34f3b447600bcc56ae7c4d10db8dac754ad05357 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Tue, 29 Sep 2026 09:59:47 +0200 Subject: [PATCH 1098/2286] kconfig: tests: {old,save}defconfig: Forward dynamic keyword arguments Let kconfig test framework forward dynamic keyword arguments for 'olddefconfig' and 'savedefconfig' and update the _run_conf() calls in warn_changed_input to use the corrsponding wrappers instead. The warn_changed_input test needed to use _run_conf() as the {old,save}defconfig wrapper did not provide the necessary flexibility for common calls. With dynamic keyword arguments, there is no need to use the framework-internal _run_conf() any more. Reviewed-by: Julian Braha Signed-off-by: Nicolas Schier Link: https://patch.msgid.link/20260929-kconfig-tests-minor-updates-v2-4-c5902bee1d0c@fritz.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/tests/conftest.py | 9 +++++---- scripts/kconfig/tests/warn_changed_input/__init__.py | 11 ++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/kconfig/tests/conftest.py b/scripts/kconfig/tests/conftest.py index 1f25de43128d34..d22c080a4e4065 100644 --- a/scripts/kconfig/tests/conftest.py +++ b/scripts/kconfig/tests/conftest.py @@ -159,13 +159,13 @@ def oldconfig(self, dot_config=None, in_keys=None): return self._run_conf('--oldconfig', dot_config=dot_config, interactive=True, in_keys=in_keys) - def olddefconfig(self, dot_config=None): + def olddefconfig(self, dot_config=None, **kw): """Run olddefconfig. dot_config: .config file to use for configuration base (optional) returncode: exit status of the Kconfig executable """ - return self._run_conf('--olddefconfig', dot_config=dot_config) + return self._run_conf('--olddefconfig', dot_config=dot_config, **kw) def defconfig(self, defconfig): """Run defconfig. @@ -229,14 +229,15 @@ def randconfig(self, all_config=None, seed=None): return self._allconfig('rand', all_config, extra_env=extra_env) - def savedefconfig(self, dot_config): + def savedefconfig(self, dot_config, out_file='defconfig', **kw): """Run savedefconfig. dot_config: .config file for input + out_file: defconfig file for output returncode: exit status of the Kconfig executable """ return self._run_conf('--savedefconfig={}'.format(out_file), - dot_config=dot_config, out_file='defconfig') + dot_config=dot_config, out_file=out_file, **kw) def listnewconfig(self, dot_config=None): """Run listnewconfig. diff --git a/scripts/kconfig/tests/warn_changed_input/__init__.py b/scripts/kconfig/tests/warn_changed_input/__init__.py index a5f07d4a882c4b..17b602dd280e88 100644 --- a/scripts/kconfig/tests/warn_changed_input/__init__.py +++ b/scripts/kconfig/tests/warn_changed_input/__init__.py @@ -15,17 +15,14 @@ def test(conf): assert conf.olddefconfig('config') == 0 assert 'user-provided values changed by Kconfig' not in conf.stderr - assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env=warn_changed_input) == 0 + assert conf.olddefconfig('config', extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_config') - assert conf._run_conf('--olddefconfig', dot_config='config', - extra_env=warn_changed_input, silent=True) == 0 + assert conf.olddefconfig('config', extra_env=warn_changed_input, + silent=True) == 0 assert conf.stderr_contains('expected_stderr') - assert conf._run_conf('--savedefconfig=defconfig', dot_config='config', - out_file='defconfig', - extra_env=warn_changed_input) == 0 + assert conf.savedefconfig('config', extra_env=warn_changed_input) == 0 assert conf.stderr_contains('expected_stderr') assert conf.config_matches('expected_defconfig') From f9a6960c7b27c03becd50a2a7620273d80e4040d Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Wed, 30 Sep 2026 14:36:45 +0200 Subject: [PATCH 1099/2286] rust: bindings: fix `VM_MAYSHARE` blocklist entry Commit 2b6a3f061f11 ("mm: declare VMA flags by bit") added constant helpers for the VMA flags and blocklist entries to suppress their direct bindings. However, it listed `VM_MAYEXEC` twice and omitted `VM_MAYSHARE`. Thus fix it. Fixes: 2b6a3f061f11 ("mm: declare VMA flags by bit") Cc: stable@vger.kernel.org Assisted-by: LLM Reviewed-by: Alice Ryhl Reviewed-by: Lorenzo Stoakes (ARM) Reviewed-by: Vishal Moola (Fractile) Link: https://patch.msgid.link/20260930123645.302318-1-ojeda@kernel.org Signed-off-by: Miguel Ojeda --- rust/bindgen_parameters | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters index 8402b0c935450c..93e622aaaf78b8 100644 --- a/rust/bindgen_parameters +++ b/rust/bindgen_parameters @@ -50,7 +50,7 @@ --blocklist-item VM_MAYREAD --blocklist-item VM_MAYWRITE --blocklist-item VM_MAYEXEC ---blocklist-item VM_MAYEXEC +--blocklist-item VM_MAYSHARE --blocklist-item VM_PFNMAP --blocklist-item VM_IO --blocklist-item VM_DONTCOPY From 158e85db3375c4b0b66223dee7aae16f2357aada Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 28 Sep 2026 18:32:19 +0100 Subject: [PATCH 1100/2286] rust: pin-init: internal: init: allow reborrow of `Pin<&mut T>` bindings Use `let mut` for created bindings of initialized field, so `Pin<&mut T>` bindings may be reborrowed in subsequent initializers or code blocks. Without `let mut`, attempt to reborrow can produce error (and also a confusing fix): error[E0596]: cannot borrow `gpu` as mutable, as it is not declared as mutable --> drivers/gpu/nova-core/driver.rs:129:17 | 129 | gpu.as_mut().run_selftests(pdev); | ^^^ cannot borrow as mutable | help: consider changing this to be mutable | 119 | mut gpu <- Gpu::new( | +++ error: aborting due to 1 previous error Reported-by: Danilo Krummrich Closes: https://lore.kernel.org/nova-gpu/DLQXWXRP3P1J.28SLFOJJQ2EDK@kernel.org/ Reviewed-by: Alexandre Courbot Tested-by: Danilo Krummrich Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/20260928-dev-pin-init-sync-v1-1-e591c8e9cf0e@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index c5b5a0ce5c4e41..4e77c7af86f95f 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -446,7 +446,8 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // Allow `non_snake_case` since the same warning is going to be reported for the // struct field. #[allow(unused_variables, non_snake_case)] - let #ident = #guard.let_binding(); + // Include `mut` so that `Pin<&mut T>` bindings can be reborrowed via `.as_mut()`. + let mut #ident = #guard.let_binding(); }, Member::Unnamed(_) => quote!(), }; From d99668f5ed5839a1a8f061dd5a47ab5d0c7dac74 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 28 Sep 2026 18:32:20 +0100 Subject: [PATCH 1101/2286] rust: pin-init: internal: init: improve diagnostics from field checks Use span of the field name for generated alignment and field duplication checks. This avoids pointing diagnostics to the full macro invocation, but just the part of it going wrong. Before the change: error[E0062]: field `0` specified more than once --> tests/ui/compile-fail/init/tuple_duplicate_field.rs:7:37 | 7 | let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); | ------------------------^------------ | | | | | used more than once | first use of `0` After the change: error[E0062]: field `0` specified more than once --> tests/ui/compile-fail/init/tuple_duplicate_field.rs:7:37 | 7 | let _ = pin_init!(Tuple { 0: 1, 0: 2, 1: 3 }); | - ^ used more than once | | | first use of `0` Link: https://patch.msgid.link/20260928-dev-pin-init-sync-v1-2-e591c8e9cf0e@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 65 +++++++++++++++++++----------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 4e77c7af86f95f..f6f3e2f8cda67e 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -478,42 +478,61 @@ fn make_field_check( init_kind: InitKind, path: &Path, ) -> TokenStream { - let field_attrs: Vec<_> = fields + let align_checks: TokenStream = fields .iter() - .filter_map(|f| f.kind.member().map(|_| &f.attrs)) + .filter_map(|f| { + let member = f.kind.member()?; + let span = member.span().resolved_at(Span::mixed_site()); + let attrs = &f.attrs; + + Some(quote_spanned! {span => + // Create references to ensure that the initialized field is properly aligned. + // Unaligned fields will cause the compiler to emit E0793. We do not support + // unaligned fields since `Init::__init` requires an aligned pointer; the call to + // `ptr::write` for value-initialization case has the same requirement. + #(#attrs)* + let _ = &(*slot).#member; + }) + }) + .collect(); + + let fake_field_init: TokenStream = fields + .iter() + .filter_map(|f| { + let member = f.kind.member()?; + let span = member.span().resolved_at(Span::mixed_site()); + let attrs = &f.attrs; + + Some(quote_spanned! {span => + #(#attrs)* + #member: loop {}, + }) + }) .collect(); - let field_name: Vec<_> = fields.iter().filter_map(|f| f.kind.member()).collect(); let zeroing_trailer = match init_kind { InitKind::Normal => None, InitKind::Zeroing => Some(quote! { ..::core::mem::zeroed() }), }; + let field_dup_checks = quote_spanned! { Span::mixed_site() => + // If the zeroing trailer is not present, this checks that all fields have been + // mentioned exactly once. If the zeroing trailer is present, all missing fields will be + // zeroed, so this checks that all fields have been mentioned at most once. The use of + // struct initializer will still generate very natural error messages for any misuse. + ::core::ptr::write(slot, #path { + #fake_field_init + #zeroing_trailer + }) + }; + quote_spanned! { Span::mixed_site() => #[allow(unreachable_code)] // We use unreachable code to perform field checks. They're still checked by the compiler. // SAFETY: this code is never executed. let _ = || unsafe { - // Create references to ensure that the initialized field is properly aligned. - // Unaligned fields will cause the compiler to emit E0793. We do not support - // unaligned fields since `Init::__init` requires an aligned pointer; the call to - // `ptr::write` for value-initialization case has the same requirement. - #( - #(#field_attrs)* - let _ = &(*slot).#field_name; - )* - - // If the zeroing trailer is not present, this checks that all fields have been - // mentioned exactly once. If the zeroing trailer is present, all missing fields will be - // zeroed, so this checks that all fields have been mentioned at most once. The use of - // struct initializer will still generate very natural error messages for any misuse. - ::core::ptr::write(slot, #path { - #( - #(#field_attrs)* - #field_name: loop {}, - )* - #zeroing_trailer - }) + #align_checks + #field_dup_checks }; } } From b2cb983f9d59ddc0ede2a4976443fda6297778af Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 28 Sep 2026 18:32:21 +0100 Subject: [PATCH 1102/2286] rust: pin-init: internal: init: use span of `<-` or `:` for the init/write call. Currently we use the full span of the field initializer as the span of the generated init call. This produces overlapping spans for type mismatch errors, which is hard to read. Use the span of `<-` or `:` for the method name, which wll cause type mismatch errors or trait bound not implemented error to be appear from the `<-` or `:` token, which declutter the output a bit and make it easier to read. Before the change: error[E0277]: `impl pin_init::PinInit` cannot be used to movably initialize `Bar` with error `_` --> tests/ui/compile-fail/init/invalid_init.rs:19:16 | 19 | bar <- Bar::new(), | -------^^^^^^^^^^ | | | | | the trait `Init` is not implemented for `impl pin_init::PinInit` | required by a bound introduced by this call | = note: if your type implements `PinInit` but not `Init`, you might be forgetting a `#[pin]` annotation on fields After the change: error[E0277]: `impl pin_init::PinInit` cannot be used to movably initialize `Bar` with error `_` --> tests/ui/compile-fail/init/invalid_init.rs:19:16 | 19 | bar <- Bar::new(), | -- ^^^^^^^^^^ the trait `Init` is not implemented for `impl pin_init::PinInit` | | | required by a bound introduced by this call | = note: if your type implements `PinInit` but not `Init`, you might be forgetting a `#[pin]` annotation on fields Suggested-by: Benno Lossin Link: https://patch.msgid.link/20260928-dev-pin-init-sync-v1-3-e591c8e9cf0e@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 38 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index f6f3e2f8cda67e..6605e04d49df27 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -101,7 +101,7 @@ enum InitializerKind { }, Init { member: Member, - _left_arrow_token: Token![<-], + left_arrow_token: Token![<-], value: Expr, }, Code { @@ -415,24 +415,40 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // `mixed_site` ensures that the guard is not accessible to the user-controlled code. let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); - let full_span = kind.span(); + let full_span = kind.span().resolved_at(Span::mixed_site()); let init = match kind { InitializerKind::Value { value, .. } => { - let value = value + let (colon_span, value) = value .as_ref() - .map(|(_, value)| quote!(#value)) - .unwrap_or_else(|| quote!(#member)); + .map(|(colon_token, value)| (colon_token.span(), quote!(#value))) + .unwrap_or_else(|| (member.span(), quote!(#member))); + + // Use `:` as the span of the method name, so the type requirement appears to come + // from `:`. + let write = + format_ident!("write", span = colon_span.resolved_at(Span::mixed_site())); quote_spanned! { full_span => #(#attrs)* - let mut #guard = #slot.write(#value); + let mut #guard = #slot.#write(#value); } } - InitializerKind::Init { value, .. } => { + InitializerKind::Init { + value, + left_arrow_token, + .. + } => { + // Use `<-` as the span of the method name, so the trait bound appears to come from + // `<-`. + let init = format_ident!( + "init", + span = left_arrow_token.span().resolved_at(Span::mixed_site()) + ); + quote_spanned! { full_span => #(#attrs)* - let mut #guard = #slot.init(#value)?; + let mut #guard = #slot.#init(#value)?; } } InitializerKind::Code { .. } => unreachable!(), @@ -690,7 +706,7 @@ impl Parse for InitializerKind { if lh.peek(Token![<-]) { Ok(Self::Init { member, - _left_arrow_token: input.parse()?, + left_arrow_token: input.parse()?, value: input.parse()?, }) } else if lh.peek(Token![:]) { @@ -819,11 +835,11 @@ impl ToTokens for InitializerKind { } Self::Init { member, - _left_arrow_token, + left_arrow_token, value, } => { member.to_tokens(tokens); - _left_arrow_token.to_tokens(tokens); + left_arrow_token.to_tokens(tokens); value.to_tokens(tokens); } Self::Code { From c82c75ae11fae66cf070562f9b5241a4663f30cb Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Sep 2026 01:50:51 +0100 Subject: [PATCH 1103/2286] rust: doctest: trim function name for reproducibility Currently rustdoc will generate function names like "_doctest_main__home_gary_Projects_linux_rust_kernel_io_rs_824_0" for a doctest located at rust/kernel/io.rs:824, when building with separate outdir using `O=`. This creates overlong symbol names and is also not reproducible. [ `rustdoc` already remaps these paths when we use Rust >= 1.95, thus this fix is needed for older toolchains. - Miguel ] Fix it by doing a custom remapping to trim it to something like `_doctest_main_rust_kernel_io_rs_824_0`. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260924005052.2805129-1-gary@kernel.org [ Added `TODO` comment about the minimum version bump. Reworded title and comment for typos. - Miguel ] Signed-off-by: Miguel Ojeda --- scripts/rustdoc_test_builder.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/rustdoc_test_builder.rs b/scripts/rustdoc_test_builder.rs index df864437cef70c..0361e22853f212 100644 --- a/scripts/rustdoc_test_builder.rs +++ b/scripts/rustdoc_test_builder.rs @@ -72,6 +72,13 @@ fn main() { // Figure out a smaller test name based on the generated function name. let name = rustdoc_function_name.split_once("_rust_kernel_").unwrap().1; + // The `rustdoc` function name can include the absolute path when building with `O=` which is + // undesirable and creates overlong symbol names. Remap it to use a relative path. + // TODO: Remove when bumping the minimum version since with Rust >= 1.95 we already do the + // path remapping via the `rustdoc` `--remap-path-prefix` flag. + let trimmed_function_name = format!("_doctest_main_rust_kernel_{name}"); + let body = body.replace(&rustdoc_function_name, &trimmed_function_name); + let path = format!("rust/test/doctests/kernel/{name}"); std::fs::write(path, body.as_bytes()).unwrap(); From 72f4c2eae4036d0d5e63f9aa548c2688f8f99ff1 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:03 +0200 Subject: [PATCH 1104/2286] lib/crypto: aes: Provide functions for zeroizing aes_key and aes_enckey Some crypto functions need to zeroize their local aes_key or aes_enckey structures after use to avoid leaking sensitive material on the stack. Provide aes_zeroize_key() and aes_zeroize_enckey() helper functions that can be used with __cleanup() to automatically zeroize the structs when they go out of scope. While we're at it, replace the memzero_explicit() calls in lib/crypto/aes.c with the new helper functions. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-2-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/aes.h | 18 ++++++++++++++++++ lib/crypto/aes.c | 10 +++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/crypto/aes.h b/include/crypto/aes.h index 3279cfa5460854..9fe868161e1d38 100644 --- a/include/crypto/aes.h +++ b/include/crypto/aes.h @@ -101,6 +101,15 @@ struct aes_enckey { union aes_enckey_arch k; }; +/** + * aes_zeroize_enckey() - Zeroize an aes_enckey structure + * @key: The aes_enckey to zeroize + */ +static inline void aes_zeroize_enckey(struct aes_enckey *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct aes_key - An AES key prepared for encryption and decryption * @aes_enckey: Common fields and the key prepared for encryption @@ -115,6 +124,15 @@ struct aes_key { union aes_invkey_arch inv_k; }; +/** + * aes_zeroize_key() - Zeroize an aes_key structure + * @key: The aes_key to zeroize + */ +static inline void aes_zeroize_key(struct aes_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /* * Please ensure that the first two fields are 16-byte aligned * relative to the start of the structure, i.e., don't move them! diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index f1549839b3de0c..07c1d912ac365b 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -539,7 +539,7 @@ static void __init aes_fips_test(void) if (memcmp(fips_test_data, data, sizeof(data)) != 0) panic("aes: FIPS self-test failed (wrong plaintext)\n"); - memzero_explicit(&key, sizeof(key)); + aes_zeroize_key(&key); } #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CBC_MACS) @@ -827,7 +827,7 @@ static void __init aes_ecb_fips_test(void) if (memcmp(fips_test_data, data, sizeof(data)) != 0) panic("aes: ECB FIPS self-test failed (wrong plaintext)\n"); - memzero_explicit(&key, sizeof(key)); + aes_zeroize_key(&key); } #else /* CONFIG_CRYPTO_LIB_AES_ECB */ static inline void aes_ecb_fips_test(void) @@ -1040,7 +1040,7 @@ static void __init aes_cbc_fips_test(void) if (memcmp(fips_test_data, data, sizeof(data)) != 0) panic("aes: CBC FIPS self-test failed (wrong plaintext)\n"); - memzero_explicit(&key, sizeof(key)); + aes_zeroize_key(&key); } /* FIPS cryptographic algorithm self-test for AES-CBC-CTS */ @@ -1069,7 +1069,7 @@ static void __init aes_cbc_cts_fips_test(void) if (memcmp(ptext, data, data_len) != 0) panic("aes: CBC-CTS FIPS self-test failed (wrong plaintext)\n"); - memzero_explicit(&key, sizeof(key)); + aes_zeroize_key(&key); } #else /* CONFIG_CRYPTO_LIB_AES_CBC */ static inline void aes_cbc_fips_test(void) @@ -1194,7 +1194,7 @@ static void __init aes_ctr_fips_test(void) if (memcmp(fips_test_data, data, sizeof(data)) != 0) panic("aes: CTR FIPS self-test failed (wrong plaintext)\n"); - memzero_explicit(&key, sizeof(key)); + aes_zeroize_enckey(&key); } #else /* CONFIG_CRYPTO_LIB_AES_CTR */ static inline void aes_ctr_fips_test(void) From bde4dcd6cac5cbdaa3144adffeb3a0c1d9bba064 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:04 +0200 Subject: [PATCH 1105/2286] lib/crypto: aes-xts: Provide function for zeroizing aes_xts_key In certain cases crypto code functions need to zeroize their local aes_xts_key structures after use to avoid leaking sensitive material. Provide an aes_xts_zeroize_key() helper function that e.g. can be used with __cleanup() to automatically zeroize the struct when it goes out of scope. While we're at it, replace the related memzero_explicit() call in lib/crypto/aes.c with a call to the new helper function. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-3-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/aes-xts.h | 13 +++++++++++-- lib/crypto/aes.c | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/crypto/aes-xts.h b/include/crypto/aes-xts.h index b9e828265e58ab..3a52e1cf40b57a 100644 --- a/include/crypto/aes-xts.h +++ b/include/crypto/aes-xts.h @@ -22,6 +22,15 @@ struct aes_xts_key { struct aes_enckey tweak_key; }; +/** + * aes_xts_zeroize_key() - Zeroize an aes_xts_key structure + * @key: The aes_xts_key to zeroize + */ +static inline void aes_xts_zeroize_key(struct aes_xts_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * aes_xts_preparekey() - Prepare a key for AES-XTS encryption and decryption * @key: (output) The key structure to initialize @@ -30,8 +39,8 @@ struct aes_xts_key { * @flags: Optional flag XTS_FORBID_WEAK_KEYS to forbid keys whose two halves * are the same. * - * Users should use memzero_explicit() to zeroize the key struct at the end of - * its lifetime. (But if this function fails, zeroization is unnecessary.) + * Users should use aes_xts_zeroize_key() to zeroize the key struct at the end + * of its lifetime. (But if this function fails, zeroization is unnecessary.) * * Context: Any context. * Return: diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 07c1d912ac365b..34ef5deca0a79f 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -1223,7 +1223,7 @@ int aes_xts_preparekey(struct aes_xts_key *key, const u8 *in_key, return 0; out_zeroize: - memzero_explicit(key, sizeof(*key)); + aes_xts_zeroize_key(key); return err; } EXPORT_SYMBOL_GPL(aes_xts_preparekey); From f0bcf2d3ef935d0fdb76ca23786fe9880ad80827 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:05 +0200 Subject: [PATCH 1106/2286] lib/crypto: aes-gcm: Provide functions for zeroizing aes_gcm* structures In certain cases crypto code needs to zeroize their local aes_gcm_key or aes_gcm_ctx structures after use to avoid leaking sensitive material. Provide aes_gcm_zeroize_key() and aes_gcm_zeroize_ctx() helper functions that e.g. can be used with __cleanup() to automatically zeroize the structures when they go out of scope. While we're at it, replace the related memzero_explicit() calls in lib/crypto/aes.c with calls to the new helper functions. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-4-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/aes-gcm.h | 22 ++++++++++++++++++++-- lib/crypto/aes.c | 8 +++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/crypto/aes-gcm.h b/include/crypto/aes-gcm.h index 2aee62f0198919..a81b00fd8e27f6 100644 --- a/include/crypto/aes-gcm.h +++ b/include/crypto/aes-gcm.h @@ -21,6 +21,15 @@ struct aes_gcm_key { size_t authtag_len; /* Length of authentication tags in bytes */ }; +/** + * aes_gcm_zeroize_key() - Zeroize an aes_gcm_key structure + * @key: The aes_gcm_key to zeroize + */ +static inline void aes_gcm_zeroize_key(struct aes_gcm_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct aes_gcm_ctx - Context for incrementally en/decrypting a message */ @@ -58,6 +67,15 @@ struct aes_gcm_ctx { u64 data_len; }; +/** + * aes_gcm_zeroize_ctx() - Zeroize an aes_gcm_ctx structure + * @ctx: The aes_gcm_ctx to zeroize + */ +static inline void aes_gcm_zeroize_ctx(struct aes_gcm_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * aes_gcm_preparekey() - Prepare a key for AES-GCM encryption and decryption * @key: (output) The key structure to initialize @@ -66,8 +84,8 @@ struct aes_gcm_ctx { * @authtag_len: Length of the authentication tag in bytes: * 4, 8, 12, 13, 14, 15, or 16. 16 is recommended. * - * Users should use memzero_explicit() to zeroize the key struct at the end of - * its lifetime. (But if this function fails, zeroization is unnecessary.) + * Users should use aes_gcm_zeroize_key() to zeroize the key struct at the end + * of its lifetime. (But if this function fails, zeroization is unnecessary.) * * Context: Any context. * Return: diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 34ef5deca0a79f..0cb5d7355926e2 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -1670,7 +1670,7 @@ void aes_gcm_encrypt_final(struct aes_gcm_ctx *ctx, u8 *authtag) ghash_final(&ctx->ghash, ctx->ctr); /* Use ctr as temp buffer */ crypto_xor_cpy(authtag, ctx->ctr, ctx->j0_enc, ctx->key->authtag_len); - memzero_explicit(ctx, sizeof(*ctx)); + aes_gcm_zeroize_ctx(ctx); } EXPORT_SYMBOL_GPL(aes_gcm_encrypt_final); @@ -1697,7 +1697,7 @@ int aes_gcm_decrypt_final(struct aes_gcm_ctx *ctx, const u8 *authtag) -EBADMSG : 0; out: - memzero_explicit(ctx, sizeof(*ctx)); + aes_gcm_zeroize_ctx(ctx); return err; } EXPORT_SYMBOL_GPL(aes_gcm_decrypt_final); @@ -1742,7 +1742,7 @@ static void __init aes_gcm_fips_test(void) { const size_t data_len = sizeof(fips_test_data); u8 buf[sizeof(fips_test_data) + AES_BLOCK_SIZE]; - struct aes_gcm_key key; + struct aes_gcm_key key __cleanup(aes_gcm_zeroize_key); int err; if (aes_gcm_preparekey(&key, fips_test_key, sizeof(fips_test_key), @@ -1760,8 +1760,6 @@ static void __init aes_gcm_fips_test(void) panic("aes: GCM FIPS self-test failed (decryption failed)\n"); if (memcmp(fips_test_data, buf, data_len) != 0) panic("aes: GCM FIPS self-test failed (wrong plaintext)\n"); - - memzero_explicit(&key, sizeof(key)); } #else /* CONFIG_CRYPTO_LIB_AES_GCM */ static inline void aes_gcm_fips_test(void) From dbb5091fb6c937ff312257aaf9b772216c3ca8f6 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:06 +0200 Subject: [PATCH 1107/2286] lib/crypto: aes-ccm: Provide functions for zeroizing aes_ccm* structures In certain cases crypto code needs to zeroize their local aes_ccm_key or aes_ccm_ctx structures after use to avoid leaking sensitive material. Provide aes_ccm_zeroize_key() and aes_ccm_zeroize_ctx() helper functions that e.g. can be used with __cleanup() to automatically zeroize the structures when they go out of scope. While we're at it, replace the related memzero_explicit() calls in lib/crypto/aes.c with calls to the new helper functions. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-5-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/aes-ccm.h | 22 ++++++++++++++++++++-- lib/crypto/aes.c | 8 +++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/crypto/aes-ccm.h b/include/crypto/aes-ccm.h index 8b00859ac4d6bd..c52982dd91d2c8 100644 --- a/include/crypto/aes-ccm.h +++ b/include/crypto/aes-ccm.h @@ -18,6 +18,15 @@ struct aes_ccm_key { size_t authtag_len; /* Length of authentication tags in bytes */ }; +/** + * aes_ccm_zeroize_key() - Zeroize an aes_ccm_key structure + * @key: The aes_ccm_key to zeroize + */ +static inline void aes_ccm_zeroize_key(struct aes_ccm_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct aes_ccm_ctx - Context for incrementally en/decrypting a message */ @@ -50,6 +59,15 @@ struct aes_ccm_ctx { bool ad_padded; }; +/** + * aes_ccm_zeroize_ctx() - Zeroize an aes_ccm_ctx structure + * @ctx: The aes_ccm_ctx to zeroize + */ +static inline void aes_ccm_zeroize_ctx(struct aes_ccm_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * aes_ccm_preparekey() - Prepare a key for AES-CCM encryption and decryption * @key: (output) The key structure to initialize @@ -58,8 +76,8 @@ struct aes_ccm_ctx { * @authtag_len: Length of the authentication tag in bytes: * 4, 6, 8, 10, 12, 14, or 16. 16 is recommended. * - * Users should use memzero_explicit() to zeroize the key struct at the end of - * its lifetime. (But if this function fails, zeroization is unnecessary.) + * Users should use aes_ccm_zeroize_key() to zeroize the key struct at the end + * of its lifetime. (But if this function fails, zeroization is unnecessary.) * * Context: Any context. * Return: diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 0cb5d7355926e2..2d29adca795328 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -2011,7 +2011,7 @@ void aes_ccm_encrypt_final(struct aes_ccm_ctx *ctx, u8 *authtag) if (ctx->partial_len) aes_encrypt(&ctx->key->aes, ctx->mac, ctx->mac); crypto_xor_cpy(authtag, ctx->mac, ctx->s0, ctx->key->authtag_len); - memzero_explicit(ctx, sizeof(*ctx)); + aes_ccm_zeroize_ctx(ctx); } EXPORT_SYMBOL_GPL(aes_ccm_encrypt_final); @@ -2032,7 +2032,7 @@ int aes_ccm_decrypt_final(struct aes_ccm_ctx *ctx, const u8 *authtag) -EBADMSG : 0; out: - memzero_explicit(ctx, sizeof(*ctx)); + aes_ccm_zeroize_ctx(ctx); return err; } EXPORT_SYMBOL_GPL(aes_ccm_decrypt_final); @@ -2084,7 +2084,7 @@ static void __init aes_ccm_fips_test(void) const size_t data_len = sizeof(fips_test_data); const size_t nonce_len = 13; u8 buf[sizeof(fips_test_data) + AES_BLOCK_SIZE]; - struct aes_ccm_key key; + struct aes_ccm_key key __cleanup(aes_ccm_zeroize_key); int err; if (aes_ccm_preparekey(&key, fips_test_key, sizeof(fips_test_key), @@ -2106,8 +2106,6 @@ static void __init aes_ccm_fips_test(void) panic("aes: CCM FIPS self-test failed (decryption failed)\n"); if (memcmp(fips_test_data, buf, data_len) != 0) panic("aes: CCM FIPS self-test failed (wrong plaintext)\n"); - - memzero_explicit(&key, sizeof(key)); } #else /* CONFIG_CRYPTO_LIB_AES_CCM */ static inline void aes_ccm_fips_test(void) From 91b4ebff7a88ee3a0570b0058b054ecf527cefa2 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:07 +0200 Subject: [PATCH 1108/2286] lib/crypto: md5: Provide functions for zeroizing hmac_md5 structures In certain cases crypto code functions need to zeroize their local hmac_md5_key or hmac_md5_ctx structures after use to avoid leaking sensitive material on the stack. Provide hmac_md5_zeroize_key() and hmac_md5_zeroize_ctx() helper functions that e.g. can be used with __cleanup() to automatically zeroize the structure when it goes out of scope. While we're at it, replace the related memzero_explicit() call in lib/crypto/md5.c with a call to the new helper function. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-6-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/md5.h | 19 +++++++++++++++++++ lib/crypto/md5.c | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/crypto/md5.h b/include/crypto/md5.h index c47aedfe67ecd0..1ed89c15b662c9 100644 --- a/include/crypto/md5.h +++ b/include/crypto/md5.h @@ -4,6 +4,7 @@ #include #include +#include #define MD5_DIGEST_SIZE 16 #define MD5_HMAC_BLOCK_SIZE 64 @@ -98,6 +99,15 @@ struct hmac_md5_key { struct md5_block_state ostate; }; +/** + * hmac_md5_zeroize_key() - Zeroize an hmac_md5_key structure + * @key: The hmac_md5_key to zeroize + */ +static inline void hmac_md5_zeroize_key(struct hmac_md5_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_md5_ctx - Context for computing HMAC-MD5 of a message * @hash_ctx: private @@ -108,6 +118,15 @@ struct hmac_md5_ctx { struct md5_block_state ostate; }; +/** + * hmac_md5_zeroize_ctx() - Zeroize an hmac_md5_ctx structure + * @ctx: The hmac_md5_ctx context to zeroize + */ +static inline void hmac_md5_zeroize_ctx(struct hmac_md5_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_md5_preparekey() - Prepare a key for HMAC-MD5 * @key: (output) the key structure to initialize diff --git a/lib/crypto/md5.c b/lib/crypto/md5.c index 3d2b017a0525ae..a8ee57600012d5 100644 --- a/lib/crypto/md5.c +++ b/lib/crypto/md5.c @@ -271,7 +271,7 @@ void hmac_md5_final(struct hmac_md5_ctx *ctx, u8 out[MD5_DIGEST_SIZE]) cpu_to_le32_array(ctx->ostate.h, ARRAY_SIZE(ctx->ostate.h)); memcpy(out, ctx->ostate.h, MD5_DIGEST_SIZE); - memzero_explicit(ctx, sizeof(*ctx)); + hmac_md5_zeroize_ctx(ctx); } EXPORT_SYMBOL_GPL(hmac_md5_final); From e2b5aa03d6f42d2e4fb9c20a4eeb9ba1a9cac8cb Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:08 +0200 Subject: [PATCH 1109/2286] lib/crypto: sm3: Provide a function for zeroizing the sm3_ctx structure In certain cases crypto code functions need to zeroize their local sm3_ctx structure after use to avoid leaking sensitive material. Provide a sm3_zeroize_ctx() helper function that e.g. can be used with __cleanup() to automatically zeroize the structure when it goes out of scope. While we're at it, replace the related memzero_explicit() call in lib/crypto/sm3.c with a call to the new helper function. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-7-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/sm3.h | 10 ++++++++++ lib/crypto/sm3.c | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/crypto/sm3.h b/include/crypto/sm3.h index 371e8a66170546..d044ca80213ba2 100644 --- a/include/crypto/sm3.h +++ b/include/crypto/sm3.h @@ -11,6 +11,7 @@ #define _CRYPTO_SM3_H #include +#include #define SM3_DIGEST_SIZE 32 #define SM3_BLOCK_SIZE 64 @@ -41,6 +42,15 @@ struct sm3_ctx { u8 buf[SM3_BLOCK_SIZE] __aligned(__alignof__(__be64)); }; +/** + * sm3_zeroize_ctx() - Zeroize an sm3_ctx structure + * @ctx: The sm3_ctx to zeroize + */ +static inline void sm3_zeroize_ctx(struct sm3_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * sm3_init() - Initialize an SM3 context for a new message * @ctx: the context to initialize diff --git a/lib/crypto/sm3.c b/lib/crypto/sm3.c index b02b8a247adf23..23059347b4493d 100644 --- a/lib/crypto/sm3.c +++ b/lib/crypto/sm3.c @@ -258,7 +258,7 @@ static void __sm3_final(struct sm3_ctx *ctx, u8 out[SM3_DIGEST_SIZE]) void sm3_final(struct sm3_ctx *ctx, u8 out[SM3_DIGEST_SIZE]) { __sm3_final(ctx, out); - memzero_explicit(ctx, sizeof(*ctx)); + sm3_zeroize_ctx(ctx); } EXPORT_SYMBOL_GPL(sm3_final); From 97560fd98d4d36b2fd656a6270e15cb2218fcbe2 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:09 +0200 Subject: [PATCH 1110/2286] lib/crypto: blake2: Provide functions for zeroizing blake2*_ctx structures In certain cases crypto code needs to zeroize their local blake2b_ctx or blake2s_ctx structures after use to avoid leaking sensitive material. Provide blake2b_zeroize_ctx() and blake2s_zeroize_ctx() helper functions that e.g. can be used with __cleanup() to automatically zeroize the structures when they go out of scope. While we're at it, replace the related memzero_explicit() calls in lib/crypto/blake2*.c with calls to the new helper functions. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-8-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/blake2b.h | 9 +++++++++ include/crypto/blake2s.h | 9 +++++++++ lib/crypto/blake2b.c | 2 +- lib/crypto/blake2s.c | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/crypto/blake2b.h b/include/crypto/blake2b.h index 3bc37fd103a7ab..eda1604bce780e 100644 --- a/include/crypto/blake2b.h +++ b/include/crypto/blake2b.h @@ -37,6 +37,15 @@ struct blake2b_ctx { unsigned int outlen; }; +/** + * blake2b_zeroize_ctx() - Zeroize a blake2b_ctx structure + * @ctx: The blake2b_ctx to zeroize + */ +static inline void blake2b_zeroize_ctx(struct blake2b_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + enum blake2b_iv { BLAKE2B_IV0 = 0x6A09E667F3BCC908ULL, BLAKE2B_IV1 = 0xBB67AE8584CAA73BULL, diff --git a/include/crypto/blake2s.h b/include/crypto/blake2s.h index 648cb782435887..bb4e6870ed1960 100644 --- a/include/crypto/blake2s.h +++ b/include/crypto/blake2s.h @@ -41,6 +41,15 @@ struct blake2s_ctx { unsigned int outlen; }; +/** + * blake2s_zeroize_ctx() - Zeroize a blake2s_ctx structure + * @ctx: The blake2s_ctx to zeroize + */ +static inline void blake2s_zeroize_ctx(struct blake2s_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + enum blake2s_iv { BLAKE2S_IV0 = 0x6A09E667UL, BLAKE2S_IV1 = 0xBB67AE85UL, diff --git a/lib/crypto/blake2b.c b/lib/crypto/blake2b.c index 581b7f8486fae8..55d6c437f311ff 100644 --- a/lib/crypto/blake2b.c +++ b/lib/crypto/blake2b.c @@ -148,7 +148,7 @@ void blake2b_final(struct blake2b_ctx *ctx, u8 *out) blake2b_compress(ctx, ctx->buf, 1, ctx->buflen); cpu_to_le64_array(ctx->h, ARRAY_SIZE(ctx->h)); memcpy(out, ctx->h, ctx->outlen); - memzero_explicit(ctx, sizeof(*ctx)); + blake2b_zeroize_ctx(ctx); } EXPORT_SYMBOL(blake2b_final); diff --git a/lib/crypto/blake2s.c b/lib/crypto/blake2s.c index 71578a08474233..24f7f34334b010 100644 --- a/lib/crypto/blake2s.c +++ b/lib/crypto/blake2s.c @@ -142,7 +142,7 @@ void blake2s_final(struct blake2s_ctx *ctx, u8 *out) blake2s_compress(ctx, ctx->buf, 1, ctx->buflen); cpu_to_le32_array(ctx->h, ARRAY_SIZE(ctx->h)); memcpy(out, ctx->h, ctx->outlen); - memzero_explicit(ctx, sizeof(*ctx)); + blake2s_zeroize_ctx(ctx); } EXPORT_SYMBOL(blake2s_final); From e09cbc96ff8c94d477a07b2e35b4b4886dc9cf30 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:10 +0200 Subject: [PATCH 1111/2286] lib/crypto: sha1: Provide functions for zeroizing hmac_sha1 structures In certain cases crypto code needs to zeroize their local hmac_sha1_key or hmac_sha1_ctx structures after use to avoid leaking sensitive material. Provide hmac_sha1_zeroize_key() and hmac_sha1_zeroize_ctx() helper functions that e.g. can be used with __cleanup() to automatically zeroize the structures when they go out of scope. While we're at it, replace the related memzero_explicit() call in lib/crypto/sha1.c with a call to the new helper function. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-9-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/sha1.h | 19 +++++++++++++++++++ lib/crypto/sha1.c | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/crypto/sha1.h b/include/crypto/sha1.h index 4d973e016cd696..bc0046bffeaeef 100644 --- a/include/crypto/sha1.h +++ b/include/crypto/sha1.h @@ -7,6 +7,7 @@ #define _CRYPTO_SHA1_H #include +#include #define SHA1_DIGEST_SIZE 20 #define SHA1_BLOCK_SIZE 64 @@ -96,6 +97,15 @@ struct hmac_sha1_key { struct sha1_block_state ostate; }; +/** + * hmac_sha1_zeroize_key() - Zeroize an hmac_sha1_key structure + * @key: The hmac_sha1_key to zeroize + */ +static inline void hmac_sha1_zeroize_key(struct hmac_sha1_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_sha1_ctx - Context for computing HMAC-SHA1 of a message * @sha_ctx: private @@ -106,6 +116,15 @@ struct hmac_sha1_ctx { struct sha1_block_state ostate; }; +/** + * hmac_sha1_zeroize_ctx() - Zeroize an hmac_sha1_ctx structure + * @ctx: The hmac_sha1_ctx context to zeroize + */ +static inline void hmac_sha1_zeroize_ctx(struct hmac_sha1_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_sha1_preparekey() - Prepare a key for HMAC-SHA1 * @key: (output) the key structure to initialize diff --git a/lib/crypto/sha1.c b/lib/crypto/sha1.c index b687b89d97cb44..c4361ef77166ef 100644 --- a/lib/crypto/sha1.c +++ b/lib/crypto/sha1.c @@ -275,7 +275,7 @@ void hmac_sha1_final(struct hmac_sha1_ctx *ctx, u8 out[SHA1_DIGEST_SIZE]) for (size_t i = 0; i < SHA1_DIGEST_SIZE; i += 4) put_unaligned_be32(ctx->ostate.h[i / 4], out + i); - memzero_explicit(ctx, sizeof(*ctx)); + hmac_sha1_zeroize_ctx(ctx); } EXPORT_SYMBOL_GPL(hmac_sha1_final); From 6df761375819da85517b2de53b3767f8f8dfb97c Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:11 +0200 Subject: [PATCH 1112/2286] security: keys: trusted: always clear the hmac_sha1_ctx before returning Clear the hmac_sha1_ctx structure via __cleanup(hmac_sha1_zeroize_ctx) to make sure that the function does not leak sensitive data on the stack when returning without calling hmac_sha1_final(). Reviewed-by: Jarkko Sakkinen Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-10-thuth@redhat.com Signed-off-by: Eric Biggers --- security/keys/trusted-keys/trusted_tpm1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index bf0bf7f3697052..e5a904b5c19469 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -101,7 +101,7 @@ static inline void dump_tpm_buf(unsigned char *buf) static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, unsigned int keylen, ...) { - struct hmac_sha1_ctx hmac_ctx; + struct hmac_sha1_ctx hmac_ctx __cleanup(hmac_sha1_zeroize_ctx); va_list argp; unsigned int dlen; unsigned char *data; From e5fc5477391326a84217da8b35899ed6547552f0 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 28 Sep 2026 10:30:53 +0200 Subject: [PATCH 1113/2286] x86/purgatory: Compile purgatory with -D__NO_FORTIFY and -D__DISABLE_EXPORTS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A subsequent patch will add #include to to use memzero_explicit() there. This will introduce a conflict with the purgatory code: purgatory.c includes both, the header and the arch/x86/boot/string.h header. The latter provides its own prototypes for a lot of string functions which clash with the fortified macros from , causing the compiler to emit errors like this: In file included from .../linux/include/linux/string.h:383, from .../linux/include/crypto/sha2.h:10, from .../linux/arch/x86/purgatory/purgatory.c:14: .../linux/include/linux/fortify-string.h:576:63: error: expected identifier or ‘(’ before ‘{’ token 576 | p_size_field, q_size_field, op) ({ \ | ^ .../linux/include/linux/fortify-string.h:644:27: note: in expansion of macro ‘__fortify_memcpy_chk’ 644 | #define memmove(p, q, s) __fortify_memcpy_chk(p, q, s, \ | ^~~~~~~~~~~~~~~~~~~~ .../linux/arch/x86/purgatory/../boot/string.h:11:7: note: in expansion of macro ‘memmove’ 11 | void *memmove(void *dst, const void *src, size_t len); | ^~~~~~~ .../linux/include/linux/fortify-string.h:208:9: error: expected identifier or ‘(’ before ‘__builtin_choose_expr’ 208 | __builtin_choose_expr(__is_constexpr(__builtin_strlen(p)), \ | ^~~~~~~~~~~~~~~~~~~~~ ./linux/arch/x86/purgatory/../boot/string.h:23:15: note: in expansion of macro ‘strlen’ 23 | extern size_t strlen(const char *s); | ^~~~~~ To avoid the problem, let's compile the whole code in the purgatory with -D__NO_FORTIFY, so can properly be included from headers like in the purgatory, too. When compiling/linking with the LLVM tools, and CONFIG_CFI=y, there is an additional problem during linking: ld.lld: error: undefined symbol: __memset >>> referenced by string.c >>> arch/x86/purgatory/purgatory.ro:(__UNIQUE_ID_addressable___memset_5) ld.lld: error: undefined symbol: __memmove >>> referenced by string.c >>> arch/x86/purgatory/purgatory.ro:(__UNIQUE_ID_addressable___memmove_6) This can be fixed by moving the -D__DISABLE_EXPORTS to the general CFLAGS of the purgatory, too. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260928083055.104266-2-thuth@redhat.com Signed-off-by: Eric Biggers --- arch/x86/purgatory/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/arch/x86/purgatory/Makefile b/arch/x86/purgatory/Makefile index 5ce1d426300004..fad91ea28fe718 100644 --- a/arch/x86/purgatory/Makefile +++ b/arch/x86/purgatory/Makefile @@ -11,8 +11,6 @@ $(obj)/string.o: $(srctree)/arch/x86/boot/compressed/string.c FORCE $(obj)/sha256.o: $(srctree)/lib/crypto/sha256.c FORCE $(call if_changed_rule,cc_o_c) -CFLAGS_sha256.o := -D__DISABLE_EXPORTS -D__NO_FORTIFY - # When profile-guided optimization is enabled, llvm emits two different # overlapping text sections, which is not supported by kexec. Remove profile # optimization flags. @@ -37,6 +35,7 @@ PURGATORY_CFLAGS := -mcmodel=small -ffreestanding -fno-zero-initialized-in-bss - PURGATORY_CFLAGS += -fpic -fvisibility=hidden PURGATORY_CFLAGS += $(DISABLE_KSTACK_ERASE) -DDISABLE_BRANCH_PROFILING PURGATORY_CFLAGS += -fno-stack-protector +PURGATORY_CFLAGS += -D__DISABLE_EXPORTS -D__NO_FORTIFY # Default KBUILD_CFLAGS can have -pg option set when FTRACE is enabled. That # in turn leaves some undefined symbols like __fentry__ in purgatory and not From fb4ccd3d321f890c87e74c5937a74ece822f47e6 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 28 Sep 2026 10:30:54 +0200 Subject: [PATCH 1114/2286] lib/crypto: sha2: Provide functions for zeroizing SHA2 hmac_sha* structures In certain cases crypto code functions need to zeroize their local SHA2 hmac_sha*_key or hmac_sha*_ctx structures after use to avoid leaking sensitive material on the stack. Provide hmac_sha*_zeroize_key() and hmac_sha*_zeroize_ctx() helper functions that can be used with __cleanup() to automatically zeroize the structure when it goes out of scope. Reviewed-by: Namjae Jeon Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260928083055.104266-3-thuth@redhat.com Signed-off-by: Eric Biggers --- include/crypto/sha2.h | 73 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/include/crypto/sha2.h b/include/crypto/sha2.h index 7bb8fe169daf23..22fbc37ae84079 100644 --- a/include/crypto/sha2.h +++ b/include/crypto/sha2.h @@ -7,6 +7,7 @@ #define _CRYPTO_SHA2_H #include +#include #define SHA224_DIGEST_SIZE 28 #define SHA224_BLOCK_SIZE 64 @@ -210,6 +211,15 @@ struct hmac_sha224_key { struct __hmac_sha256_key key; }; +/** + * hmac_sha224_zeroize_key() - Zeroize an hmac_sha224_key structure + * @key: The hmac_sha224_key to zeroize + */ +static inline void hmac_sha224_zeroize_key(struct hmac_sha224_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_sha224_ctx - Context for computing HMAC-SHA224 of a message * @ctx: private @@ -218,6 +228,15 @@ struct hmac_sha224_ctx { struct __hmac_sha256_ctx ctx; }; +/** + * hmac_sha224_zeroize_ctx() - Zeroize an hmac_sha224_ctx structure + * @ctx: The hmac_sha224_ctx context to zeroize + */ +static inline void hmac_sha224_zeroize_ctx(struct hmac_sha224_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_sha224_preparekey() - Prepare a key for HMAC-SHA224 * @key: (output) the key structure to initialize @@ -414,6 +433,15 @@ struct hmac_sha256_key { struct __hmac_sha256_key key; }; +/** + * hmac_sha256_zeroize_key() - Zeroize an hmac_sha256_key structure + * @key: The hmac_sha256_key to zeroize + */ +static inline void hmac_sha256_zeroize_key(struct hmac_sha256_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_sha256_ctx - Context for computing HMAC-SHA256 of a message * @ctx: private @@ -422,6 +450,15 @@ struct hmac_sha256_ctx { struct __hmac_sha256_ctx ctx; }; +/** + * hmac_sha256_zeroize_ctx() - Zeroize an hmac_sha256_ctx structure + * @ctx: The hmac_sha256_ctx context to zeroize + */ +static inline void hmac_sha256_zeroize_ctx(struct hmac_sha256_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_sha256_preparekey() - Prepare a key for HMAC-SHA256 * @key: (output) the key structure to initialize @@ -623,6 +660,15 @@ struct hmac_sha384_key { struct __hmac_sha512_key key; }; +/** + * hmac_sha384_zeroize_key() - Zeroize an hmac_sha384_key structure + * @key: The hmac_sha384_key to zeroize + */ +static inline void hmac_sha384_zeroize_key(struct hmac_sha384_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_sha384_ctx - Context for computing HMAC-SHA384 of a message * @ctx: private @@ -631,6 +677,15 @@ struct hmac_sha384_ctx { struct __hmac_sha512_ctx ctx; }; +/** + * hmac_sha384_zeroize_ctx() - Zeroize an hmac_sha384_ctx structure + * @ctx: The hmac_sha384_ctx context to zeroize + */ +static inline void hmac_sha384_zeroize_ctx(struct hmac_sha384_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_sha384_preparekey() - Prepare a key for HMAC-SHA384 * @key: (output) the key structure to initialize @@ -798,6 +853,15 @@ struct hmac_sha512_key { struct __hmac_sha512_key key; }; +/** + * hmac_sha512_zeroize_key() - Zeroize an hmac_sha512_key structure + * @key: The hmac_sha512_key to zeroize + */ +static inline void hmac_sha512_zeroize_key(struct hmac_sha512_key *key) +{ + memzero_explicit(key, sizeof(*key)); +} + /** * struct hmac_sha512_ctx - Context for computing HMAC-SHA512 of a message * @ctx: private @@ -806,6 +870,15 @@ struct hmac_sha512_ctx { struct __hmac_sha512_ctx ctx; }; +/** + * hmac_sha512_zeroize_ctx() - Zeroize an hmac_sha512_ctx structure + * @ctx: The hmac_sha512_ctx context to zeroize + */ +static inline void hmac_sha512_zeroize_ctx(struct hmac_sha512_ctx *ctx) +{ + memzero_explicit(ctx, sizeof(*ctx)); +} + /** * hmac_sha512_preparekey() - Prepare a key for HMAC-SHA512 * @key: (output) the key structure to initialize From 9c265474b5ab5234f9a98f832f24fe77267fe7f1 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 28 Sep 2026 10:30:55 +0200 Subject: [PATCH 1115/2286] smb: client: Use hmac_sha256_zeroize_ctx function to clear hmac_sha256_ctx It's just cosmetics, but now that we have a helper function for clearing hmac_sha256_ctx with a __cleanup() statement, we can also use it in the smb client code for good measure. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260928083055.104266-4-thuth@redhat.com Signed-off-by: Eric Biggers --- fs/smb/client/smb2transport.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/smb/client/smb2transport.c b/fs/smb/client/smb2transport.c index c407f30e004016..080864b24a0c50 100644 --- a/fs/smb/client/smb2transport.c +++ b/fs/smb/client/smb2transport.c @@ -212,7 +212,7 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) unsigned char smb2_signature[SMB2_HMACSHA256_SIZE]; struct kvec *iov = rqst->rq_iov; struct smb2_hdr *shdr = (struct smb2_hdr *)iov[0].iov_base; - struct hmac_sha256_ctx hmac_ctx; + struct hmac_sha256_ctx hmac_ctx __cleanup(hmac_sha256_zeroize_ctx); struct smb_rqst drqst; __u64 sid = le64_to_cpu(shdr->SessionId); u8 key[SMB2_NTLMV2_SESSKEY_SIZE]; @@ -250,7 +250,6 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) memcpy(shdr->Signature, smb2_signature, SMB2_SIGNATURE_SIZE); memzero_explicit(key, sizeof(key)); - memzero_explicit(&hmac_ctx, sizeof(hmac_ctx)); return rc; } From 839b60ced95f47224cbe6a517d03daac625b35bf Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Wed, 16 Sep 2026 11:50:15 +0200 Subject: [PATCH 1116/2286] lib/crypto: Add documentation about zeroization of key and context data Add a central document about zeroization in libcrypto so we don't have to repeat this information in the individual kernel docs of the zeroization functions all over the place. Signed-off-by: Thomas Huth Link: https://patch.msgid.link/20260916095022.604354-14-thuth@redhat.com Signed-off-by: Eric Biggers --- .../crypto/libcrypto-zeroization.rst | 150 ++++++++++++++++++ Documentation/crypto/libcrypto.rst | 1 + 2 files changed, 151 insertions(+) create mode 100644 Documentation/crypto/libcrypto-zeroization.rst diff --git a/Documentation/crypto/libcrypto-zeroization.rst b/Documentation/crypto/libcrypto-zeroization.rst new file mode 100644 index 00000000000000..76b65067116347 --- /dev/null +++ b/Documentation/crypto/libcrypto-zeroization.rst @@ -0,0 +1,150 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Crypto Key Zeroization +====================== + +This document describes the conventions for zeroizing crypto structures in the +kernel. + +Note: the kernel follows traditional cryptographic terminology by using +the term "zeroizing" to mean erasing sensitive parameters to prevent +their disclosure if the system is later compromised. This distinguishes +it from zeroing memory for other purposes such as initialization. + +.. contents:: + +Overview +-------- + +Cryptographic key material and intermediate state (such as HMAC contexts) must +be zeroized after use to prevent sensitive data from lingering on the stack or +heap, where it could be leaked through memory disclosure vulnerabilities, +crash dumps, or cold-boot attacks. + +For memory that has been allocated with kmalloc() or a similar function, +kfree_sensitive() should be used instead of kfree() to release the memory. + +For other cases, the kernel provides memzero_explicit() for clearing the +memory. Unlike plain memset(), memzero_explicit() is guaranteed not +to be optimized away by the compiler, even when the memory being cleared +appears to be dead. + +The crypto library builds on memzero_explicit() by providing typed +zeroization helpers for each key and context structure. These helpers serve +two purposes: + +1. They make __cleanup() annotations possible, so that structures on + the stack are automatically zeroized when they go out of scope. + +2. They improve readability by replacing ``memzero_explicit(&key, sizeof(key))`` + with a self-documenting call like ``aes_zeroize_key(&key)``. + + +What to zeroize +--------------- + +The following types of structures hold sensitive material and should be +zeroized after use: + +- **Key structures** (e.g. ``struct aes_key``, ``struct hmac_sha256_key``): + contain expanded round keys or prepared key material. + +- **HMAC/MAC context structures** (e.g. ``struct hmac_sha256_ctx``, + ``struct aes_cmac_ctx``): contain inner and outer hash states derived from + the key. + +- **Hash context structures** (e.g. ``struct sha256_ctx``): may contain + sensitive data being hashed. + +Not all of these require explicit cleanup by callers. Many ``..._final()`` +functions already zeroize their context internally (see `Automatic vs. manual +zeroization`_ below). + + +Zeroization helpers +------------------- + +Each crypto structure that callers may need to zeroize should have a +corresponding inline helper function. The naming convention is:: + + _zeroize_(struct _ *p); + +For example:: + + void aes_zeroize_key(struct aes_key *key); + void aes_zeroize_enckey(struct aes_enckey *key); + void hmac_sha256_zeroize_ctx(struct hmac_sha256_ctx *ctx); + void aes_cmac_zeroize_key(struct aes_cmac_key *key); + void aes_cmac_zeroize_ctx(struct aes_cmac_ctx *ctx); + +Each helper is a ``static inline`` function in the algorithm's header that +wraps ``memzero_explicit()``, for example:: + + static inline void hmac_sha256_zeroize_ctx(struct hmac_sha256_ctx *ctx) + { + memzero_explicit(ctx, sizeof(*ctx)); + } + + +Using __cleanup for automatic zeroization +----------------------------------------- + +The preferred way to zeroize stack-allocated key and context structures is +with the __cleanup() attribute. This ensures zeroization happens on all +exit paths, including error returns and early exits. For example:: + + static int my_aesxts_setkey(..., const u8 *key, unsigned int len) + { + struct crypto_aes_ctx aes __cleanup(aes_zeroize_ctx); + ... + + /* Only half of the key data is cipher key */ + keylen = (len >> 1); + ret = aes_expandkey(&aes, key, keylen); + if (ret) + return ret; + + ... do something with the cipher key ... + + /* The other half is the tweak key */ + ret = aes_expandkey(&aes, (u8 *)(key + keylen), keylen); + if (ret) + return ret; /* <-- Could leak cipher key without __cleanup */ + + ... do something with the tweak key ... + + /* No need for memzero_explicit() at the end thanks to the __cleanup */ + return 0; + } + +Note that __cleanup() attributes should not be used in functions that use +"goto" statements. The benefit of cleanup helpers is the removal of "gotos", +and that "goto" statements can jump between scopes, so the expectation is +that usage of "goto" and cleanup helpers is never mixed in the same function. + + +Automatic vs. manual zeroization +-------------------------------- + +Many ``..._final()`` functions in the crypto library automatically zeroize +their context before returning. When this is the case, the kernel-doc for the +function documents it:: + + After finishing, this zeroizes @ctx. So the caller does not need to do it. + +In these cases, callers on simple code paths (where ``..._final()`` is always +reached) do not need to add __cleanup() or explicit zeroization. +However, __cleanup() is still recommended whenever there are error paths +that bypass ``..._final()``, as it ensures zeroization on all paths. + +For algorithms where ``..._final()`` does *not* zeroize the context (such as +the SHAKE XOFs, where ``shake_squeeze()`` can be called multiple times), +callers must explicitly zeroize the context by calling the appropriate helper +or using __cleanup(), for example:: + + struct shake_ctx ctx __cleanup(shake_zeroize_ctx); + + shake256_init(&ctx); + shake_update(&ctx, data, data_len); + shake_squeeze(&ctx, out, out_len); + /* ctx is automatically zeroized at end of scope */ diff --git a/Documentation/crypto/libcrypto.rst b/Documentation/crypto/libcrypto.rst index e911e05215979c..9533c12caa79dc 100644 --- a/Documentation/crypto/libcrypto.rst +++ b/Documentation/crypto/libcrypto.rst @@ -165,4 +165,5 @@ API documentation libcrypto-signature libcrypto-unauth-encryption libcrypto-utils + libcrypto-zeroization sha3 From 08f28b54cfe5a11abb03a9bac2cb830ffd392346 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 24 Sep 2026 22:25:02 -0700 Subject: [PATCH 1117/2286] Documentation: libcrypto: Add additional testing information Link to the KUnit documentation, mention the existence of the benchmarks and how to enable them, explicitly mention that the tests can be run on real hardware, and mention that it's often possible to clear CPU features via the kernel command line. Acked-by: Ard Biesheuvel Link: https://patch.msgid.link/20260925052502.188024-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/crypto/libcrypto.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/crypto/libcrypto.rst b/Documentation/crypto/libcrypto.rst index 9533c12caa79dc..494541c8a8e01b 100644 --- a/Documentation/crypto/libcrypto.rst +++ b/Documentation/crypto/libcrypto.rst @@ -127,6 +127,8 @@ The crypto library uses standard KUnit tests. Like many of the kernel's other KUnit tests, they are included in the set of tests that is run by ``tools/testing/kunit/kunit.py run --alltests``. +For more information about KUnit, see Documentation/dev-tools/kunit/start.rst. + A ``.kunitconfig`` file is also provided to run just the crypto library tests. For example, here's how to run them in user-mode Linux: @@ -148,6 +150,17 @@ emulate the correct type of hardware for the code to be reached. Since correctness is essential in cryptographic code, new architecture-optimized code is accepted only if it can be tested in QEMU. +Most of the crypto KUnit tests also include benchmarks. To enable these, enable +``CONFIG_CRYPTO_LIB_BENCHMARK=y`` (in addition to the tests themselves). The +benchmark results are printed to the kernel log when the test runs. + +Of course, the crypto KUnit tests can also be run on real hardware. Note that +it is generally still possible to test and benchmark non-default code paths in +this case (for example, the software implementation of AES when the CPU has +hardware-accelerated AES), since on many architectures the kernel supports +disabling CPU features via the kernel command line. For example, on x86, +the ``clearcpuid=aes`` kernel command line option disables AES acceleration. + Note: the crypto library also includes FIPS 140 self-tests. These are lightweight, are designed specifically to meet FIPS 140 requirements, and exist *only* to meet those requirements. Normal testing done by kernel developers and From 51e9b9f2b3a770157e48885b8ea1dad3df4d05fd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 24 Sep 2026 22:32:40 -0700 Subject: [PATCH 1118/2286] MAINTAINERS: Place libcrypto docs under CRYPTO LIBRARY Place Documentation/crypto/libcrypto* under the appropriate entry in MAINTAINERS. Acked-by: Ard Biesheuvel Link: https://patch.msgid.link/20260925053240.189395-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b34..9dc69113f47a46 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6979,6 +6979,7 @@ L: linux-crypto@vger.kernel.org S: Maintained T: git https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-next T: git https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-fixes +F: Documentation/crypto/libcrypto* F: lib/crypto/ F: scripts/crypto/ From 4a6b1a175a0b866af58801c9c1541a2367feae64 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:53 -0700 Subject: [PATCH 1119/2286] lib/crypto: aes-xctr: Pass counter by value to aes_xctr_arch() Update the calling convention for aes_xctr_arch() to pass the counter by value, then do the increment in generic code. This aligns better with the x86_64 and arm64 assembly code for XCTR, which takes the counter by value and thus has to be paired with an increment in C code anyway. Reviewed-by: Thomas Huth Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/aes.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/crypto/aes.c b/lib/crypto/aes.c index 2d29adca795328..e1c19155173625 100644 --- a/lib/crypto/aes.c +++ b/lib/crypto/aes.c @@ -1094,7 +1094,7 @@ static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, } #endif #ifndef aes_xctr_arch -static bool aes_xctr_arch(u8 *dst, const u8 *src, size_t len, u64 *ctr, +static bool aes_xctr_arch(u8 *dst, const u8 *src, size_t len, u64 ctr, const u8 iv[AES_BLOCK_SIZE], const struct aes_enckey *key) { @@ -1150,8 +1150,10 @@ void aes_xctr(u8 *dst, const u8 *src, size_t len, u64 *ctr, __le64 aes_input[2]; u8 keystream[AES_BLOCK_SIZE] __aligned(__alignof__(long)); - if (likely(aes_xctr_arch(dst, src, len, ctr, iv, key.enc_key))) + if (likely(aes_xctr_arch(dst, src, len, *ctr, iv, key.enc_key))) { + *ctr += DIV_ROUND_UP(len, AES_BLOCK_SIZE); return; + } aes_input[1] = get_unaligned((const __le64 *)&iv[8]); /* Handle the full blocks. */ From 7a0c7b6afeeba25195f101849345b17dbe0a3ead Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:54 -0700 Subject: [PATCH 1120/2286] lib/crypto: x86/aes: Clean up aes-aesni.S in preparation for AES modes Various miscellaneous updates to the assembly code in preparation for adding implementations of AES modes to the same file: - Define macros for the function argument registers, function prologues, and function epilogues to centralize some of the handling of 32-bit vs 64-bit. - Refactor the actual AES encryption and AES decryption logic into macros _do_aes and _do_aes_ecb so that some of the modes can reuse it. - Rename mask to expandkey_mask to differentiate it from the bswap_mask that will be added. - Update the prototypes of aes_encrypt_aesni() and aes_decrypt_aesni() to be dst, src, key so that they will match the mode functions. Note that this means passing a pointer to the key struct instead of a (nrounds, rndkeys) pair, similar to what arch/x86/crypto/aes*.S do. Although this makes the assembly code depend on the format of the key struct, having one fewer argument makes it easier to accommodate 32-bit mode, and the C glue code becomes slightly simpler. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/aes-aesni.S | 249 +++++++++++++++++++++++++------------ lib/crypto/x86/aes.h | 28 +++-- 2 files changed, 185 insertions(+), 92 deletions(-) diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index b8c3e104a3bec4..a56fcb36da189b 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -8,25 +8,126 @@ // AVX. It does use up to SSE4.1, which all CPUs with AES-NI have. #include -.section .rodata #ifdef __x86_64__ #define RODATA(label) label(%rip) -#else + +#define ARG0 %rdi +#define ARG1 %rsi +#define ARG2 %rdx +#define ARG2_32 %edx +#define ARG3 %rcx +#define ARG4 %r8 +#define TMP %rax +#define TMP_32 %eax +#define TMP_16 %ax +#define TMP_8 %al + +#else // __x86_64__ + #define RODATA(label) label -#endif +// Caller-save GPRs and the first 3 function arguments, assuming -mregparm=3 +#define ARG0 %eax +#define ARG1 %edx +#define ARG2 %ecx +#define ARG2_32 %ecx + +// *Callee*-save GPRs. +#define ARG3 %edi +#define ARG4 %esi +#define TMP %ebx +#define TMP_32 %ebx +#define TMP_16 %bx +#define TMP_8 %bl +#endif // !__x86_64__ + +// Offsets in struct aes_key +#define OFFSETOF_NROUNDS 4 +#define OFFSETOF_ROUNDKEYS 16 +#define OFFSETOF_INVROUNDKEYS 256 + +.section .rodata +.p2align 4 +.Lexpandkey_mask: // A mask for pshufb that extracts the last dword, rotates it right by 8 // bits, and copies the result to all four dwords. -.p2align 4 -.Lmask: .byte 13, 14, 15, 12, 13, 14, 15, 12, 13, 14, 15, 12, 13, 14, 15, 12 - // The AES round constants, used during key expansion .Lrcon: + // The AES round constants, used during key expansion .long 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 .text +// In 32-bit mode, push certain callee-saved GPRs and optionally load function +// arguments from the stack into them. Do nothing in 64-bit mode. +// +// TMP is always made available as a temporary register. +// +// \uses_arg3 is 0 to not touch ARG3, 1 to make it available as a temporary +// register only, or 2 to actually load it as an argument from the stack. +// Likewise for \uses_arg4 and ARG4. It's assumed the -mregparm=3 convention is +// being used and %esp hasn't been modified since function entry. +.macro _prologue uses_arg3=0, uses_arg4=0 +#ifdef __i386__ + .set ARG3_OFFSET, 4 // Assuming -mregparm=3 + .set ARG3_PUSHED, \uses_arg3 + .set ARG4_PUSHED, \uses_arg4 +.if \uses_arg3 + push ARG3 + .set ARG3_OFFSET, ARG3_OFFSET + 4 + .if \uses_arg3 == 2 + mov ARG3_OFFSET(%esp), ARG3 + .endif +.endif +.if \uses_arg4 + push ARG4 + .set ARG3_OFFSET, ARG3_OFFSET + 4 + .if \uses_arg4 == 2 + mov ARG3_OFFSET+4(%esp), ARG4 + .endif +.endif + push TMP + .set ARG3_OFFSET, ARG3_OFFSET + 4 +#endif // __i386__ +.endm + +.macro _reload_arg3 +#ifdef __i386__ + mov ARG3_OFFSET(%esp), ARG3 +#endif +.endm + +// Undo any pushes that _prologue did, then return. +.macro _epilogue +#ifdef __i386__ + pop TMP +.if ARG4_PUSHED + pop ARG4 +.endif +.if ARG3_PUSHED + pop ARG3 +.endif +#endif + RET +.endm + +.macro _aesenc enc, rndkey, data +.if \enc + aesenc \rndkey, \data +.else + aesdec \rndkey, \data +.endif +.endm + +.macro _aesenclast enc, rndkeylast, data +.if \enc + aesenclast \rndkeylast, \data +.else + aesdeclast \rndkeylast, \data +.endif +.endm + // Transform four dwords [a0, a1, a2, a3] in \a into // [a0, a0^a1, a0^a1^a2, a0^a1^a2^a3]. \tmp is a temporary xmm register. // @@ -71,32 +172,18 @@ .endm .macro _aes_expandkey_aesni is_aes128 -#ifdef __x86_64__ // Arguments - .set RNDKEYS, %rdi - .set INV_RNDKEYS, %rsi - .set IN_KEY, %rdx + .set RNDKEYS, ARG0 + .set INV_RNDKEYS, ARG1 + .set IN_KEY, ARG2 // Other local variables - .set RCON_PTR, %rcx - .set COUNTER, %eax -#else - // Arguments, assuming -mregparm=3 - .set RNDKEYS, %eax - .set INV_RNDKEYS, %edx - .set IN_KEY, %ecx - - // Other local variables - .set RCON_PTR, %ebx - .set COUNTER, %esi -#endif + .set RCON_PTR, ARG3 + .set COUNTER, TMP_32 .set RCON, %xmm6 .set MASK, %xmm7 -#ifdef __i386__ - push %ebx - push %esi -#endif + _prologue uses_arg3=1 .if \is_aes128 // AES-128: the first round key is simply a copy of the raw key. @@ -112,7 +199,7 @@ .endif // Generate the remaining round keys. - movdqa RODATA(.Lmask), MASK + movdqa RODATA(.Lexpandkey_mask), MASK .if \is_aes128 lea RODATA(.Lrcon), RCON_PTR mov $10, COUNTER @@ -176,11 +263,7 @@ movdqu %xmm0, 16(INV_RNDKEYS) // => Last inverse round key .Ldone\@: -#ifdef __i386__ - pop %esi - pop %ebx -#endif - RET + _epilogue .endm // void aes128_expandkey_aesni(u32 rndkeys[], u32 *inv_rndkeys, @@ -195,67 +278,73 @@ SYM_FUNC_START(aes256_expandkey_aesni) _aes_expandkey_aesni 0 SYM_FUNC_END(aes256_expandkey_aesni) -.macro _aes_crypt_aesni enc -#ifdef __x86_64__ - .set RNDKEYS, %rdi - .set NROUNDS, %esi - .set OUT, %rdx - .set IN, %rcx -#else - // Assuming -mregparm=3 - .set RNDKEYS, %eax - .set NROUNDS, %edx - .set OUT, %ecx - .set IN, %ebx // Passed on stack -#endif - -#ifdef __i386__ - push %ebx - mov 8(%esp), %ebx -#endif - - // Zero-th round - movdqu (IN), %xmm0 - movdqu (RNDKEYS), %xmm1 - pxor %xmm1, %xmm0 - - // Normal rounds - add $16, RNDKEYS +// AES-encrypt (\enc=1) or AES-decrypt (\enc=0) the AESDATA registers specified +// in \vecs using the aes_enckey or aes_key pointed to by KEY. RNDKEY must be +// set to a temporary XMM register, NROUNDS to a temporary 32-bit GPR, and +// RNDKEY_PTR to a temporary full-size GPR. +.macro _do_aes enc, vecs:vararg + movl OFFSETOF_NROUNDS(KEY), NROUNDS dec NROUNDS -.Lnext_round\@: - movdqu (RNDKEYS), %xmm1 .if \enc - aesenc %xmm1, %xmm0 + .set rndkey0_offs, OFFSETOF_ROUNDKEYS .else - aesdec %xmm1, %xmm0 + .set rndkey0_offs, OFFSETOF_INVROUNDKEYS .endif - add $16, RNDKEYS + + // Do the zero-th AES round. + movdqu rndkey0_offs(KEY), RNDKEY +.irp i, \vecs + pxor RNDKEY, AESDATA\i +.endr + // Do the regular AES rounds. + lea rndkey0_offs+16(KEY), RNDKEY_PTR +.Lnext_round\@: + movdqu (RNDKEY_PTR), RNDKEY + add $16, RNDKEY_PTR +.irp i, \vecs + _aesenc \enc, RNDKEY, AESDATA\i +.endr dec NROUNDS - jne .Lnext_round\@ + jnz .Lnext_round\@ + // Do the last AES round. + movdqu (RNDKEY_PTR), RNDKEY +.irp i, \vecs + _aesenclast \enc, RNDKEY, AESDATA\i +.endr +.endm - // Last round - movdqu (RNDKEYS), %xmm1 -.if \enc - aesenclast %xmm1, %xmm0 -.else - aesdeclast %xmm1, %xmm0 -.endif - movdqu %xmm0, (OUT) +.macro _do_aes_ecb enc, vecs:vararg +.irp i, \vecs + movdqu \i*16(SRC), AESDATA\i +.endr + _do_aes \enc, \vecs +.irp i, \vecs + movdqu AESDATA\i, \i*16(DST) +.endr +.endm -#ifdef __i386__ - pop %ebx -#endif - RET +.macro _aes_crypt_aesni enc + .set DST, ARG0 + .set SRC, ARG1 + .set KEY, ARG2 + .set RNDKEY_PTR, ARG3 // Temporary register for _do_aes + .set NROUNDS, TMP_32 // Temporary register for _do_aes + .set AESDATA0, %xmm0 + .set RNDKEY, %xmm1 // Temporary register for _do_aes + + _prologue uses_arg3=1 + _do_aes_ecb \enc, 0 + _epilogue .endm -// void aes_encrypt_aesni(const u32 rndkeys[], int nrounds, -// u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +// void aes_encrypt_aesni(u8 dst[AES_BLOCK_SIZE], const u8 src[AES_BLOCK_SIZE], +// const struct aes_enckey *key); SYM_FUNC_START(aes_encrypt_aesni) _aes_crypt_aesni 1 SYM_FUNC_END(aes_encrypt_aesni) -// void aes_decrypt_aesni(const u32 inv_rndkeys[], int nrounds, -// u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +// void aes_decrypt_aesni(u8 dst[AES_BLOCK_SIZE], const u8 src[AES_BLOCK_SIZE], +// const struct aes_key *key); SYM_FUNC_START(aes_decrypt_aesni) _aes_crypt_aesni 0 SYM_FUNC_END(aes_decrypt_aesni) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index b047dee94f578d..06146fef06be04 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -7,16 +7,21 @@ #include -static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_aes); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_aesni); + +/* The assembly code assumes the following offsets. */ +static_assert(offsetof(struct aes_enckey, nrounds) == 4); +static_assert(offsetof(struct aes_enckey, k.rndkeys) == 16); +static_assert(offsetof(struct aes_key, inv_k.inv_rndkeys) == 256); void aes128_expandkey_aesni(u32 rndkeys[], u32 *inv_rndkeys, const u8 in_key[AES_KEYSIZE_128]); void aes256_expandkey_aesni(u32 rndkeys[], u32 *inv_rndkeys, const u8 in_key[AES_KEYSIZE_256]); -void aes_encrypt_aesni(const u32 rndkeys[], int nrounds, - u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); -void aes_decrypt_aesni(const u32 inv_rndkeys[], int nrounds, - u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +void aes_encrypt_aesni(u8 dst[AES_BLOCK_SIZE], const u8 src[AES_BLOCK_SIZE], + const struct aes_enckey *key); +void aes_decrypt_aesni(u8 dst[AES_BLOCK_SIZE], const u8 src[AES_BLOCK_SIZE], + const struct aes_key *key); /* * Expand an AES key using AES-NI if supported and usable or generic code @@ -36,7 +41,7 @@ static void aes_preparekey_arch(union aes_enckey_arch *k, u32 *rndkeys = k->rndkeys; u32 *inv_rndkeys = inv_k ? inv_k->inv_rndkeys : NULL; - if (static_branch_likely(&have_aes) && key_len != AES_KEYSIZE_192 && + if (static_branch_likely(&have_aesni) && key_len != AES_KEYSIZE_192 && irq_fpu_usable()) { kernel_fpu_begin(); if (key_len == AES_KEYSIZE_128) @@ -53,9 +58,9 @@ static void aes_encrypt_arch(const struct aes_enckey *key, u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]) { - if (static_branch_likely(&have_aes) && irq_fpu_usable()) { + if (static_branch_likely(&have_aesni) && irq_fpu_usable()) { kernel_fpu_begin(); - aes_encrypt_aesni(key->k.rndkeys, key->nrounds, out, in); + aes_encrypt_aesni(out, in, key); kernel_fpu_end(); } else { aes_encrypt_generic(key->k.rndkeys, key->nrounds, out, in); @@ -66,10 +71,9 @@ static void aes_decrypt_arch(const struct aes_key *key, u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]) { - if (static_branch_likely(&have_aes) && irq_fpu_usable()) { + if (static_branch_likely(&have_aesni) && irq_fpu_usable()) { kernel_fpu_begin(); - aes_decrypt_aesni(key->inv_k.inv_rndkeys, key->nrounds, - out, in); + aes_decrypt_aesni(out, in, key); kernel_fpu_end(); } else { aes_decrypt_generic(key->inv_k.inv_rndkeys, key->nrounds, @@ -81,5 +85,5 @@ static void aes_decrypt_arch(const struct aes_key *key, static void aes_mod_init_arch(void) { if (boot_cpu_has(X86_FEATURE_AES)) - static_branch_enable(&have_aes); + static_branch_enable(&have_aesni); } From d825a5f3498c570f84de10d0c2f7950658a008bd Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:55 -0700 Subject: [PATCH 1121/2286] lib/crypto: x86/aes-ecb: Add AES-NI optimization Optimize the crypto library's AES-ECB support with AES-NI, bringing its performance on par with the "ecb-aes-aesni" skcipher algorithm it will supersede. The new assembly functions are written from scratch to fit well into the crypto library. However, they are functionally very similar to the functions in arch/x86/crypto/aesni-intel_asm.S that they will supersede and are intended to provide parity with those -- including supporting 32-bit mode, having the inner loops do 4 AES blocks per iteration, etc. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-5-ebiggers@kernel.org Signed-off-by: Eric Biggers --- crypto/aes.c | 2 +- lib/crypto/x86/aes-aesni.S | 54 ++++++++++++++++++++++++++++++++++++++ lib/crypto/x86/aes.h | 35 ++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/crypto/aes.c b/crypto/aes.c index 51eee78396ead5..5dbcc9b0879987 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -610,7 +610,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "ecb(aes)", .base.cra_driver_name = "ecb-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index a56fcb36da189b..52f4ee5c13b9ed 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -348,3 +348,57 @@ SYM_FUNC_END(aes_encrypt_aesni) SYM_FUNC_START(aes_decrypt_aesni) _aes_crypt_aesni 0 SYM_FUNC_END(aes_decrypt_aesni) + +.macro _ecb_crypt enc + // Arguments + .set DST, ARG0 + .set SRC, ARG1 + .set NBLOCKS, ARG2 + .set NBLOCKS32, ARG2_32 // Used for improved code density + .set KEY, ARG3 + + // Other local variables + .set RNDKEY_PTR, ARG4 // Temporary register for _do_aes + .set NROUNDS, TMP_32 // Temporary register for _do_aes + .set AESDATA0, %xmm0 + .set AESDATA1, %xmm1 + .set AESDATA2, %xmm2 + .set AESDATA3, %xmm3 + .set RNDKEY, %xmm4 // Temporary register for _do_aes + _prologue uses_arg3=2, uses_arg4=1 + + sub $4, NBLOCKS + jl .Lecb_loop4_done\@ +.p2align 5 +.Lecb_loop4\@: + _do_aes_ecb \enc, 0,1,2,3 + add $64, SRC + add $64, DST + sub $4, NBLOCKS + jge .Lecb_loop4\@ +.Lecb_loop4_done\@: + add $4, NBLOCKS32 + jz .Lecb_done\@ + +.Lecb_loop1\@: + _do_aes_ecb \enc, 0 + add $16, SRC + add $16, DST + dec NBLOCKS32 + jnz .Lecb_loop1\@ + +.Lecb_done\@: + _epilogue +.endm + +// void aes_ecb_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// const struct aes_enckey *key); +SYM_FUNC_START(aes_ecb_encrypt_aesni) + _ecb_crypt 1 +SYM_FUNC_END(aes_ecb_encrypt_aesni) + +// void aes_ecb_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// const struct aes_key *key); +SYM_FUNC_START(aes_ecb_decrypt_aesni) + _ecb_crypt 0 +SYM_FUNC_END(aes_ecb_decrypt_aesni) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index 06146fef06be04..408625750ae9e2 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -12,6 +12,8 @@ static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_aesni); /* The assembly code assumes the following offsets. */ static_assert(offsetof(struct aes_enckey, nrounds) == 4); static_assert(offsetof(struct aes_enckey, k.rndkeys) == 16); +static_assert(offsetof(struct aes_key, nrounds) == 4); +static_assert(offsetof(struct aes_key, k.rndkeys) == 16); static_assert(offsetof(struct aes_key, inv_k.inv_rndkeys) == 256); void aes128_expandkey_aesni(u32 rndkeys[], u32 *inv_rndkeys, @@ -81,6 +83,39 @@ static void aes_decrypt_arch(const struct aes_key *key, } } +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_ECB) +void aes_ecb_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, + const struct aes_enckey *key); +void aes_ecb_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, + const struct aes_key *key); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_ecb_encrypt_arch aes_ecb_encrypt_arch +static bool aes_ecb_encrypt_arch(u8 *dst, const u8 *src, size_t len, + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + kernel_fpu_begin(); + aes_ecb_encrypt_aesni(dst, src, len / AES_BLOCK_SIZE, key); + kernel_fpu_end(); + return true; +} + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_ecb_decrypt_arch aes_ecb_decrypt_arch +static bool aes_ecb_decrypt_arch(u8 *dst, const u8 *src, size_t len, + const struct aes_key *key) +{ + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + kernel_fpu_begin(); + aes_ecb_decrypt_aesni(dst, src, len / AES_BLOCK_SIZE, key); + kernel_fpu_end(); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_ECB */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From 100d14f3f26045718d93d08d3da44f68c9569c8f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:56 -0700 Subject: [PATCH 1122/2286] lib/crypto: x86/aes-cbc: Add AES-NI optimization Optimize the crypto library's AES-CBC and AES-CBC-CTS support with AES-NI, bringing their performance on par with the "cbc-aes-aesni" and "cts-cbc-aes-aesni" skcipher algorithms they will supersede. The new assembly functions are written from scratch to fit well into the crypto library. However, they are functionally very similar to the functions in arch/x86/crypto/aesni-intel_asm.S that they will supersede and are intended to provide parity with those -- including supporting 32-bit mode, having the inner loops do 4 AES blocks per iteration, etc. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-6-ebiggers@kernel.org Signed-off-by: Eric Biggers --- crypto/aes.c | 4 +- lib/crypto/x86/aes-aesni.S | 232 +++++++++++++++++++++++++++++++++++++ lib/crypto/x86/aes.h | 89 ++++++++++++++ 3 files changed, 323 insertions(+), 2 deletions(-) diff --git a/crypto/aes.c b/crypto/aes.c index 5dbcc9b0879987..297134d5ce855e 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -625,7 +625,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "cbc(aes)", .base.cra_driver_name = "cbc-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, @@ -652,7 +652,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "cts(cbc(aes))", .base.cra_driver_name = "cts-cbc-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index 52f4ee5c13b9ed..51c8522416d72f 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -57,6 +57,14 @@ // The AES round constants, used during key expansion .long 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 +.Lcts_permute_table: + .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 + .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 + .byte 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + .byte 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 + .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 + .text // In 32-bit mode, push certain callee-saved GPRs and optionally load function @@ -402,3 +410,227 @@ SYM_FUNC_END(aes_ecb_encrypt_aesni) SYM_FUNC_START(aes_ecb_decrypt_aesni) _ecb_crypt 0 SYM_FUNC_END(aes_ecb_decrypt_aesni) + +// void aes_cbc_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// u8 iv[AES_BLOCK_SIZE], +// const struct aes_enckey *key); +// +// This assumes nblocks > 0. +SYM_FUNC_START(aes_cbc_encrypt_aesni) + // Arguments + .set DST, ARG0 + .set SRC, ARG1 + .set NBLOCKS, ARG2 + .set IV_PTR, ARG3 + .set KEY, ARG4 + + // Other local variables +#ifdef __x86_64__ + .set RNDKEY_PTR, %r9 +#else + .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later +#endif + .set NROUNDS, TMP_32 + .set AESDATA0, %xmm0 + .set PTEXT, %xmm1 + _prologue uses_arg3=2, uses_arg4=2 + + movdqu (IV_PTR), AESDATA0 +.p2align 5 +.Lcbc_enc_loop: + movdqu (SRC), PTEXT + pxor PTEXT, AESDATA0 + _do_aes 1, 0 + movdqu AESDATA0, (DST) + add $16, SRC + add $16, DST + dec NBLOCKS + jnz .Lcbc_enc_loop + + // Store the next IV. On 32-bit, reload IV_PTR from stack first. + _reload_arg3 + movdqu AESDATA0, (IV_PTR) + _epilogue +SYM_FUNC_END(aes_cbc_encrypt_aesni) + +// void aes_cbc_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// u8 iv[AES_BLOCK_SIZE], const struct aes_key *key); +SYM_FUNC_START(aes_cbc_decrypt_aesni) + // Arguments + .set DST, ARG0 + .set SRC, ARG1 + .set NBLOCKS, ARG2 + .set NBLOCKS32, ARG2_32 // Used for improved code density + .set IV_PTR, ARG3 + .set KEY, ARG4 + + // Other local variables +#ifdef __x86_64__ + .set RNDKEY_PTR, %r9 +#else + .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later +#endif + .set NROUNDS, TMP_32 + .set AESDATA0, %xmm0 + .set AESDATA1, %xmm1 + .set AESDATA2, %xmm2 + .set AESDATA3, %xmm3 + .set RNDKEY, %xmm4 + .set IV, %xmm5 + .set CTEXT0, %xmm6 + .set CTEXT1, %xmm7 +#ifdef __x86_64__ + .set CTEXT2, %xmm8 + .set CTEXT3, %xmm9 +#endif + _prologue uses_arg3=2, uses_arg4=2 + + movdqu (IV_PTR), IV + + sub $4, NBLOCKS + jl .Lcbc_dec_loop4_done +.p2align 5 +.Lcbc_dec_loop4: + movdqu 0(SRC), AESDATA0 + movdqu 16(SRC), AESDATA1 + movdqu 32(SRC), AESDATA2 + movdqu 48(SRC), AESDATA3 + movdqa AESDATA0, CTEXT0 + movdqa AESDATA1, CTEXT1 +#ifdef __x86_64__ + movdqa AESDATA2, CTEXT2 + movdqa AESDATA3, CTEXT3 +#endif + _do_aes 0, 0,1,2,3 + pxor IV, AESDATA0 + pxor CTEXT0, AESDATA1 + pxor CTEXT1, AESDATA2 +#ifdef __x86_64__ + pxor CTEXT2, AESDATA3 + movdqa CTEXT3, IV +#else + movdqu 32(SRC), CTEXT0 + pxor CTEXT0, AESDATA3 + movdqu 48(SRC), IV +#endif + movdqu AESDATA0, 0(DST) + movdqu AESDATA1, 16(DST) + movdqu AESDATA2, 32(DST) + movdqu AESDATA3, 48(DST) + add $64, SRC + add $64, DST + sub $4, NBLOCKS + jge .Lcbc_dec_loop4 +.Lcbc_dec_loop4_done: + add $4, NBLOCKS32 + jz .Lcbc_dec_done + +.Lcbc_dec_loop1: + movdqu (SRC), AESDATA0 + movdqa AESDATA0, CTEXT0 + _do_aes 0, 0 + pxor IV, AESDATA0 + movdqa CTEXT0, IV + movdqu AESDATA0, (DST) + add $16, SRC + add $16, DST + dec NBLOCKS32 + jnz .Lcbc_dec_loop1 + +.Lcbc_dec_done: + // Store the next IV. On 32-bit, reload IV_PTR from stack first. + _reload_arg3 + movdqu IV, (IV_PTR) + _epilogue +SYM_FUNC_END(aes_cbc_decrypt_aesni) + +.macro _aes_cbc_cts_crypt enc + .set DST, ARG0 + .set SRC, ARG1 + .set PN_LEN, ARG2 + .set IV_PTR, ARG3 + .set KEY, ARG4 + .set RNDKEY_PTR, IV_PTR // Temporary register for _do_aes + .set NROUNDS, TMP_32 // Temporary register for _do_aes + .set RSHIFT_MASK, %xmm0 // range(16-PN_LEN,16) + [0x80, 0x80, ...] + // Must be %xmm0 for pblendvb to work. + .set LSHIFT_MASK, %xmm1 // [0x80, 0x80, ...] + range(PN_LEN) + .set AESDATA0, %xmm2 + .set AESDATA1, %xmm3 + .set RNDKEY, %xmm4 // Temporary register for _do_aes + .set IV, %xmm5 + + _prologue uses_arg3=2, uses_arg4=2 + + // Load LSHIFT_MASK and RSHIFT_MASK. + lea RODATA(.Lcts_permute_table), TMP + movdqu (TMP,PN_LEN), LSHIFT_MASK + sub PN_LEN, TMP + movdqu 32(TMP), RSHIFT_MASK + + // Load the IV, freeing up IV_PTR to reuse for RNDKEY_PTR. + movdqu (IV_PTR), IV + + // Load the second-from-last source block (P[n-1] or C[n-1]) and + // AES-en/decrypt it. In the encryption case, XOR it with the IV first. + movdqu (SRC), AESDATA0 +.if \enc + pxor IV, AESDATA0 +.endif + _do_aes \enc, 0 + + // Load the last source block (P[n] or C[n]), left-aligned. + movdqu (SRC,PN_LEN), AESDATA1 + +.if \enc + // Right-align P[n], XOR it with the first AES output, and AES-encrypt + // the result. This gives C[n-1] in AESDATA1. + pshufb RSHIFT_MASK, AESDATA1 + pxor AESDATA0, AESDATA1 + _do_aes 1, 1 + + // Left-align the first AES output to get left-aligned C[n]. + pshufb LSHIFT_MASK, AESDATA0 +.else + // Save a copy of the first AES output in %xmm6 for later. + movdqa AESDATA0, %xmm6 + + // Left-align the first AES output and XOR it with the + // already-left-aligned C[n]. This gives the left-aligned P[n]. + pshufb LSHIFT_MASK, AESDATA0 + pxor AESDATA1, AESDATA0 + + // Right-align C[n], then use RSHIFT_MASK (in %xmm0 as pblendvb's + // implicit operand) as a blend mask to select C[n] and the remaining + // bytes from the first AES output, with the result in AESDATA1. + pshufb RSHIFT_MASK, AESDATA1 + pblendvb %xmm6, AESDATA1 + + // AES-decrypt the result and XOR with the IV to get P[n-1] in AESDATA1. + _do_aes 0, 1 + pxor IV, AESDATA1 +.endif + // Store the left-aligned C[n] or P[n], then C[n-1] or P[n-1]. + // Note that these stores may overlap. + movdqu AESDATA0, (DST,PN_LEN) + movdqu AESDATA1, (DST) + _epilogue +.endm + +// void aes_cbc_cts_encrypt_aesni(u8 *dst, const u8 *src, long pn_len, +// const u8 iv[AES_BLOCK_SIZE], +// const struct aes_enckey *key); +// void aes_cbc_cts_decrypt_aesni(u8 *dst, const u8 *src, long pn_len, +// const u8 iv[AES_BLOCK_SIZE], +// const struct aes_key *key); +// +// Use the CS3 variant of ciphertext stealing to encrypt or decrypt the last two +// blocks in a message. The two blocks are located at src and consist of one +// full block of length 16 bytes and one possibly-partial block of length 1 <= +// pn_len <= 16 bytes. The output is written to dst, which may equal src. +SYM_FUNC_START(aes_cbc_cts_encrypt_aesni) + _aes_cbc_cts_crypt 1 +SYM_FUNC_END(aes_cbc_cts_encrypt_aesni) +SYM_FUNC_START(aes_cbc_cts_decrypt_aesni) + _aes_cbc_cts_crypt 0 +SYM_FUNC_END(aes_cbc_cts_decrypt_aesni) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index 408625750ae9e2..a0346cb3ecb5cc 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -116,6 +116,95 @@ static bool aes_ecb_decrypt_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_ECB */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CBC) +void aes_cbc_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, + u8 iv[AES_BLOCK_SIZE], const struct aes_enckey *key); +void aes_cbc_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, + u8 iv[AES_BLOCK_SIZE], const struct aes_key *key); +void aes_cbc_cts_encrypt_aesni(u8 *dst, const u8 *src, long pn_len, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key); +void aes_cbc_cts_decrypt_aesni(u8 *dst, const u8 *src, long pn_len, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_key *key); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_cbc_encrypt_arch aes_cbc_encrypt_arch +static bool aes_cbc_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + kernel_fpu_begin(); + aes_cbc_encrypt_aesni(dst, src, len / AES_BLOCK_SIZE, iv, key); + kernel_fpu_end(); + return true; +} + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_cbc_decrypt_arch aes_cbc_decrypt_arch +static bool aes_cbc_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_key *key) +{ + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + kernel_fpu_begin(); + aes_cbc_decrypt_aesni(dst, src, len / AES_BLOCK_SIZE, iv, key); + kernel_fpu_end(); + return true; +} + +/* len can be any value greater than AES_BLOCK_SIZE here. */ +#define aes_cbc_cts_encrypt_arch aes_cbc_cts_encrypt_arch +static bool aes_cbc_cts_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key) +{ + const size_t cbc_blocks = (len - AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE; + const size_t pn_len = ((len - 1) % AES_BLOCK_SIZE) + 1; + + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + + kernel_fpu_begin(); + if (cbc_blocks) { + aes_cbc_encrypt_aesni(dst, src, cbc_blocks, iv, key); + dst += cbc_blocks * AES_BLOCK_SIZE; + src += cbc_blocks * AES_BLOCK_SIZE; + } + /* Encrypt the last 17 to 32 bytes. */ + aes_cbc_cts_encrypt_aesni(dst, src, pn_len, iv, key); + kernel_fpu_end(); + return true; +} + +/* len can be any value greater than AES_BLOCK_SIZE here. */ +#define aes_cbc_cts_decrypt_arch aes_cbc_cts_decrypt_arch +static bool aes_cbc_cts_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_key *key) +{ + const size_t cbc_blocks = (len - AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE; + const size_t pn_len = ((len - 1) % AES_BLOCK_SIZE) + 1; + + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + + kernel_fpu_begin(); + if (cbc_blocks) { + aes_cbc_decrypt_aesni(dst, src, cbc_blocks, iv, key); + dst += cbc_blocks * AES_BLOCK_SIZE; + src += cbc_blocks * AES_BLOCK_SIZE; + } + /* Decrypt the last 17 to 32 bytes. */ + aes_cbc_cts_decrypt_aesni(dst, src, pn_len, iv, key); + kernel_fpu_end(); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_CBC */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From 0a125ba0c31bd0250107ca62845cdd7bb8799689 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:57 -0700 Subject: [PATCH 1123/2286] lib/crypto: x86/aes-ctr: Add AES-NI optimization Optimize the crypto library's AES-CTR support with AES-NI, bringing its performance on par with the "ctr-aes-aesni" skcipher algorithm it will supersede. The new assembly function is written from scratch to fit well into the crypto library and to be more consistent with aes-ctr-avx-x86_64.S than the code in arch/x86/crypto/aesni-intel_asm.S that it will supersede. That includes using the "ctr64" convention, where the assembly code is simplified by making the C code handle incrementing the high 64 bits of the counter. Unlike the ECB, CBC, and XTS code, 32-bit support is *not* included for this one, as the existing CTR code didn't have it. Note: the priority of ctr-aes-lib is left unchanged at 110 temporarily. It will be increased when the AVX-optimized code is migrated too. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-7-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/aes-aesni.S | 130 ++++++++++++++++++++++++++++++++++++- lib/crypto/x86/aes.h | 54 +++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index 51c8522416d72f..297fe21ba8306e 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -4,7 +4,8 @@ // // Copyright 2026 Google LLC // -// The code in this file supports 32-bit and 64-bit CPUs, and it doesn't require +// The code in this file supports 32-bit and 64-bit CPUs (except for +// aes_ctr64_crypt_aesni() which supports 64-bit only), and it doesn't require // AVX. It does use up to SSE4.1, which all CPUs with AES-NI have. #include @@ -48,6 +49,12 @@ .section .rodata .p2align 4 +#ifdef __x86_64__ +.Lbswap_mask: + // A mask for pshufb that byte-reflects the value. + .byte 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#endif + .Lexpandkey_mask: // A mask for pshufb that extracts the last dword, rotates it right by 8 // bits, and copies the result to all four dwords. @@ -634,3 +641,124 @@ SYM_FUNC_END(aes_cbc_cts_encrypt_aesni) SYM_FUNC_START(aes_cbc_cts_decrypt_aesni) _aes_cbc_cts_crypt 0 SYM_FUNC_END(aes_cbc_cts_decrypt_aesni) + +#ifdef __x86_64__ +// void aes_ctr64_crypt_aesni(u8 *dst, const u8 *src, s64 len, +// const u64 le_ctr[2], +// const struct aes_enckey *key); +SYM_FUNC_START(aes_ctr64_crypt_aesni) + // Arguments + .set DST, ARG0 + .set SRC, ARG1 + .set LEN, ARG2 + .set LEN32, ARG2_32 // Used for improved code density + .set LE_CTR_PTR, ARG3 // Used as temp reg after LE_CTR is loaded + .set KEY, ARG4 + + // Other local variables + .set AESDATA0, %xmm0 + .set AESDATA1, %xmm1 + .set AESDATA2, %xmm2 + .set AESDATA3, %xmm3 + .set LE_CTR, %xmm4 // Current 128-bit little endian counter + .set LE_CTR_INC, %xmm5 // Initialized to (u64[])[1, 0] + .set BSWAP_MASK, %xmm6 + .set RNDKEY, %xmm7 + .set RNDKEY_PTR, LE_CTR_PTR // Temporary register for _do_aes + .set NROUNDS, TMP_32 // Temporary register for _do_aes + + // Initialize LE_CTR, BSWAP_MASK, and LE_CTR_INC. + movdqu (LE_CTR_PTR), LE_CTR + movdqa RODATA(.Lbswap_mask), BSWAP_MASK + mov $1, TMP_32 + movd TMP_32, LE_CTR_INC + + // Encrypt and XOR four blocks (64 bytes) at a time. + sub $64, LEN + jl .Lctr_loop4_done +.p2align 5 +.Lctr_loop4: +.irp i, 0,1,2,3 + movdqa LE_CTR, AESDATA\i + pshufb BSWAP_MASK, AESDATA\i // => big endian counter + paddq LE_CTR_INC, LE_CTR +.endr + _do_aes 1, 0,1,2,3 + // AESDATA[0-3] now contain four keystream blocks. +.irp i, 0,1,2,3 + movdqu \i*16(SRC), RNDKEY // Use RNDKEY as temp register. + pxor RNDKEY, AESDATA\i + movdqu AESDATA\i, \i*16(DST) +.endr + add $64, SRC + add $64, DST + sub $64, LEN + jge .Lctr_loop4 +.Lctr_loop4_done: + add $64, LEN32 + jz .Lctr_done + + // 1 <= LEN <= 63 bytes remain. Prepare four more keystream blocks. +.irp i, 0,1,2,3 + movdqa LE_CTR, AESDATA\i + pshufb BSWAP_MASK, AESDATA\i // => big endian counter + .if \i != 3 + paddq LE_CTR_INC, LE_CTR + .endif +.endr + _do_aes 1, 0,1,2,3 + // AESDATA[0-3] now contain four keystream blocks. + + // XOR one block (16 bytes) at a time. + sub $16, LEN32 + jl .Lctr_xor_partial +.Lctr_xor1: + movdqu (SRC), RNDKEY // Use RNDKEY as temp register. + pxor RNDKEY, AESDATA0 + movdqu AESDATA0, (DST) + movdqa AESDATA1, AESDATA0 + movdqa AESDATA2, AESDATA1 + movdqa AESDATA3, AESDATA2 + add $16, SRC + add $16, DST + sub $16, LEN32 + jge .Lctr_xor1 + + // XOR the remaining LEN mod 16 bytes. +.Lctr_xor_partial: + test $8, LEN32 + jz 1f + movq AESDATA0, TMP + xor (SRC), TMP // XOR 8 bytes. + mov TMP, (DST) + add $8, SRC + add $8, DST + psrldq $8, AESDATA0 +1: + test $4, LEN32 + jz 2f + movd AESDATA0, TMP_32 + xor (SRC), TMP_32 // XOR 4 bytes. + mov TMP_32, (DST) + add $4, SRC + add $4, DST + psrldq $4, AESDATA0 +2: + test $2, LEN32 + jz 3f + movd AESDATA0, TMP_32 + xor (SRC), TMP_16 // XOR 2 bytes. + mov TMP_16, (DST) + add $2, SRC + add $2, DST + psrldq $2, AESDATA0 +3: + test $1, LEN32 + jz .Lctr_done + movd AESDATA0, TMP_32 + xor (SRC), TMP_8 // XOR 1 byte. + mov TMP_8, (DST) +.Lctr_done: + RET +SYM_FUNC_END(aes_ctr64_crypt_aesni) +#endif // __x86_64__ diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index a0346cb3ecb5cc..2a2b26d10e8706 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -205,6 +205,60 @@ static bool aes_cbc_cts_decrypt_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_CBC */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CTR) && IS_ENABLED(CONFIG_X86_64) +void aes_ctr64_crypt_aesni(u8 *dst, const u8 *src, s64 len, const u64 le_ctr[2], + const struct aes_enckey *key); + +static void aes_ctr64_x86(u8 *dst, const u8 *src, size_t len, + const u64 le_ctr[2], const struct aes_enckey *key) +{ + aes_ctr64_crypt_aesni(dst, src, len, le_ctr, key); +} + +#define aes_ctr_arch aes_ctr_arch +static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, + u8 ctr[AES_BLOCK_SIZE], const struct aes_enckey *key) +{ + u64 le_ctr[2]; + u64 ctr64; + size_t nblocks; + size_t part1_len; + + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + + ctr64 = le_ctr[0] = get_unaligned_be64(&ctr[8]); + le_ctr[1] = get_unaligned_be64(&ctr[0]); + + kernel_fpu_begin(); + + nblocks = DIV_ROUND_UP(len, AES_BLOCK_SIZE); + ctr64 += nblocks; + + if (likely(ctr64 >= nblocks)) { + /* The low 64 bits of the counter won't overflow. */ + aes_ctr64_x86(dst, src, len, le_ctr, key); + } else { + /* + * The low 64 bits of the counter will overflow. The + * assembly doesn't handle this case, so split the + * operation into two at the point where the overflow + * will occur. After the first part, add the carry bit. + */ + part1_len = min(len, (nblocks - ctr64) * AES_BLOCK_SIZE); + aes_ctr64_x86(dst, src, part1_len, le_ctr, key); + le_ctr[0] = 0; + le_ctr[1]++; + aes_ctr64_x86(dst + part1_len, src + part1_len, len - part1_len, + le_ctr, key); + } + kernel_fpu_end(); + put_unaligned_be64(ctr64, &ctr[8]); + put_unaligned_be64(le_ctr[1], &ctr[0]); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_CTR && CONFIG_X86_64 */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From 0c2cf81e9f1db7bfce51f495a27a2582e39f49a6 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:58 -0700 Subject: [PATCH 1124/2286] lib/crypto: x86/aes-xts: Add AES-NI optimization Optimize the crypto library's AES-XTS support with AES-NI, bringing its performance on par with the "xts-aes-aesni" skcipher algorithm it will supersede. The new assembly code is written from scratch to fit well into the crypto library and to be more consistent with aes-xts-avx-x86_64.S than the code in arch/x86/crypto/aesni-intel_asm.S that it will supersede. At a high level it is quite similar though, including doing 4 blocks per iteration and supporting 32-bit mode for parity with the old code. The new assembly code also fixes the flaw the old code had where the encrypted tweaks were spilled to the destination buffer, rather than kept entirely in registers (64-bit mode) or spilled to the stack (32-bit mode). The encrypted tweaks are secret values that should not be exposed to any code that doesn't have access to the key itself. Note: the priority of xts-aes-lib is left unchanged at 110 temporarily. It will be increased when the AVX-optimized AES-XTS code is migrated too. Most systems use the AVX-optimized code; this commit just deals with support for older CPUs that have AES-NI but not AVX (and 32-bit). Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-8-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/aes-aesni.S | 159 +++++++++++++++++++++++++++++++++++++ lib/crypto/x86/aes.h | 44 ++++++++++ 2 files changed, 203 insertions(+) diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index 297fe21ba8306e..cbc0cc23f63ebb 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -49,6 +49,17 @@ .section .rodata .p2align 4 +.Lxts_gf_poly: + // For XTS: a constant used when advancing the tweak by one block by + // multiplying by the polynomial 'x' in GF(2^128). The low 64 bits of + // this value represent the polynomial x^7 + x^2 + x + 1; it is the + // value that must be XOR'd into the low 64 bits of the tweak each time + // a 1 is carried out of the high 64 bits. + // + // The high 64 bits of this value is just the internal carry bit that + // exists when there's a carry out of the low 64 bits of the tweak. + .quad 0x87, 1 + #ifdef __x86_64__ .Lbswap_mask: // A mask for pshufb that byte-reflects the value. @@ -762,3 +773,151 @@ SYM_FUNC_START(aes_ctr64_crypt_aesni) RET SYM_FUNC_END(aes_ctr64_crypt_aesni) #endif // __x86_64__ + +// Given a 128-bit XTS tweak in the xmm register \tweak, compute the next tweak +// (by multiplying by the polynomial 'x') and write it back to \tweak. +.macro _next_tweak tweak, tmp + pshufd $0x13, \tweak, \tmp + paddq \tweak, \tweak + psrad $31, \tmp + pand GF_POLY, \tmp + pxor \tmp, \tweak +.endm + +.macro _aes_xts_crypt enc + // Arguments + .set DST, ARG0 + .set SRC, ARG1 + .set NBLOCKS, ARG2 + .set NBLOCKS32, ARG2_32 // Used for improved code density + .set TWEAK_PTR, ARG3 + .set KEY, ARG4 + + // Other local variables +#ifdef __x86_64__ + .set RNDKEY_PTR, %r9 +#else + .set RNDKEY_PTR, TWEAK_PTR // TWEAK_PTR is clobbered and reloaded later. +#endif + .set NROUNDS, TMP_32 + .set AESDATA0, %xmm0 + .set AESDATA1, %xmm1 + .set AESDATA2, %xmm2 + .set AESDATA3, %xmm3 + .set GF_POLY, %xmm4 + .set RNDKEY, %xmm5 + .set TWEAK, %xmm6 + .set SAVED_TWEAK0, %xmm7 +#ifdef __x86_64__ + .set SAVED_TWEAK1, %xmm8 + .set SAVED_TWEAK2, %xmm9 +#endif + + _prologue uses_arg3=2, uses_arg4=2 +#ifdef __i386__ + // Reserve 16-byte aligned space to spill two tweaks. + push %ebp + mov %esp, %ebp + and $~15, %esp + sub $32, %esp +#endif + + movdqu (TWEAK_PTR), TWEAK + movdqa RODATA(.Lxts_gf_poly), GF_POLY + + sub $4, NBLOCKS + jl .Lxts_loop4_done\@ +.p2align 5 +.Lxts_loop4\@: + // Load the next four source blocks into AESDATA[0-3] and XOR them with + // their tweaks, advancing the tweak three times in order to do so. + // Save the four tweaks for later; on 64-bit they all fit into + // registers, while on 32-bit two tweaks are spilled to the stack. +.irp i, 0,1,2,3 + movdqu \i*16(SRC), AESDATA\i + pxor TWEAK, AESDATA\i + .if \i != 3 +#ifdef __x86_64__ + movdqa TWEAK, SAVED_TWEAK\i +#else + .if \i == 0 + movdqa TWEAK, SAVED_TWEAK0 + .else + movdqa TWEAK, (\i-1)*16(%esp) + .endif +#endif + _next_tweak TWEAK, RNDKEY + .endif +.endr + + // Encrypt or decrypt the blocks. + _do_aes \enc, 0,1,2,3 + + // XOR the blocks with the saved tweaks. + pxor SAVED_TWEAK0, AESDATA0 +#ifdef __x86_64__ + pxor SAVED_TWEAK1, AESDATA1 + pxor SAVED_TWEAK2, AESDATA2 +#else + pxor (%esp), AESDATA1 + pxor 16(%esp), AESDATA2 +#endif + pxor TWEAK, AESDATA3 + + // Store the encrypted or decrypted blocks. +.irp i, 0,1,2,3 + movdqu AESDATA\i, \i*16(DST) +.endr + + _next_tweak TWEAK, RNDKEY + add $64, SRC + add $64, DST + sub $4, NBLOCKS + jge .Lxts_loop4\@ +.Lxts_loop4_done\@: + add $4, NBLOCKS32 + jz .Lxts_done\@ + +.Lxts_loop1\@: + movdqu (SRC), AESDATA0 + pxor TWEAK, AESDATA0 + _do_aes \enc, 0 + pxor TWEAK, AESDATA0 + movdqu AESDATA0, (DST) + _next_tweak TWEAK, RNDKEY + add $16, SRC + add $16, DST + dec NBLOCKS32 + jnz .Lxts_loop1\@ + +.Lxts_done\@: +#ifdef __i386__ + // Zeroize the stack buffer that tweaks were spilled to. + pxor AESDATA0, AESDATA0 + movdqa AESDATA0, (%esp) + movdqa AESDATA0, 16(%esp) + mov %ebp, %esp + pop %ebp +#endif + // Store the next tweak. On 32-bit, reload TWEAK_PTR from stack first. + _reload_arg3 + movdqu TWEAK, (TWEAK_PTR) + _epilogue +.endm + +// void aes_xts_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// u8 tweak[AES_BLOCK_SIZE], +// const struct aes_key *key); +// void aes_xts_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, +// u8 tweak[AES_BLOCK_SIZE], +// const struct aes_key *key); +// +// `tweak` must have already been encrypted by the tweak key; `key` is just the +// main key. To allow incremental computation, `tweak` is updated to contain +// the next tweak. +SYM_FUNC_START(aes_xts_encrypt_aesni) + _aes_xts_crypt 1 +SYM_FUNC_END(aes_xts_encrypt_aesni) +SYM_FUNC_START(aes_xts_decrypt_aesni) + _aes_xts_crypt 0 +SYM_FUNC_END(aes_xts_decrypt_aesni) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index 2a2b26d10e8706..54b599bd258195 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -259,6 +259,50 @@ static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_CTR && CONFIG_X86_64 */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) +void aes_xts_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], const struct aes_key *key); +void aes_xts_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], const struct aes_key *key); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +static __always_inline bool +aes_xts_crypt_x86(u8 *dst, const u8 *src, size_t len, u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont, bool enc) +{ + const long nblocks = len / AES_BLOCK_SIZE; + + if (!static_branch_likely(&have_aesni) || unlikely(!irq_fpu_usable())) + return false; + + kernel_fpu_begin(); + if (!cont) + aes_encrypt_aesni(tweak, tweak, &key->tweak_key); + if (enc) + aes_xts_encrypt_aesni(dst, src, nblocks, tweak, &key->main_key); + else + aes_xts_decrypt_aesni(dst, src, nblocks, tweak, &key->main_key); + kernel_fpu_end(); + return true; +} + +#define aes_xts_encrypt_arch aes_xts_encrypt_arch +static bool aes_xts_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_x86(dst, src, len, tweak, key, cont, true); +} + +#define aes_xts_decrypt_arch aes_xts_decrypt_arch +static bool aes_xts_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_x86(dst, src, len, tweak, key, cont, false); +} +#endif /* CONFIG_CRYPTO_LIB_AES_XTS */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From 2f3f90b00a6604a348315b924b017ddb8bf0eb3c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:42:59 -0700 Subject: [PATCH 1125/2286] crypto: x86/aes - Drop superseded 32-bit build support Now that bare AES, AES-ECB, AES-CBC, AES-CBC-CTS, and AES-XTS are accelerated on 32-bit x86 kernels via libaes, the equivalent code in aesni-intel is redundant. Only the 64-bit-only code in aesni-intel is still needed. Thus, drop the 32-bit build support from aesni-intel. These AES modes remain accelerated on 32-bit kernels via libaes. aesni-intel_asm.S is left untouched, as later commits remove that file entirely one mode at a time anyway. This commit just drops 32-bit support right away to prevent bisection hazards during that. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-9-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 6 ++---- arch/x86/crypto/Makefile | 13 +++++++------ arch/x86/crypto/aesni-intel_glue.c | 22 +--------------------- 3 files changed, 10 insertions(+), 31 deletions(-) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index f65d7b83702f5f..3c4c582e7f2d8b 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -4,6 +4,7 @@ menu "Accelerated Cryptographic Algorithms for CPU (x86)" config CRYPTO_AES_NI_INTEL tristate "Ciphers: AES, modes: ECB, CBC, CTS, CTR, XCTR, XTS, GCM (AES-NI/VAES)" + depends on 64BIT select CRYPTO_AEAD select CRYPTO_LIB_AES select CRYPTO_LIB_GF128MUL @@ -12,13 +13,10 @@ config CRYPTO_AES_NI_INTEL AEAD cipher: AES with GCM Length-preserving ciphers: AES with ECB, CBC, CTS, CTR, XCTR, XTS - Architecture: x86 (32-bit and 64-bit) using: + Architecture: x86_64 using: - AES-NI (AES new instructions) - VAES (Vector AES) - Some algorithm implementations are supported only in 64-bit builds, - and some have additional prerequisites such as AVX2 or AVX512. - config CRYPTO_BLOWFISH_X86_64 tristate "Ciphers: Blowfish, modes: ECB, CBC" depends on 64BIT diff --git a/arch/x86/crypto/Makefile b/arch/x86/crypto/Makefile index e04ff8718d6b64..1c3feb6d72b728 100644 --- a/arch/x86/crypto/Makefile +++ b/arch/x86/crypto/Makefile @@ -40,12 +40,13 @@ obj-$(CONFIG_CRYPTO_AEGIS128_AESNI_SSE2) += aegis128-aesni.o aegis128-aesni-y := aegis128-aesni-asm.o aegis128-aesni-glue.o obj-$(CONFIG_CRYPTO_AES_NI_INTEL) += aesni-intel.o -aesni-intel-y := aesni-intel_asm.o aesni-intel_glue.o -aesni-intel-$(CONFIG_64BIT) += aes-ctr-avx-x86_64.o \ - aes-gcm-aesni-x86_64.o \ - aes-gcm-vaes-avx2.o \ - aes-gcm-vaes-avx512.o \ - aes-xts-avx-x86_64.o +aesni-intel-y := aesni-intel_asm.o \ + aesni-intel_glue.o \ + aes-ctr-avx-x86_64.o \ + aes-gcm-aesni-x86_64.o \ + aes-gcm-vaes-avx2.o \ + aes-gcm-vaes-avx512.o \ + aes-xts-avx-x86_64.o obj-$(CONFIG_CRYPTO_SM4_AESNI_AVX_X86_64) += sm4-aesni-avx-x86_64.o sm4-aesni-avx-x86_64-y := sm4-aesni-avx-asm_64.o sm4_aesni_avx_glue.o diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index 1903ed8bbe3b31..392dcfac6c2894 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -80,10 +80,8 @@ asmlinkage void aesni_xts_enc(const struct crypto_aes_ctx *ctx, u8 *out, asmlinkage void aesni_xts_dec(const struct crypto_aes_ctx *ctx, u8 *out, const u8 *in, unsigned int len, u8 *iv); -#ifdef CONFIG_X86_64 asmlinkage void aesni_ctr_enc(struct crypto_aes_ctx *ctx, u8 *out, const u8 *in, unsigned int len, u8 *iv); -#endif static inline struct crypto_aes_ctx *aes_ctx(void *raw_ctx) { @@ -319,7 +317,6 @@ static int cts_cbc_decrypt(struct skcipher_request *req) return skcipher_walk_done(&walk, 0); } -#ifdef CONFIG_X86_64 /* This is the non-AVX version. */ static int ctr_crypt_aesni(struct skcipher_request *req) { @@ -353,7 +350,6 @@ static int ctr_crypt_aesni(struct skcipher_request *req) } return err; } -#endif static int xts_setkey_aesni(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen) @@ -469,8 +465,7 @@ xts_crypt(struct skcipher_request *req, xts_encrypt_iv_func encrypt_iv, * single-scatterlist-element messages as efficiently as possible. The * code is 64-bit specific, as it assumes no page mapping is needed. */ - if (IS_ENABLED(CONFIG_X86_64) && - likely(req->src->length >= req->cryptlen && + if (likely(req->src->length >= req->cryptlen && req->dst->length >= req->cryptlen)) { (*crypt_func)(&ctx->crypt_ctx, sg_virt(req->src), sg_virt(req->dst), req->cryptlen, req->iv); @@ -557,7 +552,6 @@ static struct skcipher_alg aesni_skciphers[] = { .setkey = aesni_skcipher_setkey, .encrypt = cts_cbc_encrypt, .decrypt = cts_cbc_decrypt, -#ifdef CONFIG_X86_64 }, { .base = { .cra_name = "ctr(aes)", @@ -574,7 +568,6 @@ static struct skcipher_alg aesni_skciphers[] = { .setkey = aesni_skcipher_setkey, .encrypt = ctr_crypt_aesni, .decrypt = ctr_crypt_aesni, -#endif }, { .base = { .cra_name = "xts(aes)", @@ -594,7 +587,6 @@ static struct skcipher_alg aesni_skciphers[] = { } }; -#ifdef CONFIG_X86_64 asmlinkage void aes_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, u8 iv[AES_BLOCK_SIZE]); @@ -1604,18 +1596,6 @@ static void unregister_avx_algs(void) unregister_aeads(aes_gcm_algs_vaes_avx2); unregister_aeads(aes_gcm_algs_vaes_avx512); } -#else /* CONFIG_X86_64 */ -static struct aead_alg aes_gcm_algs_aesni[0]; - -static int __init register_avx_algs(void) -{ - return 0; -} - -static void unregister_avx_algs(void) -{ -} -#endif /* !CONFIG_X86_64 */ static const struct x86_cpu_id aesni_cpu_id[] = { X86_MATCH_FEATURE(X86_FEATURE_AES, NULL), From 0ecd756ee5f1ebf3585e1190ad28f8da9240521e Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:00 -0700 Subject: [PATCH 1126/2286] crypto: x86/aes-ecb - Remove superseded ECB skcipher Now that the AES-ECB library functions are optimized with dedicated AES-NI code and exposed via the skcipher API via crypto/aes.c, the similar implementation in aesni-intel is redundant. Remove it. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-10-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 4 +- arch/x86/crypto/aesni-intel_asm.S | 121 ----------------------------- arch/x86/crypto/aesni-intel_glue.c | 62 --------------- 3 files changed, 2 insertions(+), 185 deletions(-) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index 3c4c582e7f2d8b..f7c4243dd1871e 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -3,7 +3,7 @@ menu "Accelerated Cryptographic Algorithms for CPU (x86)" config CRYPTO_AES_NI_INTEL - tristate "Ciphers: AES, modes: ECB, CBC, CTS, CTR, XCTR, XTS, GCM (AES-NI/VAES)" + tristate "Ciphers: AES, modes: CBC, CTS, CTR, XCTR, XTS, GCM (AES-NI/VAES)" depends on 64BIT select CRYPTO_AEAD select CRYPTO_LIB_AES @@ -11,7 +11,7 @@ config CRYPTO_AES_NI_INTEL select CRYPTO_SKCIPHER help AEAD cipher: AES with GCM - Length-preserving ciphers: AES with ECB, CBC, CTS, CTR, XCTR, XTS + Length-preserving ciphers: AES with CBC, CTS, CTR, XCTR, XTS Architecture: x86_64 using: - AES-NI (AES new instructions) diff --git a/arch/x86/crypto/aesni-intel_asm.S b/arch/x86/crypto/aesni-intel_asm.S index 6abe5e38a6d753..db7f259ee8ab49 100644 --- a/arch/x86/crypto/aesni-intel_asm.S +++ b/arch/x86/crypto/aesni-intel_asm.S @@ -601,127 +601,6 @@ SYM_FUNC_START_LOCAL(_aesni_dec4) RET SYM_FUNC_END(_aesni_dec4) -/* - * void aesni_ecb_enc(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len) - */ -SYM_FUNC_START(aesni_ecb_enc) - FRAME_BEGIN -#ifndef __x86_64__ - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+16)(%esp), KEYP # ctx - movl (FRAME_OFFSET+20)(%esp), OUTP # dst - movl (FRAME_OFFSET+24)(%esp), INP # src - movl (FRAME_OFFSET+28)(%esp), LEN # len -#endif - test LEN, LEN # check length - jz .Lecb_enc_ret - mov 480(KEYP), KLEN - cmp $16, LEN - jb .Lecb_enc_ret - cmp $64, LEN - jb .Lecb_enc_loop1 -.align 4 -.Lecb_enc_loop4: - movups (INP), STATE1 - movups 0x10(INP), STATE2 - movups 0x20(INP), STATE3 - movups 0x30(INP), STATE4 - call _aesni_enc4 - movups STATE1, (OUTP) - movups STATE2, 0x10(OUTP) - movups STATE3, 0x20(OUTP) - movups STATE4, 0x30(OUTP) - sub $64, LEN - add $64, INP - add $64, OUTP - cmp $64, LEN - jge .Lecb_enc_loop4 - cmp $16, LEN - jb .Lecb_enc_ret -.align 4 -.Lecb_enc_loop1: - movups (INP), STATE1 - call _aesni_enc1 - movups STATE1, (OUTP) - sub $16, LEN - add $16, INP - add $16, OUTP - cmp $16, LEN - jge .Lecb_enc_loop1 -.Lecb_enc_ret: -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_ecb_enc) - -/* - * void aesni_ecb_dec(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len); - */ -SYM_FUNC_START(aesni_ecb_dec) - FRAME_BEGIN -#ifndef __x86_64__ - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+16)(%esp), KEYP # ctx - movl (FRAME_OFFSET+20)(%esp), OUTP # dst - movl (FRAME_OFFSET+24)(%esp), INP # src - movl (FRAME_OFFSET+28)(%esp), LEN # len -#endif - test LEN, LEN - jz .Lecb_dec_ret - mov 480(KEYP), KLEN - add $240, KEYP - cmp $16, LEN - jb .Lecb_dec_ret - cmp $64, LEN - jb .Lecb_dec_loop1 -.align 4 -.Lecb_dec_loop4: - movups (INP), STATE1 - movups 0x10(INP), STATE2 - movups 0x20(INP), STATE3 - movups 0x30(INP), STATE4 - call _aesni_dec4 - movups STATE1, (OUTP) - movups STATE2, 0x10(OUTP) - movups STATE3, 0x20(OUTP) - movups STATE4, 0x30(OUTP) - sub $64, LEN - add $64, INP - add $64, OUTP - cmp $64, LEN - jge .Lecb_dec_loop4 - cmp $16, LEN - jb .Lecb_dec_ret -.align 4 -.Lecb_dec_loop1: - movups (INP), STATE1 - call _aesni_dec1 - movups STATE1, (OUTP) - sub $16, LEN - add $16, INP - add $16, OUTP - cmp $16, LEN - jge .Lecb_dec_loop1 -.Lecb_dec_ret: -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_ecb_dec) - /* * void aesni_cbc_enc(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, * size_t len, u8 *iv) diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index 392dcfac6c2894..c1656a5e61bcf4 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -61,10 +61,6 @@ static inline void *aes_align_addr(void *addr) asmlinkage void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, unsigned int key_len); asmlinkage void aesni_enc(const void *ctx, u8 *out, const u8 *in); -asmlinkage void aesni_ecb_enc(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len); -asmlinkage void aesni_ecb_dec(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len); asmlinkage void aesni_cbc_enc(struct crypto_aes_ctx *ctx, u8 *out, const u8 *in, unsigned int len, u8 *iv); asmlinkage void aesni_cbc_dec(struct crypto_aes_ctx *ctx, u8 *out, @@ -117,50 +113,6 @@ static int aesni_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, return aes_set_key_common(aes_ctx(crypto_skcipher_ctx(tfm)), key, len); } -static int ecb_encrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes)) { - kernel_fpu_begin(); - aesni_ecb_enc(ctx, walk.dst.virt.addr, walk.src.virt.addr, - nbytes & AES_BLOCK_MASK); - kernel_fpu_end(); - nbytes &= AES_BLOCK_SIZE - 1; - err = skcipher_walk_done(&walk, nbytes); - } - - return err; -} - -static int ecb_decrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes)) { - kernel_fpu_begin(); - aesni_ecb_dec(ctx, walk.dst.virt.addr, walk.src.virt.addr, - nbytes & AES_BLOCK_MASK); - kernel_fpu_end(); - nbytes &= AES_BLOCK_SIZE - 1; - err = skcipher_walk_done(&walk, nbytes); - } - - return err; -} - static int cbc_encrypt(struct skcipher_request *req) { struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); @@ -508,20 +460,6 @@ static int xts_decrypt_aesni(struct skcipher_request *req) static struct skcipher_alg aesni_skciphers[] = { { - .base = { - .cra_name = "ecb(aes)", - .cra_driver_name = "ecb-aes-aesni", - .cra_priority = 400, - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = CRYPTO_AES_CTX_SIZE, - .cra_module = THIS_MODULE, - }, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .setkey = aesni_skcipher_setkey, - .encrypt = ecb_encrypt, - .decrypt = ecb_decrypt, - }, { .base = { .cra_name = "cbc(aes)", .cra_driver_name = "cbc-aes-aesni", From 4d07bd7b2698127bce0e96ab8f86ef75a2a990c8 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:01 -0700 Subject: [PATCH 1127/2286] crypto: x86/aes-cbc - Remove superseded CBC skciphers Now that the AES-CBC (and AES-CBC-CTS) library functions are optimized with dedicated AES-NI code and exposed via the skcipher API via crypto/aes.c, the similar implementation in aesni-intel is redundant. Remove it. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-11-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 4 +- arch/x86/crypto/aesni-intel_asm.S | 255 ----------------------------- arch/x86/crypto/aesni-intel_glue.c | 195 ---------------------- 3 files changed, 2 insertions(+), 452 deletions(-) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index f7c4243dd1871e..79e610b5e5bc37 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -3,7 +3,7 @@ menu "Accelerated Cryptographic Algorithms for CPU (x86)" config CRYPTO_AES_NI_INTEL - tristate "Ciphers: AES, modes: CBC, CTS, CTR, XCTR, XTS, GCM (AES-NI/VAES)" + tristate "Ciphers: AES, modes: CTR, XCTR, XTS, GCM (AES-NI/VAES)" depends on 64BIT select CRYPTO_AEAD select CRYPTO_LIB_AES @@ -11,7 +11,7 @@ config CRYPTO_AES_NI_INTEL select CRYPTO_SKCIPHER help AEAD cipher: AES with GCM - Length-preserving ciphers: AES with CBC, CTS, CTR, XCTR, XTS + Length-preserving ciphers: AES with CTR, XCTR, XTS Architecture: x86_64 using: - AES-NI (AES new instructions) diff --git a/arch/x86/crypto/aesni-intel_asm.S b/arch/x86/crypto/aesni-intel_asm.S index db7f259ee8ab49..16c406781b2473 100644 --- a/arch/x86/crypto/aesni-intel_asm.S +++ b/arch/x86/crypto/aesni-intel_asm.S @@ -601,261 +601,6 @@ SYM_FUNC_START_LOCAL(_aesni_dec4) RET SYM_FUNC_END(_aesni_dec4) -/* - * void aesni_cbc_enc(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len, u8 *iv) - */ -SYM_FUNC_START(aesni_cbc_enc) - FRAME_BEGIN -#ifndef __x86_64__ - pushl IVP - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+20)(%esp), KEYP # ctx - movl (FRAME_OFFSET+24)(%esp), OUTP # dst - movl (FRAME_OFFSET+28)(%esp), INP # src - movl (FRAME_OFFSET+32)(%esp), LEN # len - movl (FRAME_OFFSET+36)(%esp), IVP # iv -#endif - cmp $16, LEN - jb .Lcbc_enc_ret - mov 480(KEYP), KLEN - movups (IVP), STATE # load iv as initial state -.align 4 -.Lcbc_enc_loop: - movups (INP), IN # load input - pxor IN, STATE - call _aesni_enc1 - movups STATE, (OUTP) # store output - sub $16, LEN - add $16, INP - add $16, OUTP - cmp $16, LEN - jge .Lcbc_enc_loop - movups STATE, (IVP) -.Lcbc_enc_ret: -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN - popl IVP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_cbc_enc) - -/* - * void aesni_cbc_dec(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len, u8 *iv) - */ -SYM_FUNC_START(aesni_cbc_dec) - FRAME_BEGIN -#ifndef __x86_64__ - pushl IVP - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+20)(%esp), KEYP # ctx - movl (FRAME_OFFSET+24)(%esp), OUTP # dst - movl (FRAME_OFFSET+28)(%esp), INP # src - movl (FRAME_OFFSET+32)(%esp), LEN # len - movl (FRAME_OFFSET+36)(%esp), IVP # iv -#endif - cmp $16, LEN - jb .Lcbc_dec_just_ret - mov 480(KEYP), KLEN - add $240, KEYP - movups (IVP), IV - cmp $64, LEN - jb .Lcbc_dec_loop1 -.align 4 -.Lcbc_dec_loop4: - movups (INP), IN1 - movaps IN1, STATE1 - movups 0x10(INP), IN2 - movaps IN2, STATE2 -#ifdef __x86_64__ - movups 0x20(INP), IN3 - movaps IN3, STATE3 - movups 0x30(INP), IN4 - movaps IN4, STATE4 -#else - movups 0x20(INP), IN1 - movaps IN1, STATE3 - movups 0x30(INP), IN2 - movaps IN2, STATE4 -#endif - call _aesni_dec4 - pxor IV, STATE1 -#ifdef __x86_64__ - pxor IN1, STATE2 - pxor IN2, STATE3 - pxor IN3, STATE4 - movaps IN4, IV -#else - pxor IN1, STATE4 - movaps IN2, IV - movups (INP), IN1 - pxor IN1, STATE2 - movups 0x10(INP), IN2 - pxor IN2, STATE3 -#endif - movups STATE1, (OUTP) - movups STATE2, 0x10(OUTP) - movups STATE3, 0x20(OUTP) - movups STATE4, 0x30(OUTP) - sub $64, LEN - add $64, INP - add $64, OUTP - cmp $64, LEN - jge .Lcbc_dec_loop4 - cmp $16, LEN - jb .Lcbc_dec_ret -.align 4 -.Lcbc_dec_loop1: - movups (INP), IN - movaps IN, STATE - call _aesni_dec1 - pxor IV, STATE - movups STATE, (OUTP) - movaps IN, IV - sub $16, LEN - add $16, INP - add $16, OUTP - cmp $16, LEN - jge .Lcbc_dec_loop1 -.Lcbc_dec_ret: - movups IV, (IVP) -.Lcbc_dec_just_ret: -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN - popl IVP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_cbc_dec) - -/* - * void aesni_cts_cbc_enc(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len, u8 *iv) - */ -SYM_FUNC_START(aesni_cts_cbc_enc) - FRAME_BEGIN -#ifndef __x86_64__ - pushl IVP - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+20)(%esp), KEYP # ctx - movl (FRAME_OFFSET+24)(%esp), OUTP # dst - movl (FRAME_OFFSET+28)(%esp), INP # src - movl (FRAME_OFFSET+32)(%esp), LEN # len - movl (FRAME_OFFSET+36)(%esp), IVP # iv - lea .Lcts_permute_table, T1 -#else - lea .Lcts_permute_table(%rip), T1 -#endif - mov 480(KEYP), KLEN - movups (IVP), STATE - sub $16, LEN - mov T1, IVP - add $32, IVP - add LEN, T1 - sub LEN, IVP - movups (T1), %xmm4 - movups (IVP), %xmm5 - - movups (INP), IN1 - add LEN, INP - movups (INP), IN2 - - pxor IN1, STATE - call _aesni_enc1 - - pshufb %xmm5, IN2 - pxor STATE, IN2 - pshufb %xmm4, STATE - add OUTP, LEN - movups STATE, (LEN) - - movaps IN2, STATE - call _aesni_enc1 - movups STATE, (OUTP) - -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN - popl IVP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_cts_cbc_enc) - -/* - * void aesni_cts_cbc_dec(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len, u8 *iv) - */ -SYM_FUNC_START(aesni_cts_cbc_dec) - FRAME_BEGIN -#ifndef __x86_64__ - pushl IVP - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+20)(%esp), KEYP # ctx - movl (FRAME_OFFSET+24)(%esp), OUTP # dst - movl (FRAME_OFFSET+28)(%esp), INP # src - movl (FRAME_OFFSET+32)(%esp), LEN # len - movl (FRAME_OFFSET+36)(%esp), IVP # iv - lea .Lcts_permute_table, T1 -#else - lea .Lcts_permute_table(%rip), T1 -#endif - mov 480(KEYP), KLEN - add $240, KEYP - movups (IVP), IV - sub $16, LEN - mov T1, IVP - add $32, IVP - add LEN, T1 - sub LEN, IVP - movups (T1), %xmm4 - - movups (INP), STATE - add LEN, INP - movups (INP), IN1 - - call _aesni_dec1 - movaps STATE, IN2 - pshufb %xmm4, STATE - pxor IN1, STATE - - add OUTP, LEN - movups STATE, (LEN) - - movups (IVP), %xmm0 - pshufb %xmm0, IN1 - pblendvb IN2, IN1 - movaps IN1, STATE - call _aesni_dec1 - - pxor IV, STATE - movups STATE, (OUTP) - -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN - popl IVP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_cts_cbc_dec) - .pushsection .rodata .align 16 .Lcts_permute_table: diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index c1656a5e61bcf4..50bfdf7a65014f 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -61,14 +61,6 @@ static inline void *aes_align_addr(void *addr) asmlinkage void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, unsigned int key_len); asmlinkage void aesni_enc(const void *ctx, u8 *out, const u8 *in); -asmlinkage void aesni_cbc_enc(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); -asmlinkage void aesni_cbc_dec(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); -asmlinkage void aesni_cts_cbc_enc(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); -asmlinkage void aesni_cts_cbc_dec(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); asmlinkage void aesni_xts_enc(const struct crypto_aes_ctx *ctx, u8 *out, const u8 *in, unsigned int len, u8 *iv); @@ -113,162 +105,6 @@ static int aesni_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, return aes_set_key_common(aes_ctx(crypto_skcipher_ctx(tfm)), key, len); } -static int cbc_encrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes)) { - kernel_fpu_begin(); - aesni_cbc_enc(ctx, walk.dst.virt.addr, walk.src.virt.addr, - nbytes & AES_BLOCK_MASK, walk.iv); - kernel_fpu_end(); - nbytes &= AES_BLOCK_SIZE - 1; - err = skcipher_walk_done(&walk, nbytes); - } - - return err; -} - -static int cbc_decrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes)) { - kernel_fpu_begin(); - aesni_cbc_dec(ctx, walk.dst.virt.addr, walk.src.virt.addr, - nbytes & AES_BLOCK_MASK, walk.iv); - kernel_fpu_end(); - nbytes &= AES_BLOCK_SIZE - 1; - err = skcipher_walk_done(&walk, nbytes); - } - - return err; -} - -static int cts_cbc_encrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - int cbc_blocks = DIV_ROUND_UP(req->cryptlen, AES_BLOCK_SIZE) - 2; - struct scatterlist *src = req->src, *dst = req->dst; - struct scatterlist sg_src[2], sg_dst[2]; - struct skcipher_request subreq; - struct skcipher_walk walk; - int err; - - skcipher_request_set_tfm(&subreq, tfm); - skcipher_request_set_callback(&subreq, skcipher_request_flags(req), - NULL, NULL); - - if (req->cryptlen <= AES_BLOCK_SIZE) { - if (req->cryptlen < AES_BLOCK_SIZE) - return -EINVAL; - cbc_blocks = 1; - } - - if (cbc_blocks > 0) { - skcipher_request_set_crypt(&subreq, req->src, req->dst, - cbc_blocks * AES_BLOCK_SIZE, - req->iv); - - err = cbc_encrypt(&subreq); - if (err) - return err; - - if (req->cryptlen == AES_BLOCK_SIZE) - return 0; - - dst = src = scatterwalk_ffwd(sg_src, req->src, subreq.cryptlen); - if (req->dst != req->src) - dst = scatterwalk_ffwd(sg_dst, req->dst, - subreq.cryptlen); - } - - /* handle ciphertext stealing */ - skcipher_request_set_crypt(&subreq, src, dst, - req->cryptlen - cbc_blocks * AES_BLOCK_SIZE, - req->iv); - - err = skcipher_walk_virt(&walk, &subreq, false); - if (err) - return err; - - kernel_fpu_begin(); - aesni_cts_cbc_enc(ctx, walk.dst.virt.addr, walk.src.virt.addr, - walk.nbytes, walk.iv); - kernel_fpu_end(); - - return skcipher_walk_done(&walk, 0); -} - -static int cts_cbc_decrypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - int cbc_blocks = DIV_ROUND_UP(req->cryptlen, AES_BLOCK_SIZE) - 2; - struct scatterlist *src = req->src, *dst = req->dst; - struct scatterlist sg_src[2], sg_dst[2]; - struct skcipher_request subreq; - struct skcipher_walk walk; - int err; - - skcipher_request_set_tfm(&subreq, tfm); - skcipher_request_set_callback(&subreq, skcipher_request_flags(req), - NULL, NULL); - - if (req->cryptlen <= AES_BLOCK_SIZE) { - if (req->cryptlen < AES_BLOCK_SIZE) - return -EINVAL; - cbc_blocks = 1; - } - - if (cbc_blocks > 0) { - skcipher_request_set_crypt(&subreq, req->src, req->dst, - cbc_blocks * AES_BLOCK_SIZE, - req->iv); - - err = cbc_decrypt(&subreq); - if (err) - return err; - - if (req->cryptlen == AES_BLOCK_SIZE) - return 0; - - dst = src = scatterwalk_ffwd(sg_src, req->src, subreq.cryptlen); - if (req->dst != req->src) - dst = scatterwalk_ffwd(sg_dst, req->dst, - subreq.cryptlen); - } - - /* handle ciphertext stealing */ - skcipher_request_set_crypt(&subreq, src, dst, - req->cryptlen - cbc_blocks * AES_BLOCK_SIZE, - req->iv); - - err = skcipher_walk_virt(&walk, &subreq, false); - if (err) - return err; - - kernel_fpu_begin(); - aesni_cts_cbc_dec(ctx, walk.dst.virt.addr, walk.src.virt.addr, - walk.nbytes, walk.iv); - kernel_fpu_end(); - - return skcipher_walk_done(&walk, 0); -} - /* This is the non-AVX version. */ static int ctr_crypt_aesni(struct skcipher_request *req) { @@ -460,37 +296,6 @@ static int xts_decrypt_aesni(struct skcipher_request *req) static struct skcipher_alg aesni_skciphers[] = { { - .base = { - .cra_name = "cbc(aes)", - .cra_driver_name = "cbc-aes-aesni", - .cra_priority = 400, - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = CRYPTO_AES_CTX_SIZE, - .cra_module = THIS_MODULE, - }, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .setkey = aesni_skcipher_setkey, - .encrypt = cbc_encrypt, - .decrypt = cbc_decrypt, - }, { - .base = { - .cra_name = "cts(cbc(aes))", - .cra_driver_name = "cts-cbc-aes-aesni", - .cra_priority = 400, - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = CRYPTO_AES_CTX_SIZE, - .cra_module = THIS_MODULE, - }, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .walksize = 2 * AES_BLOCK_SIZE, - .setkey = aesni_skcipher_setkey, - .encrypt = cts_cbc_encrypt, - .decrypt = cts_cbc_decrypt, - }, { .base = { .cra_name = "ctr(aes)", .cra_driver_name = "ctr-aes-aesni", From 35dc1b81c5c5c099cbd347e6ff7dd8be2ee5facc Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:02 -0700 Subject: [PATCH 1128/2286] crypto: x86/aes-ctr - Remove superseded CTR skcipher Now that the crypto library's AES-CTR support is optimized with AES-NI (without AVX), the similar code in aesni-intel is redundant. Remove it. This only affects the non-AVX implementation ("ctr-aes-aesni"), not aes-ctr-avx-x86_64.S which is handled later. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-12-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/aesni-intel_asm.S | 125 ----------------------------- arch/x86/crypto/aesni-intel_glue.c | 54 ------------- 2 files changed, 179 deletions(-) diff --git a/arch/x86/crypto/aesni-intel_asm.S b/arch/x86/crypto/aesni-intel_asm.S index 16c406781b2473..c4d54c4a2c2314 100644 --- a/arch/x86/crypto/aesni-intel_asm.S +++ b/arch/x86/crypto/aesni-intel_asm.S @@ -33,10 +33,6 @@ #define KEY %xmm2 #define IV %xmm3 -#define BSWAP_MASK %xmm10 -#define CTR %xmm11 -#define INC %xmm12 - #define GF128MUL_MASK %xmm7 #ifdef __x86_64__ @@ -51,7 +47,6 @@ #define T1 %r10 #define TKEYP T1 #define T2 %r11 -#define TCTR_LOW T2 #else #define AREG %eax #define KEYP %edi @@ -610,128 +605,8 @@ SYM_FUNC_END(_aesni_dec4) .byte 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 -#ifdef __x86_64__ -.Lbswap_mask: - .byte 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 -#endif .popsection -#ifdef __x86_64__ -/* - * _aesni_inc_init: internal ABI - * setup registers used by _aesni_inc - * input: - * IV - * output: - * CTR: == IV, in little endian - * TCTR_LOW: == lower qword of CTR - * INC: == 1, in little endian - * BSWAP_MASK == endian swapping mask - */ -SYM_FUNC_START_LOCAL(_aesni_inc_init) - movaps .Lbswap_mask(%rip), BSWAP_MASK - movaps IV, CTR - pshufb BSWAP_MASK, CTR - mov $1, TCTR_LOW - movq TCTR_LOW, INC - movq CTR, TCTR_LOW - RET -SYM_FUNC_END(_aesni_inc_init) - -/* - * _aesni_inc: internal ABI - * Increase IV by 1, IV is in big endian - * input: - * IV - * CTR: == IV, in little endian - * TCTR_LOW: == lower qword of CTR - * INC: == 1, in little endian - * BSWAP_MASK == endian swapping mask - * output: - * IV: Increase by 1 - * changed: - * CTR: == output IV, in little endian - * TCTR_LOW: == lower qword of CTR - */ -SYM_FUNC_START_LOCAL(_aesni_inc) - paddq INC, CTR - add $1, TCTR_LOW - jnc .Linc_low - pslldq $8, INC - paddq INC, CTR - psrldq $8, INC -.Linc_low: - movaps CTR, IV - pshufb BSWAP_MASK, IV - RET -SYM_FUNC_END(_aesni_inc) - -/* - * void aesni_ctr_enc(struct crypto_aes_ctx *ctx, const u8 *dst, u8 *src, - * size_t len, u8 *iv) - */ -SYM_FUNC_START(aesni_ctr_enc) - ANNOTATE_NOENDBR - FRAME_BEGIN - cmp $16, LEN - jb .Lctr_enc_just_ret - mov 480(KEYP), KLEN - movups (IVP), IV - call _aesni_inc_init - cmp $64, LEN - jb .Lctr_enc_loop1 -.align 4 -.Lctr_enc_loop4: - movaps IV, STATE1 - call _aesni_inc - movups (INP), IN1 - movaps IV, STATE2 - call _aesni_inc - movups 0x10(INP), IN2 - movaps IV, STATE3 - call _aesni_inc - movups 0x20(INP), IN3 - movaps IV, STATE4 - call _aesni_inc - movups 0x30(INP), IN4 - call _aesni_enc4 - pxor IN1, STATE1 - movups STATE1, (OUTP) - pxor IN2, STATE2 - movups STATE2, 0x10(OUTP) - pxor IN3, STATE3 - movups STATE3, 0x20(OUTP) - pxor IN4, STATE4 - movups STATE4, 0x30(OUTP) - sub $64, LEN - add $64, INP - add $64, OUTP - cmp $64, LEN - jge .Lctr_enc_loop4 - cmp $16, LEN - jb .Lctr_enc_ret -.align 4 -.Lctr_enc_loop1: - movaps IV, STATE - call _aesni_inc - movups (INP), IN - call _aesni_enc1 - pxor IN, STATE - movups STATE, (OUTP) - sub $16, LEN - add $16, INP - add $16, OUTP - cmp $16, LEN - jge .Lctr_enc_loop1 -.Lctr_enc_ret: - movups IV, (IVP) -.Lctr_enc_just_ret: - FRAME_END - RET -SYM_FUNC_END(aesni_ctr_enc) - -#endif - .section .rodata.cst16.gf128mul_x_ble_mask, "aM", @progbits, 16 .align 16 .Lgf128mul_x_ble_mask: diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index 50bfdf7a65014f..797ff075d1e90e 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -41,7 +41,6 @@ #define AESNI_ALIGN 16 #define AESNI_ALIGN_ATTR __attribute__ ((__aligned__(AESNI_ALIGN))) -#define AES_BLOCK_MASK (~(AES_BLOCK_SIZE - 1)) #define AESNI_ALIGN_EXTRA ((AESNI_ALIGN - 1) & ~(CRYPTO_MINALIGN - 1)) #define CRYPTO_AES_CTX_SIZE (sizeof(struct crypto_aes_ctx) + AESNI_ALIGN_EXTRA) #define XTS_AES_CTX_SIZE (sizeof(struct aesni_xts_ctx) + AESNI_ALIGN_EXTRA) @@ -68,9 +67,6 @@ asmlinkage void aesni_xts_enc(const struct crypto_aes_ctx *ctx, u8 *out, asmlinkage void aesni_xts_dec(const struct crypto_aes_ctx *ctx, u8 *out, const u8 *in, unsigned int len, u8 *iv); -asmlinkage void aesni_ctr_enc(struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); - static inline struct crypto_aes_ctx *aes_ctx(void *raw_ctx) { return aes_align_addr(raw_ctx); @@ -105,40 +101,6 @@ static int aesni_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, return aes_set_key_common(aes_ctx(crypto_skcipher_ctx(tfm)), key, len); } -/* This is the non-AVX version. */ -static int ctr_crypt_aesni(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm)); - u8 keystream[AES_BLOCK_SIZE]; - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes) > 0) { - kernel_fpu_begin(); - if (nbytes & AES_BLOCK_MASK) - aesni_ctr_enc(ctx, walk.dst.virt.addr, - walk.src.virt.addr, - nbytes & AES_BLOCK_MASK, walk.iv); - nbytes &= ~AES_BLOCK_MASK; - - if (walk.nbytes == walk.total && nbytes > 0) { - aesni_enc(ctx, keystream, walk.iv); - crypto_xor_cpy(walk.dst.virt.addr + walk.nbytes - nbytes, - walk.src.virt.addr + walk.nbytes - nbytes, - keystream, nbytes); - crypto_inc(walk.iv, AES_BLOCK_SIZE); - nbytes = 0; - } - kernel_fpu_end(); - err = skcipher_walk_done(&walk, nbytes); - } - return err; -} - static int xts_setkey_aesni(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen) { @@ -296,22 +258,6 @@ static int xts_decrypt_aesni(struct skcipher_request *req) static struct skcipher_alg aesni_skciphers[] = { { - .base = { - .cra_name = "ctr(aes)", - .cra_driver_name = "ctr-aes-aesni", - .cra_priority = 400, - .cra_blocksize = 1, - .cra_ctxsize = CRYPTO_AES_CTX_SIZE, - .cra_module = THIS_MODULE, - }, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .chunksize = AES_BLOCK_SIZE, - .setkey = aesni_skcipher_setkey, - .encrypt = ctr_crypt_aesni, - .decrypt = ctr_crypt_aesni, - }, { .base = { .cra_name = "xts(aes)", .cra_driver_name = "xts-aes-aesni", From 4ce884395f42fceac5cb5380d7e4a53c956103a9 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:03 -0700 Subject: [PATCH 1129/2286] crypto: x86/aes-xts - Remove superseded XTS skcipher Now that the crypto library's AES-XTS support is optimized with AES-NI (without AVX), the similar code in aesni-intel is redundant. Remove it. This only affects the non-AVX implementation ("xts-aes-aesni"), not aes-xts-avx-x86_64.S which is handled later. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-13-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/aesni-intel_asm.S | 617 ----------------------------- arch/x86/crypto/aesni-intel_glue.c | 69 +--- 2 files changed, 1 insertion(+), 685 deletions(-) diff --git a/arch/x86/crypto/aesni-intel_asm.S b/arch/x86/crypto/aesni-intel_asm.S index c4d54c4a2c2314..b12a0f2bf00692 100644 --- a/arch/x86/crypto/aesni-intel_asm.S +++ b/arch/x86/crypto/aesni-intel_asm.S @@ -20,30 +20,11 @@ #include #include -#define STATE1 %xmm0 -#define STATE2 %xmm4 -#define STATE3 %xmm5 -#define STATE4 %xmm6 -#define STATE STATE1 -#define IN1 %xmm1 -#define IN2 %xmm7 -#define IN3 %xmm8 -#define IN4 %xmm9 -#define IN IN1 -#define KEY %xmm2 -#define IV %xmm3 - -#define GF128MUL_MASK %xmm7 - #ifdef __x86_64__ #define AREG %rax #define KEYP %rdi #define OUTP %rsi #define UKEYP OUTP -#define INP %rdx -#define LEN %rcx -#define IVP %r8 -#define KLEN %r9d #define T1 %r10 #define TKEYP T1 #define T2 %r11 @@ -52,10 +33,6 @@ #define KEYP %edi #define OUTP AREG #define UKEYP OUTP -#define INP %edx -#define LEN %esi -#define IVP %ebp -#define KLEN %ebx #define T1 %ecx #define TKEYP T1 #endif @@ -241,597 +218,3 @@ SYM_FUNC_START(aesni_set_key) FRAME_END RET SYM_FUNC_END(aesni_set_key) - -/* - * void aesni_enc(const void *ctx, u8 *dst, const u8 *src) - */ -SYM_FUNC_START(aesni_enc) - FRAME_BEGIN -#ifndef __x86_64__ - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+12)(%esp), KEYP # ctx - movl (FRAME_OFFSET+16)(%esp), OUTP # dst - movl (FRAME_OFFSET+20)(%esp), INP # src -#endif - movl 480(KEYP), KLEN # key length - movups (INP), STATE # input - call _aesni_enc1 - movups STATE, (OUTP) # output -#ifndef __x86_64__ - popl KLEN - popl KEYP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_enc) - -/* - * _aesni_enc1: internal ABI - * input: - * KEYP: key struct pointer - * KLEN: round count - * STATE: initial state (input) - * output: - * STATE: finial state (output) - * changed: - * KEY - * TKEYP (T1) - */ -SYM_FUNC_START_LOCAL(_aesni_enc1) - movaps (KEYP), KEY # key - mov KEYP, TKEYP - pxor KEY, STATE # round 0 - add $0x30, TKEYP - cmp $24, KLEN - jb .Lenc128 - lea 0x20(TKEYP), TKEYP - je .Lenc192 - add $0x20, TKEYP - movaps -0x60(TKEYP), KEY - aesenc KEY, STATE - movaps -0x50(TKEYP), KEY - aesenc KEY, STATE -.align 4 -.Lenc192: - movaps -0x40(TKEYP), KEY - aesenc KEY, STATE - movaps -0x30(TKEYP), KEY - aesenc KEY, STATE -.align 4 -.Lenc128: - movaps -0x20(TKEYP), KEY - aesenc KEY, STATE - movaps -0x10(TKEYP), KEY - aesenc KEY, STATE - movaps (TKEYP), KEY - aesenc KEY, STATE - movaps 0x10(TKEYP), KEY - aesenc KEY, STATE - movaps 0x20(TKEYP), KEY - aesenc KEY, STATE - movaps 0x30(TKEYP), KEY - aesenc KEY, STATE - movaps 0x40(TKEYP), KEY - aesenc KEY, STATE - movaps 0x50(TKEYP), KEY - aesenc KEY, STATE - movaps 0x60(TKEYP), KEY - aesenc KEY, STATE - movaps 0x70(TKEYP), KEY - aesenclast KEY, STATE - RET -SYM_FUNC_END(_aesni_enc1) - -/* - * _aesni_enc4: internal ABI - * input: - * KEYP: key struct pointer - * KLEN: round count - * STATE1: initial state (input) - * STATE2 - * STATE3 - * STATE4 - * output: - * STATE1: finial state (output) - * STATE2 - * STATE3 - * STATE4 - * changed: - * KEY - * TKEYP (T1) - */ -SYM_FUNC_START_LOCAL(_aesni_enc4) - movaps (KEYP), KEY # key - mov KEYP, TKEYP - pxor KEY, STATE1 # round 0 - pxor KEY, STATE2 - pxor KEY, STATE3 - pxor KEY, STATE4 - add $0x30, TKEYP - cmp $24, KLEN - jb .L4enc128 - lea 0x20(TKEYP), TKEYP - je .L4enc192 - add $0x20, TKEYP - movaps -0x60(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps -0x50(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 -#.align 4 -.L4enc192: - movaps -0x40(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps -0x30(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 -#.align 4 -.L4enc128: - movaps -0x20(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps -0x10(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps (TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x10(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x20(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x30(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x40(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x50(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x60(TKEYP), KEY - aesenc KEY, STATE1 - aesenc KEY, STATE2 - aesenc KEY, STATE3 - aesenc KEY, STATE4 - movaps 0x70(TKEYP), KEY - aesenclast KEY, STATE1 # last round - aesenclast KEY, STATE2 - aesenclast KEY, STATE3 - aesenclast KEY, STATE4 - RET -SYM_FUNC_END(_aesni_enc4) - -/* - * _aesni_dec1: internal ABI - * input: - * KEYP: key struct pointer - * KLEN: key length - * STATE: initial state (input) - * output: - * STATE: finial state (output) - * changed: - * KEY - * TKEYP (T1) - */ -SYM_FUNC_START_LOCAL(_aesni_dec1) - movaps (KEYP), KEY # key - mov KEYP, TKEYP - pxor KEY, STATE # round 0 - add $0x30, TKEYP - cmp $24, KLEN - jb .Ldec128 - lea 0x20(TKEYP), TKEYP - je .Ldec192 - add $0x20, TKEYP - movaps -0x60(TKEYP), KEY - aesdec KEY, STATE - movaps -0x50(TKEYP), KEY - aesdec KEY, STATE -.align 4 -.Ldec192: - movaps -0x40(TKEYP), KEY - aesdec KEY, STATE - movaps -0x30(TKEYP), KEY - aesdec KEY, STATE -.align 4 -.Ldec128: - movaps -0x20(TKEYP), KEY - aesdec KEY, STATE - movaps -0x10(TKEYP), KEY - aesdec KEY, STATE - movaps (TKEYP), KEY - aesdec KEY, STATE - movaps 0x10(TKEYP), KEY - aesdec KEY, STATE - movaps 0x20(TKEYP), KEY - aesdec KEY, STATE - movaps 0x30(TKEYP), KEY - aesdec KEY, STATE - movaps 0x40(TKEYP), KEY - aesdec KEY, STATE - movaps 0x50(TKEYP), KEY - aesdec KEY, STATE - movaps 0x60(TKEYP), KEY - aesdec KEY, STATE - movaps 0x70(TKEYP), KEY - aesdeclast KEY, STATE - RET -SYM_FUNC_END(_aesni_dec1) - -/* - * _aesni_dec4: internal ABI - * input: - * KEYP: key struct pointer - * KLEN: key length - * STATE1: initial state (input) - * STATE2 - * STATE3 - * STATE4 - * output: - * STATE1: finial state (output) - * STATE2 - * STATE3 - * STATE4 - * changed: - * KEY - * TKEYP (T1) - */ -SYM_FUNC_START_LOCAL(_aesni_dec4) - movaps (KEYP), KEY # key - mov KEYP, TKEYP - pxor KEY, STATE1 # round 0 - pxor KEY, STATE2 - pxor KEY, STATE3 - pxor KEY, STATE4 - add $0x30, TKEYP - cmp $24, KLEN - jb .L4dec128 - lea 0x20(TKEYP), TKEYP - je .L4dec192 - add $0x20, TKEYP - movaps -0x60(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps -0x50(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 -.align 4 -.L4dec192: - movaps -0x40(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps -0x30(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 -.align 4 -.L4dec128: - movaps -0x20(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps -0x10(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps (TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x10(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x20(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x30(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x40(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x50(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x60(TKEYP), KEY - aesdec KEY, STATE1 - aesdec KEY, STATE2 - aesdec KEY, STATE3 - aesdec KEY, STATE4 - movaps 0x70(TKEYP), KEY - aesdeclast KEY, STATE1 # last round - aesdeclast KEY, STATE2 - aesdeclast KEY, STATE3 - aesdeclast KEY, STATE4 - RET -SYM_FUNC_END(_aesni_dec4) - -.pushsection .rodata -.align 16 -.Lcts_permute_table: - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 - .byte 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 -.popsection - -.section .rodata.cst16.gf128mul_x_ble_mask, "aM", @progbits, 16 -.align 16 -.Lgf128mul_x_ble_mask: - .octa 0x00000000000000010000000000000087 -.previous - -/* - * _aesni_gf128mul_x_ble: Multiply in GF(2^128) for XTS IVs - * input: - * IV: current IV - * GF128MUL_MASK == mask with 0x87 and 0x01 - * output: - * IV: next IV - * changed: - * KEY: == temporary value - */ -.macro _aesni_gf128mul_x_ble - pshufd $0x13, IV, KEY - paddq IV, IV - psrad $31, KEY - pand GF128MUL_MASK, KEY - pxor KEY, IV -.endm - -.macro _aesni_xts_crypt enc - FRAME_BEGIN -#ifndef __x86_64__ - pushl IVP - pushl LEN - pushl KEYP - pushl KLEN - movl (FRAME_OFFSET+20)(%esp), KEYP # ctx - movl (FRAME_OFFSET+24)(%esp), OUTP # dst - movl (FRAME_OFFSET+28)(%esp), INP # src - movl (FRAME_OFFSET+32)(%esp), LEN # len - movl (FRAME_OFFSET+36)(%esp), IVP # iv - movdqa .Lgf128mul_x_ble_mask, GF128MUL_MASK -#else - movdqa .Lgf128mul_x_ble_mask(%rip), GF128MUL_MASK -#endif - movups (IVP), IV - - mov 480(KEYP), KLEN -.if !\enc - add $240, KEYP - - test $15, LEN - jz .Lxts_loop4\@ - sub $16, LEN -.endif - -.Lxts_loop4\@: - sub $64, LEN - jl .Lxts_1x\@ - - movdqa IV, STATE1 - movdqu 0x00(INP), IN - pxor IN, STATE1 - movdqu IV, 0x00(OUTP) - - _aesni_gf128mul_x_ble - movdqa IV, STATE2 - movdqu 0x10(INP), IN - pxor IN, STATE2 - movdqu IV, 0x10(OUTP) - - _aesni_gf128mul_x_ble - movdqa IV, STATE3 - movdqu 0x20(INP), IN - pxor IN, STATE3 - movdqu IV, 0x20(OUTP) - - _aesni_gf128mul_x_ble - movdqa IV, STATE4 - movdqu 0x30(INP), IN - pxor IN, STATE4 - movdqu IV, 0x30(OUTP) - -.if \enc - call _aesni_enc4 -.else - call _aesni_dec4 -.endif - - movdqu 0x00(OUTP), IN - pxor IN, STATE1 - movdqu STATE1, 0x00(OUTP) - - movdqu 0x10(OUTP), IN - pxor IN, STATE2 - movdqu STATE2, 0x10(OUTP) - - movdqu 0x20(OUTP), IN - pxor IN, STATE3 - movdqu STATE3, 0x20(OUTP) - - movdqu 0x30(OUTP), IN - pxor IN, STATE4 - movdqu STATE4, 0x30(OUTP) - - _aesni_gf128mul_x_ble - - add $64, INP - add $64, OUTP - test LEN, LEN - jnz .Lxts_loop4\@ - -.Lxts_ret_iv\@: - movups IV, (IVP) - -.Lxts_ret\@: -#ifndef __x86_64__ - popl KLEN - popl KEYP - popl LEN - popl IVP -#endif - FRAME_END - RET - -.Lxts_1x\@: - add $64, LEN - jz .Lxts_ret_iv\@ -.if \enc - sub $16, LEN - jl .Lxts_cts4\@ -.endif - -.Lxts_loop1\@: - movdqu (INP), STATE -.if \enc - pxor IV, STATE - call _aesni_enc1 -.else - add $16, INP - sub $16, LEN - jl .Lxts_cts1\@ - pxor IV, STATE - call _aesni_dec1 -.endif - pxor IV, STATE - _aesni_gf128mul_x_ble - - test LEN, LEN - jz .Lxts_out\@ - -.if \enc - add $16, INP - sub $16, LEN - jl .Lxts_cts1\@ -.endif - - movdqu STATE, (OUTP) - add $16, OUTP - jmp .Lxts_loop1\@ - -.Lxts_out\@: - movdqu STATE, (OUTP) - jmp .Lxts_ret_iv\@ - -.if \enc -.Lxts_cts4\@: - movdqa STATE4, STATE - sub $16, OUTP -.Lxts_cts1\@: -.else -.Lxts_cts1\@: - movdqa IV, STATE4 - _aesni_gf128mul_x_ble - - pxor IV, STATE - call _aesni_dec1 - pxor IV, STATE -.endif -#ifndef __x86_64__ - lea .Lcts_permute_table, T1 -#else - lea .Lcts_permute_table(%rip), T1 -#endif - add LEN, INP /* rewind input pointer */ - add $16, LEN /* # bytes in final block */ - movups (INP), IN1 - - mov T1, IVP - add $32, IVP - add LEN, T1 - sub LEN, IVP - add OUTP, LEN - - movups (T1), %xmm4 - movaps STATE, IN2 - pshufb %xmm4, STATE - movups STATE, (LEN) - - movups (IVP), %xmm0 - pshufb %xmm0, IN1 - pblendvb IN2, IN1 - movaps IN1, STATE - -.if \enc - pxor IV, STATE - call _aesni_enc1 - pxor IV, STATE -.else - pxor STATE4, STATE - call _aesni_dec1 - pxor STATE4, STATE -.endif - - movups STATE, (OUTP) - jmp .Lxts_ret\@ -.endm - -/* - * void aesni_xts_enc(const struct crypto_aes_ctx *ctx, u8 *dst, - * const u8 *src, unsigned int len, le128 *iv) - */ -SYM_FUNC_START(aesni_xts_enc) - _aesni_xts_crypt 1 -SYM_FUNC_END(aesni_xts_enc) - -/* - * void aesni_xts_dec(const struct crypto_aes_ctx *ctx, u8 *dst, - * const u8 *src, unsigned int len, le128 *iv) - */ -SYM_FUNC_START(aesni_xts_dec) - _aesni_xts_crypt 0 -SYM_FUNC_END(aesni_xts_dec) diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index 797ff075d1e90e..a2d955336acdf0 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -59,13 +59,6 @@ static inline void *aes_align_addr(void *addr) asmlinkage void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, unsigned int key_len); -asmlinkage void aesni_enc(const void *ctx, u8 *out, const u8 *in); - -asmlinkage void aesni_xts_enc(const struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); - -asmlinkage void aesni_xts_dec(const struct crypto_aes_ctx *ctx, u8 *out, - const u8 *in, unsigned int len, u8 *iv); static inline struct crypto_aes_ctx *aes_ctx(void *raw_ctx) { @@ -226,56 +219,6 @@ xts_crypt(struct skcipher_request *req, xts_encrypt_iv_func encrypt_iv, return xts_crypt_slowpath(req, crypt_func); } -static void aesni_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, - u8 iv[AES_BLOCK_SIZE]) -{ - aesni_enc(tweak_key, iv, iv); -} - -static void aesni_xts_encrypt(const struct crypto_aes_ctx *key, - const u8 *src, u8 *dst, int len, - u8 tweak[AES_BLOCK_SIZE]) -{ - aesni_xts_enc(key, dst, src, len, tweak); -} - -static void aesni_xts_decrypt(const struct crypto_aes_ctx *key, - const u8 *src, u8 *dst, int len, - u8 tweak[AES_BLOCK_SIZE]) -{ - aesni_xts_dec(key, dst, src, len, tweak); -} - -static int xts_encrypt_aesni(struct skcipher_request *req) -{ - return xts_crypt(req, aesni_xts_encrypt_iv, aesni_xts_encrypt); -} - -static int xts_decrypt_aesni(struct skcipher_request *req) -{ - return xts_crypt(req, aesni_xts_encrypt_iv, aesni_xts_decrypt); -} - -static struct skcipher_alg aesni_skciphers[] = { - { - .base = { - .cra_name = "xts(aes)", - .cra_driver_name = "xts-aes-aesni", - .cra_priority = 401, - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = XTS_AES_CTX_SIZE, - .cra_module = THIS_MODULE, - }, - .min_keysize = 2 * AES_MIN_KEY_SIZE, - .max_keysize = 2 * AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .walksize = 2 * AES_BLOCK_SIZE, - .setkey = xts_setkey_aesni, - .encrypt = xts_encrypt_aesni, - .decrypt = xts_decrypt_aesni, - } -}; - asmlinkage void aes_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, u8 iv[AES_BLOCK_SIZE]); @@ -1299,15 +1242,10 @@ static int __init aesni_init(void) if (!x86_match_cpu(aesni_cpu_id)) return -ENODEV; - err = crypto_register_skciphers(aesni_skciphers, - ARRAY_SIZE(aesni_skciphers)); - if (err) - return err; - err = crypto_register_aeads(aes_gcm_algs_aesni, ARRAY_SIZE(aes_gcm_algs_aesni)); if (err) - goto unregister_skciphers; + return err; err = register_avx_algs(); if (err) @@ -1319,9 +1257,6 @@ static int __init aesni_init(void) unregister_avx_algs(); crypto_unregister_aeads(aes_gcm_algs_aesni, ARRAY_SIZE(aes_gcm_algs_aesni)); -unregister_skciphers: - crypto_unregister_skciphers(aesni_skciphers, - ARRAY_SIZE(aesni_skciphers)); return err; } @@ -1329,8 +1264,6 @@ static void __exit aesni_exit(void) { crypto_unregister_aeads(aes_gcm_algs_aesni, ARRAY_SIZE(aes_gcm_algs_aesni)); - crypto_unregister_skciphers(aesni_skciphers, - ARRAY_SIZE(aesni_skciphers)); unregister_avx_algs(); } From 7e58d75b7cc73faf73e6793940f591257caeae68 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:04 -0700 Subject: [PATCH 1130/2286] lib/crypto: x86/aes-ctr: Migrate AVX-optimized code into library Migrate aes-ctr-avx-x86_64.S into lib/crypto/, wiring it up to the CTR and XCTR library functions instead of the crypto_skcipher API. It still remains available through crypto_skcipher via crypto/aes.c. Some slight adjustments to the assembly code were needed: - Take 'struct aes_enckey' instead of 'struct crypto_aes_ctx'. - Upgrade the length argument from 32-bit to 64-bit so that it's compatible with the library's use of size_t (at least assuming no lengths over S64_MAX, which seems quite safe to assume...) - Remove the CFI stubs, as the functions are now called directly. - Adjust the argument order to match the caller. Not strictly required, but it's easiest to handle this now when changing the function prototypes anyway and adding the new glue code. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-14-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 4 +- arch/x86/crypto/Makefile | 1 - arch/x86/crypto/aesni-intel_glue.c | 153 ------------------ crypto/aes.c | 7 +- lib/crypto/Makefile | 4 + .../crypto/x86}/aes-ctr-avx-x86_64.S | 103 ++++++------ lib/crypto/x86/aes.h | 80 ++++++++- 7 files changed, 140 insertions(+), 212 deletions(-) rename {arch/x86/crypto => lib/crypto/x86}/aes-ctr-avx-x86_64.S (88%) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index 79e610b5e5bc37..778ab164c571c7 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -3,7 +3,7 @@ menu "Accelerated Cryptographic Algorithms for CPU (x86)" config CRYPTO_AES_NI_INTEL - tristate "Ciphers: AES, modes: CTR, XCTR, XTS, GCM (AES-NI/VAES)" + tristate "Ciphers: AES, modes: XTS, GCM (AES-NI/VAES)" depends on 64BIT select CRYPTO_AEAD select CRYPTO_LIB_AES @@ -11,7 +11,7 @@ config CRYPTO_AES_NI_INTEL select CRYPTO_SKCIPHER help AEAD cipher: AES with GCM - Length-preserving ciphers: AES with CTR, XCTR, XTS + Length-preserving ciphers: AES with XTS Architecture: x86_64 using: - AES-NI (AES new instructions) diff --git a/arch/x86/crypto/Makefile b/arch/x86/crypto/Makefile index 1c3feb6d72b728..0c016ba87373e1 100644 --- a/arch/x86/crypto/Makefile +++ b/arch/x86/crypto/Makefile @@ -42,7 +42,6 @@ aegis128-aesni-y := aegis128-aesni-asm.o aegis128-aesni-glue.o obj-$(CONFIG_CRYPTO_AES_NI_INTEL) += aesni-intel.o aesni-intel-y := aesni-intel_asm.o \ aesni-intel_glue.o \ - aes-ctr-avx-x86_64.o \ aes-gcm-aesni-x86_64.o \ aes-gcm-vaes-avx2.o \ aes-gcm-vaes-avx512.o \ diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index a2d955336acdf0..3787b932f101c1 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -42,7 +42,6 @@ #define AESNI_ALIGN 16 #define AESNI_ALIGN_ATTR __attribute__ ((__aligned__(AESNI_ALIGN))) #define AESNI_ALIGN_EXTRA ((AESNI_ALIGN - 1) & ~(CRYPTO_MINALIGN - 1)) -#define CRYPTO_AES_CTX_SIZE (sizeof(struct crypto_aes_ctx) + AESNI_ALIGN_EXTRA) #define XTS_AES_CTX_SIZE (sizeof(struct aesni_xts_ctx) + AESNI_ALIGN_EXTRA) struct aesni_xts_ctx { @@ -60,11 +59,6 @@ static inline void *aes_align_addr(void *addr) asmlinkage void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, unsigned int key_len); -static inline struct crypto_aes_ctx *aes_ctx(void *raw_ctx) -{ - return aes_align_addr(raw_ctx); -} - static inline struct aesni_xts_ctx *aes_xts_ctx(struct crypto_skcipher *tfm) { return aes_align_addr(crypto_skcipher_ctx(tfm)); @@ -88,12 +82,6 @@ static int aes_set_key_common(struct crypto_aes_ctx *ctx, return 0; } -static int aesni_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, - unsigned int len) -{ - return aes_set_key_common(aes_ctx(crypto_skcipher_ctx(tfm)), key, len); -} - static int xts_setkey_aesni(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen) { @@ -222,100 +210,6 @@ xts_crypt(struct skcipher_request *req, xts_encrypt_iv_func encrypt_iv, asmlinkage void aes_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, u8 iv[AES_BLOCK_SIZE]); -/* __always_inline to avoid indirect call */ -static __always_inline int -ctr_crypt(struct skcipher_request *req, - void (*ctr64_func)(const struct crypto_aes_ctx *key, - const u8 *src, u8 *dst, int len, - const u64 le_ctr[2])) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *key = aes_ctx(crypto_skcipher_ctx(tfm)); - unsigned int nbytes, p1_nbytes, nblocks; - struct skcipher_walk walk; - u64 le_ctr[2]; - u64 ctr64; - int err; - - ctr64 = le_ctr[0] = get_unaligned_be64(&req->iv[8]); - le_ctr[1] = get_unaligned_be64(&req->iv[0]); - - err = skcipher_walk_virt(&walk, req, false); - - while ((nbytes = walk.nbytes) != 0) { - if (nbytes < walk.total) { - /* Not the end yet, so keep the length block-aligned. */ - nbytes = round_down(nbytes, AES_BLOCK_SIZE); - nblocks = nbytes / AES_BLOCK_SIZE; - } else { - /* It's the end, so include any final partial block. */ - nblocks = DIV_ROUND_UP(nbytes, AES_BLOCK_SIZE); - } - ctr64 += nblocks; - - kernel_fpu_begin(); - if (likely(ctr64 >= nblocks)) { - /* The low 64 bits of the counter won't overflow. */ - (*ctr64_func)(key, walk.src.virt.addr, - walk.dst.virt.addr, nbytes, le_ctr); - } else { - /* - * The low 64 bits of the counter will overflow. The - * assembly doesn't handle this case, so split the - * operation into two at the point where the overflow - * will occur. After the first part, add the carry bit. - */ - p1_nbytes = min(nbytes, (nblocks - ctr64) * AES_BLOCK_SIZE); - (*ctr64_func)(key, walk.src.virt.addr, - walk.dst.virt.addr, p1_nbytes, le_ctr); - le_ctr[0] = 0; - le_ctr[1]++; - (*ctr64_func)(key, walk.src.virt.addr + p1_nbytes, - walk.dst.virt.addr + p1_nbytes, - nbytes - p1_nbytes, le_ctr); - } - kernel_fpu_end(); - le_ctr[0] = ctr64; - - err = skcipher_walk_done(&walk, walk.nbytes - nbytes); - } - - put_unaligned_be64(ctr64, &req->iv[8]); - put_unaligned_be64(le_ctr[1], &req->iv[0]); - - return err; -} - -/* __always_inline to avoid indirect call */ -static __always_inline int -xctr_crypt(struct skcipher_request *req, - void (*xctr_func)(const struct crypto_aes_ctx *key, - const u8 *src, u8 *dst, int len, - const u8 iv[AES_BLOCK_SIZE], u64 ctr)) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *key = aes_ctx(crypto_skcipher_ctx(tfm)); - struct skcipher_walk walk; - unsigned int nbytes; - u64 ctr = 1; - int err; - - err = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { - if (nbytes < walk.total) - nbytes = round_down(nbytes, AES_BLOCK_SIZE); - - kernel_fpu_begin(); - (*xctr_func)(key, walk.src.virt.addr, walk.dst.virt.addr, - nbytes, req->iv, ctr); - kernel_fpu_end(); - - ctr += DIV_ROUND_UP(nbytes, AES_BLOCK_SIZE); - err = skcipher_walk_done(&walk, walk.nbytes - nbytes); - } - return err; -} - #define DEFINE_AVX_SKCIPHER_ALGS(suffix, driver_name_suffix, priority) \ \ asmlinkage void \ @@ -335,25 +229,6 @@ static int xts_decrypt_##suffix(struct skcipher_request *req) \ return xts_crypt(req, aes_xts_encrypt_iv, aes_xts_decrypt_##suffix); \ } \ \ -asmlinkage void \ -aes_ctr64_crypt_##suffix(const struct crypto_aes_ctx *key, \ - const u8 *src, u8 *dst, int len, const u64 le_ctr[2]);\ - \ -static int ctr_crypt_##suffix(struct skcipher_request *req) \ -{ \ - return ctr_crypt(req, aes_ctr64_crypt_##suffix); \ -} \ - \ -asmlinkage void \ -aes_xctr_crypt_##suffix(const struct crypto_aes_ctx *key, \ - const u8 *src, u8 *dst, int len, \ - const u8 iv[AES_BLOCK_SIZE], u64 ctr); \ - \ -static int xctr_crypt_##suffix(struct skcipher_request *req) \ -{ \ - return xctr_crypt(req, aes_xctr_crypt_##suffix); \ -} \ - \ static struct skcipher_alg skcipher_algs_##suffix[] = {{ \ .base.cra_name = "xts(aes)", \ .base.cra_driver_name = "xts-aes-" driver_name_suffix, \ @@ -368,34 +243,6 @@ static struct skcipher_alg skcipher_algs_##suffix[] = {{ \ .setkey = xts_setkey_aesni, \ .encrypt = xts_encrypt_##suffix, \ .decrypt = xts_decrypt_##suffix, \ -}, { \ - .base.cra_name = "ctr(aes)", \ - .base.cra_driver_name = "ctr-aes-" driver_name_suffix, \ - .base.cra_priority = priority, \ - .base.cra_blocksize = 1, \ - .base.cra_ctxsize = CRYPTO_AES_CTX_SIZE, \ - .base.cra_module = THIS_MODULE, \ - .min_keysize = AES_MIN_KEY_SIZE, \ - .max_keysize = AES_MAX_KEY_SIZE, \ - .ivsize = AES_BLOCK_SIZE, \ - .chunksize = AES_BLOCK_SIZE, \ - .setkey = aesni_skcipher_setkey, \ - .encrypt = ctr_crypt_##suffix, \ - .decrypt = ctr_crypt_##suffix, \ -}, { \ - .base.cra_name = "xctr(aes)", \ - .base.cra_driver_name = "xctr-aes-" driver_name_suffix, \ - .base.cra_priority = priority, \ - .base.cra_blocksize = 1, \ - .base.cra_ctxsize = CRYPTO_AES_CTX_SIZE, \ - .base.cra_module = THIS_MODULE, \ - .min_keysize = AES_MIN_KEY_SIZE, \ - .max_keysize = AES_MAX_KEY_SIZE, \ - .ivsize = AES_BLOCK_SIZE, \ - .chunksize = AES_BLOCK_SIZE, \ - .setkey = aesni_skcipher_setkey, \ - .encrypt = xctr_crypt_##suffix, \ - .decrypt = xctr_crypt_##suffix, \ }} DEFINE_AVX_SKCIPHER_ALGS(aesni_avx, "aesni-avx", 500); diff --git a/crypto/aes.c b/crypto/aes.c index 297134d5ce855e..88991c17e520eb 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -668,7 +668,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "ctr(aes)", .base.cra_driver_name = "ctr-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct aes_enckey), .base.cra_module = THIS_MODULE, @@ -685,7 +685,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "xctr(aes)", .base.cra_driver_name = "xctr-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct aes_enckey), .base.cra_module = THIS_MODULE, @@ -1054,8 +1054,7 @@ static struct aead_alg aead_algs[] = { IS_ENABLED(CONFIG_PPC) || \ IS_ENABLED(CONFIG_RISCV) || \ IS_ENABLED(CONFIG_S390) || \ - IS_ENABLED(CONFIG_SPARC) || \ - IS_ENABLED(CONFIG_X86)) + IS_ENABLED(CONFIG_SPARC)) { .base.cra_name = "ccm(aes)", .base.cra_driver_name = "ccm-aes-lib", diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index ca068df1f71f26..5d5484fc78eab2 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -52,7 +52,11 @@ endif # CONFIG_PPC libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned.o libaes-$(CONFIG_SPARC) += sparc/aes_asm.o + libaes-$(CONFIG_X86) += x86/aes-aesni.o +ifneq ($(CONFIG_CRYPTO_LIB_AES_CTR),) +libaes-$(CONFIG_X86_64) += x86/aes-ctr-avx-x86_64.o +endif endif # CONFIG_CRYPTO_LIB_AES_ARCH # clean-files must be defined unconditionally diff --git a/arch/x86/crypto/aes-ctr-avx-x86_64.S b/lib/crypto/x86/aes-ctr-avx-x86_64.S similarity index 88% rename from arch/x86/crypto/aes-ctr-avx-x86_64.S rename to lib/crypto/x86/aes-ctr-avx-x86_64.S index 2745918f68ee64..c232337899b691 100644 --- a/arch/x86/crypto/aes-ctr-avx-x86_64.S +++ b/lib/crypto/x86/aes-ctr-avx-x86_64.S @@ -53,7 +53,10 @@ // See the function definitions at the bottom of the file for more information. #include -#include + +// Offsets in struct aes_enckey +#define OFFSETOF_KEYLEN 0 +#define OFFSETOF_RNDKEYS 16 .section .rodata .p2align 4 @@ -278,24 +281,25 @@ .endr // Function arguments - .set KEY, %rdi // Initially points to the start of the - // crypto_aes_ctx, then is advanced to - // point to the index 1 round key - .set KEY32, %edi // Available as temp register after all - // keystream blocks have been generated + .set DST, %rdi // Pointer to next destination data .set SRC, %rsi // Pointer to next source data - .set DST, %rdx // Pointer to next destination data - .set LEN, %ecx // Remaining length in bytes. - // Note: _load_partial_block relies on - // this being in %ecx. - .set LEN64, %rcx // Zero-extend LEN before using! - .set LEN8, %cl + .set LEN, %rdx // Remaining length in bytes + .set LEN32, %edx // Used for improved code density .if \is_xctr + .set XCTR_CTR, %rcx // u64 ctr; .set XCTR_IV_PTR, %r8 // const u8 iv[AES_BLOCK_SIZE]; - .set XCTR_CTR, %r9 // u64 ctr; + .set KEY, %r9 // Initially points to the start of the + // aes_enckey, then is advanced to + // point to the index 1 round key + .set KEY32, %r9d // Available as temp register after all + // keystream blocks have been generated .else - .set LE_CTR_PTR, %r8 // const u64 le_ctr[2]; + .set LE_CTR_PTR, %rcx // const u64 le_ctr[2]; + .set KEY, %r8 + .set KEY32, %r8d .endif + // Note: the use of _load_partial_block and _store_partial_block at the + // end of the function assumes %rcx doesn't hold DST, SRC, LEN, or KEY. // Additional local variables .set RNDKEYLAST_PTR, %r10 @@ -355,17 +359,17 @@ vpsllq $1, LE_CTR_INC1, LE_CTR_INC2 // Load the AES key length: 16 (AES-128), 24 (AES-192), or 32 (AES-256). - movl 480(KEY), %eax + movl OFFSETOF_KEYLEN(KEY), %eax // Compute the pointer to the last round key. - lea 6*16(KEY, %rax, 4), RNDKEYLAST_PTR + lea OFFSETOF_RNDKEYS+6*16(KEY, %rax, 4), RNDKEYLAST_PTR // Load the zero-th and last round keys. - _vbroadcast128 (KEY), RNDKEY0 + _vbroadcast128 OFFSETOF_RNDKEYS(KEY), RNDKEY0 _vbroadcast128 (RNDKEYLAST_PTR), RNDKEYLAST // Make KEY point to the first round key. - add $16, KEY + add $OFFSETOF_RNDKEYS+16, KEY // This is the main loop, which encrypts 8 vectors of data at a time. add $-8*VL, LEN @@ -382,7 +386,7 @@ add $-8*VL, LEN jge .Lloop_8x\@ .Lloop_8x_done\@: - sub $-8*VL, LEN + sub $-8*VL, LEN32 jz .Ldone\@ // 1 <= LEN < 8*VL. Generate 2, 4, or 8 more vectors of keystream @@ -390,7 +394,7 @@ _prepare_2_ctr_vecs \is_xctr, 0, 1 _prepare_2_ctr_vecs \is_xctr, 2, 3 - cmp $4*VL, LEN + cmp $4*VL, LEN32 jle .Lenc_tail_atmost4vecs\@ // 4*VL < LEN < 8*VL. Generate 8 vectors of keystream blocks. Use the @@ -405,23 +409,23 @@ vaesenclast RNDKEYLAST, AESDATA7, AESDATA3 sub $-4*VL, SRC sub $-4*VL, DST - add $-4*VL, LEN - cmp $1*VL-1, LEN + add $-4*VL, LEN32 + cmp $1*VL-1, LEN32 jle .Lxor_tail_partial_vec_0\@ _xor_data 0 - cmp $2*VL-1, LEN + cmp $2*VL-1, LEN32 jle .Lxor_tail_partial_vec_1\@ _xor_data 1 - cmp $3*VL-1, LEN + cmp $3*VL-1, LEN32 jle .Lxor_tail_partial_vec_2\@ _xor_data 2 - cmp $4*VL-1, LEN + cmp $4*VL-1, LEN32 jle .Lxor_tail_partial_vec_3\@ _xor_data 3 jmp .Ldone\@ .Lenc_tail_atmost4vecs\@: - cmp $2*VL, LEN + cmp $2*VL, LEN32 jle .Lenc_tail_atmost2vecs\@ // 2*VL < LEN <= 4*VL. Generate 4 vectors of keystream blocks. Use the @@ -432,7 +436,7 @@ vaesenclast RNDKEYLAST, AESDATA3, AESDATA1 sub $-2*VL, SRC sub $-2*VL, DST - add $-2*VL, LEN + add $-2*VL, LEN32 jmp .Lxor_tail_upto2vecs\@ .Lenc_tail_atmost2vecs\@: @@ -443,16 +447,16 @@ vaesenclast RNDKEYLAST, AESDATA1, AESDATA1 .Lxor_tail_upto2vecs\@: - cmp $1*VL-1, LEN + cmp $1*VL-1, LEN32 jle .Lxor_tail_partial_vec_0\@ _xor_data 0 - cmp $2*VL-1, LEN + cmp $2*VL-1, LEN32 jle .Lxor_tail_partial_vec_1\@ _xor_data 1 jmp .Ldone\@ .Lxor_tail_partial_vec_1\@: - add $-1*VL, LEN + add $-1*VL, LEN32 jz .Ldone\@ sub $-1*VL, SRC sub $-1*VL, DST @@ -460,7 +464,7 @@ jmp .Lxor_tail_partial_vec_0\@ .Lxor_tail_partial_vec_2\@: - add $-2*VL, LEN + add $-2*VL, LEN32 jz .Ldone\@ sub $-2*VL, SRC sub $-2*VL, DST @@ -468,7 +472,7 @@ jmp .Lxor_tail_partial_vec_0\@ .Lxor_tail_partial_vec_3\@: - add $-3*VL, LEN + add $-3*VL, LEN32 jz .Ldone\@ sub $-3*VL, SRC sub $-3*VL, DST @@ -479,28 +483,29 @@ // loads/stores are available; otherwise it's a bit harder... .if USE_AVX512 mov $-1, %rax - bzhi LEN64, %rax, %rax + bzhi LEN, %rax, %rax kmovq %rax, %k1 vmovdqu8 (SRC), AESDATA1{%k1}{z} vpxord AESDATA1, AESDATA0, AESDATA0 vmovdqu8 AESDATA0, (DST){%k1} .else .if VL == 32 - cmp $16, LEN + cmp $16, LEN32 jl 1f vpxor (SRC), AESDATA0_XMM, AESDATA1_XMM vmovdqu AESDATA1_XMM, (DST) add $16, SRC add $16, DST - sub $16, LEN + sub $16, LEN32 jz .Ldone\@ vextracti128 $1, AESDATA0, AESDATA0_XMM 1: .endif - mov LEN, %r10d + // Note: this assumes %rcx doesn't hold DST, SRC, LEN, or KEY. + mov LEN32, %ecx _load_partial_block SRC, AESDATA1_XMM, KEY, KEY32 vpxor AESDATA1_XMM, AESDATA0_XMM, AESDATA0_XMM - mov %r10d, %ecx + mov LEN32, %ecx _store_partial_block AESDATA0_XMM, DST, KEY, KEY32 .endif @@ -515,13 +520,13 @@ // They have the following prototypes: // // -// void aes_ctr64_crypt_##suffix(const struct crypto_aes_ctx *key, -// const u8 *src, u8 *dst, int len, -// const u64 le_ctr[2]); +// void aes_ctr64_crypt_##suffix(u8 *dst, const u8 *src, s64 len, +// const u64 le_ctr[2], +// const struct aes_enckey *key); // -// void aes_xctr_crypt_##suffix(const struct crypto_aes_ctx *key, -// const u8 *src, u8 *dst, int len, -// const u8 iv[AES_BLOCK_SIZE], u64 ctr); +// void aes_xctr_crypt_##suffix(u8 *dst, const u8 *src, s64 len, u64 ctr, +// const u8 iv[AES_BLOCK_SIZE], +// const struct aes_enckey *key); // // Both functions generate |len| bytes of keystream, XOR it with the data from // |src|, and write the result to |dst|. On non-final calls, |len| must be a @@ -545,27 +550,27 @@ .set VL, 16 .set USE_AVX512, 0 -SYM_TYPED_FUNC_START(aes_ctr64_crypt_aesni_avx) +SYM_FUNC_START(aes_ctr64_crypt_aesni_avx) _aes_ctr_crypt 0 SYM_FUNC_END(aes_ctr64_crypt_aesni_avx) -SYM_TYPED_FUNC_START(aes_xctr_crypt_aesni_avx) +SYM_FUNC_START(aes_xctr_crypt_aesni_avx) _aes_ctr_crypt 1 SYM_FUNC_END(aes_xctr_crypt_aesni_avx) .set VL, 32 .set USE_AVX512, 0 -SYM_TYPED_FUNC_START(aes_ctr64_crypt_vaes_avx2) +SYM_FUNC_START(aes_ctr64_crypt_vaes_avx2) _aes_ctr_crypt 0 SYM_FUNC_END(aes_ctr64_crypt_vaes_avx2) -SYM_TYPED_FUNC_START(aes_xctr_crypt_vaes_avx2) +SYM_FUNC_START(aes_xctr_crypt_vaes_avx2) _aes_ctr_crypt 1 SYM_FUNC_END(aes_xctr_crypt_vaes_avx2) .set VL, 64 .set USE_AVX512, 1 -SYM_TYPED_FUNC_START(aes_ctr64_crypt_vaes_avx512) +SYM_FUNC_START(aes_ctr64_crypt_vaes_avx512) _aes_ctr_crypt 0 SYM_FUNC_END(aes_ctr64_crypt_vaes_avx512) -SYM_TYPED_FUNC_START(aes_xctr_crypt_vaes_avx512) +SYM_FUNC_START(aes_xctr_crypt_vaes_avx512) _aes_ctr_crypt 1 SYM_FUNC_END(aes_xctr_crypt_vaes_avx512) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index 54b599bd258195..8ed247ddb8f201 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -8,8 +8,12 @@ #include static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_aesni); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_aesni_avx); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_vaes_avx2); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_vaes_avx512); /* The assembly code assumes the following offsets. */ +static_assert(offsetof(struct aes_enckey, len) == 0); static_assert(offsetof(struct aes_enckey, nrounds) == 4); static_assert(offsetof(struct aes_enckey, k.rndkeys) == 16); static_assert(offsetof(struct aes_key, nrounds) == 4); @@ -208,11 +212,36 @@ static bool aes_cbc_cts_decrypt_arch(u8 *dst, const u8 *src, size_t len, #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CTR) && IS_ENABLED(CONFIG_X86_64) void aes_ctr64_crypt_aesni(u8 *dst, const u8 *src, s64 len, const u64 le_ctr[2], const struct aes_enckey *key); +void aes_ctr64_crypt_aesni_avx(u8 *dst, const u8 *src, s64 len, + const u64 le_ctr[2], + const struct aes_enckey *key); +void aes_ctr64_crypt_vaes_avx2(u8 *dst, const u8 *src, s64 len, + const u64 le_ctr[2], + const struct aes_enckey *key); +void aes_ctr64_crypt_vaes_avx512(u8 *dst, const u8 *src, s64 len, + const u64 le_ctr[2], + const struct aes_enckey *key); +void aes_xctr_crypt_aesni_avx(u8 *dst, const u8 *src, s64 len, u64 ctr, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key); +void aes_xctr_crypt_vaes_avx2(u8 *dst, const u8 *src, s64 len, u64 ctr, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key); +void aes_xctr_crypt_vaes_avx512(u8 *dst, const u8 *src, s64 len, u64 ctr, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key); static void aes_ctr64_x86(u8 *dst, const u8 *src, size_t len, const u64 le_ctr[2], const struct aes_enckey *key) { - aes_ctr64_crypt_aesni(dst, src, len, le_ctr, key); + if (static_branch_likely(&have_vaes_avx512)) + aes_ctr64_crypt_vaes_avx512(dst, src, len, le_ctr, key); + else if (static_branch_likely(&have_vaes_avx2)) + aes_ctr64_crypt_vaes_avx2(dst, src, len, le_ctr, key); + else if (static_branch_likely(&have_aesni_avx)) + aes_ctr64_crypt_aesni_avx(dst, src, len, le_ctr, key); + else + aes_ctr64_crypt_aesni(dst, src, len, le_ctr, key); } #define aes_ctr_arch aes_ctr_arch @@ -257,6 +286,25 @@ static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, put_unaligned_be64(le_ctr[1], &ctr[0]); return true; } + +#define aes_xctr_arch aes_xctr_arch +static bool aes_xctr_arch(u8 *dst, const u8 *src, size_t len, u64 ctr, + const u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_aesni_avx) || + unlikely(!irq_fpu_usable())) + return false; + kernel_fpu_begin(); + if (static_branch_likely(&have_vaes_avx512)) + aes_xctr_crypt_vaes_avx512(dst, src, len, ctr, iv, key); + else if (static_branch_likely(&have_vaes_avx2)) + aes_xctr_crypt_vaes_avx2(dst, src, len, ctr, iv, key); + else + aes_xctr_crypt_aesni_avx(dst, src, len, ctr, iv, key); + kernel_fpu_end(); + return true; +} #endif /* CONFIG_CRYPTO_LIB_AES_CTR && CONFIG_X86_64 */ #if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) @@ -306,6 +354,32 @@ static bool aes_xts_decrypt_arch(u8 *dst, const u8 *src, size_t len, #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { - if (boot_cpu_has(X86_FEATURE_AES)) - static_branch_enable(&have_aesni); + /* Everything below requires AES-NI. */ + if (!boot_cpu_has(X86_FEATURE_AES)) + return; + static_branch_enable(&have_aesni); + + /* Everything below requires AVX and is also 64-bit only. */ + if (!boot_cpu_has(X86_FEATURE_AVX) || !IS_ENABLED(CONFIG_X86_64)) + return; + static_branch_enable(&have_aesni_avx); + + /* + * Everything below requires VAES, and also sometimes AVX2, VPCLMULQDQ, + * and PCLMULQDQ. Use a single static key for all of them, since in + * practice every CPU with VAES also has the others. + */ + if (!boot_cpu_has(X86_FEATURE_VAES) || + !boot_cpu_has(X86_FEATURE_AVX2) || + !boot_cpu_has(X86_FEATURE_VPCLMULQDQ) || + !boot_cpu_has(X86_FEATURE_PCLMULQDQ)) + return; + static_branch_enable(&have_vaes_avx2); + + if (!boot_cpu_has(X86_FEATURE_AVX512BW) || + !boot_cpu_has(X86_FEATURE_AVX512VL) || + !boot_cpu_has(X86_FEATURE_BMI2) || + boot_cpu_has(X86_FEATURE_PREFER_YMM)) + return; + static_branch_enable(&have_vaes_avx512); } From 2bd6888849d6d2d5497c2470cc71c8d2cb170523 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:05 -0700 Subject: [PATCH 1131/2286] lib/crypto: x86/aes-xts: Migrate AVX-optimized code into library Migrate aes-xts-avx-x86_64.S into lib/crypto/, wiring it up to the XTS library functions instead of the crypto_skcipher API. It still remains available through crypto_skcipher via crypto/aes.c. Some adjustments to the assembly code were needed: - Take 'struct aes_key' instead of 'struct crypto_aes_ctx'. - Remove the ciphertext stealing support from the assembly code, as the library implements it in a generic way instead. (This does slightly reduce performance when the length isn't a multiple of 16 bytes; however, that case seems to never be reached in practice in the kernel. So it makes sense to not extensively optimize for it yet.) - Change 'int len' to 'long nblocks' for compatibility with the library's use of size_t lengths. - Remove the CFI stubs, as the functions are now called directly. - Adjust the argument order to match the caller. Not strictly required, but it's easiest to handle this now when changing the function prototypes anyway and adding the new glue code. This makes the remaining code in aesni-intel_asm.S (which just handled key expansion) unused, so remove that too. Don't remove the selection of CRYPTO_SKCIPHER from CRYPTO_AES_NI_INTEL yet, since it's still needed for the "skcipher_walk" functions. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-15-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 6 +- arch/x86/crypto/Makefile | 6 +- arch/x86/crypto/aesni-intel_asm.S | 220 ---------------- arch/x86/crypto/aesni-intel_glue.c | 239 +----------------- crypto/aes.c | 2 +- lib/crypto/Makefile | 3 + .../crypto/x86}/aes-xts-avx-x86_64.S | 179 ++++--------- lib/crypto/x86/aes.h | 67 ++++- 8 files changed, 121 insertions(+), 601 deletions(-) delete mode 100644 arch/x86/crypto/aesni-intel_asm.S rename {arch/x86/crypto => lib/crypto/x86}/aes-xts-avx-x86_64.S (81%) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index 778ab164c571c7..c1842c2d70559b 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -3,7 +3,7 @@ menu "Accelerated Cryptographic Algorithms for CPU (x86)" config CRYPTO_AES_NI_INTEL - tristate "Ciphers: AES, modes: XTS, GCM (AES-NI/VAES)" + tristate "Ciphers: AES, modes: GCM (AES-NI/VAES)" depends on 64BIT select CRYPTO_AEAD select CRYPTO_LIB_AES @@ -11,12 +11,14 @@ config CRYPTO_AES_NI_INTEL select CRYPTO_SKCIPHER help AEAD cipher: AES with GCM - Length-preserving ciphers: AES with XTS Architecture: x86_64 using: - AES-NI (AES new instructions) - VAES (Vector AES) + Note: this option no longer provides the accelerated XTS, CBC, CTR, + and ECB code. For those just use CRYPTO_XTS, CRYPTO_CBC, etc. + config CRYPTO_BLOWFISH_X86_64 tristate "Ciphers: Blowfish, modes: ECB, CBC" depends on 64BIT diff --git a/arch/x86/crypto/Makefile b/arch/x86/crypto/Makefile index 0c016ba87373e1..09bdea45bc3e76 100644 --- a/arch/x86/crypto/Makefile +++ b/arch/x86/crypto/Makefile @@ -40,12 +40,10 @@ obj-$(CONFIG_CRYPTO_AEGIS128_AESNI_SSE2) += aegis128-aesni.o aegis128-aesni-y := aegis128-aesni-asm.o aegis128-aesni-glue.o obj-$(CONFIG_CRYPTO_AES_NI_INTEL) += aesni-intel.o -aesni-intel-y := aesni-intel_asm.o \ - aesni-intel_glue.o \ +aesni-intel-y := aesni-intel_glue.o \ aes-gcm-aesni-x86_64.o \ aes-gcm-vaes-avx2.o \ - aes-gcm-vaes-avx512.o \ - aes-xts-avx-x86_64.o + aes-gcm-vaes-avx512.o obj-$(CONFIG_CRYPTO_SM4_AESNI_AVX_X86_64) += sm4-aesni-avx-x86_64.o sm4-aesni-avx-x86_64-y := sm4-aesni-avx-asm_64.o sm4_aesni_avx_glue.o diff --git a/arch/x86/crypto/aesni-intel_asm.S b/arch/x86/crypto/aesni-intel_asm.S deleted file mode 100644 index b12a0f2bf00692..00000000000000 --- a/arch/x86/crypto/aesni-intel_asm.S +++ /dev/null @@ -1,220 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Implement AES algorithm in Intel AES-NI instructions. - * - * The white paper of AES-NI instructions can be downloaded from: - * http://softwarecommunity.intel.com/isn/downloads/intelavx/AES-Instructions-Set_WP.pdf - * - * Copyright (C) 2008, Intel Corp. - * Author: Huang Ying - * Vinodh Gopal - * Kahraman Akdemir - * - * Copyright (c) 2010, Intel Corporation. - * - * Ported x86_64 version to x86: - * Author: Mathias Krause - */ - -#include -#include -#include - -#ifdef __x86_64__ -#define AREG %rax -#define KEYP %rdi -#define OUTP %rsi -#define UKEYP OUTP -#define T1 %r10 -#define TKEYP T1 -#define T2 %r11 -#else -#define AREG %eax -#define KEYP %edi -#define OUTP AREG -#define UKEYP OUTP -#define T1 %ecx -#define TKEYP T1 -#endif - -SYM_FUNC_START_LOCAL(_key_expansion_256a) - pshufd $0b11111111, %xmm1, %xmm1 - shufps $0b00010000, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - shufps $0b10001100, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - pxor %xmm1, %xmm0 - movaps %xmm0, (TKEYP) - add $0x10, TKEYP - RET -SYM_FUNC_END(_key_expansion_256a) -SYM_FUNC_ALIAS_LOCAL(_key_expansion_128, _key_expansion_256a) - -SYM_FUNC_START_LOCAL(_key_expansion_192a) - pshufd $0b01010101, %xmm1, %xmm1 - shufps $0b00010000, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - shufps $0b10001100, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - pxor %xmm1, %xmm0 - - movaps %xmm2, %xmm5 - movaps %xmm2, %xmm6 - pslldq $4, %xmm5 - pshufd $0b11111111, %xmm0, %xmm3 - pxor %xmm3, %xmm2 - pxor %xmm5, %xmm2 - - movaps %xmm0, %xmm1 - shufps $0b01000100, %xmm0, %xmm6 - movaps %xmm6, (TKEYP) - shufps $0b01001110, %xmm2, %xmm1 - movaps %xmm1, 0x10(TKEYP) - add $0x20, TKEYP - RET -SYM_FUNC_END(_key_expansion_192a) - -SYM_FUNC_START_LOCAL(_key_expansion_192b) - pshufd $0b01010101, %xmm1, %xmm1 - shufps $0b00010000, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - shufps $0b10001100, %xmm0, %xmm4 - pxor %xmm4, %xmm0 - pxor %xmm1, %xmm0 - - movaps %xmm2, %xmm5 - pslldq $4, %xmm5 - pshufd $0b11111111, %xmm0, %xmm3 - pxor %xmm3, %xmm2 - pxor %xmm5, %xmm2 - - movaps %xmm0, (TKEYP) - add $0x10, TKEYP - RET -SYM_FUNC_END(_key_expansion_192b) - -SYM_FUNC_START_LOCAL(_key_expansion_256b) - pshufd $0b10101010, %xmm1, %xmm1 - shufps $0b00010000, %xmm2, %xmm4 - pxor %xmm4, %xmm2 - shufps $0b10001100, %xmm2, %xmm4 - pxor %xmm4, %xmm2 - pxor %xmm1, %xmm2 - movaps %xmm2, (TKEYP) - add $0x10, TKEYP - RET -SYM_FUNC_END(_key_expansion_256b) - -/* - * void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, - * unsigned int key_len) - */ -SYM_FUNC_START(aesni_set_key) - FRAME_BEGIN -#ifndef __x86_64__ - pushl KEYP - movl (FRAME_OFFSET+8)(%esp), KEYP # ctx - movl (FRAME_OFFSET+12)(%esp), UKEYP # in_key - movl (FRAME_OFFSET+16)(%esp), %edx # key_len -#endif - movups (UKEYP), %xmm0 # user key (first 16 bytes) - movaps %xmm0, (KEYP) - lea 0x10(KEYP), TKEYP # key addr - movl %edx, 480(KEYP) - pxor %xmm4, %xmm4 # xmm4 is assumed 0 in _key_expansion_x - cmp $24, %dl - jb .Lenc_key128 - je .Lenc_key192 - movups 0x10(UKEYP), %xmm2 # other user key - movaps %xmm2, (TKEYP) - add $0x10, TKEYP - aeskeygenassist $0x1, %xmm2, %xmm1 # round 1 - call _key_expansion_256a - aeskeygenassist $0x1, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x2, %xmm2, %xmm1 # round 2 - call _key_expansion_256a - aeskeygenassist $0x2, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x4, %xmm2, %xmm1 # round 3 - call _key_expansion_256a - aeskeygenassist $0x4, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x8, %xmm2, %xmm1 # round 4 - call _key_expansion_256a - aeskeygenassist $0x8, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x10, %xmm2, %xmm1 # round 5 - call _key_expansion_256a - aeskeygenassist $0x10, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x20, %xmm2, %xmm1 # round 6 - call _key_expansion_256a - aeskeygenassist $0x20, %xmm0, %xmm1 - call _key_expansion_256b - aeskeygenassist $0x40, %xmm2, %xmm1 # round 7 - call _key_expansion_256a - jmp .Ldec_key -.Lenc_key192: - movq 0x10(UKEYP), %xmm2 # other user key - aeskeygenassist $0x1, %xmm2, %xmm1 # round 1 - call _key_expansion_192a - aeskeygenassist $0x2, %xmm2, %xmm1 # round 2 - call _key_expansion_192b - aeskeygenassist $0x4, %xmm2, %xmm1 # round 3 - call _key_expansion_192a - aeskeygenassist $0x8, %xmm2, %xmm1 # round 4 - call _key_expansion_192b - aeskeygenassist $0x10, %xmm2, %xmm1 # round 5 - call _key_expansion_192a - aeskeygenassist $0x20, %xmm2, %xmm1 # round 6 - call _key_expansion_192b - aeskeygenassist $0x40, %xmm2, %xmm1 # round 7 - call _key_expansion_192a - aeskeygenassist $0x80, %xmm2, %xmm1 # round 8 - call _key_expansion_192b - jmp .Ldec_key -.Lenc_key128: - aeskeygenassist $0x1, %xmm0, %xmm1 # round 1 - call _key_expansion_128 - aeskeygenassist $0x2, %xmm0, %xmm1 # round 2 - call _key_expansion_128 - aeskeygenassist $0x4, %xmm0, %xmm1 # round 3 - call _key_expansion_128 - aeskeygenassist $0x8, %xmm0, %xmm1 # round 4 - call _key_expansion_128 - aeskeygenassist $0x10, %xmm0, %xmm1 # round 5 - call _key_expansion_128 - aeskeygenassist $0x20, %xmm0, %xmm1 # round 6 - call _key_expansion_128 - aeskeygenassist $0x40, %xmm0, %xmm1 # round 7 - call _key_expansion_128 - aeskeygenassist $0x80, %xmm0, %xmm1 # round 8 - call _key_expansion_128 - aeskeygenassist $0x1b, %xmm0, %xmm1 # round 9 - call _key_expansion_128 - aeskeygenassist $0x36, %xmm0, %xmm1 # round 10 - call _key_expansion_128 -.Ldec_key: - sub $0x10, TKEYP - movaps (KEYP), %xmm0 - movaps (TKEYP), %xmm1 - movaps %xmm0, 240(TKEYP) - movaps %xmm1, 240(KEYP) - add $0x10, KEYP - lea 240-16(TKEYP), UKEYP -.align 4 -.Ldec_key_loop: - movaps (KEYP), %xmm0 - aesimc %xmm0, %xmm1 - movaps %xmm1, (UKEYP) - add $0x10, KEYP - sub $0x10, UKEYP - cmp TKEYP, KEYP - jb .Ldec_key_loop -#ifndef __x86_64__ - popl KEYP -#endif - FRAME_END - RET -SYM_FUNC_END(aesni_set_key) diff --git a/arch/x86/crypto/aesni-intel_glue.c b/arch/x86/crypto/aesni-intel_glue.c index 3787b932f101c1..a23ce12c7c338b 100644 --- a/arch/x86/crypto/aesni-intel_glue.c +++ b/arch/x86/crypto/aesni-intel_glue.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* * Support for AES-NI and VAES instructions. This file contains glue code. - * The real AES implementations are in aesni-intel_asm.S and other .S files. + * The real AES implementations are in .S files. * * Copyright (C) 2008, Intel Corp. * Author: Huang Ying @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -38,217 +37,6 @@ #include #include - -#define AESNI_ALIGN 16 -#define AESNI_ALIGN_ATTR __attribute__ ((__aligned__(AESNI_ALIGN))) -#define AESNI_ALIGN_EXTRA ((AESNI_ALIGN - 1) & ~(CRYPTO_MINALIGN - 1)) -#define XTS_AES_CTX_SIZE (sizeof(struct aesni_xts_ctx) + AESNI_ALIGN_EXTRA) - -struct aesni_xts_ctx { - struct crypto_aes_ctx tweak_ctx AESNI_ALIGN_ATTR; - struct crypto_aes_ctx crypt_ctx AESNI_ALIGN_ATTR; -}; - -static inline void *aes_align_addr(void *addr) -{ - if (crypto_tfm_ctx_alignment() >= AESNI_ALIGN) - return addr; - return PTR_ALIGN(addr, AESNI_ALIGN); -} - -asmlinkage void aesni_set_key(struct crypto_aes_ctx *ctx, const u8 *in_key, - unsigned int key_len); - -static inline struct aesni_xts_ctx *aes_xts_ctx(struct crypto_skcipher *tfm) -{ - return aes_align_addr(crypto_skcipher_ctx(tfm)); -} - -static int aes_set_key_common(struct crypto_aes_ctx *ctx, - const u8 *in_key, unsigned int key_len) -{ - int err; - - if (!crypto_simd_usable()) - return aes_expandkey(ctx, in_key, key_len); - - err = aes_check_keylen(key_len); - if (err) - return err; - - kernel_fpu_begin(); - aesni_set_key(ctx, in_key, key_len); - kernel_fpu_end(); - return 0; -} - -static int xts_setkey_aesni(struct crypto_skcipher *tfm, const u8 *key, - unsigned int keylen) -{ - struct aesni_xts_ctx *ctx = aes_xts_ctx(tfm); - int err; - - err = xts_verify_key(tfm, key, keylen); - if (err) - return err; - - keylen /= 2; - - /* first half of xts-key is for crypt */ - err = aes_set_key_common(&ctx->crypt_ctx, key, keylen); - if (err) - return err; - - /* second half of xts-key is for tweak */ - return aes_set_key_common(&ctx->tweak_ctx, key + keylen, keylen); -} - -typedef void (*xts_encrypt_iv_func)(const struct crypto_aes_ctx *tweak_key, - u8 iv[AES_BLOCK_SIZE]); -typedef void (*xts_crypt_func)(const struct crypto_aes_ctx *key, - const u8 *src, u8 *dst, int len, - u8 tweak[AES_BLOCK_SIZE]); - -/* This handles cases where the source and/or destination span pages. */ -static noinline int -xts_crypt_slowpath(struct skcipher_request *req, xts_crypt_func crypt_func) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct aesni_xts_ctx *ctx = aes_xts_ctx(tfm); - int tail = req->cryptlen % AES_BLOCK_SIZE; - struct scatterlist sg_src[2], sg_dst[2]; - struct skcipher_request subreq; - struct skcipher_walk walk; - struct scatterlist *src, *dst; - int err; - - /* - * If the message length isn't divisible by the AES block size, then - * separate off the last full block and the partial block. This ensures - * that they are processed in the same call to the assembly function, - * which is required for ciphertext stealing. - */ - if (tail) { - skcipher_request_set_tfm(&subreq, tfm); - skcipher_request_set_callback(&subreq, - skcipher_request_flags(req), - NULL, NULL); - skcipher_request_set_crypt(&subreq, req->src, req->dst, - req->cryptlen - tail - AES_BLOCK_SIZE, - req->iv); - req = &subreq; - } - - err = skcipher_walk_virt(&walk, req, false); - - while (walk.nbytes) { - kernel_fpu_begin(); - (*crypt_func)(&ctx->crypt_ctx, - walk.src.virt.addr, walk.dst.virt.addr, - walk.nbytes & ~(AES_BLOCK_SIZE - 1), req->iv); - kernel_fpu_end(); - err = skcipher_walk_done(&walk, - walk.nbytes & (AES_BLOCK_SIZE - 1)); - } - - if (err || !tail) - return err; - - /* Do ciphertext stealing with the last full block and partial block. */ - - dst = src = scatterwalk_ffwd(sg_src, req->src, req->cryptlen); - if (req->dst != req->src) - dst = scatterwalk_ffwd(sg_dst, req->dst, req->cryptlen); - - skcipher_request_set_crypt(req, src, dst, AES_BLOCK_SIZE + tail, - req->iv); - - err = skcipher_walk_virt(&walk, req, false); - if (err) - return err; - - kernel_fpu_begin(); - (*crypt_func)(&ctx->crypt_ctx, walk.src.virt.addr, walk.dst.virt.addr, - walk.nbytes, req->iv); - kernel_fpu_end(); - - return skcipher_walk_done(&walk, 0); -} - -/* __always_inline to avoid indirect call in fastpath */ -static __always_inline int -xts_crypt(struct skcipher_request *req, xts_encrypt_iv_func encrypt_iv, - xts_crypt_func crypt_func) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct aesni_xts_ctx *ctx = aes_xts_ctx(tfm); - - if (unlikely(req->cryptlen < AES_BLOCK_SIZE)) - return -EINVAL; - - kernel_fpu_begin(); - (*encrypt_iv)(&ctx->tweak_ctx, req->iv); - - /* - * In practice, virtually all XTS plaintexts and ciphertexts are either - * 512 or 4096 bytes and do not use multiple scatterlist elements. To - * optimize the performance of these cases, the below fast-path handles - * single-scatterlist-element messages as efficiently as possible. The - * code is 64-bit specific, as it assumes no page mapping is needed. - */ - if (likely(req->src->length >= req->cryptlen && - req->dst->length >= req->cryptlen)) { - (*crypt_func)(&ctx->crypt_ctx, sg_virt(req->src), - sg_virt(req->dst), req->cryptlen, req->iv); - kernel_fpu_end(); - return 0; - } - kernel_fpu_end(); - return xts_crypt_slowpath(req, crypt_func); -} - -asmlinkage void aes_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, - u8 iv[AES_BLOCK_SIZE]); - -#define DEFINE_AVX_SKCIPHER_ALGS(suffix, driver_name_suffix, priority) \ - \ -asmlinkage void \ -aes_xts_encrypt_##suffix(const struct crypto_aes_ctx *key, const u8 *src, \ - u8 *dst, int len, u8 tweak[AES_BLOCK_SIZE]); \ -asmlinkage void \ -aes_xts_decrypt_##suffix(const struct crypto_aes_ctx *key, const u8 *src, \ - u8 *dst, int len, u8 tweak[AES_BLOCK_SIZE]); \ - \ -static int xts_encrypt_##suffix(struct skcipher_request *req) \ -{ \ - return xts_crypt(req, aes_xts_encrypt_iv, aes_xts_encrypt_##suffix); \ -} \ - \ -static int xts_decrypt_##suffix(struct skcipher_request *req) \ -{ \ - return xts_crypt(req, aes_xts_encrypt_iv, aes_xts_decrypt_##suffix); \ -} \ - \ -static struct skcipher_alg skcipher_algs_##suffix[] = {{ \ - .base.cra_name = "xts(aes)", \ - .base.cra_driver_name = "xts-aes-" driver_name_suffix, \ - .base.cra_priority = priority, \ - .base.cra_blocksize = AES_BLOCK_SIZE, \ - .base.cra_ctxsize = XTS_AES_CTX_SIZE, \ - .base.cra_module = THIS_MODULE, \ - .min_keysize = 2 * AES_MIN_KEY_SIZE, \ - .max_keysize = 2 * AES_MAX_KEY_SIZE, \ - .ivsize = AES_BLOCK_SIZE, \ - .walksize = 2 * AES_BLOCK_SIZE, \ - .setkey = xts_setkey_aesni, \ - .encrypt = xts_encrypt_##suffix, \ - .decrypt = xts_decrypt_##suffix, \ -}} - -DEFINE_AVX_SKCIPHER_ALGS(aesni_avx, "aesni-avx", 500); -DEFINE_AVX_SKCIPHER_ALGS(vaes_avx2, "vaes-avx2", 600); -DEFINE_AVX_SKCIPHER_ALGS(vaes_avx512, "vaes-avx512", 800); - /* The common part of the x86_64 AES-GCM key struct */ struct aes_gcm_key { /* Expanded AES key and the AES key length in bytes */ @@ -1002,10 +790,6 @@ static int __init register_avx_algs(void) if (!boot_cpu_has(X86_FEATURE_AVX)) return 0; - err = crypto_register_skciphers(skcipher_algs_aesni_avx, - ARRAY_SIZE(skcipher_algs_aesni_avx)); - if (err) - return err; err = crypto_register_aeads(aes_gcm_algs_aesni_avx, ARRAY_SIZE(aes_gcm_algs_aesni_avx)); if (err) @@ -1022,10 +806,6 @@ static int __init register_avx_algs(void) !boot_cpu_has(X86_FEATURE_PCLMULQDQ) || !cpu_has_xfeatures(XFEATURE_MASK_SSE | XFEATURE_MASK_YMM, NULL)) return 0; - err = crypto_register_skciphers(skcipher_algs_vaes_avx2, - ARRAY_SIZE(skcipher_algs_vaes_avx2)); - if (err) - return err; err = crypto_register_aeads(aes_gcm_algs_vaes_avx2, ARRAY_SIZE(aes_gcm_algs_vaes_avx2)); if (err) @@ -1041,16 +821,10 @@ static int __init register_avx_algs(void) if (boot_cpu_has(X86_FEATURE_PREFER_YMM)) { int i; - for (i = 0; i < ARRAY_SIZE(skcipher_algs_vaes_avx512); i++) - skcipher_algs_vaes_avx512[i].base.cra_priority = 1; for (i = 0; i < ARRAY_SIZE(aes_gcm_algs_vaes_avx512); i++) aes_gcm_algs_vaes_avx512[i].base.cra_priority = 1; } - err = crypto_register_skciphers(skcipher_algs_vaes_avx512, - ARRAY_SIZE(skcipher_algs_vaes_avx512)); - if (err) - return err; err = crypto_register_aeads(aes_gcm_algs_vaes_avx512, ARRAY_SIZE(aes_gcm_algs_vaes_avx512)); if (err) @@ -1059,19 +833,13 @@ static int __init register_avx_algs(void) return 0; } -#define unregister_skciphers(A) \ - if (refcount_read(&(A)[0].base.cra_refcnt) != 0) \ - crypto_unregister_skciphers((A), ARRAY_SIZE(A)) #define unregister_aeads(A) \ if (refcount_read(&(A)[0].base.cra_refcnt) != 0) \ crypto_unregister_aeads((A), ARRAY_SIZE(A)) static void unregister_avx_algs(void) { - unregister_skciphers(skcipher_algs_aesni_avx); unregister_aeads(aes_gcm_algs_aesni_avx); - unregister_skciphers(skcipher_algs_vaes_avx2); - unregister_skciphers(skcipher_algs_vaes_avx512); unregister_aeads(aes_gcm_algs_vaes_avx2); unregister_aeads(aes_gcm_algs_vaes_avx512); } @@ -1117,6 +885,7 @@ static void __exit aesni_exit(void) module_init(aesni_init); module_exit(aesni_exit); -MODULE_DESCRIPTION("AES cipher and modes, optimized with AES-NI or VAES instructions"); +MODULE_DESCRIPTION("AES-GCM, optimized with AES-NI or VAES instructions"); MODULE_LICENSE("GPL"); -MODULE_ALIAS_CRYPTO("aes"); +MODULE_ALIAS_CRYPTO("gcm(aes)"); +MODULE_ALIAS_CRYPTO("rfc4106(gcm(aes))"); diff --git a/crypto/aes.c b/crypto/aes.c index 88991c17e520eb..f8d28c0f33205e 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -708,7 +708,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", - .base.cra_priority = 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_xts_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index 5d5484fc78eab2..02d89a2263770c 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -57,6 +57,9 @@ libaes-$(CONFIG_X86) += x86/aes-aesni.o ifneq ($(CONFIG_CRYPTO_LIB_AES_CTR),) libaes-$(CONFIG_X86_64) += x86/aes-ctr-avx-x86_64.o endif +ifneq ($(CONFIG_CRYPTO_LIB_AES_XTS),) +libaes-$(CONFIG_X86_64) += x86/aes-xts-avx-x86_64.o +endif endif # CONFIG_CRYPTO_LIB_AES_ARCH # clean-files must be defined unconditionally diff --git a/arch/x86/crypto/aes-xts-avx-x86_64.S b/lib/crypto/x86/aes-xts-avx-x86_64.S similarity index 81% rename from arch/x86/crypto/aes-xts-avx-x86_64.S rename to lib/crypto/x86/aes-xts-avx-x86_64.S index a30753a3e20726..2c13451ea343fa 100644 --- a/arch/x86/crypto/aes-xts-avx-x86_64.S +++ b/lib/crypto/x86/aes-xts-avx-x86_64.S @@ -80,14 +80,17 @@ * any CPUs that support VAES but not VPCLMULQDQ. If that changes, we might * need to start also providing an implementation using VAES alone. * - * The AES-XTS implementations in this file support everything required by the - * crypto API, including support for arbitrary input lengths and multi-part - * processing. However, they are most heavily optimized for the common case of - * power-of-2 length inputs that are processed in a single part (disk sectors). + * These assembly functions don't handle ciphertext stealing, i.e, lengths that + * aren't a multiple of 16 bytes. That case is not actually reached in the + * current use cases of AES-XTS in the kernel and is just handled by the C code. */ #include -#include + +// Offsets in struct aes_key +#define OFFSETOF_KEYLEN 0 +#define OFFSETOF_RNDKEYS 16 +#define OFFSETOF_INVRNDKEYS 256 .section .rodata .p2align 4 @@ -111,16 +114,6 @@ .Llshift_amounts: .byte 0, 0, 1, 1, 2, 2, 3, 3 - // This table contains constants for vpshufb and vpblendvb, used to - // handle variable byte shifts and blending during ciphertext stealing - // on CPUs that don't support AVX512-style masking. -.Lcts_permute_table: - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 - .byte 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 - .byte 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 .text .macro _define_Vi i @@ -149,14 +142,13 @@ .endif // Function parameters - .set KEY, %rdi // Initially points to crypto_aes_ctx, then is - // advanced to point to 7th-from-last round key + .set DST, %rdi // Pointer to next destination data .set SRC, %rsi // Pointer to next source data - .set DST, %rdx // Pointer to next destination data - .set LEN, %ecx // Remaining length in bytes - .set LEN8, %cl - .set LEN64, %rcx - .set TWEAK, %r8 // Pointer to next tweak + .set NBLOCKS, %rdx // Number of blocks remaining + .set NBLOCKS32, %edx // Used for improved code density + .set TWEAK, %rcx // Pointer to next tweak + .set KEY, %r8 // Initially points to struct aes_key, then is + // advanced to point to 7th-from-last round key // %rax holds the AES key length in bytes. .set KEYLEN, %eax @@ -468,9 +460,9 @@ // Select either the encryption round keys or the decryption round keys. .if \enc - .set OFFS, 0 + .set OFFS, OFFSETOF_RNDKEYS .else - .set OFFS, 240 + .set OFFS, OFFSETOF_INVRNDKEYS .endif // Load the round key for "round 0". @@ -615,19 +607,8 @@ .macro _aes_xts_crypt enc _define_aliases -.if !\enc - // When decrypting a message whose length isn't a multiple of the AES - // block length, exclude the last full block from the main loop by - // subtracting 16 from LEN. This is needed because ciphertext stealing - // decryption uses the last two tweaks in reverse order. We'll handle - // the last full block and the partial block specially at the end. - lea -16(LEN), %eax - test $15, LEN8 - cmovnz %eax, LEN -.endif - // Load the AES key length: 16 (AES-128), 24 (AES-192), or 32 (AES-256). - movl 480(KEY), KEYLEN + movl OFFSETOF_KEYLEN(KEY), KEYLEN // Setup the pointer to the round keys and cache as many as possible. _setup_round_keys \enc @@ -635,7 +616,7 @@ // Compute the first set of tweaks TWEAK[0-3]. _compute_first_set_of_tweaks - add $-4*VL, LEN // shorter than 'sub 4*VL' when VL=32 + sub $4*VL/16, NBLOCKS jl .Lhandle_remainder\@ .Lmain_loop\@: @@ -715,13 +696,13 @@ sub $-4*VL, SRC // shorter than 'add 4*VL' when VL=32 sub $-4*VL, DST - add $-4*VL, LEN + sub $4*VL/16, NBLOCKS jge .Lmain_loop\@ // Check for the uncommon case where the data length isn't a multiple of // 4*VL. Handle it out-of-line in order to optimize for the common // case. In the common case, just fall through to the ret. - test $4*VL-1, LEN8 + test $(4*VL/16)-1, NBLOCKS32 jnz .Lhandle_remainder\@ .Ldone\@: // Store the next tweak back to *TWEAK to support continuation calls. @@ -733,9 +714,9 @@ .Lhandle_remainder\@: - // En/decrypt any remaining full blocks, one vector at a time. + // En/decrypt any remaining blocks, one vector at a time. .if VL > 16 - add $3*VL, LEN // Undo extra sub of 4*VL, then sub VL. + add $3*VL/16, NBLOCKS32 jl .Lvec_at_a_time_done\@ .Lvec_at_a_time\@: _vmovdqu (SRC), V0 @@ -744,16 +725,16 @@ _next_tweakvec TWEAK0, V0, V1, TWEAK0 add $VL, SRC add $VL, DST - sub $VL, LEN + sub $VL/16, NBLOCKS32 jge .Lvec_at_a_time\@ .Lvec_at_a_time_done\@: - add $VL-16, LEN // Undo extra sub of VL, then sub 16. + add $VL/16, NBLOCKS32 .else - add $4*VL-16, LEN // Undo extra sub of 4*VL, then sub 16. + add $4*VL/16, NBLOCKS32 .endif - // En/decrypt any remaining full blocks, one at a time. - jl .Lblock_at_a_time_done\@ + // En/decrypt any remaining blocks, one at a time. + jz .Ldone\@ .Lblock_at_a_time\@: vmovdqu (SRC), %xmm0 _aes_crypt \enc, _XMM, TWEAK0_XMM, %xmm0, tmp=%xmm1 @@ -761,92 +742,26 @@ _next_tweak TWEAK0_XMM, %xmm0, TWEAK0_XMM add $16, SRC add $16, DST - sub $16, LEN - jge .Lblock_at_a_time\@ -.Lblock_at_a_time_done\@: - add $16, LEN // Undo the extra sub of 16. - // Now 0 <= LEN <= 15. If LEN is zero, we're done. - jz .Ldone\@ - - // Otherwise 1 <= LEN <= 15, but the real remaining length is 16 + LEN. - // Do ciphertext stealing to process the last 16 + LEN bytes. - -.if \enc - // If encrypting, the main loop already encrypted the last full block to - // create the CTS intermediate ciphertext. Prepare for the rest of CTS - // by rewinding the pointers and loading the intermediate ciphertext. - sub $16, SRC - sub $16, DST - vmovdqu (DST), %xmm0 -.else - // If decrypting, the main loop didn't decrypt the last full block - // because CTS decryption uses the last two tweaks in reverse order. - // Do it now by advancing the tweak and decrypting the last full block. - _next_tweak TWEAK0_XMM, %xmm0, TWEAK1_XMM - vmovdqu (SRC), %xmm0 - _aes_crypt \enc, _XMM, TWEAK1_XMM, %xmm0, tmp=%xmm1 -.endif - -.if USE_AVX512 - // Create a mask that has the first LEN bits set. - mov $-1, %r9d - bzhi LEN, %r9d, %r9d - kmovd %r9d, %k1 - - // Swap the first LEN bytes of the en/decryption of the last full block - // with the partial block. Note that to support in-place en/decryption, - // the load from the src partial block must happen before the store to - // the dst partial block. - vmovdqa %xmm0, %xmm1 - vmovdqu8 16(SRC), %xmm0{%k1} - vmovdqu8 %xmm1, 16(DST){%k1} -.else - lea .Lcts_permute_table(%rip), %r9 - - // Load the src partial block, left-aligned. Note that to support - // in-place en/decryption, this must happen before the store to the dst - // partial block. - vmovdqu (SRC, LEN64, 1), %xmm1 - - // Shift the first LEN bytes of the en/decryption of the last full block - // to the end of a register, then store it to DST+LEN. This stores the - // dst partial block. It also writes to the second part of the dst last - // full block, but that part is overwritten later. - vpshufb (%r9, LEN64, 1), %xmm0, %xmm2 - vmovdqu %xmm2, (DST, LEN64, 1) - - // Make xmm3 contain [16-LEN,16-LEN+1,...,14,15,0x80,0x80,...]. - sub LEN64, %r9 - vmovdqu 32(%r9), %xmm3 - - // Shift the src partial block to the beginning of its register. - vpshufb %xmm3, %xmm1, %xmm1 - - // Do a blend to generate the src partial block followed by the second - // part of the en/decryption of the last full block. - vpblendvb %xmm3, %xmm0, %xmm1, %xmm0 -.endif - // En/decrypt again and store the last full block. - _aes_crypt \enc, _XMM, TWEAK0_XMM, %xmm0, tmp=%xmm1 - vmovdqu %xmm0, (DST) + dec NBLOCKS32 + jnz .Lblock_at_a_time\@ jmp .Ldone\@ .endm -// void aes_xts_encrypt_iv(const struct crypto_aes_ctx *tweak_key, +// void aes_xts_encrypt_iv(const struct aes_enckey *tweak_key, // u8 iv[AES_BLOCK_SIZE]); // // Encrypt |iv| using the AES key |tweak_key| to get the first tweak. Assumes // that the CPU supports AES-NI and AVX, but not necessarily VAES or AVX512. -SYM_TYPED_FUNC_START(aes_xts_encrypt_iv) +SYM_FUNC_START(aes_xts_encrypt_iv) .set TWEAK_KEY, %rdi .set IV, %rsi .set KEYLEN, %eax .set KEYLEN64, %rax vmovdqu (IV), %xmm0 - vpxor (TWEAK_KEY), %xmm0, %xmm0 - movl 480(TWEAK_KEY), KEYLEN - lea -16(TWEAK_KEY, KEYLEN64, 4), TWEAK_KEY + vpxor OFFSETOF_RNDKEYS(TWEAK_KEY), %xmm0, %xmm0 + movl OFFSETOF_KEYLEN(TWEAK_KEY), KEYLEN + lea OFFSETOF_RNDKEYS-16(TWEAK_KEY, KEYLEN64, 4), TWEAK_KEY cmp $24, KEYLEN jl .Lencrypt_iv_aes128 je .Lencrypt_iv_aes192 @@ -867,39 +782,37 @@ SYM_FUNC_END(aes_xts_encrypt_iv) // Below are the actual AES-XTS encryption and decryption functions, // instantiated from the above macro. They all have the following prototype: // -// void (*xts_crypt_func)(const struct crypto_aes_ctx *key, -// const u8 *src, u8 *dst, int len, -// u8 tweak[AES_BLOCK_SIZE]); +// void aes_xts_{en,de}crypt_{cpufeature}(u8 *dst, const u8 *src, long nblocks, +// u8 tweak[AES_BLOCK_SIZE], +// const struct aes_key *key); // -// |key| is the data key. |tweak| contains the next tweak; the encryption of -// the original IV with the tweak key was already done. This function supports -// incremental computation, but |len| must always be >= 16 (AES_BLOCK_SIZE), and -// |len| must be a multiple of 16 except on the last call. If |len| is a -// multiple of 16, then this function updates |tweak| to contain the next tweak. +// `tweak` must have already been encrypted by the tweak key; `key` is just the +// main key. `tweak` is updated to contain the next tweak, allowing for +// incremental computation. .set VL, 16 .set USE_AVX512, 0 -SYM_TYPED_FUNC_START(aes_xts_encrypt_aesni_avx) +SYM_FUNC_START(aes_xts_encrypt_aesni_avx) _aes_xts_crypt 1 SYM_FUNC_END(aes_xts_encrypt_aesni_avx) -SYM_TYPED_FUNC_START(aes_xts_decrypt_aesni_avx) +SYM_FUNC_START(aes_xts_decrypt_aesni_avx) _aes_xts_crypt 0 SYM_FUNC_END(aes_xts_decrypt_aesni_avx) .set VL, 32 .set USE_AVX512, 0 -SYM_TYPED_FUNC_START(aes_xts_encrypt_vaes_avx2) +SYM_FUNC_START(aes_xts_encrypt_vaes_avx2) _aes_xts_crypt 1 SYM_FUNC_END(aes_xts_encrypt_vaes_avx2) -SYM_TYPED_FUNC_START(aes_xts_decrypt_vaes_avx2) +SYM_FUNC_START(aes_xts_decrypt_vaes_avx2) _aes_xts_crypt 0 SYM_FUNC_END(aes_xts_decrypt_vaes_avx2) .set VL, 64 .set USE_AVX512, 1 -SYM_TYPED_FUNC_START(aes_xts_encrypt_vaes_avx512) +SYM_FUNC_START(aes_xts_encrypt_vaes_avx512) _aes_xts_crypt 1 SYM_FUNC_END(aes_xts_encrypt_vaes_avx512) -SYM_TYPED_FUNC_START(aes_xts_decrypt_vaes_avx512) +SYM_FUNC_START(aes_xts_decrypt_vaes_avx512) _aes_xts_crypt 0 SYM_FUNC_END(aes_xts_decrypt_vaes_avx512) diff --git a/lib/crypto/x86/aes.h b/lib/crypto/x86/aes.h index 8ed247ddb8f201..b555290ab0755b 100644 --- a/lib/crypto/x86/aes.h +++ b/lib/crypto/x86/aes.h @@ -16,6 +16,7 @@ static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_vaes_avx512); static_assert(offsetof(struct aes_enckey, len) == 0); static_assert(offsetof(struct aes_enckey, nrounds) == 4); static_assert(offsetof(struct aes_enckey, k.rndkeys) == 16); +static_assert(offsetof(struct aes_key, len) == 0); static_assert(offsetof(struct aes_key, nrounds) == 4); static_assert(offsetof(struct aes_key, k.rndkeys) == 16); static_assert(offsetof(struct aes_key, inv_k.inv_rndkeys) == 256); @@ -312,6 +313,26 @@ void aes_xts_encrypt_aesni(u8 *dst, const u8 *src, long nblocks, u8 tweak[AES_BLOCK_SIZE], const struct aes_key *key); void aes_xts_decrypt_aesni(u8 *dst, const u8 *src, long nblocks, u8 tweak[AES_BLOCK_SIZE], const struct aes_key *key); +void aes_xts_encrypt_iv(const struct aes_enckey *tweak_key, + u8 iv[AES_BLOCK_SIZE]); +void aes_xts_encrypt_aesni_avx(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_decrypt_aesni_avx(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_encrypt_vaes_avx2(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_decrypt_vaes_avx2(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_encrypt_vaes_avx512(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_decrypt_vaes_avx512(u8 *dst, const u8 *src, long nblocks, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); /* len is always a positive multiple of AES_BLOCK_SIZE here. */ static __always_inline bool @@ -324,12 +345,46 @@ aes_xts_crypt_x86(u8 *dst, const u8 *src, size_t len, u8 tweak[AES_BLOCK_SIZE], return false; kernel_fpu_begin(); - if (!cont) - aes_encrypt_aesni(tweak, tweak, &key->tweak_key); - if (enc) - aes_xts_encrypt_aesni(dst, src, nblocks, tweak, &key->main_key); - else - aes_xts_decrypt_aesni(dst, src, nblocks, tweak, &key->main_key); + if (IS_ENABLED(CONFIG_X86_64) && + static_branch_likely(&have_vaes_avx512)) { + if (!cont) + aes_xts_encrypt_iv(&key->tweak_key, tweak); + if (enc) + aes_xts_encrypt_vaes_avx512(dst, src, nblocks, tweak, + &key->main_key); + else + aes_xts_decrypt_vaes_avx512(dst, src, nblocks, tweak, + &key->main_key); + } else if (IS_ENABLED(CONFIG_X86_64) && + static_branch_likely(&have_vaes_avx2)) { + if (!cont) + aes_xts_encrypt_iv(&key->tweak_key, tweak); + if (enc) + aes_xts_encrypt_vaes_avx2(dst, src, nblocks, tweak, + &key->main_key); + else + aes_xts_decrypt_vaes_avx2(dst, src, nblocks, tweak, + &key->main_key); + } else if (IS_ENABLED(CONFIG_X86_64) && + static_branch_likely(&have_aesni_avx)) { + if (!cont) + aes_xts_encrypt_iv(&key->tweak_key, tweak); + if (enc) + aes_xts_encrypt_aesni_avx(dst, src, nblocks, tweak, + &key->main_key); + else + aes_xts_decrypt_aesni_avx(dst, src, nblocks, tweak, + &key->main_key); + } else { + if (!cont) + aes_encrypt_aesni(tweak, tweak, &key->tweak_key); + if (enc) + aes_xts_encrypt_aesni(dst, src, nblocks, tweak, + &key->main_key); + else + aes_xts_decrypt_aesni(dst, src, nblocks, tweak, + &key->main_key); + } kernel_fpu_end(); return true; } From fd5ddddd00f8932658a2d55e321aebb8486a9e5a Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:06 -0700 Subject: [PATCH 1132/2286] lib/crypto: riscv/aes: Copy aes-macros.S to library Copy arch/riscv/crypto/aes-macros.S to lib/crypto/riscv/aes-macros.S and make lib/crypto/riscv/aes-riscv64-zvkned.S include the latter copy. This makes it possible to change these macros without interfering with the remaining code in arch/riscv/crypto/. Of course, the copy in arch/riscv/crypto/ will be removed once the rest of the AES code there is migrated to the library. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-16-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/riscv/aes-macros.S | 166 ++++++++++++++++++++++++++ lib/crypto/riscv/aes-riscv64-zvkned.S | 2 +- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 lib/crypto/riscv/aes-macros.S diff --git a/lib/crypto/riscv/aes-macros.S b/lib/crypto/riscv/aes-macros.S new file mode 100644 index 00000000000000..1384164621a506 --- /dev/null +++ b/lib/crypto/riscv/aes-macros.S @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause */ +// +// This file is dual-licensed, meaning that you can use it under your +// choice of either of the following two licenses: +// +// Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. +// +// Licensed under the Apache License 2.0 (the "License"). You can obtain +// a copy in the file LICENSE in the source distribution or at +// https://www.openssl.org/source/license.html +// +// or +// +// Copyright (c) 2023, Christoph Müllner +// Copyright (c) 2023, Phoebe Chen +// Copyright (c) 2023, Jerry Shih +// Copyright 2024 Google LLC +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file contains macros that are shared by the other aes-*.S files. The +// generated code of these macros depends on the following RISC-V extensions: +// - RV64I +// - RISC-V Vector ('V') with VLEN >= 128 +// - RISC-V Vector AES block cipher extension ('Zvkned') + +// Loads the AES round keys from \keyp into vector registers and jumps to code +// specific to the length of the key. Specifically: +// - If AES-128, loads round keys into v1-v11 and jumps to \label128. +// - If AES-192, loads round keys into v1-v13 and jumps to \label192. +// - If AES-256, loads round keys into v1-v15 and continues onwards. +// +// Also sets vl=4 and vtype=e32,m1,ta,ma. Clobbers t0 and t1. +.macro aes_begin keyp, label128, label192, key_len +.ifb \key_len + lwu t0, 480(\keyp) // t0 = key length in bytes +.endif + li t1, 24 // t1 = key length for AES-192 + vsetivli zero, 4, e32, m1, ta, ma + vle32.v v1, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v2, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v3, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v4, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v5, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v6, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v7, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v8, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v9, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v10, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v11, (\keyp) +.ifb \key_len + blt t0, t1, \label128 // If AES-128, goto label128. +.else + blt \key_len, t1, \label128 // If AES-128, goto label128. +.endif + addi \keyp, \keyp, 16 + vle32.v v12, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v13, (\keyp) +.ifb \key_len + beq t0, t1, \label192 // If AES-192, goto label192. +.else + beq \key_len, t1, \label192 // If AES-192, goto label192. +.endif + // Else, it's AES-256. + addi \keyp, \keyp, 16 + vle32.v v14, (\keyp) + addi \keyp, \keyp, 16 + vle32.v v15, (\keyp) +.endm + +// Encrypts \data using zvkned instructions, using the round keys loaded into +// v1-v11 (for AES-128), v1-v13 (for AES-192), or v1-v15 (for AES-256). \keylen +// is the AES key length in bits. vl and vtype must already be set +// appropriately. Note that if vl > 4, multiple blocks are encrypted. +.macro aes_encrypt data, keylen + vaesz.vs \data, v1 + vaesem.vs \data, v2 + vaesem.vs \data, v3 + vaesem.vs \data, v4 + vaesem.vs \data, v5 + vaesem.vs \data, v6 + vaesem.vs \data, v7 + vaesem.vs \data, v8 + vaesem.vs \data, v9 + vaesem.vs \data, v10 +.if \keylen == 128 + vaesef.vs \data, v11 +.elseif \keylen == 192 + vaesem.vs \data, v11 + vaesem.vs \data, v12 + vaesef.vs \data, v13 +.else + vaesem.vs \data, v11 + vaesem.vs \data, v12 + vaesem.vs \data, v13 + vaesem.vs \data, v14 + vaesef.vs \data, v15 +.endif +.endm + +// Same as aes_encrypt, but decrypts instead of encrypts. +.macro aes_decrypt data, keylen +.if \keylen == 128 + vaesz.vs \data, v11 +.elseif \keylen == 192 + vaesz.vs \data, v13 + vaesdm.vs \data, v12 + vaesdm.vs \data, v11 +.else + vaesz.vs \data, v15 + vaesdm.vs \data, v14 + vaesdm.vs \data, v13 + vaesdm.vs \data, v12 + vaesdm.vs \data, v11 +.endif + vaesdm.vs \data, v10 + vaesdm.vs \data, v9 + vaesdm.vs \data, v8 + vaesdm.vs \data, v7 + vaesdm.vs \data, v6 + vaesdm.vs \data, v5 + vaesdm.vs \data, v4 + vaesdm.vs \data, v3 + vaesdm.vs \data, v2 + vaesdf.vs \data, v1 +.endm + +// Expands to aes_encrypt or aes_decrypt according to \enc, which is 1 or 0. +.macro aes_crypt data, enc, keylen +.if \enc + aes_encrypt \data, \keylen +.else + aes_decrypt \data, \keylen +.endif +.endm diff --git a/lib/crypto/riscv/aes-riscv64-zvkned.S b/lib/crypto/riscv/aes-riscv64-zvkned.S index 0d988bc3d37b2a..7a52ea6c669dfa 100644 --- a/lib/crypto/riscv/aes-riscv64-zvkned.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned.S @@ -48,7 +48,7 @@ .text .option arch, +zvkned -#include "../../arch/riscv/crypto/aes-macros.S" +#include "aes-macros.S" #define RNDKEYS a0 #define KEY_LEN a1 From f50ca2d2be5422e6e96086a1b38b8b52dd9ee866 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:07 -0700 Subject: [PATCH 1133/2286] lib/crypto: riscv/aes: Pass key struct to assembly code Make the assembly code take the AES key struct directly, rather than the round keys pointer and key length separately. Make the aes_begin macro assume this convention, and remove support for the legacy 'struct crypto_aes_ctx' from it since that isn't used here. This aligns with the convention that is being used (and will continue to be used) for the AES modes, it makes the C glue code slightly simpler, and it avoids the unnecessary shuffling around of arguments. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-17-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/riscv/aes-macros.S | 25 ++++++++++--------------- lib/crypto/riscv/aes-riscv64-zvkned.S | 17 ++++++++--------- lib/crypto/riscv/aes.h | 18 ++++++++++++------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/crypto/riscv/aes-macros.S b/lib/crypto/riscv/aes-macros.S index 1384164621a506..1ab18e3584742b 100644 --- a/lib/crypto/riscv/aes-macros.S +++ b/lib/crypto/riscv/aes-macros.S @@ -44,17 +44,20 @@ // - RISC-V Vector ('V') with VLEN >= 128 // - RISC-V Vector AES block cipher extension ('Zvkned') -// Loads the AES round keys from \keyp into vector registers and jumps to code -// specific to the length of the key. Specifically: +// Offsets in struct aes_enckey +#define OFFSETOF_KEYLEN 0 +#define OFFSETOF_RNDKEYS 16 + +// Loads the AES round keys from the struct aes_enckey \keyp into vector +// registers and jumps to code specific to the length of the key. Specifically: // - If AES-128, loads round keys into v1-v11 and jumps to \label128. // - If AES-192, loads round keys into v1-v13 and jumps to \label192. // - If AES-256, loads round keys into v1-v15 and continues onwards. // -// Also sets vl=4 and vtype=e32,m1,ta,ma. Clobbers t0 and t1. -.macro aes_begin keyp, label128, label192, key_len -.ifb \key_len - lwu t0, 480(\keyp) // t0 = key length in bytes -.endif +// Also sets vl=4 and vtype=e32,m1,ta,ma. Clobbers \keyp, t0, and t1. +.macro aes_begin keyp, label128, label192 + lwu t0, OFFSETOF_KEYLEN(\keyp) // t0 = key length in bytes + addi \keyp, \keyp, OFFSETOF_RNDKEYS li t1, 24 // t1 = key length for AES-192 vsetivli zero, 4, e32, m1, ta, ma vle32.v v1, (\keyp) @@ -78,20 +81,12 @@ vle32.v v10, (\keyp) addi \keyp, \keyp, 16 vle32.v v11, (\keyp) -.ifb \key_len blt t0, t1, \label128 // If AES-128, goto label128. -.else - blt \key_len, t1, \label128 // If AES-128, goto label128. -.endif addi \keyp, \keyp, 16 vle32.v v12, (\keyp) addi \keyp, \keyp, 16 vle32.v v13, (\keyp) -.ifb \key_len beq t0, t1, \label192 // If AES-192, goto label192. -.else - beq \key_len, t1, \label192 // If AES-192, goto label192. -.endif // Else, it's AES-256. addi \keyp, \keyp, 16 vle32.v v14, (\keyp) diff --git a/lib/crypto/riscv/aes-riscv64-zvkned.S b/lib/crypto/riscv/aes-riscv64-zvkned.S index 7a52ea6c669dfa..fb35f694b5ac16 100644 --- a/lib/crypto/riscv/aes-riscv64-zvkned.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned.S @@ -50,10 +50,9 @@ #include "aes-macros.S" -#define RNDKEYS a0 -#define KEY_LEN a1 -#define OUTP a2 -#define INP a3 +#define KEYP a0 +#define OUTP a1 +#define INP a2 .macro __aes_crypt_zvkned enc, keybits vle32.v v16, (INP) @@ -63,7 +62,7 @@ .endm .macro aes_crypt_zvkned enc - aes_begin RNDKEYS, 128f, 192f, KEY_LEN + aes_begin KEYP, 128f, 192f __aes_crypt_zvkned \enc, 256 128: __aes_crypt_zvkned \enc, 128 @@ -71,14 +70,14 @@ __aes_crypt_zvkned \enc, 192 .endm -// void aes_encrypt_zvkned(const u32 rndkeys[], int key_len, -// u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +// void aes_encrypt_zvkned(const struct aes_enckey *key, u8 out[AES_BLOCK_SIZE], +// const u8 in[AES_BLOCK_SIZE]); SYM_FUNC_START(aes_encrypt_zvkned) aes_crypt_zvkned 1 SYM_FUNC_END(aes_encrypt_zvkned) -// void aes_decrypt_zvkned(const u32 rndkeys[], int key_len, -// u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +// void aes_decrypt_zvkned(const struct aes_key *key, u8 out[AES_BLOCK_SIZE], +// const u8 in[AES_BLOCK_SIZE]); SYM_FUNC_START(aes_decrypt_zvkned) aes_crypt_zvkned 0 SYM_FUNC_END(aes_decrypt_zvkned) diff --git a/lib/crypto/riscv/aes.h b/lib/crypto/riscv/aes.h index 0b26f58faf2bab..9de9dbd1e8871f 100644 --- a/lib/crypto/riscv/aes.h +++ b/lib/crypto/riscv/aes.h @@ -10,10 +10,16 @@ static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_zvkned); -void aes_encrypt_zvkned(const u32 rndkeys[], int key_len, - u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); -void aes_decrypt_zvkned(const u32 rndkeys[], int key_len, - u8 out[AES_BLOCK_SIZE], const u8 in[AES_BLOCK_SIZE]); +/* The assembly code assumes the following offsets. */ +static_assert(offsetof(struct aes_enckey, len) == 0); +static_assert(offsetof(struct aes_enckey, k.rndkeys) == 16); +static_assert(offsetof(struct aes_key, len) == 0); +static_assert(offsetof(struct aes_key, k.rndkeys) == 16); + +void aes_encrypt_zvkned(const struct aes_enckey *key, u8 out[AES_BLOCK_SIZE], + const u8 in[AES_BLOCK_SIZE]); +void aes_decrypt_zvkned(const struct aes_key *key, u8 out[AES_BLOCK_SIZE], + const u8 in[AES_BLOCK_SIZE]); static void aes_preparekey_arch(union aes_enckey_arch *k, union aes_invkey_arch *inv_k, @@ -29,7 +35,7 @@ static void aes_encrypt_arch(const struct aes_enckey *key, { if (static_branch_likely(&have_zvkned) && likely(may_use_simd())) { kernel_vector_begin(); - aes_encrypt_zvkned(key->k.rndkeys, key->len, out, in); + aes_encrypt_zvkned(key, out, in); kernel_vector_end(); } else { aes_encrypt_generic(key->k.rndkeys, key->nrounds, out, in); @@ -46,7 +52,7 @@ static void aes_decrypt_arch(const struct aes_key *key, */ if (static_branch_likely(&have_zvkned) && likely(may_use_simd())) { kernel_vector_begin(); - aes_decrypt_zvkned(key->k.rndkeys, key->len, out, in); + aes_decrypt_zvkned(key, out, in); kernel_vector_end(); } else { aes_decrypt_generic(key->inv_k.inv_rndkeys, key->nrounds, From 00e767a6d2f2ecce45ed11d00d39257c54859594 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:08 -0700 Subject: [PATCH 1134/2286] lib/crypto: riscv/aes-ecb: Migrate optimized code into library Instead of exposing the riscv-optimized AES-ECB code via a riscv-specific crypto_skcipher algorithm, just implement the AES-ECB library functions. This is simpler, it makes the AES-ECB library functions be riscv-optimized, and it also fixes the longstanding issue where the riscv-optimized AES-ECB code was disabled by default. AES-ECB support still remains available through crypto_skcipher via crypto/aes.c, but individual architectures no longer need to handle it. To match what the library expects, update the assembly functions to operate on struct aes_enckey or struct aes_key rather than struct crypto_aes_ctx, and adjust the argument order. Bump up the priority of the corresponding library-based algorithm on riscv now that it no longer has to be lower than arch/riscv/crypto/. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-18-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/riscv/crypto/Kconfig | 4 +- arch/riscv/crypto/aes-riscv64-glue.c | 61 +------------------------- arch/riscv/crypto/aes-riscv64-zvkned.S | 39 ---------------- crypto/aes.c | 3 +- lib/crypto/riscv/aes-riscv64-zvkned.S | 51 +++++++++++++++++++++ lib/crypto/riscv/aes.h | 33 ++++++++++++++ 6 files changed, 89 insertions(+), 102 deletions(-) diff --git a/arch/riscv/crypto/Kconfig b/arch/riscv/crypto/Kconfig index 6905232ddb03cb..84c41824b43338 100644 --- a/arch/riscv/crypto/Kconfig +++ b/arch/riscv/crypto/Kconfig @@ -3,13 +3,13 @@ menu "Accelerated Cryptographic Algorithms for CPU (riscv)" config CRYPTO_AES_RISCV64 - tristate "Ciphers: AES, modes: ECB, CBC, CTS, CTR, XTS" + tristate "Ciphers: AES, modes: CBC, CTS, CTR, XTS" depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_LIB_AES select CRYPTO_SKCIPHER help - Length-preserving ciphers: AES with ECB, CBC, CTS, CTR, XTS + Length-preserving ciphers: AES with CBC, CTS, CTR, XTS Architecture: riscv64 using: - Zvkned vector crypto extension diff --git a/arch/riscv/crypto/aes-riscv64-glue.c b/arch/riscv/crypto/aes-riscv64-glue.c index bbd920c9e29d1a..f7c492dcfd57b2 100644 --- a/arch/riscv/crypto/aes-riscv64-glue.c +++ b/arch/riscv/crypto/aes-riscv64-glue.c @@ -22,11 +22,6 @@ #include #include -asmlinkage void aes_ecb_encrypt_zvkned(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len); -asmlinkage void aes_ecb_decrypt_zvkned(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len); - asmlinkage void aes_cbc_encrypt_zvkned(const struct crypto_aes_ctx *key, const u8 *in, u8 *out, size_t len, u8 iv[AES_BLOCK_SIZE]); @@ -86,44 +81,6 @@ static int riscv64_aes_setkey_skcipher(struct crypto_skcipher *tfm, return riscv64_aes_setkey(ctx, key, keylen); } -/* AES-ECB */ - -static inline int riscv64_aes_ecb_crypt(struct skcipher_request *req, bool enc) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *ctx = crypto_skcipher_ctx(tfm); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { - kernel_vector_begin(); - if (enc) - aes_ecb_encrypt_zvkned(ctx, walk.src.virt.addr, - walk.dst.virt.addr, - nbytes & ~(AES_BLOCK_SIZE - 1)); - else - aes_ecb_decrypt_zvkned(ctx, walk.src.virt.addr, - walk.dst.virt.addr, - nbytes & ~(AES_BLOCK_SIZE - 1)); - kernel_vector_end(); - err = skcipher_walk_done(&walk, nbytes & (AES_BLOCK_SIZE - 1)); - } - - return err; -} - -static int riscv64_aes_ecb_encrypt(struct skcipher_request *req) -{ - return riscv64_aes_ecb_crypt(req, true); -} - -static int riscv64_aes_ecb_decrypt(struct skcipher_request *req) -{ - return riscv64_aes_ecb_crypt(req, false); -} - /* AES-CBC */ static int riscv64_aes_cbc_crypt(struct skcipher_request *req, bool enc) @@ -411,21 +368,6 @@ static int riscv64_aes_xts_decrypt(struct skcipher_request *req) static struct skcipher_alg riscv64_zvkned_aes_skcipher_algs[] = { { - .setkey = riscv64_aes_setkey_skcipher, - .encrypt = riscv64_aes_ecb_encrypt, - .decrypt = riscv64_aes_ecb_decrypt, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .walksize = 8 * AES_BLOCK_SIZE, /* matches LMUL=8 */ - .base = { - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = sizeof(struct crypto_aes_ctx), - .cra_priority = 300, - .cra_name = "ecb(aes)", - .cra_driver_name = "ecb-aes-riscv64-zvkned", - .cra_module = THIS_MODULE, - }, - }, { .setkey = riscv64_aes_setkey_skcipher, .encrypt = riscv64_aes_cbc_encrypt, .decrypt = riscv64_aes_cbc_decrypt, @@ -555,11 +497,10 @@ static void __exit riscv64_aes_mod_exit(void) module_init(riscv64_aes_mod_init); module_exit(riscv64_aes_mod_exit); -MODULE_DESCRIPTION("AES-ECB/CBC/CTS/CTR/XTS (RISC-V accelerated)"); +MODULE_DESCRIPTION("AES-CBC/CTS/CTR/XTS (RISC-V accelerated)"); MODULE_AUTHOR("Jerry Shih "); MODULE_LICENSE("GPL"); MODULE_ALIAS_CRYPTO("aes"); -MODULE_ALIAS_CRYPTO("ecb(aes)"); MODULE_ALIAS_CRYPTO("cbc(aes)"); MODULE_ALIAS_CRYPTO("cts(cbc(aes))"); MODULE_ALIAS_CRYPTO("ctr(aes)"); diff --git a/arch/riscv/crypto/aes-riscv64-zvkned.S b/arch/riscv/crypto/aes-riscv64-zvkned.S index d0fc4581a38009..00f8a06596d39b 100644 --- a/arch/riscv/crypto/aes-riscv64-zvkned.S +++ b/arch/riscv/crypto/aes-riscv64-zvkned.S @@ -56,45 +56,6 @@ #define LEN a3 #define IVP a4 -.macro __aes_ecb_crypt enc, keylen - srli t0, LEN, 2 - // t0 is the remaining length in 32-bit words. It's a multiple of 4. -1: - vsetvli t1, t0, e32, m8, ta, ma - sub t0, t0, t1 // Subtract number of words processed - slli t1, t1, 2 // Words to bytes - vle32.v v16, (INP) - aes_crypt v16, \enc, \keylen - vse32.v v16, (OUTP) - add INP, INP, t1 - add OUTP, OUTP, t1 - bnez t0, 1b - - ret -.endm - -.macro aes_ecb_crypt enc - aes_begin KEYP, 128f, 192f - __aes_ecb_crypt \enc, 256 -128: - __aes_ecb_crypt \enc, 128 -192: - __aes_ecb_crypt \enc, 192 -.endm - -// void aes_ecb_encrypt_zvkned(const struct crypto_aes_ctx *key, -// const u8 *in, u8 *out, size_t len); -// -// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). -SYM_FUNC_START(aes_ecb_encrypt_zvkned) - aes_ecb_crypt 1 -SYM_FUNC_END(aes_ecb_encrypt_zvkned) - -// Same prototype and calling convention as the encryption function -SYM_FUNC_START(aes_ecb_decrypt_zvkned) - aes_ecb_crypt 0 -SYM_FUNC_END(aes_ecb_decrypt_zvkned) - .macro aes_cbc_encrypt keylen vle32.v v16, (IVP) // Load IV 1: diff --git a/crypto/aes.c b/crypto/aes.c index f8d28c0f33205e..d6742234373594 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -610,7 +610,8 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "ecb(aes)", .base.cra_driver_name = "ecb-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_X86)) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/riscv/aes-riscv64-zvkned.S b/lib/crypto/riscv/aes-riscv64-zvkned.S index fb35f694b5ac16..3700028e62ea48 100644 --- a/lib/crypto/riscv/aes-riscv64-zvkned.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned.S @@ -81,3 +81,54 @@ SYM_FUNC_END(aes_encrypt_zvkned) SYM_FUNC_START(aes_decrypt_zvkned) aes_crypt_zvkned 0 SYM_FUNC_END(aes_decrypt_zvkned) + +#undef KEYP +#undef OUTP +#undef INP + +#define DST a0 +#define SRC a1 +#define LEN a2 +#define KEYP a3 + +.macro __aes_ecb_crypt enc, keylen + srli t0, LEN, 2 + // t0 is the remaining length in 32-bit words. It's a multiple of 4. +1: + vsetvli t1, t0, e32, m8, ta, ma + sub t0, t0, t1 // Subtract number of words processed + slli t1, t1, 2 // Words to bytes + vle32.v v16, (SRC) + aes_crypt v16, \enc, \keylen + vse32.v v16, (DST) + add SRC, SRC, t1 + add DST, DST, t1 + bnez t0, 1b + + ret +.endm + +.macro aes_ecb_crypt enc + aes_begin KEYP, 128f, 192f + __aes_ecb_crypt \enc, 256 +128: + __aes_ecb_crypt \enc, 128 +192: + __aes_ecb_crypt \enc, 192 +.endm + +// void aes_ecb_encrypt_zvkned(u8 *dst, const u8 *src, size_t len, +// const struct aes_enckey *key); +// +// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). +SYM_FUNC_START(aes_ecb_encrypt_zvkned) + aes_ecb_crypt 1 +SYM_FUNC_END(aes_ecb_encrypt_zvkned) + +// void aes_ecb_decrypt_zvkned(u8 *dst, const u8 *src, size_t len, +// const struct aes_key *key); +// +// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). +SYM_FUNC_START(aes_ecb_decrypt_zvkned) + aes_ecb_crypt 0 +SYM_FUNC_END(aes_ecb_decrypt_zvkned) diff --git a/lib/crypto/riscv/aes.h b/lib/crypto/riscv/aes.h index 9de9dbd1e8871f..5b36d0147d93c5 100644 --- a/lib/crypto/riscv/aes.h +++ b/lib/crypto/riscv/aes.h @@ -60,6 +60,39 @@ static void aes_decrypt_arch(const struct aes_key *key, } } +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_ECB) +void aes_ecb_encrypt_zvkned(u8 *dst, const u8 *src, size_t len, + const struct aes_enckey *key); +void aes_ecb_decrypt_zvkned(u8 *dst, const u8 *src, size_t len, + const struct aes_key *key); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_ecb_encrypt_arch aes_ecb_encrypt_arch +static bool aes_ecb_encrypt_arch(u8 *dst, const u8 *src, size_t len, + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + aes_ecb_encrypt_zvkned(dst, src, len, key); + kernel_vector_end(); + return true; +} + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_ecb_decrypt_arch aes_ecb_decrypt_arch +static bool aes_ecb_decrypt_arch(u8 *dst, const u8 *src, size_t len, + const struct aes_key *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + aes_ecb_decrypt_zvkned(dst, src, len, key); + kernel_vector_end(); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_ECB */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From bd888e3dc9e06731eb78e4ad142c64c82a220edf Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:09 -0700 Subject: [PATCH 1135/2286] lib/crypto: riscv/aes-cbc: Migrate optimized code into library Instead of exposing the riscv-optimized AES-CBC and AES-CBC-CTS code via a riscv-specific crypto_skcipher algorithm, just implement the AES-CBC and AES-CBC-CTS library functions. This is simpler, it makes the AES-CBC and AES-CBC-CTS library functions be riscv-optimized, and it also fixes the longstanding issue where the riscv-optimized AES-CBC and AES-CBC-CTS code was disabled by default. AES-CBC and AES-CBC-CTS support still remains available through crypto_skcipher via crypto/aes.c, but individual architectures no longer need to handle it. To match what the library expects, update the assembly functions to operate on struct aes_enckey or struct aes_key rather than struct crypto_aes_ctx, and adjust the argument order. Remove the single-block special case from the assembly code, since it's already handled by lib/crypto/aes.c before calling aes_cbc_cts_*_arch(). Bump up the priority of the corresponding library-based algorithms on riscv now that they no longer have to be lower than arch/riscv/crypto/. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-19-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/riscv/crypto/Kconfig | 4 +- arch/riscv/crypto/Makefile | 2 +- arch/riscv/crypto/aes-riscv64-glue.c | 167 +-------------- arch/riscv/crypto/aes-riscv64-zvkned.S | 273 ------------------------- crypto/aes.c | 6 +- lib/crypto/riscv/aes-riscv64-zvkned.S | 235 +++++++++++++++++++++ lib/crypto/riscv/aes.h | 68 ++++++ 7 files changed, 312 insertions(+), 443 deletions(-) delete mode 100644 arch/riscv/crypto/aes-riscv64-zvkned.S diff --git a/arch/riscv/crypto/Kconfig b/arch/riscv/crypto/Kconfig index 84c41824b43338..0a3f87ad384e5e 100644 --- a/arch/riscv/crypto/Kconfig +++ b/arch/riscv/crypto/Kconfig @@ -3,13 +3,13 @@ menu "Accelerated Cryptographic Algorithms for CPU (riscv)" config CRYPTO_AES_RISCV64 - tristate "Ciphers: AES, modes: CBC, CTS, CTR, XTS" + tristate "Ciphers: AES, modes: CTR, XTS" depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_LIB_AES select CRYPTO_SKCIPHER help - Length-preserving ciphers: AES with CBC, CTS, CTR, XTS + Length-preserving ciphers: AES with CTR, XTS Architecture: riscv64 using: - Zvkned vector crypto extension diff --git a/arch/riscv/crypto/Makefile b/arch/riscv/crypto/Makefile index 8cf31db57fc488..d8b85afa6d0b5e 100644 --- a/arch/riscv/crypto/Makefile +++ b/arch/riscv/crypto/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_CRYPTO_AES_RISCV64) += aes-riscv64.o -aes-riscv64-y := aes-riscv64-glue.o aes-riscv64-zvkned.o \ +aes-riscv64-y := aes-riscv64-glue.o \ aes-riscv64-zvkned-zvbb-zvkg.o aes-riscv64-zvkned-zvkb.o obj-$(CONFIG_CRYPTO_SM4_RISCV64) += sm4-riscv64.o diff --git a/arch/riscv/crypto/aes-riscv64-glue.c b/arch/riscv/crypto/aes-riscv64-glue.c index f7c492dcfd57b2..97f5369d7e7177 100644 --- a/arch/riscv/crypto/aes-riscv64-glue.c +++ b/arch/riscv/crypto/aes-riscv64-glue.c @@ -22,17 +22,6 @@ #include #include -asmlinkage void aes_cbc_encrypt_zvkned(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - u8 iv[AES_BLOCK_SIZE]); -asmlinkage void aes_cbc_decrypt_zvkned(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - u8 iv[AES_BLOCK_SIZE]); - -asmlinkage void aes_cbc_cts_crypt_zvkned(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - const u8 iv[AES_BLOCK_SIZE], bool enc); - asmlinkage void aes_ctr32_crypt_zvkned_zvkb(const struct crypto_aes_ctx *key, const u8 *in, u8 *out, size_t len, u8 iv[AES_BLOCK_SIZE]); @@ -81,110 +70,6 @@ static int riscv64_aes_setkey_skcipher(struct crypto_skcipher *tfm, return riscv64_aes_setkey(ctx, key, keylen); } -/* AES-CBC */ - -static int riscv64_aes_cbc_crypt(struct skcipher_request *req, bool enc) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *ctx = crypto_skcipher_ctx(tfm); - struct skcipher_walk walk; - unsigned int nbytes; - int err; - - err = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { - kernel_vector_begin(); - if (enc) - aes_cbc_encrypt_zvkned(ctx, walk.src.virt.addr, - walk.dst.virt.addr, - nbytes & ~(AES_BLOCK_SIZE - 1), - walk.iv); - else - aes_cbc_decrypt_zvkned(ctx, walk.src.virt.addr, - walk.dst.virt.addr, - nbytes & ~(AES_BLOCK_SIZE - 1), - walk.iv); - kernel_vector_end(); - err = skcipher_walk_done(&walk, nbytes & (AES_BLOCK_SIZE - 1)); - } - - return err; -} - -static int riscv64_aes_cbc_encrypt(struct skcipher_request *req) -{ - return riscv64_aes_cbc_crypt(req, true); -} - -static int riscv64_aes_cbc_decrypt(struct skcipher_request *req) -{ - return riscv64_aes_cbc_crypt(req, false); -} - -/* AES-CBC-CTS */ - -static int riscv64_aes_cbc_cts_crypt(struct skcipher_request *req, bool enc) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *ctx = crypto_skcipher_ctx(tfm); - struct scatterlist sg_src[2], sg_dst[2]; - struct skcipher_request subreq; - struct scatterlist *src, *dst; - struct skcipher_walk walk; - unsigned int cbc_len; - int err; - - if (req->cryptlen < AES_BLOCK_SIZE) - return -EINVAL; - - err = skcipher_walk_virt(&walk, req, false); - if (err) - return err; - /* - * If the full message is available in one step, decrypt it in one call - * to the CBC-CTS assembly function. This reduces overhead, especially - * on short messages. Otherwise, fall back to doing CBC up to the last - * two blocks, then invoke CTS just for the ciphertext stealing. - */ - if (unlikely(walk.nbytes != req->cryptlen)) { - cbc_len = round_down(req->cryptlen - AES_BLOCK_SIZE - 1, - AES_BLOCK_SIZE); - skcipher_walk_abort(&walk); - skcipher_request_set_tfm(&subreq, tfm); - skcipher_request_set_callback(&subreq, - skcipher_request_flags(req), - NULL, NULL); - skcipher_request_set_crypt(&subreq, req->src, req->dst, - cbc_len, req->iv); - err = riscv64_aes_cbc_crypt(&subreq, enc); - if (err) - return err; - dst = src = scatterwalk_ffwd(sg_src, req->src, cbc_len); - if (req->dst != req->src) - dst = scatterwalk_ffwd(sg_dst, req->dst, cbc_len); - skcipher_request_set_crypt(&subreq, src, dst, - req->cryptlen - cbc_len, req->iv); - err = skcipher_walk_virt(&walk, &subreq, false); - if (err) - return err; - } - kernel_vector_begin(); - aes_cbc_cts_crypt_zvkned(ctx, walk.src.virt.addr, walk.dst.virt.addr, - walk.nbytes, req->iv, enc); - kernel_vector_end(); - return skcipher_walk_done(&walk, 0); -} - -static int riscv64_aes_cbc_cts_encrypt(struct skcipher_request *req) -{ - return riscv64_aes_cbc_cts_crypt(req, true); -} - -static int riscv64_aes_cbc_cts_decrypt(struct skcipher_request *req) -{ - return riscv64_aes_cbc_cts_crypt(req, false); -} - /* AES-CTR */ static int riscv64_aes_ctr_crypt(struct skcipher_request *req) @@ -366,41 +251,6 @@ static int riscv64_aes_xts_decrypt(struct skcipher_request *req) /* Algorithm definitions */ -static struct skcipher_alg riscv64_zvkned_aes_skcipher_algs[] = { - { - .setkey = riscv64_aes_setkey_skcipher, - .encrypt = riscv64_aes_cbc_encrypt, - .decrypt = riscv64_aes_cbc_decrypt, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .base = { - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = sizeof(struct crypto_aes_ctx), - .cra_priority = 300, - .cra_name = "cbc(aes)", - .cra_driver_name = "cbc-aes-riscv64-zvkned", - .cra_module = THIS_MODULE, - }, - }, { - .setkey = riscv64_aes_setkey_skcipher, - .encrypt = riscv64_aes_cbc_cts_encrypt, - .decrypt = riscv64_aes_cbc_cts_decrypt, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .walksize = 4 * AES_BLOCK_SIZE, /* matches LMUL=4 */ - .base = { - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = sizeof(struct crypto_aes_ctx), - .cra_priority = 300, - .cra_name = "cts(cbc(aes))", - .cra_driver_name = "cts-cbc-aes-riscv64-zvkned", - .cra_module = THIS_MODULE, - }, - } -}; - static struct skcipher_alg riscv64_zvkned_zvkb_aes_skcipher_alg = { .setkey = riscv64_aes_setkey_skcipher, .encrypt = riscv64_aes_ctr_crypt, @@ -452,17 +302,11 @@ static int __init riscv64_aes_mod_init(void) if (riscv_isa_extension_available(NULL, ZVKNED) && riscv_vector_vlen() >= 128) { - err = crypto_register_skciphers( - riscv64_zvkned_aes_skcipher_algs, - ARRAY_SIZE(riscv64_zvkned_aes_skcipher_algs)); - if (err) - return err; - if (riscv_isa_extension_available(NULL, ZVKB)) { err = crypto_register_skcipher( &riscv64_zvkned_zvkb_aes_skcipher_alg); if (err) - goto unregister_zvkned_skcipher_algs; + return err; } if (riscv64_aes_xts_supported()) { @@ -478,9 +322,6 @@ static int __init riscv64_aes_mod_init(void) unregister_zvkned_zvkb_skcipher_alg: if (riscv_isa_extension_available(NULL, ZVKB)) crypto_unregister_skcipher(&riscv64_zvkned_zvkb_aes_skcipher_alg); -unregister_zvkned_skcipher_algs: - crypto_unregister_skciphers(riscv64_zvkned_aes_skcipher_algs, - ARRAY_SIZE(riscv64_zvkned_aes_skcipher_algs)); return err; } @@ -490,18 +331,14 @@ static void __exit riscv64_aes_mod_exit(void) crypto_unregister_skcipher(&riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); if (riscv_isa_extension_available(NULL, ZVKB)) crypto_unregister_skcipher(&riscv64_zvkned_zvkb_aes_skcipher_alg); - crypto_unregister_skciphers(riscv64_zvkned_aes_skcipher_algs, - ARRAY_SIZE(riscv64_zvkned_aes_skcipher_algs)); } module_init(riscv64_aes_mod_init); module_exit(riscv64_aes_mod_exit); -MODULE_DESCRIPTION("AES-CBC/CTS/CTR/XTS (RISC-V accelerated)"); +MODULE_DESCRIPTION("AES-CTR/XTS (RISC-V accelerated)"); MODULE_AUTHOR("Jerry Shih "); MODULE_LICENSE("GPL"); MODULE_ALIAS_CRYPTO("aes"); -MODULE_ALIAS_CRYPTO("cbc(aes)"); -MODULE_ALIAS_CRYPTO("cts(cbc(aes))"); MODULE_ALIAS_CRYPTO("ctr(aes)"); MODULE_ALIAS_CRYPTO("xts(aes)"); diff --git a/arch/riscv/crypto/aes-riscv64-zvkned.S b/arch/riscv/crypto/aes-riscv64-zvkned.S deleted file mode 100644 index 00f8a06596d39b..00000000000000 --- a/arch/riscv/crypto/aes-riscv64-zvkned.S +++ /dev/null @@ -1,273 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause */ -// -// This file is dual-licensed, meaning that you can use it under your -// choice of either of the following two licenses: -// -// Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. -// -// Licensed under the Apache License 2.0 (the "License"). You can obtain -// a copy in the file LICENSE in the source distribution or at -// https://www.openssl.org/source/license.html -// -// or -// -// Copyright (c) 2023, Christoph Müllner -// Copyright (c) 2023, Phoebe Chen -// Copyright (c) 2023, Jerry Shih -// Copyright 2024 Google LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// The generated code of this file depends on the following RISC-V extensions: -// - RV64I -// - RISC-V Vector ('V') with VLEN >= 128 -// - RISC-V Vector AES block cipher extension ('Zvkned') - -#include - -.text -.option arch, +zvkned - -#include "aes-macros.S" - -#define KEYP a0 -#define INP a1 -#define OUTP a2 -#define LEN a3 -#define IVP a4 - -.macro aes_cbc_encrypt keylen - vle32.v v16, (IVP) // Load IV -1: - vle32.v v17, (INP) // Load plaintext block - vxor.vv v16, v16, v17 // XOR with IV or prev ciphertext block - aes_encrypt v16, \keylen // Encrypt - vse32.v v16, (OUTP) // Store ciphertext block - addi INP, INP, 16 - addi OUTP, OUTP, 16 - addi LEN, LEN, -16 - bnez LEN, 1b - - vse32.v v16, (IVP) // Store next IV - ret -.endm - -.macro aes_cbc_decrypt keylen - srli LEN, LEN, 2 // Convert LEN from bytes to words - vle32.v v16, (IVP) // Load IV -1: - vsetvli t0, LEN, e32, m4, ta, ma - vle32.v v20, (INP) // Load ciphertext blocks - vslideup.vi v16, v20, 4 // Setup prev ciphertext blocks - addi t1, t0, -4 - vslidedown.vx v24, v20, t1 // Save last ciphertext block - aes_decrypt v20, \keylen // Decrypt the blocks - vxor.vv v20, v20, v16 // XOR with prev ciphertext blocks - vse32.v v20, (OUTP) // Store plaintext blocks - vmv.v.v v16, v24 // Next "IV" is last ciphertext block - slli t1, t0, 2 // Words to bytes - add INP, INP, t1 - add OUTP, OUTP, t1 - sub LEN, LEN, t0 - bnez LEN, 1b - - vsetivli zero, 4, e32, m1, ta, ma - vse32.v v16, (IVP) // Store next IV - ret -.endm - -// void aes_cbc_encrypt_zvkned(const struct crypto_aes_ctx *key, -// const u8 *in, u8 *out, size_t len, u8 iv[16]); -// -// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). -SYM_FUNC_START(aes_cbc_encrypt_zvkned) - aes_begin KEYP, 128f, 192f - aes_cbc_encrypt 256 -128: - aes_cbc_encrypt 128 -192: - aes_cbc_encrypt 192 -SYM_FUNC_END(aes_cbc_encrypt_zvkned) - -// Same prototype and calling convention as the encryption function -SYM_FUNC_START(aes_cbc_decrypt_zvkned) - aes_begin KEYP, 128f, 192f - aes_cbc_decrypt 256 -128: - aes_cbc_decrypt 128 -192: - aes_cbc_decrypt 192 -SYM_FUNC_END(aes_cbc_decrypt_zvkned) - -.macro aes_cbc_cts_encrypt keylen - - // CBC-encrypt all blocks except the last. But don't store the - // second-to-last block to the output buffer yet, since it will be - // handled specially in the ciphertext stealing step. Exception: if the - // message is single-block, still encrypt the last (and only) block. - li t0, 16 - j 2f -1: - vse32.v v16, (OUTP) // Store ciphertext block - addi OUTP, OUTP, 16 -2: - vle32.v v17, (INP) // Load plaintext block - vxor.vv v16, v16, v17 // XOR with IV or prev ciphertext block - aes_encrypt v16, \keylen // Encrypt - addi INP, INP, 16 - addi LEN, LEN, -16 - bgt LEN, t0, 1b // Repeat if more than one block remains - - // Special case: if the message is a single block, just do CBC. - beqz LEN, .Lcts_encrypt_done\@ - - // Encrypt the last two blocks using ciphertext stealing as follows: - // C[n-1] = Encrypt(Encrypt(P[n-1] ^ C[n-2]) ^ P[n]) - // C[n] = Encrypt(P[n-1] ^ C[n-2])[0..LEN] - // - // C[i] denotes the i'th ciphertext block, and likewise P[i] the i'th - // plaintext block. Block n, the last block, may be partial; its length - // is 1 <= LEN <= 16. If there are only 2 blocks, C[n-2] means the IV. - // - // v16 already contains Encrypt(P[n-1] ^ C[n-2]). - // INP points to P[n]. OUTP points to where C[n-1] should go. - // To support in-place encryption, load P[n] before storing C[n]. - addi t0, OUTP, 16 // Get pointer to where C[n] should go - vsetvli zero, LEN, e8, m1, tu, ma - vle8.v v17, (INP) // Load P[n] - vse8.v v16, (t0) // Store C[n] - vxor.vv v16, v16, v17 // v16 = Encrypt(P[n-1] ^ C[n-2]) ^ P[n] - vsetivli zero, 4, e32, m1, ta, ma - aes_encrypt v16, \keylen -.Lcts_encrypt_done\@: - vse32.v v16, (OUTP) // Store C[n-1] (or C[n] in single-block case) - ret -.endm - -#define LEN32 t4 // Length of remaining full blocks in 32-bit words -#define LEN_MOD16 t5 // Length of message in bytes mod 16 - -.macro aes_cbc_cts_decrypt keylen - andi LEN32, LEN, ~15 - srli LEN32, LEN32, 2 - andi LEN_MOD16, LEN, 15 - - // Save C[n-2] in v28 so that it's available later during the ciphertext - // stealing step. If there are fewer than three blocks, C[n-2] means - // the IV, otherwise it means the third-to-last ciphertext block. - vmv.v.v v28, v16 // IV - add t0, LEN, -33 - bltz t0, .Lcts_decrypt_loop\@ - andi t0, t0, ~15 - add t0, t0, INP - vle32.v v28, (t0) - - // CBC-decrypt all full blocks. For the last full block, or the last 2 - // full blocks if the message is block-aligned, this doesn't write the - // correct output blocks (unless the message is only a single block), - // because it XORs the wrong values with the raw AES plaintexts. But we - // fix this after this loop without redoing the AES decryptions. This - // approach allows more of the AES decryptions to be parallelized. -.Lcts_decrypt_loop\@: - vsetvli t0, LEN32, e32, m4, ta, ma - addi t1, t0, -4 - vle32.v v20, (INP) // Load next set of ciphertext blocks - vmv.v.v v24, v16 // Get IV or last ciphertext block of prev set - vslideup.vi v24, v20, 4 // Setup prev ciphertext blocks - vslidedown.vx v16, v20, t1 // Save last ciphertext block of this set - aes_decrypt v20, \keylen // Decrypt this set of blocks - vxor.vv v24, v24, v20 // XOR prev ciphertext blocks with decrypted blocks - vse32.v v24, (OUTP) // Store this set of plaintext blocks - sub LEN32, LEN32, t0 - slli t0, t0, 2 // Words to bytes - add INP, INP, t0 - add OUTP, OUTP, t0 - bnez LEN32, .Lcts_decrypt_loop\@ - - vsetivli zero, 4, e32, m4, ta, ma - vslidedown.vx v20, v20, t1 // Extract raw plaintext of last full block - addi t0, OUTP, -16 // Get pointer to last full plaintext block - bnez LEN_MOD16, .Lcts_decrypt_non_block_aligned\@ - - // Special case: if the message is a single block, just do CBC. - li t1, 16 - beq LEN, t1, .Lcts_decrypt_done\@ - - // Block-aligned message. Just fix up the last 2 blocks. We need: - // - // P[n-1] = Decrypt(C[n]) ^ C[n-2] - // P[n] = Decrypt(C[n-1]) ^ C[n] - // - // We have C[n] in v16, Decrypt(C[n]) in v20, and C[n-2] in v28. - // Together with Decrypt(C[n-1]) ^ C[n-2] from the output buffer, this - // is everything needed to fix the output without re-decrypting blocks. - addi t1, OUTP, -32 // Get pointer to where P[n-1] should go - vxor.vv v20, v20, v28 // Decrypt(C[n]) ^ C[n-2] == P[n-1] - vle32.v v24, (t1) // Decrypt(C[n-1]) ^ C[n-2] - vse32.v v20, (t1) // Store P[n-1] - vxor.vv v20, v24, v16 // Decrypt(C[n-1]) ^ C[n-2] ^ C[n] == P[n] ^ C[n-2] - j .Lcts_decrypt_finish\@ - -.Lcts_decrypt_non_block_aligned\@: - // Decrypt the last two blocks using ciphertext stealing as follows: - // - // P[n-1] = Decrypt(C[n] || Decrypt(C[n-1])[LEN_MOD16..16]) ^ C[n-2] - // P[n] = (Decrypt(C[n-1]) ^ C[n])[0..LEN_MOD16] - // - // We already have Decrypt(C[n-1]) in v20 and C[n-2] in v28. - vmv.v.v v16, v20 // v16 = Decrypt(C[n-1]) - vsetvli zero, LEN_MOD16, e8, m1, tu, ma - vle8.v v20, (INP) // v20 = C[n] || Decrypt(C[n-1])[LEN_MOD16..16] - vxor.vv v16, v16, v20 // v16 = Decrypt(C[n-1]) ^ C[n] - vse8.v v16, (OUTP) // Store P[n] - vsetivli zero, 4, e32, m1, ta, ma - aes_decrypt v20, \keylen // v20 = Decrypt(C[n] || Decrypt(C[n-1])[LEN_MOD16..16]) -.Lcts_decrypt_finish\@: - vxor.vv v20, v20, v28 // XOR with C[n-2] - vse32.v v20, (t0) // Store last full plaintext block -.Lcts_decrypt_done\@: - ret -.endm - -.macro aes_cbc_cts_crypt keylen - vle32.v v16, (IVP) // Load IV - beqz a5, .Lcts_decrypt\@ - aes_cbc_cts_encrypt \keylen -.Lcts_decrypt\@: - aes_cbc_cts_decrypt \keylen -.endm - -// void aes_cbc_cts_crypt_zvkned(const struct crypto_aes_ctx *key, -// const u8 *in, u8 *out, size_t len, -// const u8 iv[16], bool enc); -// -// Encrypts or decrypts a message with the CS3 variant of AES-CBC-CTS. -// This is the variant that unconditionally swaps the last two blocks. -SYM_FUNC_START(aes_cbc_cts_crypt_zvkned) - aes_begin KEYP, 128f, 192f - aes_cbc_cts_crypt 256 -128: - aes_cbc_cts_crypt 128 -192: - aes_cbc_cts_crypt 192 -SYM_FUNC_END(aes_cbc_cts_crypt_zvkned) diff --git a/crypto/aes.c b/crypto/aes.c index d6742234373594..9c90c7ef92a68a 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -626,7 +626,8 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "cbc(aes)", .base.cra_driver_name = "cbc-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_X86)) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, @@ -653,7 +654,8 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "cts(cbc(aes))", .base.cra_driver_name = "cts-cbc-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_X86)) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/riscv/aes-riscv64-zvkned.S b/lib/crypto/riscv/aes-riscv64-zvkned.S index 3700028e62ea48..61c1f185c33db6 100644 --- a/lib/crypto/riscv/aes-riscv64-zvkned.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned.S @@ -132,3 +132,238 @@ SYM_FUNC_END(aes_ecb_encrypt_zvkned) SYM_FUNC_START(aes_ecb_decrypt_zvkned) aes_ecb_crypt 0 SYM_FUNC_END(aes_ecb_decrypt_zvkned) + +#undef DST +#undef SRC +#undef LEN +#undef KEYP + +#define DST a0 +#define SRC a1 +#define LEN a2 +#define IVP a3 +#define KEYP a4 + +.macro aes_cbc_encrypt keylen + vle32.v v16, (IVP) // Load IV +1: + vle32.v v17, (SRC) // Load plaintext block + vxor.vv v16, v16, v17 // XOR with IV or prev ciphertext block + aes_encrypt v16, \keylen // Encrypt + vse32.v v16, (DST) // Store ciphertext block + addi SRC, SRC, 16 + addi DST, DST, 16 + addi LEN, LEN, -16 + bnez LEN, 1b + + vse32.v v16, (IVP) // Store next IV + ret +.endm + +.macro aes_cbc_decrypt keylen + srli LEN, LEN, 2 // Convert LEN from bytes to words + vle32.v v16, (IVP) // Load IV +1: + vsetvli t0, LEN, e32, m4, ta, ma + vle32.v v20, (SRC) // Load ciphertext blocks + vslideup.vi v16, v20, 4 // Setup prev ciphertext blocks + addi t1, t0, -4 + vslidedown.vx v24, v20, t1 // Save last ciphertext block + aes_decrypt v20, \keylen // Decrypt the blocks + vxor.vv v20, v20, v16 // XOR with prev ciphertext blocks + vse32.v v20, (DST) // Store plaintext blocks + vmv.v.v v16, v24 // Next "IV" is last ciphertext block + slli t1, t0, 2 // Words to bytes + add SRC, SRC, t1 + add DST, DST, t1 + sub LEN, LEN, t0 + bnez LEN, 1b + + vsetivli zero, 4, e32, m1, ta, ma + vse32.v v16, (IVP) // Store next IV + ret +.endm + +// void aes_cbc_encrypt_zvkned(u8 *dst, const u8 *src, size_t len, +// u8 iv[AES_BLOCK_SIZE], +// const struct aes_enckey *key); +// +// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). +SYM_FUNC_START(aes_cbc_encrypt_zvkned) + aes_begin KEYP, 128f, 192f + aes_cbc_encrypt 256 +128: + aes_cbc_encrypt 128 +192: + aes_cbc_encrypt 192 +SYM_FUNC_END(aes_cbc_encrypt_zvkned) + +// void aes_cbc_decrypt_zvkned(u8 *dst, const u8 *src, size_t len, +// u8 iv[AES_BLOCK_SIZE], +// const struct aes_key *key); +// +// |len| must be nonzero and a multiple of 16 (AES_BLOCK_SIZE). +SYM_FUNC_START(aes_cbc_decrypt_zvkned) + aes_begin KEYP, 128f, 192f + aes_cbc_decrypt 256 +128: + aes_cbc_decrypt 128 +192: + aes_cbc_decrypt 192 +SYM_FUNC_END(aes_cbc_decrypt_zvkned) + +#undef DST +#undef SRC +#undef LEN +#undef IVP +#undef KEYP + +#define DST a0 +#define SRC a1 +#define LEN a2 +#define IVP a3 +#define KEYP a4 +#define ENC a5 + +.macro aes_cbc_cts_encrypt keylen + + // CBC-encrypt all blocks except the last. But don't store the + // second-to-last block to the output buffer yet, since it will be + // handled specially in the ciphertext stealing step. + li t0, 16 + j 2f +1: + vse32.v v16, (DST) // Store ciphertext block + addi DST, DST, 16 +2: + vle32.v v17, (SRC) // Load plaintext block + vxor.vv v16, v16, v17 // XOR with IV or prev ciphertext block + aes_encrypt v16, \keylen // Encrypt + addi SRC, SRC, 16 + addi LEN, LEN, -16 + bgt LEN, t0, 1b // Repeat if more than one block remains + + // Encrypt the last two blocks using ciphertext stealing as follows: + // C[n-1] = Encrypt(Encrypt(P[n-1] ^ C[n-2]) ^ P[n]) + // C[n] = Encrypt(P[n-1] ^ C[n-2])[0..LEN] + // + // C[i] denotes the i'th ciphertext block, and likewise P[i] the i'th + // plaintext block. Block n, the last block, may be partial; its length + // is 1 <= LEN <= 16. If there are only 2 blocks, C[n-2] means the IV. + // + // v16 already contains Encrypt(P[n-1] ^ C[n-2]). + // SRC points to P[n]. DST points to where C[n-1] should go. + // To support in-place encryption, load P[n] before storing C[n]. + addi t0, DST, 16 // Get pointer to where C[n] should go + vsetvli zero, LEN, e8, m1, tu, ma + vle8.v v17, (SRC) // Load P[n] + vse8.v v16, (t0) // Store C[n] + vxor.vv v16, v16, v17 // v16 = Encrypt(P[n-1] ^ C[n-2]) ^ P[n] + vsetivli zero, 4, e32, m1, ta, ma + aes_encrypt v16, \keylen + vse32.v v16, (DST) // Store C[n-1] + ret +.endm + +#define LEN32 t4 // Length of remaining full blocks in 32-bit words +#define LEN_MOD16 t5 // Length of message in bytes mod 16 + +.macro aes_cbc_cts_decrypt keylen + andi LEN32, LEN, ~15 + srli LEN32, LEN32, 2 + andi LEN_MOD16, LEN, 15 + + // Save C[n-2] in v28 so that it's available later during the ciphertext + // stealing step. If there are fewer than three blocks, C[n-2] means + // the IV, otherwise it means the third-to-last ciphertext block. + vmv.v.v v28, v16 // IV + add t0, LEN, -33 + bltz t0, .Lcts_decrypt_loop\@ + andi t0, t0, ~15 + add t0, t0, SRC + vle32.v v28, (t0) + + // CBC-decrypt all full blocks. For the last full block, or the last 2 + // full blocks if the message is block-aligned, this doesn't write the + // correct output blocks, because it XORs the wrong values with the raw + // AES plaintexts. But we fix this after this loop without redoing the + // AES decryptions. This approach allows more of the AES decryptions to + // be parallelized. +.Lcts_decrypt_loop\@: + vsetvli t0, LEN32, e32, m4, ta, ma + addi t1, t0, -4 + vle32.v v20, (SRC) // Load next set of ciphertext blocks + vmv.v.v v24, v16 // Get IV or last ciphertext block of prev set + vslideup.vi v24, v20, 4 // Setup prev ciphertext blocks + vslidedown.vx v16, v20, t1 // Save last ciphertext block of this set + aes_decrypt v20, \keylen // Decrypt this set of blocks + vxor.vv v24, v24, v20 // XOR prev ciphertext blocks with decrypted blocks + vse32.v v24, (DST) // Store this set of plaintext blocks + sub LEN32, LEN32, t0 + slli t0, t0, 2 // Words to bytes + add SRC, SRC, t0 + add DST, DST, t0 + bnez LEN32, .Lcts_decrypt_loop\@ + + vsetivli zero, 4, e32, m4, ta, ma + vslidedown.vx v20, v20, t1 // Extract raw plaintext of last full block + addi t0, DST, -16 // Get pointer to last full plaintext block + bnez LEN_MOD16, .Lcts_decrypt_non_block_aligned\@ + + // Block-aligned message. Just fix up the last 2 blocks. We need: + // + // P[n-1] = Decrypt(C[n]) ^ C[n-2] + // P[n] = Decrypt(C[n-1]) ^ C[n] + // + // We have C[n] in v16, Decrypt(C[n]) in v20, and C[n-2] in v28. + // Together with Decrypt(C[n-1]) ^ C[n-2] from the output buffer, this + // is everything needed to fix the output without re-decrypting blocks. + addi t1, DST, -32 // Get pointer to where P[n-1] should go + vxor.vv v20, v20, v28 // Decrypt(C[n]) ^ C[n-2] == P[n-1] + vle32.v v24, (t1) // Decrypt(C[n-1]) ^ C[n-2] + vse32.v v20, (t1) // Store P[n-1] + vxor.vv v20, v24, v16 // Decrypt(C[n-1]) ^ C[n-2] ^ C[n] == P[n] ^ C[n-2] + j .Lcts_decrypt_finish\@ + +.Lcts_decrypt_non_block_aligned\@: + // Decrypt the last two blocks using ciphertext stealing as follows: + // + // P[n-1] = Decrypt(C[n] || Decrypt(C[n-1])[LEN_MOD16..16]) ^ C[n-2] + // P[n] = (Decrypt(C[n-1]) ^ C[n])[0..LEN_MOD16] + // + // We already have Decrypt(C[n-1]) in v20 and C[n-2] in v28. + vmv.v.v v16, v20 // v16 = Decrypt(C[n-1]) + vsetvli zero, LEN_MOD16, e8, m1, tu, ma + vle8.v v20, (SRC) // v20 = C[n] || Decrypt(C[n-1])[LEN_MOD16..16] + vxor.vv v16, v16, v20 // v16 = Decrypt(C[n-1]) ^ C[n] + vse8.v v16, (DST) // Store P[n] + vsetivli zero, 4, e32, m1, ta, ma + aes_decrypt v20, \keylen // v20 = Decrypt(C[n] || Decrypt(C[n-1])[LEN_MOD16..16]) +.Lcts_decrypt_finish\@: + vxor.vv v20, v20, v28 // XOR with C[n-2] + vse32.v v20, (t0) // Store last full plaintext block + ret +.endm + +.macro aes_cbc_cts_crypt keylen + vle32.v v16, (IVP) // Load IV + beqz ENC, .Lcts_decrypt\@ + aes_cbc_cts_encrypt \keylen +.Lcts_decrypt\@: + aes_cbc_cts_decrypt \keylen +.endm + +// void aes_cbc_cts_crypt_zvkned(u8 *dst, const u8 *src, size_t len, +// const u8 iv[AES_BLOCK_SIZE], +// aes_encrypt_arg key, bool enc); +// +// Encrypts or decrypts len >= 17 bytes of data using the CS3 variant of +// AES-CBC-CTS. This is the variant that always swaps the last two blocks. +SYM_FUNC_START(aes_cbc_cts_crypt_zvkned) + aes_begin KEYP, 128f, 192f + aes_cbc_cts_crypt 256 +128: + aes_cbc_cts_crypt 128 +192: + aes_cbc_cts_crypt 192 +SYM_FUNC_END(aes_cbc_cts_crypt_zvkned) diff --git a/lib/crypto/riscv/aes.h b/lib/crypto/riscv/aes.h index 5b36d0147d93c5..150cda82cb89d1 100644 --- a/lib/crypto/riscv/aes.h +++ b/lib/crypto/riscv/aes.h @@ -93,6 +93,74 @@ static bool aes_ecb_decrypt_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_ECB */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CBC) +void aes_cbc_encrypt_zvkned(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], const struct aes_enckey *key); +void aes_cbc_decrypt_zvkned(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], const struct aes_key *key); +void aes_cbc_cts_crypt_zvkned(u8 *dst, const u8 *src, size_t len, + const u8 iv[AES_BLOCK_SIZE], + aes_encrypt_arg key, bool enc); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_cbc_encrypt_arch aes_cbc_encrypt_arch +static bool aes_cbc_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + aes_cbc_encrypt_zvkned(dst, src, len, iv, key); + kernel_vector_end(); + return true; +} + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +#define aes_cbc_decrypt_arch aes_cbc_decrypt_arch +static bool aes_cbc_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_key *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + aes_cbc_decrypt_zvkned(dst, src, len, iv, key); + kernel_vector_end(); + return true; +} + +/* len can be any value greater than AES_BLOCK_SIZE here. */ +#define aes_cbc_cts_encrypt_arch aes_cbc_cts_encrypt_arch +static bool aes_cbc_cts_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + + kernel_vector_begin(); + aes_cbc_cts_crypt_zvkned(dst, src, len, iv, key, true); + kernel_vector_end(); + return true; +} + +/* len can be any value greater than AES_BLOCK_SIZE here. */ +#define aes_cbc_cts_decrypt_arch aes_cbc_cts_decrypt_arch +static bool aes_cbc_cts_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 iv[AES_BLOCK_SIZE], + const struct aes_key *key) +{ + if (!static_branch_likely(&have_zvkned) || unlikely(!may_use_simd())) + return false; + + kernel_vector_begin(); + aes_cbc_cts_crypt_zvkned(dst, src, len, iv, key, false); + kernel_vector_end(); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_CBC */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { From f083f8de5290dc6384b2929306c0e99714c93d6f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:10 -0700 Subject: [PATCH 1136/2286] lib/crypto: riscv/aes-ctr: Migrate optimized code into library Instead of exposing the riscv-optimized AES-CTR code via a riscv-specific crypto_skcipher algorithm, just implement the AES-CTR library functions. This is simpler, it makes the AES-CTR library functions be riscv-optimized, and it also fixes the longstanding issue where the riscv-optimized AES-CTR code was disabled by default. AES-CTR support still remains available through crypto_skcipher via crypto/aes.c, but individual architectures no longer need to handle it. To match what the library expects, update the assembly functions to operate on struct aes_enckey rather than struct crypto_aes_ctx, and adjust the argument order. Bump up the priority of the corresponding library-based algorithm on riscv now that it no longer has to be lower than arch/riscv/crypto/. Also re-enable the library-based "ccm(aes)" and "gcm(aes)". Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-20-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/riscv/crypto/Kconfig | 5 +- arch/riscv/crypto/Makefile | 2 +- arch/riscv/crypto/aes-riscv64-glue.c | 114 +----------------- crypto/aes.c | 5 +- lib/crypto/Makefile | 4 + .../crypto/riscv}/aes-riscv64-zvkned-zvkb.S | 23 ++-- lib/crypto/riscv/aes.h | 72 ++++++++++- 7 files changed, 94 insertions(+), 131 deletions(-) rename {arch/riscv/crypto => lib/crypto/riscv}/aes-riscv64-zvkned-zvkb.S (93%) diff --git a/arch/riscv/crypto/Kconfig b/arch/riscv/crypto/Kconfig index 0a3f87ad384e5e..0733d48944019c 100644 --- a/arch/riscv/crypto/Kconfig +++ b/arch/riscv/crypto/Kconfig @@ -3,18 +3,17 @@ menu "Accelerated Cryptographic Algorithms for CPU (riscv)" config CRYPTO_AES_RISCV64 - tristate "Ciphers: AES, modes: CTR, XTS" + tristate "Ciphers: AES, modes: XTS" depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS select CRYPTO_LIB_AES select CRYPTO_SKCIPHER help - Length-preserving ciphers: AES with CTR, XTS + Length-preserving ciphers: AES with XTS Architecture: riscv64 using: - Zvkned vector crypto extension - Zvbb vector extension (XTS) - - Zvkb vector crypto extension (CTR) - Zvkg vector crypto extension (XTS) config CRYPTO_SM4_RISCV64 diff --git a/arch/riscv/crypto/Makefile b/arch/riscv/crypto/Makefile index d8b85afa6d0b5e..08904603fc9467 100644 --- a/arch/riscv/crypto/Makefile +++ b/arch/riscv/crypto/Makefile @@ -2,7 +2,7 @@ obj-$(CONFIG_CRYPTO_AES_RISCV64) += aes-riscv64.o aes-riscv64-y := aes-riscv64-glue.o \ - aes-riscv64-zvkned-zvbb-zvkg.o aes-riscv64-zvkned-zvkb.o + aes-riscv64-zvkned-zvbb-zvkg.o obj-$(CONFIG_CRYPTO_SM4_RISCV64) += sm4-riscv64.o sm4-riscv64-y := sm4-riscv64-glue.o sm4-riscv64-zvksed-zvkb.o diff --git a/arch/riscv/crypto/aes-riscv64-glue.c b/arch/riscv/crypto/aes-riscv64-glue.c index 97f5369d7e7177..a7dcceb77c496d 100644 --- a/arch/riscv/crypto/aes-riscv64-glue.c +++ b/arch/riscv/crypto/aes-riscv64-glue.c @@ -22,10 +22,6 @@ #include #include -asmlinkage void aes_ctr32_crypt_zvkned_zvkb(const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - u8 iv[AES_BLOCK_SIZE]); - asmlinkage void aes_xts_encrypt_zvkned_zvbb_zvkg( const struct crypto_aes_ctx *key, const u8 *in, u8 *out, size_t len, @@ -62,75 +58,6 @@ static int riscv64_aes_setkey(struct crypto_aes_ctx *ctx, return aes_expandkey(ctx, key, keylen); } -static int riscv64_aes_setkey_skcipher(struct crypto_skcipher *tfm, - const u8 *key, unsigned int keylen) -{ - struct crypto_aes_ctx *ctx = crypto_skcipher_ctx(tfm); - - return riscv64_aes_setkey(ctx, key, keylen); -} - -/* AES-CTR */ - -static int riscv64_aes_ctr_crypt(struct skcipher_request *req) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct crypto_aes_ctx *ctx = crypto_skcipher_ctx(tfm); - unsigned int nbytes, p1_nbytes; - struct skcipher_walk walk; - u32 ctr32, nblocks; - int err; - - /* Get the low 32-bit word of the 128-bit big endian counter. */ - ctr32 = get_unaligned_be32(req->iv + 12); - - err = skcipher_walk_virt(&walk, req, false); - while ((nbytes = walk.nbytes) != 0) { - if (nbytes < walk.total) { - /* Not the end yet, so keep the length block-aligned. */ - nbytes = round_down(nbytes, AES_BLOCK_SIZE); - nblocks = nbytes / AES_BLOCK_SIZE; - } else { - /* It's the end, so include any final partial block. */ - nblocks = DIV_ROUND_UP(nbytes, AES_BLOCK_SIZE); - } - ctr32 += nblocks; - - kernel_vector_begin(); - if (ctr32 >= nblocks) { - /* The low 32-bit word of the counter won't overflow. */ - aes_ctr32_crypt_zvkned_zvkb(ctx, walk.src.virt.addr, - walk.dst.virt.addr, nbytes, - req->iv); - } else { - /* - * The low 32-bit word of the counter will overflow. - * The assembly doesn't handle this case, so split the - * operation into two at the point where the overflow - * will occur. After the first part, add the carry bit. - */ - p1_nbytes = min(nbytes, (nblocks - ctr32) * AES_BLOCK_SIZE); - aes_ctr32_crypt_zvkned_zvkb(ctx, walk.src.virt.addr, - walk.dst.virt.addr, - p1_nbytes, req->iv); - crypto_inc(req->iv, 12); - - if (ctr32) { - aes_ctr32_crypt_zvkned_zvkb( - ctx, - walk.src.virt.addr + p1_nbytes, - walk.dst.virt.addr + p1_nbytes, - nbytes - p1_nbytes, req->iv); - } - } - kernel_vector_end(); - - err = skcipher_walk_done(&walk, walk.nbytes - nbytes); - } - - return err; -} - /* AES-XTS */ struct riscv64_aes_xts_ctx { @@ -251,25 +178,6 @@ static int riscv64_aes_xts_decrypt(struct skcipher_request *req) /* Algorithm definitions */ -static struct skcipher_alg riscv64_zvkned_zvkb_aes_skcipher_alg = { - .setkey = riscv64_aes_setkey_skcipher, - .encrypt = riscv64_aes_ctr_crypt, - .decrypt = riscv64_aes_ctr_crypt, - .min_keysize = AES_MIN_KEY_SIZE, - .max_keysize = AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .chunksize = AES_BLOCK_SIZE, - .walksize = 4 * AES_BLOCK_SIZE, /* matches LMUL=4 */ - .base = { - .cra_blocksize = 1, - .cra_ctxsize = sizeof(struct crypto_aes_ctx), - .cra_priority = 300, - .cra_name = "ctr(aes)", - .cra_driver_name = "ctr-aes-riscv64-zvkned-zvkb", - .cra_module = THIS_MODULE, - }, -}; - static struct skcipher_alg riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg = { .setkey = riscv64_aes_xts_setkey, .encrypt = riscv64_aes_xts_encrypt, @@ -302,43 +210,27 @@ static int __init riscv64_aes_mod_init(void) if (riscv_isa_extension_available(NULL, ZVKNED) && riscv_vector_vlen() >= 128) { - if (riscv_isa_extension_available(NULL, ZVKB)) { - err = crypto_register_skcipher( - &riscv64_zvkned_zvkb_aes_skcipher_alg); - if (err) - return err; - } - if (riscv64_aes_xts_supported()) { err = crypto_register_skcipher( &riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); if (err) - goto unregister_zvkned_zvkb_skcipher_alg; + return err; } } return err; - -unregister_zvkned_zvkb_skcipher_alg: - if (riscv_isa_extension_available(NULL, ZVKB)) - crypto_unregister_skcipher(&riscv64_zvkned_zvkb_aes_skcipher_alg); - return err; } static void __exit riscv64_aes_mod_exit(void) { - if (riscv64_aes_xts_supported()) - crypto_unregister_skcipher(&riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); - if (riscv_isa_extension_available(NULL, ZVKB)) - crypto_unregister_skcipher(&riscv64_zvkned_zvkb_aes_skcipher_alg); + crypto_unregister_skcipher(&riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); } module_init(riscv64_aes_mod_init); module_exit(riscv64_aes_mod_exit); -MODULE_DESCRIPTION("AES-CTR/XTS (RISC-V accelerated)"); +MODULE_DESCRIPTION("AES-XTS (RISC-V accelerated)"); MODULE_AUTHOR("Jerry Shih "); MODULE_LICENSE("GPL"); MODULE_ALIAS_CRYPTO("aes"); -MODULE_ALIAS_CRYPTO("ctr(aes)"); MODULE_ALIAS_CRYPTO("xts(aes)"); diff --git a/crypto/aes.c b/crypto/aes.c index 9c90c7ef92a68a..b9cc0d5ff5b6e1 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -671,7 +671,8 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "ctr(aes)", .base.cra_driver_name = "ctr-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_X86)) ? 300 : 110, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct aes_enckey), .base.cra_module = THIS_MODULE, @@ -1011,7 +1012,6 @@ static struct aead_alg aead_algs[] = { !(IS_ENABLED(CONFIG_ARM) || \ IS_ENABLED(CONFIG_ARM64) || \ IS_ENABLED(CONFIG_PPC) || \ - IS_ENABLED(CONFIG_RISCV) || \ IS_ENABLED(CONFIG_S390) || \ IS_ENABLED(CONFIG_SPARC)) { @@ -1055,7 +1055,6 @@ static struct aead_alg aead_algs[] = { !(IS_ENABLED(CONFIG_ARM) || \ IS_ENABLED(CONFIG_ARM64) || \ IS_ENABLED(CONFIG_PPC) || \ - IS_ENABLED(CONFIG_RISCV) || \ IS_ENABLED(CONFIG_S390) || \ IS_ENABLED(CONFIG_SPARC)) { diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index 02d89a2263770c..ff34aeda37ba9b 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -51,6 +51,10 @@ endif # !CONFIG_SPE endif # CONFIG_PPC libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned.o +ifneq ($(CONFIG_CRYPTO_LIB_AES_CTR),) +libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned-zvkb.o +endif + libaes-$(CONFIG_SPARC) += sparc/aes_asm.o libaes-$(CONFIG_X86) += x86/aes-aesni.o diff --git a/arch/riscv/crypto/aes-riscv64-zvkned-zvkb.S b/lib/crypto/riscv/aes-riscv64-zvkned-zvkb.S similarity index 93% rename from arch/riscv/crypto/aes-riscv64-zvkned-zvkb.S rename to lib/crypto/riscv/aes-riscv64-zvkned-zvkb.S index 9962d45005870c..2981d8182de6ee 100644 --- a/arch/riscv/crypto/aes-riscv64-zvkned-zvkb.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned-zvkb.S @@ -49,11 +49,11 @@ #include "aes-macros.S" -#define KEYP a0 -#define INP a1 -#define OUTP a2 -#define LEN a3 -#define IVP a4 +#define DST a0 +#define SRC a1 +#define LEN a2 +#define IVP a3 +#define KEYP a4 #define LEN32 a5 #define VL_E32 a6 @@ -110,13 +110,13 @@ // XOR the data with the keystream. vsetvli t0, LEN, e8, m4, ta, ma - vle8.v v20, (INP) + vle8.v v20, (SRC) vxor.vv v20, v20, v24 - vse8.v v20, (OUTP) + vse8.v v20, (DST) // Advance the pointers and update the remaining length. - add INP, INP, t0 - add OUTP, OUTP, t0 + add SRC, SRC, t0 + add DST, DST, t0 sub LEN, LEN, t0 sub LEN32, LEN32, VL_E32 srli VL_BLOCKS, VL_E32, 2 @@ -133,9 +133,8 @@ ret .endm -// void aes_ctr32_crypt_zvkned_zvkb(const struct crypto_aes_ctx *key, -// const u8 *in, u8 *out, size_t len, -// u8 iv[16]); +// void aes_ctr32_crypt_zvkned_zvkb(u8 *dst, const u8 *src, size_t len, +// u8 iv[16], const struct aes_enckey *key); SYM_FUNC_START(aes_ctr32_crypt_zvkned_zvkb) aes_begin KEYP, 128f, 192f aes_ctr32_crypt 256 diff --git a/lib/crypto/riscv/aes.h b/lib/crypto/riscv/aes.h index 150cda82cb89d1..c04e04cf70193b 100644 --- a/lib/crypto/riscv/aes.h +++ b/lib/crypto/riscv/aes.h @@ -9,6 +9,7 @@ #include static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_zvkned); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_zvkned_zvkb); /* The assembly code assumes the following offsets. */ static_assert(offsetof(struct aes_enckey, len) == 0); @@ -161,10 +162,79 @@ static bool aes_cbc_cts_decrypt_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_CBC */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_CTR) +void aes_ctr32_crypt_zvkned_zvkb(u8 *dst, const u8 *src, size_t len, u8 iv[16], + const struct aes_enckey *key); + +static void aes_ctr_riscv(u8 *dst, const u8 *src, size_t len, + u8 ctr[AES_BLOCK_SIZE], const struct aes_enckey *key) +{ + u32 ctr32 = get_unaligned_be32(&ctr[12]); + u32 nblocks; + size_t part1_len; + + /* This assumes len <= U32_MAX * AES_BLOCK_SIZE. */ + nblocks = DIV_ROUND_UP(len, AES_BLOCK_SIZE); + ctr32 += nblocks; + + if (likely(ctr32 >= nblocks)) { + /* The low 32 bits of the counter won't overflow. */ + aes_ctr32_crypt_zvkned_zvkb(dst, src, len, ctr, key); + } else { + /* + * The low 32 bits of the counter will overflow. The + * assembly doesn't handle this case, so split the + * operation into two at the point where the overflow + * will occur. After the first part, add the carry bit. + */ + part1_len = + min(len, (size_t)(nblocks - ctr32) * AES_BLOCK_SIZE); + aes_ctr32_crypt_zvkned_zvkb(dst, src, part1_len, ctr, key); + for (int i = AES_BLOCK_SIZE - 5; i >= 0; i--) { + if (++ctr[i] != 0) + break; + } + if (part1_len < len) + aes_ctr32_crypt_zvkned_zvkb(dst + part1_len, + src + part1_len, + len - part1_len, ctr, key); + } +} + +#define aes_ctr_arch aes_ctr_arch +static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, + u8 ctr[AES_BLOCK_SIZE], const struct aes_enckey *key) +{ + if (!static_branch_likely(&have_zvkned_zvkb) || + unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + while (len) { + /* + * Split super long messages into chunks that each need at most + * 1 carry bit out of the low 32-bit word of the counter. Up to + * U32_MAX * AES_BLOCK_SIZE bytes could be allowed per chunk, + * but use a lower limit to guard against other errors. + */ + size_t n = min(len, round_down(S32_MAX, 32 * AES_BLOCK_SIZE)); + + aes_ctr_riscv(dst, src, n, ctr, key); + dst += n; + src += n; + len -= n; + } + kernel_vector_end(); + return true; +} +#endif /* CONFIG_CRYPTO_LIB_AES_CTR */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { if (riscv_isa_extension_available(NULL, ZVKNED) && - riscv_vector_vlen() >= 128) + riscv_vector_vlen() >= 128) { static_branch_enable(&have_zvkned); + if (riscv_isa_extension_available(NULL, ZVKB)) + static_branch_enable(&have_zvkned_zvkb); + } } From 69897f5e8552af8835a2c8a36aa7d597e1cf6689 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 27 Sep 2026 15:43:11 -0700 Subject: [PATCH 1137/2286] lib/crypto: riscv/aes-xts: Migrate optimized code into library Instead of exposing the riscv-optimized AES-XTS code via a riscv-specific crypto_skcipher algorithm, just implement the AES-XTS library functions. This is simpler, it makes the AES-XTS library functions be riscv-optimized, and it also fixes the longstanding issue where the riscv-optimized AES-XTS code was disabled by default. AES-XTS support still remains available through crypto_skcipher via crypto/aes.c, but individual architectures no longer need to handle it. To match what the library expects, update the assembly functions to operate on struct aes_key rather than struct crypto_aes_ctx, adjust the argument order, and remove the redundant ciphertext stealing support which is already implemented in a generic way in the library. Bump up the priority of the corresponding library-based algorithm on riscv now that it no longer has to be lower than arch/riscv/crypto/. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20260927224418.109759-21-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/riscv/crypto/Kconfig | 14 -- arch/riscv/crypto/Makefile | 4 - arch/riscv/crypto/aes-macros.S | 166 ------------ arch/riscv/crypto/aes-riscv64-glue.c | 236 ------------------ crypto/aes.c | 3 +- lib/crypto/Makefile | 3 + .../riscv}/aes-riscv64-zvkned-zvbb-zvkg.S | 99 ++------ lib/crypto/riscv/aes.h | 53 ++++ 8 files changed, 74 insertions(+), 504 deletions(-) delete mode 100644 arch/riscv/crypto/aes-macros.S delete mode 100644 arch/riscv/crypto/aes-riscv64-glue.c rename {arch/riscv/crypto => lib/crypto/riscv}/aes-riscv64-zvkned-zvbb-zvkg.S (74%) diff --git a/arch/riscv/crypto/Kconfig b/arch/riscv/crypto/Kconfig index 0733d48944019c..614f9321486281 100644 --- a/arch/riscv/crypto/Kconfig +++ b/arch/riscv/crypto/Kconfig @@ -2,20 +2,6 @@ menu "Accelerated Cryptographic Algorithms for CPU (riscv)" -config CRYPTO_AES_RISCV64 - tristate "Ciphers: AES, modes: XTS" - depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ - RISCV_EFFICIENT_VECTOR_UNALIGNED_ACCESS - select CRYPTO_LIB_AES - select CRYPTO_SKCIPHER - help - Length-preserving ciphers: AES with XTS - - Architecture: riscv64 using: - - Zvkned vector crypto extension - - Zvbb vector extension (XTS) - - Zvkg vector crypto extension (XTS) - config CRYPTO_SM4_RISCV64 tristate "Ciphers: SM4 (ShangMi 4)" depends on 64BIT && TOOLCHAIN_HAS_VECTOR_CRYPTO && \ diff --git a/arch/riscv/crypto/Makefile b/arch/riscv/crypto/Makefile index 08904603fc9467..9f6956cf50b1b9 100644 --- a/arch/riscv/crypto/Makefile +++ b/arch/riscv/crypto/Makefile @@ -1,8 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_CRYPTO_AES_RISCV64) += aes-riscv64.o -aes-riscv64-y := aes-riscv64-glue.o \ - aes-riscv64-zvkned-zvbb-zvkg.o - obj-$(CONFIG_CRYPTO_SM4_RISCV64) += sm4-riscv64.o sm4-riscv64-y := sm4-riscv64-glue.o sm4-riscv64-zvksed-zvkb.o diff --git a/arch/riscv/crypto/aes-macros.S b/arch/riscv/crypto/aes-macros.S deleted file mode 100644 index 1384164621a506..00000000000000 --- a/arch/riscv/crypto/aes-macros.S +++ /dev/null @@ -1,166 +0,0 @@ -/* SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause */ -// -// This file is dual-licensed, meaning that you can use it under your -// choice of either of the following two licenses: -// -// Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. -// -// Licensed under the Apache License 2.0 (the "License"). You can obtain -// a copy in the file LICENSE in the source distribution or at -// https://www.openssl.org/source/license.html -// -// or -// -// Copyright (c) 2023, Christoph Müllner -// Copyright (c) 2023, Phoebe Chen -// Copyright (c) 2023, Jerry Shih -// Copyright 2024 Google LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions -// are met: -// 1. Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -// This file contains macros that are shared by the other aes-*.S files. The -// generated code of these macros depends on the following RISC-V extensions: -// - RV64I -// - RISC-V Vector ('V') with VLEN >= 128 -// - RISC-V Vector AES block cipher extension ('Zvkned') - -// Loads the AES round keys from \keyp into vector registers and jumps to code -// specific to the length of the key. Specifically: -// - If AES-128, loads round keys into v1-v11 and jumps to \label128. -// - If AES-192, loads round keys into v1-v13 and jumps to \label192. -// - If AES-256, loads round keys into v1-v15 and continues onwards. -// -// Also sets vl=4 and vtype=e32,m1,ta,ma. Clobbers t0 and t1. -.macro aes_begin keyp, label128, label192, key_len -.ifb \key_len - lwu t0, 480(\keyp) // t0 = key length in bytes -.endif - li t1, 24 // t1 = key length for AES-192 - vsetivli zero, 4, e32, m1, ta, ma - vle32.v v1, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v2, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v3, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v4, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v5, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v6, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v7, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v8, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v9, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v10, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v11, (\keyp) -.ifb \key_len - blt t0, t1, \label128 // If AES-128, goto label128. -.else - blt \key_len, t1, \label128 // If AES-128, goto label128. -.endif - addi \keyp, \keyp, 16 - vle32.v v12, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v13, (\keyp) -.ifb \key_len - beq t0, t1, \label192 // If AES-192, goto label192. -.else - beq \key_len, t1, \label192 // If AES-192, goto label192. -.endif - // Else, it's AES-256. - addi \keyp, \keyp, 16 - vle32.v v14, (\keyp) - addi \keyp, \keyp, 16 - vle32.v v15, (\keyp) -.endm - -// Encrypts \data using zvkned instructions, using the round keys loaded into -// v1-v11 (for AES-128), v1-v13 (for AES-192), or v1-v15 (for AES-256). \keylen -// is the AES key length in bits. vl and vtype must already be set -// appropriately. Note that if vl > 4, multiple blocks are encrypted. -.macro aes_encrypt data, keylen - vaesz.vs \data, v1 - vaesem.vs \data, v2 - vaesem.vs \data, v3 - vaesem.vs \data, v4 - vaesem.vs \data, v5 - vaesem.vs \data, v6 - vaesem.vs \data, v7 - vaesem.vs \data, v8 - vaesem.vs \data, v9 - vaesem.vs \data, v10 -.if \keylen == 128 - vaesef.vs \data, v11 -.elseif \keylen == 192 - vaesem.vs \data, v11 - vaesem.vs \data, v12 - vaesef.vs \data, v13 -.else - vaesem.vs \data, v11 - vaesem.vs \data, v12 - vaesem.vs \data, v13 - vaesem.vs \data, v14 - vaesef.vs \data, v15 -.endif -.endm - -// Same as aes_encrypt, but decrypts instead of encrypts. -.macro aes_decrypt data, keylen -.if \keylen == 128 - vaesz.vs \data, v11 -.elseif \keylen == 192 - vaesz.vs \data, v13 - vaesdm.vs \data, v12 - vaesdm.vs \data, v11 -.else - vaesz.vs \data, v15 - vaesdm.vs \data, v14 - vaesdm.vs \data, v13 - vaesdm.vs \data, v12 - vaesdm.vs \data, v11 -.endif - vaesdm.vs \data, v10 - vaesdm.vs \data, v9 - vaesdm.vs \data, v8 - vaesdm.vs \data, v7 - vaesdm.vs \data, v6 - vaesdm.vs \data, v5 - vaesdm.vs \data, v4 - vaesdm.vs \data, v3 - vaesdm.vs \data, v2 - vaesdf.vs \data, v1 -.endm - -// Expands to aes_encrypt or aes_decrypt according to \enc, which is 1 or 0. -.macro aes_crypt data, enc, keylen -.if \enc - aes_encrypt \data, \keylen -.else - aes_decrypt \data, \keylen -.endif -.endm diff --git a/arch/riscv/crypto/aes-riscv64-glue.c b/arch/riscv/crypto/aes-riscv64-glue.c deleted file mode 100644 index a7dcceb77c496d..00000000000000 --- a/arch/riscv/crypto/aes-riscv64-glue.c +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * AES modes using the RISC-V vector crypto extensions - * - * Copyright (C) 2023 VRULL GmbH - * Author: Heiko Stuebner - * - * Copyright (C) 2023 SiFive, Inc. - * Author: Jerry Shih - * - * Copyright 2024 Google LLC - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -asmlinkage void aes_xts_encrypt_zvkned_zvbb_zvkg( - const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - u8 tweak[AES_BLOCK_SIZE]); - -asmlinkage void aes_xts_decrypt_zvkned_zvbb_zvkg( - const struct crypto_aes_ctx *key, - const u8 *in, u8 *out, size_t len, - u8 tweak[AES_BLOCK_SIZE]); - -static int riscv64_aes_setkey(struct crypto_aes_ctx *ctx, - const u8 *key, unsigned int keylen) -{ - /* - * For now we just use the generic key expansion, for these reasons: - * - * - zvkned's key expansion instructions don't support AES-192. - * So, non-zvkned fallback code would be needed anyway. - * - * - Users of AES in Linux usually don't change keys frequently. - * So, key expansion isn't performance-critical. - * - * - For single-block AES exposed as a "cipher" algorithm, it's - * necessary to use struct crypto_aes_ctx and initialize its 'key_dec' - * field with the round keys for the Equivalent Inverse Cipher. This - * is because with "cipher", decryption can be requested from a - * context where the vector unit isn't usable, necessitating a - * fallback to aes_decrypt(). But, zvkned can only generate and use - * the normal round keys. Of course, it's preferable to not have - * special code just for "cipher", as e.g. XTS also uses a - * single-block AES encryption. It's simplest to just use - * struct crypto_aes_ctx and aes_expandkey() everywhere. - */ - return aes_expandkey(ctx, key, keylen); -} - -/* AES-XTS */ - -struct riscv64_aes_xts_ctx { - struct crypto_aes_ctx ctx1; - struct aes_enckey tweak_key; -}; - -static int riscv64_aes_xts_setkey(struct crypto_skcipher *tfm, const u8 *key, - unsigned int keylen) -{ - struct riscv64_aes_xts_ctx *ctx = crypto_skcipher_ctx(tfm); - - return xts_verify_key(tfm, key, keylen) ?: - riscv64_aes_setkey(&ctx->ctx1, key, keylen / 2) ?: - aes_prepareenckey(&ctx->tweak_key, key + keylen / 2, keylen / 2); -} - -static int riscv64_aes_xts_crypt(struct skcipher_request *req, bool enc) -{ - struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req); - const struct riscv64_aes_xts_ctx *ctx = crypto_skcipher_ctx(tfm); - int tail = req->cryptlen % AES_BLOCK_SIZE; - struct scatterlist sg_src[2], sg_dst[2]; - struct skcipher_request subreq; - struct scatterlist *src, *dst; - struct skcipher_walk walk; - int err; - - if (req->cryptlen < AES_BLOCK_SIZE) - return -EINVAL; - - /* Encrypt the IV with the tweak key to get the first tweak. */ - aes_encrypt(&ctx->tweak_key, req->iv, req->iv); - - err = skcipher_walk_virt(&walk, req, false); - - /* - * If the message length isn't divisible by the AES block size and the - * full message isn't available in one step of the scatterlist walk, - * then separate off the last full block and the partial block. This - * ensures that they are processed in the same call to the assembly - * function, which is required for ciphertext stealing. - */ - if (unlikely(tail > 0 && walk.nbytes < walk.total)) { - skcipher_walk_abort(&walk); - - skcipher_request_set_tfm(&subreq, tfm); - skcipher_request_set_callback(&subreq, - skcipher_request_flags(req), - NULL, NULL); - skcipher_request_set_crypt(&subreq, req->src, req->dst, - req->cryptlen - tail - AES_BLOCK_SIZE, - req->iv); - req = &subreq; - err = skcipher_walk_virt(&walk, req, false); - } else { - tail = 0; - } - - while (walk.nbytes) { - unsigned int nbytes = walk.nbytes; - - if (nbytes < walk.total) - nbytes = round_down(nbytes, AES_BLOCK_SIZE); - - kernel_vector_begin(); - if (enc) - aes_xts_encrypt_zvkned_zvbb_zvkg( - &ctx->ctx1, walk.src.virt.addr, - walk.dst.virt.addr, nbytes, req->iv); - else - aes_xts_decrypt_zvkned_zvbb_zvkg( - &ctx->ctx1, walk.src.virt.addr, - walk.dst.virt.addr, nbytes, req->iv); - kernel_vector_end(); - err = skcipher_walk_done(&walk, walk.nbytes - nbytes); - } - - if (err || likely(!tail)) - return err; - - /* Do ciphertext stealing with the last full block and partial block. */ - - dst = src = scatterwalk_ffwd(sg_src, req->src, req->cryptlen); - if (req->dst != req->src) - dst = scatterwalk_ffwd(sg_dst, req->dst, req->cryptlen); - - skcipher_request_set_crypt(req, src, dst, AES_BLOCK_SIZE + tail, - req->iv); - - err = skcipher_walk_virt(&walk, req, false); - if (err) - return err; - - kernel_vector_begin(); - if (enc) - aes_xts_encrypt_zvkned_zvbb_zvkg( - &ctx->ctx1, walk.src.virt.addr, - walk.dst.virt.addr, walk.nbytes, req->iv); - else - aes_xts_decrypt_zvkned_zvbb_zvkg( - &ctx->ctx1, walk.src.virt.addr, - walk.dst.virt.addr, walk.nbytes, req->iv); - kernel_vector_end(); - - return skcipher_walk_done(&walk, 0); -} - -static int riscv64_aes_xts_encrypt(struct skcipher_request *req) -{ - return riscv64_aes_xts_crypt(req, true); -} - -static int riscv64_aes_xts_decrypt(struct skcipher_request *req) -{ - return riscv64_aes_xts_crypt(req, false); -} - -/* Algorithm definitions */ - -static struct skcipher_alg riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg = { - .setkey = riscv64_aes_xts_setkey, - .encrypt = riscv64_aes_xts_encrypt, - .decrypt = riscv64_aes_xts_decrypt, - .min_keysize = 2 * AES_MIN_KEY_SIZE, - .max_keysize = 2 * AES_MAX_KEY_SIZE, - .ivsize = AES_BLOCK_SIZE, - .chunksize = AES_BLOCK_SIZE, - .walksize = 4 * AES_BLOCK_SIZE, /* matches LMUL=4 */ - .base = { - .cra_blocksize = AES_BLOCK_SIZE, - .cra_ctxsize = sizeof(struct riscv64_aes_xts_ctx), - .cra_priority = 300, - .cra_name = "xts(aes)", - .cra_driver_name = "xts-aes-riscv64-zvkned-zvbb-zvkg", - .cra_module = THIS_MODULE, - }, -}; - -static inline bool riscv64_aes_xts_supported(void) -{ - return riscv_isa_extension_available(NULL, ZVBB) && - riscv_isa_extension_available(NULL, ZVKG) && - riscv_vector_vlen() < 2048 /* Implementation limitation */; -} - -static int __init riscv64_aes_mod_init(void) -{ - int err = -ENODEV; - - if (riscv_isa_extension_available(NULL, ZVKNED) && - riscv_vector_vlen() >= 128) { - if (riscv64_aes_xts_supported()) { - err = crypto_register_skcipher( - &riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); - if (err) - return err; - } - } - - return err; -} - -static void __exit riscv64_aes_mod_exit(void) -{ - crypto_unregister_skcipher(&riscv64_zvkned_zvbb_zvkg_aes_skcipher_alg); -} - -module_init(riscv64_aes_mod_init); -module_exit(riscv64_aes_mod_exit); - -MODULE_DESCRIPTION("AES-XTS (RISC-V accelerated)"); -MODULE_AUTHOR("Jerry Shih "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_CRYPTO("aes"); -MODULE_ALIAS_CRYPTO("xts(aes)"); diff --git a/crypto/aes.c b/crypto/aes.c index b9cc0d5ff5b6e1..fe1f68a38daa4d 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -712,7 +712,8 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_X86)) ? 300 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_xts_key), .base.cra_module = THIS_MODULE, diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index ff34aeda37ba9b..d683b8520f555d 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -54,6 +54,9 @@ libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned.o ifneq ($(CONFIG_CRYPTO_LIB_AES_CTR),) libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned-zvkb.o endif +ifneq ($(CONFIG_CRYPTO_LIB_AES_XTS),) +libaes-$(CONFIG_RISCV) += riscv/aes-riscv64-zvkned-zvbb-zvkg.o +endif libaes-$(CONFIG_SPARC) += sparc/aes_asm.o diff --git a/arch/riscv/crypto/aes-riscv64-zvkned-zvbb-zvkg.S b/lib/crypto/riscv/aes-riscv64-zvkned-zvbb-zvkg.S similarity index 74% rename from arch/riscv/crypto/aes-riscv64-zvkned-zvbb-zvkg.S rename to lib/crypto/riscv/aes-riscv64-zvkned-zvbb-zvkg.S index 146fc9cfb268d4..c45aaed80ce60a 100644 --- a/arch/riscv/crypto/aes-riscv64-zvkned-zvbb-zvkg.S +++ b/lib/crypto/riscv/aes-riscv64-zvkned-zvbb-zvkg.S @@ -50,14 +50,13 @@ #include "aes-macros.S" -#define KEYP a0 -#define INP a1 -#define OUTP a2 -#define LEN a3 -#define TWEAKP a4 +#define DST a0 +#define SRC a1 +#define LEN a2 +#define TWEAKP a3 +#define KEYP a4 #define LEN32 a5 -#define TAIL_LEN a6 #define VL a7 #define VLMAX t4 @@ -67,9 +66,6 @@ #define TWEAKS_BREV v20 // LMUL=4 (most of the time) #define MULTS_BREV v24 // LMUL=4 (most of the time) #define TMP0 v28 -#define TMP1 v29 -#define TMP2 v30 -#define TMP3 v31 // xts_init initializes the following values: // @@ -167,24 +163,21 @@ .endm .macro __aes_xts_crypt enc, keylen - // With 16 < len <= 31, there's no main loop, just ciphertext stealing. - beqz LEN32, .Lcts_without_main_loop\@ - vsetvli VLMAX, zero, e32, m4, ta, ma 1: vsetvli VL, LEN32, e32, m4, ta, ma 2: // Encrypt or decrypt VL/4 blocks. - vle32.v TMP0, (INP) + vle32.v TMP0, (SRC) vxor.vv TMP0, TMP0, TWEAKS aes_crypt TMP0, \enc, \keylen vxor.vv TMP0, TMP0, TWEAKS - vse32.v TMP0, (OUTP) + vse32.v TMP0, (DST) // Update the pointers and the remaining length. slli t0, VL, 2 - add INP, INP, t0 - add OUTP, OUTP, t0 + add SRC, SRC, t0 + add DST, DST, t0 sub LEN32, LEN32, VL // Check whether more blocks remain. @@ -217,73 +210,14 @@ vsetivli zero, 4, e32, m1, ta, ma vgmul.vv TWEAKS_BREV, MULTS_BREV // Advance to next tweak - bnez TAIL_LEN, .Lcts\@ - // Update *TWEAKP to contain the next tweak. vbrev8.v TWEAKS, TWEAKS_BREV vse32.v TWEAKS, (TWEAKP) ret - -.Lcts_without_main_loop\@: - load_x -.Lcts\@: - // TWEAKS_BREV now contains the next tweak. Compute the one after that. - vsetivli zero, 4, e32, m1, ta, ma - vmv.v.v TMP0, TWEAKS_BREV - vgmul.vv TMP0, MULTS_BREV - // Undo the bit reversal of the next two tweaks and store them in TMP1 - // and TMP2, such that TMP1 is the first needed and TMP2 the second. -.if \enc - vbrev8.v TMP1, TWEAKS_BREV - vbrev8.v TMP2, TMP0 -.else - vbrev8.v TMP1, TMP0 - vbrev8.v TMP2, TWEAKS_BREV -.endif - - // Encrypt/decrypt the last full block. - vle32.v TMP0, (INP) - vxor.vv TMP0, TMP0, TMP1 - aes_crypt TMP0, \enc, \keylen - vxor.vv TMP0, TMP0, TMP1 - - // Swap the first TAIL_LEN bytes of the above result with the tail. - // Note that to support in-place encryption/decryption, the load from - // the input tail must happen before the store to the output tail. - addi t0, INP, 16 - addi t1, OUTP, 16 - vmv.v.v TMP3, TMP0 - vsetvli zero, TAIL_LEN, e8, m1, tu, ma - vle8.v TMP0, (t0) - vse8.v TMP3, (t1) - - // Encrypt/decrypt again and store the last full block. - vsetivli zero, 4, e32, m1, ta, ma - vxor.vv TMP0, TMP0, TMP2 - aes_crypt TMP0, \enc, \keylen - vxor.vv TMP0, TMP0, TMP2 - vse32.v TMP0, (OUTP) - - ret .endm .macro aes_xts_crypt enc - - // Check whether the length is a multiple of the AES block size. - andi TAIL_LEN, LEN, 15 - beqz TAIL_LEN, 1f - - // The length isn't a multiple of the AES block size, so ciphertext - // stealing will be required. Ciphertext stealing involves special - // handling of the partial block and the last full block, so subtract - // the length of both from the length to be processed in the main loop. - sub LEN, LEN, TAIL_LEN - addi LEN, LEN, -16 -1: srli LEN32, LEN, 2 - // LEN and LEN32 now contain the total length of the blocks that will be - // processed in the main loop, in bytes and 32-bit words respectively. - xts_init aes_begin KEYP, 128f, 192f __aes_xts_crypt \enc, 256 @@ -293,15 +227,14 @@ __aes_xts_crypt \enc, 192 .endm -// void aes_xts_encrypt_zvkned_zvbb_zvkg(const struct crypto_aes_ctx *key, -// const u8 *in, u8 *out, size_t len, -// u8 tweak[16]); +// void aes_xts_encrypt_zvkned_zvbb_zvkg(u8 *dst, const u8 *src, size_t len, +// u8 tweak[AES_BLOCK_SIZE], +// const struct aes_key *key); // -// |key| is the data key. |tweak| contains the next tweak; the encryption of -// the original IV with the tweak key was already done. This function supports -// incremental computation, but |len| must always be >= 16 (AES_BLOCK_SIZE), and -// |len| must be a multiple of 16 except on the last call. If |len| is a -// multiple of 16, then this function updates |tweak| to contain the next tweak. +// Encrypts `len` bytes of data using AES-XTS using the given `key` and `tweak`. +// `key` is just the main key; `tweak` is assumed to have already been encrypted +// by the tweak key. `len` is assumed to be a multiple of 16. `tweak` is +// updated to contain the next tweak, allowing for incremental computation. SYM_FUNC_START(aes_xts_encrypt_zvkned_zvbb_zvkg) aes_xts_crypt 1 SYM_FUNC_END(aes_xts_encrypt_zvkned_zvbb_zvkg) diff --git a/lib/crypto/riscv/aes.h b/lib/crypto/riscv/aes.h index c04e04cf70193b..36e9218b507f9f 100644 --- a/lib/crypto/riscv/aes.h +++ b/lib/crypto/riscv/aes.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* + * AES using the RISC-V vector crypto extensions + * * Copyright (C) 2023 VRULL GmbH * Copyright (C) 2023 SiFive, Inc. * Copyright 2024 Google LLC @@ -10,6 +12,7 @@ static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_zvkned); static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_zvkned_zvkb); +static __ro_after_init DEFINE_STATIC_KEY_FALSE(have_xts); /* The assembly code assumes the following offsets. */ static_assert(offsetof(struct aes_enckey, len) == 0); @@ -228,6 +231,52 @@ static bool aes_ctr_arch(u8 *dst, const u8 *src, size_t len, } #endif /* CONFIG_CRYPTO_LIB_AES_CTR */ +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) +void aes_xts_encrypt_zvkned_zvbb_zvkg(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); +void aes_xts_decrypt_zvkned_zvbb_zvkg(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_key *key); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +static __always_inline bool +aes_xts_crypt_riscv(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont, bool enc) +{ + if (!static_branch_likely(&have_xts) || unlikely(!may_use_simd())) + return false; + kernel_vector_begin(); + if (!cont) + aes_encrypt_zvkned(&key->tweak_key, tweak, tweak); + if (enc) + aes_xts_encrypt_zvkned_zvbb_zvkg(dst, src, len, tweak, + &key->main_key); + else + aes_xts_decrypt_zvkned_zvbb_zvkg(dst, src, len, tweak, + &key->main_key); + kernel_vector_end(); + return true; +} + +#define aes_xts_encrypt_arch aes_xts_encrypt_arch +static bool aes_xts_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_riscv(dst, src, len, tweak, key, cont, true); +} + +#define aes_xts_decrypt_arch aes_xts_decrypt_arch +static bool aes_xts_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_riscv(dst, src, len, tweak, key, cont, false); +} +#endif /* CONFIG_CRYPTO_LIB_AES_XTS */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { @@ -236,5 +285,9 @@ static void aes_mod_init_arch(void) static_branch_enable(&have_zvkned); if (riscv_isa_extension_available(NULL, ZVKB)) static_branch_enable(&have_zvkned_zvkb); + if (riscv_isa_extension_available(NULL, ZVBB) && + riscv_isa_extension_available(NULL, ZVKG) && + riscv_vector_vlen() < 2048 /* Implementation limitation */) + static_branch_enable(&have_xts); } } From c91dcfe66f755bfaa71114272b1bd1bc6997c195 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 30 Sep 2026 10:05:40 -0700 Subject: [PATCH 1138/2286] lib/crypto: x86/aes: Set RNDKEY in aes_cbc_encrypt_aesni() Make aes_cbc_encrypt_aesni() set the RNDKEY register alias explicitly, rather than reusing the alias left by the previous function (which worked by chance). Also consistently comment the register aliases needed by _do_aes. Link: https://patch.msgid.link/20260930170542.140403-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/aes-aesni.S | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/crypto/x86/aes-aesni.S b/lib/crypto/x86/aes-aesni.S index cbc0cc23f63ebb..6795e82c34c82e 100644 --- a/lib/crypto/x86/aes-aesni.S +++ b/lib/crypto/x86/aes-aesni.S @@ -444,13 +444,14 @@ SYM_FUNC_START(aes_cbc_encrypt_aesni) // Other local variables #ifdef __x86_64__ - .set RNDKEY_PTR, %r9 + .set RNDKEY_PTR, %r9 // Temporary register for _do_aes #else - .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later + .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later #endif - .set NROUNDS, TMP_32 + .set NROUNDS, TMP_32 // Temporary register for _do_aes .set AESDATA0, %xmm0 .set PTEXT, %xmm1 + .set RNDKEY, %xmm2 // Temporary register for _do_aes _prologue uses_arg3=2, uses_arg4=2 movdqu (IV_PTR), AESDATA0 @@ -484,16 +485,16 @@ SYM_FUNC_START(aes_cbc_decrypt_aesni) // Other local variables #ifdef __x86_64__ - .set RNDKEY_PTR, %r9 + .set RNDKEY_PTR, %r9 // Temporary register for _do_aes #else - .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later + .set RNDKEY_PTR, IV_PTR // IV_PTR is clobbered and reloaded later #endif - .set NROUNDS, TMP_32 + .set NROUNDS, TMP_32 // Temporary register for _do_aes .set AESDATA0, %xmm0 .set AESDATA1, %xmm1 .set AESDATA2, %xmm2 .set AESDATA3, %xmm3 - .set RNDKEY, %xmm4 + .set RNDKEY, %xmm4 // Temporary register for _do_aes .set IV, %xmm5 .set CTEXT0, %xmm6 .set CTEXT1, %xmm7 @@ -575,7 +576,7 @@ SYM_FUNC_END(aes_cbc_decrypt_aesni) .set LSHIFT_MASK, %xmm1 // [0x80, 0x80, ...] + range(PN_LEN) .set AESDATA0, %xmm2 .set AESDATA1, %xmm3 - .set RNDKEY, %xmm4 // Temporary register for _do_aes + .set RNDKEY, %xmm4 // Temporary register for _do_aes .set IV, %xmm5 _prologue uses_arg3=2, uses_arg4=2 @@ -674,7 +675,7 @@ SYM_FUNC_START(aes_ctr64_crypt_aesni) .set LE_CTR, %xmm4 // Current 128-bit little endian counter .set LE_CTR_INC, %xmm5 // Initialized to (u64[])[1, 0] .set BSWAP_MASK, %xmm6 - .set RNDKEY, %xmm7 + .set RNDKEY, %xmm7 // Temporary register for _do_aes .set RNDKEY_PTR, LE_CTR_PTR // Temporary register for _do_aes .set NROUNDS, TMP_32 // Temporary register for _do_aes @@ -795,17 +796,17 @@ SYM_FUNC_END(aes_ctr64_crypt_aesni) // Other local variables #ifdef __x86_64__ - .set RNDKEY_PTR, %r9 + .set RNDKEY_PTR, %r9 // Temporary register for _do_aes #else .set RNDKEY_PTR, TWEAK_PTR // TWEAK_PTR is clobbered and reloaded later. #endif - .set NROUNDS, TMP_32 + .set NROUNDS, TMP_32 // Temporary register for _do_aes .set AESDATA0, %xmm0 .set AESDATA1, %xmm1 .set AESDATA2, %xmm2 .set AESDATA3, %xmm3 .set GF_POLY, %xmm4 - .set RNDKEY, %xmm5 + .set RNDKEY, %xmm5 // Temporary register for _do_aes .set TWEAK, %xmm6 .set SAVED_TWEAK0, %xmm7 #ifdef __x86_64__ From f410daa32cfcd94263f156662a1ad2097505443c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 30 Sep 2026 10:05:41 -0700 Subject: [PATCH 1139/2286] crypto: aes - Boost priority of encryption modes further Fix a regression where drivers/crypto/ccp/ (priority 300) and drivers/crypto/padlock-aes.c (priority 400) started being able to override the AES-NI and/or VAES optimized code (previously priority 400-800). Link: https://patch.msgid.link/20260930170542.140403-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- crypto/aes.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crypto/aes.c b/crypto/aes.c index fe1f68a38daa4d..73f3aa856be4ea 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -611,7 +611,7 @@ static struct skcipher_alg skcipher_algs[] = { .base.cra_name = "ecb(aes)", .base.cra_driver_name = "ecb-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || - IS_ENABLED(CONFIG_X86)) ? 300 : 110, + IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, @@ -627,7 +627,7 @@ static struct skcipher_alg skcipher_algs[] = { .base.cra_name = "cbc(aes)", .base.cra_driver_name = "cbc-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || - IS_ENABLED(CONFIG_X86)) ? 300 : 110, + IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, @@ -655,7 +655,7 @@ static struct skcipher_alg skcipher_algs[] = { .base.cra_name = "cts(cbc(aes))", .base.cra_driver_name = "cts-cbc-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || - IS_ENABLED(CONFIG_X86)) ? 300 : 110, + IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_key), .base.cra_module = THIS_MODULE, @@ -672,7 +672,7 @@ static struct skcipher_alg skcipher_algs[] = { .base.cra_name = "ctr(aes)", .base.cra_driver_name = "ctr-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || - IS_ENABLED(CONFIG_X86)) ? 300 : 110, + IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct aes_enckey), .base.cra_module = THIS_MODULE, @@ -689,7 +689,7 @@ static struct skcipher_alg skcipher_algs[] = { { .base.cra_name = "xctr(aes)", .base.cra_driver_name = "xctr-aes-lib", - .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 300 : 110, + .base.cra_priority = IS_ENABLED(CONFIG_X86) ? 500 : 110, .base.cra_blocksize = 1, .base.cra_ctxsize = sizeof(struct aes_enckey), .base.cra_module = THIS_MODULE, @@ -713,7 +713,7 @@ static struct skcipher_alg skcipher_algs[] = { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || - IS_ENABLED(CONFIG_X86)) ? 300 : 110, + IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_xts_key), .base.cra_module = THIS_MODULE, From 9ca77aa621027149c43551308112eba06b1de3af Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Wed, 30 Sep 2026 10:05:42 -0700 Subject: [PATCH 1140/2286] crypto: x86/aesni-intel - Add transitional selections Make CRYPTO_AES_NI_INTEL select CRYPTO_AES, CRYPTO_CBC, and CRYPTO_XTS so that systems with CRYPTO_AES_NI_INTEL enabled are guaranteed to still get the algorithm needed for their systems to boot with LUKS / dm-crypt. (Later, when CRYPTO_AES_NI_INTEL is removed entirely, I plan to turn it into a 'transitional' symbol with these selections.) While compatibility selections haven't been used for previous migrations of architecture-optimized code into lib/crypto/, I think it's worthwhile in this particular case. Link: https://patch.msgid.link/20260930170542.140403-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- arch/x86/crypto/Kconfig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/x86/crypto/Kconfig b/arch/x86/crypto/Kconfig index c1842c2d70559b..20f5e2a17e45af 100644 --- a/arch/x86/crypto/Kconfig +++ b/arch/x86/crypto/Kconfig @@ -9,6 +9,9 @@ config CRYPTO_AES_NI_INTEL select CRYPTO_LIB_AES select CRYPTO_LIB_GF128MUL select CRYPTO_SKCIPHER + select CRYPTO_AES + select CRYPTO_CBC + select CRYPTO_XTS help AEAD cipher: AES with GCM @@ -16,8 +19,13 @@ config CRYPTO_AES_NI_INTEL - AES-NI (AES new instructions) - VAES (Vector AES) - Note: this option no longer provides the accelerated XTS, CBC, CTR, - and ECB code. For those just use CRYPTO_XTS, CRYPTO_CBC, etc. + The module controlled by this option (aesni-intel.ko) no longer + directly provides the accelerated ECB, CBC, CBC-CTS, CTR, XCTR, and + XTS code. Those are now provided by CRYPTO_AES (aes.ko) when the + corresponding mode option (CRYPTO_XTS, CRYPTO_CBC, etc.) is enabled. + + To ensure dm-crypt continues to work in kconfigs with only this option + set, this option selects CRYPTO_AES, CRYPTO_CBC, and CRYPTO_XTS. config CRYPTO_BLOWFISH_X86_64 tristate "Ciphers: Blowfish, modes: ECB, CBC" From 499b8f8a5d0401a6ab83b346a307f923640a024a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 21 Sep 2026 12:35:46 +0100 Subject: [PATCH 1141/2286] rust: proc-macro2: enable `proc_macro_span` feature The `Span::join` function is very important in generating useful diagnostics. When invoking `syn::Spanned::span`, it attempts to return an overall span that covers the whole expression. If the `join` function is unavailable, however, only the span of the first token is generated. This causes worse diagnostics quality for all macros, notably pin-init, when compared to the pin-init's own diagnostics test suite with the feature enabled. For example, this diagnostic covers only part of the text where replacement is suggested: error: expected nothing or `..Zeroable::init_zeroed()`. --> tests/ui/compile-fail/zeroable/invalid_spread.rs:15:11 | 15 | ..MyZeroable::init_zeroed() | ^^^^^^^^^^ In the upcoming pin-init self-reference series, spans of full types or expressions are used quite heavily to make diagnostics not confusing. Thus, enable the `proc_macro_span` feature. This does make use of the unstable `Span::join` function, but this is for producing diagnostics only, so we do not have a hard reliance on the feature. A very small tweak of proc-macro2 vendored code is needed to make the code compile for 1.85. Link: https://github.com/rust-lang/rust/issues/54725 Link: https://github.com/dtolnay/proc-macro2/pull/542 Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20260921115708.4009754-1-gary@kernel.org/ [ Updated example diagnostics, removed unused PathBuf import. - Gary ] Signed-off-by: Gary Guo --- rust/Makefile | 1 + rust/proc-macro2/README.md | 3 ++- rust/proc-macro2/probe/proc_macro_span.rs | 9 --------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index da1a7409d98452..5ee9c30c324a04 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -100,6 +100,7 @@ zerocopy-envs := \ proc_macro2-cfgs := \ feature="proc-macro" \ wrap_proc_macro \ + proc_macro_span \ $(if $(call rustc-min-version,108800),proc_macro_span_file proc_macro_span_location) proc_macro2-flags := \ diff --git a/rust/proc-macro2/README.md b/rust/proc-macro2/README.md index af044fee4f59c5..519d01112a022b 100644 --- a/rust/proc-macro2/README.md +++ b/rust/proc-macro2/README.md @@ -4,7 +4,8 @@ These source files come from the Rust `proc-macro2` crate, version 1.0.101 (released 2025-08-16), hosted in the repository, licensed under "Apache-2.0 OR MIT" and only modified to add the SPDX license -identifiers and to remove the `unicode-ident` dependency. +identifiers, to remove the `unicode-ident` dependency, and to build +with 1.85 with nightly features enabled. For copyright details, please see: diff --git a/rust/proc-macro2/probe/proc_macro_span.rs b/rust/proc-macro2/probe/proc_macro_span.rs index 892a7eb3e5a05e..765bd47dbecd75 100644 --- a/rust/proc-macro2/probe/proc_macro_span.rs +++ b/rust/proc-macro2/probe/proc_macro_span.rs @@ -10,7 +10,6 @@ extern crate proc_macro; use core::ops::{Range, RangeBounds}; use proc_macro::{Literal, Span}; -use std::path::PathBuf; pub fn byte_range(this: &Span) -> Range { this.byte_range() @@ -32,14 +31,6 @@ pub fn column(this: &Span) -> usize { this.column() } -pub fn file(this: &Span) -> String { - this.file() -} - -pub fn local_file(this: &Span) -> Option { - this.local_file() -} - pub fn join(this: &Span, other: Span) -> Option { this.join(other) } From f278d04be3acfd3a9eccf045b9eb180007cceccc Mon Sep 17 00:00:00 2001 From: Davy Felipe Date: Mon, 28 Sep 2026 23:03:29 -0300 Subject: [PATCH 1142/2286] hfs: handle extent B-tree write errors hfs_brec_insert() may fail while inserting a new extent record, but __hfs_ext_write_extent() currently ignores its return value and clears HFS_FLG_EXT_DIRTY and HFS_FLG_EXT_NEW as if the insertion had succeeded. Propagate errors returned by hfs_brec_insert() and only clear the extent flags after a successful insertion. When updating an existing extent record, hfs_bnode_write() returns void. Validate the extent write parameters before calling it so an invalid update is reported as -ERANGE instead of being treated as successful. Use a reusable B-tree node range helper that takes the find data and the expected record size. The helper validates the bnode and tree pointers, entry offset and entry length, and ensures that the write range fits within the node. Negative-path testing in QEMU confirmed that an insertion error is propagated to the caller. Testing the existing-record path also confirmed that invalid write parameters are rejected before HFS_FLG_EXT_DIRTY is cleared. Changes in v6: - Rename hfs_bnode_is_valid_range() to is_hfs_bnode_range_valid(). - Use size_t for the expected record size. - Drop the redundant expected_len validity check. - Return -ERANGE for invalid find-data/range parameters. - Incorporate the final review feedback from Viacheslav Dubeyko. Signed-off-by: Davy Felipe Reviewed-by: Viacheslav Dubeyko Link: https://lore.kernel.org/r/20260929020329.32911-1-davyfelipe34@gmail.com Signed-off-by: Viacheslav Dubeyko --- fs/hfs/btree.h | 19 +++++++++++++++++++ fs/hfs/extent.c | 10 ++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/fs/hfs/btree.h b/fs/hfs/btree.h index b4c3f2a3147100..1f703f2c53d8ce 100644 --- a/fs/hfs/btree.h +++ b/fs/hfs/btree.h @@ -84,6 +84,25 @@ struct hfs_find_data { int entryoffset, entrylength; }; +static inline bool is_hfs_bnode_range_valid(struct hfs_find_data *fd, + size_t expected_len) +{ + struct hfs_bnode *node; + + if (!fd) + return false; + + node = fd->bnode; + if (!node || !node->tree) + return false; + + if (fd->entryoffset < 0 || fd->entrylength != expected_len) + return false; + + return (u64)fd->entryoffset + fd->entrylength <= + node->tree->node_size; +} + /* btree.c */ extern struct hfs_btree *hfs_btree_open(struct super_block *sb, u32 id, diff --git a/fs/hfs/extent.c b/fs/hfs/extent.c index f066a99a863bc7..26357086517fc6 100644 --- a/fs/hfs/extent.c +++ b/fs/hfs/extent.c @@ -121,12 +121,18 @@ static int __hfs_ext_write_extent(struct inode *inode, struct hfs_find_data *fd) res = hfs_bmap_reserve(fd->tree, fd->tree->depth + 1); if (res) return res; - hfs_brec_insert(fd, HFS_I(inode)->cached_extents, sizeof(hfs_extent_rec)); + res = hfs_brec_insert(fd, HFS_I(inode)->cached_extents, + sizeof(hfs_extent_rec)); + if (res) + return res; HFS_I(inode)->flags &= ~(HFS_FLG_EXT_DIRTY|HFS_FLG_EXT_NEW); } else { if (res) return res; - hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents, fd->entryoffset, fd->entrylength); + if (!is_hfs_bnode_range_valid(fd, sizeof(hfs_extent_rec))) + return -ERANGE; + hfs_bnode_write(fd->bnode, HFS_I(inode)->cached_extents, + fd->entryoffset, fd->entrylength); HFS_I(inode)->flags &= ~HFS_FLG_EXT_DIRTY; } return 0; From b7db3ba9434cbad3d15f62500104525bfa28a304 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Sat, 26 Sep 2026 10:24:13 -0500 Subject: [PATCH 1143/2286] tools/nolibc: stop rounding malloc() sizes up to 4096 bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit malloc() rounds the size of each mapping up to a multiple of 4096, presumably to align it to a page boundary. But the page size is not 4096 on all architectures, and mmap() and munmap() round the length up to the page size themselves. Drop the rounding. As heap->len is no longer rounded up, growing a block with realloc() now always creates a new mapping. Assisted-by: LLM Signed-off-by: Danish Khateeb Reviewed-by: Willy Tarreau Link: https://patch.msgid.link/20260926152414.251214-2-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/stdlib.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 6c3c620a04cf0d..dc554faff22a44 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -130,9 +130,7 @@ void *malloc(size_t len) { struct nolibc_heap *heap; - /* Always allocate memory with size multiple of 4096. */ len = sizeof(*heap) + len; - len = (len + 4095UL) & -4096UL; heap = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (__builtin_expect(heap == MAP_FAILED, 0)) From 2111eb4273e7fda0815de85319653104f323c441 Mon Sep 17 00:00:00 2001 From: Danish Khateeb Date: Sat, 26 Sep 2026 10:24:14 -0500 Subject: [PATCH 1144/2286] tools/nolibc: check for overflow in malloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit malloc() adds the size of its header to the requested size without checking for overflow. For a size within the header size of SIZE_MAX, the sum wraps around to a few bytes, and malloc() returns a single page instead of NULL. calloc() checks its multiplication for overflow, but then passes the product to malloc(), so calloc(SIZE_MAX, 1) returns a single page too. So does realloc(ptr, SIZE_MAX). Such a size is usually a bug in the caller, for instance a length of -1 used as a size_t, and returning a page instead of NULL can turn it into a heap overflow. Fail with ENOMEM when adding the header overflows, as glibc does for sizes this large. Fixes: 0e0ff638400b ("tools/nolibc/stdlib: Implement `malloc()`, `calloc()`, `realloc()` and `free()`") Assisted-by: LLM Signed-off-by: Danish Khateeb Link: https://patch.msgid.link/20260926152414.251214-3-danishkhateeb03@gmail.com Signed-off-by: Thomas Weißschuh --- tools/include/nolibc/stdlib.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index dc554faff22a44..cea8290ac0b99d 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -130,7 +130,11 @@ void *malloc(size_t len) { struct nolibc_heap *heap; - len = sizeof(*heap) + len; + if (__builtin_expect(__builtin_add_overflow(len, sizeof(*heap), &len), 0)) { + SET_ERRNO(ENOMEM); + return NULL; + } + heap = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (__builtin_expect(heap == MAP_FAILED, 0)) From 5f81ffdf26072fd262093c4127e656276564395f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 24 Sep 2026 09:11:58 +0200 Subject: [PATCH 1145/2286] tools/nolibc: fix the function argument order of calloc() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per the man page, the number of elements should be the first argument and the size of each one the second. Switch the arguments around. The behavior will not change, as both arguments are only multiplied with each other. Reported-by: Sean Christopherson Link: https://lore.kernel.org/lkml/aqQmCtPf_5M-BjRW@google.com/ Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260924-nolibc-calloc-v1-1-c49b383475d5@weissschuh.net --- tools/include/nolibc/stdlib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index cea8290ac0b99d..1c5a914c0f2317 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -145,7 +145,7 @@ void *malloc(size_t len) } static __attribute__((unused)) -void *calloc(size_t size, size_t nmemb) +void *calloc(size_t nmemb, size_t size) { size_t x; From 9df3e4316c2ee861074b631b8a232ae37ce093ab Mon Sep 17 00:00:00 2001 From: "Mario Limonciello (AMD)" Date: Sat, 26 Sep 2026 17:55:51 -0500 Subject: [PATCH 1146/2286] PM: sleep: Add resume event mapping for PMSG_POWEROFF PM_EVENT_POWEROFF (0x0800) and PMSG_POWEROFF were already defined in include/linux/pm.h, and pm_op(), pm_late_early_op() and pm_noirq_op() already dispatch PM_EVENT_POWEROFF to the same ops->poweroff*() callbacks as PM_EVENT_HIBERNATE. Add the matching entry to resume_event() so that when a poweroff suspend sequence is aborted (for example a device's ->poweroff_noirq() callback fails and the PM core rolls the already-suspended devices back), those devices are resumed with PMSG_RESTORE and their ->restore*() callbacks run. Map it to PMSG_RESTORE, mirroring the existing PM_EVENT_HIBERNATE handling. This is correct because both events suspend via ops->poweroff*(), whose counterpart is ops->restore*(). Mapping to PMSG_ON would be a no-op: it is what the default return already yields, and pm_op()/pm_late_early_op()/pm_noirq_op() have no PM_EVENT_ON case, so they would return NULL and silently skip every resume callback on the abort path. This completes the PMSG_POWEROFF infrastructure needed to support differentiating between hibernation and shutdown sequences when re-using callbacks for common code. Hibernation is started by writing a hibernation method (such as 'platform' 'shutdown', or 'reboot') to use into /sys/power/disk and writing 'disk' to /sys/power/state. Shutdown is initiated with the reboot() syscall with arguments on whether to halt the system or power it off. Tested-by: Eric Naim Signed-off-by: Mario Limonciello (AMD) [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260926225555.2916525-1-mario.limonciello@amd.com Signed-off-by: Rafael J. Wysocki --- drivers/base/power/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 4e0d6357a37cb7..88a31e09e4f3fa 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1482,6 +1482,7 @@ static pm_message_t resume_event(pm_message_t sleep_state) case PM_EVENT_QUIESCE: return PMSG_RECOVER; case PM_EVENT_HIBERNATE: + case PM_EVENT_POWEROFF: return PMSG_RESTORE; } return PMSG_ON; From 29671cabfc09afb1ee2426b21173cdde276a95ac Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 13:21:17 +0800 Subject: [PATCH 1147/2286] x86/hibernate: Ignore page-zero RAM in E820 checksum The legacy kexec_load path reconstructs the E820 map exported through sysfs. kexec-tools leaves the first 1 KiB unavailable for the real-mode transition, so a kernel entered through kexec_load can see conventional RAM starting at 0x400. A subsequent firmware boot reports the same RAM range starting at zero. The hibernation E820 checksum compares those byte representations and rejects the image, even though the maps agree from page one onwards. A QEMU guest showed this transition: firmware: RAM [0-0x9fbff] kexec_load: gap [0-0x3ff], RAM [0x400-0x9fbff] firmware: RAM [0-0x9fbff] The checksum covers e820_table_firmware, which trim_bios_range() leaves alone. The nosave regions come from e820_table instead, where trim_bios_range() has already turned conventional RAM in page zero into reserved memory, so page zero never occurs in the image, whatever the firmware map says about it. Canonicalise only the conventional-RAM portion below PAGE_SIZE before calculating the checksum. Preserve RESERVED, ACPI, NVS, UNUSABLE, PMEM and all other E820 types so that changes to exceptional mappings remain detectable. This is deliberately narrower than Marco Scardovi's June proposal to checksum only RAM and its opt-in relaxed_memmap successor. Rafael noted that ignoring non-RAM changes could hide moved ACPI or UEFI regions still used by the resumed kernel. This patch preserves every non-RAM entry and ignores only RAM within page zero, which common setup already reserves and excludes from the image. Bump RESTORE_MAGIC, since the checksum semantics change, so that an image written by a kernel without this change is refused by the magic check instead of being compared under different rules. In the same guest, with the page-zero difference kept, hibernation and resume now work after both a direct boot and a kexec_load boot. An image written by an unpatched kernel is refused at resume with "Unrecognized hibernate image header format!", and the firmware-booted kernel carries on. Fixes: 62a03defeabd ("PM / hibernate: Verify the consistent of e820 memory map by md5 digest") Reported-by: Roberto Ricci Signed-off-by: Matthias Goergens Link: https://lore.kernel.org/all/Z-hYWc9LtBU1Yhtg@desktop0a/ Link: https://lists.openwall.net/linux-kernel/2025/04/04/1372 Link: https://lore.kernel.org/all/CAJZ5v0jmOj0WBtMTvbnaD+2b0bTFowA=JWrqRzaaCYpHpai1Nw@mail.gmail.com/ Link: https://lore.kernel.org/all/20260623165724.10753-1-scardracs@disroot.org/ Link: https://patch.msgid.link/20260926052117.3613637-1-matthias.goergens@gmail.com Signed-off-by: Rafael J. Wysocki --- arch/x86/power/hibernate.c | 51 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/arch/x86/power/hibernate.c b/arch/x86/power/hibernate.c index a2294c1649f65e..dbb340fb24adaa 100644 --- a/arch/x86/power/hibernate.c +++ b/arch/x86/power/hibernate.c @@ -63,6 +63,25 @@ struct restore_data_record { unsigned long e820_checksum; }; +static bool trim_e820_page_zero_ram(struct e820_entry *entry) +{ + u64 lowmem_size; + + /* + * Page zero is BIOS-owned and registered as nosave. Boot loaders may + * therefore omit part of its conventional RAM entry without changing + * any memory available to the image. Preserve all other E820 types. + */ + if (entry->type != E820_TYPE_RAM || entry->addr >= PAGE_SIZE) + return true; + + lowmem_size = min_t(u64, entry->size, PAGE_SIZE - entry->addr); + entry->addr += lowmem_size; + entry->size -= lowmem_size; + + return entry->size; +} + /** * compute_e820_crc32 - calculate crc32 of a given e820 table * @@ -70,18 +89,38 @@ struct restore_data_record { * * Return: the resulting checksum */ -static inline u32 compute_e820_crc32(struct e820_table *table) +static u32 compute_e820_crc32(struct e820_table *table) { - int size = offsetof(struct e820_table, entries) + - sizeof(struct e820_entry) * table->nr_entries; + struct e820_entry entry; + u32 crc = ~0; + u32 nr_entries = 0; + u32 i; + + for (i = 0; i < table->nr_entries; i++) { + entry = table->entries[i]; + if (trim_e820_page_zero_ram(&entry)) + nr_entries++; + } + + crc = crc32_le(crc, (unsigned char const *)&nr_entries, + sizeof(nr_entries)); + + for (i = 0; i < table->nr_entries; i++) { + entry = table->entries[i]; + if (!trim_e820_page_zero_ram(&entry)) + continue; + + crc = crc32_le(crc, (unsigned char const *)&entry, + sizeof(entry)); + } - return ~crc32_le(~0, (unsigned char const *)table, size); + return ~crc; } #ifdef CONFIG_X86_64 -#define RESTORE_MAGIC 0x23456789ABCDEF02UL +#define RESTORE_MAGIC 0x23456789ABCDEF03UL #else -#define RESTORE_MAGIC 0x12345679UL +#define RESTORE_MAGIC 0x1234567AUL #endif /** From d0e7866390ad1ae2cf24c8d3bf9856e44fd135b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 28 Sep 2026 18:52:53 +0200 Subject: [PATCH 1148/2286] selinux: bounds-check role and user membership bitmaps at load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit role->dominates, role->types and user->roles are read as ebitmaps but their set bits are never checked against the role and type symbol tables. A bit naming an undefined value is silently accepted, and an out-of-range one is used to index those tables later; in particular role_bounds_sanity_check() passes such a bit straight to sym_name(), reading p->sym_val_to_name[] out of bounds on a crafted bounded role. Validate the three bitmaps in policydb_index() once every symbol table is populated (roles are read before types, so this cannot be done in role_read()). Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 13d88e47db3c9f..e0304297def4d0 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -707,6 +707,50 @@ static int sens_cat_index_check(void *key, void *datum, void *datap) return 0; } +static int role_index_check(void *key, void *datum, void *datap) +{ + const struct policydb *p = datap; + const struct role_datum *role = datum; + struct ebitmap_node *node; + u32 bit; + + ebitmap_for_each_positive_bit(&role->dominates, node, bit) { + if (!policydb_role_isvalid(p, bit + 1)) { + pr_err("SELinux: role %s declares dominance on undefined role %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + ebitmap_for_each_positive_bit(&role->types, node, bit) { + if (!policydb_type_isvalid(p, bit + 1)) { + pr_err("SELinux: role %s authorizes undefined type %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + return 0; +} + +static int user_index_check(void *key, void *datum, void *datap) +{ + const struct policydb *p = datap; + const struct user_datum *usr = datum; + struct ebitmap_node *node; + u32 bit; + + ebitmap_for_each_positive_bit(&usr->roles, node, bit) { + if (!policydb_role_isvalid(p, bit + 1)) { + pr_err("SELinux: user %s authorizes undefined role %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + return 0; +} + /* clang-format off */ static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = { common_index, @@ -836,6 +880,14 @@ static int policydb_index(struct policydb *p) goto out; } + rc = hashtab_map(&p->p_roles.table, role_index_check, p); + if (rc) + goto out; + + rc = hashtab_map(&p->p_users.table, user_index_check, p); + if (rc) + goto out; + rc = 0; out: return rc; From 167d70651df872adcb37ab8317b837eb3a47d824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 28 Sep 2026 18:52:51 +0200 Subject: [PATCH 1149/2286] selinux: validate user MLS range and default level at load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user_read() reads each user's MLS range and default level but, unlike range transitions and security contexts, never validates them. Validate usr->range and usr->dfltlevel in user_index_check() when MLS is enabled, once the level and category symbol tables are populated. Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index e0304297def4d0..e51293e3ca8c49 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -748,6 +748,20 @@ static int user_index_check(void *key, void *datum, void *datap) } } + if (p->mls_enabled) { + if (!mls_range_isvalid(p, &usr->range)) { + pr_err("SELinux: user %s has an invalid MLS range\n", + (const char *)key); + return -EINVAL; + } + + if (!mls_level_isvalid(p, &usr->dfltlevel)) { + pr_err("SELinux: user %s has an invalid MLS default level\n", + (const char *)key); + return -EINVAL; + } + } + return 0; } From b73b967b0fc44707f39faa4f298f1f9d9b7bfac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 28 Sep 2026 18:52:52 +0200 Subject: [PATCH 1150/2286] selinux: validate permissive and neveraudit map types at load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The permissive and neveraudit maps are read as ebitmaps and indexed by type value, but their set bits are never checked. A bit that is not a defined, non-attribute type is silently accepted. Validate every set bit of both maps in policydb_index() with policydb_simpletype_isvalid(). Signed-off-by: Christian Göttsche Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index e51293e3ca8c49..fe0c0792583f86 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -818,8 +818,9 @@ static inline void symtab_hash_eval(struct symtab *s) */ static int policydb_index(struct policydb *p) { + struct ebitmap_node *node; int i, rc; - u32 v; + u32 bit, v; if (p->mls_enabled) pr_debug( @@ -902,6 +903,23 @@ static int policydb_index(struct policydb *p) if (rc) goto out; + ebitmap_for_each_positive_bit(&p->permissive_map, node, bit) { + if (!policydb_simpletype_isvalid(p, bit)) { + pr_err("SELinux: permissive map refers to invalid type %u\n", + bit); + rc = -EINVAL; + goto out; + } + } + ebitmap_for_each_positive_bit(&p->neveraudit_map, node, bit) { + if (!policydb_simpletype_isvalid(p, bit)) { + pr_err("SELinux: neveraudit map refers to invalid type %u\n", + bit); + rc = -EINVAL; + goto out; + } + } + rc = 0; out: return rc; From 50e5282df427830fcd810e553f0e355d9cccb3e2 Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Wed, 30 Sep 2026 10:15:54 -0700 Subject: [PATCH 1151/2286] x86,fs/resctrl: Add event group descriptions for Intel Diamond Rapids Intel Diamond Rapids exposes Application Energy Telemetry through two new PMT aggregator GUIDs: one covering the "energy" feature, and one covering the "perf" feature, both documented in the Intel-PMT XML repository at https://github.com/intel/Intel-PMT The "perf" aggregator references two events not previously enumerated by resctrl: retired instructions (INST_RETIRED), and a count of unhalted core clock cycles perceived by hardware as contributing to instruction execution (PCNT). Add definitions for both aggregators, and add the two new events to the resctrl filesystem so they can be exposed to user space. Signed-off-by: Tony Luck Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Link: https://patch.msgid.link/20260930171554.12794-1-tony.luck@intel.com --- arch/x86/kernel/cpu/resctrl/intel_aet.c | 34 +++++++++++++++++++++++++ fs/resctrl/monitor.c | 2 ++ include/linux/resctrl_types.h | 2 ++ 3 files changed, 38 insertions(+) diff --git a/arch/x86/kernel/cpu/resctrl/intel_aet.c b/arch/x86/kernel/cpu/resctrl/intel_aet.c index c22c3cf5167d23..66cd8c2c7a51f3 100644 --- a/arch/x86/kernel/cpu/resctrl/intel_aet.c +++ b/arch/x86/kernel/cpu/resctrl/intel_aet.c @@ -125,9 +125,43 @@ static struct event_group perf_0x26557651 = { } }; +/* + * Link: https://github.com/intel/Intel-PMT/blob/main/xml/DMR/OOBMSM/RMID-ENERGY/dmr_aggregator.xml + */ +static struct event_group energy_0x47903099 = { + .pfname = "energy", + .guid = 0x47903099, + .num_rmid = 256, + .mmio_size = XML_MMIO_SIZE(256, 2, 3), + .num_events = 2, + .evts = { + EVT(PMT_EVENT_ENERGY, 0, 18), + EVT(PMT_EVENT_ACTIVITY, 1, 18), + } +}; + +/* + * Link: https://github.com/intel/Intel-PMT/blob/main/xml/DMR/OOBMSM/RMID-PERF/dmr_aggregator.xml + */ +static struct event_group perf_0x1415910a = { + .pfname = "perf", + .guid = 0x1415910a, + .num_rmid = 256, + .mmio_size = XML_MMIO_SIZE(256, 4, 3), + .num_events = 4, + .evts = { + EVT(PMT_EVENT_UNHALTED_REF_CYCLES, 0, 0), + EVT(PMT_EVENT_UNHALTED_CORE_CYCLES, 1, 0), + EVT(PMT_EVENT_INST_RETIRED, 2, 0), + EVT(PMT_EVENT_PCNT, 3, 0), + } +}; + static struct event_group *known_event_groups[] = { &energy_0x26696143, + &energy_0x47903099, &perf_0x26557651, + &perf_0x1415910a, }; #define for_each_event_group(_peg) \ diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c index 73413cb128eab4..04e4ea612b7c6d 100644 --- a/fs/resctrl/monitor.c +++ b/fs/resctrl/monitor.c @@ -1041,6 +1041,8 @@ struct mon_evt mon_event_all[QOS_NUM_EVENTS] = { MON_EVENT(PMT_EVENT_AUTO_C6_RES, "c6_res", RDT_RESOURCE_PERF_PKG, false), MON_EVENT(PMT_EVENT_UNHALTED_REF_CYCLES, "unhalted_ref_cycles", RDT_RESOURCE_PERF_PKG, false), MON_EVENT(PMT_EVENT_UOPS_RETIRED, "uops_retired", RDT_RESOURCE_PERF_PKG, false), + MON_EVENT(PMT_EVENT_INST_RETIRED, "inst_retired", RDT_RESOURCE_PERF_PKG, false), + MON_EVENT(PMT_EVENT_PCNT, "pcnt", RDT_RESOURCE_PERF_PKG, false), }; bool resctrl_enable_mon_event(enum resctrl_event_id eventid, bool any_cpu, diff --git a/include/linux/resctrl_types.h b/include/linux/resctrl_types.h index a5f56faa18d22f..e2e2cf3d9ff6a5 100644 --- a/include/linux/resctrl_types.h +++ b/include/linux/resctrl_types.h @@ -60,6 +60,8 @@ enum resctrl_event_id { PMT_EVENT_AUTO_C6_RES, PMT_EVENT_UNHALTED_REF_CYCLES, PMT_EVENT_UOPS_RETIRED, + PMT_EVENT_INST_RETIRED, + PMT_EVENT_PCNT, /* Must be the last */ QOS_NUM_EVENTS, From 706081c6b9327dd611dd72b20130a77f0f059b6e Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 00:14:43 +0200 Subject: [PATCH 1152/2286] OPP: Fix the return value in dev_pm_opp_get_of_node() kernel-doc dev_pm_opp_get_of_node() returns NULL when the OPP is invalid, but its kernel-doc says "else 0 on success", which was never true. Say NULL. Fixes: e2f4b5f8dc59 ("PM / OPP: Implement dev_pm_opp_get_of_node()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Viresh Kumar --- drivers/opp/of.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 2f3bbde9a9e174..ea93800795783f 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1409,7 +1409,7 @@ bool dev_pm_opp_of_has_required_opp(struct device *dev) * dev_pm_opp_get_of_node() - Gets the DT node corresponding to an opp * @opp: opp for which DT node has to be returned for * - * Return: DT node corresponding to the opp, else 0 on success. + * Return: DT node corresponding to the opp, else NULL. * * The caller needs to put the node with of_node_put() after using it. */ From 4b1f75be23c4fb0016a102d1cb108ba355c4c00d Mon Sep 17 00:00:00 2001 From: Shrikanth Hegde Date: Tue, 29 Sep 2026 22:17:12 +0530 Subject: [PATCH 1153/2286] sched/core: Fix context analysis errors in non-preferred CPU push Nathan reported build failure with clang23 when CONFIG_PREFERRED_CPU=y. kernel/sched/core.c:11298:3: error: releasing raw_spinlock 'rq_lockp(rq)' that was not held [-Werror,-Wthread-safety-analysis] 11298 | rq_unlock(rq, &rf); | ^ kernel/sched/core.c:11303:1: error: raw_spinlock 'rq_lockp(__this_rq())' is not held on every path through here [-Werror,-Wthread-safety-analysis] context_unsafe_alias(rq) was placed after rq_lock(), by which point it had already associated the lock with the original rq alias. Move the alias barrier before rq_lock() so that the changing rq alias is accounted for before acquiring the lock. Fixes: 74699f56ebcf ("sched/core: Push current task from non preferred CPU") Closes: https://lore.kernel.org/all/20260929121838.GA1814129@ax162/ Reported-by: Nathan Chancellor Signed-off-by: Shrikanth Hegde Signed-off-by: Peter Zijlstra (Intel) Tested-by: Nathan Chancellor # build Link: https://patch.msgid.link/20260929164712.1054883-1-sshegde@linux.ibm.com --- kernel/sched/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 0bb86a43a5920c..23677d76f9d241 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -11283,10 +11283,10 @@ static int sched_non_preferred_cpu_push_stop(void *arg) * safely bail out. */ cpu = select_fallback_rq(rq->cpu, p); + context_unsafe_alias(rq); rq_lock(rq, &rf); rq->npc_push_work_pending = false; update_rq_clock(rq); - context_unsafe_alias(rq); if (task_rq(p) == rq && task_on_rq_queued(p)) { struct rq *dest_rq = __migrate_task(rq, &rf, p, cpu); From 53bc5c556b82a3ca32b62ba349c4511f91526b9f Mon Sep 17 00:00:00 2001 From: Sechang Lim Date: Tue, 30 Jun 2026 08:47:37 +0000 Subject: [PATCH 1154/2286] sched: Set TIF_NEED_RESCHED before calling __trace_set_need_resched() set_tsk_need_resched() tests TIF_NEED_RESCHED, calls __trace_set_need_resched() if the flag is clear, then sets it via set_tsk_thread_flag(). A BPF raw_tp program attached to sched_set_need_resched executes synchronously inside __bpf_trace_run(). On return, __bpf_trace_run() drops the RCU lock with rcu_read_unlock_migrate(), which on the preempt-or-BH-disabled path calls set_need_resched_current() -> set_tsk_need_resched() again. set_tsk_thread_flag() follows the tracepoint call, so every re-entrant frame sees TIF_NEED_RESCHED clear and calls __trace_set_need_resched() again: BUG: TASK stack guard page was hit at ffffc9001224ff98 Oops: stack guard page: 0000 [#1] SMP KASAN PTI RIP: 0010:__bpf_trace_sched_set_need_resched_tp+0x1c/0x190 Call Trace: trace_sched_set_need_resched_tp+0x110/0x130 set_tsk_need_resched include/linux/sched.h:2076 set_need_resched_current include/linux/sched.h:2094 rcu_read_unlock_special+0x43a/0x440 __rcu_read_unlock+0x9e/0x120 rcu_read_unlock_migrate+0xa9/0x240 __bpf_trace_run+0x131/0x180 bpf_trace_run3+0x333/0x430 __bpf_trace_sched_set_need_resched_tp+0x13a/0x190 trace_sched_set_need_resched_tp+0x110/0x130 set_tsk_need_resched include/linux/sched.h:2076 ... __resched_curr() has the same ordering, firing the tracepoint before setting the flag via set_ti_thread_flag() or set_nr_and_not_polling(). Fix it for consistency. Replace the separate test_tsk_thread_flag() + set_tsk_thread_flag() pair in set_tsk_need_resched() with test_and_set_tsk_thread_flag(). In __resched_curr(), move the tracepoint call after the flag is set in each path. Fixes: adcc3bfa8806 ("sched: Adapt sched tracepoints for RV task model") Signed-off-by: Sechang Lim Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Andrea Righi Acked-by: Gabriele Monaco Link: https://patch.msgid.link/20260630084750.2792851-1-rhkrqnwk98@gmail.com --- include/linux/sched.h | 5 ++--- kernel/sched/core.c | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h index 48639b0c75d08f..7b91cae4a59cd5 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -2105,10 +2105,9 @@ static inline int test_tsk_thread_flag(struct task_struct *tsk, int flag) static inline void set_tsk_need_resched(struct task_struct *tsk) { - if (tracepoint_enabled(sched_set_need_resched_tp) && - !test_tsk_thread_flag(tsk, TIF_NEED_RESCHED)) + if (!test_and_set_tsk_thread_flag(tsk, TIF_NEED_RESCHED) && + tracepoint_enabled(sched_set_need_resched_tp)) __trace_set_need_resched(tsk, TIF_NEED_RESCHED); - set_tsk_thread_flag(tsk,TIF_NEED_RESCHED); } static inline void clear_tsk_need_resched(struct task_struct *tsk) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 23677d76f9d241..71d3c948e14ae0 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -1196,6 +1196,7 @@ static void __resched_curr(struct rq *rq, int tif) { struct task_struct *curr = rq->curr; struct thread_info *cti = task_thread_info(curr); + bool need_ipi; int cpu; lockdep_assert_rq_held(rq); @@ -1212,15 +1213,17 @@ static void __resched_curr(struct rq *rq, int tif) cpu = cpu_of(rq); - trace_sched_set_need_resched_tp(curr, cpu, tif); if (cpu == smp_processor_id()) { set_ti_thread_flag(cti, tif); if (tif == TIF_NEED_RESCHED) set_preempt_need_resched(); + trace_sched_set_need_resched_tp(curr, cpu, tif); return; } - if (set_nr_and_not_polling(cti, tif)) { + need_ipi = set_nr_and_not_polling(cti, tif); + trace_sched_set_need_resched_tp(curr, cpu, tif); + if (need_ipi) { if (tif == TIF_NEED_RESCHED) smp_send_reschedule(cpu); } else { From c8fc4136fd3c58458592b77168f0916d08dbea5a Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 29 Sep 2026 18:54:40 +0200 Subject: [PATCH 1155/2286] sched/fair: Honor asymmetric SMT priority in idle selection POWER7 uses SD_ASYM_PACKING at the shared-capacity SMT level to order hardware threads, and NVIDIA Olympus benefits from the same policy. Idle CPU selection does not consult that order, so a task can wake on an arbitrary sibling and remain there until load balancing corrects the placement. On these systems, that initial choice can prevent the core from entering its preferred lower-thread resource mode and cause a large and persistent performance loss. When idle selection finds an available CPU in an SMT core, choose the highest-priority available sibling. On SMT2 Olympus this only changes selection on fully idle cores. A partially idle core has only one available CPU. On wider SMT systems such as POWER7, it also fills available siblings in priority order while the core is partially busy. Apply the preference to idle-core and idle-CPU scans, asymmetric-capacity scans, target, previous, recently-used CPU fast paths and the slow path. Inspect the lowest scheduling domain directly, but require both CPUs to share its span because isolcpus can split hardware siblings across scheduling domains. Keep physical-core capacity selection independent from SMT sibling ordering. SD_ASYM_CPUCAPACITY first selects among cores with different maximum capacities, then SD_ASYM_PACKING selects the preferred available sibling inside the chosen core, whose siblings continue to share equal capacity. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Srikar Dronamraju Reviewed-by: K Prateek Nayak Reviewed-by: Vincent Guittot Reviewed-by: Kayra Cizmeci Tested-by: K Prateek Nayak Tested-by: Kayra Cizmeci Tested-by: Breno Leitao Link: https://patch.msgid.link/20260929165538.726616-2-arighi@nvidia.com --- kernel/sched/fair.c | 85 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 1c687c3c70f116..b42a0ca2a69e98 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -8575,6 +8575,35 @@ static inline bool test_idle_cores(int cpu) return false; } +/* + * Redirect a CPU to a higher-priority available sibling in its SMT domain, + * subject to task affinity. + */ +static inline int select_idle_smt_cpu(struct task_struct *p, int cpu) +{ + struct sched_domain *sd; + int best = cpu; + int sibling; + + if (!sched_smt_active()) + return cpu; + + sd = rcu_dereference_all(cpu_rq(cpu)->sd); + if (!sd || !(sd->flags & SD_SHARE_CPUCAPACITY) || + !(sd->flags & SD_ASYM_PACKING)) + return cpu; + + for_each_cpu_and(sibling, sched_domain_span(sd), p->cpus_ptr) { + if (sibling == best || !choose_idle_cpu(sibling, p)) + continue; + + if (sched_asym_prefer(sibling, best)) + best = sibling; + } + + return best; +} + /* * Scans the local SMT mask to see if the entire core is idle, and records this * information in sd_balance_shared->has_idle_cores. @@ -8959,7 +8988,7 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) if (choose_idle_cpu(target, p) && asym_fits_cpu(task_util, util_min, util_max, target)) - return target; + goto select_smt_priority; /* * If the previous CPU is cache affine and idle, don't be stupid: @@ -8969,8 +8998,10 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) asym_fits_cpu(task_util, util_min, util_max, prev)) { if (!static_branch_unlikely(&sched_cluster_active) || - cpus_share_resources(prev, target)) - return prev; + cpus_share_resources(prev, target)) { + target = prev; + goto select_smt_priority; + } prev_aff = prev; } @@ -8988,7 +9019,8 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) prev == smp_processor_id() && this_rq()->nr_running <= 1 && asym_fits_cpu(task_util, util_min, util_max, prev)) { - return prev; + target = prev; + goto select_smt_priority; } /* Check a recently used CPU as a potential idle candidate: */ @@ -9002,8 +9034,10 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) asym_fits_cpu(task_util, util_min, util_max, recent_used_cpu)) { if (!static_branch_unlikely(&sched_cluster_active) || - cpus_share_resources(recent_used_cpu, target)) - return recent_used_cpu; + cpus_share_resources(recent_used_cpu, target)) { + target = recent_used_cpu; + goto select_smt_priority; + } } else { recent_used_cpu = -1; @@ -9025,7 +9059,11 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) */ if (sd) { i = select_idle_capacity(p, sd, target); - return ((unsigned)i < nr_cpumask_bits) ? i : target; + if ((unsigned int)i < nr_cpumask_bits) { + target = i; + goto select_smt_priority; + } + return target; } } @@ -9038,14 +9076,18 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) if (!has_idle_core && cpus_share_cache(prev, target)) { i = select_idle_smt(p, sd, prev); - if ((unsigned int)i < nr_cpumask_bits) - return i; + if ((unsigned int)i < nr_cpumask_bits) { + target = i; + goto select_smt_priority; + } } } i = select_idle_cpu(p, sd, has_idle_core, target); - if ((unsigned)i < nr_cpumask_bits) - return i; + if ((unsigned int)i < nr_cpumask_bits) { + target = i; + goto select_smt_priority; + } /* * For cluster machines which have lower sharing cache like L2 or @@ -9053,12 +9095,19 @@ static int select_idle_sibling(struct task_struct *p, int prev, int target) * first. But prev_cpu or recent_used_cpu may also be a good candidate, * use them if possible when no idle CPU found in select_idle_cpu(). */ - if ((unsigned int)prev_aff < nr_cpumask_bits) - return prev_aff; - if ((unsigned int)recent_used_cpu < nr_cpumask_bits) - return recent_used_cpu; + if ((unsigned int)prev_aff < nr_cpumask_bits) { + target = prev_aff; + goto select_smt_priority; + } + if ((unsigned int)recent_used_cpu < nr_cpumask_bits) { + target = recent_used_cpu; + goto select_smt_priority; + } return target; + +select_smt_priority: + return select_idle_smt_cpu(p, target); } /** @@ -9735,8 +9784,10 @@ select_task_rq_fair(struct task_struct *p, int prev_cpu, int wake_flags) } /* Slow path */ - if (unlikely(sd)) - return sched_balance_find_dst_cpu(sd, p, cpu, prev_cpu, sd_flag); + if (unlikely(sd)) { + new_cpu = sched_balance_find_dst_cpu(sd, p, cpu, prev_cpu, sd_flag); + return select_idle_smt_cpu(p, new_cpu); + } /* Fast path */ if (wake_flags & WF_TTWU) From 648d44bda7314ffb5805f2bea8910b4cdccaecd0 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Tue, 29 Sep 2026 18:54:41 +0200 Subject: [PATCH 1156/2286] sched/topology: Add asymmetric SMT packing override Architectures can use SD_ASYM_PACKING to describe preferred CPU ordering at the SMT scheduling domain. Some systems benefit from this policy, but their firmware cannot describe the preference and inferring it from the CPU model would embed a platform-specific policy in the kernel. Add sched_smt_asym_packing=on boot option to force SD_ASYM_PACKING at the SMT level. Omitting the option preserves the topology provided by the architecture or firmware. Apply the override centrally to domains with SD_SHARE_CPUCAPACITY so it also covers architectures with custom SMT topology callbacks, including powerpc. When enabled, priority remains defined by arch_asym_cpu_priority(). The weak default prefers lower-numbered logical CPUs, while architecture overrides remain authoritative. Siblings with equal priorities remain unordered. In particular, x86 with CONFIG_SCHED_MC_PRIO normally gives both SMT siblings the same core priority, so enabling the option there does not prioritize a sibling. Signed-off-by: Andrea Righi Signed-off-by: Peter Zijlstra (Intel) Tested-by: Breno Leitao Link: https://patch.msgid.link/20260929165538.726616-3-arighi@nvidia.com --- .../admin-guide/kernel-parameters.txt | 12 ++++++++++++ kernel/sched/topology.c | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..8f53d66afbf9aa 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -6836,6 +6836,18 @@ Kernel parameters solution to mutex-based priority inversion. Format: + sched_smt_asym_packing= [KNL,SMP] + Format: on + Force asymmetric packing at the SMT scheduling domain. + Idle CPU selection prefers siblings with a higher + arch_asym_cpu_priority(). The default implementation + prefers lower-numbered logical CPUs, but architecture + overrides remain authoritative. Equal priorities do + not establish a sibling preference. For example, x86 + with CONFIG_SCHED_MC_PRIO normally assigns the same + core priority to both SMT siblings, so this option + does not favor either sibling. + sched_verbose [KNL,EARLY] Enables verbose scheduler debug messages. schedstats= [KNL,X86] Enable or disable scheduled statistics. diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c index 0248227d983a71..dd2b1d70ec9d8a 100644 --- a/kernel/sched/topology.c +++ b/kernel/sched/topology.c @@ -32,6 +32,20 @@ static int __init sched_debug_setup(char *str) } early_param("sched_verbose", sched_debug_setup); +#ifdef CONFIG_SCHED_SMT +static bool sched_smt_asym_packing __read_mostly; + +static int __init setup_sched_smt_asym_packing(char *str) +{ + if (strcmp(str, "on")) + return 0; + + sched_smt_asym_packing = true; + return 1; +} +__setup("sched_smt_asym_packing=", setup_sched_smt_asym_packing); +#endif + static inline bool sched_debug(void) { return sched_debug_verbose; @@ -1950,6 +1964,10 @@ sd_init(struct sched_domain_topology_level *tl, if (WARN_ONCE(sd_flags & ~TOPOLOGY_SD_FLAGS, "wrong sd_flags in topology description\n")) sd_flags &= TOPOLOGY_SD_FLAGS; +#ifdef CONFIG_SCHED_SMT + if (sched_smt_asym_packing && (sd_flags & SD_SHARE_CPUCAPACITY)) + sd_flags |= SD_ASYM_PACKING; +#endif sd_flags |= asym_cpu_capacity_classify(sd_span, cpu_map); *sd = (struct sched_domain){ From 791b1760accdc15248929f9b767410a76be199b1 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 16:38:12 +0200 Subject: [PATCH 1157/2286] irq_work: Update a comment regarding CPU hotplug invocation hotplug_cfd() no longer exists and the flow changed a bit since the rework of CPU hotplug as of commit 31487f8328f20 ("smp/cfd: Convert core to hotplug state machine"). Update the comment to reflect reality as of today. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911143815.997254-2-bigeasy@linutronix.de --- kernel/irq_work.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kernel/irq_work.c b/kernel/irq_work.c index f7e2dc2c30c621..9f19c05c9962c3 100644 --- a/kernel/irq_work.c +++ b/kernel/irq_work.c @@ -252,10 +252,7 @@ static void irq_work_run_list(struct llist_head *list) irq_work_single(work); } -/* - * hotplug calls this through: - * hotplug_cfd() -> flush_smp_call_function_queue() - */ +/* CPU hotplug calls this through smpcfd_dying_cpu() */ void irq_work_run(void) { irq_work_run_list(this_cpu_ptr(&raised_list)); From 40dcc9bdbef3d93a516c8ee32cb1b50eddeeebcc Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 16:38:13 +0200 Subject: [PATCH 1158/2286] irq_work: Flush lazy work CPU down on PREEMPT_RT PREEMPT_RT invokes IRQ_WORK_LAZY callbacks and callbacks which are not explicitly marked IRQ_WORK_HARD_IRQ from thread context. If a CPU gets shutdown, the irq_work is flushed during smpcfd_dying_cpu() which is invoked on the target CPU with disabled interrupts. The callbacks enqueued on lazy_list on PREEMPT_RT are not flushed because they require thread context. The callbacks remain and get processed once the CPU gets back online. per-CPU irq_work items continue to work for enqueues on their local-CPU, only the work on the "offline" CPU get stuck. "Single" irq_work are worse because they remain "claimed" and can not be used again. One "symptom" is the late printk() in CPU down path which enqueues the irq_work for printing but it never gets scheduled. Further prints don't print to the console because the irq_work is "pending" of the offline CPU. Another problem is the canceling/ flushing of the irq_work which is stuck. Since the irq_work will not continue, the task waiting for its completion will block waiting. While looking through the users of irq_work, it does not matter if the callback is enqueued on another CPU. This makes it possible to invoke the callbacks from a remote CPU by smpcfd_dead_cpu(). This is invoked from the control CPU with enabled interrupts shortly after the hotplugged CPU is shutdown so the llist can be accessed safely. Add irq_work_run_cpu() which invokes the irq_work callbacks of the specified dead CPU. Invoke it from smpcfd_dead_cpu(). Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911143815.997254-3-bigeasy@linutronix.de --- include/linux/irq_work.h | 2 ++ kernel/irq_work.c | 11 +++++++++++ kernel/smp.c | 1 + 3 files changed, 14 insertions(+) diff --git a/include/linux/irq_work.h b/include/linux/irq_work.h index c5afd053ae32c2..7184e12739653c 100644 --- a/include/linux/irq_work.h +++ b/include/linux/irq_work.h @@ -59,6 +59,7 @@ void irq_work_sync(struct irq_work *work); #include void irq_work_run(void); +void irq_work_run_cpu(unsigned int cpu); bool irq_work_needs_cpu(void); void irq_work_single(void *arg); @@ -67,6 +68,7 @@ void arch_irq_work_raise(void); #else static inline bool irq_work_needs_cpu(void) { return false; } static inline void irq_work_run(void) { } +static inline void irq_work_run_cpu(unsigned int cpu) { } static inline void irq_work_single(void *arg) { } #endif diff --git a/kernel/irq_work.c b/kernel/irq_work.c index 9f19c05c9962c3..73eabcbdcd50c7 100644 --- a/kernel/irq_work.c +++ b/kernel/irq_work.c @@ -263,6 +263,17 @@ void irq_work_run(void) } EXPORT_SYMBOL_GPL(irq_work_run); +void irq_work_run_cpu(unsigned int cpu) +{ + if (WARN_ON_ONCE(!cpumask_test_cpu(cpu, cpu_dying_mask))) + return; + + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + return; + + irq_work_run_list(per_cpu_ptr(&lazy_list, cpu)); +} + void irq_work_tick(void) { struct llist_head *raised = this_cpu_ptr(&raised_list); diff --git a/kernel/smp.c b/kernel/smp.c index b696bcc60c08fc..8f530a094c304f 100644 --- a/kernel/smp.c +++ b/kernel/smp.c @@ -87,6 +87,7 @@ int smpcfd_dead_cpu(unsigned int cpu) free_cpumask_var(cfd->cpumask); free_cpumask_var(cfd->cpumask_ipi); + irq_work_run_cpu(cpu); return 0; } From 4a3b51aab6e25244d97936aa65e6d5425adf98e1 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 11 Sep 2026 16:38:14 +0200 Subject: [PATCH 1159/2286] smpboot: Don't park the thread if work is pending Once a smpboot thread gets work assigned it receives a wake up. If the thread had no chance to run and receives a parking requesst during a CPU-hotplug event then it is parked without running the callback first. As a result the enqueued work remains stuck until the CPU gets back online. There is nothing wrong with invoking the thread function first and parking in the following iteration. This would ensure that the callbacks are processed before the shutdown. Honor the park request once thread function does not need to run. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260911143815.997254-4-bigeasy@linutronix.de --- kernel/smpboot.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/smpboot.c b/kernel/smpboot.c index 4503b60ce9bd2a..3f60e8c6dd3011 100644 --- a/kernel/smpboot.c +++ b/kernel/smpboot.c @@ -103,6 +103,7 @@ static int smpboot_thread_fn(void *data) { struct smpboot_thread_data *td = data; struct smp_hotplug_thread *ht = td->ht; + bool should_run; while (1) { set_current_state(TASK_INTERRUPTIBLE); @@ -117,7 +118,8 @@ static int smpboot_thread_fn(void *data) return 0; } - if (kthread_should_park()) { + should_run = td->status == HP_THREAD_ACTIVE && ht->thread_should_run(td->cpu); + if (kthread_should_park() && !should_run) { __set_current_state(TASK_RUNNING); preempt_enable(); if (ht->park && td->status == HP_THREAD_ACTIVE) { @@ -151,7 +153,7 @@ static int smpboot_thread_fn(void *data) continue; } - if (!ht->thread_should_run(td->cpu)) { + if (!should_run) { preempt_enable_no_resched(); schedule(); } else { From 5b5224220747029bbaee8ab851cc6c599c48eded Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Wed, 12 Aug 2026 20:56:38 +0200 Subject: [PATCH 1160/2286] refcount: Use CONFIG_BUG_ON_DATA_CORRUPTION for UAF-related errors Some refcount issues are not necessarily associated with memory corruption, but REFCOUNT_ADD_UAF suggests that a UAF either just happened or is about to happen. REFCOUNT_SUB_UAF is also an indicator that reference counting is wrong, and suggests (less strongly) that a UAF access might have happened recently. In these cases, BUG() is appropriate if CONFIG_BUG_ON_DATA_CORRUPTION is set. Signed-off-by: Jann Horn Signed-off-by: Peter Zijlstra (Intel) Acked-by: Will Deacon Link: https://patch.msgid.link/20260812-refcount-bug-on-data-corruption-v1-1-c6da63566101@google.com --- lib/refcount.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/refcount.c b/lib/refcount.c index a207a8f22b3ca3..c0f0dc5296eb47 100644 --- a/lib/refcount.c +++ b/lib/refcount.c @@ -10,6 +10,15 @@ #define REFCOUNT_WARN(str) WARN_ONCE(1, "refcount_t: " str ".\n") +#ifdef CONFIG_BUG_ON_DATA_CORRUPTION +#define REFCOUNT_CORRUPTION(str) ({ \ + pr_err("refcount_t: " str ".\n"); \ + BUG(); \ +}) +#else +#define REFCOUNT_CORRUPTION(str) REFCOUNT_WARN(str) +#endif + void refcount_warn_saturate(refcount_t *r, enum refcount_saturation_type t) { refcount_set(r, REFCOUNT_SATURATED); @@ -22,10 +31,10 @@ void refcount_warn_saturate(refcount_t *r, enum refcount_saturation_type t) REFCOUNT_WARN("saturated; leaking memory"); break; case REFCOUNT_ADD_UAF: - REFCOUNT_WARN("addition on 0; use-after-free"); + REFCOUNT_CORRUPTION("addition on 0; use-after-free"); break; case REFCOUNT_SUB_UAF: - REFCOUNT_WARN("underflow; use-after-free"); + REFCOUNT_CORRUPTION("underflow; use-after-free"); break; case REFCOUNT_DEC_LEAK: REFCOUNT_WARN("decrement hit 0; leaking memory"); @@ -84,7 +93,7 @@ bool refcount_dec_not_one(refcount_t *r) new = val - 1; if (new > val) { - WARN_ONCE(new > val, "refcount_t: underflow; use-after-free.\n"); + REFCOUNT_CORRUPTION("underflow; use-after-free"); return true; } From d92b5ac5aeba1ffc734576e30a5bca882722de76 Mon Sep 17 00:00:00 2001 From: Lance Yang Date: Thu, 1 Oct 2026 16:15:55 +0800 Subject: [PATCH 1161/2286] x86/mm: Fix spurious warning on __add_pages() failure __add_pages() can fail if we run out of memory. Callers already handle that. Do not WARN_ON_ONCE() just because an allocation failed. Return the error instead, and only update the end-of-memory variables after __add_pages() succeeds. [ dhansen: imperative voice changelog fixups ] Fixes: 10f22dde556d ("x86: arch/x86/mm/init_64.c printk fixes") Reported-by: David Hildenbrand Suggested-by: Muchun Song Signed-off-by: Lance Yang Signed-off-by: Dave Hansen Reviewed-by: David Hildenbrand (Arm) Acked-by: Muchun Song Link: https://lore.kernel.org/all/d4fac8af-fd71-47a2-bfe9-3c6559b92209@kernel.org/ Link: https://lore.kernel.org/all/203892F4-B04A-4F69-A1B3-DC1619176C67@linux.dev/ Link: https://patch.msgid.link/20261001081555.35485-1-lance.yang@linux.dev --- arch/x86/mm/init_64.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c index 70e682180291aa..8b6d86fb5a42ee 100644 --- a/arch/x86/mm/init_64.c +++ b/arch/x86/mm/init_64.c @@ -970,7 +970,8 @@ int add_pages(int nid, unsigned long start_pfn, unsigned long nr_pages, return -ERANGE; ret = __add_pages(nid, start_pfn, nr_pages, params); - WARN_ON_ONCE(ret); + if (ret) + return ret; /* * Special case: add_pages() is called by memremap_pages() for adding device From f73411ae5be68b19a36a4ddc0e4e9a399c18f7fa Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Thu, 17 Sep 2026 08:11:52 +0530 Subject: [PATCH 1162/2286] arm64/cpufeature: Drop redundant define(ID_AA64MMFR0..._SUPPORTED_LPA2) ID_AA64MMFR0_EL1_TGRAN_2_SUPPORTED_LPA2 is always defined as 0x3. Hence any defined() test on the macro always going to be evaluated as true. Hence the very test itself is redundant and can be dropped off safely. Although similar defined(ID_AA64MMFR0_EL1_TGRAN_LPA2) test is retained back as the macro does depend on the configured granule size. Cc: Will Deacon Cc: Ryan Roberts Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpufeature.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 1f694a61d3b5db..eb57c62c5c8235 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1931,7 +1931,7 @@ static bool has_nv1(const struct arm64_cpu_capabilities *entry, int scope) is_midr_in_range_list(nv1_ni_list))); } -#if defined(ID_AA64MMFR0_EL1_TGRAN_LPA2) && defined(ID_AA64MMFR0_EL1_TGRAN_2_SUPPORTED_LPA2) +#if defined(ID_AA64MMFR0_EL1_TGRAN_LPA2) static bool has_lpa2_at_stage1(u64 mmfr0) { unsigned int tgran; From eb0fde98fee407118bfcb10e38400494e0699201 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 8 Sep 2026 15:56:22 +0100 Subject: [PATCH 1163/2286] arm64: mpam: Document when to set arm64.nompam arm64.nompam skips the MPAM2_EL2 and MPAMHCR_EL2 writes in finalise_el2_state and leaves the ARM64_MPAM cpucap unset. Where EL3 firmware has cleared MPAM3_EL3.TRAPLOWER, the EL2 trap controls are left unwritten, at reset values that are UNKNOWN, and KVM still hides MPAM from the guest but no longer enables the traps that stop a guest from using it. KVM never saves or restores MPAM0_EL1, MPAM1_EL1 and MPAMSM_EL1, so what one guest writes reaches the next guest and the host. Nor can the kernel make the EL2 writes unconditionally: they trap to EL3 on the firmware the option exists for, and TRAPLOWER cannot be read below EL3. Say so in mpam.rst, and add the rule and a pointer to the kernel-parameters entry. Suggested-by: Ben Horgan Link: https://lore.kernel.org/all/CA+EHjTxeWxZiuSmnKLGLxTBXP4oJT7-LuffbPAyCSZZ5TW=5Ew@mail.gmail.com/ Reviewed-by: Bradley Morgan Signed-off-by: Fuad Tabba Reviewed-by: Ben Horgan Signed-off-by: Catalin Marinas --- .../admin-guide/kernel-parameters.txt | 3 ++- Documentation/arch/arm64/mpam.rst | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 33cd30996e47ef..fdd671c7057560 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -575,7 +575,8 @@ Kernel parameters Set instructions support arm64.nompam [ARM64] Unconditionally disable Memory Partitioning And - Monitoring support + Monitoring support. Only for a machine that does not + boot without it. See Documentation/arch/arm64/mpam.rst arm64.nomte [ARM64] Unconditionally disable Memory Tagging Extension support diff --git a/Documentation/arch/arm64/mpam.rst b/Documentation/arch/arm64/mpam.rst index 67fe515ed501c9..e445f4078ee1a1 100644 --- a/Documentation/arch/arm64/mpam.rst +++ b/Documentation/arch/arm64/mpam.rst @@ -87,6 +87,32 @@ The supported features are: MBWU monitors can be exposed to the user after support for more monitoring scopes is added to resctrl. +Command line parameters +======================= + +arm64.nompam +------------ +Firmware controls MPAM through two bits of MPAM3_EL3. MPAMEN enables +it: while set, the PARTID and PMG in the MPAMn_ELx registers label the +CPU's memory requests. TRAPLOWER, which resets to 1, traps accesses to +the MPAM system registers from the lower exception levels to EL3. +Firmware must clear it, or handle the trap and emulate MPAM as disabled. +Where it does neither, the CPUs still advertise MPAM in the ID registers, +the kernel's MPAM register accesses trap to EL3, and the boot fails. +``arm64.nompam`` exists for that firmware: it makes the kernel treat the +CPUs as not implementing MPAM, so no MPAM system register is accessed. +Set it only on a machine that does not boot without it. + +It is not a way to turn MPAM off. Where firmware has cleared TRAPLOWER, +the option leaves the trap controls in MPAM2_EL2 and MPAMHCR_EL2 +unwritten, and their reset values are UNKNOWN. KVM still hides MPAM from +guests but no longer enables the traps that stop a guest from using it, +so a guest may be able to read and write MPAM0_EL1, MPAM1_EL1 and +MPAMSM_EL1. KVM does not save or restore them, so what one guest writes +is still there when the next guest runs on that CPU, and when the host +does. With MPAMEN set, EL0 and EL1 requests then carry that PARTID and +PMG. + Reporting Bugs ============== If you are not seeing the counters or controls you expect please share the From 1a58c47fae2a3982b38ec6348c739a7c60833189 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 11 Sep 2026 09:47:05 -0700 Subject: [PATCH 1164/2286] PCI/AER: Map a raw AER Capability image field by field struct aer_capability_regs is not the hardware layout: the embedded struct pcie_tlp_log spans 60 bytes where the Header Log it stands in for is 16, so a flat copy misplaces everything behind it. extlog_print_pcie(), ghes_handle_aer() and cxl_rch_get_aer_info() work around that by stopping at the Header Log, dropping everything past offset 44. Flit mode is never decoded either: the Flit bit and Logged TLP Size sit at offset 0x18, unread, so the Flit DWORDs at 0x38 are lost and the log prints as non-Flit. Add aer_cap_regs_unpack() to map the registers individually, taking the TLP Log layout from the Flit bit as pcie_read_tlp_log() does. Clamp the logged length: Logged TLP Size is 5 bits wide, so an untrusted value reaches 31 where dw[] holds 14 and pcie_print_tlp_log() walks header_len unbounded. Take the image little-endian, as both sources are: CPER by definition, PCIe registers on the wire. Read it with get_unaligned_le32(), since a CPER section sits wherever firmware put it, and take the two source IDs out of the Error Source Identification register by bit position rather than byte offset. cxl_rch_get_aer_info() uses readl(), so it hands back __le32. Convert all three callers. cxl_rch_get_aer_info() now reads the whole MMIO block, so size its ioremap by the hardware registers (96 bytes) rather than by sizeof(struct aer_capability_regs) (100). Shrinking the mapping is safe: the read it replaces stopped at 44 bytes. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260904172337.1409775-1-dave.jiang@intel.com?part=5 Link: https://lore.kernel.org/linux-cxl/CAJZ5v0hDv11cPuztPZsaDd7uwD_49KznJy=tzuRO+dZc=CnAEQ@mail.gmail.com/ Assisted-by: LLM Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260911164705.841407-1-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 14 +----- drivers/acpi/apei/ghes.c | 15 +------ drivers/cxl/core/ras_rch.c | 27 ++++-------- drivers/pci/pcie/tlp.c | 88 ++++++++++++++++++++++++++++++++++++++ include/linux/aer.h | 9 ++++ 5 files changed, 107 insertions(+), 46 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 9e61354a807b33..bf5f7b1a9aea77 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -156,19 +156,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, aer_severity = cper_severity_to_aer(severity); - /* - * struct pcie_tlp_log is larger than the hardware layout, so aer_info - * only maps onto the struct up to the four Header Log DWORDs. Copy that - * much, then place the TLP Prefix Log from where the hardware keeps it. - * Everything else stays zero: nothing reads root_command, root_status or - * the error source IDs, and header_len and flit are software-only. - */ - memcpy(&aer_regs, pcie_err->aer_info, - offsetof(struct aer_capability_regs, header_log) + - PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); - memcpy(aer_regs.header_log.prefix, - pcie_err->aer_info + PCI_ERR_PREFIX_LOG, - sizeof(aer_regs.header_log.prefix)); + aer_cap_regs_unpack(&aer_regs, pcie_err->aer_info, sizeof(pcie_err->aer_info)); domain = pcie_err->device_id.segment; bus = pcie_err->device_id.bus; diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 08c985e729d6c7..ade1cc8c9f8d14 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -668,20 +668,7 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) if (!aer_info) return; - /* - * Map aer_info onto the struct as extlog_print_pcie() does: - * copy up to the four Header Log DWORDs, then place the TLP - * Prefix Log from where the hardware keeps it. The rest stays - * zero, so firmware cannot drive the pcie_print_tlp_log() loop - * over dw[] out of bounds. - */ - memset(aer_info, 0, sizeof(struct aer_capability_regs)); - memcpy(aer_info, pcie_err->aer_info, - offsetof(struct aer_capability_regs, header_log) + - PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); - memcpy(aer_info->header_log.prefix, - pcie_err->aer_info + PCI_ERR_PREFIX_LOG, - sizeof(aer_info->header_log.prefix)); + aer_cap_regs_unpack(aer_info, pcie_err->aer_info, sizeof(pcie_err->aer_info)); aer_recover_queue(pcie_err->device_id.segment, pcie_err->device_id.bus, diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c index e0e01aa5eba6cd..50abbdc80722ed 100644 --- a/drivers/cxl/core/ras_rch.c +++ b/drivers/cxl/core/ras_rch.c @@ -19,7 +19,7 @@ void cxl_dport_map_rch_aer(struct cxl_dport *dport) aer_phys = aer_cap + dport->rcrb.base; dport->regs.dport_aer = devm_cxl_iomap_block(host, aer_phys, - sizeof(struct aer_capability_regs)); + PCIE_AER_CAP_HW_SIZE); } } @@ -58,31 +58,20 @@ void cxl_disable_rch_root_ints(struct cxl_dport *dport) static bool cxl_rch_get_aer_info(void __iomem *aer_base, struct aer_capability_regs *aer_regs) { - /* - * Bound the copy to the physically-defined AER registers (header - * through the 16-byte Header Log). struct aer_capability_regs is a - * software layout whose embedded struct pcie_tlp_log is larger than - * the on-wire AER capability; copying sizeof(*aer_regs) would - * over-read the RCRB-mapped MMIO block. - */ - int read_cnt = (PCI_ERR_HEADER_LOG + 16) / sizeof(u32); - u32 *aer_regs_buf = (u32 *)aer_regs; - int n; + /* A flat copy cannot fill the struct; aer_cap_regs_unpack() places it. */ + __le32 raw[PCIE_AER_CAP_HW_SIZE / sizeof(__le32)]; if (!aer_base) return false; /* - * Zero the destination so the software-only tail fields - * (e.g. header_log.header_len) are deterministic rather than - * left as uninitialized stack, which could drive a bogus loop - * length in pcie_print_tlp_log(). + * Use readl() to guarantee 32-bit accesses; it returns host order, so + * put the registers back little-endian for aer_cap_regs_unpack(). */ - memset(aer_regs, 0, sizeof(*aer_regs)); + for (int n = 0; n < ARRAY_SIZE(raw); n++) + raw[n] = cpu_to_le32(readl(aer_base + n * sizeof(u32))); - /* Use readl() to guarantee 32-bit accesses */ - for (n = 0; n < read_cnt; n++) - aer_regs_buf[n] = readl(aer_base + n * sizeof(u32)); + aer_cap_regs_unpack(aer_regs, raw, sizeof(raw)); writel(aer_regs->uncor_status, aer_base + PCI_ERR_UNCOR_STATUS); writel(aer_regs->cor_status, aer_base + PCI_ERR_COR_STATUS); diff --git a/drivers/pci/pcie/tlp.c b/drivers/pci/pcie/tlp.c index 71f8fc9ea2edcc..7f94a09ac7c767 100644 --- a/drivers/pci/pcie/tlp.c +++ b/drivers/pci/pcie/tlp.c @@ -8,8 +8,11 @@ #include #include #include +#include +#include #include #include +#include #include "../pci.h" @@ -92,6 +95,91 @@ int pcie_read_tlp_log(struct pci_dev *dev, int where, int where2, return 0; } +/* The Prefix Log registers hold dw[4..13] in Flit mode. */ +static_assert((PCI_ERR_PREFIX_LOG + + (PCIE_STD_MAX_TLP_HEADERLOG - PCIE_STD_NUM_TLP_HEADERLOG) * + sizeof(u32)) == PCIE_AER_CAP_HW_SIZE); + +/** + * aer_cap_regs_unpack - Convert a raw AER Capability image to the kernel layout + * @regs: Destination, fully initialised + * @raw: AER Capability register block in hardware order, little-endian + * @raw_len: Bytes readable at @raw + * + * struct aer_capability_regs is not the hardware layout: struct pcie_tlp_log + * spans 60 bytes where the Header Log it stands in for is 16, so a flat copy + * misplaces everything behind it. Map the registers one by one instead, + * taking the TLP Log layout from the Flit bit as pcie_read_tlp_log() does. + * + * @raw is not necessarily aligned. Registers past @raw_len are left zero, so + * a short image cannot be read past. + */ +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len) +{ + unsigned int i, tlp_len; + bool flit; + + memset(regs, 0, sizeof(*regs)); + + if (raw_len < PCI_ERR_HEADER_LOG) + return; + + /* The Extended Capability Header, at offset 0, has no define */ + regs->header = get_unaligned_le32(raw); + regs->uncor_status = get_unaligned_le32(raw + PCI_ERR_UNCOR_STATUS); + regs->uncor_mask = get_unaligned_le32(raw + PCI_ERR_UNCOR_MASK); + regs->uncor_severity = get_unaligned_le32(raw + PCI_ERR_UNCOR_SEVER); + regs->cor_status = get_unaligned_le32(raw + PCI_ERR_COR_STATUS); + regs->cor_mask = get_unaligned_le32(raw + PCI_ERR_COR_MASK); + regs->cap_control = get_unaligned_le32(raw + PCI_ERR_CAP); + + flit = FIELD_GET(PCI_ERR_CAP_TLP_LOG_FLIT, regs->cap_control); + if (flit) { + tlp_len = FIELD_GET(PCI_ERR_CAP_TLP_LOG_SIZE, regs->cap_control); + } else { + /* + * dw[4..7] alias the Prefix Log. Take all four whatever + * eetlp_prefix_max says; pcie_print_tlp_log() stops at the + * first zero one. + */ + tlp_len = PCIE_STD_NUM_TLP_HEADERLOG + PCIE_STD_MAX_TLP_PREFIXLOG; + } + + tlp_len = min(tlp_len, ARRAY_SIZE(regs->header_log.dw)); + + for (i = 0; i < tlp_len; i++) { + unsigned int off; + + if (i < PCIE_STD_NUM_TLP_HEADERLOG) + off = PCI_ERR_HEADER_LOG + i * sizeof(u32); + else + off = PCI_ERR_PREFIX_LOG + + (i - PCIE_STD_NUM_TLP_HEADERLOG) * sizeof(u32); + + if (off + sizeof(u32) > raw_len) + break; + regs->header_log.dw[i] = get_unaligned_le32(raw + off); + } + + /* @i may be short of tlp_len; non-Flit needs the TLP parsed, so cap at 4 */ + regs->header_log.header_len = flit ? i : min(i, PCIE_STD_NUM_TLP_HEADERLOG); + regs->header_log.flit = flit; + + if (raw_len >= PCI_ERR_ROOT_COMMAND + sizeof(u32)) + regs->root_command = get_unaligned_le32(raw + PCI_ERR_ROOT_COMMAND); + if (raw_len >= PCI_ERR_ROOT_STATUS + sizeof(u32)) + regs->root_status = get_unaligned_le32(raw + PCI_ERR_ROOT_STATUS); + if (raw_len >= PCI_ERR_ROOT_ERR_SRC + sizeof(u32)) { + u32 src = get_unaligned_le32(raw + PCI_ERR_ROOT_ERR_SRC); + + /* One register: ERR_COR in 15:0, ERR_FATAL/NONFATAL in 31:16 */ + regs->cor_err_source = src; + regs->uncor_err_source = src >> 16; + } +} +EXPORT_SYMBOL_GPL(aer_cap_regs_unpack); + #define EE_PREFIX_STR " E-E Prefixes:" /** diff --git a/include/linux/aer.h b/include/linux/aer.h index df0f5c382286fa..043ee76b6f538b 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -24,6 +24,13 @@ #define PCIE_STD_MAX_TLP_PREFIXLOG 4 #define PCIE_STD_MAX_TLP_HEADERLOG (PCIE_STD_NUM_TLP_HEADERLOG + 10) +/* + * Size of the AER Capability register block in hardware, long enough for the + * longest Flit mode TLP Log. struct aer_capability_regs below is larger and + * laid out differently; use aer_cap_regs_unpack() to convert. + */ +#define PCIE_AER_CAP_HW_SIZE 96 + struct pci_dev; struct pcie_tlp_log { @@ -68,6 +75,8 @@ static inline void pci_aer_unmask_internal_errors(struct pci_dev *dev) { } void pci_print_aer(struct pci_dev *dev, int aer_severity, struct aer_capability_regs *aer); +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len); int cper_severity_to_aer(int cper_severity); void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, int severity, struct aer_capability_regs *aer_regs); From b667a680917119827916bf2eb7d6f8de9c47531f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 21:23:47 -0500 Subject: [PATCH 1165/2286] cpufreq/amd-pstate: Fix TOCTOU when changing driver mode via sysfs amd_pstate_update_status() evaluates mode_state_machine[cppc_state] [mode_idx] before taking amd_pstate_driver_lock, and then re-evaluates it again once the lock is held. cppc_state is global and only stable under the lock, so concurrent sysfs writes race: CPU 0 CPU 1 ----- ----- // cppc_state == A, mode == B if (mode_state_machine[A][B]) // sees non-NULL if (mode_state_machine[A][B]) guard(&amd_pstate_driver_lock); mode_state_machine[A][B](B); cppc_state = B; guard(&amd_pstate_driver_lock); // cppc_state is now B mode_state_machine[B][B](B); // NULL -> NULL deref The re-read under the lock can resolve to a self-transition (NULL pointer, immediate NULL deref) or to an unexpected transition that redundantly runs amd_pstate_driver_cleanup(), double-freeing current_pstate_driver->attr. Take the lock first, then read cppc_state and resolve the transition function exactly once into a local before calling it, so the check and the call observe the same state. Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-f86ff3a6-55df-4eef-8e4d-62ac270dba25 Fixes: 6f0b13f16f7a ("cpufreq/amd-pstate: Overhaul locking") Signed-off-by: Mario Limonciello Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Link: https://lore.kernel.org/r/20260922022347.1270746-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index c93a4b765dffde..d054720abee352 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1886,6 +1886,7 @@ EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_get_status); int amd_pstate_update_status(const char *buf, size_t size) { + cppc_mode_transition_fn fn; int mode_idx; if (size > strlen("passive") || size < strlen("active")) @@ -1895,10 +1896,12 @@ int amd_pstate_update_status(const char *buf, size_t size) if (mode_idx < 0) return mode_idx; - if (mode_state_machine[cppc_state][mode_idx]) { - guard(mutex)(&amd_pstate_driver_lock); - return mode_state_machine[cppc_state][mode_idx](mode_idx); - } + guard(mutex)(&amd_pstate_driver_lock); + + /* Resolve the transition under the lock; cppc_state is only stable while held. */ + fn = mode_state_machine[cppc_state][mode_idx]; + if (fn) + return fn(mode_idx); return 0; } From 52763c00945ab30241d9c097547600b9e234e559 Mon Sep 17 00:00:00 2001 From: Vishal Badole Date: Tue, 29 Sep 2026 02:05:23 +0530 Subject: [PATCH 1166/2286] cpufreq: amd-pstate: Update Zen6 client EPP tuning values The per-CPU-type EPP values for the Zen6 client platform are further tuned based on platform characterization. Adjust the performance and low-power core entries so each core type uses its intended EPP settings. Reviewed-by: Mario Limonciello Signed-off-by: Vishal Badole Link: https://lore.kernel.org/r/20260928203523.1290623-1-Vishal.Badole@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index d054720abee352..8d5886f1e90528 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -211,7 +211,7 @@ static const struct amd_pstate_epp_soc epp_soc_zen6_client __initconst = { .performance = 25, .balance_performance = 51, .balance_power = 64, - .power = 64, + .power = 115, }, .efficiency_core = { .performance = 25, @@ -221,9 +221,9 @@ static const struct amd_pstate_epp_soc epp_soc_zen6_client __initconst = { }, .low_power_core = { .performance = 25, - .balance_performance = 51, + .balance_performance = 64, .balance_power = 64, - .power = 115, + .power = 64, }, }; From 4817d3e2b90dac50eb11cee32a0cf92d294261ea Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 1 Oct 2026 09:23:39 -0500 Subject: [PATCH 1167/2286] ACPI: CPPC: Refactor boost ratio handling Rename amd_get_boost_ratio_numerator() to amd_get_effective_highest_perf() to better reflect that it returns the effective highest performance value for a CPU, accounting for preferred cores and heterogeneous topologies. Add a new helper amd_get_boost_ratio() for doing both for boost ratio calculations and update callers to use it. This refactoring is needed because on some AMD systems (Zen6+), the boost ratio should be calculated from frequency values rather than performance values. Newer versions of the ACPI will introduce a proper interface for getting this information, but for now hardcode values so that these systems perform as intended and to unblock future scheduler work while we continue to wait. For now use some temporary hardcoded values for Zen6 platforms. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20261001142341.2934582-2-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- arch/x86/kernel/acpi/cppc.c | 166 ++++++++++++++++++++++----------- drivers/cpufreq/acpi-cpufreq.c | 5 +- drivers/cpufreq/amd-pstate.c | 14 ++- include/acpi/cppc_acpi.h | 14 ++- 4 files changed, 134 insertions(+), 65 deletions(-) diff --git a/arch/x86/kernel/acpi/cppc.c b/arch/x86/kernel/acpi/cppc.c index bbade0da5130f9..f230e9323187dd 100644 --- a/arch/x86/kernel/acpi/cppc.c +++ b/arch/x86/kernel/acpi/cppc.c @@ -10,6 +10,7 @@ #include #include #include +#include #define CPPC_HIGHEST_PERF_PERFORMANCE 196 #define CPPC_HIGHEST_PERF_PREFCORE 166 @@ -81,31 +82,18 @@ int cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val) static void amd_set_max_freq_ratio(void) { - struct cppc_perf_caps perf_caps; - u64 numerator, nominal_perf; + u64 numerator, denominator; u64 perf_ratio; int rc; - rc = cppc_get_perf_caps(0, &perf_caps); - if (rc) { - pr_debug("Could not retrieve perf counters (%d)\n", rc); - return; - } - - rc = amd_get_boost_ratio_numerator(0, &numerator); + rc = amd_get_boost_ratio(0, &numerator, &denominator); if (rc) { - pr_debug("Could not retrieve highest performance (%d)\n", rc); - return; - } - nominal_perf = perf_caps.nominal_perf; - - if (!nominal_perf) { - pr_debug("Could not retrieve nominal performance\n"); + pr_debug("Could not retrieve boost ratio (%d)\n", rc); return; } /* midpoint between max_boost and max_P */ - perf_ratio = (div_u64(numerator * SCHED_CAPACITY_SCALE, nominal_perf) + SCHED_CAPACITY_SCALE) >> 1; + perf_ratio = (div_u64(numerator * SCHED_CAPACITY_SCALE, denominator) + SCHED_CAPACITY_SCALE) >> 1; freq_invariance_set_perf_ratio(perf_ratio, false); } @@ -225,35 +213,30 @@ int amd_detect_prefcore(bool *detected) EXPORT_SYMBOL_GPL(amd_detect_prefcore); /** - * amd_get_boost_ratio_numerator: Get the numerator to use for boost ratio calculation - * @cpu: CPU to get numerator for. - * @numerator: Output variable for numerator. - * - * Determine the numerator to use for calculating the boost ratio on - * a CPU. On systems that support preferred cores, this will be a hardcoded - * value. On other systems this will the highest performance register value. + * amd_get_effective_highest_perf: Get the effective highest performance value + * @cpu: CPU to get highest performance for. * - * If booting the system with amd-pstate enabled but preferred cores disabled then - * the correct boost numerator will be returned to match hardware capabilities - * even if the preferred cores scheduling hints are not enabled. + * Get the effective highest performance value for a CPU, accounting for + * preferred cores and heterogeneous topologies. On systems with preferred + * cores, this may be a hardcoded value. On heterogeneous systems, this + * may be a per-CPU value. On other systems, this is the shared highest + * performance value. * - * Return: 0 for success, negative error code otherwise. + * Return: Effective highest performance value, or negative error code. */ -int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator) +int amd_get_effective_highest_perf(unsigned int cpu) { bool prefcore; int ret; u32 tmp; ret = amd_detect_prefcore(&prefcore); - if (ret) + if (ret < 0) return ret; /* without preferred cores, return the highest perf register value */ - if (!prefcore) { - *numerator = boost_numerator; - return 0; - } + if (!prefcore) + return boost_numerator; /* * For AMD CPUs with Family ID 19H and Model ID range 0x70 to 0x7f, @@ -263,8 +246,7 @@ int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator) if (cpu_feature_enabled(X86_FEATURE_ZEN4)) { switch (boot_cpu_data.x86_model) { case 0x70 ... 0x7f: - *numerator = CPPC_HIGHEST_PERF_PERFORMANCE; - return 0; + return CPPC_HIGHEST_PERF_PERFORMANCE; default: break; } @@ -272,28 +254,100 @@ int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator) /* detect if running on heterogeneous design */ if (cpu_feature_enabled(X86_FEATURE_AMD_HTR_CORES)) { - switch (cpu_data(cpu).topo.cpu_type) { - case TOPO_CPU_TYPE_UNKNOWN: - case TOPO_CPU_TYPE_ANY: - pr_warn("Undefined core type found for cpu %d\n", cpu); - break; - case TOPO_CPU_TYPE_PERFORMANCE: - /* use the max scale for performance cores */ - *numerator = CPPC_HIGHEST_PERF_PERFORMANCE; - return 0; - case TOPO_CPU_TYPE_LOW_POWER: - case TOPO_CPU_TYPE_EFFICIENCY: - /* use the highest perf value for efficiency and low-power cores */ - ret = amd_get_highest_perf(cpu, &tmp); - if (ret) - return ret; - *numerator = tmp; - return 0; - } + if (cpu_feature_enabled(X86_FEATURE_ZEN5) && + cpu_data(cpu).topo.cpu_type == TOPO_CPU_TYPE_PERFORMANCE) + return CPPC_HIGHEST_PERF_PERFORMANCE; + + /* Zen 5 efficiency, and Zen 6+ */ + ret = amd_get_highest_perf(cpu, &tmp); + if (ret < 0) + return ret; + + return tmp; + } + + return CPPC_HIGHEST_PERF_PREFCORE; +} +EXPORT_SYMBOL_GPL(amd_get_effective_highest_perf); + +struct amd_max_freq { + u32 performance; + u32 efficiency; + u32 low_power; +}; + +static const struct amd_max_freq max_freq_zen6_client = { + .performance = 5025, + .efficiency = 3524, + .low_power = 2399, +}; + +int amd_get_max_frequency(unsigned int cpu) +{ + enum x86_topology_cpu_type type = cpu_data(cpu).topo.cpu_type; + const struct amd_max_freq *max_freq = NULL; + + if (cpu_feature_enabled(X86_FEATURE_ZEN6) && + cpu_feature_enabled(X86_FEATURE_AMD_HTR_CORES)) + max_freq = &max_freq_zen6_client; + + if (!max_freq) + return 0; + + switch (type) { + case TOPO_CPU_TYPE_PERFORMANCE: + return max_freq->performance; + case TOPO_CPU_TYPE_EFFICIENCY: + return max_freq->efficiency; + case TOPO_CPU_TYPE_LOW_POWER: + return max_freq->low_power; + default: + break; } - *numerator = CPPC_HIGHEST_PERF_PREFCORE; + return 0; +} +EXPORT_SYMBOL_GPL(amd_get_max_frequency); + +/** + * amd_get_boost_ratio: Get numerator and denominator for boost ratio + * @cpu: CPU to get the boost ratio for. + * @numerator: Output variable for numerator. + * @denominator: Output variable for denominator. + * + * Get the numerator and denominator for calculating the boost ratio. + * + * Return: 0 for success, negative error code otherwise. + */ +int amd_get_boost_ratio(unsigned int cpu, u64 *numerator, u64 *denominator) +{ + struct cppc_perf_caps perf_caps; + int max_frequency; + int ret; + + ret = cppc_get_perf_caps(cpu, &perf_caps); + if (ret) + return ret; + + max_frequency = amd_get_max_frequency(cpu); + /* Use frequency values if available */ + if (max_frequency && perf_caps.nominal_freq) { + *numerator = max_frequency; + *denominator = perf_caps.nominal_freq; + return 0; + } + + /* Fall back to performance values */ + ret = amd_get_effective_highest_perf(cpu); + if (ret < 0) + return ret; + + *numerator = ret; + + *denominator = perf_caps.nominal_perf; + if (!*denominator) + return -EINVAL; return 0; } -EXPORT_SYMBOL_GPL(amd_get_boost_ratio_numerator); +EXPORT_SYMBOL_GPL(amd_get_boost_ratio); diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 10ea6035f4ad6f..523ee1a06ef8a3 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -640,12 +640,13 @@ static u64 get_max_boost_ratio(unsigned int cpu, u64 *nominal_freq) } if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) { - ret = amd_get_boost_ratio_numerator(cpu, &highest_perf); - if (ret) { + ret = amd_get_effective_highest_perf(cpu); + if (ret < 0) { pr_debug("CPU%d: Unable to get boost ratio numerator (%d)\n", cpu, ret); return 0; } + highest_perf = ret; } else { highest_perf = perf_caps.highest_perf; } diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 8d5886f1e90528..83ab0385a3ea40 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -555,11 +555,13 @@ static int msr_init_perf(struct amd_cpudata *cpudata) if (ret) return ret; - ret = amd_get_boost_ratio_numerator(cpudata->cpu, &numerator); - if (ret) + ret = amd_get_effective_highest_perf(cpudata->cpu); + if (ret < 0) return ret; - ret = rdmsrq_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, &cppc_req); + numerator = ret; + + ret = rdmsrq_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ, &cppc_req); if (ret) return ret; @@ -590,10 +592,12 @@ static int shmem_init_perf(struct amd_cpudata *cpudata) if (ret) return ret; - ret = amd_get_boost_ratio_numerator(cpudata->cpu, &numerator); - if (ret) + ret = amd_get_effective_highest_perf(cpudata->cpu); + if (ret < 0) return ret; + numerator = ret; + perf.highest_perf = numerator; perf.max_limit_perf = numerator; perf.min_limit_perf = cppc_perf.lowest_perf; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 94a6277edab273..4c0b11a718e38e 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -192,7 +192,9 @@ extern int cppc_set_auto_sel(int cpu, bool enable); extern int cppc_get_perf_limited(int cpu, u64 *perf_limited); extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear); extern int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf); -extern int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator); +extern int amd_get_effective_highest_perf(unsigned int cpu); +extern int amd_get_max_frequency(unsigned int cpu); +extern int amd_get_boost_ratio(unsigned int cpu, u64 *numerator, u64 *denominator); extern int amd_detect_prefcore(bool *detected); #else /* !CONFIG_ACPI_CPPC_LIB */ static inline int cppc_get_desired_perf(int cpunum, u64 *desired_perf) @@ -305,7 +307,15 @@ static inline int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf) { return -ENODEV; } -static inline int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator) +static inline int amd_get_effective_highest_perf(unsigned int cpu) +{ + return -EOPNOTSUPP; +} +static inline int amd_get_max_frequency(unsigned int cpu) +{ + return 0; +} +static inline int amd_get_boost_ratio(unsigned int cpu, u64 *numerator, u64 *denominator) { return -EOPNOTSUPP; } From f9da951d309b24aea5052b76ab9438d7854a17ae Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 1 Oct 2026 09:23:40 -0500 Subject: [PATCH 1168/2286] cpufreq/acpi-cpufreq: Use amd_get_boost_ratio() Update get_max_boost_ratio() to use the new amd_get_boost_ratio() helper function instead of calling amd_get_effective_highest_perf() directly. This ensures the boost ratio is calculated correctly on systems where frequency values should be used instead of performance values. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Acked-by: Rafael J. Wysocki (Intel) Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20261001142341.2934582-3-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/acpi-cpufreq.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 523ee1a06ef8a3..2e74a000cdc74d 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -626,7 +626,7 @@ static int acpi_cpufreq_blacklist(struct cpuinfo_x86 *c) static u64 get_max_boost_ratio(unsigned int cpu, u64 *nominal_freq) { struct cppc_perf_caps perf_caps; - u64 highest_perf, nominal_perf; + u64 numerator, denominator; int ret; if (acpi_pstate_strict) @@ -640,33 +640,31 @@ static u64 get_max_boost_ratio(unsigned int cpu, u64 *nominal_freq) } if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD) { - ret = amd_get_effective_highest_perf(cpu); - if (ret < 0) { - pr_debug("CPU%d: Unable to get boost ratio numerator (%d)\n", + ret = amd_get_boost_ratio(cpu, &numerator, &denominator); + if (ret) { + pr_debug("CPU%d: Unable to get boost ratio (%d)\n", cpu, ret); return 0; } - highest_perf = ret; } else { - highest_perf = perf_caps.highest_perf; + numerator = perf_caps.highest_perf; + denominator = perf_caps.nominal_perf; } - nominal_perf = perf_caps.nominal_perf; - if (nominal_freq) *nominal_freq = perf_caps.nominal_freq * 1000; - if (!highest_perf || !nominal_perf) { + if (!numerator || !denominator) { pr_debug("CPU%d: highest or nominal performance missing\n", cpu); return 0; } - if (highest_perf < nominal_perf) { + if (numerator < denominator) { pr_debug("CPU%d: nominal performance above highest\n", cpu); return 0; } - return div_u64(highest_perf << SCHED_CAPACITY_SHIFT, nominal_perf); + return div_u64(numerator << SCHED_CAPACITY_SHIFT, denominator); } #else From b3f9051ae263940af4a69212c88fd094401cf92d Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 1 Oct 2026 09:23:41 -0500 Subject: [PATCH 1169/2286] cpufreq/amd-pstate: Get Highest Freq for a CPU If the highest frequency is known for a CPU, use this instead of trying to calculate by linear interpolation. Reviewed-by: K Prateek Nayak Tested-by: K Prateek Nayak Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20261001142341.2934582-4-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 83ab0385a3ea40..743d90ed6af464 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -778,7 +778,10 @@ static void amd_pstate_update_min_max_limit(struct cpufreq_policy *policy) struct amd_cpudata *cpudata = policy->driver_data; union perf_cached perf = READ_ONCE(cpudata->perf); - perf.max_limit_perf = freq_to_perf(perf, cpudata->nominal_freq, policy->max); + if (policy->max >= READ_ONCE(cpudata->max_freq)) + perf.max_limit_perf = perf.highest_perf; + else + perf.max_limit_perf = freq_to_perf(perf, cpudata->nominal_freq, policy->max); WRITE_ONCE(cpudata->max_limit_freq, policy->max); if (cpudata->policy == CPUFREQ_POLICY_PERFORMANCE) { @@ -1104,8 +1107,10 @@ static int amd_pstate_init_freq(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->nominal_freq, nominal_freq); - /* max_freq is calculated according to (nominal_freq * highest_perf)/nominal_perf */ - max_freq = perf_to_freq(perf, nominal_freq, perf.highest_perf); + /* try to look up from BIOS/quirk first, fall back to (nominal_freq * highest_perf)/nominal_perf */ + max_freq = amd_get_max_frequency(cpudata->cpu) * 1000; + if (!max_freq) + max_freq = perf_to_freq(perf, nominal_freq, perf.highest_perf); WRITE_ONCE(cpudata->max_freq, max_freq); lowest_nonlinear_freq = perf_to_freq(perf, nominal_freq, perf.lowest_nonlinear_perf); From f6d3cbc63935fa8911da87e91ed768824d495470 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 14:02:32 -0500 Subject: [PATCH 1170/2286] cpufreq: amd-pstate: Restore previous mode when changing driver mode fails amd_pstate_change_driver_mode() unregisters the currently active driver before registering the requested mode. If amd_pstate_register_driver() fails for the new mode, the function returns the error without restoring anything, leaving the system with no cpufreq scaling driver at all until a valid mode is manually re-selected. Remember the mode that was active before the transition and, if registering the requested mode fails, register the previous mode again so the system keeps a working scaling driver. The original error is still returned to the caller so the sysfs write reports the failure. Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-5b138ddb-c88b-4f82-8a2d-f39446b625d4 Fixes: 3ca7bc818d8c ("cpufreq: amd-pstate: Add guided mode control support via sysfs") Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20260921190235.3651688-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index 743d90ed6af464..a1d58d1f5f3aa8 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1837,6 +1837,7 @@ static int amd_pstate_change_mode_without_dvr_change(int mode) static int amd_pstate_change_driver_mode(int mode) { + int old_mode = cppc_state; int ret; lockdep_assert_held(&amd_pstate_driver_lock); @@ -1846,10 +1847,16 @@ static int amd_pstate_change_driver_mode(int mode) return ret; ret = amd_pstate_register_driver(mode); - if (ret) - return ret; + if (ret) { + pr_err("Failed to register %s mode, restoring %s mode\n", + amd_pstate_get_mode_string(mode), + amd_pstate_get_mode_string(old_mode)); + if (amd_pstate_register_driver(old_mode)) + pr_err("Failed to restore %s mode\n", + amd_pstate_get_mode_string(old_mode)); + } - return 0; + return ret; } static cppc_mode_transition_fn mode_state_machine[AMD_PSTATE_MAX][AMD_PSTATE_MAX] = { From 8de705037124dd1cd49bd8cae6536d63ec4e1807 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 14:02:33 -0500 Subject: [PATCH 1171/2286] cpufreq: amd-pstate: Propagate cppc_set_auto_sel() errors on mode change amd_pstate_change_mode_without_dvr_change() iterates over the online CPUs and calls cppc_set_auto_sel() to enable or disable hardware autonomous selection, but discards its return value and unconditionally returns 0. If the firmware rejects the request, the failure is hidden: the cpufreq core records the mode transition as successful while the hardware stays in its previous autonomous-selection state. The software mode and the actual hardware behaviour then disagree, breaking the expected frequency scaling. Check the return value of cppc_set_auto_sel() and propagate the first error to the caller so the sysfs write reports the failure. Reported-by: Sashiko Closes: https://sashiko.dev/#/bug/linux-d114a94a-655b-43a6-91b1-9484889726c9 Fixes: 3ca7bc818d8c ("cpufreq: amd-pstate: Add guided mode control support via sysfs") Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20260921190235.3651688-2-mario.limonciello@amd.com Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index a1d58d1f5f3aa8..d8e2eec6e72508 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -1822,6 +1822,7 @@ static int amd_pstate_unregister_driver(int dummy) static int amd_pstate_change_mode_without_dvr_change(int mode) { int cpu = 0; + int ret; cppc_state = mode; @@ -1829,7 +1830,9 @@ static int amd_pstate_change_mode_without_dvr_change(int mode) return 0; for_each_online_cpu(cpu) { - cppc_set_auto_sel(cpu, (cppc_state == AMD_PSTATE_PASSIVE) ? 0 : 1); + ret = cppc_set_auto_sel(cpu, (cppc_state == AMD_PSTATE_PASSIVE) ? 0 : 1); + if (ret) + return ret; } return 0; From 6b64d65c83688db2e58fbd9de7c74090f15271b7 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Mon, 21 Sep 2026 14:02:35 -0500 Subject: [PATCH 1172/2286] cpufreq: amd-pstate-ut: Fix max_freq and EPP test failures on Zen6 Zen6 platforms expose conditions that cause two amd-pstate unit tests to fail: low-power cores can report max_freq below the shared nominal_freq, and multiple EPP preferences can alias to the same raw value. Relax the max_freq >= nominal_freq assertion to max_freq >= lowest_nonlinear_freq, and tolerate aliased EPP preferences by validating the raw programmed value instead of string comparison. Signed-off-by: K Prateek Nayak Tested-by: Mario Limonciello Signed-off-by: Mario Limonciello --- drivers/cpufreq/amd-pstate-ut.c | 54 ++++++++++++++++++++++++--------- drivers/cpufreq/amd-pstate.c | 44 ++------------------------- drivers/cpufreq/amd-pstate.h | 44 +++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 57 deletions(-) diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c index c2c1a166b3a9e7..506d7bb2e66091 100644 --- a/drivers/cpufreq/amd-pstate-ut.c +++ b/drivers/cpufreq/amd-pstate-ut.c @@ -216,7 +216,14 @@ static int amd_pstate_ut_check_perf(u32 index) /* * Check if frequency values are reasonable. - * max_freq >= nominal_freq > lowest_nonlinear_freq > min_freq > 0 + * nominal_freq > lowest_nonlinear_freq >= min_freq > 0 + * max_freq >= lowest_nonlinear_freq + * + * On most parts the boost frequency is the highest, i.e. + * max_freq >= nominal_freq. On heterogeneous designs (e.g. Zen6) a + * low-power core can have a maximum frequency below the shared nominal + * reference frequency, so only require the boost frequency to sit above + * the lowest nonlinear frequency here. * check max freq when set support boost mode. */ static int amd_pstate_ut_check_freq(u32 index) @@ -235,11 +242,11 @@ static int amd_pstate_ut_check_freq(u32 index) cpudata = policy->driver_data; perf = READ_ONCE(cpudata->perf); - if (!((policy->cpuinfo.max_freq >= cpudata->nominal_freq) && + if (!((policy->cpuinfo.max_freq >= cpudata->lowest_nonlinear_freq) && (cpudata->nominal_freq > cpudata->lowest_nonlinear_freq) && (cpudata->lowest_nonlinear_freq >= policy->cpuinfo.min_freq) && (policy->cpuinfo.min_freq > 0))) { - pr_err("%s cpu%d max=%d >= nominal=%d > lowest_nonlinear=%d > min=%d > 0, the formula is incorrect!\n", + pr_err("%s cpu%d max=%d, nominal=%d, lowest_nonlinear=%d, min=%d, the formula is incorrect!\n", __func__, cpu, policy->cpuinfo.max_freq, cpudata->nominal_freq, cpudata->lowest_nonlinear_freq, policy->cpuinfo.min_freq); return -EINVAL; @@ -297,12 +304,12 @@ static int amd_pstate_set_mode(enum amd_pstate_mode mode) static int amd_pstate_ut_epp(u32 index) { - static const char * const epp_strings[] = { - "dynamic", - "power", - "balance_power", - "balance_performance", - "performance", + enum energy_perf_value_index epp_modes[] = { + EPP_INDEX_DYNAMIC, + EPP_INDEX_POWERSAVE, + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_BALANCE_PERFORMANCE, + EPP_INDEX_PERFORMANCE, }; char *buf __free(cleanup_page) = NULL; struct cpufreq_policy *policy = NULL; @@ -371,9 +378,9 @@ static int amd_pstate_ut_epp(u32 index) } } - for (i = 0; i < ARRAY_SIZE(epp_strings); i++) { + for (i = 0; i < ARRAY_SIZE(epp_modes); i++) { memset(buf, 0, PAGE_SIZE); - snprintf(buf, PAGE_SIZE, "%s", epp_strings[i]); + snprintf(buf, PAGE_SIZE, "%s", energy_perf_strings[epp_modes[i]]); ret = store_energy_performance_preference(policy, buf, strlen(buf)); if (ret < 0) goto out; @@ -389,8 +396,24 @@ static int amd_pstate_ut_epp(u32 index) */ strreplace(buf, '(', '\0'); - if (strcmp(buf, epp_strings[i])) { - pr_err("String EPP value mismatch: %s != %s\n", buf, epp_strings[i]); + if (strcmp(buf, energy_perf_strings[epp_modes[i]])) { + u8 epp = FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached); + + /* + * The preference read back may legitimately differ from the one + * written: some platforms (e.g. Zen6) program the same raw EPP + * value for more than one named preference, so show() reports the + * first name that matches that value. + * + * If strings mismatch, confirm the CPU is programmed with the correct + * EPP value for the mode. This verifies hardware programming while + * tolerating such aliasing in driver. + */ + if (epp == amd_pstate_cpu_epp_values(cpudata->cpu_type)[epp_modes[i]]) + continue; + + pr_err("String EPP value mismatch: %s != %s\n", + buf, energy_perf_strings[epp_modes[i]]); ret = -EINVAL; goto out; } @@ -406,11 +429,12 @@ static int amd_pstate_ut_epp(u32 index) * restore it here before dropping policy reference. */ if (orig_dynamic_epp) { + const char * const dynamic_string = energy_perf_strings[epp_modes[0]]; int ret2; ret2 = store_energy_performance_preference(policy, - epp_strings[0], - strlen(epp_strings[0])); + dynamic_string, + strlen(dynamic_string)); if (!ret && (ret2 < 0)) ret = ret2; } diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c index d8e2eec6e72508..4bf158f677f3ea 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -44,7 +44,6 @@ #include #include -#include #include #include @@ -89,46 +88,6 @@ static int cppc_state = AMD_PSTATE_UNDEFINED; static bool amd_pstate_prefcore = true; static struct quirk_entry *quirks; -/* - * AMD Energy Preference Performance (EPP) - * The EPP is used in the CCLK DPM controller to drive - * the frequency that a core is going to operate during - * short periods of activity. EPP values will be utilized for - * different OS profiles (balanced, performance, power savings) - * display strings corresponding to EPP index in the - * energy_perf_strings[] - * index String - *------------------------------------- - * 0 default - * 1 performance - * 2 balance_performance - * 3 balance_power - * 4 power - * 5 custom (for raw EPP values) - * 6 dynamic (platform profile driven selection) - */ -enum energy_perf_value_index { - EPP_INDEX_DEFAULT = 0, - EPP_INDEX_PERFORMANCE, - EPP_INDEX_BALANCE_PERFORMANCE, - EPP_INDEX_BALANCE_POWERSAVE, - EPP_INDEX_POWERSAVE, - EPP_INDEX_CUSTOM, - EPP_INDEX_DYNAMIC, - EPP_INDEX_MAX, -}; - -static const char * const energy_perf_strings[] = { - [EPP_INDEX_DEFAULT] = "default", - [EPP_INDEX_PERFORMANCE] = "performance", - [EPP_INDEX_BALANCE_PERFORMANCE] = "balance_performance", - [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", - [EPP_INDEX_POWERSAVE] = "power", - [EPP_INDEX_CUSTOM] = "custom", - [EPP_INDEX_DYNAMIC] = "dynamic", -}; -static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); - /* * The numeric EPP value programmed for each named preference. First dimension * is CPU type (TOPO_CPU_TYPE_ANY for non-hybrid, TOPO_CPU_TYPE_PERFORMANCE/ @@ -159,7 +118,7 @@ static_assert(ARRAY_SIZE(epp_values) == TOPO_CPU_TYPE_LOW_POWER + 1, * Non-hybrid systems use TOPO_CPU_TYPE_ANY; hybrid systems use the CPU's * actual type (PERFORMANCE/EFFICIENCY/LOW_POWER). */ -static inline u8 *amd_pstate_cpu_epp_values(enum x86_topology_cpu_type cpu_type) +u8 *amd_pstate_cpu_epp_values(enum x86_topology_cpu_type cpu_type) { switch (cpu_type) { case TOPO_CPU_TYPE_PERFORMANCE: @@ -170,6 +129,7 @@ static inline u8 *amd_pstate_cpu_epp_values(enum x86_topology_cpu_type cpu_type) return epp_values[TOPO_CPU_TYPE_ANY]; } } +EXPORT_SYMBOL_FOR_PSTATE_UT(amd_pstate_cpu_epp_values); /** * struct amd_pstate_epp_values - EPP values for the four named preferences diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h index c7189d177b81a5..422188c639b1e8 100644 --- a/drivers/cpufreq/amd-pstate.h +++ b/drivers/cpufreq/amd-pstate.h @@ -11,6 +11,8 @@ #include #include +#include + #if IS_MODULE(CONFIG_X86_AMD_PSTATE_UT) #define EXPORT_SYMBOL_FOR_PSTATE_UT(symbol) \ EXPORT_SYMBOL_FOR_MODULES(symbol, "amd-pstate-ut") @@ -162,6 +164,48 @@ enum amd_pstate_mode { AMD_PSTATE_MAX, }; +/* + * AMD Energy Preference Performance (EPP) + * The EPP is used in the CCLK DPM controller to drive + * the frequency that a core is going to operate during + * short periods of activity. EPP values will be utilized for + * different OS profiles (balanced, performance, power savings) + * display strings corresponding to EPP index in the + * energy_perf_strings[] + * index String + *------------------------------------- + * 0 default + * 1 performance + * 2 balance_performance + * 3 balance_power + * 4 power + * 5 custom (for raw EPP values) + * 6 dynamic (platform profile driven selection) + */ +enum energy_perf_value_index { + EPP_INDEX_DEFAULT = 0, + EPP_INDEX_PERFORMANCE, + EPP_INDEX_BALANCE_PERFORMANCE, + EPP_INDEX_BALANCE_POWERSAVE, + EPP_INDEX_POWERSAVE, + EPP_INDEX_CUSTOM, + EPP_INDEX_DYNAMIC, + EPP_INDEX_MAX, +}; + +static const char * const energy_perf_strings[] = { + [EPP_INDEX_DEFAULT] = "default", + [EPP_INDEX_PERFORMANCE] = "performance", + [EPP_INDEX_BALANCE_PERFORMANCE] = "balance_performance", + [EPP_INDEX_BALANCE_POWERSAVE] = "balance_power", + [EPP_INDEX_POWERSAVE] = "power", + [EPP_INDEX_CUSTOM] = "custom", + [EPP_INDEX_DYNAMIC] = "dynamic", +}; +static_assert(ARRAY_SIZE(energy_perf_strings) == EPP_INDEX_MAX); + +u8 *amd_pstate_cpu_epp_values(enum x86_topology_cpu_type cpu_type); + static inline u8 freq_to_perf(union perf_cached perf, u32 nominal_freq, unsigned int freq_val) { u32 perf_val = DIV_ROUND_UP_ULL((u64)freq_val * perf.nominal_perf, nominal_freq); From 0d20d54f089eca8cf43ecc9a2bca552d322619da Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:43:03 +0200 Subject: [PATCH 1173/2286] iommu/arm-smmu-v3: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Nicolin Chen Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3636226.QJadu78ljV@rafael.j.wysocki --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 5732f3ba0122d6..98f105798b89a1 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -5307,7 +5307,7 @@ static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node, adev = acpi_dev_get_first_match_dev("NVDA200C", uid, -1); if (adev) { /* Tegra241 CMDQV driver is responsible for put_device() */ - smmu->impl_dev = get_device(acpi_get_first_physical_node(adev)); + smmu->impl_dev = acpi_bus_get_primary_device(adev); smmu->options |= ARM_SMMU_OPT_TEGRA241_CMDQV; dev_info(smmu->dev, "found companion CMDQV device: %s\n", dev_name(smmu->impl_dev)); From 2f47382b3ea31346b46444389e0520ca3cad2c81 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:42:15 +0200 Subject: [PATCH 1174/2286] ALSA: hda: cs35l41: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Takashi Iwai Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/6200303.MhkbZ0Pkbq@rafael.j.wysocki --- sound/hda/codecs/side-codecs/cs35l41_hda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 62296be45c55e7..32abb71dd40195 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1926,8 +1926,7 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i } cs35l41->dacpi = adev; - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); if (!physdev) { acpi_dev_put(adev); return -ENODEV; From b85fdc68f3d9ac545aa523ffdddd46c470a652b8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:42:36 +0200 Subject: [PATCH 1175/2286] ALSA: hda: tas2781: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Takashi Iwai Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/8782185.NyiUUSuA9g@rafael.j.wysocki --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 3 +-- sound/hda/codecs/side-codecs/tas2781_hda_spi.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b7e..161ca965c63e53 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -100,8 +100,7 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) return -ENODEV; } - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); if (ret < 0) { dev_err(p->dev, "Failed to get ACPI resource.\n"); diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c index da71b57a26ee0d..f3ba7132d2b874 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c @@ -341,8 +341,7 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, strscpy(p->dev_name, hid, sizeof(p->dev_name)); - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) return -ENODEV; From 4ed5b18e4ebd3c8bfc353ec68c1bfc818f1b9aac Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:41:40 +0200 Subject: [PATCH 1176/2286] efi/dev-path-parser: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3974502.kQq0lBPeGt@rafael.j.wysocki --- drivers/firmware/efi/dev-path-parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c index 13ea141c0defb5..e0f864cbaa8cab 100644 --- a/drivers/firmware/efi/dev-path-parser.c +++ b/drivers/firmware/efi/dev-path-parser.c @@ -37,9 +37,9 @@ static long __init parse_acpi_path(const struct efi_dev_path *node, if (!adev) return -ENODEV; - phys_dev = acpi_get_first_physical_node(adev); + phys_dev = acpi_bus_get_primary_device(adev); if (phys_dev) { - *child = get_device(phys_dev); + *child = phys_dev; acpi_dev_put(adev); } else *child = &adev->dev; From 538076b45429652424801cb127581ed3dc56a0b7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:45:01 +0200 Subject: [PATCH 1177/2286] ACPI: bus: Drop acpi_get_first_physical_node() Fold acpi_get_first_physical_node() into acpi_primary_dev_companion() that is its only remaining caller and drop its signature from the header file. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/4537440.ejJDZkT8p0@rafael.j.wysocki --- drivers/acpi/bus.c | 20 +++++--------------- include/linux/acpi.h | 7 ------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 808c6746be14dd..cc6989d88645da 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -809,24 +809,14 @@ struct device *acpi_bus_get_primary_device(struct acpi_device *adev) } EXPORT_SYMBOL_GPL(acpi_bus_get_primary_device); -/** - * acpi_get_first_physical_node - Find first physical node of an ACPI device - * @adev: ACPI device in question - * - * Return: First physical node of ACPI device @adev - */ -struct device *acpi_get_first_physical_node(struct acpi_device *adev) -{ - guard(mutex)(&adev->physical_node_lock); - - return primary_physical_device(adev); -} -EXPORT_SYMBOL_GPL(acpi_get_first_physical_node); - static struct acpi_device *acpi_primary_dev_companion(struct acpi_device *adev, const struct device *dev) { - const struct device *phys_dev = acpi_get_first_physical_node(adev); + const struct device *phys_dev; + + guard(mutex)(&adev->physical_node_lock); + + phys_dev = primary_physical_device(adev); return phys_dev && phys_dev == dev ? adev : NULL; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ba5234bd93a29a..6f69819f9c9a80 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -105,8 +105,6 @@ static inline const char *acpi_dev_name(struct acpi_device *adev) return dev_name(&adev->dev); } -struct device *acpi_get_first_physical_node(struct acpi_device *adev); - enum acpi_irq_model_id { ACPI_IRQ_MODEL_PIC = 0, ACPI_IRQ_MODEL_IOAPIC, @@ -938,11 +936,6 @@ static inline const char *acpi_dev_name(struct acpi_device *adev) return NULL; } -static inline struct device *acpi_get_first_physical_node(struct acpi_device *adev) -{ - return NULL; -} - static inline void acpi_early_init(void) { } static inline void acpi_subsystem_init(void) { } From e91836593fbfb0014c290e9a0bbd86d0b98c1d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=A9baud=20Weksteen?= Date: Thu, 17 Sep 2026 15:11:47 +1000 Subject: [PATCH 1178/2286] rust: macros: allow non-ASCII characters in `authors` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Module authors may have non-ASCII characters in their names. In C, `MODULE_AUTHOR` allows arbitrary string literals which are emitted as raw UTF-8 bytes into the `.modinfo` section, and multiple in-tree modules use non-ASCII author names. Originally, the single `author` field permitted arbitrary string literals including non-ASCII characters. When support for multiple authors was introduced in commit 38559da6afb2 ("rust: module: introduce `authors` key"), it reused the `expect_string_array` helper that had originally been added for module aliases. Because that helper enforced an ASCII check, `authors` inadvertently became restricted to ASCII-only string literals. Later, when the macro parsing was rewritten to use `syn` in commit c578ad703ae9 ("rust: macros: use `syn` to parse `module!` macro"), this restriction was carried over by using AsciiLitStr in `authors` type. Change the element type of `authors` in `ModuleInfo` from `AsciiLitStr` to `LitStr` so that UTF-8 author names are permitted. Fixes: 38559da6afb2 ("rust: module: introduce `authors` key") Signed-off-by: Thiébaud Weksteen Acked-by: Petr Pavlu Link: https://patch.msgid.link/20260917051147.98775-1-tweek@google.com Signed-off-by: Miguel Ojeda --- rust/macros/lib.rs | 2 +- rust/macros/module.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 24f96feaeb34a1..ea2e232b61b1d6 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -129,7 +129,7 @@ use syn::parse_macro_input; /// # Supported argument types /// - `type`: type which implements the [`Module`] trait (required). /// - `name`: ASCII string literal of the name of the kernel module (required). -/// - `authors`: array of ASCII string literals of the authors of the kernel module. +/// - `authors`: array of string literals of the authors of the kernel module. /// - `description`: string literal of the description of the kernel module. /// - `license`: ASCII string literal of the license of the kernel module (required). /// - `alias`: array of ASCII string literals of the alias names of the kernel module. diff --git a/rust/macros/module.rs b/rust/macros/module.rs index bc7027f8dbb2c2..0a7b9e24c96007 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -369,7 +369,7 @@ pub(crate) struct ModuleInfo { type_: Type, license: AsciiLitStr, name: AsciiLitStr, - authors: Option>, + authors: Option>, description: Option, alias: Option>, firmware: Option>, From 951d09ca450ca76a695016ea76c3fa653bd20439 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Thu, 1 Oct 2026 21:31:03 +0900 Subject: [PATCH 1179/2286] rust: bug: pass TAINT_WARN to warn_slowpath_fmt() on UML On UML, `warn_flags!` calls `warn_slowpath_fmt()` directly. Its third argument is a taint number, but `warn_on!` passes its bug flags there. As a result, `add_taint()` gets 2304 instead of 9 (`TAINT_WARN`). The warning is not recorded: `/proc/sys/kernel/tainted` stays 0 after `warn_on!` fires, where it should be 512. It also causes an out-of-bounds write in `add_taint()`. Pass `TAINT_WARN` directly, as C does on UML. Cc: stable@vger.kernel.org Fixes: dff64b072708 ("rust: Add warn_on macro") Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20261001123103.2198104-1-tomo@flapping.org Signed-off-by: Miguel Ojeda --- rust/kernel/bug.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs index 3566f0234ca403..af8eba4563def9 100644 --- a/rust/kernel/bug.rs +++ b/rust/kernel/bug.rs @@ -85,6 +85,7 @@ macro_rules! warn_flags { ($file:expr, $flags:expr) => { if false { _ = $file; + _ = $flags; } // SAFETY: It is always safe to call `warn_slowpath_fmt()` @@ -93,7 +94,7 @@ macro_rules! warn_flags { $crate::bindings::warn_slowpath_fmt( $crate::str::CStrExt::as_char_ptr($crate::c_str!(::core::file!())), line!() as $crate::ffi::c_int, - $flags as $crate::ffi::c_uint, + $crate::bindings::TAINT_WARN, ::core::ptr::null(), ); } From cd56a1c858ff34cc8b71674a6bdf2f7a899abf67 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Wed, 16 Sep 2026 23:09:52 +0900 Subject: [PATCH 1180/2286] rust: bug: add a KUnit test for warn_on! The test runs warn_on!(false) and warn_on!(true) inside a KUnit warning suppression block, then checks that one warning was counted. The counter is incremented by the kernel warning path, not by warn_on! itself, so a count of one means the warning was really reported. warn_flags! has a different definition per architecture. The test does not look at any of them, so it runs on every architecture that supports Rust. The new CONFIG_RUST_BUG_KUNIT_TEST depends on BUG. With CONFIG_BUG=n, warn_on! does nothing and no warning is counted. Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20260916140952.3290924-1-tomo@flapping.org [ Added newlines for clarity. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/Kconfig.test | 11 +++++++++++ rust/kernel/bug.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test index e6a5c7a795f0fc..966b07872ba154 100644 --- a/rust/kernel/Kconfig.test +++ b/rust/kernel/Kconfig.test @@ -83,4 +83,15 @@ config RUST_BITFIELD_KUNIT_TEST If unsure, say N. +config RUST_BUG_KUNIT_TEST + bool "KUnit tests for the Rust BUG/WARN functionality" if !KUNIT_ALL_TESTS + depends on BUG + default KUNIT_ALL_TESTS + help + This option enables KUnit tests for the Rust BUG/WARN functionality. + These are only for development and testing, not for regular + kernel use cases. + + If unsure, say N. + endif diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs index af8eba4563def9..6abed2c055b1f5 100644 --- a/rust/kernel/bug.rs +++ b/rust/kernel/bug.rs @@ -153,3 +153,33 @@ macro_rules! warn_on { cond }}; } + +#[cfg(CONFIG_RUST_BUG_KUNIT_TEST)] +#[macros::kunit_tests(rust_kernel_bug)] +mod tests { + // The counter is incremented by the kernel warning path, not by `warn_on!` + // itself. A count of one means the warning was really reported. + #[test] + fn test_warn_on() { + // SAFETY: `kunit_get_current_test()` is always safe to call (it has + // fallbacks for when no KUnit test is running). + let test = unsafe { bindings::kunit_get_current_test() }; + + // SAFETY: This function runs only as a KUnit test case, so `test` is a + // valid pointer to the running test. + let handle = unsafe { bindings::kunit_start_suppress_warning(test) }; + + assert!(!warn_on!(false)); + assert!(warn_on!(true)); + + // SAFETY: `kunit_suppressed_warning_count()` accepts any value returned by + // `kunit_start_suppress_warning()`. + let suppressed_count = unsafe { bindings::kunit_suppressed_warning_count(handle) }; + + // SAFETY: `test` is valid as above. `kunit_end_suppress_warning()` accepts any + // value returned by `kunit_start_suppress_warning()`. + unsafe { bindings::kunit_end_suppress_warning(test, handle) }; + + assert_eq!(suppressed_count, 1); + } +} From dd47e7d8672b64ae8680f8abefbe4b5c55895cda Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 10 Sep 2026 18:05:42 +0100 Subject: [PATCH 1181/2286] rust: bitfield: remove unnecessary `#[allow]` The `#[allow(non_camel_case_types)]` was there because register names can be commonly SCREAMING_CASE and thus not CamelCase. However, when a bitfield type is created by itself, this justification does not apply. Thus, remove it. Users that want to use non-standard styles can add the `#[allow]` themselves. For example, the invocation of `bitfield!` inside `register!` macro carry this annotation. Signed-off-by: Gary Guo Acked-by: Alexandre Courbot Link: https://patch.msgid.link/20260910170542.92010-1-gary@kernel.org Signed-off-by: Miguel Ojeda --- rust/kernel/bitfield.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/kernel/bitfield.rs b/rust/kernel/bitfield.rs index a0d089423f21c9..861522c8cf3e93 100644 --- a/rust/kernel/bitfield.rs +++ b/rust/kernel/bitfield.rs @@ -295,7 +295,6 @@ macro_rules! bitfield { $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } ) => { $crate::bitfield!(@core - #[allow(non_camel_case_types)] $(#[$attr])* $vis $name $storage ); $crate::bitfield!(@fields $vis $name $storage { $($fields)* }); From af78eca13b0350ee04665ffa4af88ff4dae6c3e0 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:50 -0700 Subject: [PATCH 1182/2286] rcu: Make call_rcu() safe to call from any context RCU's per-CPU callback list is only touched with interrupts disabled: the enqueue runs under local_irq_save() (and the nocb locks when offloaded), as do callback invocation and grace-period work. A call_rcu() that arrives with interrupts already disabled, whether from an NMI or from instrumentation that re-enters RCU, can interrupt one of those and corrupt the list or deadlock. Defer instead: stage the callback on a per-CPU llist and raise an irq_work that re-issues it once interrupts are on, straight to the enqueue so it cannot defer again. The gate is bare irqs_disabled(), so callers that merely hold interrupts off are deferred too and pay one irq_work hop. Skip it while the scheduler is down (RCU_SCHEDULER_INACTIVE): irq_work is not usable that early, rcu_init() already calls call_rcu(), and the per-CPU deferral state is not initialised until rcu_init_one() runs later in it. rcu_barrier() drains every CPU's ->defer_head before it scans the lists, and rcutree_migrate_callbacks() drains an outgoing CPU's. A drain re-issues onto the draining CPU, so a barrier moves other CPUs' staged callbacks onto its own ->cblist; call_rcu() promises no CPU affinity for invocation. ->defer_lock is held across llist_del_all() and the whole re-issue so the drainers serialize: one that finds the list empty can conclude that everything staged before it is already on a callback list. Interrupts stay off for the batch. Where the arch has an irq_work self-IPI that is what one interrupts-disabled region could stage, normally a single callback; where arch_irq_work_has_interrupt() is false the drain waits for the tick, so several regions can accumulate first. The drain clears ->next before re-issuing. A double call_rcu() on a head that is already debug-object-active self-links the staged node, and rcu_do_enqueue()'s duplicate path returns without clearing it, so the drain would spin. A re-add behind other staged callbacks makes a longer cycle, which that does not bound; a double call_rcu() stays undefined. llist_del_all() yields newest-first, so a batch is re-issued in reverse call order; nothing depends on call_rcu() ordering. The re-issue drops the lazy hint, since staging records only ->func, so a deferred callback loses its batching on CONFIG_RCU_LAZY. kasan_record_aux_stack() moves to __call_rcu_common() so a use-after-free report names the caller rather than the irq_work. The re-issue runs with interrupts disabled, so instrumentation on the enqueue path can re-enter call_rcu(), stage another callback and re-raise the irq_work, livelocking the drain. A per-CPU flag guards it: a deferral that arrives while this CPU is draining, and is not from an NMI, is dropped. The WARN_ONCE() is under CONFIG_PROVE_RCU, so a production kernel drops it silently. That leaks the callback and can strand state the caller tied to it, since a one-shot flag only the callback clears never resets, but the alternative is an unbounded loop. A callback deferred past the CPUHP_AP_SMPCFD_DYING irq_work flush leaves ->defer_work claimed with its self-IPI lost. rcutree_migrate_callbacks() still re-issues the callback, but the first deferral after that CPU comes back raises no IPI and waits for the next irq_work there, or for rcu_barrier(). Unqueueing an irq_work is not something the API offers. The irq_work is IRQ_WORK_INIT_HARD so the re-issue stays prompt on PREEMPT_RT, where a non-HARD irq_work runs in a kthread that can be delayed under load. A hidden CONFIG_RCU_DEFER gates the deferral code and its IRQ_WORK dependency, though the rcu_data members are unconditional; without it call_rcu() enqueues directly as before. Under CONFIG_PROVE_RCU, warn if the direct path is reached from an NMI. Suggested-by: Paul E. McKenney Signed-off-by: Puranjay Mohan Signed-off-by: Paul E. McKenney --- kernel/rcu/Kconfig | 6 +++ kernel/rcu/rcu.h | 11 ++++ kernel/rcu/tree.c | 131 +++++++++++++++++++++++++++++++++++++++++---- kernel/rcu/tree.h | 6 +++ 4 files changed, 143 insertions(+), 11 deletions(-) diff --git a/kernel/rcu/Kconfig b/kernel/rcu/Kconfig index 332df7a7a6347c..bc9483c0b5d84f 100644 --- a/kernel/rcu/Kconfig +++ b/kernel/rcu/Kconfig @@ -175,6 +175,12 @@ config RCU_STALL_COMMON config RCU_NEED_SEGCBLIST def_bool ( TREE_RCU || TREE_SRCU || TASKS_RCU_GENERIC ) +# The deferral (and the IRQ_WORK it uses) is only needed where call_rcu() / +# call_srcu() can be invoked while a callback-list operation is in flight. +config RCU_DEFER + def_bool HAVE_NMI || KPROBES || FUNCTION_TRACER || TRACEPOINTS + select IRQ_WORK + config RCU_FANOUT int "Tree-based hierarchical RCU fanout value" range 2 64 if 64BIT diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h index 39a9f6fa9a7b29..91e33571a554d9 100644 --- a/kernel/rcu/rcu.h +++ b/kernel/rcu/rcu.h @@ -572,6 +572,17 @@ static inline void tasks_cblist_init_generic(void) { } #define RCU_SCHEDULER_INIT 1 #define RCU_SCHEDULER_RUNNING 2 +/* + * Defer whenever interrupts are disabled, since a callback-list operation may + * be in flight on this CPU. Not before the scheduler is up: irq_work is not + * usable that early, and rcu_init() itself calls call_rcu(). + */ +static inline bool should_rcu_defer(void) +{ + return IS_ENABLED(CONFIG_RCU_DEFER) && irqs_disabled() && + rcu_scheduler_active != RCU_SCHEDULER_INACTIVE; +} + enum rcutorture_type { RCU_FLAVOR, RCU_TASKS_FLAVOR, diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 96848fc1f02b8f..ff9a2395c9e8e9 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -3148,21 +3149,19 @@ static void check_cb_ovld(struct rcu_data *rdp) raw_spin_unlock_rcu_node(rnp); } -static void -__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in) +/* + * Also called by __rcu_defer_drain() to re-issue a deferred callback, so it + * must not re-check the deferral condition. Either caller may have interrupts + * already disabled, and a drain of a remote CPU re-issues onto the draining + * CPU. + */ +static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func, bool lazy_in) { static atomic_t doublefrees; unsigned long flags; bool lazy; struct rcu_data *rdp; - /* Misaligned rcu_head! */ - WARN_ON_ONCE((unsigned long)head & (sizeof(void *) - 1)); - - /* Avoid NULL dereference if callback is NULL. */ - if (WARN_ON_ONCE(!func)) - return; - if (debug_rcu_head_queue(head)) { /* * Probable double call_rcu(), so leak the callback. @@ -3178,7 +3177,6 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in) } head->func = func; head->next = NULL; - kasan_record_aux_stack(head); local_irq_save(flags); rdp = this_cpu_ptr(&rcu_data); @@ -3206,6 +3204,103 @@ __call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in) local_irq_restore(flags); } +/* + * Re-issue deferred callbacks straight to the enqueue so they cannot defer + * again. ->defer_lock serializes the drainers: this CPU's irq_work, + * rcu_defer_flush() and rcutree_migrate_callbacks(). + */ +static void __rcu_defer_drain(struct rcu_data *rdp) +{ + struct llist_node *node, *next; + unsigned long flags; + + if (!IS_ENABLED(CONFIG_RCU_DEFER)) + return; + + raw_spin_lock_irqsave(&rdp->defer_lock, flags); + llist_for_each_safe(node, next, llist_del_all(&rdp->defer_head)) { + struct rcu_head *head = (struct rcu_head *)node; + + /* Bounds a node self-linked by a double call_rcu(). */ + head->next = NULL; + rcu_do_enqueue(head, head->func, false); + } + raw_spin_unlock_irqrestore(&rdp->defer_lock, flags); +} + +/* + * Only the irq_work drain can be re-fed by its own re-issue, so only it sets + * ->defer_draining. Anything staged during a direct drain is picked up by the + * staging CPU's own irq_work. Every caller of irq_work_run_list() has + * interrupts disabled, so the flag is never visible with them enabled. + */ +static void rcu_defer_drain(struct irq_work *iw) +{ + struct rcu_data *rdp = container_of(iw, struct rcu_data, defer_work); + + WRITE_ONCE(rdp->defer_draining, true); + __rcu_defer_drain(rdp); + WRITE_ONCE(rdp->defer_draining, false); +} + +/* + * Stage @head for this CPU's irq_work to re-issue once interrupts are on. Only + * the drain side takes a lock, so this stays safe from NMI. + */ +static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func) +{ + struct rcu_data *rdp = this_cpu_ptr(&rcu_data); + + /* + * Instrumentation on the enqueue path can re-enter here from inside the + * drain. Re-queuing would livelock it, so drop the callback; an NMI + * cannot loop, so let it through. + */ + if (READ_ONCE(rdp->defer_draining) && !in_nmi()) { + WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU), + "call_rcu() re-entered during callback drain; leaking callback\n"); + return; + } + head->func = func; + if (llist_add((struct llist_node *)head, &rdp->defer_head)) + irq_work_queue(&rdp->defer_work); +} + +static void rcu_defer_flush(void) +{ + int cpu; + + for_each_possible_cpu(cpu) + __rcu_defer_drain(per_cpu_ptr(&rcu_data, cpu)); +} + +static void +__call_rcu_common(struct rcu_head *head, rcu_callback_t func, bool lazy_in) +{ + /* Misaligned rcu_head! */ + WARN_ON_ONCE((unsigned long)head & (sizeof(void *) - 1)); + + /* Avoid NULL dereference if callback is NULL. */ + if (WARN_ON_ONCE(!func)) + return; + + /* Record the caller: the irq_work's stack says nothing about it. */ + kasan_record_aux_stack(head); + + if (should_rcu_defer()) { + call_rcu_defer(head, func); + return; + } + + /* + * Only reachable from an NMI when deferral is off: before the scheduler + * is up, or with CONFIG_RCU_DEFER=n. The enqueue can then race. + */ + WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi()); + + rcu_do_enqueue(head, func, lazy_in); +} + #ifdef CONFIG_RCU_LAZY static bool enable_rcu_lazy __read_mostly = !IS_ENABLED(CONFIG_RCU_LAZY_DEFAULT_OFF); module_param(enable_rcu_lazy, bool, 0444); @@ -3896,8 +3991,12 @@ void rcu_barrier(void) unsigned long flags; unsigned long gseq; struct rcu_data *rdp; - unsigned long s = rcu_seq_snap(&rcu_state.barrier_sequence); + unsigned long s; + /* Register any deferred callbacks before snapshotting the sequence. */ + rcu_defer_flush(); + + s = rcu_seq_snap(&rcu_state.barrier_sequence); rcu_barrier_trace(TPS("Begin"), -1, s); /* Take mutex to serialize concurrent rcu_barrier() requests. */ @@ -4231,6 +4330,9 @@ rcu_boot_init_percpu_data(int cpu) rdp->rcu_onl_gp_state = RCU_GP_CLEANED; rdp->last_sched_clock = jiffies; rdp->cpu = cpu; + init_llist_head(&rdp->defer_head); + raw_spin_lock_init(&rdp->defer_lock); + rdp->defer_work = IRQ_WORK_INIT_HARD(rcu_defer_drain); rcu_boot_init_nocb_percpu_data(rdp); } @@ -4528,6 +4630,13 @@ void rcutree_migrate_callbacks(int cpu) struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu); bool needwake; + /* + * Callbacks deferred past the point the outgoing CPU's irq_work can run + * sit on ->defer_head, which the ->cblist migration below does not + * cover. Drain them here, before the early returns. + */ + __rcu_defer_drain(rdp); + if (rcu_rdp_is_offloaded(rdp)) return; diff --git a/kernel/rcu/tree.h b/kernel/rcu/tree.h index eedfa43059e802..b7cac7a13b4f22 100644 --- a/kernel/rcu/tree.h +++ b/kernel/rcu/tree.h @@ -229,6 +229,12 @@ struct rcu_data { struct rcu_head barrier_head; int exp_watching_snap; /* Double-check need for IPI. */ + /* Deferral of an NMI/reentrant call_rcu(); see __call_rcu_common(). */ + struct llist_head defer_head; + struct irq_work defer_work; + raw_spinlock_t defer_lock; + bool defer_draining; + /* 5) Callback offloading. */ #ifdef CONFIG_RCU_NOCB_CPU struct swait_queue_head nocb_cb_wq; /* For nocb kthreads to sleep on. */ From 0ed912d346fdb2ecc3bd03674134b956aa8e7c5d Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:51 -0700 Subject: [PATCH 1183/2286] rcu: Make Tiny call_rcu() safe to call from any context Give Tiny call_rcu() the same treatment as Tree RCU. When interrupts are disabled and the scheduler is up, stage the callback on a lockless list that an irq_work re-issues later. One global list and irq_work suffice since Tiny RCU is uniprocessor, and there is no CPU-offline drain. The re-issue runs with interrupts disabled and can be re-entered by instrumentation, so a draining flag drops a deferring call_rcu() seen mid-drain (unless from an NMI), as in Tree RCU. Gated by CONFIG_RCU_DEFER, though the deferral state is unconditional. Interrupts stay off for the whole batch, but the re-issue is a tail append with no locks. TINY_RCU implies !SMP, where arch_irq_work_has_interrupt() is false, so the drain always waits for the tick and a batch is whatever one tick's worth of interrupts-disabled call_rcu()s staged. As in Tree RCU the drain clears ->next before re-issuing, which bounds a node self-linked by a double call_rcu(): rcu_do_enqueue()'s duplicate path returns without clearing it. A longer cycle is not bounded; a double call_rcu() stays undefined. The idle-task reschedule moves out of the enqueue helper so that a drain does it once for the batch rather than once per callback, which would otherwise take the runqueue lock N times with interrupts disabled. Suggested-by: Paul E. McKenney Signed-off-by: Puranjay Mohan Signed-off-by: Paul E. McKenney --- kernel/rcu/tiny.c | 127 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 23 deletions(-) diff --git a/kernel/rcu/tiny.c b/kernel/rcu/tiny.c index dccccd6be94111..656b6a682e31a0 100644 --- a/kernel/rcu/tiny.c +++ b/kernel/rcu/tiny.c @@ -11,6 +11,8 @@ */ #include #include +#include +#include #include #include #include @@ -42,8 +44,100 @@ static struct rcu_ctrlblk rcu_ctrlblk = { .gp_seq = 0 - 300UL, }; +/* + * The callback list is only accessed with interrupts disabled, so a call_rcu() + * that arrives with interrupts off stages the callback on a lockless list that + * an irq_work re-issues later. One global list and irq_work suffice, as Tiny + * RCU is uniprocessor. + */ +static void rcu_defer_drain(struct irq_work *iw); +static LLIST_HEAD(rcu_defer_list); +static struct irq_work rcu_defer_iw = IRQ_WORK_INIT_HARD(rcu_defer_drain); +static bool rcu_defer_draining; + +/* + * Also called by __rcu_defer_drain() to re-issue a deferred callback, so it + * must not re-check the deferral condition. + */ +static void rcu_do_enqueue(struct rcu_head *head, rcu_callback_t func) +{ + static atomic_t doublefrees; + unsigned long flags; + + if (debug_rcu_head_queue(head)) { + if (atomic_inc_return(&doublefrees) < 4) { + pr_err("%s(): Double-freed CB %p->%pS()!!! ", __func__, head, head->func); + mem_dump_obj(head); + } + return; + } + + head->func = func; + head->next = NULL; + + local_irq_save(flags); + *rcu_ctrlblk.curtail = head; + rcu_ctrlblk.curtail = &head->next; + local_irq_restore(flags); +} + +/* Force scheduling for rcu_qs() when enqueuing from the idle task. */ +static void rcu_resched_if_idle(void) +{ + if (unlikely(is_idle_task(current))) + resched_cpu(0); +} + +static void __rcu_defer_drain(void) +{ + struct llist_node *node, *next; + bool drained = false; + unsigned long flags; + + if (!IS_ENABLED(CONFIG_RCU_DEFER)) + return; + + /* Re-issued newest-first; nothing depends on call_rcu() ordering. */ + local_irq_save(flags); + llist_for_each_safe(node, next, llist_del_all(&rcu_defer_list)) { + struct rcu_head *head = (struct rcu_head *)node; + + /* Bounds a node self-linked by a double call_rcu(). */ + head->next = NULL; + rcu_do_enqueue(head, head->func); + drained = true; + } + local_irq_restore(flags); + + if (drained) + rcu_resched_if_idle(); +} + +/* Only the irq_work drain can be re-fed by its own re-issue; see Tree RCU. */ +static void rcu_defer_drain(struct irq_work *iw) +{ + WRITE_ONCE(rcu_defer_draining, true); + __rcu_defer_drain(); + WRITE_ONCE(rcu_defer_draining, false); +} + +static void call_rcu_defer(struct rcu_head *head, rcu_callback_t func) +{ + /* A re-entrant call_rcu() during the drain would livelock it; drop it. */ + if (READ_ONCE(rcu_defer_draining) && !in_nmi()) { + WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU), + "call_rcu() re-entered during callback drain; leaking callback\n"); + return; + } + head->func = func; + if (llist_add((struct llist_node *)head, &rcu_defer_list)) + irq_work_queue(&rcu_defer_iw); +} + void rcu_barrier(void) { + /* Register any deferred callbacks so the wait below covers them. */ + __rcu_defer_drain(); wait_rcu_gp(call_rcu_hurry); } EXPORT_SYMBOL(rcu_barrier); @@ -157,29 +251,19 @@ EXPORT_SYMBOL_GPL(synchronize_rcu); */ void call_rcu(struct rcu_head *head, rcu_callback_t func) { - static atomic_t doublefrees; - unsigned long flags; - - if (debug_rcu_head_queue(head)) { - if (atomic_inc_return(&doublefrees) < 4) { - pr_err("%s(): Double-freed CB %p->%pS()!!! ", __func__, head, head->func); - mem_dump_obj(head); - } + if (should_rcu_defer()) { + call_rcu_defer(head, func); return; } - head->func = func; - head->next = NULL; + /* + * Only reachable from an NMI when deferral is off: before the scheduler + * is up, or with CONFIG_RCU_DEFER=n. The enqueue can then race. + */ + WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi()); - local_irq_save(flags); - *rcu_ctrlblk.curtail = head; - rcu_ctrlblk.curtail = &head->next; - local_irq_restore(flags); - - if (unlikely(is_idle_task(current))) { - /* force scheduling for rcu_qs() */ - resched_cpu(0); - } + rcu_do_enqueue(head, func); + rcu_resched_if_idle(); } EXPORT_SYMBOL_GPL(call_rcu); @@ -211,10 +295,7 @@ unsigned long start_poll_synchronize_rcu(void) { unsigned long gp_seq = get_state_synchronize_rcu(); - if (unlikely(is_idle_task(current))) { - /* force scheduling for rcu_qs() */ - resched_cpu(0); - } + rcu_resched_if_idle(); return gp_seq; } EXPORT_SYMBOL_GPL(start_poll_synchronize_rcu); From 35a26d87042b7c9814165f244a141104655ed345 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:52 -0700 Subject: [PATCH 1184/2286] srcu: Make call_srcu() safe to call from any context call_srcu() has the same constraint as call_rcu(): srcu_gp_start_if_needed() enqueues under raw_spin_lock_irqsave() and may walk the srcu_node tree, as do callback invocation and grace-period work, so a call_srcu() with interrupts already disabled can race an operation in flight on this CPU. call_rcu_tasks_trace() is call_srcu() under the hood, so a sleepable BPF program freeing an object can reach this. Defer as call_rcu() does: stage the callback on the srcu_data's ->defer_cbs, chain that srcu_data onto a per-CPU list, and raise a per-CPU irq_work that re-issues it straight to the enqueue helper, never back through __call_srcu(). The irqs-enabled path is unchanged; as for call_rcu() the gate is bare irqs_disabled(), so callers that merely hold interrupts off are deferred too and pay one irq_work hop, including call_rcu_tasks_trace() from the BPF memalloc irq_work. The irq_work is per-CPU rather than per-srcu_struct and statically initialized, so deferral never runs check_init_srcu_struct(); it is IRQ_WORK_INIT_HARD as for call_rcu(). srcu_barrier() flushes it first, and rcutree_migrate_callbacks() calls srcu_offline_drain() for an outgoing CPU. cleanup_srcu_struct() drains before its "just leak it" early returns, and srcu_module_going() before freeing any ->sda, since a staged srcu_data left chained on a per-CPU list would dangle. Staging is two steps, the callback onto ->defer_cbs and then the srcu_data onto the per-CPU list, so a flusher can find the per-CPU list empty while a callback whose call_srcu() has not returned sits on ->defer_cbs; the staging CPU's own irq_work takes that one. The per-CPU srcu_defer ->lock, not any srcu_data's, is held with interrupts off across the whole nested drain: the chain of srcu_datas staged on that CPU and, for each, its callbacks, with srcu_do_enqueue() taking that srcu_data's ->lock and possibly starting a grace period for every one. That is what serializes the drainers. The bound is as for call_rcu(): what one interrupts-disabled region could stage, normally a single callback, or whatever accumulates before the tick where arch_irq_work_has_interrupt() is false. Both lists are drained newest-first; nothing depends on call_srcu() ordering. The drain clears ->next before re-issuing, which bounds a node self-linked by a double call_srcu(); a longer cycle is not bounded, and a double call_srcu() stays undefined, as for call_rcu(). A callback deferred past the CPUHP_AP_SMPCFD_DYING irq_work flush leaves that CPU's srcu_defer ->iw claimed with its self-IPI lost, as for call_rcu(). srcu_offline_drain() still re-issues the callback, but the irq_work cannot be un-queued, and here the claim is shared by every srcu_struct on the CPU. As in call_rcu(), the re-issue runs with interrupts disabled and can be re-entered by instrumentation, so a per-CPU flag, set only while that CPU is inside its own irq_work drain, drops a deferring call_srcu() seen mid-drain unless it comes from an NMI. Such a drop can strand state the caller associated with the callback, not just the callback itself. Staging records only the callback, so a deferred expedited call_srcu() completes as a normal grace period. Only srcu_expedite_current() can hit that, and only when invoked with interrupts already disabled. Gated by CONFIG_RCU_DEFER, though the srcu_data members and the per-CPU srcu_defer are unconditional. Under CONFIG_PROVE_RCU, warn if the direct path is reached from an NMI. Suggested-by: Paul E. McKenney Signed-off-by: Puranjay Mohan Signed-off-by: Paul E. McKenney --- include/linux/srcutree.h | 4 + kernel/rcu/rcu.h | 3 + kernel/rcu/srcutree.c | 170 ++++++++++++++++++++++++++++++++++++++- kernel/rcu/tree.c | 2 + 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h index 75e54e4f963fac..1ce759fb70948b 100644 --- a/include/linux/srcutree.h +++ b/include/linux/srcutree.h @@ -13,6 +13,8 @@ #include #include +#include +#include struct srcu_node; struct srcu_struct; @@ -41,6 +43,8 @@ struct srcu_data { bool srcu_cblist_invoking; /* Invoking these CBs? */ struct timer_list delay_work; /* Delay for CB invoking */ struct work_struct work; /* Context for CB invoking. */ + struct llist_head defer_cbs; /* Callbacks deferred on re-entry. */ + struct llist_node defer_link; /* Links onto the per-CPU deferral drain list */ struct rcu_head srcu_barrier_head; /* For srcu_barrier() use. */ struct rcu_head srcu_ec_head; /* For srcu_expedite_current() use. */ int srcu_ec_state; /* State for srcu_expedite_current(). */ diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h index 91e33571a554d9..d60444bf3a02dd 100644 --- a/kernel/rcu/rcu.h +++ b/kernel/rcu/rcu.h @@ -583,6 +583,9 @@ static inline bool should_rcu_defer(void) rcu_scheduler_active != RCU_SCHEDULER_INACTIVE; } +/* Drain an outgoing CPU's deferred SRCU callbacks; see rcutree_migrate_callbacks(). */ +void srcu_offline_drain(int cpu); + enum rcutorture_type { RCU_FLAVOR, RCU_TASKS_FLAVOR, diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index ad27880dd690f4..b1b3c81a72f45f 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,38 @@ static void process_srcu(struct work_struct *work); static void srcu_irq_work(struct irq_work *work); static void srcu_delay_timer(struct timer_list *t); +struct srcu_defer; +static void srcu_defer_drain(struct irq_work *iw); +static void __srcu_defer_drain(struct srcu_defer *sndp); + +/* + * Per-CPU call_srcu() deferral state, shared by every srcu_struct. A deferred + * callback is staged on its srcu_data's ->defer_cbs; that srcu_data is chained + * via ->defer_link onto ->list, which the irq_work walks. + */ +struct srcu_defer { + struct llist_head list; + struct irq_work iw; + raw_spinlock_t lock; + bool draining; +}; + +static DEFINE_PER_CPU(struct srcu_defer, srcu_defer) = { + .lock = __RAW_SPIN_LOCK_UNLOCKED(srcu_defer.lock), + .iw = IRQ_WORK_INIT_HARD(srcu_defer_drain), +}; + +/* + * Flush pending deferred callbacks so a following srcu_barrier() waits for them. + */ +static void srcu_defer_flush(void) +{ + int cpu; + + for_each_possible_cpu(cpu) + __srcu_defer_drain(&per_cpu(srcu_defer, cpu)); +} + /* * Initialize SRCU per-CPU data. Note that statically allocated * srcu_struct structures might already have srcu_read_lock() and @@ -107,6 +140,11 @@ static void init_srcu_struct_data(struct srcu_struct *ssp) sdp->cpu = cpu; INIT_WORK(&sdp->work, srcu_invoke_callbacks); timer_setup(&sdp->delay_work, srcu_delay_timer, 0); + /* + * ->defer_cbs and ->defer_link are valid when zeroed and are not + * reinitialized here: that would clobber callbacks a reentrant + * call_srcu() already staged. See __call_srcu(). + */ sdp->ssp = ssp; } } @@ -688,6 +726,14 @@ void cleanup_srcu_struct(struct srcu_struct *ssp) unsigned long delay; struct srcu_usage *sup = ssp->srcu_sup; + /* + * Drain before the early returns below: they leak the srcu_struct, but + * srcu_module_going() frees ->sda regardless, and a staged srcu_data + * left chained on a per-CPU list would then dangle. Draining first also + * has to precede the ->irq_work sync, since re-issuing a callback can + * start a grace period and re-queue ->irq_work, which schedules ->work. + */ + srcu_defer_flush(); raw_spin_lock_irq_rcu_node(ssp->srcu_sup); delay = srcu_get_delay(ssp); raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); @@ -695,7 +741,6 @@ void cleanup_srcu_struct(struct srcu_struct *ssp) return; /* Just leak it! */ if (WARN_ON(srcu_readers_active(ssp))) return; /* Just leak it! */ - /* Wait for irq_work to finish first as it may queue a new work. */ irq_work_sync(&sup->irq_work); flush_delayed_work(&sup->work); for_each_possible_cpu(cpu) { @@ -1411,8 +1456,8 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp, * srcu_read_lock(), and srcu_read_unlock() that are all passed the same * srcu_struct structure. */ -static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, - rcu_callback_t func, bool do_norm) +static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp, + rcu_callback_t func, bool do_norm) { if (debug_rcu_head_queue(rhp)) { /* Probable double call_srcu(), so leak the callback. */ @@ -1424,6 +1469,108 @@ static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, (void)srcu_gp_start_if_needed(ssp, rhp, do_norm); } +/* + * The srcu_cblist and srcu_node tree are only accessed with interrupts + * disabled, so defer when interrupts are already off rather than enqueue into + * an operation that may be in flight on this CPU. + */ +static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, + rcu_callback_t func, bool do_norm) +{ + if (should_rcu_defer()) { + struct srcu_defer *sndp = this_cpu_ptr(&srcu_defer); + struct srcu_data *sdp; + + /* + * Instrumentation on the enqueue path can re-enter here from + * inside the drain. Re-queuing would livelock it, so drop the + * callback; an NMI cannot loop, so let it through. + */ + if (READ_ONCE(sndp->draining) && !in_nmi()) { + WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU), + "call_srcu() re-entered during callback drain; leaking callback\n"); + return; + } + sdp = this_cpu_ptr(ssp->sda); + rhp->func = func; + if (llist_add((struct llist_node *)rhp, &sdp->defer_cbs)) { + /* + * Chain this srcu_data for the drain. ->ssp must be + * published here: deferral skips + * check_init_srcu_struct(), so on a never-initialized + * static srcu_struct the srcu_data are still zeroed and + * the drain would read a NULL ->ssp. + */ + sdp->ssp = ssp; + if (llist_add(&sdp->defer_link, &sndp->list)) + irq_work_queue(&sndp->iw); + } + return; + } + + /* + * Only reachable from an NMI when deferral is off: before the scheduler + * is up, or with CONFIG_RCU_DEFER=n. The enqueue can then race. + */ + WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi()); + + srcu_do_enqueue(ssp, rhp, func, do_norm); +} + +/* + * Re-issue deferred callbacks straight to srcu_do_enqueue() so they cannot defer + * again. ->lock serializes the drainers: the irq_work, srcu_defer_flush() and + * srcu_offline_drain(). + */ +static void __srcu_defer_drain(struct srcu_defer *sndp) +{ + struct llist_node *snode, *snext; + unsigned long flags; + + if (!IS_ENABLED(CONFIG_RCU_DEFER)) + return; + + raw_spin_lock_irqsave(&sndp->lock, flags); + llist_for_each_safe(snode, snext, llist_del_all(&sndp->list)) { + struct srcu_data *sdp = container_of(snode, struct srcu_data, defer_link); + struct srcu_struct *ssp = sdp->ssp; + struct llist_node *cnode, *cnext; + + cnode = llist_del_all(&sdp->defer_cbs); + llist_for_each_safe(cnode, cnext, cnode) { + struct rcu_head *rhp = (struct rcu_head *)cnode; + + /* Bounds a node self-linked by a double call_srcu(). */ + rhp->next = NULL; + srcu_do_enqueue(ssp, rhp, rhp->func, true); + } + } + raw_spin_unlock_irqrestore(&sndp->lock, flags); +} + +/* + * Only the irq_work drain can be re-fed by its own re-issue, so only it sets + * ->draining. A direct drain re-issues onto this CPU, and anything staged + * during it is picked up by that CPU's own irq_work. + */ +static void srcu_defer_drain(struct irq_work *iw) +{ + struct srcu_defer *sndp = container_of(iw, struct srcu_defer, iw); + + WRITE_ONCE(sndp->draining, true); + __srcu_defer_drain(sndp); + WRITE_ONCE(sndp->draining, false); +} + +/* + * Drain @cpu's deferred call_srcu() callbacks once @cpu is dead. One pass + * covers every srcu_struct; the re-issue lands on the current CPU. + */ +void srcu_offline_drain(int cpu) +{ + __srcu_defer_drain(&per_cpu(srcu_defer, cpu)); +} + /** * call_srcu() - Queue a callback for invocation after an SRCU grace period * @ssp: srcu_struct in queue the callback @@ -1678,9 +1825,18 @@ void srcu_barrier(struct srcu_struct *ssp) { int cpu; int idx; - unsigned long s = rcu_seq_snap(&ssp->srcu_sup->srcu_barrier_seq); + unsigned long s; check_init_srcu_struct(ssp); + + /* + * Register any deferred callbacks before snapshotting the sequence. The + * staging list is per-CPU, not per-srcu_struct, so this also drains + * other srcu_structs'. + */ + srcu_defer_flush(); + + s = rcu_seq_snap(&ssp->srcu_sup->srcu_barrier_seq); mutex_lock(&ssp->srcu_sup->srcu_barrier_mutex); if (rcu_seq_done(&ssp->srcu_sup->srcu_barrier_seq, s)) { smp_mb(); /* Force ordering following return. */ @@ -2135,6 +2291,12 @@ static void srcu_module_going(struct module *mod) struct srcu_struct *ssp; struct srcu_struct **sspp = mod->srcu_struct_ptrs; + /* + * Deferral skips check_init_srcu_struct(), so cleanup_srcu_struct() + * below can be skipped for an srcu_struct that has staged callbacks. + * Drain them before any ->sda is freed. + */ + srcu_defer_flush(); for (i = 0; i < mod->num_srcu_structs; i++) { ssp = *(sspp++); if (!rcu_seq_state(smp_load_acquire(&ssp->srcu_sup->srcu_gp_seq_needed)) && diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index ff9a2395c9e8e9..e363e1a6a33c9f 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -4636,6 +4636,8 @@ void rcutree_migrate_callbacks(int cpu) * cover. Drain them here, before the early returns. */ __rcu_defer_drain(rdp); + /* Likewise for the outgoing CPU's deferred call_srcu() callbacks. */ + srcu_offline_drain(cpu); if (rcu_rdp_is_offloaded(rdp)) return; From a04b2854bedc788d2e1bb421592c2a68633fd036 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:53 -0700 Subject: [PATCH 1185/2286] srcu: Make Tiny call_srcu() safe to call from any context Give Tiny call_srcu() the same treatment as Tree SRCU. When interrupts are disabled and the scheduler is up, stage the callback on the srcu_struct's lockless list for an irq_work to re-issue later. Tiny SRCU is uniprocessor, so there is no CPU-offline drain. A draining flag drops a deferring call_srcu() that re-enters mid-drain (unless from an NMI), as in Tree SRCU; such a drop can strand state the caller tied to the callback, not just the callback itself. Interrupts stay off for the whole batch. TINY_SRCU implies !SMP, where arch_irq_work_has_interrupt() is false, so the drain always waits for the tick and a batch is whatever one tick's worth of interrupts-disabled call_srcu()s staged. Unlike the other three flavors srcu_do_enqueue() here has no debug_rcu_head_queue(), so nothing reports a double call_srcu(); termination of the drain rests on srcu_do_enqueue() clearing ->next, and the callback list self-links at the tail exactly as a double call_srcu() made it before. srcu_barrier() (now out of line) and cleanup_srcu_struct() drain the deferred list first, so a deferred callback is re-issued onto the callback list and invoked by the grace-period work that cleanup_srcu_struct() flushes, rather than stranded on a soon-to-be-freed srcu_struct. cleanup_srcu_struct() also syncs ->defer_iw, since that irq_work is embedded in the srcu_struct the caller is about to free. The draining flag is global rather than per-srcu_struct: a re-entrant call_srcu(B) inside a drain of A raises B's own ->defer_iw, whose drain can stage back onto A, so a per-srcu_struct flag would not break the chain. The cost is that a drain of A also drops a non-NMI call_srcu() to any other srcu_struct for its duration. Gated by CONFIG_RCU_DEFER like Tree SRCU, though the srcu_struct members are unconditional. Suggested-by: Paul E. McKenney Signed-off-by: Puranjay Mohan Signed-off-by: Paul E. McKenney --- include/linux/srcutiny.h | 12 ++++-- kernel/rcu/srcutiny.c | 93 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 8 deletions(-) diff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h index fbcf13bc12d15e..85b5de438450b3 100644 --- a/include/linux/srcutiny.h +++ b/include/linux/srcutiny.h @@ -12,6 +12,7 @@ #define _LINUX_SRCU_TINY_H #include +#include #include struct srcu_struct { @@ -26,6 +27,8 @@ struct srcu_struct { struct rcu_head **srcu_cb_tail; /* Pending callbacks: Tail. */ struct work_struct srcu_work; /* For driving grace periods. */ struct irq_work srcu_irq_work; /* Defer schedule_work() to irq work. */ + struct llist_head defer_cbs; /* Callbacks deferred on re-entry. */ + struct irq_work defer_iw; /* Re-issues defer_cbs later. */ #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */ @@ -33,6 +36,7 @@ struct srcu_struct { void srcu_drive_gp(struct work_struct *wp); void srcu_tiny_irq_work(struct irq_work *irq_work); +void srcu_defer_drain(struct irq_work *irq_work); #define __SRCU_STRUCT_INIT(name, __ignored, ___ignored, ____ignored) \ { \ @@ -40,6 +44,9 @@ void srcu_tiny_irq_work(struct irq_work *irq_work); .srcu_cb_tail = &name.srcu_cb_head, \ .srcu_work = __WORK_INITIALIZER(name.srcu_work, srcu_drive_gp), \ .srcu_irq_work = { .func = srcu_tiny_irq_work }, \ + .defer_cbs = LLIST_HEAD_INIT(name.defer_cbs), \ + .defer_iw = { .node = { .u_flags = IRQ_WORK_HARD_IRQ }, \ + .func = srcu_defer_drain }, \ __SRCU_DEP_MAP_INIT(name) \ } @@ -131,10 +138,7 @@ static inline void synchronize_srcu_expedited(struct srcu_struct *ssp) synchronize_srcu(ssp); } -static inline void srcu_barrier(struct srcu_struct *ssp) -{ - synchronize_srcu(ssp); -} +void srcu_barrier(struct srcu_struct *ssp); static inline void srcu_expedite_current(struct srcu_struct *ssp) { } #define srcu_check_read_flavor(ssp, read_flavor) do { } while (0) diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c index 558ba8d316db6c..5de9a690583835 100644 --- a/kernel/rcu/srcutiny.c +++ b/kernel/rcu/srcutiny.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,8 @@ extern int rcu_scheduler_active; static LIST_HEAD(srcu_boot_list); static bool srcu_init_done; +static void __srcu_defer_drain(struct srcu_struct *ssp); + static int init_srcu_struct_fields(struct srcu_struct *ssp) { ssp->srcu_lock_nesting[0] = 0; @@ -43,6 +46,8 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp) INIT_WORK(&ssp->srcu_work, srcu_drive_gp); INIT_LIST_HEAD(&ssp->srcu_work.entry); init_irq_work(&ssp->srcu_irq_work, srcu_tiny_irq_work); + init_llist_head(&ssp->defer_cbs); + ssp->defer_iw = IRQ_WORK_INIT_HARD(srcu_defer_drain); return 0; } @@ -86,6 +91,16 @@ EXPORT_SYMBOL_GPL(init_srcu_struct_generic); void cleanup_srcu_struct(struct srcu_struct *ssp) { WARN_ON(srcu_readers_active(ssp)); + /* + * Re-issue any deferred callbacks, then wait out ->defer_iw before it is + * freed. Skipped entirely with CONFIG_RCU_DEFER=n: irq_work_sync() ends + * in an unconditional synchronize_rcu() wherever + * arch_irq_work_has_interrupt() is false, which is every !SMP target. + */ + if (IS_ENABLED(CONFIG_RCU_DEFER)) { + __srcu_defer_drain(ssp); + irq_work_sync(&ssp->defer_iw); + } irq_work_sync(&ssp->srcu_irq_work); flush_work(&ssp->srcu_work); WARN_ON(ssp->srcu_gp_running); @@ -213,11 +228,11 @@ static void srcu_gp_start_if_needed(struct srcu_struct *ssp) } /* - * Enqueue an SRCU callback on the specified srcu_struct structure, - * initiating grace-period processing if it is not already running. + * Also called by __srcu_defer_drain() to re-issue a deferred callback, so it + * must not re-check the deferral condition. */ -void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, - rcu_callback_t func) +static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp, + rcu_callback_t func) { unsigned long flags; @@ -231,6 +246,68 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, srcu_gp_start_if_needed(ssp); preempt_enable(); } + +/* + * Set only by the irq_work drain, the one drain its own re-issue can re-feed; + * a callback staged during a direct drain is taken by ->defer_iw afterwards. + * Global rather than per-srcu_struct: a re-entrant call_srcu(B) inside a drain + * of A raises B's own ->defer_iw, whose drain can stage back onto A. + */ +static bool srcu_defer_draining; + +static void __srcu_defer_drain(struct srcu_struct *ssp) +{ + struct llist_node *node, *next; + unsigned long flags; + + if (!IS_ENABLED(CONFIG_RCU_DEFER)) + return; + + /* Re-issued newest-first; nothing depends on call_srcu() ordering. */ + local_irq_save(flags); + llist_for_each_safe(node, next, llist_del_all(&ssp->defer_cbs)) { + struct rcu_head *rhp = (struct rcu_head *)node; + + srcu_do_enqueue(ssp, rhp, rhp->func); + } + local_irq_restore(flags); +} + +/* Only the irq_work drain can be re-fed by its own re-issue; see Tree SRCU. */ +void srcu_defer_drain(struct irq_work *iw) +{ + struct srcu_struct *ssp = container_of(iw, struct srcu_struct, defer_iw); + + WRITE_ONCE(srcu_defer_draining, true); + __srcu_defer_drain(ssp); + WRITE_ONCE(srcu_defer_draining, false); +} +EXPORT_SYMBOL_GPL(srcu_defer_drain); + +void call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, + rcu_callback_t func) +{ + if (should_rcu_defer()) { + /* A re-entrant call_srcu() during the drain would livelock it. */ + if (READ_ONCE(srcu_defer_draining) && !in_nmi()) { + WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU), + "call_srcu() re-entered during callback drain; leaking callback\n"); + return; + } + rhp->func = func; + if (llist_add((struct llist_node *)rhp, &ssp->defer_cbs)) + irq_work_queue(&ssp->defer_iw); + return; + } + + /* + * Only reachable from an NMI when deferral is off: before the scheduler + * is up, or with CONFIG_RCU_DEFER=n. The enqueue can then race. + */ + WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi()); + + srcu_do_enqueue(ssp, rhp, func); +} EXPORT_SYMBOL_GPL(call_srcu); /* @@ -260,6 +337,14 @@ void synchronize_srcu(struct srcu_struct *ssp) } EXPORT_SYMBOL_GPL(synchronize_srcu); +/* Register any deferred callbacks, then wait for all in-flight ones. */ +void srcu_barrier(struct srcu_struct *ssp) +{ + __srcu_defer_drain(ssp); + synchronize_srcu(ssp); +} +EXPORT_SYMBOL_GPL(srcu_barrier); + /* * get_state_synchronize_srcu - Provide an end-of-grace-period cookie */ From d23e30d76c32d88845ce8cf4ce069addb5fa4910 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 5 Aug 2026 13:18:58 -0700 Subject: [PATCH 1186/2286] rcutorture: Disable fragile readers during overload testing Vanilla RCU readers, when either non-preemptible or preemptible with priority-boosting enabled, can handle heavy overload conditions. Other RCU implementations are more fragile. This commit adds an rcu_torture_ops structure field named ->rdrs_handle_load that is set for non-fragile configurations of vanilla RCU, but cleared otherwise. It also refrains from running fragile readers concurrently with overload testing, thus avoiding false-positive overload-testing failures. Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 794937e13e7c32..7e08be857f01fa 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -440,6 +440,7 @@ struct rcu_torture_ops { int debug_objects; int start_poll_irqsoff; int have_up_down; + int rdrs_handle_load; const char *name; }; @@ -648,6 +649,7 @@ static struct rcu_torture_ops rcu_ops = { .extendables = RCUTORTURE_MAX_EXTEND, .debug_objects = 1, .start_poll_irqsoff = 1, + .rdrs_handle_load = !IS_ENABLED(CONFIG_PREEMPT_RCU) || IS_ENABLED(CONFIG_RCU_BOOST), .name = "rcu" }; @@ -2534,8 +2536,10 @@ static bool rcu_torture_one_read_start(struct rcu_torture_one_read_state *rtorsp rtorsp->p = rcu_dereference_check(rcu_torture_current, !cur_ops->readlock_held || cur_ops->readlock_held() || (rtorsp->readstate & RCUTORTURE_RDR_UPDOWN)); - if (rtorsp->p == NULL) { - /* Wait for rcu_torture_writer to get underway */ + if ((!cur_ops->rdrs_handle_load && atomic_read(&rcu_fwd_cb_nodelay)) || rtorsp->p == NULL) { + // Wait for rcu_torture_writer to get underway and + // (if readers cannot handle heavy loads) for any + // forward-progress testing to complete. rcutorture_one_extend(&rtorsp->readstate, 0, trsp, rtorsp->rtrsp); return false; } From 813f2ad27003296b9a0dc8ffd764c834626a2549 Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:54 -0700 Subject: [PATCH 1187/2286] rcutorture: Exercise ->call() from NMI context call_rcu() and call_srcu() are now safe to invoke from NMI, but rcutorture never does, leaving the deferral path untested. Add an ->nmi_capable flag to rcu_torture_ops. For flavors that set it, arm a per-CPU hardware perf counter whose overflow handler submits a callback via ->call(). The handler acts only when in_nmi(), so only a genuine NMI exercises the deferral path. One preallocated callback per CPU is kept in flight, guarded by an atomic, to avoid allocating in NMI. The counter uses a fixed sample period rather than a frequency: a frequency-based event sets TICK_DEP_BIT_PERF_EVENTS and would pin the tick for the whole run on NO_HZ_FULL kernels. Report the count issued from NMI ("nmi-calls:") and the count invoked ("nmi-cbs:"). rcu_torture_cleanup() disables the counters and then calls cb_barrier(), which drains every deferred callback, so the two counts must then match; a mismatch fails the test. This relies on rcu_barrier()/srcu_barrier() flushing deferred callbacks, as added earlier in the series. Set ->nmi_capable on the NMI-safe flavors: rcu, srcu, srcud, and tasks-tracing (call_srcu() under the hood). Tasks and Tasks Rude are left alone, as call_rcu_tasks_generic() is not yet NMI-safe. Enabled by default; the nmi_calls parameter disables it, which helps rule NMI handling in or out when triaging a failure. Requires CONFIG_PERF_EVENTS and a hardware PMU: without one nothing is issued from NMI and the end-of-test check compares zero against zero, so a pass does not by itself mean the path ran. That is the case under kvm.sh, which boots qemu with -cpu kvm64 and no vPMU; rcu_torture_nmi_cleanup() says so on the console. Signed-off-by: Puranjay Mohan Signed-off-by: Paul E. McKenney --- .../admin-guide/kernel-parameters.txt | 7 + kernel/rcu/rcutorture.c | 150 +++++++++++++++++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..fd9acc3fd9b7a4 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -6173,6 +6173,13 @@ Kernel parameters stress RCU, they don't participate in the actual test, hence the "fake". + rcutorture.nmi_calls= [KNL] + Enable issuing RCU callbacks from an NMI, on the + RCU flavors that support it, to exercise the + any-context callback path. Requires + CONFIG_PERF_EVENTS and a hardware PMU; without + both, nothing is issued. Defaults to enabled. + rcutorture.nocbs_nthreads= [KNL] Set number of RCU callback-offload togglers. Zero (the default) disables toggling. diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 7e08be857f01fa..4d1be2a49f011b 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -48,6 +48,7 @@ #include #include #include +#include #include "rcu.h" @@ -115,6 +116,7 @@ torture_param(int, leakpointer, 0, "Leak pointer dereferences from readers"); torture_param(int, n_barrier_cbs, 0, "# of callbacks/kthreads for barrier testing"); torture_param(int, n_up_down, 32, "# of concurrent up/down hrtimer-based RCU readers"); torture_param(int, nfakewriters, 4, "Number of RCU fake writer threads"); +torture_param(bool, nmi_calls, true, "Exercise ->call() from NMI on nmi_capable flavors"); torture_param(int, nreaders, -1, "Number of RCU reader threads"); torture_param(bool, nwriters, 1, "Number of RCU writer threads (0 or 1)"); torture_param(int, object_debug, 0, "Enable debug-object double call_rcu() testing"); @@ -216,6 +218,8 @@ static long n_rcu_torture_boost_failure; static long n_rcu_torture_boosts; static atomic_long_t n_rcu_torture_timers; static atomic_long_t n_rcu_torture_irqs; +static atomic_long_t n_rcu_torture_nmi_call; +static atomic_long_t n_rcu_torture_nmi_cb; static long n_barrier_attempts; static long n_barrier_successes; /* did rcu_barrier test succeed? */ static unsigned long n_read_exits; @@ -433,6 +437,7 @@ struct rcu_torture_ops { bool (*is_task_rcu_boosted)(void); long cbflood_max; int irq_capable; + int nmi_capable; int can_boost; int extendables; int slow_gps; @@ -650,6 +655,7 @@ static struct rcu_torture_ops rcu_ops = { .debug_objects = 1, .start_poll_irqsoff = 1, .rdrs_handle_load = !IS_ENABLED(CONFIG_PREEMPT_RCU) || IS_ENABLED(CONFIG_RCU_BOOST), + .nmi_capable = 1, .name = "rcu" }; @@ -944,6 +950,7 @@ static struct rcu_torture_ops srcu_ops = { .debug_objects = 1, .have_up_down = IS_ENABLED(CONFIG_TINY_SRCU) ? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN, + .nmi_capable = 1, .name = "srcu" }; @@ -1007,6 +1014,7 @@ static struct rcu_torture_ops srcud_ops = { .debug_objects = 1, .have_up_down = IS_ENABLED(CONFIG_TINY_SRCU) ? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN, + .nmi_capable = 1, .name = "srcud" }; @@ -1271,6 +1279,7 @@ static struct rcu_torture_ops tasks_tracing_ops = { .cbflood_max = 50000, .irq_capable = 1, .slow_gps = 1, + .nmi_capable = 1, .name = "tasks-tracing" }; @@ -2663,6 +2672,124 @@ static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid) static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_timer_rand); +/* + * Exercise ->call() from NMI context for flavors that set ->nmi_capable. A + * per-CPU hardware perf counter overflows into an NMI, and its handler submits + * a preallocated callback via ->call(). One callback per CPU is in flight at a + * time (guarded by an atomic) to avoid allocating in NMI. + */ +#ifdef CONFIG_PERF_EVENTS +static struct perf_event_attr rcu_torture_nmi_attr = { + .type = PERF_TYPE_HARDWARE, + .config = PERF_COUNT_HW_CPU_CYCLES, + .size = sizeof(struct perf_event_attr), + .pinned = 1, + .disabled = 1, + /* + * A fixed period rather than .freq: a frequency-based event bumps + * nr_freq_events, which sets TICK_DEP_BIT_PERF_EVENTS and would pin the + * tick for the whole run on NO_HZ_FULL kernels. + */ + .sample_period = 20 * 1000 * 1000, +}; + +/* One in-flight callback per CPU; ->inuse is released by the callback. */ +struct rcu_torture_nmi_cb { + struct rcu_head rh; + atomic_t inuse; +}; + +static struct perf_event **rcu_torture_nmi_events; +static int rcu_torture_nmi_hp_state; +static DEFINE_PER_CPU(struct rcu_torture_nmi_cb, rcu_torture_nmi_cb); + +static void rcu_torture_nmi_invoked(struct rcu_head *rhp) +{ + struct rcu_torture_nmi_cb *rtncp = container_of(rhp, struct rcu_torture_nmi_cb, rh); + + atomic_long_inc(&n_rcu_torture_nmi_cb); + atomic_set(&rtncp->inuse, 0); +} + +static void rcu_torture_nmi_overflow(struct perf_event *event, + struct perf_sample_data *data, + struct pt_regs *regs) +{ + struct rcu_torture_nmi_cb *rtncp = this_cpu_ptr(&rcu_torture_nmi_cb); + + if (!in_nmi()) + return; + if (cur_ops->call && !atomic_xchg(&rtncp->inuse, 1)) { + atomic_long_inc(&n_rcu_torture_nmi_call); + cur_ops->call(&rtncp->rh, rcu_torture_nmi_invoked); + } +} + +static int rcu_torture_nmi_online(unsigned int cpu) +{ + struct perf_event *event; + + event = perf_event_create_kernel_counter(&rcu_torture_nmi_attr, cpu, NULL, + rcu_torture_nmi_overflow, NULL); + if (IS_ERR(event)) + return 0; + rcu_torture_nmi_events[cpu] = event; + perf_event_enable(event); + return 0; +} + +static int rcu_torture_nmi_offline(unsigned int cpu) +{ + struct perf_event *event = rcu_torture_nmi_events[cpu]; + + if (event) { + rcu_torture_nmi_events[cpu] = NULL; + perf_event_disable(event); + perf_event_release_kernel(event); + } + return 0; +} + +/* Drive the counters from hotplug callbacks so coverage survives onoff. */ +static void rcu_torture_nmi_init(void) +{ + int ret; + + if (!nmi_calls || !cur_ops->nmi_capable || !cur_ops->call) + return; + rcu_torture_nmi_events = kcalloc(nr_cpu_ids, sizeof(*rcu_torture_nmi_events), + GFP_KERNEL); + if (!rcu_torture_nmi_events) + return; + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "rcutorture/nmi:online", + rcu_torture_nmi_online, rcu_torture_nmi_offline); + if (ret < 0) { + kfree(rcu_torture_nmi_events); + rcu_torture_nmi_events = NULL; + return; + } + rcu_torture_nmi_hp_state = ret; +} + +static void rcu_torture_nmi_cleanup(void) +{ + if (!rcu_torture_nmi_events) + return; + if (rcu_torture_nmi_hp_state > 0) { + cpuhp_remove_state(rcu_torture_nmi_hp_state); + rcu_torture_nmi_hp_state = 0; + } + kfree(rcu_torture_nmi_events); + rcu_torture_nmi_events = NULL; + if (!atomic_long_read(&n_rcu_torture_nmi_call)) + pr_alert("%s: nmi_calls set but no ->call() ever issued from NMI, so NMI ->call() went untested (no PMU, or NMIs unavailable here).\n", + __func__); +} +#else /* #ifdef CONFIG_PERF_EVENTS */ +static void rcu_torture_nmi_init(void) { } +static void rcu_torture_nmi_cleanup(void) { } +#endif /* #else #ifdef CONFIG_PERF_EVENTS */ + /* * RCU torture reader from timer handler. Dereferences rcu_torture_current, * incrementing the corresponding element of the pipeline array. The @@ -3051,6 +3178,9 @@ rcu_torture_stats_print(void) data_race(n_barrier_attempts), data_race(n_rcu_torture_barrier_error)); pr_cont("read-exits: %ld ", data_race(n_read_exits)); // Statistic. + pr_cont("nmi-calls: %ld nmi-cbs: %ld ", + atomic_long_read(&n_rcu_torture_nmi_call), + atomic_long_read(&n_rcu_torture_nmi_cb)); pr_cont("nocb-toggles: %ld:%ld ", atomic_long_read(&n_nocb_offload), atomic_long_read(&n_nocb_deoffload)); pr_cont("gpwraps: %ld\n", n_gpwraps); @@ -3199,7 +3329,7 @@ rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag) "read_exit_delay=%d read_exit_burst=%d " "reader_flavor=%x " "nocbs_nthreads=%d nocbs_toggle=%d " - "test_nmis=%d " + "test_nmis=%d nmi_calls=%d " "preempt_duration=%d preempt_interval=%d n_up_down=%d\n", torture_type, tag, nrealreaders, nwriters, nrealfakewriters, stat_interval, verbose, test_no_idle_hz, shuffle_interval, @@ -3213,7 +3343,7 @@ rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag) read_exit_delay, read_exit_burst, reader_flavor, nocbs_nthreads, nocbs_toggle, - test_nmis, + test_nmis, nmi_calls, preempt_duration, preempt_interval, n_up_down); } @@ -4287,6 +4417,7 @@ rcu_torture_cleanup(void) int i; if (torture_cleanup_begin()) { + rcu_torture_nmi_cleanup(); if (cur_ops->cb_barrier != NULL) { pr_info("%s: Invoking %pS().\n", __func__, cur_ops->cb_barrier); cur_ops->cb_barrier(); @@ -4329,6 +4460,7 @@ rcu_torture_cleanup(void) kfree(reader_tasks); reader_tasks = NULL; } + rcu_torture_nmi_cleanup(); kfree(rcu_torture_reader_mbchk); rcu_torture_reader_mbchk = NULL; @@ -4358,6 +4490,19 @@ rcu_torture_cleanup(void) pr_info("%s: Invoking %pS().\n", __func__, cur_ops->cb_barrier); cur_ops->cb_barrier(); } + + /* + * cb_barrier() above drained every deferred callback, so the count + * issued from NMI must equal the count invoked. + */ + if (atomic_long_read(&n_rcu_torture_nmi_call) != + atomic_long_read(&n_rcu_torture_nmi_cb)) { + pr_alert("%s: NMI ->call() lost a callback: issued %ld invoked %ld\n", + __func__, atomic_long_read(&n_rcu_torture_nmi_call), + atomic_long_read(&n_rcu_torture_nmi_cb)); + atomic_inc(&n_rcu_torture_error); + } + if (cur_ops->cleanup != NULL) cur_ops->cleanup(); @@ -4790,6 +4935,7 @@ rcu_torture_init(void) firsterr = -ENOMEM; goto unwind; } + rcu_torture_nmi_init(); for (i = 0; i < nrealreaders; i++) { rcu_torture_reader_mbchk[i].rtc_chkrdr = -1; firsterr = torture_create_kthread(rcu_torture_reader, (void *)i, From 0464f587c61af5bba0cc8122e8341769c8f8d73d Mon Sep 17 00:00:00 2001 From: Puranjay Mohan Date: Mon, 10 Aug 2026 05:27:55 -0700 Subject: [PATCH 1188/2286] selftests/bpf: Add a call_srcu() re-entry reproducer Re-enter call_srcu() from a BPF program to exercise its any-context safety, via call_rcu_tasks_trace(), which is call_srcu() on rcu_tasks_trace_srcu_struct. An fentry program on rcu_segcblist_enqueue() fires mid-enqueue: that function is reached from srcu_gp_start_if_needed() with the srcu_data ->lock held. The program does a task-storage delete, whose only deferred work is call_rcu_tasks_trace(), re-entering the enqueue on the same CPU. The handler matches on TID and fires once; pinning the thread removes the migration window between picking the srcu_data and taking its lock. Without the fix the nested call re-takes the same sdp lock and self-deadlocks; with it the nested __call_srcu() sees interrupts disabled and defers via irq_work, so the delete returns and the test passes. The test skips where it does not apply: Tiny RCU has no rcu_segcblist_enqueue() to attach to, and a UP+PREEMPT kernel pairs Tree RCU with Tiny SRCU, so the attach succeeds but call_srcu() never reaches the enqueue. Tiny SRCU is told apart by srcu_expedite_current(), which it stubs out, so on Tree SRCU a zero hit count fails rather than skips and the reproducer cannot quietly stop reproducing. Signed-off-by: Puranjay Mohan Acked-by: Kumar Kartikeya Dwivedi Signed-off-by: Paul E. McKenney --- .../selftests/bpf/prog_tests/rcu_reentry.c | 93 +++++++++++++++++++ .../testing/selftests/bpf/progs/rcu_reentry.c | 51 ++++++++++ 2 files changed, 144 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/rcu_reentry.c create mode 100644 tools/testing/selftests/bpf/progs/rcu_reentry.c diff --git a/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c new file mode 100644 index 00000000000000..de23a14b3d408f --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/rcu_reentry.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Exercise re-entry into call_srcu() from BPF; see progs/rcu_reentry.c. */ +#define _GNU_SOURCE +#include +#include +#include "task_local_storage_helpers.h" +#include "trace_helpers.h" +#include "rcu_reentry.skel.h" + +/* Tiny RCU has no rcu_segcblist_enqueue() to attach to. */ +static bool have_attach_target(void) +{ + unsigned long long addr; + + return kallsyms_find("rcu_segcblist_enqueue", &addr) == 0; +} + +/* Tiny SRCU stubs out srcu_expedite_current(); Tree SRCU exports it. */ +static bool have_tree_srcu(void) +{ + unsigned long long addr; + + return kallsyms_find("srcu_expedite_current", &addr) == 0; +} + +void test_rcu_reentry(void) +{ + struct rcu_reentry *skel; + int err, pidfd = -1, map_fd; + cpu_set_t set, old_set; + bool affinity_saved; + __u64 val = 1; + int cpu; + + if (!have_attach_target()) { + test__skip(); + return; + } + + skel = rcu_reentry__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open_and_load")) + return; + + err = rcu_reentry__attach(skel); + if (!ASSERT_OK(err, "skel_attach")) + goto out; + + /* Keep the re-entry on a single CPU; a cpuset may exclude CPU 0. */ + affinity_saved = !sched_getaffinity(0, sizeof(old_set), &old_set); + cpu = sched_getcpu(); + if (!ASSERT_GE(cpu, 0, "getcpu")) + goto out; + CPU_ZERO(&set); + CPU_SET(cpu, &set); + if (!ASSERT_OK(sched_setaffinity(0, sizeof(set), &set), "setaffinity")) + goto out; + + pidfd = sys_pidfd_open(getpid(), 0); + if (!ASSERT_GE(pidfd, 0, "pidfd_open")) + goto restore; + map_fd = bpf_map__fd(skel->maps.task_stg); + err = bpf_map_update_elem(map_fd, &pidfd, &val, BPF_NOEXIST); + if (!ASSERT_OK(err, "boot_create")) + goto restore; + + /* Arm the handler for this thread, then trigger call_rcu_tasks_trace(). */ + skel->bss->target_pid = syscall(__NR_gettid); + err = bpf_map_delete_elem(map_fd, &pidfd); + if (!ASSERT_OK(err, "boot_delete")) + goto restore; + + /* + * Only Tree SRCU reaches rcu_segcblist_enqueue() from call_srcu(); a + * UP+PREEMPT kernel pairs Tree RCU with Tiny SRCU, so the attach + * succeeds but nothing fires. On Tree SRCU it must fire. + */ + if (!skel->bss->hits) { + if (have_tree_srcu()) + ASSERT_GT(skel->bss->hits, 0, "prog_fired"); + else + test__skip(); + goto restore; + } + ASSERT_EQ(skel->bss->get_errs, 0, "nested_storage_get"); + ASSERT_EQ(skel->bss->del_errs, 0, "nested_storage_delete"); +restore: + if (affinity_saved) + sched_setaffinity(0, sizeof(old_set), &old_set); +out: + if (pidfd >= 0) + close(pidfd); + rcu_reentry__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/rcu_reentry.c b/tools/testing/selftests/bpf/progs/rcu_reentry.c new file mode 100644 index 00000000000000..47a36f704cf3e1 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/rcu_reentry.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Re-enter call_srcu() from a BPF program. fentry on rcu_segcblist_enqueue() + * fires inside call_srcu()'s enqueue (reached from srcu_gp_start_if_needed() + * with the srcu_data ->lock held); the handler then calls call_rcu_tasks_trace() + * -- itself call_srcu() on rcu_tasks_trace_srcu_struct -- re-entering the same + * srcu_data on the same CPU. + */ +#include "vmlinux.h" +#include +#include + +char _license[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_TASK_STORAGE); + __uint(map_flags, BPF_F_NO_PREALLOC); + __type(key, int); + __type(value, __u64); +} task_stg SEC(".maps"); + +int target_pid; +int hits; +int get_errs; +int del_errs; +int done; + +SEC("fentry/rcu_segcblist_enqueue") +int BPF_PROG(reenter) +{ + struct task_struct *cur; + + if (done || !target_pid) + return 0; + + cur = bpf_get_current_task_btf(); + if (cur->pid != target_pid) + return 0; + + /* Issue the nested call exactly once, so the test is deterministic. */ + done = 1; + __sync_fetch_and_add(&hits, 1); + + /* Re-enter via a task-storage delete, which calls call_rcu_tasks_trace(). */ + if (!bpf_task_storage_get(&task_stg, cur, 0, BPF_LOCAL_STORAGE_GET_F_CREATE)) + __sync_fetch_and_add(&get_errs, 1); + else if (bpf_task_storage_delete(&task_stg, cur)) + __sync_fetch_and_add(&del_errs, 1); + + return 0; +} From 835a10eeb028a932a0061ef141537f5d17fc9aac Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Thu, 9 Jul 2026 09:36:34 -0700 Subject: [PATCH 1189/2286] srcutiny: Make a Tiny SRCU grace period imply an RCU grace period In non-preemptible kernels, a Tiny SRCU grace period implies an RCU grace period because any context switch suffices. But in preemptible kernels, it is possible for a Tiny SRCU grace period to elapse without a corresponding RCU grace period. Which was OK until RCU Tasks Trace was re-implemented in terms of SRCU-fast, which in Tiny SRCU is implemented as SRCU, which is already fast. And RCU Tasks Trace grace periods are required to imply RCU grace periods. This commit therefore adds a synchronize_rcu(), but only in preemptible kernels. Because preemptible Tiny SRCU is not in mainline, this added call to synchronize_rcu() will not slow anything down: The comparison would instead be with TREE SRCU. But if this added call ever becomes a problem, the Tiny SRCU srcu_struct structure could track whether or not this is for SRCU-fast, and to add the synchronize_rcu() only in the SRCU-fast case. However, at the moment, this is seen as unnecessary complexity. Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutiny.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c index 5de9a690583835..32b37d63d58aa4 100644 --- a/kernel/rcu/srcutiny.c +++ b/kernel/rcu/srcutiny.c @@ -159,6 +159,8 @@ void srcu_drive_gp(struct work_struct *wp) WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 1); WRITE_ONCE(ssp->srcu_gp_waiting, true); /* srcu_read_unlock() wakes! */ preempt_enable(); + if (IS_ENABLED(CONFIG_PREEMPTION)) + synchronize_rcu(); // Needed for RCU Tasks Trace to imply RCU grace period do { // Deadlock issues prevent __srcu_read_unlock() from // doing an unconditional wakeup, so polling is required. From 21345a8eff2bf1e12a5defdd12fcf364b29791a9 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 18 Aug 2026 10:51:42 -0700 Subject: [PATCH 1190/2286] srcutree: Suppress srcu_advance_state() mutex_lock in atomic The new SRCU_READ_FLAVOR_ATOMIC prohibits sleeping in both readers and grace-period waits in order to permit waiting on an SRCU grace period within an OOM notifier. This means that srcu_advance_state() cannot acquire ->srcu_gp_mutex in this case, because mutex_lock() can sleep. This commit therefore adds an is_atomic parameter to srcu_advance_state(). When this parameter is false, current behavior is maintained, in other words, ->srcu_gp_mutex is acquired and released as before. But when this is_atomic parameter is false, the caller is responsible for excluding other callers. Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index b1b3c81a72f45f..c305c54a3203ba 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -1941,13 +1941,16 @@ EXPORT_SYMBOL_GPL(srcu_batches_completed); /* * Core SRCU state machine. Push state bits of ->srcu_gp_seq * to SRCU_STATE_SCAN2, and invoke srcu_gp_end() when scan has - * completed in that state. + * completed in that state. Set is_atomic to indicate that + * the caller is excluding other calls so that ->srcu_gp_mutex + * is not needed, and to indicate that blocking is forbidden. */ -static void srcu_advance_state(struct srcu_struct *ssp) +static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) { int idx; - mutex_lock(&ssp->srcu_sup->srcu_gp_mutex); + if (!is_atomic) + mutex_lock(&ssp->srcu_sup->srcu_gp_mutex); /* * Because readers might be delayed for an extended period after @@ -1965,7 +1968,8 @@ static void srcu_advance_state(struct srcu_struct *ssp) if (ULONG_CMP_GE(ssp->srcu_sup->srcu_gp_seq, ssp->srcu_sup->srcu_gp_seq_needed)) { WARN_ON_ONCE(rcu_seq_state(ssp->srcu_sup->srcu_gp_seq)); raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); - mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); + if (!is_atomic) + mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); return; } idx = rcu_seq_state(READ_ONCE(ssp->srcu_sup->srcu_gp_seq)); @@ -1973,7 +1977,8 @@ static void srcu_advance_state(struct srcu_struct *ssp) srcu_gp_start(ssp); raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); if (idx != SRCU_STATE_IDLE) { - mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); + if (!is_atomic) + mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); return; /* Someone else started the grace period. */ } } @@ -1981,7 +1986,8 @@ static void srcu_advance_state(struct srcu_struct *ssp) if (rcu_seq_state(READ_ONCE(ssp->srcu_sup->srcu_gp_seq)) == SRCU_STATE_SCAN1) { idx = !(ssp->srcu_ctrp - &ssp->sda->srcu_ctrs[0]); if (!try_check_zero(ssp, idx, 1)) { - mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); + if (!is_atomic) + mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); return; /* readers present, retry later. */ } srcu_flip(ssp); @@ -1999,7 +2005,8 @@ static void srcu_advance_state(struct srcu_struct *ssp) */ idx = !(ssp->srcu_ctrp - &ssp->sda->srcu_ctrs[0]); if (!try_check_zero(ssp, idx, 2)) { - mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); + if (!is_atomic) + mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); return; /* readers present, retry later. */ } ssp->srcu_sup->srcu_n_exp_nodelay = 0; @@ -2107,7 +2114,7 @@ static void process_srcu(struct work_struct *work) sup = container_of(work, struct srcu_usage, work.work); ssp = sup->srcu_ssp; - srcu_advance_state(ssp); + srcu_advance_state(ssp, false); raw_spin_lock_irq_rcu_node(ssp->srcu_sup); curdelay = srcu_get_delay(ssp); raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); From ee6bd18ef1673f526e795b368603f692576bb148 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 18 Aug 2026 11:41:01 -0700 Subject: [PATCH 1191/2286] srcutree: Suppress to-big transition for atomic SRCU Initially (and perhaps forever), call_srcu() will not be available for atomic srcu_struct structures. There is therefore no reason to transition such a structure to big, because the main purpose of such a transition is to reduce lock contention for concurrent SRCU callback queueing. This commit therefore adds an is_atomic parameter to both the check_init_srcu_struct() and init_srcu_struct_fields() functions, which suppresses the initialization-time transition to big that is enabled by default on large systems. It will still be possible to force a transition using rcutorture as a destructive test. This might (or might not) be adjusted later. [ paulmck: Apply feedback from Kunwu Chan. ] Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index c305c54a3203ba..0292d3f9a83aa5 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -238,8 +238,10 @@ static bool init_srcu_struct_nodes(struct srcu_struct *ssp, gfp_t gfp_flags) * Initialize non-compile-time initialized fields, including the * associated srcu_node and srcu_data structures. The is_static parameter * tells us that ->sda has already been wired up to srcu_data. + * The is_atomic parameter tells us that there is no reason to + * ever transition to big. */ -static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static) +static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static, bool is_atomic) { if (!is_static) ssp->srcu_sup = kzalloc_obj(*ssp->srcu_sup); @@ -267,7 +269,8 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static) init_srcu_struct_data(ssp); ssp->srcu_sup->srcu_gp_seq_needed_exp = SRCU_GP_SEQ_INITIAL_VAL; ssp->srcu_sup->srcu_last_gp_end = ktime_get_mono_fast_ns(); - if (READ_ONCE(ssp->srcu_sup->srcu_size_state) == SRCU_SIZE_SMALL && SRCU_SIZING_IS_INIT()) { + if (!is_atomic && + READ_ONCE(ssp->srcu_sup->srcu_size_state) == SRCU_SIZE_SMALL && SRCU_SIZING_IS_INIT()) { if (!preemptible()) WRITE_ONCE(ssp->srcu_sup->srcu_size_state, SRCU_SIZE_ALLOC); else if (init_srcu_struct_nodes(ssp, GFP_KERNEL)) @@ -301,7 +304,7 @@ __init_srcu_struct_common(struct srcu_struct *ssp, const char *name, struct lock /* Don't re-initialize a lock while it is held. */ debug_check_no_locks_freed((void *)ssp, sizeof(*ssp)); lockdep_init_map(&ssp->dep_map, name, key, 0); - return init_srcu_struct_fields(ssp, false); + return init_srcu_struct_fields(ssp, false, false); } int init_srcu_struct_lockdep(struct srcu_struct *ssp, const char *name, @@ -343,7 +346,7 @@ EXPORT_SYMBOL_GPL(__init_srcu_struct_fast_updown); int init_srcu_struct_generic(struct srcu_struct *ssp) { ssp->srcu_reader_flavor = 0; - return init_srcu_struct_fields(ssp, false); + return init_srcu_struct_fields(ssp, false, false); } EXPORT_SYMBOL_GPL(init_srcu_struct_generic); @@ -360,7 +363,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct_generic); int init_srcu_struct_fast(struct srcu_struct *ssp) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_FAST; - return init_srcu_struct_fields(ssp, false); + return init_srcu_struct_fields(ssp, false, false); } EXPORT_SYMBOL_GPL(init_srcu_struct_fast); @@ -378,7 +381,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct_fast); int init_srcu_struct_fast_updown(struct srcu_struct *ssp) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_FAST_UPDOWN; - return init_srcu_struct_fields(ssp, false); + return init_srcu_struct_fields(ssp, false, false); } EXPORT_SYMBOL_GPL(init_srcu_struct_fast_updown); @@ -470,9 +473,11 @@ static void raw_spin_lock_irqsave_ssp_contention(struct srcu_struct *ssp, unsign * done with compile-time initialization, so this check is added * to each update-side SRCU primitive. Use ssp->lock, which -is- * compile-time initialized, to resolve races involving multiple - * CPUs trying to garner first-use privileges. + * CPUs trying to garner first-use privileges. The is_atomic + * parameter tells us that there will never be a reason to + * transition to big. */ -static void check_init_srcu_struct(struct srcu_struct *ssp) +static void check_init_srcu_struct(struct srcu_struct *ssp, bool is_atomic) { unsigned long flags; @@ -484,7 +489,7 @@ static void check_init_srcu_struct(struct srcu_struct *ssp) raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); return; } - init_srcu_struct_fields(ssp, true); + init_srcu_struct_fields(ssp, true, is_atomic); raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); } @@ -1279,7 +1284,7 @@ static bool srcu_should_expedite(struct srcu_struct *ssp) unsigned long t; unsigned long tlast; - check_init_srcu_struct(ssp); + check_init_srcu_struct(ssp, false); /* If _lite() readers, don't do unsolicited expediting. */ if (this_cpu_read(ssp->sda->srcu_reader_flavor) & SRCU_READ_FLAVOR_SLOWGP) return false; @@ -1338,7 +1343,7 @@ static unsigned long srcu_gp_start_if_needed(struct srcu_struct *ssp, struct srcu_node *sdp_mynode; int ss_state; - check_init_srcu_struct(ssp); + check_init_srcu_struct(ssp, false); /* * While starting a new grace period, make sure we are in an * SRCU read-side critical section so that the grace-period @@ -1617,7 +1622,7 @@ static void __synchronize_srcu(struct srcu_struct *ssp, bool do_norm) if (rcu_scheduler_active == RCU_SCHEDULER_INACTIVE) return; might_sleep(); - check_init_srcu_struct(ssp); + check_init_srcu_struct(ssp, false); init_completion(&rcu.completion); init_rcu_head_on_stack(&rcu.head); __call_srcu(ssp, &rcu.head, wakeme_after_rcu, do_norm); @@ -1827,7 +1832,7 @@ void srcu_barrier(struct srcu_struct *ssp) int idx; unsigned long s; - check_init_srcu_struct(ssp); + check_init_srcu_struct(ssp, false); /* * Register any deferred callbacks before snapshotting the sequence. The From 27441d76b3d6604de9162c4c24d034bcfe3249de Mon Sep 17 00:00:00 2001 From: Brian Gerst Date: Sat, 5 Sep 2026 18:55:19 -0400 Subject: [PATCH 1192/2286] x86/asm: Remove CR4 read fault handler Remove the fault handler from native_read_cr4() since 486 support has been removed. Signed-off-by: Brian Gerst Signed-off-by: Borislav Petkov (AMD) Link: https://patch.msgid.link/20260905225519.1002976-1-brgerst@gmail.com --- arch/x86/include/asm/special_insns.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h index 46aa2c9c1bda39..6c80f69cf62b74 100644 --- a/arch/x86/include/asm/special_insns.h +++ b/arch/x86/include/asm/special_insns.h @@ -46,20 +46,7 @@ static __always_inline void native_write_cr3(unsigned long val) static inline unsigned long native_read_cr4(void) { unsigned long val; -#ifdef CONFIG_X86_32 - /* - * This could fault if CR4 does not exist. Non-existent CR4 - * is functionally equivalent to CR4 == 0. Keep it simple and pretend - * that CR4 == 0 on CPUs that don't have CR4. - */ - asm volatile("1: mov %%cr4, %0\n" - "2:\n" - _ASM_EXTABLE(1b, 2b) - : "=r" (val) : "0" (0)); -#else - /* CR4 always exists on x86_64. */ asm volatile("mov %%cr4,%0" : "=r" (val)); -#endif return val; } From 317e7abcaf60b79765dcf35c014edac8b9d37a6e Mon Sep 17 00:00:00 2001 From: Bartosz Szpila Date: Thu, 1 Oct 2026 08:57:13 +0000 Subject: [PATCH 1193/2286] platform/chrome: cros_ec_typec: Validate SVID and mode counts in discovery data cros_typec_register_altmodes() iterates over sop_disc->svid_count and sop_disc->svids[i].mode_count from the EC_CMD_TYPEC_DISCOVERY response without validating their bounds. port->disc_data is allocated as a buffer of EC_PROTO2_MAX_RESPONSE_SIZE bytes, and each SVID entry contains a fixed mode_vdo[6] array. If the EC returns an out-of-bounds svid_count or mode_count, the loops read past the end of the mode_vdo array or the disc_data buffer. Validate that the discovery response with svid_count entries fits within EC_PROTO2_MAX_RESPONSE_SIZE using struct_size(), and ensure each SVID's mode_count does not exceed ARRAY_SIZE(sop_disc->svids[i].mode_vdo) before registering altmodes. Fixes: de0f49487db3 ("platform/chrome: cros_ec_typec: Register partner altmodes") Signed-off-by: Bartosz Szpila Reviewed-by: Andrei Kuchynski Link: https://lore.kernel.org/r/20261001085714.1086242-1-bszpila@google.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_typec.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 50a68819ceb7bb..4427897eb3f59f 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -7,8 +7,10 @@ */ #include +#include #include #include +#include #include #include #include @@ -894,7 +896,15 @@ static int cros_typec_register_altmodes(struct cros_typec_data *typec, int port_ int ret = 0; int i, j; + if (struct_size(sop_disc, svids, sop_disc->svid_count) > EC_PROTO2_MAX_RESPONSE_SIZE) + return -EINVAL; + for (i = 0; i < sop_disc->svid_count; i++) { + if (sop_disc->svids[i].mode_count > ARRAY_SIZE(sop_disc->svids[i].mode_vdo)) { + ret = -EINVAL; + goto err_cleanup; + } + for (j = 0; j < sop_disc->svids[i].mode_count; j++) { memset(&desc, 0, sizeof(desc)); desc.svid = sop_disc->svids[i].svid; From 948440b3f429f84680db58b4bf529b1ed2a20e15 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 10 Jun 2026 12:42:22 +0100 Subject: [PATCH 1194/2286] rust: mem: add `Align` type Rust's `repr(align())` only works with integers. Add an `Align` type so it can also be specified via const generics. Signed-off-by: Gary Guo Reviewed-by: Andreas Hindborg Link: https://patch.msgid.link/20260610-const-align-v2-1-ccd12f0aedb7@garyguo.net [ Adjusted the title of the new module to match the one used in patch "rust: mem: add `transmute` with deferred size check" (landing via driver-core), which is also closer to the upstream standard library title. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/lib.rs | 1 + rust/kernel/mem.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 rust/kernel/mem.rs diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 4d5c96ddc49c79..7225abc6408447 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -98,6 +98,7 @@ pub mod jump_label; pub mod kunit; pub mod list; pub mod maple_tree; +pub mod mem; pub mod miscdevice; pub mod mm; pub mod module; diff --git a/rust/kernel/mem.rs b/rust/kernel/mem.rs new file mode 100644 index 00000000000000..33676760bee2a1 --- /dev/null +++ b/rust/kernel/mem.rs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Basic utilities for dealing with memory, values, and types. + +// TODO: `Alignment` should be moved here as well, Rust moved it upstream in 1.96. +pub use crate::ptr::Alignment; + +/// Trait indicating that [`Align`] is a valid alignment. +pub trait ValidAlign { + #[doc(hidden)] + type Repr: Copy; +} + +/// Zero-sized type that provides the alignment specified via the generic parameter. +/// +/// # Examples +/// +/// ``` +/// # use kernel::mem::Align; +/// const ALIGN: usize = 128; +/// struct MyStruct { +/// _align: Align, +/// } +/// assert_eq!(align_of::(), ALIGN); +/// ``` +#[repr(transparent)] +#[derive(Default, Clone, Copy)] +pub struct Align([::Repr; 0]) +where + Self: ValidAlign; + +impl Align +where + Self: ValidAlign, +{ + /// Create a new [`Align`]. + #[inline] + pub const fn new() -> Self { + Align([]) + } +} + +macro_rules! impl_align { + () => {}; + ($a:literal $($rest:literal)*) => { + const _: () = { + #[repr(align($a))] + #[derive(Clone, Copy)] + pub struct Repr; + + impl ValidAlign for Align<$a> { + type Repr = Repr; + } + }; + + impl_align!($($rest)*); + } +} + +impl_align!(1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536); + +impl core::fmt::Debug for Align +where + Self: ValidAlign, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Align<{N}>") + } +} From 9fea79f5948bd5e0bea1cf773fd7742a6855017b Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Thu, 1 Oct 2026 10:22:21 +0200 Subject: [PATCH 1195/2286] dt-bindings: timer: Add the Renesas RZ/N1 timer The Renesas RZ/N1 timer block controller is the controller in charge of timers available in the Renesas RZ/N1 SoCs family. This controller handles 8 timers: - 6 16-bit timers - 2 32-bit timers Signed-off-by: Herve Codina (Schneider Electric) Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20261001082225.125956-2-herve.codina@bootlin.com --- .../bindings/timer/renesas,rzn1-timer.yaml | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml diff --git a/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml new file mode 100644 index 00000000000000..7218c0c2671d57 --- /dev/null +++ b/Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/timer/renesas,rzn1-timer.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/N1 timers + +maintainers: + - Herve Codina + +description: | + The Renesas RZ/N1 SoCs timers block controller is composed of 8 independent + timers. + - 6 are 16-bit timers + - 2 are 32-bit timers + + Each timer has its own interrupt line and can work in either one-shot or + periodic mode. + +properties: + compatible: + items: + - const: renesas,r9a06g032-timer # RZ/N1D + - const: renesas,rzn1-timer + + reg: + maxItems: 1 + + clocks: + items: + - description: APB internal bus clock + + clock-names: + items: + - const: pclk + + power-domains: + maxItems: 1 + + interrupts: + maxItems: 8 + +required: + - compatible + - reg + - clocks + - clock-names + - power-domains + - interrupts + +additionalProperties: false + +examples: + - | + #include + #include + + timer@51001000 { + compatible = "renesas,r9a06g032-timer", "renesas,rzn1-timer"; + reg = <0x51001000 0x200>; + clocks = <&sysctrl R9A06G032_HCLK_TIMER0>; + clock-names = "pclk"; + power-domains = <&sysctrl>; + interrupts = , + , + , + , + , + , + , + ; + }; +... From 435167b4f30191aa7368de68870117bc7218ee8f Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Thu, 1 Oct 2026 10:22:22 +0200 Subject: [PATCH 1196/2286] clocksource/drivers: Add support for the Renesas RZ/N1 timers The Renesas RZ/N1 timer block controller is the controller in charge of timers available in the Renesas RZ/N1 SoCs family. This controller handles 8 timers: - 6 16-bit timers - 2 32-bit timers Each timer has its own interrupt, its own prescaler that can be used to divide the clock by 25 and all of them can work in either one-shot or periodic mode. Signed-off-by: Herve Codina (Schneider Electric) Signed-off-by: Daniel Lezcano Tested-by: Wolfram Sang Link: https://patch.msgid.link/20261001082225.125956-3-herve.codina@bootlin.com --- drivers/clocksource/Kconfig | 11 + drivers/clocksource/Makefile | 1 + drivers/clocksource/timer-rzn1.c | 483 +++++++++++++++++++++++++++++++ 3 files changed, 495 insertions(+) create mode 100644 drivers/clocksource/timer-rzn1.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index d9c76dd443f8ac..da83483296d5a0 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -152,6 +152,17 @@ config REALTEK_OTTO_TIMER RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series such as RTL9301, RTL9302 or RTL9303. +config RZN1_TIMER + bool "Renesas RZ/N1 Timer" + depends on HAS_IOMEM && COMMON_CLK + depends on ARCH_RZN1 || COMPILE_TEST + help + Enables support for RZ/N1 SoC timers. + A timers block in RZ/N1 SoCs is composed of 8 timers + - 6 16-bit timers + - 2 32-bit timers + Two timers blocks are available in RZ/N1 SoCs. + config SUN4I_TIMER bool "Sun4i timer driver" if COMPILE_TEST depends on HAS_IOMEM diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index b46376af6b4901..464ec6690ab663 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -96,3 +96,4 @@ obj-$(CONFIG_EP93XX_TIMER) += timer-ep93xx.o obj-$(CONFIG_RALINK_TIMER) += timer-ralink.o obj-$(CONFIG_NXP_STM_TIMER) += timer-nxp-stm.o obj-$(CONFIG_RTK_SYSTIMER) += timer-realtek.o +obj-$(CONFIG_RZN1_TIMER) += timer-rzn1.o diff --git a/drivers/clocksource/timer-rzn1.c b/drivers/clocksource/timer-rzn1.c new file mode 100644 index 00000000000000..25ebebff3c639c --- /dev/null +++ b/drivers/clocksource/timer-rzn1.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * RZ/N1 timers driver + * + * Copyright (C) 2026 Schneider-Electric + * + * Author: Herve Codina + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Per-timer register offsets */ +#define RZN1_TIMER_LOAD_COUNT_REG 0x00 +#define RZN1_TIMER_CURRENT_COUNT_REG 0x04 +#define RZN1_TIMER_CONTROL_REG 0x08 +#define RZN1_TIMER_CONTROL_INT_UNMASK BIT(3) +#define RZN1_TIMER_CONTROL_ENABLE BIT(2) +#define RZN1_TIMER_CONTROL_AUTORELOAD BIT(1) +#define RZN1_TIMER_CONTROL_PRESCALER_DIV25 BIT(0) +#define RZN1_TIMER_CLEAR_INT_REG 0x0c +#define RZN1_TIMER_STATUS_INT0_REG 0x10 +#define RZN1_TIMER_STATUS_INT1_REG 0x14 + +/* + * 8 timers are available. Among those 8 timers, the first 6 timers are 16-bit + * timers and the last two ones are 32-bit timers. + */ +#define RZN1_TIMER_BASE_INDEX_16BIT_TIMERS 0 +#define RZN1_TIMER_NB_16BIT_TIMERS 6 +#define RZN1_TIMER_LAST_INDEX_16BIT_TIMERS (RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + \ + RZN1_TIMER_NB_16BIT_TIMERS - 1) + +#define RZN1_TIMER_BASE_INDEX_32BIT_TIMERS 6 +#define RZN1_TIMER_NB_32BIT_TIMERS 2 +#define RZN1_TIMER_LAST_INDEX_32BIT_TIMERS (RZN1_TIMER_BASE_INDEX_32BIT_TIMERS + \ + RZN1_TIMER_NB_32BIT_TIMERS - 1) + +#define RZN1_TIMER_IS_16BIT_TIMER(_i) ((_i) < RZN1_TIMER_BASE_INDEX_32BIT_TIMERS) + +/* Total numbers of timers */ +#define RZN1_TIMER_NB_TIMERS (RZN1_TIMER_NB_16BIT_TIMERS + \ + RZN1_TIMER_NB_32BIT_TIMERS) + +/* Offset of registers related to the timer number _i in the timers regs area */ +#define RZN1_TIMER_REGS_OFFSET(_i) (0x20 * (_i)) + +struct rzn1_timer { + void __iomem *base; + unsigned int width; + unsigned long rate; + unsigned long hz_period; + struct clock_event_device ced; +}; + +static int rzn1_timer_config(struct rzn1_timer *timer, u32 period, bool is_periodic) +{ + u32 ctrl; + + ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG); + + /* Disable the timer */ + writel(ctrl & ~RZN1_TIMER_CONTROL_ENABLE, + timer->base + RZN1_TIMER_CONTROL_REG); + + /* Set the new period */ + writel(period, timer->base + RZN1_TIMER_LOAD_COUNT_REG); + + /* Prepare the timer mode */ + if (is_periodic) + ctrl |= RZN1_TIMER_CONTROL_AUTORELOAD; + else + ctrl &= ~RZN1_TIMER_CONTROL_AUTORELOAD; + + /* + * Enable the timer. + * + * This automatically resets the CURRENT_COUNT register. + */ + ctrl |= RZN1_TIMER_CONTROL_ENABLE; + writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG); + + return 0; +} + +static void rzn1_timer_disable(struct rzn1_timer *timer) +{ + u32 ctrl; + + ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG); + ctrl &= ~RZN1_TIMER_CONTROL_ENABLE; + writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG); +} + +static void rzn1_timer_int_enable(struct rzn1_timer *timer) +{ + u32 ctrl; + + ctrl = readl(timer->base + RZN1_TIMER_CONTROL_REG); + ctrl |= RZN1_TIMER_CONTROL_INT_UNMASK; + writel(ctrl, timer->base + RZN1_TIMER_CONTROL_REG); +} + +static irqreturn_t rzn1_timer_interrupt(int irq, void *dev_id) +{ + struct rzn1_timer *timer = dev_id; + + /* Ack the interrupt */ + readl(timer->base + RZN1_TIMER_CLEAR_INT_REG); + + if (timer->ced.event_handler) + timer->ced.event_handler(&timer->ced); + + return IRQ_HANDLED; +} + +static int rzn1_clkevt_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced); + + return rzn1_timer_config(timer, cycles, false); +} + +static int rzn1_clkevt_shutdown(struct clock_event_device *evt) +{ + struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced); + + rzn1_timer_disable(timer); + return 0; +} + +static int rzn1_clkevt_set_periodic(struct clock_event_device *evt) +{ + struct rzn1_timer *timer = container_of(evt, struct rzn1_timer, ced); + + rzn1_timer_config(timer, timer->hz_period, true); + return 0; +} + +/* + * This global lock is used to prevent race conditions during global timers + * test and set operation in case the driver is using the async probe option. + */ +static DEFINE_MUTEX(rzn1_global_timers_lock); + +static int rzn1_timer_instance; + +/* scheduler and clocksource */ +static struct rzn1_timer *rzn1_sched_clock; + +static u64 notrace rzn1_sched_read(void) +{ + return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG); +} + +static u64 rzn1_clksrc_read(struct clocksource *cs) +{ + return readl_relaxed(rzn1_sched_clock->base + RZN1_TIMER_CURRENT_COUNT_REG); +} + +static struct clocksource rzn1_clocksource = { + .name = "rzn1_timer_clocksource", + .rating = 200, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .read = rzn1_clksrc_read, +}; + +/* Timers table where 16-bit timers will be assigned per-cpu */ +static struct rzn1_timer *rzn1_tab_timers; + +static int rzn1_local_timer_starting_cpu(unsigned int cpu) +{ + struct rzn1_timer *timer; + + /* We assign the first and second 16-bit timer as per-cpu timers. */ + if (cpu >= 2) + return 0; + + timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS]; + + timer->ced.cpumask = cpumask_of(cpu); + irq_force_affinity(timer->ced.irq, cpumask_of(cpu)); + + clockevents_config_and_register(&timer->ced, timer->rate, 1, + GENMASK(timer->width - 1, 0)); + /* + * Time to enable the interrupt even if no interrupt can be fired yet. + * Indeed, the timer is still globally disabled. + * It will be enabled when rzn1_timer_config() is called + */ + enable_irq(timer->ced.irq); + return 0; +} + +static int rzn1_local_timer_dying_cpu(unsigned int cpu) +{ + struct rzn1_timer *timer; + + if (cpu >= 2) + return 0; + + timer = &rzn1_tab_timers[cpu + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS]; + + rzn1_timer_disable(timer); + disable_irq_nosync(timer->ced.irq); + return 0; +} + +static void rzn1_timer_init(struct rzn1_timer *timer, unsigned int index, + void __iomem *timers_base, unsigned long clock_rate) +{ + timer->base = timers_base + RZN1_TIMER_REGS_OFFSET(index); + + /* Disable the timer */ + writel(0, timer->base + RZN1_TIMER_CONTROL_REG); + + /* Clear potential interrupts */ + readl(timer->base + RZN1_TIMER_CLEAR_INT_REG); + + if (RZN1_TIMER_IS_16BIT_TIMER(index)) { + timer->width = 16; + /* + * Enable pre-scaler for 16-bit timers. + * + * Timers are fed by a 25MHz clock. With this pre-scaler (1/25) + * enabled, the 16-bit timers resolution is 1 microsecond. + */ + writel(RZN1_TIMER_CONTROL_PRESCALER_DIV25, + timer->base + RZN1_TIMER_CONTROL_REG); + timer->rate = clock_rate / 25; + } else { + timer->width = 32; + timer->rate = clock_rate; + } + timer->hz_period = DIV_ROUND_UP(timer->rate, HZ); +} + +static void rzn1_timer_clkevt_init_ced(struct rzn1_timer *timer, const char *name, + int irq) +{ + timer->ced.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + timer->ced.set_next_event = rzn1_clkevt_set_next_event; + timer->ced.set_state_shutdown = rzn1_clkevt_shutdown; + timer->ced.set_state_periodic = rzn1_clkevt_set_periodic; + timer->ced.cpumask = cpu_possible_mask; + + /* + * 32-bit timers are fed with a 25 MHz clock directly derived from the + * main PLL (1 GHz). The arch timer, ARMv7 CP15 in RZ/N1 SoCs, is fed + * with to a 6.25 MHz clock. + * + * The clock of the RZ/N1 timers block is a stable one and the RZ/N1 + * timers have a higher resolution than the ARM CP15 one. + * + * Use a high rating for the 32-bit RZ/N1 timers. + */ + timer->ced.rating = timer->width == 16 ? 200 : 460; + timer->ced.owner = THIS_MODULE; + timer->ced.name = name; + timer->ced.irq = irq; +} + +static int rzn1_timer_setup_timers(struct platform_device *pdev, + struct rzn1_timer *tab_timers, + unsigned int first_timer_index, + unsigned int last_timer_index, + void __iomem *base, unsigned long clock_rate) +{ + struct device *dev = &pdev->dev; + struct rzn1_timer *timer; + unsigned int i; + char *name; + int irq; + int ret; + + for (i = first_timer_index; i <= last_timer_index; i++) { + timer = &tab_timers[i]; + + rzn1_timer_init(timer, i, base, clock_rate); + + irq = platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-%u", dev_name(dev), i); + if (!name) + return -ENOMEM; + + rzn1_timer_clkevt_init_ced(timer, name, irq); + + ret = devm_request_irq(dev, timer->ced.irq, rzn1_timer_interrupt, + IRQF_TIMER | IRQF_NO_AUTOEN, + timer->ced.name, timer); + if (ret < 0) + return dev_err_probe(dev, ret, "timer%u: Failed to request IRQ\n", i); + + rzn1_timer_int_enable(timer); + } + return 0; +} + +static void rzn1_timer_register_clkevt(struct rzn1_timer *tab_timers, + unsigned int first_timer_index, + unsigned int last_timer_index) +{ + struct rzn1_timer *timer; + unsigned int i; + + for (i = first_timer_index; i <= last_timer_index; i++) { + timer = &tab_timers[i]; + clockevents_config_and_register(&timer->ced, timer->rate, + 1, GENMASK(timer->width - 1, 0)); + /* + * Time to enable the interrupt even if no interrupt can be + * fired yet. Indeed, the timer is still globally disabled. + * It will be enabled when rzn1_timer_config() is called + */ + enable_irq(timer->ced.irq); + } +} + +static int rzn1_timer_probe_first(struct platform_device *pdev, + struct rzn1_timer *tab_timers, + void __iomem *base, unsigned long clock_rate) +{ + struct device *dev = &pdev->dev; + struct rzn1_timer *timer; + int ret; + + /* + * Each timer instance has 6 x 16-bit timers and 2 x 32-bit timers. + * + * Probe the first instance. In that case, timers are assigned as + * follow: + * - First and second 16-bit timers: clock events per CPU + * - Last (second one) 32-bit timer: clocksource and sched_clock + * - Other timers: clock events for all possible CPUs + * + * First step, perform all operation that could fail without calling + * clockevents_config_and_register(), sched_clock_register() nor + * cpuhp_setup_state(). Those operation don't have unregister nor + * teardown counterparts and so, once called, we cannot remove the + * related resource. + */ + + /* First step for 16-bit timers and the first 32-bit timers. */ + ret = rzn1_timer_setup_timers(pdev, tab_timers, + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS, + RZN1_TIMER_BASE_INDEX_32BIT_TIMERS, + base, clock_rate); + if (ret) + return ret; + + /* + * Second step, almost all operations that can fail have been called. + * Timers are ready to work. Start with the last operation that can fail, + * installing and invoking hotplug callbacks + */ + rzn1_tab_timers = tab_timers; + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "clockevents/rzn1/global_timer:starting", + rzn1_local_timer_starting_cpu, + rzn1_local_timer_dying_cpu); + if (ret < 0) + return dev_err_probe(dev, ret, "Setup CPU hotplug state failed\n"); + + /* + * At this point, no more operations can fail. Perform remaining + * operations. Starting by handling the last (second one) 32-bit timer + */ + timer = &tab_timers[RZN1_TIMER_LAST_INDEX_32BIT_TIMERS]; + + rzn1_timer_init(timer, RZN1_TIMER_LAST_INDEX_32BIT_TIMERS, base, clock_rate); + rzn1_timer_config(timer, GENMASK(timer->width - 1, 0), true); + + rzn1_sched_clock = timer; + + sched_clock_register(rzn1_sched_read, rzn1_sched_clock->width, rzn1_sched_clock->rate); + + rzn1_clocksource.mask = CLOCKSOURCE_MASK(rzn1_sched_clock->width); + clocksource_register_hz(&rzn1_clocksource, rzn1_sched_clock->rate); + + /* + * Register clockevents for 16-bit timers (except the first and second + * ones) and for the first 32-bit timer. The first and second 16-bit + * timers clockevents are registered by CPU hotplug startup function set + * previously by the cpuhp_setup_state() call. + */ + rzn1_timer_register_clkevt(tab_timers, + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS + 2, + RZN1_TIMER_BASE_INDEX_32BIT_TIMERS); + + return 0; +} + +static int rzn1_timer_probe_other(struct platform_device *pdev, + struct rzn1_timer *tab_timers, + void __iomem *base, unsigned long clock_rate) +{ + int ret; + + /* + * Probe other instance(s), i.e. not the first one. In that case, + * all timers are used as clock events and available for all possible + * CPUs + * + * First step, perform all operation that could fail without calling + * clockevents_config_and_register(). Unregister counterpart does not + * exist and so, once called, we cannot remove the related resource. + */ + ret = rzn1_timer_setup_timers(pdev, tab_timers, + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS, + RZN1_TIMER_LAST_INDEX_32BIT_TIMERS, + base, clock_rate); + if (ret) + return ret; + + /* + * Second step, all operation that can fail have been called. We can + * register our timers. + */ + rzn1_timer_register_clkevt(tab_timers, + RZN1_TIMER_BASE_INDEX_16BIT_TIMERS, + RZN1_TIMER_LAST_INDEX_32BIT_TIMERS); + return 0; +} + +static int rzn1_timer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rzn1_timer *tab_timers; + unsigned long clock_rate; + void __iomem *base; + struct clk *clk; + int ret; + + tab_timers = devm_kcalloc(dev, RZN1_TIMER_NB_TIMERS, sizeof(*tab_timers), + GFP_KERNEL); + if (!tab_timers) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + clk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n"); + + clock_rate = clk_get_rate(clk); + + scoped_guard(mutex, &rzn1_global_timers_lock) { + ret = !rzn1_timer_instance ? + rzn1_timer_probe_first(pdev, tab_timers, base, clock_rate) : + rzn1_timer_probe_other(pdev, tab_timers, base, clock_rate); + if (!ret) + rzn1_timer_instance++; + } + + return ret; +} + +static const struct of_device_id rzn1_timer_of_match[] = { + { .compatible = "renesas,rzn1-timer", }, + { /* sentinel */ } +}; + +static struct platform_driver rzn1_timer_driver = { + .driver = { + .name = "rzn1_timer", + .of_match_table = rzn1_timer_of_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver_probe(rzn1_timer_driver, rzn1_timer_probe); From 57fb7ac3584c27939a55eb3870be9d46f0b7e7e9 Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Thu, 1 Oct 2026 10:22:24 +0200 Subject: [PATCH 1197/2286] MAINTAINERS: Add the Renesas RZ/N1 timers driver entry After contributing the driver, add myself as the maintainer for the Renesas RZ/N1 timers driver. Signed-off-by: Herve Codina (Schneider Electric) Signed-off-by: Daniel Lezcano Reviewed-by: Geert Uytterhoeven Reviewed-by: Wolfram Sang Link: https://patch.msgid.link/20261001082225.125956-5-herve.codina@bootlin.com --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 72294ddfa5b757..7bdacc8feeed49 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23295,6 +23295,13 @@ S: Maintained F: Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml F: drivers/rtc/rtc-rzn1.c +RENESAS RZ/N1 TIMERS DRIVER +M: Herve Codina +L: linux-renesas-soc@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/timer/renesas,rzn1-timer.yaml +F: drivers/clocksource/timer-rzn1.c + RENESAS RZ/N1 USBF CONTROLLER DRIVER M: Herve Codina L: linux-renesas-soc@vger.kernel.org From 9e174422f6f15a1d88994a07067f66c920d2f947 Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Thu, 1 Oct 2026 10:25:16 +0200 Subject: [PATCH 1198/2286] irq: Add {over,under}flow detection for local_interrupt_{enable,disable} Currently there is no way to detect overflow and underflow inside the HARDIRQ_DISABLE_MASK part of preempt_count. Add these detection guarded by DEBUG_PREEMPT similar to the detection for PREEMPT_MASK {over,under}flow. Suggested-by: Shrikanth Hegde Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20261001082519.16195-3-boqun@kernel.org --- include/linux/interrupt_rc.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/linux/interrupt_rc.h b/include/linux/interrupt_rc.h index a9ed937a80e74c..d385ab05a2b686 100644 --- a/include/linux/interrupt_rc.h +++ b/include/linux/interrupt_rc.h @@ -11,6 +11,7 @@ * make it easier to include without hitting circular header dependencies. */ +#include #include #include #include @@ -65,6 +66,13 @@ static inline void local_interrupt_disable(void) local_irq_save(flags); new_count = hardirq_disable_enter(); + /* Is hardirq disable count overflow soon? */ + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) && + DEBUG_LOCKS_WARN_ON(((new_count & HARDIRQ_DISABLE_MASK) + + (10 << HARDIRQ_DISABLE_SHIFT)) >= + HARDIRQ_DISABLE_MASK)) + return; + if ((new_count & HARDIRQ_DISABLE_MASK) == HARDIRQ_DISABLE_OFFSET) _local_interrupt_save_state(flags); } @@ -73,6 +81,11 @@ static inline void local_interrupt_enable(void) { int new_count; + /* Unpaired local_interrupt_enable()? Warn and abort. */ + if (IS_ENABLED(CONFIG_DEBUG_PREEMPT) && + DEBUG_LOCKS_WARN_ON((preempt_count() & HARDIRQ_DISABLE_MASK) == 0)) + return; + new_count = hardirq_disable_exit(); if ((new_count & HARDIRQ_DISABLE_MASK) == 0) From 1259028ec8f2a788cf560b40efca2c1fa74da89c Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Thu, 1 Oct 2026 10:25:18 +0200 Subject: [PATCH 1199/2286] irq: Explain better on NMI_MASK overflow condition Currently __preempt_count_nmi_enter() uses 15 as the maximum NMI nesting level, and it's because 4 bits are used to represent the nesting level of NMI. Replace the tautological comment with a more direct mention to the 4 bits limitation to help readers understand this condition. Suggested-by: Shrikanth Hegde Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20261001082519.16195-5-boqun@kernel.org --- include/linux/hardirq.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/hardirq.h b/include/linux/hardirq.h index 73b48dd9f1354f..2648c49cd8f6cd 100644 --- a/include/linux/hardirq.h +++ b/include/linux/hardirq.h @@ -108,7 +108,7 @@ DECLARE_PER_CPU(unsigned int, nmi_nesting); #define __preempt_count_nmi_enter() \ do { \ __preempt_count_add(HARDIRQ_OFFSET); \ - /* Maximum NMI nesting is 15. */ \ + /* NMI nesting is represented in 4 bits. */ \ BUG_ON(__this_cpu_read(nmi_nesting) >= 15); \ __this_cpu_inc(nmi_nesting); \ preempt_count_set(preempt_count() | NMI_MASK); \ From 67e0eadd862616cb22145d6117b79b79c4ae48fe Mon Sep 17 00:00:00 2001 From: Boqun Feng Date: Thu, 1 Oct 2026 10:25:19 +0200 Subject: [PATCH 1200/2286] irq: Add max local_interrupt_disable() nesting level kunit test case To confirm the max nesting level of local_interrupt_disable() works, a kunit test is added to the whole test suite. Note that when DEBUG_PREEMPT=y, it'll generate a warning which is expected. Suggested-by: Shrikanth Hegde Signed-off-by: Boqun Feng Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Shrikanth Hegde Link: https://patch.msgid.link/20261001082519.16195-6-boqun@kernel.org --- kernel/irq/refcount_interrupt_test.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/kernel/irq/refcount_interrupt_test.c b/kernel/irq/refcount_interrupt_test.c index ca904dba24b9c1..38dfccbaa4d410 100644 --- a/kernel/irq/refcount_interrupt_test.c +++ b/kernel/irq/refcount_interrupt_test.c @@ -52,6 +52,27 @@ static void test_multiple_irq_change(struct kunit *test) TEST_IRQ_ON(); } +static void test_max_nesting_irq_change(struct kunit *test) +{ + for (int i = 0; i < __IRQ_MASK(HARDIRQ_DISABLE_BITS); i++) { + local_interrupt_disable(); + TEST_IRQ_OFF(); + } + + + for (int i = 0; i < __IRQ_MASK(HARDIRQ_DISABLE_BITS); i++) { + TEST_IRQ_OFF(); + local_interrupt_enable(); + } + + TEST_IRQ_ON(); + + local_interrupt_disable(); + TEST_IRQ_OFF(); + local_interrupt_enable(); + TEST_IRQ_ON(); +} + static void test_irq_save(struct kunit *test) { unsigned long flags; @@ -79,6 +100,7 @@ static struct kunit_case test_cases[] = { KUNIT_CASE(test_single_irq_change), KUNIT_CASE(test_nested_irq_change), KUNIT_CASE(test_multiple_irq_change), + KUNIT_CASE(test_max_nesting_irq_change), KUNIT_CASE(test_irq_save), {}, }; From a05997891e64b8e32733c0e062a062dcc3617a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Tue, 29 Sep 2026 14:14:11 +0200 Subject: [PATCH 1201/2286] clocksource/drivers/mips-gic-timer: Simplify gic_next_event() for per-cpu timer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GIC clock event device is registered per CPU and is flagged with CLOCK_EVT_FEAT_C3STOP. This guarantees that the set_next_event() callback is always invoked on the CPU of the event. Drop the code path configuring the compare register of another CPU as it is never called and does not correctly handle the case of having multiple clusters, and document this guarantee by adding the CLOCK_EVT_FEAT_PERCPU flag to the clock event device features. Signed-off-by: Benoît Monin Signed-off-by: Daniel Lezcano Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260929-sync-gic-counters-v4-4-ec70c4b60434@bootlin.com --- drivers/clocksource/mips-gic-timer.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c index 1501c7db9a8e3c..cdaf4ba1d509ed 100644 --- a/drivers/clocksource/mips-gic-timer.c +++ b/drivers/clocksource/mips-gic-timer.c @@ -52,18 +52,13 @@ static u64 notrace gic_read_count(void) static int gic_next_event(unsigned long delta, struct clock_event_device *evt) { - int cpu = cpumask_first(evt->cpumask); u64 cnt; int res; cnt = gic_read_count(); cnt += (u64)delta; - if (cpu == raw_smp_processor_id()) { - write_gic_vl_compare(cnt); - } else { - write_gic_vl_other(mips_cm_vp_id(cpu)); - write_gic_vo_compare(cnt); - } + write_gic_vl_compare(cnt); + res = ((int)(gic_read_count() - cnt) >= 0) ? -ETIME : 0; return res; } @@ -82,6 +77,7 @@ static void gic_clockevent_cpu_init(unsigned int cpu, { cd->name = "MIPS GIC"; cd->features = CLOCK_EVT_FEAT_ONESHOT | + CLOCK_EVT_FEAT_PERCPU | CLOCK_EVT_FEAT_C3STOP; cd->rating = 350; From a2cab84d6601d92f8b67b9dbdae568f5276bf841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Monin?= Date: Tue, 29 Sep 2026 14:14:12 +0200 Subject: [PATCH 1202/2286] clocksource/drivers/mips-gic-timer: Use local counter on synced multi-cluster systems MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a multi-cluster MIPS system there is one GIC per cluster, each with its own independent counter. These counters are not synchronized in hardware and can drift relative to one another, which is why multi- cluster systems currently fall back to gic_hpt_read_multicluster(): every clocksource read is redirected to cluster 0's counter via a cross-cluster register access. Instead, actively synchronize the counter of each secondary cluster to cluster 0's counter as its CPUs come online. When the first CPU of a cluster starts and the GIC counter is stopped, gic_sync_counter_64() is used to align the local counter with cluster 0 on systems using 64-bit CM accesses: - the local counter is stopped, loaded with cluster 0's counter value plus an accumulated offset, and restarted; - the alignment is checked by reading the local counter, cluster 0's counter and the local counter again (t0, t1, t2). If t1 lies between t0 and t2 the two counters are considered in sync; - otherwise the offset is refined by half of the measured error to compensate for the cross-cluster access latency, and the process is retried. After a short delay the alignment is re-checked to confirm the counters have not drifted apart, and only then is the cluster recorded as synchronized in gic_synced_cl_map. Clusters with no cores and cluster 0 itself are marked synchronized up-front. Once every cluster is synchronized, gic_clocksource_promote() sets gic_use_local_counter so that gic_hpt_read() stops redirecting reads to cluster 0 and uses the local counter instead. It also re-enables the GIC VDSO clock mode and registers the GIC counter as the sched_clock. Systems where synchronization cannot be achieved keep using the safe cross-cluster read path. The promotion is deferred to a workqueue because it is triggered from the CPU hotplug STARTING callback, which runs with interrupts disabled, while registering the sched_clock and switching the VDSO clock mode should be done from process context. Note that the clocksource is only promoted once when all clusters are first online and all GIC counters are in sync. It is assumed that even if a cluster is fully powered off and later powered on again, gic_sync_counter_64() will be able to re-synchronize it. Said differently: there is no support to "demote" the clocksource. On the dual-cluster Mobileye EyeQ6H SoC, this allows four times faster clock_gettime(CLOCK_MONOTONIC) and a much higher precision sched_clock instead of jiffies. Signed-off-by: Benoît Monin Signed-off-by: Daniel Lezcano Reviewed-by: Thomas Bogendoerfer Link: https://patch.msgid.link/20260929-sync-gic-counters-v4-5-ec70c4b60434@bootlin.com --- drivers/clocksource/mips-gic-timer.c | 128 ++++++++++++++++++++++++--- 1 file changed, 117 insertions(+), 11 deletions(-) diff --git a/drivers/clocksource/mips-gic-timer.c b/drivers/clocksource/mips-gic-timer.c index cdaf4ba1d509ed..fc97b31f0adb8e 100644 --- a/drivers/clocksource/mips-gic-timer.c +++ b/drivers/clocksource/mips-gic-timer.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,9 @@ static int gic_timer_irq; static unsigned int gic_frequency; static unsigned int gic_count_width; static bool __read_mostly gic_clock_unstable; +static bool __read_mostly gic_use_local_counter = true; +static unsigned long *gic_synced_cl_map; +static struct work_struct gic_promote_work; static void gic_clocksource_unstable(char *reason); @@ -102,10 +106,71 @@ static void gic_update_frequency(void *data) clockevents_update_freq(this_cpu_ptr(&gic_clockevent_device), rate); } +/* Number of iterations to synchronize the local GIC counter */ +#define GIC_SYNC_ITERATIONS 4 + +/* Delay in us to check if the local GIC counter is still in sync with cluster 0 */ +#define GIC_SYNC_CHECK_DELAY 100 + +static void gic_sync_counter_64(unsigned int cluster) +{ + unsigned int config = read_gic_config(); + u64 t0, t1, t2; + s64 offset = 0; + + mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL); + + for (int i = 0; i < GIC_SYNC_ITERATIONS; i++) { + write_gic_config(config | GIC_CONFIG_COUNTSTOP); + write_gic_counter(read_gic_redir_counter() + offset); + write_gic_config(config & ~GIC_CONFIG_COUNTSTOP); + + t0 = read_gic_counter(); + t1 = read_gic_redir_counter(); + t2 = read_gic_counter(); + + if (time_in_range64(t1, t0, t2)) + break; + + /* + * Compute the offset to apply to the local counter + * so that (t1 - t0) equals (t2 - t1). + */ + offset += (s64)(2 * t1 - t0 - t2) / 2; + } + + mips_cm_unlock_other(); + + if (!time_in_range64(t1, t0, t2)) + return; + + udelay(GIC_SYNC_CHECK_DELAY); + + mips_cm_lock_other(0, 0, 0, CM_GCR_Cx_OTHER_BLOCK_GLOBAL); + t0 = read_gic_counter(); + t1 = read_gic_redir_counter(); + t2 = read_gic_counter(); + mips_cm_unlock_other(); + + /* If so, mark the cluster as synchronized */ + if (time_in_range64(t1, t0, t2) && gic_synced_cl_map) + bitmap_set(gic_synced_cl_map, cluster, 1); +} + static int gic_starting_cpu(unsigned int cpu) { - /* Ensure the GIC counter is running */ - clear_gic_config(GIC_CONFIG_COUNTSTOP); + unsigned int cluster = cpu_cluster(&cpu_data[cpu]); + + if (read_gic_config() & GIC_CONFIG_COUNTSTOP) { + clear_gic_config(GIC_CONFIG_COUNTSTOP); + + if (cluster && mips_cm_is64 && !gic_clock_unstable) + gic_sync_counter_64(cluster); + + if (gic_synced_cl_map && + bitmap_full(gic_synced_cl_map, mips_cps_numclusters())) + schedule_work(&gic_promote_work); + } gic_clockevent_cpu_init(cpu, this_cpu_ptr(&gic_clockevent_device)); return 0; @@ -154,11 +219,6 @@ static int gic_clockevent_init(void) return 0; } -static u64 gic_hpt_read(struct clocksource *cs) -{ - return gic_read_count(); -} - static u64 gic_hpt_read_multicluster(struct clocksource *cs) { unsigned int hi, hi2, lo; @@ -190,6 +250,14 @@ static u64 gic_hpt_read_multicluster(struct clocksource *cs) return count; } +static u64 gic_hpt_read(struct clocksource *cs) +{ + if (unlikely(!READ_ONCE(gic_use_local_counter))) + return gic_hpt_read_multicluster(cs); + + return gic_read_count(); +} + static struct clocksource gic_clocksource = { .name = "GIC", .read = gic_hpt_read, @@ -209,8 +277,28 @@ static void gic_clocksource_unstable(char *reason) clocksource_mark_unstable(&gic_clocksource); } +static void gic_clocksource_promote(struct work_struct *work) +{ + if (gic_clock_unstable || READ_ONCE(gic_use_local_counter)) + return; + + WRITE_ONCE(gic_use_local_counter, true); + +#ifdef CONFIG_GENERIC_GETTIMEOFDAY + gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_GIC; +#endif + + if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) { + sched_clock_register(mips_cm_is64 ? + gic_read_count_64 : gic_read_count_2x32, + gic_count_width, gic_frequency); + } +} + static int __init __gic_clocksource_init(void) { + unsigned int numclusters; + bool synced = false; int ret; /* Set clocksource mask. */ @@ -222,14 +310,32 @@ static int __init __gic_clocksource_init(void) /* Calculate a somewhat reasonable rating value. */ if (mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) - gic_clocksource.rating = 300; /* Good when frequecy is stable */ + gic_clocksource.rating = 300; /* Good when frequency is stable */ else gic_clocksource.rating = 200; gic_clocksource.rating += clamp(gic_frequency / 10000000, 0, 99); - if (mips_cps_multicluster_cpus()) { - gic_clocksource.read = &gic_hpt_read_multicluster; + numclusters = mips_cps_numclusters(); + if (numclusters > 1) + gic_synced_cl_map = bitmap_zalloc(numclusters, GFP_KERNEL); + + /* + * Mark cluster 0 as synchronized (with itself), and all clusters + * without cores since there is no local GIC counter access on those. + */ + if (gic_synced_cl_map) { + bitmap_set(gic_synced_cl_map, 0, 1); + for (unsigned int cl = 0; cl < numclusters; cl++) { + if (!mips_cps_numcores(cl)) + bitmap_set(gic_synced_cl_map, cl, 1); + } + synced = bitmap_full(gic_synced_cl_map, numclusters); + } + + if (numclusters > 1 && !synced) { + WRITE_ONCE(gic_use_local_counter, false); gic_clocksource.vdso_clock_mode = VDSO_CLOCKMODE_NONE; + INIT_WORK(&gic_promote_work, gic_clocksource_promote); } ret = clocksource_register_hz(&gic_clocksource, gic_frequency); @@ -288,7 +394,7 @@ static int __init gic_clocksource_of_init(struct device_node *node) * change performed by the CPC core clocks divider. */ if ((mips_cm_revision() >= CM_REV_CM3 || !IS_ENABLED(CONFIG_CPU_FREQ)) && - !mips_cps_multicluster_cpus()) { + READ_ONCE(gic_use_local_counter)) { sched_clock_register(mips_cm_is64 ? gic_read_count_64 : gic_read_count_2x32, gic_count_width, gic_frequency); From 0d3a67b1954a3988fef20ba7641d33e6085ad313 Mon Sep 17 00:00:00 2001 From: "Markus Schneider-Pargmann (TI)" Date: Fri, 28 Aug 2026 20:47:45 +0200 Subject: [PATCH 1203/2286] clocksource/drivers/timer-ti-dm: Register clocksource/event only for am654 Registering clocksource/clockevent for the older platforms breaks booting them. The older platforms have their own implementation in early boot that is used to register clocksource/clockevent in timer-ti-dm-systimer.c. With am654, the timer is much more complex with more dependencies so doing this in early boot with the other driver is very difficult and gets ugly. Instead it is implemented in the platform driver for am654. However the old platforms may not register clocksource/clockevent in this driver as that would break the boot. Add a system_timer_capable flag that is only set for am654-timer that gates the clocksource/clockevent registration. Reported-by: H. Nikolaus Schaller Closes: https://lore.kernel.org/r/2013A99A-0502-4128-ACFA-B04886224393@goldelico.com Fixes: b8eeeca55456 ("clocksource/drivers/timer-ti-dm: Add clocksource support") Signed-off-by: Markus Schneider-Pargmann (TI) Signed-off-by: Daniel Lezcano Tested-by: Andreas Kemnade # OMAP3 GTA04 Tested-by: Kevin Hilman # OMAP3 Beagle XM Tested-by: H. Nikolaus Schaller # OMAP3 OpenPandora Link: https://patch.msgid.link/20260828-v7-3-next-topic-ti-timer-dm-clocksource-omap-fix-v1-1-85f0e4dc6f9f@baylibre.com --- drivers/clocksource/timer-ti-dm.c | 4 +++- include/linux/platform_data/dmtimer-omap.h | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-ti-dm.c b/drivers/clocksource/timer-ti-dm.c index 6787acac9a4387..ae8777d0321540 100644 --- a/drivers/clocksource/timer-ti-dm.c +++ b/drivers/clocksource/timer-ti-dm.c @@ -1475,7 +1475,8 @@ static int omap_dm_timer_probe(struct platform_device *pdev) timer->pdev = pdev; - if (timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) { + if (pdata->system_timer_capable && + timer->capability & OMAP_TIMER_ALWON && !IS_ERR_OR_NULL(timer->fclk)) { if (!omap_dm_timer_sched_clock_counter) { ret = omap_dm_timer_setup_clocksource(timer); if (ret) @@ -1585,6 +1586,7 @@ static const struct dmtimer_platform_data omap3plus_pdata = { static const struct dmtimer_platform_data am6_pdata = { .timer_ops = &dmtimer_ops, + .system_timer_capable = true, }; static const struct of_device_id omap_timer_match[] = { diff --git a/include/linux/platform_data/dmtimer-omap.h b/include/linux/platform_data/dmtimer-omap.h index 726d8914384293..b78ee4676b98ae 100644 --- a/include/linux/platform_data/dmtimer-omap.h +++ b/include/linux/platform_data/dmtimer-omap.h @@ -57,6 +57,8 @@ struct dmtimer_platform_data { u32 timer_errata; int (*get_context_loss_count)(struct device *); const struct omap_dm_timer_ops *timer_ops; + /* always-on timers can be used for clocksource/clockevent */ + bool system_timer_capable; }; #endif /* __PLATFORM_DATA_DMTIMER_OMAP_H__ */ From 8f30b035a6676e292ee8e54334711d9905dd0495 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Fri, 11 Sep 2026 14:09:17 +0800 Subject: [PATCH 1204/2286] clocksource/drivers/dbx500-prcmu: Check timer register mapping Return -ENOMEM if of_iomap() fails, before reading or configuring the timer register block. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: 9d2aa8c7961a ("ARM/clocksource: use automatic DT probing for ux500 PRCMU") Assisted-by: LLM Signed-off-by: Slavin Liu Signed-off-by: Daniel Lezcano Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260911060917.94310-1-bolin.liu@seu.edu.cn --- drivers/clocksource/clksrc-dbx500-prcmu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c index 2fc93e46cea38e..1af223b0e89634 100644 --- a/drivers/clocksource/clksrc-dbx500-prcmu.c +++ b/drivers/clocksource/clksrc-dbx500-prcmu.c @@ -52,6 +52,8 @@ static struct clocksource clocksource_dbx500_prcmu = { static int __init clksrc_dbx500_prcmu_init(struct device_node *node) { clksrc_dbx500_timer_base = of_iomap(node, 0); + if (!clksrc_dbx500_timer_base) + return -ENOMEM; /* * The A9 sub system expects the timer to be configured as From 595373206d0c0cfc4e65f233e36d7b3e110b0709 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Wed, 30 Sep 2026 12:28:56 +0200 Subject: [PATCH 1205/2286] isofs: Simplify isofs_read_level3_size() After recent changes it is possible to further simplify isofs_read_level3_size() by reorganizing the loop a bit and removing empty_blocks, block_saved, offset_saved variables. Reviewed-by: Matthias Goergens Tested-by: Matthias Goergens Signed-off-by: Jan Kara --- fs/isofs/inode.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index e884618c0c5357..c8f0f8803a7b13 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -1173,9 +1173,8 @@ static int isofs_read_level3_size(struct inode *inode) unsigned long bufsize = ISOFS_BUFFER_SIZE(inode); int high_sierra = ISOFS_SB(inode->i_sb)->s_high_sierra; struct buffer_head *bh = NULL; - unsigned long block, offset, block_saved, offset_saved; + unsigned long block, offset; int i = 0; - int empty_blocks = 0; int more_entries = 0; struct iso_inode_info *ei = ISOFS_I(inode); @@ -1209,7 +1208,7 @@ static int isofs_read_level3_size(struct inode *inode) * chain of empty blocks could be walked without bound. */ if (offset >= bufsize || de->length[0] == 0) { - if (offset == 0 && ++empty_blocks + i > 100) + if (offset == 0 && ++i > 100) goto out_toomany; brelse(bh); bh = NULL; @@ -1225,21 +1224,17 @@ static int isofs_read_level3_size(struct inode *inode) return -EIO; } + /* Save the first continuation directory entry in the inode */ + if (more_entries && !ei->i_next_section_block) { + ei->i_next_section_block = block; + ei->i_next_section_offset = offset; + } de_len = de->length[0]; - block_saved = block; - offset_saved = offset; offset += de_len; - inode->i_size += isonum_733(de->size); - if (i == 1) { - ei->i_next_section_block = block_saved; - ei->i_next_section_offset = offset_saved; - } - more_entries = de->flags[-high_sierra] & 0x80; - i++; - if (i + empty_blocks > 100) + if (++i > 100) goto out_toomany; } while (more_entries); out: From b8253bcf92d6365b20eea6465f5a93c9fc4aa029 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 12:29:13 +0800 Subject: [PATCH 1206/2286] isofs: return long Joliet names whole get_joliet_filename() writes into a buffer allocated by its caller but is not told how big that buffer is. It hardcodes PAGE_SIZE as the output limit for utf16s_to_utf8s() and gives uni16_to_x8() no limit at all. PAGE_SIZE was never the right bound. Before commit b2eb2e288604 ("isofs: Drop support of directory entries straddling blocks") both callers allocated a page, but only its first 1024 bytes were for the name, the rest holding a copy of a directory record that straddled a block. It could not overflow only because a directory record holds at most 222 bytes of name (255 bytes, 33 of them fixed), which no converter expands beyond 1024 bytes. It also keeps the converted length in an unsigned char. 222 bytes of name are 111 UTF-16 units, and the UTF-8 converter, used for iocharset=utf8 and when CONFIG_NLS_DEFAULT is "utf8", needs three bytes for each CJK, Thai or Devanagari character, so such a name can take up to 333 bytes. Past 255 the length wraps. readdir then reports a short name that usually ends in the middle of a UTF-8 sequence, and lookup finds the file only under that name, never under its real one; a name that wraps to exactly 0 bytes is not listed at all. Such names are out of spec, since Joliet allows 64 units, but common tools write them: PowerISO 9.5 and UltraISO 9.76 both keep up to 110 units by default. Windows, for which Joliet was made, returns them whole, including 111-unit names in records that leave out the padding byte. Return them whole here too. Pass the buffer size down and have both converters respect it. utf16s_to_utf8s() stops before a character that does not fit, and uni16_to_x8() now hands uni2char() the space that actually remains and stops on -ENAMETOOLONG, as fs/hfsplus/unicode.c does. Both callers pass JOLIET_NAME_MAX + 1, room for 111 units at three bytes each plus the terminator; no character set needs more than three bytes for a UTF-16 unit, and isofs_dir_record_valid() already rejects a record whose name_len claims more than the record holds, so no name is cut. Make the length an int, which is what both converters return. These names are longer than NAME_MAX. POSIX lets the limit vary by filesystem, reported by pathconf(_PC_NAME_MAX), and the VFS limits a name only by PATH_MAX (verify_dirent_name() in fs/readdir.c). vfat, exfat, hfsplus and ntfs3 already return names of up to 255 UTF-16 units, 765 bytes of UTF-8, and such names break the same things there: copying the file to a filesystem with a 255-byte limit, such as ext4 or tmpfs, fails with ENAMETOOLONG, and cp, tar, rsync and Python's shutil report that file and carry on with the rest, so the copy is incomplete but says so. glibc's readdir() returns the names, but the deprecated readdir_r() skips them and fails with ENAMETOOLONG, and an inotify reader with the buffer size inotify(7) suggests gets EINVAL. In the kernel, fanotify reports events on such a file without its name and warns once in fanotify_info_copy_name(), and a directory with such a name cannot be reconnected from a file handle, because the generic get_name() in fs/exportfs, which isofs uses, skips names longer than NAME_MAX. Cutting at NAME_MAX would avoid these, but would list names that Windows does not show and that can coincide within a directory. Every name of at most 255 bytes is returned exactly as before. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926042916.3277409-2-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/dir.c | 4 +++- fs/isofs/isofs.h | 11 ++++++++++- fs/isofs/joliet.c | 27 ++++++++++++++++++++------- fs/isofs/namei.c | 3 ++- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index c7ca7603e97a12..f4243fcbb69790 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -199,7 +199,9 @@ static int do_isofs_readdir(struct inode *inode, struct file *file, if (map) { #ifdef CONFIG_JOLIET if (sbi->s_joliet_level) { - len = get_joliet_filename(de, tmpname, inode); + len = get_joliet_filename(de, tmpname, + JOLIET_NAME_MAX + 1, + inode); p = tmpname; } else #endif diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h index 79ca0256843aa5..47c43a3c6a614f 100644 --- a/fs/isofs/isofs.h +++ b/fs/isofs/isofs.h @@ -121,7 +121,16 @@ bool isofs_dir_record_valid(struct iso_directory_record *de, unsigned long offset, unsigned long bufsize); -int get_joliet_filename(struct iso_directory_record *, unsigned char *, struct inode *); +/* + * The longest name the Joliet converter returns, in bytes. A directory + * record is at most 255 bytes long, which leaves room for 111 UTF-16 units + * of name, and no character set needs more than three bytes for one unit. + */ +#define JOLIET_NAME_MAX \ + ((255 - sizeof(struct iso_directory_record)) / 2 * 3) + +int get_joliet_filename(struct iso_directory_record *de, unsigned char *outname, + int outsize, struct inode *inode); int get_acorn_filename(struct iso_directory_record *, char *, struct inode *); extern struct dentry *isofs_lookup(struct inode *, struct dentry *, unsigned int flags); diff --git a/fs/isofs/joliet.c b/fs/isofs/joliet.c index c0f04a1e7f695f..d37e67c5e36f42 100644 --- a/fs/isofs/joliet.c +++ b/fs/isofs/joliet.c @@ -15,19 +15,26 @@ * Convert Unicode 16 to UTF-8 or ASCII. */ static int -uni16_to_x8(unsigned char *ascii, __be16 *uni, int len, struct nls_table *nls) +uni16_to_x8(unsigned char *ascii, __be16 *uni, int len, struct nls_table *nls, + int outsize) { __be16 *ip, ch; - unsigned char *op; + unsigned char *op, *end; ip = uni; op = ascii; + end = ascii + outsize - 1; /* leave room for the terminator */ while ((ch = get_unaligned(ip)) && len) { int llen; - llen = nls->uni2char(be16_to_cpu(ch), op, NLS_MAX_CHARSET_SIZE); + + if (op >= end) + break; + llen = nls->uni2char(be16_to_cpu(ch), op, end - op); if (llen > 0) op += llen; + else if (llen == -ENAMETOOLONG) + break; else *op++ = '?'; ip++; @@ -38,21 +45,27 @@ uni16_to_x8(unsigned char *ascii, __be16 *uni, int len, struct nls_table *nls) return (op - ascii); } +/* + * Convert the Joliet name of @de into @outname, a buffer of @outsize bytes. + * The result is at most @outsize - 1 bytes long; a longer name is cut at a + * character boundary. + */ int -get_joliet_filename(struct iso_directory_record * de, unsigned char *outname, struct inode * inode) +get_joliet_filename(struct iso_directory_record *de, unsigned char *outname, + int outsize, struct inode *inode) { struct nls_table *nls; - unsigned char len = 0; + int len = 0; nls = ISOFS_SB(inode->i_sb)->s_nls_iocharset; if (!nls) { len = utf16s_to_utf8s((const wchar_t *) de->name, de->name_len[0] >> 1, UTF16_BIG_ENDIAN, - outname, PAGE_SIZE); + outname, outsize - 1); } else { len = uni16_to_x8(outname, (__be16 *) de->name, - de->name_len[0] >> 1, nls); + de->name_len[0] >> 1, nls, outsize); } if ((len > 2) && (outname[len-2] == ';') && (outname[len-1] == '1')) len -= 2; diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index 010682f5901a49..ccf6f01cb3bbbf 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -107,7 +107,8 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry, dpnt = tmpname; #ifdef CONFIG_JOLIET } else if (sbi->s_joliet_level) { - dlen = get_joliet_filename(de, tmpname, dir); + dlen = get_joliet_filename(de, tmpname, + JOLIET_NAME_MAX + 1, dir); dpnt = tmpname; #endif } else if (sbi->s_mapping == 'a') { From 1885d252b3a944997701db0a4df89761741ed43f Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 12:29:14 +0800 Subject: [PATCH 1207/2286] isofs: report the Joliet name length in statfs isofs_statfs() reports NAME_MAX as the longest name, but a Joliet name can now be up to JOLIET_NAME_MAX bytes long. glibc answers pathconf(_PC_NAME_MAX) from f_namelen, so a program sizing a buffer from it should get room for every name readdir returns. vfat has reported 255 times NLS_MAX_CHARSET_SIZE, 1530, since commit f68e542f3478 ("fat: Fix statfs->f_namelen"), which followed a report of readdir_r() overflowing a buffer sized that way, and exfat reports the same. Windows reports a maximum component length of 110 for CDFS. Report JOLIET_NAME_MAX on Joliet mounts. Rock Ridge and plain ISO 9660 mounts keep NAME_MAX. Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926042916.3277409-3-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c index c8f0f8803a7b13..4824e42995ccfd 100644 --- a/fs/isofs/inode.c +++ b/fs/isofs/inode.c @@ -1012,7 +1012,8 @@ static int isofs_statfs (struct dentry *dentry, struct kstatfs *buf) buf->f_files = ISOFS_SB(sb)->s_ninodes; buf->f_ffree = 0; buf->f_fsid = u64_to_fsid(id); - buf->f_namelen = NAME_MAX; + buf->f_namelen = ISOFS_SB(sb)->s_joliet_level ? + JOLIET_NAME_MAX : NAME_MAX; return 0; } From 9f3d3b4b5300124416d87e71280a8fea6a60f6c9 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 12:29:15 +0800 Subject: [PATCH 1208/2286] isofs: pass the name buffer size to get_rock_ridge_filename() get_rock_ridge_filename() assembles a name from one or more NM entries into a buffer allocated by its caller. It is not told how big that buffer is, and instead stops adding NM entries once the name would exceed NAME_MAX, relying on the callers' buffer being larger. Pass the size in and check against it. Both callers pass NAME_MAX + 1, so the behaviour is unchanged: an NM entry that would take the name past 255 bytes is dropped, together with any that follow, and the entries before it are returned. Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926042916.3277409-4-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/dir.c | 3 ++- fs/isofs/isofs.h | 3 ++- fs/isofs/namei.c | 3 ++- fs/isofs/rock.c | 8 ++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index f4243fcbb69790..ed8a8dc41fb271 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -190,7 +190,8 @@ static int do_isofs_readdir(struct inode *inode, struct file *file, map = 1; if (sbi->s_rock) { - len = get_rock_ridge_filename(de, tmpname, inode); + len = get_rock_ridge_filename(de, tmpname, NAME_MAX + 1, + inode); if (len != 0) { /* may be -1 */ p = tmpname; map = 0; diff --git a/fs/isofs/isofs.h b/fs/isofs/isofs.h index 47c43a3c6a614f..a2d28a23e8924a 100644 --- a/fs/isofs/isofs.h +++ b/fs/isofs/isofs.h @@ -115,7 +115,8 @@ struct timespec64 iso_date(u8 *p, int flags); struct inode; /* To make gcc happy */ extern int parse_rock_ridge_inode(struct iso_directory_record *, struct inode *, int relocated); -extern int get_rock_ridge_filename(struct iso_directory_record *, char *, struct inode *); +int get_rock_ridge_filename(struct iso_directory_record *de, char *retname, + int retnamesize, struct inode *inode); extern int isofs_name_translate(struct iso_directory_record *, char *, struct inode *); bool isofs_dir_record_valid(struct iso_directory_record *de, unsigned long offset, diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index ccf6f01cb3bbbf..7ee1a2e1e38b37 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -102,7 +102,8 @@ isofs_find_entry(struct inode *dir, struct dentry *dentry, dpnt = de->name; if (sbi->s_rock && - ((i = get_rock_ridge_filename(de, tmpname, dir)))) { + ((i = get_rock_ridge_filename(de, tmpname, NAME_MAX + 1, + dir)))) { dlen = i; /* possibly -1 */ dpnt = tmpname; #ifdef CONFIG_JOLIET diff --git a/fs/isofs/rock.c b/fs/isofs/rock.c index 84e0d764c2100f..5a5984b7222478 100644 --- a/fs/isofs/rock.c +++ b/fs/isofs/rock.c @@ -209,10 +209,14 @@ static int rock_check_overflow(struct rock_state *rs, int sig) } /* + * Build the Rock Ridge name of @de in @retname, a buffer of @retnamesize + * bytes. From the first NM entry that does not fit along with the + * terminator, the rest of the name is dropped. + * * return length of name field; 0: not found, -1: to be ignored */ int get_rock_ridge_filename(struct iso_directory_record *de, - char *retname, struct inode *inode) + char *retname, int retnamesize, struct inode *inode) { struct rock_state rs; struct rock_ridge *rr; @@ -287,7 +291,7 @@ int get_rock_ridge_filename(struct iso_directory_record *de, break; } len = rr->len - 5; - if (retnamlen + len > NAME_MAX) { + if (retnamlen + len >= retnamesize) { truncate = 1; break; } From e646566b4ad537286ebee65a55de460003a7bf90 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 12:29:16 +0800 Subject: [PATCH 1209/2286] isofs: shrink the name conversion buffer isofs_readdir() and isofs_lookup() allocate 1024 bytes for the name converters. Now that get_rock_ridge_filename() and get_joliet_filename() are told the buffer size, nothing writes beyond JOLIET_NAME_MAX + 1 bytes: get_joliet_filename() is passed that size, get_rock_ridge_filename() is passed NAME_MAX + 1, isofs_name_translate() copies at most the 222 bytes of name a directory record can hold, and get_acorn_filename() appends at most five bytes to that. Allocate JOLIET_NAME_MAX + 1 bytes, matching what the Joliet callers pass. No functional change. Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926042916.3277409-5-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/isofs/dir.c | 7 ++++++- fs/isofs/namei.c | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/isofs/dir.c b/fs/isofs/dir.c index ed8a8dc41fb271..310b74b08b5e6a 100644 --- a/fs/isofs/dir.c +++ b/fs/isofs/dir.c @@ -240,7 +240,12 @@ static int isofs_readdir(struct file *file, struct dir_context *ctx) char *tmpname; struct inode *inode = file_inode(file); - tmpname = kmalloc(1024, GFP_KERNEL); + /* + * Rockridge can produce names of NAME_MAX size, Acorn extensions at + * most 227 chars. + */ + BUILD_BUG_ON(JOLIET_NAME_MAX < NAME_MAX || JOLIET_NAME_MAX < 227); + tmpname = kmalloc(JOLIET_NAME_MAX + 1, GFP_KERNEL); if (tmpname == NULL) return -ENOMEM; diff --git a/fs/isofs/namei.c b/fs/isofs/namei.c index 7ee1a2e1e38b37..19128de0c3a7be 100644 --- a/fs/isofs/namei.c +++ b/fs/isofs/namei.c @@ -155,7 +155,7 @@ struct dentry *isofs_lookup(struct inode *dir, struct dentry *dentry, unsigned i struct inode *inode; char *tmpname; - tmpname = kmalloc(1024, GFP_USER); + tmpname = kmalloc(JOLIET_NAME_MAX + 1, GFP_USER); if (!tmpname) return ERR_PTR(-ENOMEM); From 0f242ca0878896df30292f4922700a32e8fdd039 Mon Sep 17 00:00:00 2001 From: Giorgi Kobakhia Date: Mon, 28 Sep 2026 13:04:45 -0700 Subject: [PATCH 1210/2286] udf: fix return value for non-recorded-allocated extents in udf_map_block() When someone creates a corrupted UDF filesystem where symlink contains other extent type than EXT_RECORDED_ALLOCATED, it can leads to a crash like: Oops: general protection fault KASAN: maybe wild-memory-access RIP: 0010:page_get_link (fs/namei.c:6503) Call Trace: vfs_readlink (fs/namei.c:6419) do_readlinkat (fs/stat.c:583) __x64_sys_readlink (fs/stat.c:605 fs/stat.c:602 fs/stat.c:602) This is because in this case udf_map_block() fails to convert return value of inode_bmap() from 1 to 0. Returned '1' propagates through udf_bread() all the way up to __page_get_link() where it ends up being treated as a folio pointer with obvious results. Move the reset to 0 line outside the if, so udf_map_block() returns either 0 or -errno, even if the extent type is not EXT_RECORDED_ALLOCATED. Fixes: c226964ec786 ("udf: refactor inode_bmap() to handle error") Cc: stable@vger.kernel.org Assisted-by: LLM Tested-by: Xiang Mei Signed-off-by: Giorgi Kobakhia Link: https://patch.msgid.link/20260928200445.18722-1-gkobakhi@asu.edu Signed-off-by: Jan Kara --- fs/udf/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/udf/inode.c b/fs/udf/inode.c index 8babc2fbce9a10..bacf2a86161e6c 100644 --- a/fs/udf/inode.c +++ b/fs/udf/inode.c @@ -372,8 +372,8 @@ static int udf_map_block(struct inode *inode, struct udf_map_rq *map) map->pblk = udf_get_lb_pblock(inode->i_sb, &eloc, offset); map->oflags |= UDF_BLK_MAPPED; - ret = 0; } + ret = 0; out_read: up_read(&iinfo->i_data_sem); brelse(epos.bh); From 547948954f1ca78d3bb0b38201d205a16dc52d56 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Fri, 4 Sep 2026 09:31:09 +0000 Subject: [PATCH 1211/2286] rust: kbuild: add `no_io_statics` to `core-cfgs` In upstream Rust, PR #155625 moved `std::io::Error` into `core` under the `core_io` feature gate. To support decoding and formatting raw OS errors (`RawOsError`) from within `core` without depending on OS facilities, `core` introduced a global atomic pointer: static OS_FUNCTIONS: atomic::AtomicPtr At runtime, `std` registers its OS error vtable with this pointer via `set_functions()`, which uses `core::sync::atomic` to modify the value of this atomic variable. To ensure that `core` does not introduce usage of `core::sync::atomic` into the kernel image, set the `no_io_statics` cfg, which removes this logic from the build. Note that changing the `OS_FUNCTIONS` global is only possible via the unstable `from_raw_os_error_with_functions()` function from `core`, which is called by `std`. There is no way for us to invoke it from the kernel, as we do not use `std`. [ Pass `-Aunused_variables` to `core` to avoid: error: unused variable: `f` --> .../library/core/src/io/error/os_functions.rs:9:36 | 9 | pub(super) unsafe fn set_functions(f: &'static OsFunctions) { | ^ help: if this is intentional, prefix it with an underscore: `_f` | = note: `-D unused-variables` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(unused_variables)]` I sent a PR upstream [1] to clean it up and to add a test to check that `core` builds are warning-free with the `cfg` enabled, similar to what I added for other `cfg`s. Link: https://github.com/rust-lang/rust/pull/163633 [1] - Miguel ] Link: https://github.com/rust-lang/rust/pull/155625 Link: https://github.com/rust-lang/rust/issues/154046 Link: https://github.com/rust-lang/libs-team/issues/755 Signed-off-by: Alice Ryhl Reviewed-by: Gary Guo Link: https://patch.msgid.link/20260904-no-io-statics-v1-1-a2386eb0d255@google.com Signed-off-by: Miguel Ojeda --- rust/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/Makefile b/rust/Makefile index da1a7409d98452..152649f209d8ed 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -75,7 +75,8 @@ endif cfgs-to-flags = $(patsubst %,--cfg='%',$1) core-cfgs := \ - no_fp_fmt_parse + no_fp_fmt_parse \ + no_io_statics core-edition := $(if $(call rustc-min-version,108700),2024,2021) @@ -85,6 +86,7 @@ core-skip_flags := \ core-flags := \ --edition=$(core-edition) \ + -Aunused_variables \ $(call cfgs-to-flags,$(core-cfgs)) zerocopy-cfgs := \ From 9fde91b915ff10ab6b093140d78c3f47bf77e1b5 Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:50 -0700 Subject: [PATCH 1212/2286] x86/tdx: Take the Quote buffer as a generic pointer tdx_hcall_get_quote() takes a "u8 *" buffer unnecessarily. It is never used as such, since only the buffer's memory address matters. Accept a "void *" buffer instead. This avoids a cast to "u8 *" when a later change gives the guest driver's Quote buffer an explicit type. AI was used under supervision to collect/apply feedback, review code and workshop logs. Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Rick Edgecombe Link: https://patch.msgid.link/20260930103739.2851980-2-peter.fang@intel.com --- arch/x86/coco/tdx/tdx.c | 2 +- arch/x86/include/asm/tdx.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index a38e444018402e..92c24385117120 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -191,7 +191,7 @@ EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_extend_rtmr, "tdx-guest"); * * Return 0 on success or error code on failure. */ -u64 tdx_hcall_get_quote(u8 *buf, size_t size) +u64 tdx_hcall_get_quote(void *buf, size_t size) { /* Since buf is a shared memory, set the shared (decrypted) bits */ return _tdx_hypercall(TDVMCALL_GET_QUOTE, cc_mkdec(virt_to_phys(buf)), size, 0, 0); diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index e186dfe5bf8855..97bf6c8e0d89f6 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -82,7 +82,7 @@ int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport); int tdx_mcall_extend_rtmr(u8 index, u8 *data); -u64 tdx_hcall_get_quote(u8 *buf, size_t size); +u64 tdx_hcall_get_quote(void *buf, size_t size); void __init tdx_dump_attributes(u64 td_attr); void __init tdx_dump_td_ctls(u64 td_ctls); From 0d3fe9bad7c9028b23f753c216d4fdc0a1c76fa7 Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:51 -0700 Subject: [PATCH 1213/2286] virt: tdx-guest: Give the Quote buffer an explicit type The Quote buffer is always a "struct tdx_quote_buf", but its global pointer is a "void *". A function would have to convert it back to make sense of it. Declare the global with its actual type, and give it a better name. This creates variable shadowing in wait_for_quote_completion(), so rename its parameter for clarity. AI was used under supervision to collect/apply feedback, review code and workshop logs. Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Reviewed-by: Rick Edgecombe Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Binbin Wu Link: https://patch.msgid.link/20260930103739.2851980-3-peter.fang@intel.com --- drivers/virt/coco/tdx-guest/tdx-guest.c | 36 ++++++++++++------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index a21bd0376b74ad..83322cd7673a99 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -191,8 +191,7 @@ struct tdx_quote_buf { u8 data[]; }; -/* Quote data buffer */ -static void *quote_data; +static struct tdx_quote_buf *quote_buf; /* Lock to streamline quote requests */ static DEFINE_MUTEX(quote_lock); @@ -209,7 +208,7 @@ static long tdx_get_report0(struct tdx_report_req __user *req) USER_SOCKPTR(req->tdreport)); } -static void free_quote_buf(void *buf) +static void free_quote_buf(struct tdx_quote_buf *buf) { size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE); unsigned int count = len >> PAGE_SHIFT; @@ -222,25 +221,25 @@ static void free_quote_buf(void *buf) free_pages_exact(buf, len); } -static void *alloc_quote_buf(void) +static struct tdx_quote_buf *alloc_quote_buf(void) { size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE); unsigned int count = len >> PAGE_SHIFT; - void *addr; + struct tdx_quote_buf *buf; - addr = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO); - if (!addr) + buf = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO); + if (!buf) return NULL; - if (set_memory_decrypted((unsigned long)addr, count)) + if (set_memory_decrypted((unsigned long)buf, count)) return NULL; - return addr; + return buf; } /* * wait_for_quote_completion() - Wait for Quote request completion - * @quote_buf: Address of Quote buffer. + * @buf: Address of Quote buffer. * @timeout: Timeout in seconds to wait for the Quote generation. * * As per TDX GHCI v1.0 specification, sec titled "TDG.VP.VMCALL", @@ -249,7 +248,7 @@ static void *alloc_quote_buf(void) * or error code after processing is complete. So wait till the status * changes from GET_QUOTE_IN_FLIGHT or the request being timed out. */ -static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeout) +static int wait_for_quote_completion(struct tdx_quote_buf *buf, u32 timeout) { int i = 0; @@ -257,7 +256,7 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou * Quote requests usually take a few seconds to complete, so waking up * once per second to recheck the status is fine for this use case. */ - while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) { + while (buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) { if (msleep_interruptible(MSEC_PER_SEC)) return -EINTR; } @@ -268,7 +267,6 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou static int tdx_report_new_locked(struct tsm_report *report) { u8 *buf; - struct tdx_quote_buf *quote_buf = quote_data; struct tsm_report_desc *desc = &report->desc; u32 out_len; int ret; @@ -285,7 +283,7 @@ static int tdx_report_new_locked(struct tsm_report *report) if (desc->inblob_len != TDX_REPORTDATA_LEN) return -EINVAL; - memset(quote_data, 0, GET_QUOTE_BUF_SIZE); + memset(quote_buf, 0, GET_QUOTE_BUF_SIZE); /* Update Quote buffer header */ quote_buf->version = GET_QUOTE_CMD_VER; @@ -296,7 +294,7 @@ static int tdx_report_new_locked(struct tsm_report *report) if (ret) return ret; - err = tdx_hcall_get_quote(quote_data, GET_QUOTE_BUF_SIZE); + err = tdx_hcall_get_quote(quote_buf, GET_QUOTE_BUF_SIZE); if (err) { pr_err("GetQuote hypercall failed, status:%llx\n", err); return -EIO; @@ -417,8 +415,8 @@ static int __init tdx_guest_init(void) if (ret) goto deinit_mr; - quote_data = alloc_quote_buf(); - if (!quote_data) { + quote_buf = alloc_quote_buf(); + if (!quote_buf) { pr_err("Failed to allocate Quote buffer\n"); ret = -ENOMEM; goto free_misc; @@ -431,7 +429,7 @@ static int __init tdx_guest_init(void) return 0; free_quote: - free_quote_buf(quote_data); + free_quote_buf(quote_buf); free_misc: misc_deregister(&tdx_misc_dev); deinit_mr: @@ -444,7 +442,7 @@ module_init(tdx_guest_init); static void __exit tdx_guest_exit(void) { tsm_report_unregister(&tdx_tsm_ops); - free_quote_buf(quote_data); + free_quote_buf(quote_buf); misc_deregister(&tdx_misc_dev); tdx_mr_deinit(tdx_attr_groups[0]); } From 683cc6010d113bd19693add9150a77862b1231ff Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:52 -0700 Subject: [PATCH 1214/2286] virt: tdx-guest: Calculate the Quote buffer size safely struct tdx_quote_buf has a trailing flexible array member. struct_size() calculates the size of this kind of struct safely. It handles overflow, which helps since the Quote size comes from the host. Use it to rewrite the bounds check logic, since "header_size + data_size > buf_size" ... is more readable than "data_size > buf_size - header_size". This also prepares for a later change that needs the same "header_size + data_size" calculation for the Quote buffer size. AI was used under supervision to collect/apply feedback, review code and workshop logs. Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Tony Lindgren Reviewed-by: Xiaoyao Li Reviewed-by: Binbin Wu Reviewed-by: Kiryl Shutsemau (Meta) Reviewed-by: Rick Edgecombe Link: https://patch.msgid.link/20260930103739.2851980-4-peter.fang@intel.com --- drivers/virt/coco/tdx-guest/tdx-guest.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index 83322cd7673a99..77c63c3f820f14 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -170,8 +170,6 @@ static void tdx_mr_deinit(const struct attribute_group *mr_grp) #define GET_QUOTE_SUCCESS 0 #define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff -#define TDX_QUOTE_MAX_LEN (GET_QUOTE_BUF_SIZE - sizeof(struct tdx_quote_buf)) - /* struct tdx_quote_buf: Format of Quote request buffer. * @version: Quote format version, filled by TD. * @status: Status code of Quote request, filled by VMM. @@ -313,7 +311,7 @@ static int tdx_report_new_locked(struct tsm_report *report) out_len = READ_ONCE(quote_buf->out_len); - if (out_len > TDX_QUOTE_MAX_LEN) + if (struct_size(quote_buf, data, out_len) > GET_QUOTE_BUF_SIZE) return -EFBIG; buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL); From ef83bbc05ceb1df7d28e2cbb44ab3cd8584594de Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:53 -0700 Subject: [PATCH 1215/2286] virt: tdx-guest: Add a helper for the Quote buffer size The Quote buffer size is a constant sprinkled across several places. Read it from a helper instead. And rename the constant to avoid using a verb. This sets up the plumbing for a later change that makes the size dynamic. There are several "sizes" at play here: the raw Quote data, the Quote buffer (the header metadata plus the raw data), and the allocation (the page-padded Quote buffer). The helper returns the Quote buffer size and leaves page alignment to callers that need it. AI was used under supervision to collect/apply feedback, review code and workshop logs. Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Link: https://patch.msgid.link/20260930103739.2851980-5-peter.fang@intel.com --- drivers/virt/coco/tdx-guest/tdx-guest.c | 31 +++++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index 77c63c3f820f14..0cf078f09a73b5 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -162,7 +162,7 @@ static void tdx_mr_deinit(const struct attribute_group *mr_grp) * DICE-based attestation uses layered evidence that requires * larger Quote size (~100K). */ -#define GET_QUOTE_BUF_SIZE SZ_128K +#define TDX_DEFAULT_QUOTE_SIZE SZ_128K #define GET_QUOTE_CMD_VER 1 @@ -206,26 +206,36 @@ static long tdx_get_report0(struct tdx_report_req __user *req) USER_SOCKPTR(req->tdreport)); } +/* Size of the header metadata plus the largest possible raw Quote. */ +static size_t get_quote_buf_size(void) +{ + return TDX_DEFAULT_QUOTE_SIZE; +} + static void free_quote_buf(struct tdx_quote_buf *buf) { - size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE); - unsigned int count = len >> PAGE_SHIFT; + size_t alloc_size = PAGE_ALIGN(get_quote_buf_size()); + unsigned int count; + + count = alloc_size >> PAGE_SHIFT; if (set_memory_encrypted((unsigned long)buf, count)) { pr_err("Failed to restore encryption mask for Quote buffer, leak it\n"); return; } - free_pages_exact(buf, len); + free_pages_exact(buf, alloc_size); } static struct tdx_quote_buf *alloc_quote_buf(void) { - size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE); - unsigned int count = len >> PAGE_SHIFT; + size_t alloc_size = PAGE_ALIGN(get_quote_buf_size()); struct tdx_quote_buf *buf; + unsigned int count; + + count = alloc_size >> PAGE_SHIFT; - buf = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO); + buf = alloc_pages_exact(alloc_size, GFP_KERNEL | __GFP_ZERO); if (!buf) return NULL; @@ -266,6 +276,7 @@ static int tdx_report_new_locked(struct tsm_report *report) { u8 *buf; struct tsm_report_desc *desc = &report->desc; + size_t quote_buf_size = get_quote_buf_size(); u32 out_len; int ret; u64 err; @@ -281,7 +292,7 @@ static int tdx_report_new_locked(struct tsm_report *report) if (desc->inblob_len != TDX_REPORTDATA_LEN) return -EINVAL; - memset(quote_buf, 0, GET_QUOTE_BUF_SIZE); + memset(quote_buf, 0, quote_buf_size); /* Update Quote buffer header */ quote_buf->version = GET_QUOTE_CMD_VER; @@ -292,7 +303,7 @@ static int tdx_report_new_locked(struct tsm_report *report) if (ret) return ret; - err = tdx_hcall_get_quote(quote_buf, GET_QUOTE_BUF_SIZE); + err = tdx_hcall_get_quote(quote_buf, PAGE_ALIGN(quote_buf_size)); if (err) { pr_err("GetQuote hypercall failed, status:%llx\n", err); return -EIO; @@ -311,7 +322,7 @@ static int tdx_report_new_locked(struct tsm_report *report) out_len = READ_ONCE(quote_buf->out_len); - if (struct_size(quote_buf, data, out_len) > GET_QUOTE_BUF_SIZE) + if (struct_size(quote_buf, data, out_len) > quote_buf_size) return -EFBIG; buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL); From a2bf0ccd55b14a577e5bd16a245602687072b5fd Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:54 -0700 Subject: [PATCH 1216/2286] x86/tdx: Add a helper to query maximum Quote size Newer crypto algorithms can make Quotes larger, so guests can no longer rely on a fixed-size buffer. The TDX module added a new ABI that reports the largest possible Quote size via a metadata field [1]. Add a helper to query the size instead of exposing tdg_vm_rd() directly, as it can read arbitrary metadata fields. The reported size covers every Quote type the platform can produce, including SGX-based Quotes. AI was used under supervision to collect/apply feedback, review code and workshop logs. [1] Intel TDX Module ABI Definitions, August 2026, TD-scope metadata field "TD_QUOTE_MAX_SIZE" Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Reviewed-by: Rick Edgecombe Link: https://patch.msgid.link/20260930103739.2851980-6-peter.fang@intel.com --- arch/x86/coco/tdx/tdx.c | 16 ++++++++++++++++ arch/x86/include/asm/shared/tdx.h | 1 + arch/x86/include/asm/tdx.h | 2 ++ 3 files changed, 19 insertions(+) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index 92c24385117120..02c2871b99d1ad 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -198,6 +198,22 @@ u64 tdx_hcall_get_quote(void *buf, size_t size) } EXPORT_SYMBOL_FOR_MODULES(tdx_hcall_get_quote, "tdx-guest"); +/* + * Ask the TDX module what the largest Quote on the host platform might be. + */ +int tdx_get_max_quote_size(u64 *max_quote_size) +{ + u64 err; + + err = tdg_vm_rd(TDCS_QUOTE_MAX_SIZE, max_quote_size); + + /* Old modules do not support this. Tell the caller. */ + if (err) + return -EINVAL; + + return 0; +} + static void __noreturn tdx_panic(const char *msg) { struct tdx_module_args args = { diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h index f2cfa71cf0eab0..f1c5439580282e 100644 --- a/arch/x86/include/asm/shared/tdx.h +++ b/arch/x86/include/asm/shared/tdx.h @@ -50,6 +50,7 @@ /* TDX TD-Scope Metadata. To be used by TDG.VM.WR and TDG.VM.RD */ #define TDCS_CONFIG_FLAGS 0x1110000300000016 #define TDCS_TD_CTLS 0x1110000300000017 +#define TDCS_QUOTE_MAX_SIZE 0x9010000200000007 #define TDCS_NOTIFY_ENABLES 0x9100000000000010 #define TDCS_TOPOLOGY_ENUM_CONFIGURED 0x9100000000000019 diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 97bf6c8e0d89f6..1fcfaed515fec5 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -84,6 +84,8 @@ int tdx_mcall_extend_rtmr(u8 index, u8 *data); u64 tdx_hcall_get_quote(void *buf, size_t size); +int tdx_get_max_quote_size(u64 *max_quote_size); + void __init tdx_dump_attributes(u64 td_attr); void __init tdx_dump_td_ctls(u64 td_ctls); From 5b8212d45d99b77c84e3ad305a295e9e65d5ef20 Mon Sep 17 00:00:00 2001 From: Peter Fang Date: Wed, 30 Sep 2026 03:30:55 -0700 Subject: [PATCH 1217/2286] virt: tdx-guest: Make the Quote buffer size dynamic Support a new TDX module ABI that reports the Quote size limit in a metadata field. The fixed 128KB buffer in the driver may be too small for Quotes that use new algorithms like post-quantum cryptography (PQC), which can produce much larger certificates. With this ABI, the guest sizes the buffer to the platform's needs and no longer has to rely on some empirical number. The shared buffer comes from the buddy allocator, as the host expects it to be physically contiguous. The allocator's page order limit should be sufficient for current attestation needs. Platforms that don't report the limit fall back to the default 128KB buffer. Assume the TDX module always reports the same size. It is a TDX module bug if the size changes. AI was used under supervision to collect/apply feedback, review code and workshop logs. Based on a patch originally by Kuppuswamy Sathyanarayanan. Signed-off-by: Peter Fang Signed-off-by: Dave Hansen Reviewed-by: Rick Edgecombe Link: https://patch.msgid.link/20260930103739.2851980-7-peter.fang@intel.com --- arch/x86/coco/tdx/tdx.c | 1 + drivers/virt/coco/tdx-guest/tdx-guest.c | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c index 02c2871b99d1ad..ad489f2abcf18e 100644 --- a/arch/x86/coco/tdx/tdx.c +++ b/arch/x86/coco/tdx/tdx.c @@ -213,6 +213,7 @@ int tdx_get_max_quote_size(u64 *max_quote_size) return 0; } +EXPORT_SYMBOL_FOR_MODULES(tdx_get_max_quote_size, "tdx-guest"); static void __noreturn tdx_panic(const char *msg) { diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index 0cf078f09a73b5..11d741da3b35f5 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -209,7 +209,20 @@ static long tdx_get_report0(struct tdx_report_req __user *req) /* Size of the header metadata plus the largest possible raw Quote. */ static size_t get_quote_buf_size(void) { - return TDX_DEFAULT_QUOTE_SIZE; + size_t buf_size; + u64 max_size; + + /* Start with the default buffer size, which includes the header */ + buf_size = TDX_DEFAULT_QUOTE_SIZE; + + /* + * Override the default when the TDX module reports a size. Add room + * for the header metadata. The size is fixed during TD runtime. + */ + if (!tdx_get_max_quote_size(&max_size)) + buf_size = struct_size_t(struct tdx_quote_buf, data, max_size); + + return buf_size; } static void free_quote_buf(struct tdx_quote_buf *buf) From 27fececa00df55d9a8c49235751cddd166837dd7 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Wed, 30 Sep 2026 17:49:30 +0100 Subject: [PATCH 1218/2286] arm64: mm: Handle Granule Protection Faults (GPFs) If the host attempts to access granules that have been delegated to RMM (for use as an RMM object or Realm Data), these accesses will be caught and will trigger a Granule Protection Fault (GPF). A fault during a page walk signals a bug in the kernel and is handled by oopsing the kernel. A non-page walk fault could be caused by: * Userspace having access to a page which has been delegated. We don't allow mapping a delegated page (which may have Realm VM private data) to EL0. But if we do encounter this, trigger a SIGBUS to allow debugging * A kernel mode access is even more serious, except for the cases where : - Benign overreads e.g. load_unaligned_zeropad(), we should be able to fix this up. - A kdump kernel trying to access delegated page (donated by the primary kernel). We do not support this yet, but can be added in the later series. There is ongoing work to unmap the guest_memfd backed private pages from the linear map. We would additionally need to unmap the other delegated pages too. For now handle the GPF and only fixing up kernel mode accesses via kernel VA (which would cover both the legitimate cases above) Reviewed-by: Gavin Shan Signed-off-by: Steven Price Signed-off-by: Suzuki K Poulose Reviewed-by: Catalin Marinas Signed-off-by: Catalin Marinas --- arch/arm64/mm/fault.c | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 75c3e463df2ef2..ded9288a5dd2f1 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -914,6 +914,29 @@ static int do_tag_check_fault(unsigned long far, unsigned long esr, return 0; } +static int do_gpf_ptw(unsigned long far, unsigned long esr, struct pt_regs *regs) +{ + const struct fault_info *inf = esr_to_fault_info(esr); + unsigned long addr = untagged_addr(far); + + die_kernel_fault(inf->name, addr, esr, regs); + return 0; +} + +static int do_gpf(unsigned long far, unsigned long esr, struct pt_regs *regs) +{ + /* + * Userspace must not have a delegated page mapped in. If the kernel + * is made to access it, then we have a serious problem. + * Only fixup if the access came via kernel VA. e.g., load_unaligned_zeropad() + */ + if (!user_mode(regs) && !is_el1_instruction_abort(esr) && + !is_ttbr0_addr(untagged_addr(far)) && fixup_exception(regs, esr)) + return 0; + + return 1; +} + static const struct fault_info fault_info[] = { { do_bad, SIGKILL, SI_KERNEL, "ttbr address size fault" }, { do_bad, SIGKILL, SI_KERNEL, "level 1 address size fault" }, @@ -950,12 +973,12 @@ static const struct fault_info fault_info[] = { { do_bad, SIGKILL, SI_KERNEL, "unknown 32" }, { do_alignment_fault, SIGBUS, BUS_ADRALN, "alignment fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 34" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 35" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 36" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 37" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 38" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 39" }, - { do_bad, SIGKILL, SI_KERNEL, "unknown 40" }, + { do_gpf_ptw, SIGKILL, SI_KERNEL, "level -1 granule protection fault (translation table walk)" }, + { do_gpf_ptw, SIGKILL, SI_KERNEL, "level 0 granule protection fault (translation table walk)" }, + { do_gpf_ptw, SIGKILL, SI_KERNEL, "level 1 granule protection fault (translation table walk)" }, + { do_gpf_ptw, SIGKILL, SI_KERNEL, "level 2 granule protection fault (translation table walk)" }, + { do_gpf_ptw, SIGKILL, SI_KERNEL, "level 3 granule protection fault (translation table walk)" }, + { do_gpf, SIGBUS, BUS_OBJERR, "granule protection fault" }, { do_bad, SIGKILL, SI_KERNEL, "level -1 address size fault" }, { do_bad, SIGKILL, SI_KERNEL, "unknown 42" }, { do_translation_fault, SIGSEGV, SEGV_MAPERR, "level -1 translation fault" }, From 9e201043cbd2194299c7d1c7ba0d8e9dbb04e79e Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:14:57 +0100 Subject: [PATCH 1219/2286] firmware: arm_rmm: Add SMC definitions for calling the RMM The RMM (Realm Management Monitor) provides functionality that can be accessed by SMC calls from the host. The SMC definitions are based on DEN0137[1] version 2.0-bet3 [1] https://developer.arm.com/documentation/den0137/2-0bet3/ Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Tested-by: Gavin Shan Acked-by: Catalin Marinas Signed-off-by: Steven Price Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- MAINTAINERS | 1 + include/linux/arm-smccc-rmi.h | 516 ++++++++++++++++++++++++++++++++++ 2 files changed, 517 insertions(+) create mode 100644 include/linux/arm-smccc-rmi.h diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b34..16314a0193d226 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3953,6 +3953,7 @@ F: Documentation/arch/arm64/ F: arch/arm64/ F: drivers/virt/coco/arm-cca-guest/ F: drivers/virt/coco/pkvm-guest/ +F: include/linux/arm-smccc-rmi.h F: tools/testing/selftests/arm64/ X: arch/arm64/boot/dts/ X: arch/arm64/configs/defconfig diff --git a/include/linux/arm-smccc-rmi.h b/include/linux/arm-smccc-rmi.h new file mode 100644 index 00000000000000..655726b3746241 --- /dev/null +++ b/include/linux/arm-smccc-rmi.h @@ -0,0 +1,516 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2026 ARM Ltd. + * + * The values and structures in this file are from the Realm Management Monitor + * specification (DEN0137) version 2.0-bet3: + * https://developer.arm.com/documentation/den0137/2-0bet3/ + */ + +#ifndef __LINUX_ARM_SMCCC_RMI_H_ +#define __LINUX_ARM_SMCCC_RMI_H_ + +#include +#include +#include +#include +#include +#include + +#include + +#define SMC_RMI_CALL(func) \ + ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, \ + ARM_SMCCC_SMC_64, \ + ARM_SMCCC_OWNER_STANDARD, \ + (func)) + +#define SMC_RMI_VERSION SMC_RMI_CALL(0x0150) + +#define SMC_RMI_RTT_DATA_MAP_INIT SMC_RMI_CALL(0x0153) + +#define SMC_RMI_REALM_ACTIVATE SMC_RMI_CALL(0x0157) +#define SMC_RMI_REALM_CREATE SMC_RMI_CALL(0x0158) +#define SMC_RMI_REALM_DESTROY SMC_RMI_CALL(0x0159) +#define SMC_RMI_REC_CREATE SMC_RMI_CALL(0x015a) +#define SMC_RMI_REC_DESTROY SMC_RMI_CALL(0x015b) +#define SMC_RMI_REC_ENTER SMC_RMI_CALL(0x015c) +#define SMC_RMI_RTT_CREATE SMC_RMI_CALL(0x015d) +#define SMC_RMI_RTT_DESTROY SMC_RMI_CALL(0x015e) + +#define SMC_RMI_RTT_READ_ENTRY SMC_RMI_CALL(0x0161) + +#define SMC_RMI_RTT_DEV_VALIDATE SMC_RMI_CALL(0x0163) +#define SMC_RMI_PSCI_COMPLETE SMC_RMI_CALL(0x0164) +#define SMC_RMI_FEATURES SMC_RMI_CALL(0x0165) +#define SMC_RMI_RTT_FOLD SMC_RMI_CALL(0x0166) + +#define SMC_RMI_RTT_INIT_RIPAS SMC_RMI_CALL(0x0168) +#define SMC_RMI_RTT_SET_RIPAS SMC_RMI_CALL(0x0169) +#define SMC_RMI_VSMMU_CREATE SMC_RMI_CALL(0x016a) +#define SMC_RMI_VSMMU_DESTROY SMC_RMI_CALL(0x016b) + +#define SMC_RMI_RMM_CONFIG_SET SMC_RMI_CALL(0x016e) +#define SMC_RMI_PSMMU_IRQ_NOTIFY SMC_RMI_CALL(0x016f) +#define SMC_RMI_ATTEST_PLAT_TOKEN_REFRESH SMC_RMI_CALL(0x0170) + +#define SMC_RMI_PDEV_ABORT SMC_RMI_CALL(0x0174) +#define SMC_RMI_PDEV_COMMUNICATE SMC_RMI_CALL(0x0175) +#define SMC_RMI_PDEV_CREATE SMC_RMI_CALL(0x0176) +#define SMC_RMI_PDEV_DESTROY SMC_RMI_CALL(0x0177) +#define SMC_RMI_PDEV_GET_STATE SMC_RMI_CALL(0x0178) + +#define SMC_RMI_PDEV_STREAM_KEY_REFRESH SMC_RMI_CALL(0x017a) +#define SMC_RMI_PDEV_SET_PUBKEY SMC_RMI_CALL(0x017b) +#define SMC_RMI_PDEV_STOP SMC_RMI_CALL(0x017c) +#define SMC_RMI_RTT_AUX_CREATE SMC_RMI_CALL(0x017d) +#define SMC_RMI_RTT_AUX_DESTROY SMC_RMI_CALL(0x017e) +#define SMC_RMI_RTT_AUX_FOLD SMC_RMI_CALL(0x017f) + +#define SMC_RMI_VDEV_ABORT SMC_RMI_CALL(0x0185) +#define SMC_RMI_VDEV_COMMUNICATE SMC_RMI_CALL(0x0186) +#define SMC_RMI_VDEV_CREATE SMC_RMI_CALL(0x0187) +#define SMC_RMI_VDEV_DESTROY SMC_RMI_CALL(0x0188) +#define SMC_RMI_VDEV_GET_STATE SMC_RMI_CALL(0x0189) +#define SMC_RMI_VDEV_UNLOCK SMC_RMI_CALL(0x018a) +#define SMC_RMI_RTT_SET_S2AP SMC_RMI_CALL(0x018b) + +#define SMC_RMI_VDEV_GET_INTERFACE_REPORT SMC_RMI_CALL(0x01d0) +#define SMC_RMI_VDEV_GET_MEASUREMENTS SMC_RMI_CALL(0x01d1) +#define SMC_RMI_VDEV_LOCK SMC_RMI_CALL(0x01d2) +#define SMC_RMI_VDEV_START SMC_RMI_CALL(0x01d3) + +#define SMC_RMI_VSMMU_EVENT_HANDLE SMC_RMI_CALL(0x01d6) +#define SMC_RMI_PSMMU_ACTIVATE SMC_RMI_CALL(0x01d7) +#define SMC_RMI_PSMMU_DEACTIVATE SMC_RMI_CALL(0x01d8) + +#define SMC_RMI_PSMMU_ST_L2_CREATE SMC_RMI_CALL(0x01db) +#define SMC_RMI_PSMMU_ST_L2_DESTROY SMC_RMI_CALL(0x01dc) +#define SMC_RMI_DPT_L0_CREATE SMC_RMI_CALL(0x01dd) +#define SMC_RMI_DPT_L0_DESTROY SMC_RMI_CALL(0x01de) +#define SMC_RMI_DPT_L1_CREATE SMC_RMI_CALL(0x01df) +#define SMC_RMI_DPT_L1_DESTROY SMC_RMI_CALL(0x01e0) +#define SMC_RMI_GRANULE_TRACKING_GET SMC_RMI_CALL(0x01e1) + +#define SMC_RMI_GRANULE_TRACKING_SET SMC_RMI_CALL(0x01e3) + +#define SMC_RMI_RMM_CONFIG_GET SMC_RMI_CALL(0x01ec) + +#define SMC_RMI_RMM_STATE_GET SMC_RMI_CALL(0x01ee) + +#define SMC_RMI_PSMMU_EVENT_CONSUME SMC_RMI_CALL(0x01f0) +#define SMC_RMI_GRANULE_RANGE_DELEGATE SMC_RMI_CALL(0x01f1) +#define SMC_RMI_GRANULE_RANGE_UNDELEGATE SMC_RMI_CALL(0x01f2) +#define SMC_RMI_GPT_L1_CREATE SMC_RMI_CALL(0x01f3) +#define SMC_RMI_GPT_L1_DESTROY SMC_RMI_CALL(0x01f4) +#define SMC_RMI_RTT_DATA_MAP SMC_RMI_CALL(0x01f5) +#define SMC_RMI_RTT_DATA_UNMAP SMC_RMI_CALL(0x01f6) +#define SMC_RMI_RTT_DEV_MAP SMC_RMI_CALL(0x01f7) +#define SMC_RMI_RTT_DEV_UNMAP SMC_RMI_CALL(0x01f8) +#define SMC_RMI_RTT_ARCH_DEV_MAP SMC_RMI_CALL(0x01f9) +#define SMC_RMI_RTT_ARCH_DEV_UNMAP SMC_RMI_CALL(0x01fa) +#define SMC_RMI_RTT_UNPROT_MAP SMC_RMI_CALL(0x01fb) +#define SMC_RMI_RTT_UNPROT_UNMAP SMC_RMI_CALL(0x01fc) +#define SMC_RMI_RTT_AUX_PROT_MAP SMC_RMI_CALL(0x01fd) +#define SMC_RMI_RTT_AUX_PROT_UNMAP SMC_RMI_CALL(0x01fe) +#define SMC_RMI_RTT_AUX_UNPROT_MAP SMC_RMI_CALL(0x01ff) +#define SMC_RMI_RTT_AUX_UNPROT_UNMAP SMC_RMI_CALL(0x0200) +#define SMC_RMI_REALM_TERMINATE SMC_RMI_CALL(0x0201) +#define SMC_RMI_RMM_ACTIVATE SMC_RMI_CALL(0x0202) +#define SMC_RMI_OP_CONTINUE SMC_RMI_CALL(0x0203) +#define SMC_RMI_PDEV_STREAM_CONNECT SMC_RMI_CALL(0x0204) +#define SMC_RMI_PDEV_STREAM_DISCONNECT SMC_RMI_CALL(0x0205) +#define SMC_RMI_PDEV_STREAM_COMPLETE SMC_RMI_CALL(0x0206) +#define SMC_RMI_PDEV_STREAM_KEY_PURGE SMC_RMI_CALL(0x0207) +#define SMC_RMI_OP_MEM_DONATE SMC_RMI_CALL(0x0208) +#define SMC_RMI_OP_MEM_RECLAIM SMC_RMI_CALL(0x0209) +#define SMC_RMI_OP_CANCEL SMC_RMI_CALL(0x020a) +#define SMC_RMI_VSMMU_FEATURES SMC_RMI_CALL(0x020b) +#define SMC_RMI_VSMMU_CMD_GET SMC_RMI_CALL(0x020c) +#define SMC_RMI_VSMMU_CMD_COMPLETE SMC_RMI_CALL(0x020d) +#define SMC_RMI_PSMMU_INFO SMC_RMI_CALL(0x020e) +#define SMC_RMI_RMM_DEACTIVATE SMC_RMI_CALL(0x020f) +#define SMC_RMI_PDEV_STREAM_INFO SMC_RMI_CALL(0x0210) +#define SMC_RMI_GPT_INFO SMC_RMI_CALL(0x0211) + +#define RMI_ABI_MAJOR_VERSION 2 +#define RMI_ABI_MINOR_VERSION 0 + +#define RMI_ABI_VERSION_MAJOR_MASK GENMASK(30, 16) +#define RMI_ABI_VERSION_MINOR_MASK GENMASK(15, 0) + +#define RMI_ABI_VERSION_GET_MAJOR(v) FIELD_GET(RMI_ABI_VERSION_MAJOR_MASK, (v)) +#define RMI_ABI_VERSION_GET_MINOR(v) FIELD_GET(RMI_ABI_VERSION_MINOR_MASK, (v)) +#define RMI_ABI_VERSION(major, minor) \ + (FIELD_PREP(RMI_ABI_VERSION_MAJOR_MASK, (major)) | \ + FIELD_PREP(RMI_ABI_VERSION_MINOR_MASK, (minor))) + +/* RmiResult Type definitions */ +#define RMI_RESULT_STATUS_MASK GENMASK(7, 0) +/* RmiResultDataLevel field */ +#define RMI_RESULT_DATA_LEVEL_MASK GENMASK(15, 8) +/* RmiResultDataIncomplete fields */ +#define RMI_RESULT_MEMREQ_MASK GENMASK(9, 8) +#define RMI_RESULT_CAN_CANCEL_MASK BIT(10) + +#define RMI_RESULT_STATUS(ret) FIELD_GET(RMI_RESULT_STATUS_MASK, ret) +#define RMI_RESULT_DATA_LEVEL(ret) FIELD_GET(RMI_RESULT_DATA_LEVEL_MASK, ret) +#define RMI_RESULT_MEMREQ(ret) FIELD_GET(RMI_RESULT_MEMREQ_MASK, ret) +#define RMI_RESULT_CAN_CANCEL(ret) FIELD_GET(RMI_RESULT_CAN_CANCEL_MASK, ret) + +#define RMI_SUCCESS 0 +#define RMI_ERROR_INPUT 1 +#define RMI_ERROR_REALM 2 +#define RMI_ERROR_REC 3 +#define RMI_ERROR_RTT 4 +#define RMI_ERROR_NOT_SUPPORTED 5 +#define RMI_ERROR_DEVICE 6 +#define RMI_ERROR_RTT_AUX 7 +#define RMI_ERROR_PSMMU_ST 8 +#define RMI_ERROR_DPT 9 +#define RMI_BUSY 10 +#define RMI_ERROR_GLOBAL 11 +#define RMI_ERROR_TRACKING 12 +#define RMI_INCOMPLETE 13 +#define RMI_BLOCKED 14 +#define RMI_ERROR_GPT 15 +#define RMI_ERROR_GRANULE 16 + +#define RMI_CONTINUE_KEEP_GOING 0 +#define RMI_CONTINUE_STOP 1 + +#define RMI_OP_MEM_REQ_NONE 0 +#define RMI_OP_MEM_REQ_DONATE 1 +#define RMI_OP_MEM_REQ_RECLAIM 2 + +#define RMI_OP_CANNOT_CANCEL 0 +#define RMI_OP_CAN_CANCEL 1 + +#define RMI_DONATE_STATE_MASK GENMASK(18, 17) +#define RMI_DONATE_CONTIG_MASK BIT(16) +#define RMI_DONATE_COUNT_MASK GENMASK(15, 2) +#define RMI_DONATE_BLOCK_SIZE_MASK GENMASK(1, 0) + +#define RMI_DONATE_STATE(req) FIELD_GET(RMI_DONATE_STATE_MASK, req) +#define RMI_DONATE_CONTIG(req) FIELD_GET(RMI_DONATE_CONTIG_MASK, req) +#define RMI_DONATE_COUNT(req) FIELD_GET(RMI_DONATE_COUNT_MASK, req) +#define RMI_DONATE_BLOCK_SIZE(req) FIELD_GET(RMI_DONATE_BLOCK_SIZE_MASK, req) + +#define RMI_OP_MEM_DELEGATED 0 +#define RMI_OP_MEM_UNDELEGATED 1 +#define RMI_OP_MEM_CONDITIONAL 2 + +#define RMI_OP_MEM_NON_CONTIG 0 +#define RMI_OP_MEM_CONTIG 1 + +#define RMI_ADDR_TYPE_NONE 0 +#define RMI_ADDR_TYPE_SINGLE 1 +#define RMI_ADDR_TYPE_LIST 2 + +#define RMI_ADDR_RANGE_STATE_MASK GENMASK_ULL(63, 62) +#define RMI_ADDR_RANGE_ADDR_MASK GENMASK_ULL(51, PAGE_SHIFT) +#define RMI_ADDR_RANGE_COUNT_MASK GENMASK(PAGE_SHIFT - 1, 2) +#define RMI_ADDR_RANGE_BLOCK_SIZE_MASK GENMASK(1, 0) + +#define RMI_ADDR_RANGE_STATE(r) FIELD_GET(RMI_ADDR_RANGE_STATE_MASK, (r)) +#define RMI_ADDR_RANGE_ADDR(r) ((r) & RMI_ADDR_RANGE_ADDR_MASK) +#define RMI_ADDR_RANGE_COUNT(r) FIELD_GET(RMI_ADDR_RANGE_COUNT_MASK, (r)) +#define RMI_ADDR_RANGE_BLOCK_SIZE(r) FIELD_GET(RMI_ADDR_RANGE_BLOCK_SIZE_MASK, (r)) + +enum rmi_ripas { + RMI_EMPTY = 0, + RMI_RAM = 1, + RMI_DESTROYED = 2, + RMI_DEV = 3, +}; + +#define RMI_NO_MEASURE_CONTENT 0 +#define RMI_MEASURE_CONTENT 1 + +#define RMI_FEATURE_REGISTER_0_S2OASZ GENMASK_ULL(40, 33) +#define RMI_FEATURE_REGISTER_0_L0GPT_BLOCK_DELEGATE BIT_ULL(32) +#define RMI_FEATURE_REGISTER_0_PMU_NUM_CTRS GENMASK(31, 27) +#define RMI_FEATURE_REGISTER_0_PMU BIT(26) +#define RMI_FEATURE_REGISTER_0_NUM_WPS GENMASK(25, 20) +#define RMI_FEATURE_REGISTER_0_NUM_BPS GENMASK(19, 14) +#define RMI_FEATURE_REGISTER_0_SVE_VL GENMASK(13, 10) +#define RMI_FEATURE_REGISTER_0_SVE BIT(9) +#define RMI_FEATURE_REGISTER_0_LPA2 BIT(8) +#define RMI_FEATURE_REGISTER_0_S2SZ GENMASK(7, 0) + +#define RMI_FEATURE_REGISTER_1_PPS GENMASK(16, 14) +#define RMI_FEATURE_REGISTER_1_L0GPTSZ GENMASK(13, 10) +#define RMI_FEATURE_REGISTER_1_MAX_RECS_ORDER GENMASK(9, 6) +#define RMI_FEATURE_REGISTER_1_HASH_SHA_512 BIT(5) +#define RMI_FEATURE_REGISTER_1_HASH_SHA_384 BIT(4) +#define RMI_FEATURE_REGISTER_1_HASH_SHA_256 BIT(3) +#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_64KB BIT(2) +#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_16KB BIT(1) +#define RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_4KB BIT(0) + +#define RMI_FEATURE_REGISTER_2_REALM_MAX_VDEVS_ORDER GENMASK(14, 10) +#define RMI_FEATURE_REGISTER_2_NON_TEE_STREAM BIT(9) +#define RMI_FEATURE_REGISTER_2_VDEV_KROU BIT(8) +#define RMI_FEATURE_REGISTER_2_PDEV_MAX_VDEVS_ORDER GENMASK(7, 4) +#define RMI_FEATURE_REGISTER_2_ATS BIT(3) +#define RMI_FEATURE_REGISTER_2_VSMMU BIT(2) +#define RMI_FEATURE_REGISTER_2_DA_COH BIT(1) +#define RMI_FEATURE_REGISTER_2_DA BIT(0) + +#define RMI_FEATURE_REGISTER_3_RTT_S2AP_INDIRECT BIT(6) +#define RMI_FEATURE_REGISTER_3_RTT_PLANE GENMASK(5, 4) +#define RMI_FEATURE_REGISTER_3_MAX_NUM_AUX_PLANES GENMASK(3, 0) + +#define RMI_FEATURE_REGISTER_4_MEC_COUNT GENMASK_ULL(63, 0) + +#define RMI_MEM_CATEGORY_CONVENTIONAL 0 +#define RMI_MEM_CATEGORY_DEV_NCOH 1 +#define RMI_MEM_CATEGORY_DEV_COH 2 +#define RMI_MEM_CATEGORY_NONE 3 + +#define RMI_TRACKING_RESERVED 0 +#define RMI_TRACKING_NONE 1 +#define RMI_TRACKING_FINE 2 +#define RMI_TRACKING_COARSE 3 +#define RMI_TRACKING_INTERMEDIATE 4 + +#define RMI_GRANULE_SIZE_4KB 0 +#define RMI_GRANULE_SIZE_16KB 1 +#define RMI_GRANULE_SIZE_64KB 2 + +#define RMI_GPT_PAR_RESERVED 0U +#define RMI_GPT_PAR_PLAT 1U +#define RMI_GPT_PAR_HOST_NOT_CREATED 2U +#define RMI_GPT_PAR_HOST_CREATED 3U + +/* + * Note many of these fields are smaller than u64 but all fields have u64 + * alignment, so use u64 to ensure correct alignment. + */ +struct rmm_config { + union { /* 0x0 */ + struct { + u64 tracking_region_size; + u64 rmi_granule_size; + }; + u8 sizer[SZ_4K]; + }; +}; + +static_assert(sizeof(struct rmm_config) == SZ_4K); + +#define RMI_REALM_PARAM_FLAG_MEC_POLICY_MASK GENMASK(8, 7) +#define RMI_REALM_PARAM_FLAG_LFA_POLICY_MASK GENMASK(6, 5) +#define RMI_REALM_PARAM_FLAG_DA BIT(3) +#define RMI_REALM_PARAM_FLAG_PMU BIT(2) +#define RMI_REALM_PARAM_FLAG_SVE BIT(1) + +#define RMI_HASH_SHA_256 0 +#define RMI_HASH_SHA_512 1 +#define RMI_HASH_SHA_384 2 + +struct realm_params { + union { /* 0x0 */ + struct { + u64 flags0; + u64 s2sz; + u64 sve_vl; + u64 num_bps; + u64 num_wps; + u64 pmu_num_ctrs; + u64 hash_algo; + u64 num_aux_planes; + }; + u8 sizer0[0x400]; + }; + union { /* 0x400 */ + struct { + u8 rpv[64]; + u64 ats_plane; + }; + u8 sizer1[0x400]; + }; + union { /* 0x800 */ + struct { + u64 sizer2; + u64 rtt_base; + s64 rtt_level_start; + u64 rtt_num_start; + u64 flags1; + u64 max_num_vdevs; + }; + u8 sizer3[0x700]; + }; + union { /* 0xf00 */ + struct { + u8 sizer4[0x80]; + u64 aux_rtt_base[3]; + }; + u8 sizer5[0x100]; + }; +}; + +static_assert(offsetof(struct realm_params, rpv) == 0x400); +static_assert(offsetof(struct realm_params, rtt_base) == 0x808); +static_assert(offsetof(struct realm_params, aux_rtt_base) == 0xf80); +static_assert(sizeof(struct realm_params) == SZ_4K); + +/* + * The number of GPRs (starting from X0) that are configured by the host when + * a REC is created. + */ +#define REC_CREATE_NR_GPRS 8 + +#define REC_PARAMS_FLAG_RUNNABLE BIT(0) + +struct rec_params { + union { /* 0x0 */ + u64 flags; + u8 sizer0[0x100]; + }; + union { /* 0x100 */ + u64 mpidr; + u8 sizer1[0x100]; + }; + union { /* 0x200 */ + u64 pc; + u8 sizer2[0x100]; + }; + union { /* 0x300 */ + u64 gprs[REC_CREATE_NR_GPRS]; + u8 sizer3[0xd00]; + }; +}; + +static_assert(offsetof(struct rec_params, mpidr) == 0x100); +static_assert(offsetof(struct rec_params, pc) == 0x200); +static_assert(offsetof(struct rec_params, gprs) == 0x300); +static_assert(sizeof(struct rec_params) == SZ_4K); + +#define REC_ENTER_FLAG_FORCE_P0 BIT(7) +#define REC_ENTER_FLAG_DEV_MEM_RESPONSE BIT(6) +#define REC_ENTER_FLAG_S2AP_RESPONSE BIT(5) +#define REC_ENTER_FLAG_RIPAS_RESPONSE BIT(4) +#define REC_ENTER_FLAG_TRAP_WFE BIT(3) +#define REC_ENTER_FLAG_TRAP_WFI BIT(2) +#define REC_ENTER_FLAG_INJECT_SEA BIT(1) +#define REC_ENTER_FLAG_EMULATED_MMIO BIT(0) + +#define REC_RUN_GPRS 31 + +struct rec_enter { + union { /* 0x000 */ + u64 flags; + u8 sizer0[0x200]; + }; + union { /* 0x200 */ + u64 gprs[REC_RUN_GPRS]; + u8 sizer1[0x600]; + }; +}; + +static_assert(offsetof(struct rec_enter, gprs) == 0x200); +static_assert(sizeof(struct rec_enter) == SZ_2K); + +#define RMI_EXIT_SYNC 0x00 +#define RMI_EXIT_IRQ 0x01 +#define RMI_EXIT_FIQ 0x02 +#define RMI_EXIT_PSCI 0x03 +#define RMI_EXIT_RIPAS_CHANGE 0x04 +#define RMI_EXIT_HOST_CALL 0x05 +#define RMI_EXIT_SERROR 0x06 +#define RMI_EXIT_S2AP_CHANGE 0x07 +#define RMI_EXIT_VDEV_VALIDATE_MAPPING 0x08 +#define RMI_EXIT_VSMMU_COMMAND 0x0a + +struct rec_exit { + union { /* 0x000 */ + u8 exit_reason; + u8 sizer0[0x100]; + }; + union { /* 0x100 */ + struct { + u64 esr; + u64 far; + u64 hpfar; + u64 rtt_tree; + }; + u8 sizer1[0x100]; + }; + union { /* 0x200 */ + u64 gprs[REC_RUN_GPRS]; + u8 sizer2[0x200]; + }; + union { /* 0x400 */ + struct { + u64 cntp_ctl; + u64 cntp_cval; + u64 cntv_ctl; + u64 cntv_cval; + }; + u8 sizer4[0x100]; + }; + union { /* 0x500 */ + struct { + u64 ripas_base; + u64 ripas_top; + u8 ripas_value; + u8 sizer5[0xf]; + u64 s2ap_base; + u64 s2ap_top; + u64 vdev_id_1; + u64 vdev_id_2; + u64 dev_mem_base; + u64 dev_mem_top; + u64 dev_mem_pa; + }; + u8 sizer6[0x100]; + }; + union { /* 0x600 */ + struct { + u16 imm; + u8 sizer7[0x6]; + u64 plane; + }; + u8 sizer8[0x100]; + }; + union { /* 0x700 */ + struct { + u8 pmu_ovf_status; + u8 sizer9[0xf]; + u64 vsmmu; + }; + u8 sizer10[0x100]; + }; +}; + +static_assert(sizeof(struct rec_exit) == SZ_2K); +static_assert(offsetof(struct rec_exit, esr) == 0x100); +static_assert(offsetof(struct rec_exit, gprs) == 0x200); +static_assert(offsetof(struct rec_exit, cntp_ctl) == 0x400); +static_assert(offsetof(struct rec_exit, ripas_base) == 0x500); +static_assert(offsetof(struct rec_exit, imm) == 0x600); +static_assert(offsetof(struct rec_exit, pmu_ovf_status) == 0x700); + +struct rec_run { + struct rec_enter enter; + struct rec_exit exit; +}; + +static_assert(sizeof(struct rec_run) == SZ_4K); + +/* RMI_RTT_UNPROT_MAP_FLAGS definitions */ +#define RMI_RTT_UNPROT_MAP_FLAGS_S2AP_MASK GENMASK(22, 19) +#define RMI_RTT_UNPROT_MAP_FLAGS_MEMATTR_MASK GENMASK(18, 16) +#define RMI_RTT_UNPROT_MAP_FLAGS_LIST_COUNT_MASK GENMASK(15, 2) +#define RMI_RTT_UNPROT_MAP_FLAGS_OADDR_TYPE_MASK GENMASK(1, 0) + +/* RMI_RTT_PROT_MAP_FLAGS definitions */ +#define RMI_RTT_PROT_MAP_FLAGS_LIST_COUNT_MASK GENMASK(15, 2) +#define RMI_RTT_PROT_MAP_FLAGS_OADDR_TYPE_MASK GENMASK(1, 0) + +/* S2AP Direct Encodings, used in RMI_RTT_UNPROT_MAP_FLAGS_S2AP */ +#define RMI_S2AP_DIRECT_READ BIT(1) +#define RMI_S2AP_DIRECT_WRITE BIT(0) + +#endif /* __LINUX_ARM_SMCCC_RMI_H_ */ From 63b3495c3d663eab2316e926583e2248679801b1 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:06 +0100 Subject: [PATCH 1220/2286] arm_mpam: Move MPAMF_ECR write helpers to allow reuse In preparation for calling mpam_enable_msc_ecr() from the CPU hotplug handlers move it higher in the file. Move mpam_disable_msc_ecr() too to keep them together. Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index dd422c56fbb147..580d7ed38569b1 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1817,6 +1817,25 @@ static void mpam_reprogram_msc(struct mpam_msc *msc) mutex_unlock(&msc->cfg_lock); } +static int mpam_enable_msc_ecr(void *_msc) +{ + struct mpam_msc *msc = _msc; + + __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN); + + return 0; +} + +/* This can run in mpam_disable(), and the interrupt handler on the same CPU */ +static int mpam_disable_msc_ecr(void *_msc) +{ + struct mpam_msc *msc = _msc; + + __mpam_write_reg(msc, MPAMF_ECR, 0); + + return 0; +} + static void _enable_percpu_irq(void *_irq) { int *irq = _irq; @@ -2443,25 +2462,6 @@ static char *mpam_errcode_names[16] = { [12 ... 15] = "Reserved" }; -static int mpam_enable_msc_ecr(void *_msc) -{ - struct mpam_msc *msc = _msc; - - __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN); - - return 0; -} - -/* This can run in mpam_disable(), and the interrupt handler on the same CPU */ -static int mpam_disable_msc_ecr(void *_msc) -{ - struct mpam_msc *msc = _msc; - - __mpam_write_reg(msc, MPAMF_ECR, 0); - - return 0; -} - static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc) { u64 reg; From 9ced048825c7920ca235ec382658b1d87709c5b1 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:07 +0100 Subject: [PATCH 1221/2286] arm_mpam: Restore the error interrupt enable from mpam_cpu_online() When all the associated CPUs to an MSC are offline, possibly caused by PSCI_CPU_OFF, the power supply to the MSC can be cut off and all the registers lose their value, including MPAMF_ECR.INTEN. Afterwards, we need to explicitly set MPAMF_ECR.INTEN when the first associated CPU becomes online and the power supply to the MSC is recovered. Restore MPAMF_ECR.INTEN in mpam_cpu_online() when the first associated CPU of the MSC becomes online. Fixes: 49aa621c4dca ("arm_mpam: Register and enable IRQs") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse [morse: rephrased commit message] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 580d7ed38569b1..32bde94dfbc374 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1856,8 +1856,14 @@ static int mpam_cpu_online(unsigned int cpu) if (msc->reenable_error_ppi) _enable_percpu_irq(&msc->reenable_error_ppi); - if (atomic_fetch_inc(&msc->online_refs) == 0) + if (atomic_fetch_inc(&msc->online_refs) == 0) { + mutex_lock(&msc->error_irq_lock); + if (msc->error_irq_hw_enabled) + mpam_touch_msc(msc, mpam_enable_msc_ecr, msc); + mutex_unlock(&msc->error_irq_lock); + mpam_reprogram_msc(msc); + } } if (mpam_resctrl_enabled) From b010d258f30a512a9eafaba6c50a0943f8420ab2 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:08 +0100 Subject: [PATCH 1222/2286] arm_mpam: Set mpam_feat_msmon_mbwu_31counter when there are bandwidth counters When MPAMF_MSMON_IDR.MSMON_MBWU is 1, MSMON_MBWU is present and has a VALUE field of 31 bits. If additionally, MPAMF_MBWUMON_IDR.HAS_LONG is 1, then MSMON_MBWU_L is also present and has a VALUE field of 44 or 63 bits as indicated by MPAMF_MBWUMON_IDR.LWD. Hence, if there are memory bandwidth counters then there are always 31 bit counters irrespective of whether there also long counters. Set the 31 bit bandwidth counter feature bit whenever there are bandwidth counters. Fixes: fdc29a141d63 ("arm_mpam: Probe for long/lwd mbwu counters") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 32bde94dfbc374..e349db525882f2 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -930,9 +930,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) mpam_set_feature(mpam_feat_msmon_mbwu_63counter, props); else mpam_set_feature(mpam_feat_msmon_mbwu_44counter, props); - } else { - mpam_set_feature(mpam_feat_msmon_mbwu_31counter, props); } + + mpam_set_feature(mpam_feat_msmon_mbwu_31counter, props); } } } From 0045b87f65bbdd3b433747cce0d723ae94f77d38 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:09 +0100 Subject: [PATCH 1223/2286] arm_mpam: Add missing mon_sel locking in MBWU save and restore The mon_sel_lock is used to protect the mbwu_state, as well as h/w accesses that use MPAMCFG_MON_SEL. However, in mpam_restore/save_mbwu_state(), mbwu_state is accessed without holding the mon_sel_lock. Add the missing locking. Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index e349db525882f2..d39d210574a626 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1652,17 +1652,26 @@ static int mpam_restore_mbwu_state(void *_ris) u64 val; struct mon_read mwbu_arg; struct mpam_msc_ris *ris = _ris; + struct mpam_msc *msc = ris->vmsc->msc; struct mpam_class *class = ris->vmsc->comp->class; for (i = 0; i < ris->props.num_mbwu_mon; i++) { - if (ris->mbwu_state[i].enabled) { - mwbu_arg.ris = ris; - mwbu_arg.ctx = &ris->mbwu_state[i].cfg; - mwbu_arg.type = mpam_msmon_choose_counter(class); - mwbu_arg.val = &val; + if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) + return -EIO; - __ris_msmon_read(&mwbu_arg); + if (!ris->mbwu_state[i].enabled) { + mpam_mon_sel_unlock(msc); + continue; } + + mwbu_arg.ris = ris; + mwbu_arg.ctx = &ris->mbwu_state[i].cfg; + mwbu_arg.type = mpam_msmon_choose_counter(class); + mwbu_arg.val = &val; + + mpam_mon_sel_unlock(msc); + + __ris_msmon_read(&mwbu_arg); } return 0; @@ -1680,12 +1689,12 @@ static int mpam_save_mbwu_state(void *arg) struct mpam_msc *msc = ris->vmsc->msc; for (i = 0; i < ris->props.num_mbwu_mon; i++) { - mbwu_state = &ris->mbwu_state[i]; - cfg = &mbwu_state->cfg; - if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) return -EIO; + mbwu_state = &ris->mbwu_state[i]; + cfg = &mbwu_state->cfg; + mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); From 80db6616c710a358391516ad82de44361580f8da Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:10 +0100 Subject: [PATCH 1224/2286] arm_mpam: Ensure MBWU counters are reset on restore When an MSC becomes inaccessible due to cpu offline CFG_MBWU_CTL is set to zero in mpam_save_mbwu_state(). This is very likely to mean that the config will mismatch when restoring and so the monitor will be reset. However, the state may have been lost and so there are no guarantees. Ensure the reset happens by setting the reset_on_next_read and remove the unnecessary writes from mpam_save_mbwu_state(). Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse [morse: Restored clearing of CTL register for kexec/powerdown] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index d39d210574a626..6df48bec82e2c3 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1652,6 +1652,7 @@ static int mpam_restore_mbwu_state(void *_ris) u64 val; struct mon_read mwbu_arg; struct mpam_msc_ris *ris = _ris; + struct msmon_mbwu_state *mbwu_state; struct mpam_msc *msc = ris->vmsc->msc; struct mpam_class *class = ris->vmsc->comp->class; @@ -1659,16 +1660,20 @@ static int mpam_restore_mbwu_state(void *_ris) if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) return -EIO; - if (!ris->mbwu_state[i].enabled) { + mbwu_state = &ris->mbwu_state[i]; + + if (!mbwu_state->enabled) { mpam_mon_sel_unlock(msc); continue; } mwbu_arg.ris = ris; - mwbu_arg.ctx = &ris->mbwu_state[i].cfg; + mwbu_arg.ctx = &mbwu_state->cfg; mwbu_arg.type = mpam_msmon_choose_counter(class); mwbu_arg.val = &val; + mbwu_state->reset_on_next_read = true; + mpam_mon_sel_unlock(msc); __ris_msmon_read(&mwbu_arg); @@ -1703,13 +1708,10 @@ static int mpam_save_mbwu_state(void *arg) cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL); mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); - if (mpam_ris_has_mbwu_long_counter(ris)) { + if (mpam_ris_has_mbwu_long_counter(ris)) val = mpam_msc_read_mbwu_l(msc); - mpam_msc_zero_mbwu_l(msc); - } else { + else val = mpam_read_monsel_reg(msc, MBWU); - mpam_write_monsel_reg(msc, MBWU, 0); - } cfg->mon = i; cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt); From d056d29aefbe8fabe26862b1372837484bdde4c6 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:11 +0100 Subject: [PATCH 1225/2286] arm_mpam: Use __ris_msmon_read() for saving MBWU state mbwu_save_mbwu_state() reads the MBWU counters and adds that to a saved correction value. However, the type of counter to read is determined by the RIS rather than the class and overflow is not taken into account. Fix this and mitigate against further divergence by using a locked variant of the same helper used for user monitor reads, __ris_msmon_read(). Using the locked variant avoids having to drop and retake the mon_sel lock. If the lock was dropped, an interleaved monitor read which detects overflow would cause the overflow not to be accounted for in the saved value of mbwu_state->correction. The correction is no longer updated for disabled counters but this has no effect as the saved values are not expected to be useful for disabled counters. Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan [morse: Added comment on correction being included in mpam_save_mbwu_state()] Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 68 +++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 6df48bec82e2c3..a3d58a51dc73cb 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1029,12 +1029,6 @@ struct mon_read { bool waited_timeout; }; -static bool mpam_ris_has_mbwu_long_counter(struct mpam_msc_ris *ris) -{ - return (mpam_has_feature(mpam_feat_msmon_mbwu_63counter, &ris->props) || - mpam_has_feature(mpam_feat_msmon_mbwu_44counter, &ris->props)); -} - static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc) { int retry = 3; @@ -1202,13 +1196,12 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type, return overflow_val; } -static void __ris_msmon_read(void *arg) +static void __ris_msmon_read_locked(struct mon_read *m) { u64 now; bool nrdy = false; bool config_mismatch; bool overflow = false; - struct mon_read *m = arg; struct mon_cfg *ctx = m->ctx; bool reset_on_next_read = false; struct mpam_msc_ris *ris = m->ris; @@ -1216,10 +1209,8 @@ static void __ris_msmon_read(void *arg) struct mpam_msc *msc = m->ris->vmsc->msc; u32 mon_sel, ctl_val, flt_val, cur_ctl, cur_flt; - if (!mpam_mon_sel_lock(msc)) { - m->err = -EIO; - return; - } + mpam_mon_sel_lock_held(msc); + mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, ctx->mon) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); @@ -1312,7 +1303,6 @@ static void __ris_msmon_read(void *arg) default: m->err = -EINVAL; } - mpam_mon_sel_unlock(msc); if (nrdy) m->err = -EBUSY; @@ -1323,6 +1313,21 @@ static void __ris_msmon_read(void *arg) *m->val += now; } +static void __ris_msmon_read(void *arg) +{ + struct mon_read *m = arg; + struct mpam_msc *msc = m->ris->vmsc->msc; + + if (!mpam_mon_sel_lock(msc)) { + m->err = -EIO; + return; + } + + __ris_msmon_read_locked(m); + + mpam_mon_sel_unlock(msc); +} + static int _msmon_read(struct mpam_component *comp, struct mon_read *arg) { int err, any_err = 0; @@ -1674,9 +1679,9 @@ static int mpam_restore_mbwu_state(void *_ris) mbwu_state->reset_on_next_read = true; - mpam_mon_sel_unlock(msc); + __ris_msmon_read_locked(&mwbu_arg); - __ris_msmon_read(&mwbu_arg); + mpam_mon_sel_unlock(msc); } return 0; @@ -1688,10 +1693,12 @@ static int mpam_save_mbwu_state(void *arg) int i; u64 val; struct mon_cfg *cfg; + struct mon_read mbwu_arg; u32 cur_flt, cur_ctl, mon_sel; struct mpam_msc_ris *ris = arg; struct msmon_mbwu_state *mbwu_state; struct mpam_msc *msc = ris->vmsc->msc; + struct mpam_class *class = ris->vmsc->comp->class; for (i = 0; i < ris->props.num_mbwu_mon; i++) { if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) @@ -1708,17 +1715,36 @@ static int mpam_save_mbwu_state(void *arg) cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL); mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); - if (mpam_ris_has_mbwu_long_counter(ris)) - val = mpam_msc_read_mbwu_l(msc); - else - val = mpam_read_monsel_reg(msc, MBWU); - cfg->mon = i; cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt); cfg->match_pmg = FIELD_GET(MSMON_CFG_x_CTL_MATCH_PMG, cur_ctl); cfg->partid = FIELD_GET(MSMON_CFG_x_FLT_PARTID, cur_flt); - mbwu_state->correction += val; mbwu_state->enabled = FIELD_GET(MSMON_CFG_x_CTL_EN, cur_ctl); + + if (!mbwu_state->enabled) { + mpam_mon_sel_unlock(msc); + continue; + } + + val = 0; + mbwu_arg = (struct mon_read) { + .ris = ris, + .ctx = cfg, + .type = mpam_msmon_choose_counter(class), + .val = &val, + }; + + __ris_msmon_read_locked(&mbwu_arg); + + mbwu_state->reset_on_next_read = true; + if (!mbwu_arg.err) { + /* + * __ris_msmon_read_locked() already included the + * previous correction value. + */ + mbwu_state->correction = val; + } + mpam_mon_sel_unlock(msc); } From 4643335d5846123a25f535aad697c23f0135faca Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:12 +0100 Subject: [PATCH 1226/2286] arm_mpam: Initialize all of struct mon_read in mpam_restore_mbwu_state() m->err and *m->val may be read before initialization in __ris_msmon_read() when called from mpam_restore_mbwu_state(). Initialize the whole struct mon_read in mpam_restore_mbwu_state() and fix the spelling of mbwu in the name. Fixes: 41e8a14950e1 ("arm_mpam: Track bandwidth counter state for power management") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index a3d58a51dc73cb..2369f814de489c 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1655,7 +1655,7 @@ static int mpam_restore_mbwu_state(void *_ris) { int i; u64 val; - struct mon_read mwbu_arg; + struct mon_read mbwu_arg; struct mpam_msc_ris *ris = _ris; struct msmon_mbwu_state *mbwu_state; struct mpam_msc *msc = ris->vmsc->msc; @@ -1672,14 +1672,17 @@ static int mpam_restore_mbwu_state(void *_ris) continue; } - mwbu_arg.ris = ris; - mwbu_arg.ctx = &mbwu_state->cfg; - mwbu_arg.type = mpam_msmon_choose_counter(class); - mwbu_arg.val = &val; + val = 0; + mbwu_arg = (struct mon_read) { + .ris = ris, + .ctx = &mbwu_state->cfg, + .type = mpam_msmon_choose_counter(class), + .val = &val, + }; mbwu_state->reset_on_next_read = true; - __ris_msmon_read_locked(&mwbu_arg); + __ris_msmon_read_locked(&mbwu_arg); mpam_mon_sel_unlock(msc); } From 724eae25d16ee49e5cabbd927af95ef2be8fd78c Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:13 +0100 Subject: [PATCH 1227/2286] arm_mpam: resctrl: Correct check that existing class is L3 The class used to back mbm_total_bytes can be either at the L3 or the memory. If a platform had candidate classes at both the memory and the L3 then, as the check for whether the existing class is L3 or not in counter_update_class() is broken, the class that will be chosen depends on which order the classes are considered and so the probe order. Fix the check. Fixes: 1458c4f05335 ("arm_mpam: resctrl: Add support for csu counters") Tested-by: Gavin Shan Signed-off-by: Ben Horgan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_resctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c index 9d223057953abc..b825bd90d4bdb9 100644 --- a/drivers/resctrl/mpam_resctrl.c +++ b/drivers/resctrl/mpam_resctrl.c @@ -1076,7 +1076,7 @@ static void counter_update_class(enum resctrl_event_id evt_id, struct mpam_class *existing_class = mpam_resctrl_counters[evt_id].class; if (existing_class) { - if (class->level == 3) { + if (existing_class->level == 3) { pr_debug("Existing class is L3 - L3 wins\n"); return; } From 09ff5358c6233c3155c8779c608c24d7f146f532 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:14 +0100 Subject: [PATCH 1228/2286] arm_mpam: resctrl: Make read_mon_cdp_safe() self consistent When read_mon_cdp_safe() is called with cdp_enabled equal to false then the reading returned in val is the sum of the monitor values for the given component. However, when cdp_enabled equal to true it is again the sum of the monitor values but with the initial value of val also added. Both resctrl_arch_cntr_read() and resctrl_arch_rmid_read() call read_mon_cdp_safe() and are documented to return the val directly without consideration of the older value. The val is always zeroed before read_mon_cdp_safe() is called except in __check_limbo() and __l3_mon_event_count_sum() but the latter is not relevant on MPAM systems. The extra accumulation in __check_limbo() means when there are multiple RMIDs in limbo some of them may be marked dirty when they are already clean. Change the cdp_enabled equals true case to match the false case. Fixes: fb56b29932ca ("arm_mpam: resctrl: Add resctrl_arch_rmid_read()") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse [morse: fixed commit message indenting and removed stale description] Signed-off-by: James Morse --- drivers/resctrl/mpam_resctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c index b825bd90d4bdb9..321741a702feac 100644 --- a/drivers/resctrl/mpam_resctrl.c +++ b/drivers/resctrl/mpam_resctrl.c @@ -497,7 +497,7 @@ static int read_mon_cdp_safe(struct mpam_resctrl_mon *mon, struct mpam_component if (err) return err; - *val += code_val + data_val; + *val = code_val + data_val; return 0; } From 1e105782a1ce59ad066492a574ee57ff2f4374d1 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:15 +0100 Subject: [PATCH 1229/2286] arm_mpam: Don't loop forever if there is the maximum possible amount of PARTIDs On a theoretical platform with the maximum possible number of usable PARTIDs, 0XFFFF, the loops iterating over PARTID in mpam_reset_ris() and mpam_reprogram_ris() will never reach their termination condition leading to a system hang when register the cpu hotplug handlers when MPAM is enabling. Fix these loops. Fixes: f188a36ca241 ("arm_mpam: Reset MSC controls from cpuhp callbacks") Fixes: 09b89d2a72f3 ("arm_mpam: Allow configuration to be applied and restored during cpu online") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 2369f814de489c..67545df984fa9e 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1760,7 +1760,7 @@ static int mpam_save_mbwu_state(void *arg) */ static int mpam_reset_ris(void *arg) { - u16 partid, partid_max; + u16 partid_max; struct mpam_config reset_cfg = {}; struct mpam_msc_ris *ris = arg; @@ -1770,7 +1770,7 @@ static int mpam_reset_ris(void *arg) spin_lock(&partid_max_lock); partid_max = mpam_partid_max; spin_unlock(&partid_max_lock); - for (partid = 0; partid <= partid_max; partid++) + for (u32 partid = 0; partid <= partid_max; partid++) mpam_reprogram_ris_partid(ris, partid, &reset_cfg); return 0; @@ -1817,7 +1817,6 @@ static int __write_config(void *arg) static void mpam_reprogram_msc(struct mpam_msc *msc) { - u16 partid; bool reset; struct mpam_config *cfg; struct mpam_msc_ris *ris; @@ -1841,7 +1840,7 @@ static void mpam_reprogram_msc(struct mpam_msc *msc) arg.comp = ris->vmsc->comp; arg.ris = ris; reset = true; - for (partid = 0; partid <= mpam_partid_max; partid++) { + for (u32 partid = 0; partid <= mpam_partid_max; partid++) { cfg = &ris->vmsc->comp->cfg[partid]; if (!bitmap_empty(cfg->features, MPAM_FEATURE_LAST)) reset = false; From 046384f02b57da84a697d32aff7d7d46e881ae84 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:16 +0100 Subject: [PATCH 1230/2286] arm_mpam: Switch to kvzmalloc_objs() for allocation of component cfg Each component cfg is sized by the size of a per-PARTID multiplied by the number of PARTIDs. On a platform sized to push the limits of the specification, the allocation using kzalloc_objs() will consistently fail. Change to use kvzmalloc_objs() so that large allocations fall back to vmalloc() based allocations. Fixes: 09b89d2a72f3 ("arm_mpam: Allow configuration to be applied and restored during cpu online") Signed-off-by: Ben Horgan Tested-by: Gavin Shan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 67545df984fa9e..527867ab5f1b4e 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -157,7 +157,7 @@ static void mpam_free_garbage(void) if (iter->pdev) devm_kfree(&iter->pdev->dev, iter->to_free); else - kfree(iter->to_free); + kvfree(iter->to_free); } } @@ -2698,7 +2698,7 @@ static int __allocate_component_cfg(struct mpam_component *comp) if (comp->cfg) return 0; - comp->cfg = kzalloc_objs(*comp->cfg, mpam_partid_max + 1); + comp->cfg = kvzalloc_objs(*comp->cfg, mpam_partid_max + 1); if (!comp->cfg) return -ENOMEM; From 1c39415492323b9c5fd4a59179b390d45c26778a Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Thu, 17 Sep 2026 15:56:17 +0100 Subject: [PATCH 1231/2286] arm_mpam: resctrl: Don't stop early when tearing down a class An MPAM class can back more than one control and more than one monitor. mpam_resctrl_teardown_class() iterates through all the controls but stops if it finds a match which means classes backing more than one class aren't torn down. The same for monitors, in which case the mbwu counters will be leaked. Stop terminating the loops early. As we are already changing mpam_resctrl_teardown_class() remove the misleading comment. It doesn't call resctrl_exit() and isn't required to do so as the only caller does it first. Fixes: fb481ec08699 ("arm_mpam: resctrl: Call resctrl_init() on platforms that can support resctrl") Reported-by: Sashiko Signed-off-by: Ben Horgan Reviewed-by: Gavin Shan Reviewed-by: James Morse Signed-off-by: James Morse --- drivers/resctrl/mpam_resctrl.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/resctrl/mpam_resctrl.c b/drivers/resctrl/mpam_resctrl.c index 321741a702feac..9cec5843e8fa2c 100644 --- a/drivers/resctrl/mpam_resctrl.c +++ b/drivers/resctrl/mpam_resctrl.c @@ -1943,10 +1943,6 @@ static void mpam_resctrl_teardown_mon(struct mpam_resctrl_mon *mon, struct mpam_ mon->mbwu_idx_to_mon = NULL; } -/* - * The driver is detaching an MSC from this class, if resctrl was using it, - * pull on resctrl_exit(). - */ void mpam_resctrl_teardown_class(struct mpam_class *class) { struct mpam_resctrl_res *res; @@ -1957,17 +1953,14 @@ void mpam_resctrl_teardown_class(struct mpam_class *class) might_sleep(); for_each_mpam_resctrl_control(res, rid) { - if (res->class == class) { + if (res->class == class) res->class = NULL; - break; - } } for_each_mpam_resctrl_mon(mon, eventid) { if (mon->class == class) { mon->class = NULL; mpam_resctrl_teardown_mon(mon, class); - break; } } } From 69b97fc19aceffe927583c947d4e93945af440aa Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Thu, 3 Sep 2026 19:23:00 +0100 Subject: [PATCH 1232/2286] MAINTAINERS: Add mpam.rst to the MPAM DRIVER entry Documentation/arch/arm64/mpam.rst is not listed under MPAM DRIVER, so get_maintainer.pl names neither MPAM maintainer on a patch to it. Signed-off-by: Fuad Tabba Signed-off-by: James Morse --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index cc3cae2e378b34..c107b266a15029 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18440,6 +18440,7 @@ M: Ben Horgan R: Reinette Chatre R: Fenghua Yu S: Maintained +F: Documentation/arch/arm64/mpam.rst F: drivers/resctrl/mpam_* F: drivers/resctrl/test_mpam_* F: include/linux/arm_mpam.h From 0ce336dd6205fc6e38900e30c9e30833f1f6af1f Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Tue, 15 Sep 2026 11:31:01 +0100 Subject: [PATCH 1233/2286] MAINTAINERS: Use linux-arm-kernel list for the MPAM driver As MPAM is an arm64 feature and patches mostly go via the arm64 maintainers it may be reasonably expected that the MPAM driver uses the list-arm-kernel list. Make this official by adding the L: in MAINTAINERS. Suggested-by: Gavin Shan Suggested-by: Yin Li Signed-off-by: Ben Horgan Signed-off-by: James Morse --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index c107b266a15029..da0fdc9f96c14d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18439,6 +18439,7 @@ M: James Morse M: Ben Horgan R: Reinette Chatre R: Fenghua Yu +L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) S: Maintained F: Documentation/arch/arm64/mpam.rst F: drivers/resctrl/mpam_* From 84e8795c271a6a44c3a477e43fef0a05403ddfec Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:26 +0200 Subject: [PATCH 1234/2286] arm_mpam: let low level MSC accessors return an error The upcoming MPAM-Fb support does not use MMIO primitives to access an MSC, but employs a shared-memory/doorbell based firmware protocol. Its complexity means that it must be able to handle errors, whereas we always assume an MMIO based MSC access succeeds today. Change the __mpam_read_reg() low level accessor function to return the requested data through a pointer, and return an error code instead. Also change the __mpam_write_reg() accessor function to return an error code. At the moment this is always 0, so all error handling paths are not exercised at the moment. This will change later when alternative MSC access methods like MPAM-Fb are activated. Change all direct users of those MSC wrappers to comply with the new prototypes, though the errors are not propagated all the way up yet. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 203 +++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 73 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 527867ab5f1b4e..77502a87f0f44f 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -177,59 +177,68 @@ static void mpam_assert_partid_sizes_fixed(void) WARN_ON_ONCE(!partid_max_published); } -static u32 __mpam_read_reg(struct mpam_msc *msc, u16 reg) +static int __mpam_read_reg(struct mpam_msc *msc, u16 reg, u32 *res) { WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); - return readl_relaxed(msc->mapped_hwpage + reg); + *res = readl_relaxed(msc->mapped_hwpage + reg); + + return 0; } -static inline u32 _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg) +static inline int _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg, + u32 *res) { lockdep_assert_held_once(&msc->part_sel_lock); - return __mpam_read_reg(msc, reg); + return __mpam_read_reg(msc, reg, res); } -#define mpam_read_partsel_reg(msc, reg) _mpam_read_partsel_reg(msc, MPAMF_##reg) +#define mpam_read_partsel_reg(msc, reg, res) _mpam_read_partsel_reg(msc, MPAMF_##reg, res) -static void __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val) +static int __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val) { WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz); WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); writel_relaxed(val, msc->mapped_hwpage + reg); + + return 0; } -static inline void _mpam_write_partsel_reg(struct mpam_msc *msc, u16 reg, u32 val) +static inline int _mpam_write_partsel_reg(struct mpam_msc *msc, u16 reg, u32 val) { lockdep_assert_held_once(&msc->part_sel_lock); - __mpam_write_reg(msc, reg, val); + return __mpam_write_reg(msc, reg, val); } #define mpam_write_partsel_reg(msc, reg, val) _mpam_write_partsel_reg(msc, MPAMCFG_##reg, val) -static inline u32 _mpam_read_monsel_reg(struct mpam_msc *msc, u16 reg) +static inline int _mpam_read_monsel_reg(struct mpam_msc *msc, u16 reg, + u32 *res) { mpam_mon_sel_lock_held(msc); - return __mpam_read_reg(msc, reg); + return __mpam_read_reg(msc, reg, res); } -#define mpam_read_monsel_reg(msc, reg) _mpam_read_monsel_reg(msc, MSMON_##reg) +#define mpam_read_monsel_reg(msc, reg, res) _mpam_read_monsel_reg(msc, MSMON_##reg, res) -static inline void _mpam_write_monsel_reg(struct mpam_msc *msc, u16 reg, u32 val) +static inline int _mpam_write_monsel_reg(struct mpam_msc *msc, u16 reg, u32 val) { mpam_mon_sel_lock_held(msc); - __mpam_write_reg(msc, reg, val); + return __mpam_write_reg(msc, reg, val); } #define mpam_write_monsel_reg(msc, reg, val) _mpam_write_monsel_reg(msc, MSMON_##reg, val) static bool mpam_msc_check_aidr(struct mpam_msc *msc) { - u32 aidr = __mpam_read_reg(msc, MPAMF_AIDR); - u32 major = FIELD_GET(MPAMF_AIDR_ARCH_MAJOR_REV, aidr); - u32 minor = FIELD_GET(MPAMF_AIDR_ARCH_MINOR_REV, aidr); + u32 aidr, major, minor; + if (__mpam_read_reg(msc, MPAMF_AIDR, &aidr)) + return false; + + major = FIELD_GET(MPAMF_AIDR_ARCH_MAJOR_REV, aidr); + minor = FIELD_GET(MPAMF_AIDR_ARCH_MINOR_REV, aidr); /* * v0.0 and >v2.x aren't supported, but anything else should be backward * compatible to v0.1 or v1.0. @@ -242,25 +251,41 @@ static bool mpam_msc_check_aidr(struct mpam_msc *msc) return true; } -static u64 mpam_msc_read_idr(struct mpam_msc *msc) +static int mpam_msc_read_idr(struct mpam_msc *msc, u64 *res) { - u64 idr_high = 0, idr_low; + u32 idr_high, idr_low; + int ret; lockdep_assert_held(&msc->part_sel_lock); - idr_low = mpam_read_partsel_reg(msc, IDR); - if (FIELD_GET(MPAMF_IDR_EXT, idr_low)) - idr_high = mpam_read_partsel_reg(msc, IDR + 4); + ret = mpam_read_partsel_reg(msc, IDR, &idr_low); + if (ret) + return ret; + + if (FIELD_GET(MPAMF_IDR_EXT, idr_low)) { + ret = mpam_read_partsel_reg(msc, IDR + 4, &idr_high); + if (ret) + return ret; + } else { + idr_high = 0; + } + + *res = ((u64)idr_high << 32) | idr_low; - return (idr_high << 32) | idr_low; + return 0; } -static void mpam_msc_clear_esr(struct mpam_msc *msc) +static int mpam_msc_clear_esr(struct mpam_msc *msc) { - u64 esr_low = __mpam_read_reg(msc, MPAMF_ESR); + u32 esr_low; + int ret; + + ret = __mpam_read_reg(msc, MPAMF_ESR, &esr_low); + if (ret) + return ret; if (!esr_low) - return; + return 0; /* * Clearing the high/low bits of MPAMF_ESR can not be atomic. @@ -268,44 +293,59 @@ static void mpam_msc_clear_esr(struct mpam_msc *msc) * lower half prevent hardware from updating either half of the * register. */ - if (msc->has_extd_esr) - __mpam_write_reg(msc, MPAMF_ESR + 4, 0); - __mpam_write_reg(msc, MPAMF_ESR, 0); + if (msc->has_extd_esr) { + ret = __mpam_write_reg(msc, MPAMF_ESR + 4, 0); + if (ret) + return ret; + } + + return __mpam_write_reg(msc, MPAMF_ESR, 0); } -static u64 mpam_msc_read_esr(struct mpam_msc *msc) +static int mpam_msc_read_esr(struct mpam_msc *msc, u64 *res) { - u64 esr_high = 0, esr_low; + u32 esr_high, esr_low; + int ret; - esr_low = __mpam_read_reg(msc, MPAMF_ESR); - if (msc->has_extd_esr) - esr_high = __mpam_read_reg(msc, MPAMF_ESR + 4); + ret = __mpam_read_reg(msc, MPAMF_ESR, &esr_low); + if (ret) + return ret; - return (esr_high << 32) | esr_low; + if (msc->has_extd_esr) { + ret = __mpam_read_reg(msc, MPAMF_ESR + 4, &esr_high); + if (ret) + return ret; + } else { + esr_high = 0; + } + + *res = ((u64)esr_high << 32) | esr_low; + + return 0; } -static void __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc) +static int __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc) { lockdep_assert_held(&msc->part_sel_lock); - mpam_write_partsel_reg(msc, PART_SEL, partsel); + return mpam_write_partsel_reg(msc, PART_SEL, partsel); } -static void __mpam_part_sel(u8 ris_idx, u16 partid, struct mpam_msc *msc) +static int __mpam_part_sel(u8 ris_idx, u16 partid, struct mpam_msc *msc) { u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) | FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, partid); - __mpam_part_sel_raw(partsel, msc); + return __mpam_part_sel_raw(partsel, msc); } -static void __mpam_intpart_sel(u8 ris_idx, u16 intpartid, struct mpam_msc *msc) +static int __mpam_intpart_sel(u8 ris_idx, u16 intpartid, struct mpam_msc *msc) { u32 partsel = FIELD_PREP(MPAMCFG_PART_SEL_RIS, ris_idx) | FIELD_PREP(MPAMCFG_PART_SEL_PARTID_SEL, intpartid) | MPAMCFG_PART_SEL_INTERNAL; - __mpam_part_sel_raw(partsel, msc); + return __mpam_part_sel_raw(partsel, msc); } int mpam_register_requestor(u16 partid_max, u8 pmg_max) @@ -774,13 +814,13 @@ static bool mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris) mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val); _mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY); - now = _mpam_read_monsel_reg(msc, MSMON_CSU); + _mpam_read_monsel_reg(msc, MSMON_CSU, &now); can_set = now & MSMON___NRDY; _mpam_write_monsel_reg(msc, MSMON_CSU, 0); /* Configuration change to try and coax hardware into setting nrdy */ mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1); - now = _mpam_read_monsel_reg(msc, MSMON_CSU); + _mpam_read_monsel_reg(msc, MSMON_CSU, &now); can_clear = !(now & MSMON___NRDY); mpam_mon_sel_unlock(msc); @@ -800,7 +840,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Cache Capacity Partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_CCAP_PART, ris->idr)) { - u32 ccap_features = mpam_read_partsel_reg(msc, CCAP_IDR); + u32 ccap_features; + + mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features); props->cmax_wd = FIELD_GET(MPAMF_CCAP_IDR_CMAX_WD, ccap_features); if (props->cmax_wd && @@ -823,7 +865,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Cache Portion partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_CPOR_PART, ris->idr)) { - u32 cpor_features = mpam_read_partsel_reg(msc, CPOR_IDR); + u32 cpor_features; + + mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features); props->cpbm_wd = FIELD_GET(MPAMF_CPOR_IDR_CPBM_WD, cpor_features); if (props->cpbm_wd) @@ -832,7 +876,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Memory bandwidth partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_MBW_PART, ris->idr)) { - u32 mbw_features = mpam_read_partsel_reg(msc, MBW_IDR); + u32 mbw_features; + + mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features); /* portion bitmap resolution */ props->mbw_pbm_bits = FIELD_GET(MPAMF_MBW_IDR_BWPBM_WD, mbw_features); @@ -860,7 +906,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Priority partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_PRI_PART, ris->idr)) { - u32 pri_features = mpam_read_partsel_reg(msc, PRI_IDR); + u32 pri_features; + + mpam_read_partsel_reg(msc, PRI_IDR, &pri_features); props->intpri_wd = FIELD_GET(MPAMF_PRI_IDR_INTPRI_WD, pri_features); if (props->intpri_wd && FIELD_GET(MPAMF_PRI_IDR_HAS_INTPRI, pri_features)) { @@ -879,7 +927,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Performance Monitoring */ if (FIELD_GET(MPAMF_IDR_HAS_MSMON, ris->idr)) { - u32 msmon_features = mpam_read_partsel_reg(msc, MSMON_IDR); + u32 msmon_features; + + mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features); /* * If the firmware max-nrdy-us property is missing, the @@ -892,7 +942,8 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_CSU, msmon_features)) { u32 csumonidr; - csumonidr = mpam_read_partsel_reg(msc, CSUMON_IDR); + mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr); + props->num_csu_mon = FIELD_GET(MPAMF_CSUMON_IDR_NUM_MON, csumonidr); if (props->num_csu_mon) { bool hw_managed; @@ -915,7 +966,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) } if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_MBWU, msmon_features)) { bool has_long; - u32 mbwumon_idr = mpam_read_partsel_reg(msc, MBWUMON_IDR); + u32 mbwumon_idr; + + mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr); props->num_mbwu_mon = FIELD_GET(MPAMF_MBWUMON_IDR_NUM_MON, mbwumon_idr); if (props->num_mbwu_mon) { @@ -945,8 +998,11 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) */ if (FIELD_GET(MPAMF_IDR_HAS_PARTID_NRW, ris->idr) && class->type != MPAM_CLASS_UNKNOWN) { - u32 nrwidr = mpam_read_partsel_reg(msc, PARTID_NRW_IDR); - u16 partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr); + u16 partid_max; + u32 nrwidr; + + mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr); + partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr); mpam_set_feature(mpam_feat_partid_nrw, props); msc->partid_max = min(msc->partid_max, partid_max); @@ -970,8 +1026,9 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) /* Grab an IDR value to find out how many RIS there are */ mutex_lock(&msc->part_sel_lock); - idr = mpam_msc_read_idr(msc); - msc->iidr = mpam_read_partsel_reg(msc, IIDR); + mpam_msc_read_idr(msc, &idr); + mpam_read_partsel_reg(msc, IIDR, &msc->iidr); + mutex_unlock(&msc->part_sel_lock); mpam_enable_quirks(msc); @@ -985,7 +1042,7 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) for (ris_idx = 0; ris_idx <= msc->ris_max; ris_idx++) { mutex_lock(&msc->part_sel_lock); __mpam_part_sel(ris_idx, 0, msc); - idr = mpam_msc_read_idr(msc); + mpam_msc_read_idr(msc, &idr); mutex_unlock(&msc->part_sel_lock); partid_max = FIELD_GET(MPAMF_IDR_PARTID_MAX, idr); @@ -1033,24 +1090,24 @@ static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc) { int retry = 3; u32 mbwu_l_low; - u64 mbwu_l_high1, mbwu_l_high2; + u32 mbwu_l_high1, mbwu_l_high2; mpam_mon_sel_lock_held(msc); WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); - mbwu_l_high2 = __mpam_read_reg(msc, MSMON_MBWU_L + 4); + __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); do { mbwu_l_high1 = mbwu_l_high2; - mbwu_l_low = __mpam_read_reg(msc, MSMON_MBWU_L); - mbwu_l_high2 = __mpam_read_reg(msc, MSMON_MBWU_L + 4); + __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low); + __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); retry--; } while (mbwu_l_high1 != mbwu_l_high2 && retry > 0); if (mbwu_l_high1 == mbwu_l_high2) - return (mbwu_l_high1 << 32) | mbwu_l_low; + return ((u64)mbwu_l_high1 << 32) | mbwu_l_low; pr_warn("Failed to read a stable value\n"); return MSMON___L_NRDY; @@ -1114,14 +1171,14 @@ static void read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val, switch (m->type) { case mpam_feat_msmon_csu: - *ctl_val = mpam_read_monsel_reg(msc, CFG_CSU_CTL); - *flt_val = mpam_read_monsel_reg(msc, CFG_CSU_FLT); + mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val); + mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val); break; case mpam_feat_msmon_mbwu_31counter: case mpam_feat_msmon_mbwu_44counter: case mpam_feat_msmon_mbwu_63counter: - *ctl_val = mpam_read_monsel_reg(msc, CFG_MBWU_CTL); - *flt_val = mpam_read_monsel_reg(msc, CFG_MBWU_FLT); + mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val); + mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val); break; default: pr_warn("Unexpected monitor type %d\n", m->type); @@ -1199,6 +1256,7 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type, static void __ris_msmon_read_locked(struct mon_read *m) { u64 now; + u32 now32; bool nrdy = false; bool config_mismatch; bool overflow = false; @@ -1258,9 +1316,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) switch (m->type) { case mpam_feat_msmon_csu: - now = mpam_read_monsel_reg(msc, CSU); - nrdy = now & MSMON___NRDY; - now = FIELD_GET(MSMON___VALUE, now); + mpam_read_monsel_reg(msc, CSU, &now32); + nrdy = now32 & MSMON___NRDY; + now = FIELD_GET(MSMON___VALUE, now32); if (mpam_has_quirk(IGNORE_CSU_NRDY, msc) && m->waited_timeout) nrdy = false; @@ -1278,9 +1336,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) else now = FIELD_GET(MSMON___L_VALUE, now); } else { - now = mpam_read_monsel_reg(msc, MBWU); - nrdy = now & MSMON___NRDY; - now = FIELD_GET(MSMON___VALUE, now); + mpam_read_monsel_reg(msc, MBWU, &now32); + nrdy = now32 & MSMON___NRDY; + now = FIELD_GET(MSMON___VALUE, now32); } if (mpam_has_quirk(T241_MBW_COUNTER_SCALE_64, msc)) @@ -1713,9 +1771,8 @@ static int mpam_save_mbwu_state(void *arg) mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); - - cur_flt = mpam_read_monsel_reg(msc, CFG_MBWU_FLT); - cur_ctl = mpam_read_monsel_reg(msc, CFG_MBWU_CTL); + mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt); + mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl); mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); cfg->mon = i; @@ -2518,7 +2575,7 @@ static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc) &msc->accessibility))) return IRQ_NONE; - reg = mpam_msc_read_esr(msc); + mpam_msc_read_esr(msc, ®); errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg); if (!errcode) From 0db1715e4c9c8cc3fdffe1d1d4114c86a1c07730 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:27 +0200 Subject: [PATCH 1235/2286] arm_mpam: propagate MSC access errors for hw_probe functions Allow the functions probing for MSC hardware and features to return an error, and propagate read and write errors from the lower level up. This uses some "scoped cleanup" functions like scoped_guard() and ACQUIRE() to avoid the complexity of error handling when a lock has been taken. Since the mon_sel_lock is a bit special (even more so in an upcoming patch), we define a new GUARD type for it. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 145 ++++++++++++++++++++++---------- drivers/resctrl/mpam_internal.h | 4 + 2 files changed, 103 insertions(+), 46 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 77502a87f0f44f..a5365b56b1e721 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -792,44 +792,62 @@ static void mpam_enable_quirks(struct mpam_msc *msc) * Try and see what values stick in this bit. If we can write either value, * its probably not implemented by hardware. */ -static bool mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris) +static int mpam_ris_hw_probe_csu_nrdy(struct mpam_msc_ris *ris) { u32 now, mon_sel, ctl_val; bool can_set, can_clear; struct mpam_msc *msc = ris->vmsc->msc; + int ret; - if (WARN_ON_ONCE(!mpam_mon_sel_lock(msc))) - return false; + ACQUIRE(mon_sel_lock, guard)(msc); + ret = ACQUIRE_ERR(mon_sel_lock, &guard); + if (ret) + return ret; mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, 0) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); - mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); + ret = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); + if (ret) + return ret; /* Hardware might ignore nrdy if it's not enabled */ ctl_val = MSMON_CFG_CSU_CTL_TYPE_CSU; ctl_val |= MSMON_CFG_x_CTL_MATCH_PARTID; ctl_val |= MSMON_CFG_x_CTL_MATCH_PMG; ctl_val |= MSMON_CFG_x_CTL_EN; - mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0); - mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val); + ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val); + if (ret) + return ret; - _mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY); - _mpam_read_monsel_reg(msc, MSMON_CSU, &now); + ret = _mpam_write_monsel_reg(msc, MSMON_CSU, MSMON___NRDY); + if (ret) + return ret; + ret = _mpam_read_monsel_reg(msc, MSMON_CSU, &now); + if (ret) + return ret; can_set = now & MSMON___NRDY; - _mpam_write_monsel_reg(msc, MSMON_CSU, 0); + ret = _mpam_write_monsel_reg(msc, MSMON_CSU, 0); + if (ret) + return ret; /* Configuration change to try and coax hardware into setting nrdy */ - mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1); - _mpam_read_monsel_reg(msc, MSMON_CSU, &now); + ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, 0x1); + if (ret) + return ret; + ret = _mpam_read_monsel_reg(msc, MSMON_CSU, &now); + if (ret) + return ret; can_clear = !(now & MSMON___NRDY); - mpam_mon_sel_unlock(msc); return (!can_set || !can_clear); } -static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) +static int mpam_ris_hw_probe(struct mpam_msc_ris *ris) { - int err; + int fw_has_nrdy_err, err; struct mpam_msc *msc = ris->vmsc->msc; struct device *dev = &msc->pdev->dev; struct mpam_props *props = &ris->props; @@ -842,7 +860,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) if (FIELD_GET(MPAMF_IDR_HAS_CCAP_PART, ris->idr)) { u32 ccap_features; - mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features); + err = mpam_read_partsel_reg(msc, CCAP_IDR, &ccap_features); + if (err) + return err; props->cmax_wd = FIELD_GET(MPAMF_CCAP_IDR_CMAX_WD, ccap_features); if (props->cmax_wd && @@ -867,7 +887,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) if (FIELD_GET(MPAMF_IDR_HAS_CPOR_PART, ris->idr)) { u32 cpor_features; - mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features); + err = mpam_read_partsel_reg(msc, CPOR_IDR, &cpor_features); + if (err) + return err; props->cpbm_wd = FIELD_GET(MPAMF_CPOR_IDR_CPBM_WD, cpor_features); if (props->cpbm_wd) @@ -877,8 +899,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Memory bandwidth partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_MBW_PART, ris->idr)) { u32 mbw_features; - - mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features); + err = mpam_read_partsel_reg(msc, MBW_IDR, &mbw_features); + if (err) + return err; /* portion bitmap resolution */ props->mbw_pbm_bits = FIELD_GET(MPAMF_MBW_IDR_BWPBM_WD, mbw_features); @@ -907,8 +930,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) /* Priority partitioning */ if (FIELD_GET(MPAMF_IDR_HAS_PRI_PART, ris->idr)) { u32 pri_features; - - mpam_read_partsel_reg(msc, PRI_IDR, &pri_features); + err = mpam_read_partsel_reg(msc, PRI_IDR, &pri_features); + if (err) + return err; props->intpri_wd = FIELD_GET(MPAMF_PRI_IDR_INTPRI_WD, pri_features); if (props->intpri_wd && FIELD_GET(MPAMF_PRI_IDR_HAS_INTPRI, pri_features)) { @@ -929,20 +953,24 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) if (FIELD_GET(MPAMF_IDR_HAS_MSMON, ris->idr)) { u32 msmon_features; - mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features); + err = mpam_read_partsel_reg(msc, MSMON_IDR, &msmon_features); + if (err) + return err; /* * If the firmware max-nrdy-us property is missing, the * CSU counters can't be used. Should we wait forever? */ - err = device_property_read_u32(&msc->pdev->dev, - "arm,not-ready-us", - &msc->nrdy_usec); + fw_has_nrdy_err = device_property_read_u32(&msc->pdev->dev, + "arm,not-ready-us", + &msc->nrdy_usec); if (FIELD_GET(MPAMF_MSMON_IDR_MSMON_CSU, msmon_features)) { u32 csumonidr; - mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr); + err = mpam_read_partsel_reg(msc, CSUMON_IDR, &csumonidr); + if (err) + return err; props->num_csu_mon = FIELD_GET(MPAMF_CSUMON_IDR_NUM_MON, csumonidr); if (props->num_csu_mon) { @@ -954,13 +982,16 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) mpam_set_feature(mpam_feat_msmon_csu_xcl, props); /* Is NRDY hardware managed? */ - hw_managed = mpam_ris_hw_probe_csu_nrdy(ris); + err = mpam_ris_hw_probe_csu_nrdy(ris); + if (err < 0) + return err; + hw_managed = err; /* * Accept the missing firmware property if NRDY appears * un-implemented. */ - if (err && hw_managed) + if (fw_has_nrdy_err && hw_managed) dev_err_once(dev, "Counters are not usable because not-ready timeout was not provided by firmware."); } } @@ -968,7 +999,9 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) bool has_long; u32 mbwumon_idr; - mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr); + err = mpam_read_partsel_reg(msc, MBWUMON_IDR, &mbwumon_idr); + if (err) + return err; props->num_mbwu_mon = FIELD_GET(MPAMF_MBWUMON_IDR_NUM_MON, mbwumon_idr); if (props->num_mbwu_mon) { @@ -1001,16 +1034,22 @@ static void mpam_ris_hw_probe(struct mpam_msc_ris *ris) u16 partid_max; u32 nrwidr; - mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr); + err = mpam_read_partsel_reg(msc, PARTID_NRW_IDR, &nrwidr); + if (err) + return err; + partid_max = FIELD_GET(MPAMF_PARTID_NRW_IDR_INTPARTID_MAX, nrwidr); mpam_set_feature(mpam_feat_partid_nrw, props); msc->partid_max = min(msc->partid_max, partid_max); } + + return 0; } static int mpam_msc_hw_probe(struct mpam_msc *msc) { + int ret; u64 idr; u16 partid_max; u8 ris_idx, pmg_max; @@ -1025,11 +1064,15 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) } /* Grab an IDR value to find out how many RIS there are */ - mutex_lock(&msc->part_sel_lock); - mpam_msc_read_idr(msc, &idr); - mpam_read_partsel_reg(msc, IIDR, &msc->iidr); + scoped_guard(mutex, &msc->part_sel_lock) { + ret = mpam_msc_read_idr(msc, &idr); + if (ret) + return ret; - mutex_unlock(&msc->part_sel_lock); + ret = mpam_read_partsel_reg(msc, IIDR, &msc->iidr); + if (ret) + return ret; + } mpam_enable_quirks(msc); @@ -1040,10 +1083,15 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) msc->pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr); for (ris_idx = 0; ris_idx <= msc->ris_max; ris_idx++) { - mutex_lock(&msc->part_sel_lock); - __mpam_part_sel(ris_idx, 0, msc); - mpam_msc_read_idr(msc, &idr); - mutex_unlock(&msc->part_sel_lock); + scoped_guard(mutex, &msc->part_sel_lock) { + ret = __mpam_part_sel(ris_idx, 0, msc); + if (ret) + return ret; + + ret = mpam_msc_read_idr(msc, &idr); + if (ret) + return ret; + } partid_max = FIELD_GET(MPAMF_IDR_PARTID_MAX, idr); pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr); @@ -1051,17 +1099,22 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) msc->pmg_max = min(msc->pmg_max, pmg_max); msc->has_extd_esr = FIELD_GET(MPAMF_IDR_HAS_EXTD_ESR, idr); - mutex_lock(&mpam_list_lock); - ris = mpam_get_or_create_ris(msc, ris_idx); - mutex_unlock(&mpam_list_lock); - if (IS_ERR(ris)) - return PTR_ERR(ris); + scoped_guard(mutex, &mpam_list_lock) { + ris = mpam_get_or_create_ris(msc, ris_idx); + if (IS_ERR(ris)) + return PTR_ERR(ris); + } ris->idr = idr; - mutex_lock(&msc->part_sel_lock); - __mpam_part_sel(ris_idx, 0, msc); - mpam_ris_hw_probe(ris); - mutex_unlock(&msc->part_sel_lock); + scoped_guard(mutex, &msc->part_sel_lock) { + ret = __mpam_part_sel(ris_idx, 0, msc); + if (ret) + return ret; + + ret = mpam_ris_hw_probe(ris); + if (ret) + return ret; + } } /* Clear any stale errors */ diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index def0e3a65c2305..68a6cf2b9cc738 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -162,6 +162,10 @@ static inline void mpam_mon_sel_lock_init(struct mpam_msc *msc) raw_spin_lock_init(&msc->_mon_sel_lock); } +DEFINE_GUARD(mon_sel, struct mpam_msc *, + mpam_mon_sel_lock(_T), mpam_mon_sel_unlock(_T)); +DEFINE_GUARD_COND(mon_sel, _lock, mpam_mon_sel_lock(_T), _RET); + /* Bits for mpam features bitmaps */ enum mpam_device_features { mpam_feat_cpor_part, From 7ec57e8b75c96e9b2c70e4183f5a3447fed3b04b Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 16:48:37 +0100 Subject: [PATCH 1236/2286] arm_mpam: propagate MSC access errors for MBWU counters Allow the mpam_msc_read_mbwu_l() function to return an error, and propagate errors from the lower level up. This also changes the users of this function. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper [morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index a5365b56b1e721..c573fe08ce0114 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1139,8 +1139,9 @@ struct mon_read { bool waited_timeout; }; -static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc) +static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res) { + int ret; int retry = 3; u32 mbwu_l_low; u32 mbwu_l_high1, mbwu_l_high2; @@ -1150,20 +1151,30 @@ static u64 mpam_msc_read_mbwu_l(struct mpam_msc *msc) WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); - __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); + ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); + if (ret) + return ret; + do { mbwu_l_high1 = mbwu_l_high2; - __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low); - __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); + ret = __mpam_read_reg(msc, MSMON_MBWU_L, &mbwu_l_low); + if (ret) + return ret; + ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); + if (ret) + return ret; retry--; } while (mbwu_l_high1 != mbwu_l_high2 && retry > 0); - if (mbwu_l_high1 == mbwu_l_high2) - return ((u64)mbwu_l_high1 << 32) | mbwu_l_low; + if (mbwu_l_high1 == mbwu_l_high2) { + *res = ((u64)mbwu_l_high1 << 32) | mbwu_l_low; + } else { + pr_warn("Failed to read a stable value\n"); + *res = MSMON___L_NRDY; + } - pr_warn("Failed to read a stable value\n"); - return MSMON___L_NRDY; + return 0; } static void mpam_msc_zero_mbwu_l(struct mpam_msc *msc) @@ -1381,7 +1392,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) case mpam_feat_msmon_mbwu_44counter: case mpam_feat_msmon_mbwu_63counter: if (m->type != mpam_feat_msmon_mbwu_31counter) { - now = mpam_msc_read_mbwu_l(msc); + m->err = mpam_msc_read_mbwu_l(msc, &now); + if (m->err) + return; nrdy = now & MSMON___L_NRDY; if (m->type == mpam_feat_msmon_mbwu_63counter) From e8166b11399bae5454c06111bbd16dc0b3713732 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:29 +0200 Subject: [PATCH 1237/2286] arm_mpam: propagate MSC access errors for msmon helpers Allow the helper functions for msmon accesses to return an error, and propagate read and write errors from the lower level up. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper Tested-by: Ritwick Sharma Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 72 ++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index c573fe08ce0114..72a5d1ab968486 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1177,15 +1177,20 @@ static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res) return 0; } -static void mpam_msc_zero_mbwu_l(struct mpam_msc *msc) +static int mpam_msc_zero_mbwu_l(struct mpam_msc *msc) { + int ret; + mpam_mon_sel_lock_held(msc); WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); - __mpam_write_reg(msc, MSMON_MBWU_L, 0); - __mpam_write_reg(msc, MSMON_MBWU_L + 4, 0); + ret = __mpam_write_reg(msc, MSMON_MBWU_L, 0); + if (ret) + return ret; + + return __mpam_write_reg(msc, MSMON_MBWU_L + 4, 0); } static void gen_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val, @@ -1228,24 +1233,28 @@ static void gen_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val, } } -static void read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val, - u32 *flt_val) +static int read_msmon_ctl_flt_vals(struct mon_read *m, u32 *ctl_val, + u32 *flt_val) { struct mpam_msc *msc = m->ris->vmsc->msc; + int ret; switch (m->type) { case mpam_feat_msmon_csu: - mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val); - mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val); - break; + ret = mpam_read_monsel_reg(msc, CFG_CSU_CTL, ctl_val); + if (ret) + return ret; + return mpam_read_monsel_reg(msc, CFG_CSU_FLT, flt_val); case mpam_feat_msmon_mbwu_31counter: case mpam_feat_msmon_mbwu_44counter: case mpam_feat_msmon_mbwu_63counter: - mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val); - mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val); - break; + ret = mpam_read_monsel_reg(msc, CFG_MBWU_CTL, ctl_val); + if (ret) + return ret; + return mpam_read_monsel_reg(msc, CFG_MBWU_FLT, flt_val); default: pr_warn("Unexpected monitor type %d\n", m->type); + return -EINVAL; } } @@ -1258,10 +1267,11 @@ static inline void clean_msmon_ctl_val(u32 *cur_ctl) *cur_ctl &= ~MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L; } -static void write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val, - u32 flt_val) +static int write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val, + u32 flt_val) { struct mpam_msc *msc = m->ris->vmsc->msc; + int ret; /* * Write the ctl_val with the enable bit cleared, reset the counter, @@ -1269,25 +1279,37 @@ static void write_msmon_ctl_flt_vals(struct mon_read *m, u32 ctl_val, */ switch (m->type) { case mpam_feat_msmon_csu: - mpam_write_monsel_reg(msc, CFG_CSU_FLT, flt_val); - mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val); - mpam_write_monsel_reg(msc, CSU, 0); - mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val | MSMON_CFG_x_CTL_EN); - break; + ret = mpam_write_monsel_reg(msc, CFG_CSU_FLT, flt_val); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CSU, 0); + if (ret) + return ret; + return mpam_write_monsel_reg(msc, CFG_CSU_CTL, ctl_val | MSMON_CFG_x_CTL_EN); case mpam_feat_msmon_mbwu_31counter: case mpam_feat_msmon_mbwu_44counter: case mpam_feat_msmon_mbwu_63counter: - mpam_write_monsel_reg(msc, CFG_MBWU_FLT, flt_val); - mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val); - mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val | MSMON_CFG_x_CTL_EN); + ret = mpam_write_monsel_reg(msc, CFG_MBWU_FLT, flt_val); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, ctl_val); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, + ctl_val | MSMON_CFG_x_CTL_EN); + if (ret) + return ret; /* Counting monitors require NRDY to be reset by software */ if (m->type == mpam_feat_msmon_mbwu_31counter) - mpam_write_monsel_reg(msc, MBWU, 0); - else - mpam_msc_zero_mbwu_l(m->ris->vmsc->msc); - break; + return mpam_write_monsel_reg(msc, MBWU, 0); + + return mpam_msc_zero_mbwu_l(m->ris->vmsc->msc); default: pr_warn("Unexpected monitor type %d\n", m->type); + return -EINVAL; } } From d3269953938585a23b3fa2027b34ed858ef4127c Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:00:26 +0100 Subject: [PATCH 1238/2286] arm_mpam: propagate MSC access errors for __ris_msmon_read() Allow the function for RIS accesses to return an error, and propagate read and write errors from the lower level up. This function is a bit special, since it can be called via an SMP helper, so the prototype must stay as void. So propagate any errors using the "err" field in the struct passed in as the sole argument to that function. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper [morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 37 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 72a5d1ab968486..853e5c5d71588d 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1339,6 +1339,10 @@ static u64 mpam_msmon_overflow_val(enum mpam_device_features type, return overflow_val; } +/* + * This function might be called via smp_call_function_any(), so propagate + * errors inside the arg struct. + */ static void __ris_msmon_read_locked(struct mon_read *m) { u64 now; @@ -1357,7 +1361,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, ctx->mon) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); - mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); + m->err = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); + if (m->err) + return; switch (m->type) { case mpam_feat_msmon_mbwu_31counter: @@ -1377,7 +1383,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) * Read the existing configuration to avoid re-writing the same values. * This saves waiting for 'nrdy' on subsequent reads. */ - read_msmon_ctl_flt_vals(m, &cur_ctl, &cur_flt); + m->err = read_msmon_ctl_flt_vals(m, &cur_ctl, &cur_flt); + if (m->err) + return; if (mpam_feat_msmon_mbwu_31counter == m->type) overflow = cur_ctl & MSMON_CFG_x_CTL_OFLOW_STATUS; @@ -1391,18 +1399,24 @@ static void __ris_msmon_read_locked(struct mon_read *m) cur_ctl != (ctl_val | MSMON_CFG_x_CTL_EN); if (config_mismatch || reset_on_next_read) { - write_msmon_ctl_flt_vals(m, ctl_val, flt_val); + m->err = write_msmon_ctl_flt_vals(m, ctl_val, flt_val); + if (m->err) + return; overflow = false; } else if (overflow) { - mpam_write_monsel_reg(msc, CFG_MBWU_CTL, - cur_ctl & - ~(MSMON_CFG_x_CTL_OFLOW_STATUS | - MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L)); + m->err = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, + cur_ctl & + ~(MSMON_CFG_x_CTL_OFLOW_STATUS | + MSMON_CFG_MBWU_CTL_OFLOW_STATUS_L)); + if (m->err) + return; } switch (m->type) { case mpam_feat_msmon_csu: - mpam_read_monsel_reg(msc, CSU, &now32); + m->err = mpam_read_monsel_reg(msc, CSU, &now32); + if (m->err) + return; nrdy = now32 & MSMON___NRDY; now = FIELD_GET(MSMON___VALUE, now32); @@ -1424,7 +1438,9 @@ static void __ris_msmon_read_locked(struct mon_read *m) else now = FIELD_GET(MSMON___L_VALUE, now); } else { - mpam_read_monsel_reg(msc, MBWU, &now32); + m->err = mpam_read_monsel_reg(msc, MBWU, &now32); + if (m->err) + return; nrdy = now32 & MSMON___NRDY; now = FIELD_GET(MSMON___VALUE, now32); } @@ -1831,6 +1847,9 @@ static int mpam_restore_mbwu_state(void *_ris) __ris_msmon_read_locked(&mbwu_arg); mpam_mon_sel_unlock(msc); + + if (mbwu_arg.err && mbwu_arg.err != -EBUSY) + return mbwu_arg.err; } return 0; From a3a94c13c44216a92e75d6e50331897d49c67e9b Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:04:41 +0100 Subject: [PATCH 1239/2286] arm_mpam: propagate MSC access errors for state saving function Allow the mpam_save_mbwu_state() function to return an error, and propagate read and write errors from the lower level up. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper [morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 853e5c5d71588d..39437361ed7aed 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1859,6 +1859,7 @@ static int mpam_restore_mbwu_state(void *_ris) static int mpam_save_mbwu_state(void *arg) { int i; + int ret; u64 val; struct mon_cfg *cfg; struct mon_read mbwu_arg; @@ -1877,10 +1878,18 @@ static int mpam_save_mbwu_state(void *arg) mon_sel = FIELD_PREP(MSMON_CFG_MON_SEL_MON_SEL, i) | FIELD_PREP(MSMON_CFG_MON_SEL_RIS, ris->ris_idx); - mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); - mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt); - mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl); - mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); + ret = mpam_write_monsel_reg(msc, CFG_MON_SEL, mon_sel); + if (ret) + return ret; + ret = mpam_read_monsel_reg(msc, CFG_MBWU_FLT, &cur_flt); + if (ret) + return ret; + ret = mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl); + if (ret) + return ret; + ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); + if (ret) + return ret; cfg->mon = i; cfg->pmg = FIELD_GET(MSMON_CFG_x_FLT_PMG, cur_flt); From 60e33a1864fd19d2152753caa16fbb6c4fd7bafa Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:08:10 +0100 Subject: [PATCH 1240/2286] arm_mpam: propagate MSC access errors for mpam_reprogram_ris_partid() Allow the mpam_reprogram_ris_partid() function to check for and return errors, and propagate MSC read and write errors from the lower level up. This also covers the callers of this function: mpam_reset_ris() and apply_config(). Signed-off-by: Andre Przywara Reviewed-by: Ben Horgan Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper Reviewed-by: Srivathsa L Rao [morse: Fixed merge conflicts in mpam_save_mbwu_state() and __ris_msmon_read()] Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 137 ++++++++++++++++++++++----------- 1 file changed, 94 insertions(+), 43 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 39437361ed7aed..7e7b483ff90086 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1616,7 +1616,7 @@ void mpam_msmon_reset_mbwu(struct mpam_component *comp, struct mon_cfg *ctx) } } -static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd) +static int mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd) { u32 num_words, msb; u32 bm = ~0; @@ -1625,15 +1625,20 @@ static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd) lockdep_assert_held(&msc->part_sel_lock); if (wd == 0) - return; + return 0; /* * Write all ~0 to all but the last 32bit-word, which may * have fewer bits... */ num_words = DIV_ROUND_UP(wd, 32); - for (i = 0; i < num_words - 1; i++, reg += sizeof(bm)) - __mpam_write_reg(msc, reg, bm); + for (i = 0; i < num_words - 1; i++, reg += sizeof(bm)) { + int ret; + + ret = __mpam_write_reg(msc, reg, bm); + if (ret) + return ret; + } /* * ....and then the last (maybe) partial 32bit word. When wd is a @@ -1641,7 +1646,7 @@ static void mpam_reset_msc_bitmap(struct mpam_msc *msc, u16 reg, u16 wd) */ msb = (wd - 1) % 32; bm = GENMASK(msb, 0); - __mpam_write_reg(msc, reg, bm); + return __mpam_write_reg(msc, reg, bm); } static void mpam_apply_t241_erratum(struct mpam_msc_ris *ris, u16 partid) @@ -1714,40 +1719,59 @@ static u16 mpam_wa_t241_calc_min_from_max(struct mpam_props *props, } /* Called via IPI. Call while holding an SRCU reference */ -static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, - struct mpam_config *cfg) +static int mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, + struct mpam_config *cfg) { u16 cmax = MPAMCFG_CMAX_CMAX; struct mpam_msc *msc = ris->vmsc->msc; struct mpam_props *rprops = &ris->props; + int ret; - mutex_lock(&msc->part_sel_lock); - __mpam_part_sel(ris->ris_idx, partid, msc); + guard(mutex)(&msc->part_sel_lock); + ret = __mpam_part_sel(ris->ris_idx, partid, msc); + if (ret) + return ret; if (mpam_has_feature(mpam_feat_partid_nrw, rprops)) { /* Update the intpartid mapping */ - mpam_write_partsel_reg(msc, INTPARTID, - MPAMCFG_INTPARTID_INTERNAL | partid); + ret = mpam_write_partsel_reg(msc, INTPARTID, + MPAMCFG_INTPARTID_INTERNAL | partid); + if (ret) + return ret; /* * Then switch to the 'internal' partid to update the * configuration. */ - __mpam_intpart_sel(ris->ris_idx, partid, msc); + ret = __mpam_intpart_sel(ris->ris_idx, partid, msc); + if (ret) + return ret; } if (mpam_has_feature(mpam_feat_cpor_part, rprops)) { - if (mpam_has_feature(mpam_feat_cpor_part, cfg)) - mpam_write_partsel_reg(msc, CPBM, cfg->cpbm); - else - mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM, rprops->cpbm_wd); + if (mpam_has_feature(mpam_feat_cpor_part, cfg)) { + ret = mpam_write_partsel_reg(msc, CPBM, cfg->cpbm); + if (ret) + return ret; + } else { + ret = mpam_reset_msc_bitmap(msc, MPAMCFG_CPBM, + rprops->cpbm_wd); + if (ret) + return ret; + } } if (mpam_has_feature(mpam_feat_mbw_part, rprops)) { - if (mpam_has_feature(mpam_feat_mbw_part, cfg)) - mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm); - else - mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, rprops->mbw_pbm_bits); + if (mpam_has_feature(mpam_feat_mbw_part, cfg)) { + ret = mpam_write_partsel_reg(msc, MBW_PBM, cfg->mbw_pbm); + if (ret) + return ret; + } else { + ret = mpam_reset_msc_bitmap(msc, MPAMCFG_MBW_PBM, + rprops->mbw_pbm_bits); + if (ret) + return ret; + } } if (mpam_has_feature(mpam_feat_mbw_min, rprops)) { @@ -1760,27 +1784,47 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, val = max(val, min); } - mpam_write_partsel_reg(msc, MBW_MIN, val); + ret = mpam_write_partsel_reg(msc, MBW_MIN, val); + if (ret) + return ret; } if (mpam_has_feature(mpam_feat_mbw_max, rprops)) { - if (mpam_has_feature(mpam_feat_mbw_max, cfg)) - mpam_write_partsel_reg(msc, MBW_MAX, cfg->mbw_max); - else - mpam_write_partsel_reg(msc, MBW_MAX, MPAMCFG_MBW_MAX_MAX); + if (mpam_has_feature(mpam_feat_mbw_max, cfg)) { + ret = mpam_write_partsel_reg(msc, MBW_MAX, cfg->mbw_max); + if (ret) + return ret; + } else { + ret = mpam_write_partsel_reg(msc, MBW_MAX, + MPAMCFG_MBW_MAX_MAX); + if (ret) + return ret; + } } - if (mpam_has_feature(mpam_feat_mbw_prop, rprops)) - mpam_write_partsel_reg(msc, MBW_PROP, 0); + if (mpam_has_feature(mpam_feat_mbw_prop, rprops)) { + ret = mpam_write_partsel_reg(msc, MBW_PROP, 0); + if (ret) + return ret; + } - if (mpam_has_feature(mpam_feat_cmax_cmax, rprops)) - mpam_write_partsel_reg(msc, CMAX, cmax); + if (mpam_has_feature(mpam_feat_cmax_cmax, rprops)) { + ret = mpam_write_partsel_reg(msc, CMAX, cmax); + if (ret) + return ret; + } - if (mpam_has_feature(mpam_feat_cmax_cmin, rprops)) - mpam_write_partsel_reg(msc, CMIN, 0); + if (mpam_has_feature(mpam_feat_cmax_cmin, rprops)) { + ret = mpam_write_partsel_reg(msc, CMIN, 0); + if (ret) + return ret; + } - if (mpam_has_feature(mpam_feat_cmax_cassoc, rprops)) - mpam_write_partsel_reg(msc, CASSOC, MPAMCFG_CASSOC_CASSOC); + if (mpam_has_feature(mpam_feat_cmax_cassoc, rprops)) { + ret = mpam_write_partsel_reg(msc, CASSOC, MPAMCFG_CASSOC_CASSOC); + if (ret) + return ret; + } if (mpam_has_feature(mpam_feat_intpri_part, rprops) || mpam_has_feature(mpam_feat_dspri_part, rprops)) { @@ -1804,12 +1848,14 @@ static void mpam_reprogram_ris_partid(struct mpam_msc_ris *ris, u16 partid, pri_val |= FIELD_PREP(MPAMCFG_PRI_DSPRI, dspri); } - mpam_write_partsel_reg(msc, PRI, pri_val); + ret = mpam_write_partsel_reg(msc, PRI, pri_val); + if (ret) + return ret; } mpam_quirk_post_config_change(ris, partid, cfg); - mutex_unlock(&msc->part_sel_lock); + return 0; } /* Call with msc cfg_lock held */ @@ -1936,6 +1982,7 @@ static int mpam_reset_ris(void *arg) u16 partid_max; struct mpam_config reset_cfg = {}; struct mpam_msc_ris *ris = arg; + int ret; if (ris->in_reset_state) return 0; @@ -1943,8 +1990,11 @@ static int mpam_reset_ris(void *arg) spin_lock(&partid_max_lock); partid_max = mpam_partid_max; spin_unlock(&partid_max_lock); - for (u32 partid = 0; partid <= partid_max; partid++) - mpam_reprogram_ris_partid(ris, partid, &reset_cfg); + for (u32 partid = 0; partid <= partid_max; partid++) { + ret = mpam_reprogram_ris_partid(ris, partid, &reset_cfg); + if (ret) + return ret; + } return 0; } @@ -1983,9 +2033,8 @@ static int __write_config(void *arg) { struct mpam_write_config_arg *c = arg; - mpam_reprogram_ris_partid(c->ris, c->partid, &c->comp->cfg[c->partid]); - - return 0; + return mpam_reprogram_ris_partid(c->ris, c->partid, + &c->comp->cfg[c->partid]); } static void mpam_reprogram_msc(struct mpam_msc *msc) @@ -3144,6 +3193,7 @@ int mpam_apply_config(struct mpam_component *comp, u16 partid, struct mpam_msc_ris *ris; struct mpam_vmsc *vmsc; struct mpam_msc *msc; + int ret; lockdep_assert_cpus_held(); @@ -3161,14 +3211,15 @@ int mpam_apply_config(struct mpam_component *comp, u16 partid, srcu_read_lock_held(&mpam_srcu)) { msc = vmsc->msc; - mutex_lock(&msc->cfg_lock); + guard(mutex)(&msc->cfg_lock); list_for_each_entry_srcu(ris, &vmsc->ris, vmsc_list, srcu_read_lock_held(&mpam_srcu)) { arg.ris = ris; - mpam_touch_msc(msc, __write_config, &arg); + ret = mpam_touch_msc(msc, __write_config, &arg); + if (ret) + return ret; ris->in_reset_state = false; } - mutex_unlock(&msc->cfg_lock); } return 0; From 49a5f825e9a56b5304ec26836a68132e7428a557 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:09:41 +0100 Subject: [PATCH 1241/2286] arm_mpam: propagate MSC access errors for interrupt control Allow the functions dealing with interrupt registration and enablement to check for and return errors, and propagate MSC read and write errors from the lower level up. This does not cover the IRQ handler yet, as this needs some more attention. Signed-off-by: Andre Przywara Reviewed-by: Srivathsa L Rao Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 7e7b483ff90086..c0f51d41e7fe05 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -1118,7 +1118,9 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc) } /* Clear any stale errors */ - mpam_msc_clear_esr(msc); + ret = mpam_msc_clear_esr(msc); + if (ret) + return ret; spin_lock(&partid_max_lock); mpam_partid_max = min(mpam_partid_max, msc->partid_max); @@ -2082,9 +2084,7 @@ static int mpam_enable_msc_ecr(void *_msc) { struct mpam_msc *msc = _msc; - __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN); - - return 0; + return __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN); } /* This can run in mpam_disable(), and the interrupt handler on the same CPU */ @@ -2092,9 +2092,7 @@ static int mpam_disable_msc_ecr(void *_msc) { struct mpam_msc *msc = _msc; - __mpam_write_reg(msc, MPAMF_ECR, 0); - - return 0; + return __mpam_write_reg(msc, MPAMF_ECR, 0); } static void _enable_percpu_irq(void *_irq) @@ -2823,11 +2821,12 @@ static int mpam_register_irqs(void) return err; } - mutex_lock(&msc->error_irq_lock); + guard(mutex)(&msc->error_irq_lock); msc->error_irq_req = true; - mpam_touch_msc(msc, mpam_enable_msc_ecr, msc); + err = mpam_touch_msc(msc, mpam_enable_msc_ecr, msc); + if (err) + return err; msc->error_irq_hw_enabled = true; - mutex_unlock(&msc->error_irq_lock); } return 0; @@ -2848,8 +2847,8 @@ static void mpam_unregister_irqs(void) mutex_lock(&msc->error_irq_lock); if (msc->error_irq_hw_enabled) { - mpam_touch_msc(msc, mpam_disable_msc_ecr, msc); - msc->error_irq_hw_enabled = false; + if (!mpam_touch_msc(msc, mpam_disable_msc_ecr, msc)) + msc->error_irq_hw_enabled = false; } if (msc->error_irq_req) { From cea32229ec5cd3c080fb7b88cc102c16cfd8a572 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:34 +0200 Subject: [PATCH 1242/2286] arm_mpam: propagate MSC access errors in mpam_reset_class_locked() The mpam_reset_ris() function can return an error, propagate this up through mpam_reset_component_locked() to mpam_reset_class_locked(), which is exported. The external caller is a void function, so cannot really handle the error at the moment, but at least we report everything on the MPAM interface side. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 24 ++++++++++++++++++------ drivers/resctrl/mpam_internal.h | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index c0f51d41e7fe05..31f4826ab39984 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -3040,7 +3040,7 @@ static void mpam_enable_once(void) mpam_partid_max + 1, mpam_pmg_max + 1); } -static void mpam_reset_component_locked(struct mpam_component *comp) +static int mpam_reset_component_locked(struct mpam_component *comp) { struct mpam_vmsc *vmsc; @@ -3054,26 +3054,38 @@ static void mpam_reset_component_locked(struct mpam_component *comp) srcu_read_lock_held(&mpam_srcu)) { struct mpam_msc *msc = vmsc->msc; struct mpam_msc_ris *ris; + int ret; list_for_each_entry_srcu(ris, &vmsc->ris, vmsc_list, srcu_read_lock_held(&mpam_srcu)) { - if (!ris->in_reset_state) - mpam_touch_msc(msc, mpam_reset_ris, ris); + if (!ris->in_reset_state) { + ret = mpam_touch_msc(msc, mpam_reset_ris, ris); + if (ret) + return ret; + } ris->in_reset_state = true; } } + + return 0; } -void mpam_reset_class_locked(struct mpam_class *class) +int mpam_reset_class_locked(struct mpam_class *class) { struct mpam_component *comp; + int ret; lockdep_assert_cpus_held(); guard(srcu)(&mpam_srcu); list_for_each_entry_srcu(comp, &class->components, class_list, - srcu_read_lock_held(&mpam_srcu)) - mpam_reset_component_locked(comp); + srcu_read_lock_held(&mpam_srcu)) { + ret = mpam_reset_component_locked(comp); + if (ret) + return ret; + } + + return 0; } static void mpam_reset_class(struct mpam_class *class) diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index 68a6cf2b9cc738..09f8168fb4d2c9 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -465,7 +465,7 @@ void mpam_enable(struct work_struct *work); void mpam_disable(struct work_struct *work); /* Reset all the RIS in a class under cpus_read_lock() */ -void mpam_reset_class_locked(struct mpam_class *class); +int mpam_reset_class_locked(struct mpam_class *class); int mpam_apply_config(struct mpam_component *comp, u16 partid, struct mpam_config *cfg); From ca539a37d59a65d05538b24406a9ac49869fd86c Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:35 +0200 Subject: [PATCH 1243/2286] arm_mpam: prepare mon_sel locking for MPAM-Fb The MSC MON_SEL register needs to be accessed from hardirq for the overflow interrupt, and when taking an IPI to access these registers on platforms where MSCs are not accesible from every CPU. This makes an irqsave spinlock the obvious lock to protect these registers. On systems with MPAM-Fb mailbox MSC access it must be able to sleep, meaning a mutex must be used. So MPAM-Fb platforms cannot support an overflow interrupt easily. Clearly these two methods can't exist for one MSC at the same time. Change the mon_sel locking wrapper function to only use a spinlock when the MSC is accessed directly via MMIO. In case of MPAM-Fb, we use a mutex, but only if we are in a sleepable context. If that's not the case, we return an error. This should not happen, as MPAM-Fb by design does not require an MSC access to happen from a specific CPU, so there is no need for any IPIs or preemption disabling to satisfy CPU constraints. And since overflow interrupts are not supported at the moment anyway, we also wouldn't meet the other case. Bailing out early is already happening in rare occasions today. Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Reviewed-by: Gavin Shan Tested-by: Gavin Shan # on NVIDIA Grace Hopper Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 6 ++++- drivers/resctrl/mpam_internal.h | 43 +++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 31f4826ab39984..2a0ac23cc042e7 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -2349,7 +2349,6 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev) if (err) return ERR_PTR(err); - mpam_mon_sel_lock_init(msc); msc->id = pdev->id; msc->pdev = pdev; INIT_LIST_HEAD_RCU(&msc->all_msc_list); @@ -2370,6 +2369,11 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev) else msc->iface = MPAM_IFACE_PCC; + /* Lock type depends on MSC interface used */ + err = mpam_mon_sel_lock_init(dev, msc); + if (err) + return ERR_PTR(err); + if (msc->iface == MPAM_IFACE_MMIO) { void __iomem *io; diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index 09f8168fb4d2c9..58f7523bc087bc 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -126,6 +126,12 @@ struct mpam_msc { */ raw_spinlock_t _mon_sel_lock; unsigned long _mon_sel_flags; + /* + * mon_sel_mutex is the mutex version of the _mon_sel_lock above. + * Always use the mpam_mon_sel_lock() helpers when taking the lock, + * as this will select the correct lock type automatically. + */ + struct mutex mon_sel_mutex; void __iomem *mapped_hwpage; size_t mapped_hwpage_sz; @@ -139,27 +145,50 @@ struct mpam_msc { /* Returning false here means accesses to mon_sel must fail and report an error. */ static inline bool __must_check mpam_mon_sel_lock(struct mpam_msc *msc) { - /* Locking will require updating to support a firmware backed interface */ - if (WARN_ON_ONCE(msc->iface != MPAM_IFACE_MMIO)) + if (msc->iface == MPAM_IFACE_MMIO) { + raw_spin_lock_irqsave(&msc->_mon_sel_lock, msc->_mon_sel_flags); + + return true; + } + + if (!preemptible()) return false; - raw_spin_lock_irqsave(&msc->_mon_sel_lock, msc->_mon_sel_flags); + mutex_lock(&msc->mon_sel_mutex); + return true; } static inline void mpam_mon_sel_unlock(struct mpam_msc *msc) { - raw_spin_unlock_irqrestore(&msc->_mon_sel_lock, msc->_mon_sel_flags); + if (msc->iface == MPAM_IFACE_MMIO) { + raw_spin_unlock_irqrestore(&msc->_mon_sel_lock, + msc->_mon_sel_flags); + + return; + } + + mutex_unlock(&msc->mon_sel_mutex); } static inline void mpam_mon_sel_lock_held(struct mpam_msc *msc) { - lockdep_assert_held_once(&msc->_mon_sel_lock); + if (msc->iface == MPAM_IFACE_MMIO) + lockdep_assert_held_once(&msc->_mon_sel_lock); + else + lockdep_assert_held_once(&msc->mon_sel_mutex); } -static inline void mpam_mon_sel_lock_init(struct mpam_msc *msc) +static inline int mpam_mon_sel_lock_init(struct device *dev, + struct mpam_msc *msc) { - raw_spin_lock_init(&msc->_mon_sel_lock); + if (msc->iface == MPAM_IFACE_MMIO) { + raw_spin_lock_init(&msc->_mon_sel_lock); + + return 0; + } + + return devm_mutex_init(dev, &msc->mon_sel_mutex); } DEFINE_GUARD(mon_sel, struct mpam_msc *, From 0ef7c5eb77c6cf57582bda2b96a845bdd9e8c9d2 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 1 Oct 2026 17:34:36 +0200 Subject: [PATCH 1244/2286] arm_mpam: add MPAM-Fb MSC firmware access support The Arm MPAM Firmware-backed (Fb) Profile document[1] describes an alternative way of accessing the "Memory System Components" (MSC) in an MPAM enabled system. Normally the MSCs are MMIO mapped, but in some implementations this might not be possible (MSC located outside of the local socket, MSC mapped secure-only) or desirable (direct MMIO access too slow or needs to be mediated through a control processor). MPAM-fb standardises a protocol to abstract MSC accesses, building on the SCMI protocol. Add functions that do an MSC read or write access by redirecting the request through a firmware interface. For now this done via an ACPI PCC shared memory and mailbox combination. Since the protocol used is only a small subset of the full SCMI spec, and the SCMI protocol has no full ACPI support anyway, open-code the (simple) SCMI message generation, for just the fields we need. [1] https://developer.arm.com/documentation/den0144/latest Signed-off-by: Andre Przywara Reviewed-by: Jonathan Cameron Tested-by: Ritwick Sharma # on Arm AGI CPU Reviewed-by: Gavin Shan Reviewed-by: Srivathsa L Rao Reviewed-by: Ben Horgan Signed-off-by: James Morse --- drivers/acpi/arm64/mpam.c | 6 +- drivers/resctrl/Kconfig | 1 + drivers/resctrl/Makefile | 2 +- drivers/resctrl/mpam_devices.c | 65 ++++++++-- drivers/resctrl/mpam_fb.c | 217 ++++++++++++++++++++++++++++++++ drivers/resctrl/mpam_internal.h | 19 +++ include/linux/arm_mpam.h | 2 +- 7 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 drivers/resctrl/mpam_fb.c diff --git a/drivers/acpi/arm64/mpam.c b/drivers/acpi/arm64/mpam.c index 84963a20c3e788..c57bd367922b75 100644 --- a/drivers/acpi/arm64/mpam.c +++ b/drivers/acpi/arm64/mpam.c @@ -220,8 +220,8 @@ static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_ struct platform_device *pdev __free(platform_device_put) = platform_device_alloc("mpam_msc", tbl_msc->identifier); int next_res = 0, next_prop = 0, err; - /* pcc, nrdy, affinity and a sentinel */ - struct property_entry props[4] = { 0 }; + /* pcc, msc-id, nrdy, affinity and a sentinel */ + struct property_entry props[5] = { 0 }; /* mmio, 2xirq, no sentinel. */ struct resource res[3] = { 0 }; struct acpi_device *companion; @@ -256,6 +256,8 @@ static struct platform_device * __init acpi_mpam_parse_msc(struct acpi_mpam_msc_ } else if (iface == MPAM_IFACE_PCC) { props[next_prop++] = PROPERTY_ENTRY_U32("pcc-channel", tbl_msc->base_address); + props[next_prop++] = PROPERTY_ENTRY_U32("mpam-fb-msc-id", + tbl_msc->identifier); } acpi_mpam_parse_irqs(pdev, tbl_msc, res, &next_res); diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig index 672abea3b03ccb..9591d792736e5a 100644 --- a/drivers/resctrl/Kconfig +++ b/drivers/resctrl/Kconfig @@ -2,6 +2,7 @@ menuconfig ARM64_MPAM_DRIVER bool "MPAM driver" depends on ARM64 && ARM64_MPAM select ACPI_MPAM if ACPI + select MAILBOX help Memory System Resource Partitioning and Monitoring (MPAM) driver for System IP, e.g. caches and memory controllers. diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile index 4f6d0e81f9b8f3..097c036724e975 100644 --- a/drivers/resctrl/Makefile +++ b/drivers/resctrl/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_ARM64_MPAM_DRIVER) += mpam.o -mpam-y += mpam_devices.o +mpam-y += mpam_devices.o mpam_fb.o mpam-$(CONFIG_ARM64_MPAM_RESCTRL_FS) += mpam_resctrl.o ccflags-$(CONFIG_ARM64_MPAM_DRIVER_DEBUG) += -DDEBUG diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 2a0ac23cc042e7..89706106e8906f 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,21 @@ static DECLARE_WORK(mpam_broken_work, &mpam_disable); /* When mpam is disabled, the printed reason to aid debugging */ static char *mpam_disable_reason; +static int mpam_disable_errno; +static int mpam_disable_mpam_fb_err; + +void mpam_fb_disable_mpam(int err, int mpam_fb_err) +{ + /* Prevent repeated calls when mpam_disable() does MSC accesses. */ + if (!mpam_is_enabled()) + return; + + mpam_disable_errno = err; + mpam_disable_mpam_fb_err = mpam_fb_err; + + mpam_disable_reason = "MPAM-Fb error"; + schedule_work(&mpam_broken_work); +} /* * Whether resctrl has been setup. Used by cpuhp in preference to @@ -179,8 +195,11 @@ static void mpam_assert_partid_sizes_fixed(void) static int __mpam_read_reg(struct mpam_msc *msc, u16 reg, u32 *res) { - WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); + if (msc->iface == MPAM_IFACE_PCC) + return mpam_fb_send_read_request(msc, reg, res); + + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); *res = readl_relaxed(msc->mapped_hwpage + reg); return 0; @@ -197,9 +216,12 @@ static inline int _mpam_read_partsel_reg(struct mpam_msc *msc, u16 reg, static int __mpam_write_reg(struct mpam_msc *msc, u16 reg, u32 val) { - WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz); - WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); + if (msc->iface == MPAM_IFACE_PCC) + return mpam_fb_send_write_request(msc, reg, val); + + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); + WARN_ON_ONCE(reg + sizeof(u32) > msc->mapped_hwpage_sz); writel_relaxed(val, msc->mapped_hwpage + reg); return 0; @@ -1150,8 +1172,11 @@ static int mpam_msc_read_mbwu_l(struct mpam_msc *msc, u64 *res) mpam_mon_sel_lock_held(msc); - WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); - WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); + if (msc->iface == MPAM_IFACE_MMIO) { + WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), + &msc->accessibility)); + } ret = __mpam_read_reg(msc, MSMON_MBWU_L + 4, &mbwu_l_high2); if (ret) @@ -1185,8 +1210,11 @@ static int mpam_msc_zero_mbwu_l(struct mpam_msc *msc) mpam_mon_sel_lock_held(msc); - WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); - WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility)); + if (msc->iface == MPAM_IFACE_MMIO) { + WARN_ON_ONCE((MSMON_MBWU_L + sizeof(u64)) > msc->mapped_hwpage_sz); + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), + &msc->accessibility)); + } ret = __mpam_write_reg(msc, MSMON_MBWU_L, 0); if (ret) @@ -1507,11 +1535,16 @@ static int _msmon_read(struct mpam_component *comp, struct mon_read *arg) srcu_read_lock_held(&mpam_srcu)) { arg->ris = ris; - err = smp_call_function_any(&msc->accessibility, - __ris_msmon_read, arg, - true); - if (!err && arg->err) + if (msc->iface == MPAM_IFACE_MMIO) { + err = smp_call_function_any(&msc->accessibility, + __ris_msmon_read, + arg, true); + if (!err) + err = arg->err; + } else { + __ris_msmon_read(arg); err = arg->err; + } /* * Save one error to be returned to the caller, but @@ -2018,6 +2051,9 @@ static int mpam_get_msc_preferred_cpu(struct mpam_msc *msc) static int mpam_touch_msc(struct mpam_msc *msc, int (*fn)(void *a), void *arg) { + if (msc->iface != MPAM_IFACE_MMIO) + return fn(arg); + lockdep_assert_irqs_enabled(); lockdep_assert_cpus_held(); WARN_ON_ONCE(!srcu_read_lock_held((&mpam_srcu))); @@ -3148,7 +3184,12 @@ void mpam_disable(struct work_struct *ignored) mutex_unlock(&mpam_list_lock); mpam_free_garbage(); - pr_err_once("MPAM disabled due to %s\n", mpam_disable_reason); + if (mpam_disable_errno || mpam_disable_mpam_fb_err) + pr_err_once("MPAM disabled due to %s: %s, MPAM-Fb error %d\n", + mpam_disable_reason, errname(mpam_disable_errno), + mpam_disable_mpam_fb_err); + else + pr_err_once("MPAM disabled due to %s\n", mpam_disable_reason); } /* diff --git a/drivers/resctrl/mpam_fb.c b/drivers/resctrl/mpam_fb.c new file mode 100644 index 00000000000000..72f45e9582df9c --- /dev/null +++ b/drivers/resctrl/mpam_fb.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2024-2026 Arm Ltd. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mpam_internal.h" + +#define MPAM_MSC_TOKEN_MASK GENMASK_U32(27, 18) +#define MPAM_MSC_PROT_ID_MASK GENMASK_U32(17, 10) + +#define MPAM_FB_PROTOCOL_ID 0x1a + +#define MPAM_PROTOCOL_VERSION_CMD 0x0 +#define MPAM_MSC_READ_CMD 0x4 +#define MPAM_MSC_WRITE_CMD 0x5 + +#define MPAM_FB_PROT_HEADER_LEN sizeof(u32) + +#define MPAM_FB_SUCCESS 0 +#define MPAM_FB_ERR_NOT_SUPPORTED -1 +#define MPAM_FB_ERR_INVALID_PARAMETERS -2 +#define MPAM_FB_ERR_DENIED -3 +#define MPAM_FB_ERR_NOT_FOUND -4 +#define MPAM_FB_ERR_OUT_OF_RANGE -5 +#define MPAM_FB_ERR_BUSY -6 +#define MPAM_FB_ERR_COMMS_ERROR -7 +#define MPAM_FB_ERR_GENERIC_ERROR -8 +#define MPAM_FB_ERR_HW_ERROR -9 +#define MPAM_FB_ERR_PROTOCOL_ERROR -10 +#define MPAM_FB_ERR_IN_USE -11 + +static atomic_t mpam_fb_token = ATOMIC_INIT(0); + +static int mpam_fb_to_linux_errno(int mpam_fb_code) +{ + switch (mpam_fb_code) { + case MPAM_FB_ERR_NOT_SUPPORTED: + return -EOPNOTSUPP; + case MPAM_FB_ERR_INVALID_PARAMETERS: + return -EINVAL; + case MPAM_FB_ERR_NOT_FOUND: + return -ENOENT; + case MPAM_FB_ERR_OUT_OF_RANGE: + return -ERANGE; + case MPAM_FB_ERR_BUSY: + return -EBUSY; + default: + return -EINVAL; + } +} + +static void mpam_fb_build_version_message(unsigned int token, + void __iomem *msg_buf) +{ + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf; + + /* .signature is filled by the platform */ + writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags); + writel_relaxed(MPAM_FB_PROT_HEADER_LEN, &pcc_shmem->length); + writel_relaxed(MPAM_PROTOCOL_VERSION_CMD | + FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) | + FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID), + &pcc_shmem->command); +} + +static void mpam_fb_build_read_message(int msc_fb_id, int reg, + unsigned int token, + void __iomem *msg_buf) +{ + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf; + struct mpam_fb_read_payload { + u32 msc_fb_id; + u32 flags; + u32 reg_offset; + } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem); + int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload); + + /* .signature is filled by the platform */ + writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags); + writel_relaxed(msg_size, &pcc_shmem->length); + writel_relaxed(MPAM_MSC_READ_CMD | + FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) | + FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID), + &pcc_shmem->command); + + writel_relaxed(msc_fb_id, &payload->msc_fb_id); + writel_relaxed(0, &payload->flags); + writel_relaxed(reg, &payload->reg_offset); +} + +static void mpam_fb_build_write_message(int msc_fb_id, int reg, u32 val, + unsigned int token, + void __iomem *msg_buf) +{ + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem = msg_buf; + struct mpam_fb_write_payload { + u32 msc_fb_id; + u32 flags; + u32 reg_offset; + u32 value; + } __packed __iomem *payload = msg_buf + sizeof(*pcc_shmem); + int msg_size = MPAM_FB_PROT_HEADER_LEN + sizeof(*payload); + + /* .signature is filled by the platform */ + writel_relaxed(PCC_CMD_COMPLETION_NOTIFY, &pcc_shmem->flags); + writel_relaxed(msg_size, &pcc_shmem->length); + writel_relaxed(MPAM_MSC_WRITE_CMD | + FIELD_PREP(MPAM_MSC_TOKEN_MASK, token) | + FIELD_PREP(MPAM_MSC_PROT_ID_MASK, MPAM_FB_PROTOCOL_ID), + &pcc_shmem->command); + + writel_relaxed(msc_fb_id, &payload->msc_fb_id); + writel_relaxed(0, &payload->flags); + writel_relaxed(reg, &payload->reg_offset); + writel_relaxed(val, &payload->value); +} + +static int mpam_fb_send_request(struct mpam_msc *msc, u32 msc_fb_id, + u16 reg, u32 *result, int mpam_fb_command) +{ + unsigned int token = atomic_inc_return(&mpam_fb_token); + struct acpi_pcct_ext_pcc_shared_memory __iomem *pcc_shmem; + struct mpam_pcc_chan *pcc_chan; + struct pcc_mbox_chan *chan; + void __iomem *payload_ofs; + int mpam_fb_err = 0; + u32 status; + int ret; + + pcc_chan = msc->pcc_chan; + if (!pcc_chan) + return -ENODEV; + + chan = pcc_chan->pcc_chan; + + /* prune token to fit into the 10 bits inside the command register */ + token = FIELD_GET(MPAM_MSC_TOKEN_MASK, + FIELD_PREP(MPAM_MSC_TOKEN_MASK, token)); + + mutex_lock(&pcc_chan->pcc_chan_lock); + + switch (mpam_fb_command) { + case MPAM_PROTOCOL_VERSION_CMD: + mpam_fb_build_version_message(token, chan->shmem); + break; + case MPAM_MSC_READ_CMD: + mpam_fb_build_read_message(msc_fb_id, reg, token, chan->shmem); + break; + case MPAM_MSC_WRITE_CMD: + mpam_fb_build_write_message(msc_fb_id, reg, *result, + token, chan->shmem); + break; + default: + dev_err(&msc->pdev->dev, "unsupported MPAM-Fb command %d\n", + mpam_fb_command); + ret = -EINVAL; + goto out_err; + } + + dma_wmb(); + ret = mbox_send_message(chan->mchan, NULL); + if (ret < 0) + goto out_err; + + pcc_shmem = chan->shmem; + payload_ofs = chan->shmem + sizeof(*pcc_shmem); + status = readl(&pcc_shmem->command); + if (FIELD_GET(MPAM_MSC_TOKEN_MASK, status) != token) { + ret = -ETIMEDOUT; + goto out_err; + } + + mpam_fb_err = readl(payload_ofs + 0x0); + if (mpam_fb_err < 0) { + ret = mpam_fb_to_linux_errno(mpam_fb_err); + goto out_err; + } + + if (mpam_fb_command != MPAM_MSC_WRITE_CMD) + *result = readl(payload_ofs + 0x4); + + mutex_unlock(&pcc_chan->pcc_chan_lock); + + return 0; + +out_err: + mutex_unlock(&pcc_chan->pcc_chan_lock); + + mpam_fb_disable_mpam(ret, mpam_fb_err); + + /* + * TODO: Error codes are filtered in higher layers, return something + * that is known there for now, to actually report them. + */ + return -EIO; +} + +int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result) +{ + return mpam_fb_send_request(msc, msc->fb_id, reg, result, + MPAM_MSC_READ_CMD); +} + +int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value) +{ + return mpam_fb_send_request(msc, msc->fb_id, reg, &value, + MPAM_MSC_WRITE_CMD); +} diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index 58f7523bc087bc..99057d6abee1ee 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,15 @@ struct mpam_garbage { struct platform_device *pdev; }; +struct mpam_pcc_chan { + struct list_head pcc_chans; + struct mbox_client pcc_cl; + struct pcc_mbox_chan *pcc_chan; + struct mutex pcc_chan_lock; /* only one message at a time */ + struct kref refcount; + int subspace_id; +}; + struct mpam_msc { /* member of mpam_all_msc */ struct list_head all_msc_list; @@ -66,6 +76,8 @@ struct mpam_msc { /* Not modified after mpam_is_enabled() becomes true */ enum mpam_msc_iface iface; + struct mpam_pcc_chan *pcc_chan; + int fb_id; u32 nrdy_usec; cpumask_t accessibility; bool has_extd_esr; @@ -493,6 +505,9 @@ extern u8 mpam_pmg_max; void mpam_enable(struct work_struct *work); void mpam_disable(struct work_struct *work); +/* helper function to call from outside mpam_devices.c */ +void mpam_fb_disable_mpam(int err, int mpam_fb_err); + /* Reset all the RIS in a class under cpus_read_lock() */ int mpam_reset_class_locked(struct mpam_class *class); @@ -520,6 +535,10 @@ static inline void mpam_resctrl_offline_cpu(unsigned int cpu) { } static inline void mpam_resctrl_teardown_class(struct mpam_class *class) { } #endif /* CONFIG_RESCTRL_FS */ +/* MPAM-Fb Firmware-backed protocol wrappers */ +int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result); +int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value); + /* * MPAM MSCs have the following register layout. See: * Arm Memory System Resource Partitioning and Monitoring (MPAM) System diff --git a/include/linux/arm_mpam.h b/include/linux/arm_mpam.h index f92a36187a5272..002f56e1536263 100644 --- a/include/linux/arm_mpam.h +++ b/include/linux/arm_mpam.h @@ -12,7 +12,7 @@ struct mpam_msc; enum mpam_msc_iface { MPAM_IFACE_MMIO, /* a real MPAM MSC */ - MPAM_IFACE_PCC, /* a fake MPAM MSC */ + MPAM_IFACE_PCC, /* using the MPAM-Fb firmware redirection */ }; enum mpam_class_types { From 453654684a0d43715b89205918c63fa98b056123 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:12:54 +0100 Subject: [PATCH 1245/2286] arm_mpam: change MPAM-Fb error IRQ to use a threaded IRQ handler When an MPAM MSC gets into an error condition, it can trigger an error IRQ. We cannot really do much about those errors, but we at least query and log the error, then disable MPAM functionality. This error report relies on reading the MSC's error status register (ESR) in the current hard-IRQ handler, which is not possible for MPAM-Fb based MSC accesses, since they involve mailbox routines that might sleep. The same is true for clearing the interrupt at the source, which requires a (potentially sleeping) MSC access as well. Change the error IRQ handler to be a threaded interrupt, but keep the handling in the hard-IRQ part for MMIO MSCs. This is needed since the CPU affinity check in the MSC accessors requires a non-preemptible context. When the MSC is using an MPAM-Fb based access, we push the work into the threaded part of the handler, where the accessors are allowed to sleep. Also forbid per-CPU interrupts (PPIs) for MPAM-Fb, as we cannot use a threaded IRQ here. The actual IRQ handler learns how to deal with errors. We cannot really handle them, but we can try our best to disable the IRQ anyway. Should the level IRQ line deactivation fail on the device side, we mask the IRQ on the irqchip level, to prevent an interrupt storm. Signed-off-by: Andre Przywara Reviewed-by: Gavin Shan Reviewed-by: Srivathsa L Rao Reviewed-by: Jonathan Cameron Reviewed-by: Ben Horgan Signed-off-by: James Morse --- drivers/resctrl/mpam_devices.c | 77 ++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 89706106e8906f..95caa151b27ae3 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -2767,24 +2767,42 @@ static char *mpam_errcode_names[16] = { [12 ... 15] = "Reserved" }; +/* + * This will run as the threaded IRQ handler part when using MPAM-Fb, but + * as the sole hard-IRQ handler for MMIO based accesses. + */ static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc) { u64 reg; + int ret; u16 partid; u8 errcode, pmg, ris; - if (WARN_ON_ONCE(!msc) || + if (WARN_ON_ONCE(!msc)) + return IRQ_NONE; + + if (msc->iface == MPAM_IFACE_MMIO && WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), &msc->accessibility))) return IRQ_NONE; - mpam_msc_read_esr(msc, ®); + ret = mpam_msc_read_esr(msc, ®); + if (ret) { + pr_err_ratelimited("unknown error irq from msc:%u\n", msc->id); + + /* Try out best here ... */ + goto out_disable; + } errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg); if (!errcode) return IRQ_NONE; - /* Clear level triggered irq */ + /* + * Clear the level triggered IRQ. If that fails, we cannot do anything + * about it, so ignore any errors. We will disable the IRQ either on + * the device side or on the irqchip level next anyway. + */ mpam_msc_clear_esr(msc); partid = FIELD_GET(MPAMF_ESR_PARTID_MON, reg); @@ -2795,16 +2813,24 @@ static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc) msc->id, mpam_errcode_names[errcode], partid, pmg, ris); - /* Disable this interrupt. */ - mpam_disable_msc_ecr(msc); +out_disable: + /* + * Disable this interrupt on the device side. If that fails, disable + * the IRQ on the irqchip level, as we must prevent further handler + * invocations. We only take the interrupt once anyway, as we + * are going to free the IRQ next, in mpam_disable(). + */ + ret = mpam_disable_msc_ecr(msc); + if (ret) + disable_irq_nosync(irq); - /* Are we racing with the thread disabling MPAM? */ + /* Check whether we are racing with the thread disabling MPAM. */ if (!mpam_is_enabled()) return IRQ_HANDLED; /* - * Schedule the teardown work. Don't use a threaded IRQ as we can't - * unregister the interrupt from the threaded part of the handler. + * Schedule the teardown work. We have to defer it as we can't + * unregister the interrupt from the threaded part of a handler. */ mpam_disable_reason = "hardware error interrupt"; schedule_work(&mpam_broken_work); @@ -2819,10 +2845,30 @@ static irqreturn_t mpam_ppi_handler(int irq, void *dev_id) return __mpam_irq_handler(irq, msc); } -static irqreturn_t mpam_spi_handler(int irq, void *dev_id) +/* + * MMIO based MSC accesses must run in non-preemptible context, as they + * might have affinity requirements to check. + * MPAM-Fb based MSC accesses must NOT run in hard-IRQ context, as they + * can sleep. + * So split the IRQ handling up, depending on the MSC access type. + */ +static irqreturn_t mpam_shared_hard_irq(int irq, void *dev_id) { struct mpam_msc *msc = dev_id; + if (msc->iface == MPAM_IFACE_MMIO) + return __mpam_irq_handler(irq, msc); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t mpam_shared_thread_irq(int irq, void *dev_id) +{ + struct mpam_msc *msc = dev_id; + + if (msc->iface == MPAM_IFACE_MMIO) + return IRQ_HANDLED; + return __mpam_irq_handler(irq, msc); } @@ -2843,6 +2889,11 @@ static int mpam_register_irqs(void) /* The MPAM spec says the interrupt can be SPI, PPI or LPI */ /* We anticipate sharing the interrupt with other MSCs */ if (irq_is_percpu(irq)) { + if (msc->iface != MPAM_IFACE_MMIO) { + dev_err(&msc->pdev->dev, + "Only MMIO MSCs can use per-CPU interrupts\n"); + return -EINVAL; + } err = request_percpu_irq(irq, &mpam_ppi_handler, "mpam:msc:error", msc->error_dev_id); @@ -2854,9 +2905,11 @@ static int mpam_register_irqs(void) &_enable_percpu_irq, &irq, true); } else { - err = devm_request_irq(&msc->pdev->dev, irq, - &mpam_spi_handler, IRQF_SHARED, - "mpam:msc:error", msc); + err = devm_request_threaded_irq(&msc->pdev->dev, irq, + &mpam_shared_hard_irq, + &mpam_shared_thread_irq, + IRQF_SHARED | IRQF_ONESHOT, + "mpam:msc:error", msc); if (err) return err; } From cb351e3f304b260fc2f11cca5ed819fc9ac7c46d Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Fri, 2 Oct 2026 17:11:28 +0100 Subject: [PATCH 1246/2286] arm_mpam: detect and enable MPAM-Fb PCC support The Arm MPAM-Fb specification [1] describes a protocol to access MSC registers through a firmware interface. This requires a shared memory region to hold the message, and a mailbox to trigger the access. For ACPI this is wrapped as a PCC channel, described using existing ACPI abstractions. Add code to parse those PCC table descriptions associated with an MSC, and store the parsed information in the MSC struct. There can be multiple PCC channels, and each channel can serve multiple MSCs, so we need to keep track of the channel usage, using a list and a refcount. This will be used by the MPAM-Fb access wrapper code. [1] https://developer.arm.com/documentation/den0144/latest Signed-off-by: Andre Przywara Tested-by: Ritwick Sharma # on Arm AGI CPU Reviewed-by: Jonathan Cameron Tested-by: Srivathsa L Rao Reviewed-by: Srivathsa L Rao Reviewed-by: Gavin Shan Reviewed-by: Ben Horgan Signed-off-by: James Morse --- drivers/resctrl/Kconfig | 1 + drivers/resctrl/mpam_devices.c | 119 +++++++++++++++++++++++++++++++- drivers/resctrl/mpam_fb.c | 41 +++++++++++ drivers/resctrl/mpam_internal.h | 2 + 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig index 9591d792736e5a..f5ff9b1f6c48e8 100644 --- a/drivers/resctrl/Kconfig +++ b/drivers/resctrl/Kconfig @@ -2,6 +2,7 @@ menuconfig ARM64_MPAM_DRIVER bool "MPAM driver" depends on ARM64 && ARM64_MPAM select ACPI_MPAM if ACPI + select PCC if ACPI select MAILBOX help Memory System Resource Partitioning and Monitoring (MPAM) driver for diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index 95caa151b27ae3..c32175426433e8 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -20,14 +20,19 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include +#include + #include "mpam_internal.h" /* Values for the T241 errata workaround */ @@ -50,6 +55,87 @@ static LIST_HEAD(mpam_all_msc); struct srcu_struct mpam_srcu; +/* PCC channels might be serving multiple MSCs, so keep a refcounted list. */ +static DEFINE_MUTEX(pcc_chan_list_lock); +static LIST_HEAD(pcc_chan_list); + +static void mpam_pcc_chan_release(struct kref *ref) +{ + struct mpam_pcc_chan *cur = container_of(ref, struct mpam_pcc_chan, + refcount); + + pcc_mbox_free_channel(cur->pcc_chan); + list_del(&cur->pcc_chans); + mutex_destroy(&cur->pcc_chan_lock); + kfree(cur); +} + +static struct mpam_pcc_chan *mpam_pcc_chan_get(struct device *dev, + int subspace_id) +{ + struct mpam_pcc_chan *cur; + + guard(mutex)(&pcc_chan_list_lock); + + list_for_each_entry(cur, &pcc_chan_list, pcc_chans) { + if (cur->subspace_id == subspace_id) { + kref_get(&cur->refcount); + + return cur; + } + } + + cur = kzalloc_obj(*cur); + if (!cur) + return ERR_PTR(-ENOMEM); + + cur->pcc_cl.tx_block = true; + + cur->pcc_chan = pcc_mbox_request_channel(&cur->pcc_cl, subspace_id); + if (IS_ERR(cur->pcc_chan)) { + long err = PTR_ERR(cur->pcc_chan); + + kfree(cur); + return ERR_PTR(err); + } + + /* + * Timeout based on the "nominal latency" in us, from the + * PCC ACPI table. tx_tout is in ms. + * Add some margin here to be on the safe side. + */ + cur->pcc_cl.tx_tout = DIV_ROUND_UP(cur->pcc_chan->latency * 5, 1000); + + mutex_init(&cur->pcc_chan_lock); + + cur->subspace_id = subspace_id; + kref_init(&cur->refcount); + + list_add_tail(&cur->pcc_chans, &pcc_chan_list); + + return cur; +} + +static int mpam_pcc_chan_put(struct mpam_pcc_chan *pcc_chan) +{ + struct mpam_pcc_chan *cur, *tmp; + + if (!pcc_chan) + return 0; + + guard(mutex)(&pcc_chan_list_lock); + + list_for_each_entry_safe(cur, tmp, &pcc_chan_list, pcc_chans) { + if (cur == pcc_chan) { + kref_put(&cur->refcount, mpam_pcc_chan_release); + + return 0; + } + } + + return -ENOENT; +} + /* * Number of MSCs that have been probed. Once all MSCs have been probed MPAM * can be enabled. @@ -2334,6 +2420,8 @@ static void mpam_msc_destroy(struct mpam_msc *msc) mpam_ris_destroy(ris); list_del_rcu(&msc->all_msc_list); + synchronize_srcu(&mpam_srcu); + mpam_pcc_chan_put(msc->pcc_chan); platform_set_drvdata(pdev, NULL); add_to_garbage(msc); @@ -2356,7 +2444,7 @@ static void mpam_msc_drv_remove(struct platform_device *pdev) static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev) { int err; - u32 tmp; + u32 pcc_subspace_id; struct mpam_msc *msc; struct resource *msc_res; struct device *dev = &pdev->dev; @@ -2400,7 +2488,7 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev) if (err) return ERR_PTR(err); - if (device_property_read_u32(&pdev->dev, "pcc-channel", &tmp)) + if (device_property_read_u32(dev, "pcc-channel", &pcc_subspace_id)) msc->iface = MPAM_IFACE_MMIO; else msc->iface = MPAM_IFACE_PCC; @@ -2421,6 +2509,33 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev) } msc->mapped_hwpage_sz = msc_res->end - msc_res->start; msc->mapped_hwpage = io; + } else if (msc->iface == MPAM_IFACE_PCC) { + u32 mpam_fb_msc_id; + + msc->pcc_chan = mpam_pcc_chan_get(dev, pcc_subspace_id); + if (IS_ERR(msc->pcc_chan)) { + pr_err("Failed to request MSC PCC channel\n"); + return ERR_CAST(msc->pcc_chan); + } + + err = mpam_fb_check_shared_buffer_size(msc); + if (err) { + mpam_pcc_chan_put(msc->pcc_chan); + + return ERR_PTR(err); + } + + err = mpam_fb_check_protocol_version(msc); + if (err) { + mpam_pcc_chan_put(msc->pcc_chan); + + return ERR_PTR(err); + } + + if (device_property_read_u32(&pdev->dev, "mpam-fb-msc-id", + &mpam_fb_msc_id)) + mpam_fb_msc_id = msc->id; + msc->fb_id = mpam_fb_msc_id; } else { return ERR_PTR(-EINVAL); } diff --git a/drivers/resctrl/mpam_fb.c b/drivers/resctrl/mpam_fb.c index 72f45e9582df9c..54834052cbde0a 100644 --- a/drivers/resctrl/mpam_fb.c +++ b/drivers/resctrl/mpam_fb.c @@ -20,10 +20,15 @@ #define MPAM_FB_PROTOCOL_ID 0x1a #define MPAM_PROTOCOL_VERSION_CMD 0x0 +#define MPAM_FB_VERSION_MAJOR_MASK GENMASK_U32(31, 16) +#define MPAM_FB_VERSION_MINOR_MASK GENMASK_U32(15, 0) + #define MPAM_MSC_READ_CMD 0x4 #define MPAM_MSC_WRITE_CMD 0x5 #define MPAM_FB_PROT_HEADER_LEN sizeof(u32) +/* The longest message is MPAM_MSC_WRITE, with 4 parameters. */ +#define MPAM_FB_MAX_MSG_SIZE (4 * sizeof(u32)) #define MPAM_FB_SUCCESS 0 #define MPAM_FB_ERR_NOT_SUPPORTED -1 @@ -215,3 +220,39 @@ int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value) return mpam_fb_send_request(msc, msc->fb_id, reg, &value, MPAM_MSC_WRITE_CMD); } + +/* We only support MPAM-Fb protocol version 1.x */ +int mpam_fb_check_protocol_version(struct mpam_msc *msc) +{ + u32 version; + int ret; + + ret = mpam_fb_send_request(msc, 0, 0, &version, + MPAM_PROTOCOL_VERSION_CMD); + if (ret) + return ret; + + if (FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version) != 1) { + pr_err("Incompatible MPAM-Fb protocol version %d.%d\n", + FIELD_GET(MPAM_FB_VERSION_MAJOR_MASK, version), + FIELD_GET(MPAM_FB_VERSION_MINOR_MASK, version)); + + return -EINVAL; + } + + return 0; +} + +int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc) +{ + int min_buffer_size = MPAM_FB_MAX_MSG_SIZE + + sizeof(struct acpi_pcct_ext_pcc_shared_memory); + + if (msc->pcc_chan->pcc_chan->shmem_size < min_buffer_size) { + pr_err("MPAM-Fb PCC channel size too small.\n"); + + return -ENOMEM; + } + + return 0; +} diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index 99057d6abee1ee..ee7c3953a9b6c9 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -538,6 +538,8 @@ static inline void mpam_resctrl_teardown_class(struct mpam_class *class) { } /* MPAM-Fb Firmware-backed protocol wrappers */ int mpam_fb_send_read_request(struct mpam_msc *msc, u16 reg, u32 *result); int mpam_fb_send_write_request(struct mpam_msc *msc, u16 reg, u32 value); +int mpam_fb_check_protocol_version(struct mpam_msc *msc); +int mpam_fb_check_shared_buffer_size(struct mpam_msc *msc); /* * MPAM MSCs have the following register layout. See: From 24657f2ce53695e7f750c776cb007812a4d6dea3 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:14:58 +0100 Subject: [PATCH 1247/2286] firmware: arm_rmm: Check for RMI support at init Query the RMI version number and check if it is a compatible version. All RMM feature registers are read and exposed for future code to use. Also restrict the support to SMC conduit. We only support this for Little Endian kernels, the Big Endian kernel support is anyway marked BROKEN and is being removed. Cc: Marc Zyngier Cc: Oliver Upton Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Reviewed-by: Catalin Marinas Tested-by: Gavin Shan Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- MAINTAINERS | 2 + arch/arm64/Kconfig | 1 + arch/arm64/kernel/cpufeature.c | 1 + arch/arm64/kvm/sys_regs.c | 2 + drivers/firmware/Kconfig | 1 + drivers/firmware/Makefile | 1 + drivers/firmware/arm_rmm/Kconfig | 25 +++++++ drivers/firmware/arm_rmm/Makefile | 1 + drivers/firmware/arm_rmm/rmi.c | 112 ++++++++++++++++++++++++++++++ include/linux/arm-rmi-cmds.h | 48 +++++++++++++ 10 files changed, 194 insertions(+) create mode 100644 drivers/firmware/arm_rmm/Kconfig create mode 100644 drivers/firmware/arm_rmm/Makefile create mode 100644 drivers/firmware/arm_rmm/rmi.c create mode 100644 include/linux/arm-rmi-cmds.h diff --git a/MAINTAINERS b/MAINTAINERS index 16314a0193d226..22db3edec3db0c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3951,8 +3951,10 @@ S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git F: Documentation/arch/arm64/ F: arch/arm64/ +F: drivers/firmware/arm_rmm/ F: drivers/virt/coco/arm-cca-guest/ F: drivers/virt/coco/pkvm-guest/ +F: include/linux/arm-rmi-cmds.h F: include/linux/arm-smccc-rmi.h F: tools/testing/selftests/arm64/ X: arch/arm64/boot/dts/ diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..5a1b10496cf5ac 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -83,6 +83,7 @@ config ARM64 select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_SUPPORTS_PER_VMA_LOCK select ARCH_SUPPORTS_HUGE_PFNMAP if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_RMM select ARCH_SUPPORTS_RT select ARCH_SUPPORTS_SCHED_SMT select ARCH_SUPPORTS_SCHED_CLUSTER diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa7b..e8b29983b00214 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -293,6 +293,7 @@ static const struct arm64_ftr_bits ftr_id_aa64isar3[] = { static const struct arm64_ftr_bits ftr_id_aa64pfr0[] = { ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_CSV3_SHIFT, 4, 0), ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_CSV2_SHIFT, 4, 0), + ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_RME_SHIFT, 4, 0), ARM64_FTR_BITS(FTR_VISIBLE, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_DIT_SHIFT, 4, 0), ARM64_FTR_BITS(FTR_HIDDEN, FTR_NONSTRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_AMU_SHIFT, 4, 0), ARM64_FTR_BITS(FTR_HIDDEN, FTR_STRICT, FTR_LOWER_SAFE, ID_AA64PFR0_EL1_MPAM_SHIFT, 4, 0), diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c index 44aae52c473d7b..f0ef348f725b6b 100644 --- a/arch/arm64/kvm/sys_regs.c +++ b/arch/arm64/kvm/sys_regs.c @@ -2163,6 +2163,8 @@ static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val) */ val &= ~ID_AA64PFR0_EL1_MPAM_MASK; + val &= ~ID_AA64PFR0_EL1_RME_MASK; + return val; } diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index b7cc11e4fbfa65..f1a76d48242979 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -297,6 +297,7 @@ config TURRIS_MOX_RWTM_KEYCTL endif # TURRIS_MOX_RWTM source "drivers/firmware/arm_ffa/Kconfig" +source "drivers/firmware/arm_rmm/Kconfig" source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/cirrus/Kconfig" source "drivers/firmware/google/Kconfig" diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index be46f1e1dc77fa..54755db0cf6ea0 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_TRUSTED_FOUNDATIONS) += trusted_foundations.o obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o obj-y += arm_ffa/ +obj-y += arm_rmm/ obj-y += arm_scmi/ obj-y += broadcom/ obj-y += cirrus/ diff --git a/drivers/firmware/arm_rmm/Kconfig b/drivers/firmware/arm_rmm/Kconfig new file mode 100644 index 00000000000000..a37cba6647360f --- /dev/null +++ b/drivers/firmware/arm_rmm/Kconfig @@ -0,0 +1,25 @@ + +config ARCH_SUPPORTS_RMM + bool + +config ARM_RMM_RMI + bool "Realm Management Interface (RMI) Support" + depends on ARCH_SUPPORTS_RMM + help + Support the Realm Management Monitor (RMM) on Arm systems that + implement the Realm Management Extension (RME), as defined by the + Arm Confidential Compute Architecture. + + The RMM runs at EL2 in the Realm world and provides the Realm + Management Interface (RMI) used by a Normal World host to create, + manage and run protected virtual machines called Realms. The RMM can + also act as a TSM, as defined by the PCIe TDISP and can manage the + PCI IDE setup for securing the PCIe links. + + This option builds the host-side RMI support used by KVM to detect a + compatible RMM, configure it, manage delegated memory and enable + Realm guests. + + Selecting this option does not by itself make Realm guests available: + the system must also provide RME-capable hardware and firmware with a + compatible RMM implementation. diff --git a/drivers/firmware/arm_rmm/Makefile b/drivers/firmware/arm_rmm/Makefile new file mode 100644 index 00000000000000..25e6ad80d3a192 --- /dev/null +++ b/drivers/firmware/arm_rmm/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ARM_RMM_RMI) += rmi.o diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c new file mode 100644 index 00000000000000..06f5392c982d20 --- /dev/null +++ b/drivers/firmware/arm_rmm/rmi.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2026 ARM Ltd. + */ + +#include +#include +#include +#include + +#include +#include + +/* RMM v2.0 defines RmiFeatureRegister0 to RmiFeatureRegister4. */ +static unsigned long rmi_feat_reg_cache[5] __ro_after_init; + +static int rmi_check_version(void) +{ + unsigned short version_major, version_minor; + unsigned long host_version = RMI_ABI_VERSION(RMI_ABI_MAJOR_VERSION, + RMI_ABI_MINOR_VERSION); + unsigned long aa64pfr0 = read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1); + struct arm_smccc_1_2_regs res = { + SMC_RMI_VERSION, host_version, + }; + + /* If RME isn't supported, then RMI can't be */ + if (cpuid_feature_extract_unsigned_field(aa64pfr0, ID_AA64PFR0_EL1_RME_SHIFT) == 0) + return -ENXIO; + + if (arm_smccc_1_1_get_conduit() != SMCCC_CONDUIT_SMC) + return -ENXIO; + + rmi_smccc_invoke(&res); + if (res.a0 == SMCCC_RET_NOT_SUPPORTED) + return -ENXIO; + + version_major = RMI_ABI_VERSION_GET_MAJOR(res.a1); + version_minor = RMI_ABI_VERSION_GET_MINOR(res.a1); + + if (res.a0 != RMI_SUCCESS) { + unsigned short high_version_major, high_version_minor; + + high_version_major = RMI_ABI_VERSION_GET_MAJOR(res.a2); + high_version_minor = RMI_ABI_VERSION_GET_MINOR(res.a2); + + pr_err("Unsupported RMI ABI (v%d.%d - v%d.%d) we want v%d.%d\n", + version_major, version_minor, + high_version_major, high_version_minor, + RMI_ABI_MAJOR_VERSION, + RMI_ABI_MINOR_VERSION); + return -ENXIO; + } + + pr_info("RMI ABI version %d.%d\n", version_major, version_minor); + + return 0; +} + +static int rmi_read_features(void) +{ + /* + * Since we've negotiated a compatible version these feature registers + * should always be available + */ + for (int i = 0; i < ARRAY_SIZE(rmi_feat_reg_cache); i++) { + struct arm_smccc_1_2_regs args = { + SMC_RMI_FEATURES, i, + }; + + rmi_smccc_invoke(&args); + if (WARN_ON(args.a0 != RMI_SUCCESS)) + return -EINVAL; + + rmi_feat_reg_cache[i] = args.a1; + } + + return 0; +} + +unsigned long rmi_feat_reg(unsigned int index) +{ + if (WARN_ON(index >= ARRAY_SIZE(rmi_feat_reg_cache))) + return 0; + + return rmi_feat_reg_cache[index]; +} +EXPORT_SYMBOL_GPL(rmi_feat_reg); + + +static int __init arm64_init_rmi(void) +{ + int ret; + + /* If we can't agree on the RMI ABI version, don't proceed further */ + ret = rmi_check_version(); + if (ret) + return ret; + + ret = rmi_read_features(); + if (ret) + return ret; + + return 0; +} + +/* + * Note arm64_init_rmi() must be called before kvm_init_rmi() otherwise KVM + * will not support realm guests. subsys_initcall() is called before + * module_init() (used for KVM) so this is OK. + */ +subsys_initcall(arm64_init_rmi); diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h new file mode 100644 index 00000000000000..9718f2f5a88bea --- /dev/null +++ b/include/linux/arm-rmi-cmds.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2026 ARM Ltd. + */ + +#ifndef __LINUX_ARM_RMI_CMDS_H_ +#define __LINUX_ARM_RMI_CMDS_H_ + +#include +#include +#include +#include + +#define RMM_BLOCKED_RETRY_COUNT 2 +/* + * rmi_smccc_invoke: Invoke the RMI call and return the results, retrying the + * command when status is RMI_BUSY. If we encounter RMI_BLOCKED, we retry + * it one more time before we give up. The caller is supposed to handle the + * result and reissue if required. + * + * We don't expect to see RMI_BLOCKED on a practical system, except when + * there are parallel requests that results in long standing operation, + * with one blocking the other. + * + * @regs: Input parameters filled in. Updated with the output results + * after the call. + */ +static inline void rmi_smccc_invoke(struct arm_smccc_1_2_regs *regs) +{ + struct arm_smccc_1_2_regs args = *regs; + long status; + int i = 0; + + while (i < RMM_BLOCKED_RETRY_COUNT) { + arm_smccc_1_2_smc(&args, regs); + + status = RMI_RESULT_STATUS(regs->a0); + if (status != RMI_BUSY && status != RMI_BLOCKED) + break; + if (status == RMI_BLOCKED) + i++; + cpu_relax(); + } +} + +unsigned long rmi_feat_reg(unsigned int index); + +#endif /* __LINUX_ARM_RMI_CMDS_H_ */ From a0382431a0fe79d9e1c22420efc29f51234e3eff Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:14:59 +0100 Subject: [PATCH 1248/2286] firmware: arm_rmm: Configure the RMM with the host's page size RMM v2.0 brings the ability to set the RMM's granule size. Check the feature registers and configure the RMM so that it matches the host's page size. This means that operations can be done with a granularity equal to PAGE_SIZE. Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Reviewed-by: Catalin Marinas Tested-by: Gavin Shan Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rmi.c | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c index 06f5392c982d20..eeb61b453b83d9 100644 --- a/drivers/firmware/arm_rmm/rmi.c +++ b/drivers/firmware/arm_rmm/rmi.c @@ -87,6 +87,72 @@ unsigned long rmi_feat_reg(unsigned int index) } EXPORT_SYMBOL_GPL(rmi_feat_reg); +static int rmi_rmm_config_set(unsigned long cfg_ptr) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RMM_CONFIG_SET, cfg_ptr, + }; + + rmi_smccc_invoke(®s); + + return regs.a0; +} + +static int rmi_configure(void) +{ + unsigned long granule_feature; + unsigned long granule_size; + int ret; + + switch (PAGE_SIZE) { + case SZ_4K: + granule_size = RMI_GRANULE_SIZE_4KB; + granule_feature = RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_4KB; + break; + case SZ_16K: + granule_size = RMI_GRANULE_SIZE_16KB; + granule_feature = RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_16KB; + break; + case SZ_64K: + granule_size = RMI_GRANULE_SIZE_64KB; + granule_feature = RMI_FEATURE_REGISTER_1_RMI_GRAN_SZ_64KB; + break; + default: + BUILD_BUG(); + } + + if (!(rmi_feat_reg(1) & granule_feature)) { + pr_err("RMM does not support %luKB granules\n", + PAGE_SIZE >> 10); + return -ENXIO; + } + + struct rmm_config *config __free(free_page) = + (struct rmm_config *)get_zeroed_page(GFP_KERNEL); + + if (!config) { + pr_err("Unable to allocate memory for RMM config\n"); + return -ENOMEM; + } + + config->rmi_granule_size = granule_size; + + /* + * For now we set the tracking_region_size to 0 which is the only option + * for 4KB PAGE_SIZE (1GB for 4KB PAGE_SIZE, 32MB/512MB for 16KB/64KB). + * TODO: Support other tracking sizes via Kconfig option for other + * PAGE_SIZES + */ + config->tracking_region_size = 0; + + ret = rmi_rmm_config_set(virt_to_phys(config)); + if (ret) { + pr_err("RMM config set failed (%d)\n", ret); + return -EINVAL; + } + + return 0; +} static int __init arm64_init_rmi(void) { @@ -101,6 +167,10 @@ static int __init arm64_init_rmi(void) if (ret) return ret; + ret = rmi_configure(); + if (ret) + return ret; + return 0; } From bc1360cabe273886c502d8b6b2da7f05474cb923 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:15:00 +0100 Subject: [PATCH 1249/2286] firmware: arm_rmm: Add support for SRO RMM v2.0 introduces the concept of "Stateful RMI Operations" (SRO). This means that an SMC can return with an operation still in progress. The host is expected to continue the operation until it reaches a conclusion (either success or failure). During this process the RMM can request additional memory ('donate') or hand memory back to the host ('reclaim'). The host can request an in progress operation is cancelled, but still continue the operation until it has completed (otherwise the incomplete operation may cause future RMM operations to fail). All memory donate operations are cancellable and if the RMM request cannot be satisfied, we cancel the request and fail the operation. (e.g., if the allocation size > MAX_PAGE_ORDER) The SRO is tracked using a struct rmi_sro_state object which keeps track of any memory which has been allocated but not yet consumed by the RMM or reclaimed from the RMM. This allows the memory to be reused in a future request within the same operation. It will also permit an operation to be done in a context where memory allocation may be difficult (e.g. atomic context) with the option to abort the operation and retry the memory allocation outside of the atomic context. The memory stored in the struct rmi_sro_state object can then be reused on the subsequent attempt. Wrappers for SRO RMI commands are also provided here because they depend on the rmi_sro_execute() implementation added by this patch. Delegate/undelegate handles are also added here because they now use the SRO/stateful command infrastructure and are also used for the memory DONATE/RECLAIM flows. Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Reviewed-by: Catalin Marinas Tested-by: Gavin Shan Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rmi.c | 686 +++++++++++++++++++++++++++++++++ include/linux/arm-rmi-cmds.h | 41 ++ 2 files changed, 727 insertions(+) diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c index eeb61b453b83d9..1a5fa55dee2467 100644 --- a/drivers/firmware/arm_rmm/rmi.c +++ b/drivers/firmware/arm_rmm/rmi.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,691 @@ /* RMM v2.0 defines RmiFeatureRegister0 to RmiFeatureRegister4. */ static unsigned long rmi_feat_reg_cache[5] __ro_after_init; +/* + * rmi_granule_range_delegate() - Delegate granules + * @base: PA of the first granule of the range + * @top: PA of the first granule after the range + * @out_top: PA of the first granule not delegated + * + * Delegate a range of granule for use by the realm world. If the entire range + * was delegated then @out_top == @top, otherwise the function should be called + * again with @base == @out_top. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static long rmi_granule_range_delegate(unsigned long base, + unsigned long top, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_GRANULE_RANGE_DELEGATE, base, top + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +/* + * rmi_delegate_range: Delegate a physically contiguous range. + * We iterate over the range until we hit an error. So we may + * return an error, but with a partially delegated range. The + * caller must always look at the @out_phys to figure out, how + * much progress was made. + * + * @phys: Base of the physical address range + * @size: Size of the physical address range + * @out_phys: Top of the range that was completed. This is always + * valid, irrespective of the result. + * + * Returns RMI_SUCCESS on successful completion. Otherwise, returns + * the Linux error number or the RMI status code as described + * by the RMM spec for RMI_GRANULE_DELEGATE_RANGE or RMI_BLOCKED. + */ +int rmi_delegate_range(phys_addr_t phys, + unsigned long size, + phys_addr_t *out_phys) +{ + long ret = 0; + unsigned long top = phys + size; + unsigned long out_top; + + while (phys < top) { + ret = rmi_granule_range_delegate(phys, top, &out_top); + + if (ret != RMI_SUCCESS) + break; + /* + * Buggy RMM ? Let the caller handle the failure. + * We can't know how far the RMM delegated in this + * iteration, so we return the best known good limit. + * RMM can deal with granules already in "undelegated" + * in a given range. So, it is fine for the caller to + * try the range we return. + */ + if (WARN_ON(out_top <= phys)) { + ret = -ENXIO; + break; + } + phys = out_top; + } + + if (out_phys) + *out_phys = phys; + + return ret; +} +EXPORT_SYMBOL_GPL(rmi_delegate_range); + +/* + * rmi_granule_range_undelegate() - Undelegate a range of granules + * @base: Base PA of the target range + * @top: Top PA of the target range + * @out_top: Returns the top PA of range whose state is undelegated + * + * Undelegate a range of granules to allow use by the normal world. Will fail + * if the granules are in use by RMM. RMM can ignore granules that are already + * undelegated and thus is safe to be called on a range with a mix of delegated + * and undelegated granules. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static long rmi_granule_range_undelegate(unsigned long base, + unsigned long top, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_GRANULE_RANGE_UNDELEGATE, base, top + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +int rmi_undelegate_range(phys_addr_t phys, + unsigned long size) +{ + long ret = 0; + unsigned long top = phys + size; + unsigned long out_top; + + while (phys < top) { + ret = rmi_granule_range_undelegate(phys, top, &out_top); + + if (ret != RMI_SUCCESS) + break; + /* Buggy RMM ? Let the caller leak the pages */ + if (WARN_ON(out_top <= phys)) + return -ENXIO; + phys = out_top; + } + + return ret; +} +EXPORT_SYMBOL_GPL(rmi_undelegate_range); + +/* + * rmi_free_delegated_page: Undelegate and free a page that has been previously + * delegated to the Realm world. If we are unable to undelegate it, the page is + * leaked. + * NOTE: Do not use this helper if the page could be concurrently operated by + * another thread, as it may get leaked if the undelegation fails due to RMI_BLOCKED + */ +int rmi_free_delegated_page(phys_addr_t phys) +{ + if (WARN_ON_ONCE(rmi_undelegate_page(phys))) { + /* Undelegate failed: leak the page */ + return -EBUSY; + } + + free_page((unsigned long)phys_to_virt(phys)); + + return 0; +} +EXPORT_SYMBOL_GPL(rmi_free_delegated_page); + +/* + * Convert the RmiAddrBlockSize to actual size. This is used in RmiDonateReq + * and RmiAddrRangeDesc*. + */ +static unsigned long rmi_addr_block_size_to_bytes(unsigned long block_size_fld) +{ + return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(3 - block_size_fld)); +} + +/* + * free_addr_range: Free memory described by the address range entry, which may + * be partially consumed by RMM. + * + * @entry: RMI_ADDR_RANGE descriptor + * @consumed_size: Page aligned size consumed by the RMM from the address range. + * + * If the state of the address is DELEGATED, undelegate it back, before freeing. + * Leaks the memory if we cannot undelegate the range. + */ +static void free_addr_range(unsigned long entry, unsigned long consumed_size) +{ + unsigned long phys = RMI_ADDR_RANGE_ADDR(entry); + unsigned long block_size_fld = RMI_ADDR_RANGE_BLOCK_SIZE(entry); + unsigned long count = RMI_ADDR_RANGE_COUNT(entry); + unsigned long state = RMI_ADDR_RANGE_STATE(entry); + unsigned long size = rmi_addr_block_size_to_bytes(block_size_fld) * count; + + WARN_ON(!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(consumed_size)); + + /* We shouldn't see this in reclaim path, leak it for now */ + if (WARN_ON((state != RMI_OP_MEM_DELEGATED) && + (state != RMI_OP_MEM_UNDELEGATED))) + return; + + /* Adjust the address and size for partially consumed entry */ + phys += consumed_size; + size -= consumed_size; + /* + * Undelegate the pages back if required. If we can't + * change them back, leak the pages. + */ + if (state == RMI_OP_MEM_DELEGATED && + WARN_ON(rmi_undelegate_range(phys, size))) + return; + free_pages_exact(phys_to_virt(phys), size); +} + +static void rmi_op_continue(unsigned long sro_handle, unsigned long flags, + struct arm_smccc_1_2_regs *out_regs) +{ + *out_regs = (struct arm_smccc_1_2_regs) { + SMC_RMI_OP_CONTINUE, sro_handle, flags + }; + + rmi_smccc_invoke(out_regs); +} + +static void rmi_op_cancel(unsigned long sro_handle, + struct arm_smccc_1_2_regs *out_regs) +{ + *out_regs = (struct arm_smccc_1_2_regs) { + SMC_RMI_OP_CANCEL, sro_handle + }; + + rmi_smccc_invoke(out_regs); +} + +static void rmi_op_mem_donate(unsigned long sro_handle, unsigned long list_addr, + unsigned long list_count, unsigned long flags, + struct arm_smccc_1_2_regs *out_regs) +{ + *out_regs = (struct arm_smccc_1_2_regs) { + SMC_RMI_OP_MEM_DONATE, sro_handle, list_addr, list_count, flags + }; + + /* + * The output donated count (a1) is always valid, irrespective + * of the return result. i.e., 0 if there was an error + */ + rmi_smccc_invoke(out_regs); +} + +static void rmi_op_mem_reclaim(unsigned long sro_handle, + unsigned long list_addr, + unsigned long list_count, + struct arm_smccc_1_2_regs *out_regs) +{ + *out_regs = (struct arm_smccc_1_2_regs) { + SMC_RMI_OP_MEM_RECLAIM, sro_handle, list_addr, list_count + }; + + rmi_smccc_invoke(out_regs); +} + +static int rmi_sro_ensure_capacity(struct rmi_sro_state *sro, + unsigned long count) +{ + if (WARN_ON_ONCE(sro->addr_count > RMI_MAX_ADDR_LIST)) + return -EOVERFLOW; + + if (count > RMI_MAX_ADDR_LIST - sro->addr_count) + return -ENOSPC; + + return 0; +} + +static int rmi_sro_donate_contig(struct rmi_sro_state *sro, + unsigned long sro_handle, + unsigned long donatereq, + struct arm_smccc_1_2_regs *out_regs, + gfp_t gfp) +{ + unsigned long block_size_fld = RMI_DONATE_BLOCK_SIZE(donatereq); + unsigned long block_size = rmi_addr_block_size_to_bytes(block_size_fld); + unsigned long count = RMI_DONATE_COUNT(donatereq); + unsigned long state = RMI_DONATE_STATE(donatereq); + unsigned long size = block_size * count; + unsigned long addr_range; + unsigned long donated_size; + int ret; + void *virt; + phys_addr_t phys; + + /* + * The RMM specification requires contiguous allocations are always a + * power of 2 + */ + if (WARN_ON_ONCE(!is_power_of_2(size))) + return -EINVAL; + /* + * RMM clamps the Maximum value of RmiOpMemDonateReq:count to prevent + * overflow in the RMI_ADDR_RANGE_COUNT field. + */ + if (WARN_ON_ONCE(count > FIELD_MAX(RMI_ADDR_RANGE_COUNT_MASK))) + return -EINVAL; + + /* + * We can't allocate blocks more than what the buddy allocator can + * satisfy. TODO: Add support for larger allocations. + */ + if (get_order(size) > MAX_PAGE_ORDER) + return -ENOMEM; + + /* Reuse the cached address range if we have one */ + for (int i = 0; i < sro->addr_count; i++) { + unsigned long entry = sro->addr_list[i]; + + if (RMI_ADDR_RANGE_BLOCK_SIZE(entry) == block_size_fld && + RMI_ADDR_RANGE_COUNT(entry) == count && + RMI_ADDR_RANGE_STATE(entry) == state && + IS_ALIGNED(RMI_ADDR_RANGE_ADDR(entry), size)) { + sro->addr_count--; + swap(sro->addr_list[sro->addr_count], + sro->addr_list[i]); + + goto mem_donate; + } + } + + ret = rmi_sro_ensure_capacity(sro, 1); + /* In the odd case we have filled up the cache, free up the entire list */ + if (ret) + rmi_sro_free(sro); + + virt = alloc_pages_exact(size, gfp); + if (!virt) + return -ENOMEM; + phys = virt_to_phys(virt); + + if (state == RMI_OP_MEM_DELEGATED) { + phys_addr_t delegated_phys; + + if (rmi_delegate_range(phys, size, &delegated_phys)) { + if (!rmi_undelegate_range(phys, delegated_phys - phys)) + free_pages_exact(virt, size); + return -ENXIO; + } + } + + addr_range = phys & RMI_ADDR_RANGE_ADDR_MASK; + FIELD_MODIFY(RMI_ADDR_RANGE_BLOCK_SIZE_MASK, &addr_range, block_size_fld); + FIELD_MODIFY(RMI_ADDR_RANGE_COUNT_MASK, &addr_range, count); + FIELD_MODIFY(RMI_ADDR_RANGE_STATE_MASK, &addr_range, state); + + sro->addr_list[sro->addr_count] = addr_range; + +mem_donate: + rmi_op_mem_donate(sro_handle, + virt_to_phys(&sro->addr_list[sro->addr_count]), 1, + 0, out_regs); + donated_size = out_regs->a1 << PAGE_SHIFT; + + if (WARN_ON(out_regs->a1 > (size >> PAGE_SHIFT))) + donated_size = size; + + /* All granules consumed by the RMM */ + if (donated_size == size) + return 0; + /* + * No granules were consumed by the RMM, cache them. This is a valid + * case, where the RMM was donated memory by another competing thread. + */ + if (donated_size == 0) { + sro->addr_count++; + return 0; + } + + /* The granules were partially consumed, reclaim the unused ones. */ + free_addr_range(sro->addr_list[sro->addr_count], donated_size); + + return 0; +} + +static int rmi_sro_donate_noncontig(struct rmi_sro_state *sro, + unsigned long sro_handle, + unsigned long donatereq, + struct arm_smccc_1_2_regs *out_regs, + gfp_t gfp) +{ + unsigned long block_size_fld = RMI_DONATE_BLOCK_SIZE(donatereq); + unsigned long block_size = rmi_addr_block_size_to_bytes(block_size_fld); + unsigned long count = RMI_DONATE_COUNT(donatereq); + unsigned long state = RMI_DONATE_STATE(donatereq); + unsigned long found = 0; + unsigned long donated_granules; + unsigned long granules_per_block = block_size >> PAGE_SHIFT; + unsigned long consumed_blocks; + int addr_list_start = sro->addr_count; + int ret, i, src; + + /* + * We can't allocate blocks more than what the buddy allocator can + * satisfy. TODO: Add support for larger allocations. + */ + if (get_order(block_size) > MAX_PAGE_ORDER) + return -ENOMEM; + /* + * Maximum value of RmiOpMemDonateReq:count is 2^(PAGE_SHIFT-2) - 1. + * But we further clamp it down by the number of entries we can do + * in one go. The RMM can request the remaining in the next iteration. + */ + if (count > RMI_MAX_ADDR_LIST) + count = RMI_MAX_ADDR_LIST; + + /* Gather the suitable entries to the end of the list */ + i = 0; + while (i < addr_list_start && found < count) { + unsigned long entry = sro->addr_list[i]; + + if (RMI_ADDR_RANGE_BLOCK_SIZE(entry) == block_size_fld && + RMI_ADDR_RANGE_COUNT(entry) == 1 && + RMI_ADDR_RANGE_STATE(entry) == state) { + addr_list_start--; + swap(sro->addr_list[addr_list_start], + sro->addr_list[i]); + found++; + /* Continue from the swapped in entry */ + continue; + } + /* Skip past the entry */ + i++; + } + + ret = rmi_sro_ensure_capacity(sro, count - found); + if (ret) { + /* If we have found some entries, donate them and try again */ + if (found) + goto mem_donate; + /* Otherwise free up the list and start again */ + rmi_sro_free(sro); + /* Reset the addr_list_start to match sro->addr_count */ + addr_list_start = 0; + } + + for (; found < count; found++) { + unsigned long addr_range; + void *virt = alloc_pages_exact(block_size, gfp); + phys_addr_t phys; + + if (!virt) + return -ENOMEM; + + phys = virt_to_phys(virt); + + if (state == RMI_OP_MEM_DELEGATED) { + phys_addr_t delegated_phys; + + if (rmi_delegate_range(phys, block_size, &delegated_phys)) { + if (!rmi_undelegate_range(phys, delegated_phys - phys)) + free_pages_exact(virt, block_size); + return -ENXIO; + } + } + + addr_range = phys & RMI_ADDR_RANGE_ADDR_MASK; + FIELD_MODIFY(RMI_ADDR_RANGE_BLOCK_SIZE_MASK, &addr_range, block_size_fld); + FIELD_MODIFY(RMI_ADDR_RANGE_COUNT_MASK, &addr_range, 1); + FIELD_MODIFY(RMI_ADDR_RANGE_STATE_MASK, &addr_range, state); + + sro->addr_list[sro->addr_count++] = addr_range; + } + +mem_donate: + rmi_op_mem_donate(sro_handle, + virt_to_phys(&sro->addr_list[addr_list_start]), + found, 0, out_regs); + + donated_granules = out_regs->a1; + /* + * The RMM shouldn't report more granules than we provided, but clamp + * just in case. + */ + if (WARN_ON_ONCE(donated_granules > found * granules_per_block)) + donated_granules = found * granules_per_block; + + /* + * The RMM reports the consumed memory in terms of granules, but we + * track in the address lists in block-sized ranges. So divide to get + * the number of (complete) consumed blocks. + */ + consumed_blocks = donated_granules / granules_per_block; + if (donated_granules % granules_per_block) { + /* + * A block has been partially consumed, the start is owned by + * the RMM, the tail is owned by the host + */ + unsigned long entry = + sro->addr_list[addr_list_start + consumed_blocks]; + unsigned long donated_size = + (donated_granules % granules_per_block) << PAGE_SHIFT; + + free_addr_range(entry, donated_size); + /* + * This block is now fully 'consumed' (either held by the RMM or + * freed) + */ + consumed_blocks++; + } + + /* + * Keep just the blocks the RMM didn't use in addr_list + * RMM claimed consumed_blocks entries from addr_list_start. + * Move the entries left out at the end i.e., + * [ addr_list_start + consumed_blocks, addr_list_start + found) + * to the rest of the valid entries and adjust the addr_count to + * reflect the available entries. + */ + for (i = 0, src = addr_list_start + consumed_blocks; + i < found - consumed_blocks; i++) + sro->addr_list[addr_list_start + i] = sro->addr_list[src + i]; + + sro->addr_count -= consumed_blocks; + + return 0; +} + +static int rmi_sro_donate(struct rmi_sro_state *sro, + unsigned long sro_handle, + unsigned long donatereq, + struct arm_smccc_1_2_regs *regs, + gfp_t gfp) +{ + unsigned long state = RMI_DONATE_STATE(donatereq); + + if (WARN_ON_ONCE(!RMI_DONATE_COUNT(donatereq))) + return -EINVAL; + + /* + * We do not support RMI_OP_MEM_CONDITIONAL yet. This is only required + * for use in RMI_GRANULE_TRACKING_SET, which we don't support yet. + */ + if (WARN_ON_ONCE((state != RMI_OP_MEM_DELEGATED) && + (state != RMI_OP_MEM_UNDELEGATED))) + return -EINVAL; + + if (RMI_DONATE_CONTIG(donatereq) == RMI_OP_MEM_CONTIG) + return rmi_sro_donate_contig(sro, sro_handle, donatereq, regs, gfp); + else + return rmi_sro_donate_noncontig(sro, sro_handle, donatereq, regs, gfp); +} + +static int rmi_sro_reclaim(struct rmi_sro_state *sro, + unsigned long sro_handle, + struct arm_smccc_1_2_regs *out_regs) +{ + unsigned long capacity; + + /* + * We don't do a partial free of the entries. So for now free the + * entire address list as we prepare to reclaim more from the RMM. + */ + if (rmi_sro_ensure_capacity(sro, 1)) + rmi_sro_free(sro); + + capacity = RMI_MAX_ADDR_LIST - sro->addr_count; + + rmi_op_mem_reclaim(sro_handle, + virt_to_phys(&sro->addr_list[sro->addr_count]), + capacity, out_regs); + + /* + * RMI_OP_MEM_RECLAIM always return RMI_INCOMPLETE, except when the + * input parameters were invalid. + */ + if (WARN_ON_ONCE(RMI_RESULT_STATUS(out_regs->a0) != RMI_INCOMPLETE)) + return -EINVAL; + if (WARN_ON_ONCE(out_regs->a1 > capacity)) + out_regs->a1 = capacity; + + sro->addr_count += out_regs->a1; + + return 0; +} + +void rmi_sro_free(struct rmi_sro_state *sro) +{ + /* Handle the worse */ + if (WARN_ON(sro->addr_count < 0)) + return; + + if (WARN_ON(sro->addr_count > RMI_MAX_ADDR_LIST)) + sro->addr_count = RMI_MAX_ADDR_LIST; + + for (int i = 0; i < sro->addr_count; i++) + free_addr_range(sro->addr_list[i], 0); + + sro->addr_count = 0; +} +EXPORT_SYMBOL_GPL(rmi_sro_free); + +long rmi_sro_memxfer_execute(struct rmi_sro_state *sro, gfp_t gfp) +{ + struct arm_smccc_1_2_regs *regs = &sro->regs; + bool cancelled = false; + unsigned long sro_handle; + + rmi_smccc_invoke(regs); + + sro_handle = regs->a1; + while (RMI_RESULT_STATUS(regs->a0) == RMI_INCOMPLETE) { + bool can_cancel = RMI_RESULT_CAN_CANCEL(regs->a0) == RMI_OP_CAN_CANCEL; + int ret = 0; + + switch (RMI_RESULT_MEMREQ(regs->a0)) { + case RMI_OP_MEM_REQ_NONE: + rmi_op_continue(sro_handle, RMI_CONTINUE_KEEP_GOING, + regs); + break; + case RMI_OP_MEM_REQ_DONATE: + ret = rmi_sro_donate(sro, sro_handle, regs->a2, regs, + gfp); + break; + case RMI_OP_MEM_REQ_RECLAIM: + ret = rmi_sro_reclaim(sro, sro_handle, regs); + break; + default: + WARN_ON_ONCE(1); + ret = -ENXIO; + } + + if (ret) { + /* + * All memory donating SROs must be cancellable. So a + * failure in memory allocation shouldn't be an issue. + * However, if we encounter a random failure (e.g., + * buggy RMM), don't loop forever, just give up. + */ + if (WARN_ON_ONCE(!can_cancel)) + return ret; + /* + * If we have already cancelled, and came back here due + * to an error in MEMREQ, then there is no point + * in going in loops. + */ + if (WARN_ON_ONCE(cancelled)) + break; + rmi_op_cancel(sro_handle, regs); + cancelled = true; + + if (WARN_ON_ONCE(RMI_RESULT_STATUS(regs->a0) != RMI_INCOMPLETE)) + return ret; + } + } + + if (cancelled) + return -ECANCELED; + + return regs->a0; +} +EXPORT_SYMBOL_GPL(rmi_sro_memxfer_execute); + +/* + * rmi_sro_execute: Execute an RMI command that is stateful but not memory + * transferring. Takes regs, filled with the FIDs and the arguments in place. + * + * Returns : + * -ECANCELED - If the operation had to be aborted and SRO was cancellable. + * Otherwise, returns the result of the RMI command. + */ +long rmi_sro_execute(struct arm_smccc_1_2_regs *regs) +{ + bool cancelled = false; + unsigned long sro_handle; + + rmi_smccc_invoke(regs); + + sro_handle = regs->a1; + while (RMI_RESULT_STATUS(regs->a0) == RMI_INCOMPLETE) { + bool can_cancel = RMI_RESULT_CAN_CANCEL(regs->a0) == RMI_OP_CAN_CANCEL; + + switch (RMI_RESULT_MEMREQ(regs->a0)) { + case RMI_OP_MEM_REQ_NONE: + rmi_op_continue(sro_handle, RMI_CONTINUE_KEEP_GOING, + regs); + break; + default: + WARN_ON_ONCE(1); + if (!can_cancel) + return regs->a0; + /* + * We can't get here normally, but handle this anyway + * for a buggy RMM implementation. + */ + if (cancelled) + return -ECANCELED; + rmi_op_cancel(sro_handle, regs); + cancelled = true; + } + } + + if (cancelled) + return -ECANCELED; + + return regs->a0; +} +EXPORT_SYMBOL_GPL(rmi_sro_execute); + static int rmi_check_version(void) { unsigned short version_major, version_minor; diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h index 9718f2f5a88bea..dd5380ace4e9ec 100644 --- a/include/linux/arm-rmi-cmds.h +++ b/include/linux/arm-rmi-cmds.h @@ -8,9 +8,19 @@ #include #include +#include #include +#include #include +#define RMI_MAX_ADDR_LIST 256 + +struct rmi_sro_state { + struct arm_smccc_1_2_regs regs; + int addr_count; + unsigned long addr_list[RMI_MAX_ADDR_LIST]; +}; + #define RMM_BLOCKED_RETRY_COUNT 2 /* * rmi_smccc_invoke: Invoke the RMI call and return the results, retrying the @@ -45,4 +55,35 @@ static inline void rmi_smccc_invoke(struct arm_smccc_1_2_regs *regs) unsigned long rmi_feat_reg(unsigned int index); +int rmi_delegate_range(phys_addr_t phys, unsigned long size, + phys_addr_t *out_phys); +int rmi_undelegate_range(phys_addr_t phys, unsigned long size); +int rmi_free_delegated_page(phys_addr_t phys); + +static inline int rmi_delegate_page(phys_addr_t phys) +{ + return rmi_delegate_range(phys, PAGE_SIZE, NULL); +} + +static inline int rmi_undelegate_page(phys_addr_t phys) +{ + return rmi_undelegate_range(phys, PAGE_SIZE); +} + +long rmi_sro_memxfer_execute(struct rmi_sro_state *sro, gfp_t gfp); +void rmi_sro_free(struct rmi_sro_state *sro); +long rmi_sro_execute(struct arm_smccc_1_2_regs *regs); + +/* + * Resetting the addr_count is sufficient to ignore the addr_list contents. + */ +#define rmi_sro_memxfer_cmd(sro, gfp, ...) ({ \ + struct rmi_sro_state *__sro = (sro); \ + __sro->addr_count = 0; \ + __sro->regs = (struct arm_smccc_1_2_regs){ __VA_ARGS__ }; \ + long __ret = rmi_sro_memxfer_execute(__sro, gfp); \ + rmi_sro_free(__sro); \ + __ret; \ +}) + #endif /* __LINUX_ARM_RMI_CMDS_H_ */ From fa193c502990c08cb05dcc11ed842ba39881088b Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:15:01 +0100 Subject: [PATCH 1250/2286] firmware: arm_rmm: Activate the RMM Activate the RMM after the basic configuration. This is a memory transferring stateful operation. Reviewed-by: Gavin Shan Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Tested-by: Gavin Shan Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rmi.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c index 1a5fa55dee2467..b9788be89a9174 100644 --- a/drivers/firmware/arm_rmm/rmi.c +++ b/drivers/firmware/arm_rmm/rmi.c @@ -857,7 +857,18 @@ static int __init arm64_init_rmi(void) if (ret) return ret; - return 0; + /* Activate the RMM */ + struct rmi_sro_state *sro __free(kfree) = kmalloc_obj(*sro); + if (!sro) + return -ENOMEM; + + ret = rmi_sro_memxfer_cmd(sro, GFP_KERNEL, SMC_RMI_RMM_ACTIVATE); + if (ret) { + pr_err("RMM activate failed (%d)\n", ret); + ret = ret < 0 ? ret : -ENXIO; + } + + return ret; } /* From c20b65a03eff7c5b4fbd0122ceb89fb53b823155 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:15:02 +0100 Subject: [PATCH 1251/2286] firmware: arm_rmm: Ensure the RMM has GPT entries for memory The RMM maintains the state of all the granules in the system to make sure that the host is abiding by the rules. This state can be maintained at different granularity, per page (TRACKING_FINE) or per region (TRACKING_COARSE or TRACKING_INTERMEDIATE). The region size depends on the underlying "RMI_GRANULE_SIZE". For a "coarse"/"intermediate" region, all pages in the region must be of the same state, this implies we need to have "fine" tracking for DRAM, so that we can delegate individual pages. For now we only support a statically carved out memory for tracking granules for the "fine" regions. This can be extended in the future to allow modifying the tracking granularity and remove the need for a static allocation by the firmware. Similarly, the firmware may create L0 GPT entries describing the total address space. But if we change the "PAS" (Physical Address Space) of a granule, then the firmware may need to create L1 tables to track the PAS at a finer granularity. Linux therefore checks if the platform firmware manages the PAR region. i.e., the firmware is in charge of managing the L1 GPTs (creation and the required memory for the GPT tables - via static carveouts) without host intervention. Support for dynamic GPT creation by the host will be added later. If the firmware requires us to manage the tracking or GPT memory, deactivate the RMM and reclaim any memory donated at RMM activation. Apply the same checks when hotplugged memory is brought online. Reviewed-by: Jonathan Cameron Reviewed-by: Gavin Shan Reviewed-by: Catalin Marinas Tested-by: Gavin Shan Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rmi.c | 247 ++++++++++++++++++++++++++++++++- include/linux/arm-rmi-cmds.h | 18 +++ 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c index b9788be89a9174..0f102160eb0b40 100644 --- a/drivers/firmware/arm_rmm/rmi.c +++ b/drivers/firmware/arm_rmm/rmi.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,25 @@ /* RMM v2.0 defines RmiFeatureRegister0 to RmiFeatureRegister4. */ static unsigned long rmi_feat_reg_cache[5] __ro_after_init; +static bool arm64_rmm_active; +static bool arm64_rmi_is_available; + +/* is_rmm_active: Returns if the RMM is ACTIVE */ +bool is_rmm_active(void) +{ + return READ_ONCE(arm64_rmm_active); +} +EXPORT_SYMBOL_GPL(is_rmm_active); + +/* + * is_rmi_available: Returns if the RMM is ACTIVE and is configured with static + * carveout for metadata nad L1GPTs. + */ +bool is_rmi_available(void) +{ + return READ_ONCE(arm64_rmi_is_available); +} +EXPORT_SYMBOL_GPL(is_rmi_available); /* * rmi_granule_range_delegate() - Delegate granules @@ -840,6 +860,216 @@ static int rmi_configure(void) return 0; } +/** + * rmi_granule_tracking_get() - Get configuration of a Granule tracking region + * @start: Base PA of the tracking region + * @end: End of the PA region + * @out_category: Memory category + * @out_state: Tracking region state + * @out_top: Top of the memory region + * + * Return: RMI return code + */ +static int rmi_granule_tracking_get(unsigned long start, + unsigned long end, + unsigned long *out_category, + unsigned long *out_state, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_GRANULE_TRACKING_GET, start, end, + }; + + rmi_smccc_invoke(®s); + + if (regs.a0 != RMI_SUCCESS) + return regs.a0; + + if (out_category) + *out_category = regs.a1; + if (out_state) + *out_state = regs.a2; + if (out_top) + *out_top = regs.a3; + + return RMI_SUCCESS; +} + +/* + * Make sure the area is tracked by RMM at FINE granularity. + * We do not support changing the tracking yet. + */ +static int rmi_verify_memory_tracking(phys_addr_t start, phys_addr_t end) +{ + while (start < end) { + unsigned long ret, category, state, next; + + ret = rmi_granule_tracking_get(start, end, &category, &state, &next); + if (ret != RMI_SUCCESS) { + pr_err("RMI_GRANULE_TRACKING_GET failed for %llx-%llx: (%lu)\n", + start, end, ret); + return -ENXIO; + } + + if (WARN_ON(next <= start)) + return -ENXIO; + + if (state != RMI_TRACKING_FINE || + category != RMI_MEM_CATEGORY_CONVENTIONAL) { + /* TODO: Set granule tracking in this case */ + pr_err("Granule tracking for region isn't fine/conventional: %llx-%lx\n", + start, next); + return -ENODEV; + } + start = next; + } + + return 0; +} + +/* + * rmi_gpt_info - Query the GPT info for the given PAR. + * @start: Base of the physical address region + * @end: Top of the physical address region + * @out_top: Top of the physical address region for which the GPT @out_gpt_par_state + * is valid + * @out_gpt_par_state: State of the GPT covered by [start, out_top) + */ +static long rmi_gpt_info(unsigned long start, unsigned long end, + unsigned long *out_top, + unsigned long *out_gpt_par_state) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_GPT_INFO, start, end, + }; + + rmi_smccc_invoke(®s); + if (regs.a0 != RMI_SUCCESS) + return regs.a0; + + if (out_top) + *out_top = regs.a1; + if (out_gpt_par_state) + *out_gpt_par_state = regs.a2; + + return RMI_SUCCESS; +} + +/* + * We do not support creating L1 GPTs yet. So, make sure that all the regions + * are managed by the firmware. + */ +static int rmi_verify_gpt_firmware_managed(phys_addr_t start, phys_addr_t end) +{ + unsigned long l0gpt_sz; + unsigned long next, par_state; + + l0gpt_sz = 1UL << (30 + FIELD_GET(RMI_FEATURE_REGISTER_1_L0GPTSZ, + rmi_feat_reg(1))); + start = ALIGN_DOWN(start, l0gpt_sz); + end = ALIGN(end, l0gpt_sz); + + while (start < end) { + long ret = rmi_gpt_info(start, end, &next, &par_state); + + if (ret != RMI_SUCCESS) { + pr_err("RMI_GPT_INFO failed for %llx-%llx: (%ld)\n", + start, end, ret); + return -ENXIO; + } + + if (WARN_ON(next <= start)) + return -ENXIO; + + if (par_state != RMI_GPT_PAR_PLAT) { + pr_err("GPT for the region is not managed by firmware %llx-%lx\n", + start, next); + return -ENODEV; + } + start = next; + } + + return 0; +} + +static int rmi_prepare_memory(phys_addr_t start, phys_addr_t end) +{ + int ret; + + if (start >= end) + return -EINVAL; + + ret = rmi_verify_memory_tracking(start, end); + if (ret) + return ret; + + return rmi_verify_gpt_firmware_managed(start, end); +} + +static int rmi_init_metadata(void) +{ + phys_addr_t start, end; + struct memblock_region *r; + + for_each_mem_region(r) { + int ret; + + /* Firmware-reserved NOMAP regions are not usable system RAM */ + if (memblock_is_nomap(r)) + continue; + + start = PAGE_ALIGN(r->base); + /* + * We always deal with PAGE_SIZE and if in the odd case this + * region boundary is not PAGE aligned, we stick to the page + * that we can use from the region. + */ + end = PAGE_ALIGN_DOWN(r->base + r->size); + /* Too small ? */ + if (start >= end) + continue; + + ret = rmi_prepare_memory(start, end); + if (ret) + return ret; + } + + return 0; +} + +static int rmi_memory_notifier(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct memory_notify *arg = data; + phys_addr_t start, end; + int ret; + + if (action != MEM_GOING_ONLINE) + return NOTIFY_DONE; + + start = PFN_PHYS(arg->start_pfn); + end = PFN_PHYS(arg->start_pfn + arg->nr_pages); + + ret = rmi_prepare_memory(start, end); + + return notifier_from_errno(ret); +} + +static struct notifier_block rmi_memory_nb = { + .notifier_call = rmi_memory_notifier, +}; + +static int rmi_init_memory(void) +{ + int ret; + + ret = rmi_init_metadata(); + if (ret) + return ret; + + return register_memory_notifier(&rmi_memory_nb); +} + static int __init arm64_init_rmi(void) { int ret; @@ -866,9 +1096,24 @@ static int __init arm64_init_rmi(void) if (ret) { pr_err("RMM activate failed (%d)\n", ret); ret = ret < 0 ? ret : -ENXIO; + return ret; } - return ret; + WRITE_ONCE(arm64_rmm_active, true); + + ret = rmi_init_memory(); + if (ret) { + /* Deactivate the RMM */ + if (!WARN_ON(rmi_sro_memxfer_cmd(sro, GFP_KERNEL, SMC_RMI_RMM_DEACTIVATE))) + WRITE_ONCE(arm64_rmm_active, false); + return ret; + } + + /* Mark the RMM is usable */ + WRITE_ONCE(arm64_rmi_is_available, true); + pr_info("RMI configured\n"); + + return 0; } /* diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h index dd5380ace4e9ec..de2da311cc706b 100644 --- a/include/linux/arm-rmi-cmds.h +++ b/include/linux/arm-rmi-cmds.h @@ -86,4 +86,22 @@ long rmi_sro_execute(struct arm_smccc_1_2_regs *regs); __ret; \ }) +#ifdef CONFIG_ARM_RMM_RMI + +bool is_rmm_active(void); +bool is_rmi_available(void); + +#else + +static inline bool is_rmm_active(void) +{ + return false; +} + +static inline bool is_rmi_available(void) +{ + return false; +} +#endif /* CONFIG_ARM_RMM_RMI */ + #endif /* __LINUX_ARM_RMI_CMDS_H_ */ From 5487bff69b85ca9bde7884655e2e45997d947acb Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 2 Oct 2026 07:15:03 +0100 Subject: [PATCH 1252/2286] PM: hibernate: Add an arch hook for preventing hibernation Add a hook to allow arch code disable hibernation. On arm64, with an RMM active, there could be pages that are delegated to the "Realm" security state. Any access to them are fatal (e.g., hibernate save image or restoring an image to a delegated page). pKVM host also has its restrictions on enabling hibernation. Add a weak arch hook to let the architectures have a say. Cc: "Rafael J. Wysocki" Cc: Len Brown Cc: Pavel Machek Cc: linux-pm@vger.kernel.org Cc: Sudeep Holla Suggested-by: Catalin Marinas Signed-off-by: Suzuki K Poulose Reviewed-by: Catalin Marinas Reviewed-by: Sudeep Holla Signed-off-by: Catalin Marinas --- include/linux/suspend.h | 1 + kernel/power/hibernate.c | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/linux/suspend.h b/include/linux/suspend.h index b02876f1ae38ac..a3815027773c55 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -401,6 +401,7 @@ int hibernate_quiet_exec(int (*func)(void *data), void *data); int hibernate_resume_nonboot_cpu_disable(void); int arch_hibernation_header_save(void *addr, unsigned int max_size); int arch_hibernation_header_restore(void *addr); +bool arch_hibernation_available(void); #else /* CONFIG_HIBERNATION */ static inline void register_nosave_region(unsigned long b, unsigned long e) {} diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a4c..9d9d53828542f6 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -106,11 +106,17 @@ bool hibernation_in_progress(void) return !atomic_read(&hibernate_atomic); } +__weak bool arch_hibernation_available(void) +{ + return true; +} + bool hibernation_available(void) { return nohibernate == 0 && !security_locked_down(LOCKDOWN_HIBERNATION) && - !secretmem_active() && !cxl_mem_active(); + !secretmem_active() && !cxl_mem_active() && + arch_hibernation_available(); } /** From fe4a24613bc206a8c8d77dc624c0f4fa46f5e724 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 2 Oct 2026 07:15:04 +0100 Subject: [PATCH 1253/2286] arm64: Block hibernate and kexec while RMM is active RMM can be deactivated only after all delegated granules have been reclaimed. If a new kernel is entered while any granules remain in the Realm PAS, accesses to that memory can raise a Granule Protection Fault and be fatal to the new kernel. Crash kexec/kdump needs separate handling. It can be supported only once the crash kernel can tolerate delegated memory inherited from the primary kernel. i.e., be able to read the pages safely and fixup the GPF. Until then disable the kexec completely. Hibernate has a similar problem. The image cannot be safely saved for delegated pages, as the RMM doesn't support exporting the pages. Also, on the resume path, if the RMM is active, there could be delegated granules and overwriting them is fatal. Disable both kexec and hiberation while the RMM is active. Reviewed-by: Catalin Marinas Signed-off-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- arch/arm64/kernel/hibernate.c | 14 ++++++++++++++ arch/arm64/kernel/machine_kexec.c | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index 424291c547f02a..8c39863d2d63fb 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -10,6 +10,8 @@ * Copyright (C) 2006 Rafael J. Wysocki */ #define pr_fmt(x) "hibernate: " x + +#include #include #include #include @@ -105,6 +107,18 @@ void notrace restore_processor_state(void) { } +bool arch_hibernation_available(void) +{ + /* + * If we have activated the RMM, there could be pages that are + * delegated to the RMM. Trying to save them to the image will be fatal. + * Also, we donate pages to the RMM at activation and restoring data + * to those pages are going to be fatal. + * Hence, disable the hibernation when the RMM is active + */ + return !is_rmm_active(); +} + int arch_hibernation_header_save(void *addr, unsigned int max_size) { struct arch_hibernate_hdr *hdr = addr; diff --git a/arch/arm64/kernel/machine_kexec.c b/arch/arm64/kernel/machine_kexec.c index 8f9bc2327dc856..48f343704cb540 100644 --- a/arch/arm64/kernel/machine_kexec.c +++ b/arch/arm64/kernel/machine_kexec.c @@ -6,6 +6,7 @@ * Copyright (C) Huawei Futurewei Technologies. */ +#include #include #include #include @@ -59,6 +60,16 @@ int machine_kexec_prepare(struct kimage *kimage) return -EBUSY; } + /* + * We will be able to allow kdump to proceed, once we have the support + * for handling GPF from vmcore accesses to delegated pages. Until then + * block kexec completely. + */ + if (is_rmm_active()) { + pr_err("Can't kexec: RMM is active.\n"); + return -EBUSY; + } + return 0; } From c4a16839abb1ef58888dad6f2c3dd1531a77a6f0 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 2 Oct 2026 07:15:05 +0100 Subject: [PATCH 1254/2286] firmware: arm_rmm: Add wrappers for Realm related RMI commands Introduce wrappers for the RMI functions needed for creating and managing realm guests. This will be used by the KVM to manage the Realms. Reviewed-by: Gavin Shan Reviewed-by: Jonathan Cameron Reviewed-by: Catalin Marinas Signed-off-by: Steven Price Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose [catalin.marinas@arm.com: remove regs copy in rmi_smccc_invoke_once()] Signed-off-by: Catalin Marinas --- include/linux/arm-rmi-cmds.h | 474 +++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) diff --git a/include/linux/arm-rmi-cmds.h b/include/linux/arm-rmi-cmds.h index de2da311cc706b..a84e50ac851c9d 100644 --- a/include/linux/arm-rmi-cmds.h +++ b/include/linux/arm-rmi-cmds.h @@ -53,6 +53,18 @@ static inline void rmi_smccc_invoke(struct arm_smccc_1_2_regs *regs) } } +/* + * rmi_smccc_invoke_once: Invoke the RMI call and return the results. Do not + * retry the command. Let the caller deal with RMI_BUSY or RMI_BLOCKED. + * + * @regs: Input parameters filled in. Updated with the output results + * after the call. + */ +static __always_inline void rmi_smccc_invoke_once(struct arm_smccc_1_2_regs *regs) +{ + arm_smccc_1_2_smc(regs, regs); +} + unsigned long rmi_feat_reg(unsigned int index); int rmi_delegate_range(phys_addr_t phys, unsigned long size, @@ -104,4 +116,466 @@ static inline bool is_rmi_available(void) } #endif /* CONFIG_ARM_RMM_RMI */ +/** + * rmi_rtt_data_map_init() - Create a mapping at protected IPA, copying contents + * from a given non-secure source granule. + * @rd: PA of the RD + * @data: PA of the target granule mapped in the guest + * @ipa: IPA at which the granule @data will be mapped in the guest + * @src: PA of the source granule with contents + * @flags: RMI_MEASURE_CONTENT if the contents should be measured + * + * Create a mapping from Protected IPA space to conventional memory, copying + * contents from a Non-secure Granule provided by the caller. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_data_map_init(unsigned long rd, unsigned long data, + unsigned long ipa, unsigned long src, + unsigned long flags) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_DATA_MAP_INIT, rd, data, ipa, src, flags + }; + + return rmi_sro_execute(®s); +} + +/** + * rmi_rtt_data_map() - Create mappings in protected IPA range with unknown contents + * @rd: PA of the RD + * @base: Base of the target IPA range + * @top: Top of the target IPA range + * @flags: Flags + * @oaddr: Output address set descriptor + * @out_top: Top address of range which was processed. + * + * Return: 0 on success, positive RMI result code or negative Linux error code. + * NOTE: out_top is only valid with ret == RMI_SUCCESS + */ +static inline long rmi_rtt_data_map(unsigned long rd, + unsigned long base, + unsigned long top, + unsigned long flags, + unsigned long oaddr, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_DATA_MAP, rd, base, top, flags, oaddr + }; + long ret; + + ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +/** + * rmi_rtt_data_unmap() - Remove mappings to conventional memory at a protected + * IPA range + * @rd: PA of the RD + * @base: Base of the target IPA range + * @top: Top of the target IPA range + * @flags: Flags + * @oaddr: Output address set descriptor + * @out_top: Returns top IPA of range which has been unmapped + * @out_range: Output address range + * @out_count: Number of entries in output address list + * + * Removes mappings to convention memory with a target Protected IPA range. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + * NOTE: out_top is only valid with ret == RMI_SUCCESS + */ +static inline long rmi_rtt_data_unmap(unsigned long rd, + unsigned long base, + unsigned long top, + unsigned long flags, + unsigned long oaddr, + unsigned long *out_top, + unsigned long *out_range, + unsigned long *out_count) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_DATA_UNMAP, rd, base, top, flags, oaddr + }; + long ret; + + ret = rmi_sro_execute(®s); + + if (ret != RMI_SUCCESS) + return ret; + + if (out_top) + *out_top = regs.a1; + if (out_range) + *out_range = regs.a2; + if (out_count) + *out_count = regs.a3; + + return RMI_SUCCESS; +} + +/** + * rmi_psci_complete() - Complete pending PSCI command + * @calling_rec: PA of the calling REC + * @status: Status of the PSCI request + * + * Completes a pending PSCI command. + * + * Return: RMI return code + */ +static inline long rmi_psci_complete(unsigned long calling_rec, + unsigned long status) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_PSCI_COMPLETE, calling_rec, status, + }; + + rmi_smccc_invoke(®s); + return regs.a0; +} + +/** + * rmi_realm_activate() - Activate a realm + * @rd: PA of the RD + * + * Mark a realm as Active, signalling that creation is completed, allowing + * execution of the realm. + * + * Return: RMI return code + */ +static inline long rmi_realm_activate(unsigned long rd) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_REALM_ACTIVATE, rd, + }; + + rmi_smccc_invoke(®s); + return regs.a0; +} + +/** + * rmi_realm_create() - Create a realm + * @rd: PA of the RD + * @params: PA of realm parameters + * @sro: Preallocated SRO context + * + * Create a new realm using the given parameters. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_realm_create(unsigned long rd, unsigned long params, + struct rmi_sro_state *sro) +{ + return rmi_sro_memxfer_cmd(sro, GFP_KERNEL, + SMC_RMI_REALM_CREATE, rd, params); +} + +/** + * rmi_realm_terminate() - Terminate a realm + * @rd: PA of the RD + * @sro: Preallocated SRO context + * + * Terminates a realm, moving it into a ZOMBIE state + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_realm_terminate(unsigned long rd, + struct rmi_sro_state *sro) +{ + return rmi_sro_memxfer_cmd(sro, GFP_KERNEL, + SMC_RMI_REALM_TERMINATE, rd); +} + +/** + * rmi_realm_destroy() - Destroy a realm + * @rd: PA of the RD + * @sro: Preallocated SRO context + * + * Destroys a realm, all objects belonging to the realm must be destroyed first. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_realm_destroy(unsigned long rd, + struct rmi_sro_state *sro) +{ + return rmi_sro_memxfer_cmd(sro, GFP_KERNEL, + SMC_RMI_REALM_DESTROY, rd); +} + +/** + * rmi_rec_create() - Create a REC + * @rd: PA of the RD + * @rec: PA of the target REC + * @params: PA of REC parameters + * @sro: Allocated SRO context to be used + * + * Create a REC using the parameters specified in the struct rec_params pointed + * to by @params. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rec_create(unsigned long rd, + unsigned long rec, + unsigned long params, + struct rmi_sro_state *sro) +{ + return rmi_sro_memxfer_cmd(sro, GFP_KERNEL, + SMC_RMI_REC_CREATE, rd, rec, params); +} + +/** + * rmi_rec_destroy() - Destroy a REC + * @rec: PA of the target REC + * @sro: Allocated SRO context to be used + * + * Destroys a REC. The REC must not be running. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static __always_inline long rmi_rec_destroy(unsigned long rec, + struct rmi_sro_state *sro) +{ + return rmi_sro_memxfer_cmd(sro, GFP_KERNEL, SMC_RMI_REC_DESTROY, rec); +} + +/** + * rmi_rec_enter() - Enter a REC + * @rec: PA of the target REC + * @run_ptr: PA of RecRun structure + * + * Starts (or continues) execution within a REC. + * + * Return: RMI result + */ +static __always_inline long rmi_rec_enter(unsigned long rec, unsigned long run_ptr) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_REC_ENTER, rec, run_ptr, + }; + + rmi_smccc_invoke_once(®s); + return regs.a0; +} + +/** + * rmi_rtt_create() - Creates an RTT + * @rd: PA of the RD + * @rtt: PA of the target RTT + * @ipa: Base of the IPA range described by the RTT + * @level: Depth of the RTT within the tree + * + * Creates an RTT (Realm Translation Table) at the specified level for the + * translation of the specified address within the realm. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_create(unsigned long rd, unsigned long rtt, + unsigned long ipa, long level) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_CREATE, rd, rtt, ipa, level + }; + + return rmi_sro_execute(®s); +} + +/** + * rmi_rtt_destroy() - Destroy an RTT + * @rd: PA of the RD + * @ipa: Base of the IPA range described by the RTT + * @level: RTT level + * @out_rtt: Pointer to write the PA of the RTT which was destroyed + * @out_top: Pointer to write the top IPA of non-live RTT entries, from entry + * at which the RTT walk terminated. + * + * Destroys an RTT. The RTT must be non-live, i.e. none of the entries in the + * table are in ASSIGNED or TABLE state. + * + * Return: 0 on success, positive RMI result code or negative Linux error code. + */ +static inline long rmi_rtt_destroy(unsigned long rd, + unsigned long ipa, + long level, + unsigned long *out_rtt, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_DESTROY, rd, ipa, level + }; + long ret = rmi_sro_execute(®s); + + switch (RMI_RESULT_STATUS(ret)) { + case RMI_SUCCESS: + if (out_rtt) + *out_rtt = regs.a1; + fallthrough; + case RMI_ERROR_RTT: + if (out_top) + *out_top = regs.a2; + break; + default: + break; + } + + return ret; +} + +/** + * rmi_rtt_fold() - Fold an RTT + * @rd: PA of the RD + * @ipa: Base of the IPA range described by the RTT + * @level: Depth of the RTT within the tree + * @out_rtt: Pointer to write the PA of the RTT which was destroyed + * + * Folds an RTT. If all entries with the RTT are 'homogeneous' the RTT can be + * folded into the parent and the RTT destroyed. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_fold(unsigned long rd, unsigned long ipa, + long level, unsigned long *out_rtt) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_FOLD, rd, ipa, level + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_rtt) + *out_rtt = regs.a1; + + return ret; +} + +/** + * rmi_rtt_init_ripas() - Set RIPAS for new realm + * @rd: PA of the RD + * @base: Base of target IPA region + * @top: Top of target IPA region + * @out_top: Top IPA of range whose RIPAS was modified + * + * Sets the RIPAS of a target IPA range to RAM, for a realm in the NEW state. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_init_ripas(unsigned long rd, unsigned long base, + unsigned long top, unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_INIT_RIPAS, rd, base, top + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +/** + * rmi_rtt_unprot_map() - Map unprotected granules into a realm + * @rd: PA of the RD + * @base: Base IPA of the mapping + * @top: Top of the target IPA range + * @flags: Flags + * @oaddr: Output address set descriptor + * @out_top: Top IPA of range which has been mapped + * + * Create mappings to memory within a target unprotected IPA range. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_unprot_map(unsigned long rd, + unsigned long base, + unsigned long top, + unsigned long flags, + unsigned long oaddr, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_UNPROT_MAP, rd, base, top, flags, oaddr + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +/** + * rmi_rtt_set_ripas() - Set RIPAS for an running realm + * @rd: PA of the RD + * @rec: PA of the REC making the request + * @base: Base of target IPA region + * @top: Top of target IPA region + * @out_top: Pointer to write top IPA of range whose RIPAS was modified + * + * Completes a request made by the realm to change the RIPAS of a target IPA + * range. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + */ +static inline long rmi_rtt_set_ripas(unsigned long rd, unsigned long rec, + unsigned long base, unsigned long top, + unsigned long *out_top) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_SET_RIPAS, rd, rec, base, top + }; + long ret = rmi_sro_execute(®s); + + if (ret == RMI_SUCCESS && out_top) + *out_top = regs.a1; + + return ret; +} + +/** + * rmi_rtt_unprot_unmap() - Remove mappings within an unprotected IPA range + * @rd: PA of the RD + * @base: Base IPA of the mapping + * @top: Top of the target IPA range + * @flags: Flags + * @oaddr: Output address set descriptor + * @out_top: Top IPA which has been unmapped + * @out_range: Output address range + * @out_count: Number of entries in output address list + * + * Removes mappings to memory within a target unprotected IPA range. + * + * Return: 0 on success, positive RMI result code or negative Linux error code + * NOTE: out_top is only valid with ret == RMI_SUCCESS. + */ +static inline long rmi_rtt_unprot_unmap(unsigned long rd, + unsigned long base, + unsigned long top, + unsigned long flags, + unsigned long oaddr, + unsigned long *out_top, + unsigned long *out_range, + unsigned long *out_count) +{ + struct arm_smccc_1_2_regs regs = { + SMC_RMI_RTT_UNPROT_UNMAP, rd, base, top, flags, oaddr + }; + long ret = rmi_sro_execute(®s); + + if (ret != RMI_SUCCESS) + return ret; + + if (out_top) + *out_top = regs.a1; + if (out_range) + *out_range = regs.a2; + if (out_count) + *out_count = regs.a3; + + return RMI_SUCCESS; +} + #endif /* __LINUX_ARM_RMI_CMDS_H_ */ From 06898e5320b5c12dd8c21d0ee8d5a68347001746 Mon Sep 17 00:00:00 2001 From: Suzuki K Poulose Date: Fri, 2 Oct 2026 07:15:06 +0100 Subject: [PATCH 1255/2286] firmware: arm_rmm: hotplug: Skip memory added to ZONE_MOVABLE If the hotadded memory is in ZONE_MOVABLE, we could allow that to proceed without the RMM having fine grained tracking, as we won't allocate pages from there for guest-memfd or kernel allocation Cc: David Hildenbrand (Arm) Reviewed-by: Catalin Marinas Reviewed-by: Gavin Shan Signed-off-by: Suzuki K Poulose Acked-by: David Hildenbrand (Arm) Signed-off-by: Catalin Marinas --- drivers/firmware/arm_rmm/rmi.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/arm_rmm/rmi.c b/drivers/firmware/arm_rmm/rmi.c index 0f102160eb0b40..77255a90e0a654 100644 --- a/drivers/firmware/arm_rmm/rmi.c +++ b/drivers/firmware/arm_rmm/rmi.c @@ -1050,6 +1050,17 @@ static int rmi_memory_notifier(struct notifier_block *nb, start = PFN_PHYS(arg->start_pfn); end = PFN_PHYS(arg->start_pfn + arg->nr_pages); + /* + * If the hotplugged memory region is in ZONE_MOVABLE, neither the + * guest-memfd nor the kernel allocations can come from there. So we are + * fine to skip this. + * For boot time memory, we don't allow this discount, as the + * decision to move boot memory pages to ZONE_MOVABLE is purely + * a kernel choice and the firmware must have decided what it covers. + */ + if (is_zone_movable_page(pfn_to_page(arg->start_pfn))) + return NOTIFY_DONE; + ret = rmi_prepare_memory(start, end); return notifier_from_errno(ret); From d6ae12c900f28d5ae79599947c77b2079e1f55ae Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 15 Sep 2026 03:51:05 -0700 Subject: [PATCH 1256/2286] arm64/sve: Don't zero the SVE state buffer when the SVE state is live Currently do_sve_acc() always zeroes current->thread.sve_state. This is not necessary in the common case, and avoiding the zeroing has a measurable impact on some benchmarks. In the common case where the task is not preempted and its state is not altered by a tracer, do_sve_acc() will observe that TIF_FOREIGN_FPSTATE is clear. In such cases, only the live register values matter, and the in-memory copy is stale regardless of whether it is saved in FP_STATE_FPSIMD format or FP_STATE_SVE format. It is worth skipping the zeroing because the SVE state is discarded on syscall entry, so userspace that mixes SVE and syscalls re-traps constantly. A fleet profile of arm64 hosts running services whose memset() is SVE shows the memset under do_sve_acc() accounting for 29% of the trap handling cost. Measured on a 72-core Neoverse V2 (SVE VL 128, sve_state_size 546, performance governor) with perf bench sched pipe pinned to one CPU, and SVE operation on write, so that each loop also takes an SVE access trap. * -0.99% kernel instructions * -1.38% kernel cycles * -1.12% wall clock Signed-off-by: Breno Leitao Acked-by: Mark Rutlame Signed-off-by: Catalin Marinas --- arch/arm64/kernel/fpsimd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kernel/fpsimd.c b/arch/arm64/kernel/fpsimd.c index e7f1682a3059b5..324c9799b0511a 100644 --- a/arch/arm64/kernel/fpsimd.c +++ b/arch/arm64/kernel/fpsimd.c @@ -1316,7 +1316,7 @@ void do_sve_acc(unsigned long esr, struct pt_regs *regs) return; } - sve_alloc(current, true); + sve_alloc(current, false); if (!current->thread.sve_state) { force_sig(SIGKILL); return; @@ -1341,6 +1341,7 @@ void do_sve_acc(unsigned long esr, struct pt_regs *regs) sve_flush_live(); fpsimd_bind_task_to_cpu(); } else { + memset(current->thread.sve_state, 0, sve_state_size(current)); fpsimd_to_sve(current); current->thread.fp_type = FP_STATE_SVE; fpsimd_flush_task_state(current); From 02e32115ed9875ec453237a1d6cb2d2821189f84 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Fri, 2 Oct 2026 18:21:47 +0100 Subject: [PATCH 1257/2286] firmware: arm_rmm: Add SPDX and sort the Kconfig and Makefile entries The new firmware/arm_rmm/ Kconfig and Makefile miss SPDX headers. Add them. The arm_rmm entries were added at the end of the drivers/firmware Kconfig and Makefile lists and ARCH_SUPPORTS_RMM in the middle of the ARCH_HAS_* selects in arch/arm64/Kconfig. Move them so that they stay (roughly) in alphabetical order. Signed-off-by: Catalin Marinas --- arch/arm64/Kconfig | 2 +- drivers/firmware/Kconfig | 2 +- drivers/firmware/Makefile | 2 +- drivers/firmware/arm_rmm/Kconfig | 2 +- drivers/firmware/arm_rmm/Makefile | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index ff9565d3ffa596..5a1b10496cf5ac 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -38,7 +38,6 @@ config ARM64 select ARCH_HAS_MEMBARRIER_SYNC_CORE select ARCH_HAS_MEM_ENCRYPT select ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS - select ARCH_SUPPORTS_RMM select ARCH_HAS_NMI_SAFE_THIS_CPU_OPS select ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE select ARCH_HAS_NONLEAF_PMD_YOUNG if ARM64_HAFT @@ -84,6 +83,7 @@ config ARM64 select ARCH_SUPPORTS_PAGE_TABLE_CHECK select ARCH_SUPPORTS_PER_VMA_LOCK select ARCH_SUPPORTS_HUGE_PFNMAP if TRANSPARENT_HUGEPAGE + select ARCH_SUPPORTS_RMM select ARCH_SUPPORTS_RT select ARCH_SUPPORTS_SCHED_SMT select ARCH_SUPPORTS_SCHED_CLUSTER diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index 62660bf520a8da..f1a76d48242979 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -297,6 +297,7 @@ config TURRIS_MOX_RWTM_KEYCTL endif # TURRIS_MOX_RWTM source "drivers/firmware/arm_ffa/Kconfig" +source "drivers/firmware/arm_rmm/Kconfig" source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/cirrus/Kconfig" source "drivers/firmware/google/Kconfig" @@ -310,6 +311,5 @@ source "drivers/firmware/samsung/Kconfig" source "drivers/firmware/smccc/Kconfig" source "drivers/firmware/tegra/Kconfig" source "drivers/firmware/xilinx/Kconfig" -source "drivers/firmware/arm_rmm/Kconfig" endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 196a650ccf025b..54755db0cf6ea0 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_TRUSTED_FOUNDATIONS) += trusted_foundations.o obj-$(CONFIG_TURRIS_MOX_RWTM) += turris-mox-rwtm.o obj-y += arm_ffa/ +obj-y += arm_rmm/ obj-y += arm_scmi/ obj-y += broadcom/ obj-y += cirrus/ @@ -39,4 +40,3 @@ obj-y += samsung/ obj-y += smccc/ obj-y += tegra/ obj-y += xilinx/ -obj-y += arm_rmm/ diff --git a/drivers/firmware/arm_rmm/Kconfig b/drivers/firmware/arm_rmm/Kconfig index e981e12fdbe6a8..ccf590cfda5b61 100644 --- a/drivers/firmware/arm_rmm/Kconfig +++ b/drivers/firmware/arm_rmm/Kconfig @@ -1,4 +1,4 @@ - +# SPDX-License-Identifier: GPL-2.0-only config ARCH_SUPPORTS_RMM bool diff --git a/drivers/firmware/arm_rmm/Makefile b/drivers/firmware/arm_rmm/Makefile index 3708b37a9aafc6..eac088ea693d36 100644 --- a/drivers/firmware/arm_rmm/Makefile +++ b/drivers/firmware/arm_rmm/Makefile @@ -1,2 +1,2 @@ - +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_ARM_RMM_RSI) = rsi.o From 5ec807292c55eff56387f32d5b2a0a81f2926b81 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 11 Aug 2026 14:55:55 +0100 Subject: [PATCH 1258/2286] rust: macros: vtable: hide generated `HAS_` constants for required methods These constants have to be generated as the implementation-site `#[vtable]` invocation has no way of knowing whether the methods are required or provided-but-overridden. However, these constants will have a fixed value so shouldn't be used. Thus, hide them. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260811-vtable_macro-v1-1-50c5aab3c211@garyguo.net [ Reworded for typo. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/macros/vtable.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs index be9a5ed8abe5e8..1dfc40eb53f8f9 100644 --- a/rust/macros/vtable.rs +++ b/rust/macros/vtable.rs @@ -9,7 +9,10 @@ use proc_macro2::{ Ident, TokenStream, // }; -use quote::ToTokens; +use quote::{ + quote, + ToTokens, // +}; use syn::{ parse_quote, Error, @@ -56,12 +59,18 @@ fn handle_trait(mut item: ItemTrait) -> Result { // We don't know on the implementation-site whether a method is required or provided // so we have to generate a const for all methods. + // However, hide it for required methods as it will always be true. + let doc = if fn_item.default.is_some() { + let comment = + format!("Indicates if the `{name}` method is overridden by the implementor."); + quote!(#[doc = #comment]) + } else { + quote!(#[doc(hidden)]) + }; let cfg_attrs = crate::helpers::gather_cfg_attrs(&fn_item.attrs); - let comment = - format!("Indicates if the `{name}` method is overridden by the implementor."); gen_items.push(parse_quote! { #(#cfg_attrs)* - #[doc = #comment] + #doc const #gen_const_name: bool = false; }); } From a52a7596c376ac7bce5a5322c526d96e2780ad17 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 11 Aug 2026 14:55:56 +0100 Subject: [PATCH 1259/2286] rust: macros: vtable: add `#[optional]` attribute For most users of the `#[vtable]` macro, the optional methods do not have a Rust-side default implementation; a `NULL` pointer is inserted into the vtable and the C subsystem does things differently when it sees `NULL`. Streamline this use case by providing a `#[optional]` attribute that does what the `build_error!(VTABLE_DEFAULT_ERROR)` boilerplate does. Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260811-vtable_macro-v1-2-50c5aab3c211@garyguo.net [ Rewrapped docs. Reworded message, comment and error for typos. Rebased. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/macros/lib.rs | 41 +++++++++++++---------------------------- rust/macros/vtable.rs | 26 +++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index ea2e232b61b1d6..b84db5c6bdc4f8 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -156,25 +156,15 @@ pub fn module(input: TokenStream) -> TokenStream { /// associated constant bool for each method in the trait that is set to true if /// the implementer has overridden the associated method. /// -/// For a trait method to be optional, it must have a default implementation. -/// This is also the case for traits annotated with `#[vtable]`, but in this -/// case the default implementation will never be executed. The reason for this -/// is that the functions will be called through function pointers installed in -/// C side vtables. When an optional method is not implemented on a `#[vtable]` -/// trait, a `NULL` entry is installed in the vtable. Thus the default -/// implementation is never called. Since these traits are not designed to be -/// used on the Rust side, it should not be possible to call the default -/// implementation. This is done to ensure that we call the vtable methods -/// through the C vtable, and not through the Rust vtable. Therefore, the -/// default implementation should call `build_error!`, which prevents -/// calls to this function at compile time: -/// -/// ```compile_fail -/// # // Intentionally missing `use`s to simplify `rusttest`. -/// build_error!(VTABLE_DEFAULT_ERROR) -/// ``` -/// -/// Note that you might need to import [`kernel::error::VTABLE_DEFAULT_ERROR`]. +/// For a trait method to be optional for normal traits, it must have a default +/// implementation. However, for many users of `#[vtable]`, the functions will +/// be called through function pointers installed in C side vtables. When an +/// optional method is not implemented on a `#[vtable]` trait, a `NULL` entry is +/// installed in the vtable; thus the default implementation is never called. If +/// this is the case, `#[optional]` can be applied on the optional method. +/// Methods annotated as such do not need to be implemented nor need a default +/// implementation. Calling these methods on types that do not implement them +/// will fail to build using the `build_error!` mechanism. /// /// This macro should not be used when all functions are required. /// @@ -187,7 +177,6 @@ pub fn module(input: TokenStream) -> TokenStream { /// # Examples /// /// ``` -/// use kernel::error::VTABLE_DEFAULT_ERROR; /// use kernel::prelude::*; /// /// # struct LocalModule; @@ -204,13 +193,11 @@ pub fn module(input: TokenStream) -> TokenStream { /// // Declares a `#[vtable]` trait /// #[vtable] /// pub trait Operations: Send + Sync + Sized { -/// fn foo(&self) -> Result<()> { -/// build_error!(VTABLE_DEFAULT_ERROR) -/// } +/// #[optional] +/// fn foo(&self) -> Result<()>; /// -/// fn bar(&self) -> Result<()> { -/// build_error!(VTABLE_DEFAULT_ERROR) -/// } +/// #[optional] +/// fn bar(&self) -> Result<()>; /// } /// /// struct Foo; @@ -228,8 +215,6 @@ pub fn module(input: TokenStream) -> TokenStream { /// assert_eq!(::HAS_BAR, false); /// # } /// ``` -/// -/// [`kernel::error::VTABLE_DEFAULT_ERROR`]: ../kernel/error/constant.VTABLE_DEFAULT_ERROR.html #[proc_macro_attribute] pub fn vtable(attr: TokenStream, input: TokenStream) -> TokenStream { parse_macro_input!(attr as syn::parse::Nothing); diff --git a/rust/macros/vtable.rs b/rust/macros/vtable.rs index 1dfc40eb53f8f9..981ad2e4964777 100644 --- a/rust/macros/vtable.rs +++ b/rust/macros/vtable.rs @@ -49,7 +49,7 @@ fn handle_trait(mut item: ItemTrait) -> Result { }); } - for item in &item.items { + for item in &mut item.items { if let TraitItem::Fn(fn_item) = item { let name = &fn_item.sig.ident; let gen_const_name = Ident::new( @@ -57,6 +57,30 @@ fn handle_trait(mut item: ItemTrait) -> Result { name.span(), ); + if fn_item + .attrs + .extract_if(.., |attr| attr.path().is_ident("optional")) + .count() + != 0 + { + if let Some(default) = &fn_item.default { + Err(Error::new_spanned( + default, + "`#[optional]` methods must not have a default implementation", + ))?; + } + + // Optional methods in Rust need a default implementation. Inject one that fails + // the build at compile time if not overridden. + fn_item.default = Some(parse_quote!({ + ::kernel::build_assert::build_error!( + "This function must not be called, see the `#[vtable]` documentation." + ); + })); + // Ensure that the function is never code generated unless used. + fn_item.attrs.push(parse_quote!(#[inline])); + } + // We don't know on the implementation-site whether a method is required or provided // so we have to generate a const for all methods. // However, hide it for required methods as it will always be true. From a4082121eeb5caa7704734184aa24199e83e19bb Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 11 Aug 2026 14:55:57 +0100 Subject: [PATCH 1260/2286] rust: miscdevice: use `#[optional]` attribute of `#[vtable]` macro Replace the `build_error!(VTABLE_DEFAULT_ERROR)` boilerplate with the `#[optional]` attribute. Signed-off-by: Gary Guo Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260811-vtable_macro-v1-3-50c5aab3c211@garyguo.net [ Reworded title for prefix. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/miscdevice.rs | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/rust/kernel/miscdevice.rs b/rust/kernel/miscdevice.rs index 8d4b719bd83f7c..eacea29e3fb079 100644 --- a/rust/kernel/miscdevice.rs +++ b/rust/kernel/miscdevice.rs @@ -11,10 +11,7 @@ use crate::{ bindings, device::Device, - error::{ - to_result, - VTABLE_DEFAULT_ERROR, // - }, + error::to_result, fs::{ File, Kiocb, // @@ -145,37 +142,33 @@ pub trait MiscDevice: Sized { /// the VMA initialization by calling methods of `vma`. If the function does not return an /// error, the kernel will complete initialization of the VMA according to the properties of /// `vma`. + #[optional] fn mmap( _device: ::Borrowed<'_>, _file: &File, _vma: &VmaNew, - ) -> Result { - build_error!(VTABLE_DEFAULT_ERROR) - } + ) -> Result; /// Read from this miscdevice. - fn read_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterDest<'_>) -> Result { - build_error!(VTABLE_DEFAULT_ERROR) - } + #[optional] + fn read_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterDest<'_>) -> Result; /// Write to this miscdevice. - fn write_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterSource<'_>) -> Result { - build_error!(VTABLE_DEFAULT_ERROR) - } + #[optional] + fn write_iter(_kiocb: Kiocb<'_, Self::Ptr>, _iov: &mut IovIterSource<'_>) -> Result; /// Handler for ioctls. /// /// The `cmd` argument is usually manipulated using the utilities in [`kernel::ioctl`]. /// /// [`kernel::ioctl`]: mod@crate::ioctl + #[optional] fn ioctl( _device: ::Borrowed<'_>, _file: &File, _cmd: u32, _arg: usize, - ) -> Result { - build_error!(VTABLE_DEFAULT_ERROR) - } + ) -> Result; /// Handler for ioctls. /// @@ -185,23 +178,21 @@ pub trait MiscDevice: Sized { /// that have different layout on 32-bit and 64-bit userspace. If no implementation is /// provided, then `compat_ptr_ioctl` will be used instead. #[cfg(CONFIG_COMPAT)] + #[optional] fn compat_ioctl( _device: ::Borrowed<'_>, _file: &File, _cmd: u32, _arg: usize, - ) -> Result { - build_error!(VTABLE_DEFAULT_ERROR) - } + ) -> Result; /// Show info for this fd. + #[optional] fn show_fdinfo( _device: ::Borrowed<'_>, _m: &SeqFile, _file: &File, - ) { - build_error!(VTABLE_DEFAULT_ERROR) - } + ); } /// A vtable for the file operations of a Rust miscdevice. From 69cb84e259b163671ff241621544368af84c7c8f Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:04 +0200 Subject: [PATCH 1261/2286] scripts: add TOML config to container tool Add support for a TOML configuration file to the scripts/container tool. This improves user experience by not having to keep passing the same command line options all the time or overly relying on built-in default values. Include the concept of 'profiles' with different named sections in the file to cover various use cases. Command line options take precedence over the config file, and values defined in profile sections take precedence over the default one. Add a -c option to override the location of the .container.toml config file which should otherwise be located in the current working directory. If not found, the file is silently ignored as it is not strictly required unless the -c option is used. Add a -p option to choose a particular profile section in the config file rather than the default. Signed-off-by: Guillaume Tucker Acked-by: Nicolas Schier Link: https://patch.msgid.link/68b14a76a4cb2e6437c869bab948983a98acffbd.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- scripts/container | 86 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/scripts/container b/scripts/container index b05333d8530be5..a3a55180d6ed79 100755 --- a/scripts/container +++ b/scripts/container @@ -1,17 +1,19 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only -# Copyright (C) 2025 Guillaume Tucker +# Copyright (C) 2025-2026 Guillaume Tucker """Containerized builds""" import abc import argparse +import dataclasses import logging import os import pathlib import shutil import subprocess import sys +import tomllib import uuid @@ -20,10 +22,17 @@ class ContainerRuntime(abc.ABC): name = None # Property defined in each implementation class - def __init__(self, args, logger): - self._uid = args.uid or os.getuid() - self._gid = args.gid or args.uid or os.getgid() - self._env_file = args.env_file + def __init__(self, args, config, logger): + def _first_not_none(*args): + return next((item for item in args if item is not None)) + + self._uid = _first_not_none( + args.uid, config.uid, os.getuid() + ) + self._gid = _first_not_none( + args.gid, config.gid, args.uid, config.uid, os.getgid() + ) + self._env_file = args.env_file or config.env_file self._shell = args.shell self._logger = logger @@ -131,6 +140,42 @@ class Runtimes: raise ValueError("no runtime found") +@dataclasses.dataclass +class Config: + """Container configuration""" + image: str = None + runtime: str = None + env_file: str = None + uid: int = None + gid: int = None + + @classmethod + def from_toml(cls, config_file_path, profile_name): + """Create a config object from a TOML file""" + if not config_file_path: + config_file_path = '.container.toml' + if not os.path.exists(config_file_path): + return cls() + elif not os.path.exists(config_file_path): + raise ValueError(f"config file not found: {config_file_path}") + with open(config_file_path, 'rb') as config_file: + config = tomllib.load(config_file) + default = config.get('DEFAULT', {}) + if not profile_name: + profile = {} + else: + profile = config.get(profile_name) + if profile is None: + raise ValueError(f"unknown profile: {profile_name}") + kwargs = { + name: type(value) for (name, type, value) in ( + (op.name, op.type, profile.get(op.name, default.get(op.name))) + for op in dataclasses.fields(cls) + ) if value is not None + } + return cls(**kwargs) + + def _get_logger(verbose): """Set up a logger with the appropriate level""" logger = logging.getLogger('container') @@ -147,13 +192,19 @@ def main(args): """Main entry point for the container tool""" logger = _get_logger(args.verbose) try: - cls = Runtimes.get(args.runtime) if args.runtime else Runtimes.find() + config = Config.from_toml(args.config_file, args.config_profile) + runtime = args.runtime or config.runtime + cls = Runtimes.get(runtime) if runtime else Runtimes.find() except ValueError as ex: logger.error(ex) return 1 logger.debug("runtime: %s", cls.name) - logger.debug("image: %s", args.image) - return cls(args, logger).run(args.image, args.cmd) + image = args.image or config.image + if not image: + logger.error("no image specified") + return 1 + logger.debug("image: %s", image) + return cls(args, config, logger).run(image, args.cmd) if __name__ == '__main__': @@ -162,18 +213,28 @@ if __name__ == '__main__': description="See the documentation for more details: " "https://docs.kernel.org/dev-tools/container.html" ) + parser.add_argument( + '-c', '--config-file', + help="Path to the config file. If not specified, the default is to " + "look for .container.toml in the current working directory." + ) parser.add_argument( '-e', '--env-file', help="Path to an environment file to load in the container." ) parser.add_argument( - '-g', '--gid', + '-g', '--gid', type=int, help="Group ID to use inside the container." ) parser.add_argument( - '-i', '--image', required=True, + '-i', '--image', help="Container image name." ) + parser.add_argument( + '-p', '--config-profile', + help="Profile section to use in the config file. This will override " + "any values defined in the DEFAULT section." + ) parser.add_argument( '-r', '--runtime', choices=Runtimes.get_names(), help="Container runtime name. If not specified, the first one found " @@ -184,9 +245,10 @@ if __name__ == '__main__': help="Run the container in an interactive shell." ) parser.add_argument( - '-u', '--uid', + '-u', '--uid', type=int, help="User ID to use inside the container. If the -g option is not " - "specified, the user ID will also be set as the group ID." + "specified and no group ID is defined in the configuration file, the " + "user ID will also be set as the group ID." ) parser.add_argument( '-v', '--verbose', action='store_true', From 56a124338eab93aaa5f3f416ff25f5bf5558fc78 Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:05 +0200 Subject: [PATCH 1262/2286] Documentation: dev-tools: update container.rst with config file Update the container.rst documentation page with a new section for the TOML configuration file, its associated command line options and an intro paragraph. Also clarify how UIDs and GIDs are used as a GID can be set in the config file while a UID is provided on the command line. Provide a sample config using the TuxMake images with an example to show how to simplify the command line syntax. Bump the requirement from Python 3.10 to 3.11 which is when tomllib was introduced since it is a new dependency. Signed-off-by: Guillaume Tucker Link: https://patch.msgid.link/fa2dccdbea6a444ecf25f705ec96c5ad7ebd7497.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- Documentation/dev-tools/container.rst | 94 ++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/Documentation/dev-tools/container.rst b/Documentation/dev-tools/container.rst index 9e23f79d5ae13e..5c2957da1ec729 100644 --- a/Documentation/dev-tools/container.rst +++ b/Documentation/dev-tools/container.rst @@ -26,6 +26,11 @@ Command line syntax:: Available options: +``-c, --config-file CONFIG_FILE`` + + Path to the config file. If not specified, the default is to look for + ``.container.toml`` in the current working directory. + ``-e, --env-file ENV_FILE`` Path to an environment file to load in the container. @@ -38,6 +43,11 @@ Available options: Container image name (required). +``-p, --config-profile CONFIG_PROFILE`` + + Profile section to use in the config file. This will override any values + defined in the ``DEFAULT`` section. + ``-r, --runtime RUNTIME`` Container runtime name. Supported runtimes: ``podman``, ``docker``. @@ -53,8 +63,8 @@ Available options: User id to use inside the container. - If the ``-g`` option is not specified, the user id will also be used for - the group id. + If the ``-g`` option is not specified and no group id is defined in the + configuration file, the user id will also be set as the group id. ``-v, --verbose`` @@ -86,9 +96,13 @@ container with SIGINT (Ctrl-C). To run commands interactively with a TTY, the shell directly rather than the parent ``container`` process. To exit an interactive shell, use Ctrl-D or ``exit``. +A :ref:`configuration file` may be used to facilitate running +containers for various use cases. It also removes the burden of repeatedly +providing the same options on the command line. + .. note:: - The only host requirement aside from a container runtime is Python 3.10 or + The only host requirement aside from a container runtime is Python 3.11 or later. .. note:: @@ -151,6 +165,11 @@ with user id 1234 which can access the files in the volume but not in the user the kernel tree but it is worth highlighting here as it might matter for special corner cases. +Group IDs (GID) follow the same logic as user IDs (UID). When specifying a UID +via the ``--uid`` option or in the configuration file, the GID takes the same +value as the UID by default. A specific GID can be set via ``--gid`` or the +configuration file. + .. note:: Podman's `Docker compatibility @@ -225,3 +244,72 @@ To build the HTML documentation, which requires the ``kdocs`` image built with ``make PREFIX=kernel.org/ extra`` as it's not a compiler toolchain:: scripts/container -i kernel.org/kdocs make htmldocs + +.. _config_file: + +Configuration File +================== + +By default, the tool will look for a configuration file named +``.container.toml`` in the current working directory. If not found, it will be +silently ignored as it's not required. Alternatively, any other path can be +specified with the ``-c`` option in which case the file needs to be present or +an error will be raised. + +Its data follows the standard TOML format and is made up of different sections +with ``DEFAULT`` as the default one. Other sections may be used to define +alternative profiles under arbitrary names which can be selected by the ``-p`` +option. Command line options take precedence over configuration values, and +profile sections take precedence over the default one. + +Supported options in each section are: + +``env_file`` + + Path to an environment file to load in the container, equivalent to the + ``-e`` command line option. + +``image`` + + Name of the container image to use, equivalent to the ``-i`` command line + option. + +``runtime`` + + Name of the container runtime, equivalent to the ``-r`` command line option. + +``uid`` / ``gid`` (integers) + + User and group id numbers to use inside the container, equivalents to the + ``-u`` and ``-g`` command line options respectively. + + +Here's a sample configuration with an extra ``clang`` profile:: + + [DEFAULT] + runtime = "podman" + image = "tuxmake/korg-gcc" + + [clang] + image = "tuxmake/korg-clang" + env_file = ".clang.env" + +It mentions a ``.clang.env`` file which simply contains this flag:: + + LLVM=1 + +Let's take a look again at this example mentioned earlier:: + + scripts/container -i docker.io/tuxmake/korg-clang -- make LLVM=1 defconfig + +Using the configuration file, it can now be simplified into this:: + + scripts/container -p clang -- make defconfig + +Then to override the default user, the ``-u`` option can still be used:: + + scripts/container -p clang -u0 -- make defconfig + +This also illustrates how values take precedence over each other: the runtime +is loaded from the ``DEFAULT`` section, the image from the ``clang`` section +and the user ID from the command line ``-u`` option. From fd1ef9b669e969cd57261a7f0e94823f42e10cda Mon Sep 17 00:00:00 2001 From: Guillaume Tucker Date: Mon, 7 Sep 2026 13:10:06 +0200 Subject: [PATCH 1263/2286] Documentation: dev-tools: refer to user ID rather than id User and group identifiers are commonly known as IDs i.e. UID and GID written in capital letters rather than lowercase 'id'. Update the scripts/container documentation accordingly. Signed-off-by: Guillaume Tucker Link: https://patch.msgid.link/5585f8bc5c64e005aa8e4b7651f7cbcac0d93ad3.1788779245.git.gtucker@gtucker.io Signed-off-by: Nathan Chancellor --- Documentation/dev-tools/container.rst | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Documentation/dev-tools/container.rst b/Documentation/dev-tools/container.rst index 5c2957da1ec729..5dde95f6b2229d 100644 --- a/Documentation/dev-tools/container.rst +++ b/Documentation/dev-tools/container.rst @@ -11,7 +11,7 @@ various platforms, for example when a test bot has reported an issue which requires a specific version of a compiler or an external test suite. While this can already be done by users who are familiar with containers, having a dedicated tool in the kernel tree lowers the barrier to entry by solving common -problems once and for all (e.g. user id management). It also makes it easier +problems once and for all (e.g. user ID management). It also makes it easier to share an exact command line leading to a particular result. The main use case is likely to be kernel builds but virtually anything can be run: KUnit, checkpatch etc. provided a suitable image is available. @@ -37,7 +37,7 @@ Available options: ``-g, --gid GID`` - Group id to use inside the container. + Group ID to use inside the container. ``-i, --image IMAGE`` @@ -61,10 +61,10 @@ Available options: ``-u, --uid UID`` - User id to use inside the container. + User ID to use inside the container. - If the ``-g`` option is not specified and no group id is defined in the - configuration file, the user id will also be set as the group id. + If the ``-g`` option is not specified and no group ID is defined in the + configuration file, the user ID will also be set as the group ID. ``-v, --verbose`` @@ -81,7 +81,7 @@ Usage It's entirely up to the user to choose which image to use and the ``CMD`` arguments are passed directly as an arbitrary command line to run in the container. The tool will take care of mounting the source tree as the current -working directory and adjust the user and group id as needed. +working directory and adjust the user and group ID as needed. The container image which would typically include a compiler toolchain is provided by the user and selected via the ``-i`` option. The container runtime @@ -145,22 +145,22 @@ User IDs This is an area where the behaviour will vary slightly depending on the container runtime. The goal is to run commands as the user invoking the tool. -With Podman, a namespace is created to map the current user id to a different +With Podman, a namespace is created to map the current user ID to a different one in the container (1000 by default). With Docker, while this is also possible with recent versions it requires a special feature to be enabled in the daemon so it's not used here for simplicity. Instead, the container is run -with the current user id directly. In both cases, this will provide the same +with the current user ID directly. In both cases, this will provide the same file permissions for the kernel source tree mounted as a volume. The only -difference is that when using Docker without a namespace, the user id may not +difference is that when using Docker without a namespace, the user ID may not be the same as the default one set in the image. -Say, we're using an image which sets up a default user with id 1000 and the -current user calling the ``container`` tool has id 1234. The kernel source +Say, we're using an image which sets up a default user with ID 1000 and the +current user calling the ``container`` tool has ID 1234. The kernel source tree was checked out by this same user so the files belong to user 1234. With -Podman, the container will be running as user id 1000 with a mapping to id 1234 -so that the files from the mounted volume appear to belong to id 1000 inside +Podman, the container will be running as user ID 1000 with a mapping to ID 1234 +so that the files from the mounted volume appear to belong to ID 1000 inside the container. With Docker and no namespace, the container will be running -with user id 1234 which can access the files in the volume but not in the user +with user ID 1234 which can access the files in the volume but not in the user 1000 home directory. This shouldn't be an issue when running commands only in the kernel tree but it is worth highlighting here as it might matter for special corner cases. @@ -280,7 +280,7 @@ Supported options in each section are: ``uid`` / ``gid`` (integers) - User and group id numbers to use inside the container, equivalents to the + User and group ID numbers to use inside the container, equivalents to the ``-u`` and ``-g`` command line options respectively. From 26d665bef44fa15f7551ee1efc19a1df331d6bbc Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 15 Sep 2026 13:43:31 +0300 Subject: [PATCH 1264/2286] kbuild: add header check facility as a manually run static analyzer There have been various attempts at adding a header test or check mechanism in the kernel build system. The header check primarily consists of ensuring headers are self-contained, have include guards, and, in some cases, pass kernel-doc. The main problems have been: - The dependency tracking creates undesirable artefacts (infamously also known as disgusting turds) in the build directory. - Gating the feature behind a kconfig option is complicated due to allyesconfig builds. It's possible, but requires a verbose and confusing negative proxy config option. - Naming the dependency tracking files with a dot prefix or placing them in a dot prefixed subdirectory in the build directory to hide them has been exceedingly difficult to achieve. (In part due to some Makefiles building files in subdirectory hierarchies.) - The debate which headers, if any, should really be self-contained is virtually open-ended. Approach the problem from a slightly different angle. Add a top level "headercheck" static analysis target which you have to explicitly run, and where you have to explicitly state which headers to check: $ make HEADER_CHECK="(headers|dirs)" headercheck For example: $ make HEADER_CHECK="include/drm drivers/gpu/drm/drm_draw_internal.h" headercheck You can specify multiple path/to/header.h or path/to/dir, relative to $(srctree), where path/to/dir is recursively scanned for any .h files. Add a generic %.header-check rule in scripts/Makefile.build to support this. Note that this rule should *not* be used in, say, driver or subsystem makefile targets, precisely because of the problems listed above. While this still generates the .header-check dependency tracking artefacts in the build directory, you will only get them as a result of manually running 'make headercheck', not as part of the regular build. This approach also allows anyone to check any headers in the tree, with no makefile modification or kconfig changes required. What you do with the results is up to you, and the header check facility does not enforce anything like it would as part of the regular build. Cc: Linus Torvalds Cc: Nathan Chancellor Cc: Nicolas Schier Cc: Jason Gunthorpe Cc: Masahiro Yamada Cc: linux-kbuild@vger.kernel.org Signed-off-by: Jani Nikula Tested-by: Randy Dunlap Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260915104331.255636-1-jani.nikula@intel.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 + Makefile | 25 ++++++++++++++++++++++++- scripts/Makefile.build | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9875120ea7bde0..948c760b270456 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ *.gcno *.gcda *.gz +*.header-check *.i *.ko *.lex.c diff --git a/Makefile b/Makefile index c496d16391035e..28502178746fd4 100644 --- a/Makefile +++ b/Makefile @@ -302,7 +302,7 @@ no-dot-config-targets := $(clean-targets) \ run-command no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \ image_name -single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %/ +single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %.header-check %/ config-build := mixed-build := @@ -1544,6 +1544,26 @@ PHONY += scripts_gen_packed_field_checks scripts_gen_packed_field_checks: scripts_basic $(Q)$(MAKE) $(build)=scripts scripts/gen_packed_field_checks +# --------------------------------------------------------------------------- +# Header check + +# Check the headers in HEADER_CHECK=(headers|dirs) +PHONY += headercheck + +ifneq ($(HEADER_CHECK),) + +header-check-dirs := $(filter-out %.h,$(HEADER_CHECK)) +header-check-files := $(filter %.h,$(HEADER_CHECK)) $(if $(header-check-dirs),$(shell cd $(srctree) && find $(header-check-dirs) -name '*.h' 2>/dev/null)) +header-check-targets := $(patsubst %.h,%.header-check,$(sort $(header-check-files))) + +headercheck: + $(if $(header-check-targets),,$(error $@ found no headers in HEADER_CHECK="$(HEADER_CHECK)")) + $(Q)$(MAKE) $(header-check-targets) +else +headercheck: + $(error $@ requires HEADER_CHECK=(headers|dirs)) +endif + # --------------------------------------------------------------------------- # Install @@ -1890,6 +1910,8 @@ help: @echo ' versioncheck - Sanity check on version.h usage' @echo ' includecheck - Check for duplicate included header files' @echo ' headerdep - Detect inclusion cycles in headers' + @echo ' headercheck - Check headers in HEADER_CHECK=(headers|dirs) are' + @echo ' self-contained, have header guards, and pass kernel-doc.' @echo ' coccicheck - Check with Coccinelle' @echo ' kconfig-sym-check - Check for dangling Kconfig symbol references' @echo ' clang-analyzer - Check with clang static analyzer' @@ -2255,6 +2277,7 @@ clean: $(clean-dirs) -o -name '*.ll' \ -o -name '*.gcno' \ -o -name '*.long-type-*.txt' \ + -o -name '*.header-check' \ \) -type f -print \ -o -name '.tmp_*' -print \ | xargs rm -rf diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 4349108e75e1f7..6127feb970c650 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -306,6 +306,27 @@ quiet_cmd_cc_lst_c = MKLST $@ $(obj)/%.lst: $(obj)/%.c FORCE $(call if_changed_dep,cc_lst_c) +# Compile C headers (.h) for header check +# --------------------------------------------------------------------------- + +# Check a header is self-contained, has header guards, and passes kernel-doc. +# +# This is for 'make HEADER_CHECK=(headers|dirs) headercheck'. Not to be confused +# with CONFIG_UAPI_HEADER_TEST. +# +# Please do *not* plug %.header-check into any configurable targets, unless you +# also figure out a way to completely hide the %.header-check artefacts. + +quiet_cmd_header_check = HDRCHK $(patsubst %.header-check,%.h,$@) + cmd_header_check = \ + $(CC) $(c_flags) -fsyntax-only -x c /dev/null -include $< -include $<; \ + PYTHONDONTWRITEBYTECODE=1 $(PYTHON3) $(KERNELDOC) -none $(KDOCFLAGS) \ + $(if $(findstring e, $(KBUILD_EXTRA_WARN))$(CONFIG_WERROR),-Werror) $<; \ + touch $@ + +$(obj)/%.header-check: $(obj)/%.h FORCE + $(call if_changed_dep,header_check) + # Compile Rust sources (.rs) # --------------------------------------------------------------------------- From da4d5933e30340766925480f9dd9f250c4355e24 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 1 Oct 2026 20:44:05 -0700 Subject: [PATCH 1265/2286] lib/crypto: x86/aes-ctr: Remove some unreachable code In the AES-CTR and AES-XCTR code that handles a remainder of '4*VL < LEN < 8*VL', first 4 full vectors are processed, leaving 0 < LEN < 4*VL. After that, five different cases are handled depending on whether there are 0, 1, 2, 3, or 4 full vectors remaining. But the case of 4 vectors is never reached there, making the code handling it unused. Remove it. With that done, the '.Lxor_tail_partial_vec_3\@' block would be reached only via an unconditional jump. Therefore, relocate it to the only place that jumps to it. This allows removing the jump at the end of the '.Lxor_tail_partial_vec_2\@' block and making it fall through. Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20261002034405.49886-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/aes-ctr-avx-x86_64.S | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/crypto/x86/aes-ctr-avx-x86_64.S b/lib/crypto/x86/aes-ctr-avx-x86_64.S index c232337899b691..13b3e2cfae37a1 100644 --- a/lib/crypto/x86/aes-ctr-avx-x86_64.S +++ b/lib/crypto/x86/aes-ctr-avx-x86_64.S @@ -419,10 +419,12 @@ cmp $3*VL-1, LEN32 jle .Lxor_tail_partial_vec_2\@ _xor_data 2 - cmp $4*VL-1, LEN32 - jle .Lxor_tail_partial_vec_3\@ - _xor_data 3 - jmp .Ldone\@ + add $-3*VL, LEN32 + jz .Ldone\@ + sub $-3*VL, SRC + sub $-3*VL, DST + _vmovdqa AESDATA3, AESDATA0 + jmp .Lxor_tail_partial_vec_0\@ .Lenc_tail_atmost4vecs\@: cmp $2*VL, LEN32 @@ -469,14 +471,6 @@ sub $-2*VL, SRC sub $-2*VL, DST _vmovdqa AESDATA2, AESDATA0 - jmp .Lxor_tail_partial_vec_0\@ - -.Lxor_tail_partial_vec_3\@: - add $-3*VL, LEN32 - jz .Ldone\@ - sub $-3*VL, SRC - sub $-3*VL, DST - _vmovdqa AESDATA3, AESDATA0 .Lxor_tail_partial_vec_0\@: // XOR the remaining 1 <= LEN < VL bytes. It's easy if masked From 1847c80537891baead3975f3ca969233ff7b2a11 Mon Sep 17 00:00:00 2001 From: Kim Phillips Date: Mon, 31 Aug 2026 16:09:16 -0500 Subject: [PATCH 1266/2286] x86/bugs: Fix CONFIG_MITIGATION_RETPOLINE=n mitigation reporting When the kernel lacks retpoline support, spectre_v2_select_retpoline() emits "Kernel not compiled with retpoline; no mitigation available!" before returning SPECTRE_V2_NONE to its callers. SPECTRE_V2_NONE is not the final answer, though: spectre_v2_update_mitigation() runs afterwards and can still upgrade the mitigation. On an Intel CPU affected by RETBleed, built with CONFIG_MITIGATION_IBRS_ENTRY=y and CONFIG_MITIGATION_RETPOLINE=n, and booted with spectre_v2=auto, retbleed_select_mitigation() picks RETBLEED_MITIGATION_IBRS, and spectre_v2_update_mitigation() then promotes SPECTRE_V2_NONE to SPECTRE_V2_IBRS. The boot log claims no mitigation is available immediately before reporting "Spectre V2 : Mitigation: IBRS", and sysfs reports the machine as mitigated. The message is alarming and simply wrong. Drop "no mitigation available!" because the final mitigation state is printed afterwards in spectre_v2_update_mitigation() anyway. [ bp: Drop CC:stable, massage commit message. ] Fixes: 480e803dacf8 ("x86/bugs: Restructure spectre_v2 mitigation") Suggested-by: Pawan Gupta Assisted-by: LLM Signed-off-by: Kim Phillips Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Pawan Gupta Link: https://patch.msgid.link/20260831210916.987152-1-kim.phillips@amd.com --- arch/x86/kernel/cpu/bugs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c index 1b2381da4d8319..000ebee2d247b1 100644 --- a/arch/x86/kernel/cpu/bugs.c +++ b/arch/x86/kernel/cpu/bugs.c @@ -1958,7 +1958,7 @@ early_param("spectre_v2", spectre_v2_parse_cmdline); static enum spectre_v2_mitigation __init spectre_v2_select_retpoline(void) { if (!IS_ENABLED(CONFIG_MITIGATION_RETPOLINE)) { - pr_err("Kernel not compiled with retpoline; no mitigation available!"); + pr_err("Kernel not compiled with retpoline"); return SPECTRE_V2_NONE; } From 3504d1bce9920b23499ab1af682cdf1eceba7c75 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 2 Sep 2026 22:59:59 +0100 Subject: [PATCH 1267/2286] rust: kunit: use `file!()` inside `kunit_assert!` This parameter is needed currently because doctests want to override the file. Simplify it by changing it to use `file!()`. Have doctests override `file!()` macro to achieve the current behavior. This allows us to remove the `file` helper and associated Kconfig options. Reviewed-by: David Gow Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260902-kunit-v2-1-eda3ed6c00c5@garyguo.net Signed-off-by: Miguel Ojeda --- Documentation/rust/general-information.rst | 4 ++-- init/Kconfig | 3 --- rust/kernel/kunit.rs | 9 +++++---- rust/macros/helpers.rs | 16 ---------------- rust/macros/kunit.rs | 5 ++--- rust/macros/lib.rs | 5 ----- scripts/rustdoc_test_gen.rs | 10 ++++++++-- 7 files changed, 17 insertions(+), 35 deletions(-) diff --git a/Documentation/rust/general-information.rst b/Documentation/rust/general-information.rst index 09234bed272c28..20e3178ebfd47b 100644 --- a/Documentation/rust/general-information.rst +++ b/Documentation/rust/general-information.rst @@ -157,5 +157,5 @@ numerical comparisons, one may define a new Kconfig symbol: .. code-block:: kconfig - config RUSTC_HAS_SPAN_FILE - def_bool RUSTC_VERSION >= 108800 + config RUSTC_HAS_FILE_AS_C_STR + def_bool RUSTC_VERSION >= 109100 diff --git a/init/Kconfig b/init/Kconfig index 8583d9f06c522e..24b019104d8866 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -178,9 +178,6 @@ config LD_CAN_USE_KEEP_IN_OVERLAY # https://github.com/llvm/llvm-project/pull/130661 def_bool LD_IS_BFD || LLD_VERSION >= 210000 -config RUSTC_HAS_SPAN_FILE - def_bool RUSTC_VERSION >= 108800 - config RUSTC_HAS_UNNECESSARY_TRANSMUTES def_bool RUSTC_VERSION >= 108800 diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs index 91eaff8c186afd..12084873b51ebc 100644 --- a/rust/kernel/kunit.rs +++ b/rust/kernel/kunit.rs @@ -58,14 +58,15 @@ pub fn info(args: fmt::Arguments<'_>) { #[doc(hidden)] #[macro_export] macro_rules! kunit_assert { - ($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => { + ($name:literal, $diff:expr, $condition:expr $(,)?) => { 'out: { // Do nothing if the condition is `true`. if $condition { break 'out; } - static FILE: &'static $crate::str::CStr = $file; + // Use `file!()` instead of `::core::file!()` here so it can be overridden. + static FILE: &'static $crate::str::CStr = $crate::c_str!(file!()); static LINE: i32 = ::core::line!() as i32 - $diff; static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition)); @@ -164,10 +165,10 @@ macro_rules! kunit_assert { #[doc(hidden)] #[macro_export] macro_rules! kunit_assert_eq { - ($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{ + ($name:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{ // For the moment, we just forward to the expression assert because, for binary asserts, // KUnit supports only a few types (e.g. integers). - $crate::kunit_assert!($name, $file, $diff, $left == $right); + $crate::kunit_assert!($name, $diff, $left == $right); }}; } diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs index d18fbf4daa0a5f..8cc9a20f470e85 100644 --- a/rust/macros/helpers.rs +++ b/rust/macros/helpers.rs @@ -38,22 +38,6 @@ impl AsciiLitStr { } } -pub(crate) fn file() -> String { - #[cfg(not(CONFIG_RUSTC_HAS_SPAN_FILE))] - { - proc_macro::Span::call_site() - .source_file() - .path() - .to_string_lossy() - .into_owned() - } - - #[cfg(CONFIG_RUSTC_HAS_SPAN_FILE)] - { - proc_macro::Span::call_site().file() - } -} - /// Obtain all `#[cfg]` attributes. pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator + '_ { attr.iter().filter(|a| a.path().is_ident("cfg")) diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs index ae20ed6768f156..936eff01487023 100644 --- a/rust/macros/kunit.rs +++ b/rust/macros/kunit.rs @@ -109,12 +109,11 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result {{ - kernel::kunit_assert!(#test_str, #path, 0, $cond); + kernel::kunit_assert!(#test_str, 0, $cond); }} } }); @@ -122,7 +121,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result {{ - kernel::kunit_assert_eq!(#test_str, #path, 0, $left, $right); + kernel::kunit_assert_eq!(#test_str, 0, $left, $right); }} } }); diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index b84db5c6bdc4f8..68386b2ed55f17 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -8,11 +8,6 @@ // Stable since Rust 1.87.0. #![feature(extract_if)] -// -// Stable since Rust 1.88.0 under a different name, `proc_macro_span_file`, -// which was added in Rust 1.88.0. This is why `cfg_attr` is used here, i.e. -// to avoid depending on the full `proc_macro_span` on Rust >= 1.88.0. -#![cfg_attr(not(CONFIG_RUSTC_HAS_SPAN_FILE), feature(proc_macro_span))] mod concat_idents; mod export; diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs index d087c0d9fcb3bd..bb57745ec339e5 100644 --- a/scripts/rustdoc_test_gen.rs +++ b/scripts/rustdoc_test_gen.rs @@ -176,12 +176,18 @@ fn main() { r#"/// Generated `{name}` KUnit test case from a Rust documentation test. #[no_mangle] pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ + /// Overrides the usual [`file!`] macro with one that expands to the real path. + #[allow(unused)] + macro_rules! file {{ + () => {{ "{real_path}" }} + }} + /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. #[allow(unused)] macro_rules! assert {{ ($cond:expr $(,)?) => {{{{ ::kernel::kunit_assert!( - "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $cond + "{kunit_name}", __DOCTEST_ANCHOR - {line}, $cond ); }}}} }} @@ -191,7 +197,7 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ macro_rules! assert_eq {{ ($left:expr, $right:expr $(,)?) => {{{{ ::kernel::kunit_assert_eq!( - "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right + "{kunit_name}", __DOCTEST_ANCHOR - {line}, $left, $right ); }}}} }} From e9a35442ef91f7f599a95e04928e80748fba2998 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 2 Sep 2026 23:00:00 +0100 Subject: [PATCH 1268/2286] rust: kunit: use `line!()` inside `kunit_assert!` The `diff` parameter is needed currently because doctests want to override the line number. Simplify it by changing it to use `line!()`. Have doctests override `line!()` macro to achieve the current behavior. An existing doctest requires `line!()` to expand to a literal; so update it to use `::core::line!()` instead. Reviewed-by: David Gow Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260902-kunit-v2-2-eda3ed6c00c5@garyguo.net [ Removed spurious `///`-to-`//` change and adjusted `line!` to match. Added a comment to match `file!`'s. Made `NOTE:` more precise. Reworded to say it is only one doctest being updated. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/kunit.rs | 9 +++++---- rust/kernel/str.rs | 2 +- rust/macros/kunit.rs | 4 ++-- scripts/rustdoc_test_gen.rs | 14 +++++++++++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs index 12084873b51ebc..25ff272d29b2b3 100644 --- a/rust/kernel/kunit.rs +++ b/rust/kernel/kunit.rs @@ -58,7 +58,7 @@ pub fn info(args: fmt::Arguments<'_>) { #[doc(hidden)] #[macro_export] macro_rules! kunit_assert { - ($name:literal, $diff:expr, $condition:expr $(,)?) => { + ($name:literal, $condition:expr $(,)?) => { 'out: { // Do nothing if the condition is `true`. if $condition { @@ -67,7 +67,8 @@ macro_rules! kunit_assert { // Use `file!()` instead of `::core::file!()` here so it can be overridden. static FILE: &'static $crate::str::CStr = $crate::c_str!(file!()); - static LINE: i32 = ::core::line!() as i32 - $diff; + // Use `line!()` instead of `::core::line!()` here so it can be overridden. + static LINE: i32 = line!() as i32; static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition)); // SAFETY: FFI call without safety requirements. @@ -165,10 +166,10 @@ macro_rules! kunit_assert { #[doc(hidden)] #[macro_export] macro_rules! kunit_assert_eq { - ($name:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{ + ($name:literal, $left:expr, $right:expr $(,)?) => {{ // For the moment, we just forward to the expression assert because, for binary asserts, // KUnit supports only a few types (e.g. integers). - $crate::kunit_assert!($name, $diff, $left == $right); + $crate::kunit_assert!($name, $left == $right); }}; } diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs index b3caa9a1c898f4..644b4279a11601 100644 --- a/rust/kernel/str.rs +++ b/rust/kernel/str.rs @@ -411,7 +411,7 @@ impl AsRef for CStr { /// const BAD: &CStr = c_str!("literal"); /// /// // `c_str!` is still needed for static non-literal C strings. -/// const GOOD: &CStr = c_str!(concat!(file!(), ":", line!(), ": My CStr!")); +/// const GOOD: &CStr = c_str!(concat!(file!(), ":", ::core::line!(), ": My CStr!")); /// ``` #[macro_export] macro_rules! c_str { diff --git a/rust/macros/kunit.rs b/rust/macros/kunit.rs index 936eff01487023..e9152b9d51f945 100644 --- a/rust/macros/kunit.rs +++ b/rust/macros/kunit.rs @@ -113,7 +113,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result {{ - kernel::kunit_assert!(#test_str, 0, $cond); + kernel::kunit_assert!(#test_str, $cond); }} } }); @@ -121,7 +121,7 @@ pub(crate) fn kunit_tests(test_suite: Ident, mut module: ItemMod) -> Result {{ - kernel::kunit_assert_eq!(#test_str, 0, $left, $right); + kernel::kunit_assert_eq!(#test_str, $left, $right); }} } }); diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs index bb57745ec339e5..7159cd6e726d07 100644 --- a/scripts/rustdoc_test_gen.rs +++ b/scripts/rustdoc_test_gen.rs @@ -182,12 +182,20 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ () => {{ "{real_path}" }} }} + /// Overrides the usual [`line!`] macro with one that expands to the real line number. + #[allow(unused)] + macro_rules! line {{ + // NOTE: This does not expand to a literal, but a constant expression. + // Therefore code that depends on `line!()` expanding to a literal needs special adjustment. + () => {{ const {{ ::core::line!() - __DOCTEST_ANCHOR + {line} }} }} + }} + /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. #[allow(unused)] macro_rules! assert {{ ($cond:expr $(,)?) => {{{{ ::kernel::kunit_assert!( - "{kunit_name}", __DOCTEST_ANCHOR - {line}, $cond + "{kunit_name}", $cond ); }}}} }} @@ -197,7 +205,7 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ macro_rules! assert_eq {{ ($left:expr, $right:expr $(,)?) => {{{{ ::kernel::kunit_assert_eq!( - "{kunit_name}", __DOCTEST_ANCHOR - {line}, $left, $right + "{kunit_name}", $left, $right ); }}}} }} @@ -219,7 +227,7 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ /// The anchor where the test code body starts. #[allow(unused)] - static __DOCTEST_ANCHOR: i32 = ::core::line!() as i32 + {body_offset} + 2; + static __DOCTEST_ANCHOR: u32 = ::core::line!() + {body_offset} + 2; {{ #![allow(unreachable_pub, clippy::disallowed_names)] {body} From a41ec2936d2fecdb958e4f4417781771bcfa683d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 2 Sep 2026 23:00:01 +0100 Subject: [PATCH 1269/2286] rust: doctest: generate Rust kunit test suites For doctest, instead of generating C FFI functions, generate a Rust test suite with `#[kunit_tests]` and `#[test]` attributes. This makes the C glue no longer needed. Reviewed-by: David Gow Signed-off-by: Gary Guo Link: https://patch.msgid.link/20260902-kunit-v2-3-eda3ed6c00c5@garyguo.net [ Removed generated file from `rust/.gitignore` and added it to `scripts/remove-stale-files`. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/.gitignore | 1 - rust/Makefile | 4 +-- scripts/remove-stale-files | 2 ++ scripts/rustdoc_test_gen.rs | 69 +++++-------------------------------- 4 files changed, 12 insertions(+), 64 deletions(-) diff --git a/rust/.gitignore b/rust/.gitignore index d3829ffab80ba0..5ba6b490d1e918 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -3,7 +3,6 @@ bindings_generated.rs bindings_helpers_generated.rs doctests_kernel_generated.rs -doctests_kernel_generated_kunit.c uapi_generated.rs exports_*_generated.h doc/ diff --git a/rust/Makefile b/rust/Makefile index 152649f209d8ed..97f577ed0b8f98 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -39,10 +39,8 @@ obj-$(CONFIG_RUST) += exports.o always-$(CONFIG_RUST) += host/libproc_macro2.rlib host/libquote.rlib host/libsyn.rlib always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs -always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.c obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.o -obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.o always-$(subst y,$(CONFIG_RUST),$(CONFIG_JUMP_LABEL)) += kernel/generated_arch_static_branch_asm.rs ifndef CONFIG_UML @@ -402,7 +400,7 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $< $< $(rustdoc_test_kernel_quiet); \ $(objtree)/scripts/rustdoc_test_gen -%/doctests_kernel_generated.rs %/doctests_kernel_generated_kunit.c: \ +%/doctests_kernel_generated.rs: \ $(src)/kernel/lib.rs $(obj)/kernel.o \ $(objtree)/scripts/rustdoc_test_builder \ $(objtree)/scripts/rustdoc_test_gen FORCE diff --git a/scripts/remove-stale-files b/scripts/remove-stale-files index 6e39fa8540df2f..f1f776caae00ad 100755 --- a/scripts/remove-stale-files +++ b/scripts/remove-stale-files @@ -26,3 +26,5 @@ rm -f scripts/selinux/genheaders/genheaders rm -f *.spec rm -f lib/test_fortify.log + +rm -f rust/doctests_kernel_generated_kunit.c diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs index 7159cd6e726d07..754c5aafcce441 100644 --- a/scripts/rustdoc_test_gen.rs +++ b/scripts/rustdoc_test_gen.rs @@ -125,9 +125,7 @@ fn main() { // Sort paths. paths.sort(); - let mut rust_tests = String::new(); - let mut c_test_declarations = String::new(); - let mut c_test_cases = String::new(); + let mut tests = String::new(); let mut body = String::new(); let mut last_file = String::new(); let mut number = 0; @@ -172,10 +170,10 @@ fn main() { use std::fmt::Write; write!( - rust_tests, + tests, r#"/// Generated `{name}` KUnit test case from a Rust documentation test. -#[no_mangle] -pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ +#[test] +fn {kunit_name}() {{ /// Overrides the usual [`file!`] macro with one that expands to the real path. #[allow(unused)] macro_rules! file {{ @@ -190,26 +188,6 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ () => {{ const {{ ::core::line!() - __DOCTEST_ANCHOR + {line} }} }} }} - /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. - #[allow(unused)] - macro_rules! assert {{ - ($cond:expr $(,)?) => {{{{ - ::kernel::kunit_assert!( - "{kunit_name}", $cond - ); - }}}} - }} - - /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead. - #[allow(unused)] - macro_rules! assert_eq {{ - ($left:expr, $right:expr $(,)?) => {{{{ - ::kernel::kunit_assert_eq!( - "{kunit_name}", $left, $right - ); - }}}} - }} - // Many tests need the prelude, so provide it by default. #[allow(unused)] use ::kernel::prelude::*; @@ -238,14 +216,9 @@ pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{ "# ) .unwrap(); - - write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap(); - write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap(); } - let rust_tests = rust_tests.trim(); - let c_test_declarations = c_test_declarations.trim(); - let c_test_cases = c_test_cases.trim(); + let tests = tests.trim(); write!( BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()), @@ -271,34 +244,10 @@ impl ModuleMetadata for LocalModule {{ }}; }} -{rust_tests} -"# - ) - .unwrap(); - - write!( - BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()), - r#"/* - * `kernel` crate documentation tests. - */ - -#include - -{c_test_declarations} - -static struct kunit_case test_cases[] = {{ - {c_test_cases} - {{ }} -}}; - -static struct kunit_suite test_suite = {{ - .name = "rust_doctests_kernel", - .test_cases = test_cases, -}}; - -kunit_test_suite(test_suite); - -MODULE_LICENSE("GPL"); +#[kernel::macros::kunit_tests(rust_doctests_kernel)] +mod tests {{ +{tests} +}} "# ) .unwrap(); From 2dee3d3adcadcc57d62ba6608cd02f50b0c10264 Mon Sep 17 00:00:00 2001 From: Nicolas Schier Date: Wed, 17 Jun 2026 16:09:48 +0200 Subject: [PATCH 1270/2286] rust: kbuild: Replace and dissolve procmacro-extension variable Use make's $(basename) function [1] and substitution reference [2] instead of $(patsubst) to extract crate names for rust procmacro targets. As there is only one user of the procmacro-extension variable, do the crate name extraction in-place and remove procmacro-extension. The procmacro-extension variable is used to record the binary file suffix of dynamically loadable rust libraries -- which is 'so' on Linux and others and 'dylib' in MacOS -- to remove that very suffix from a target file name for reconstructing the corresponding crate name in cmd_rustc_procmacro. Extracting the crate name from the target name in-place is shorter and reduces complexity. Link: https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html # [1] Link: https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html # [2] Reviewed-by: Tamir Duberstein Tested-by: Tamir Duberstein Signed-off-by: Nicolas Schier Link: https://lore.kernel.org/linux-kbuild/CANiq72nmPPF=6AUp5mcDJVni80ORQr0AQ-4uCZaE+CzB5kV=eg@mail.gmail.com/ Link: https://patch.msgid.link/20260617-rust-kbuild-replace-procmacro-extension-v2-1-667bdc3a6435@fritz.com [ Rebased. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rust/Makefile b/rust/Makefile index 97f577ed0b8f98..320ab6bb36c42e 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -51,7 +51,6 @@ endif ifdef CONFIG_RUST procmacro-name = $(shell MAKEFLAGS= $(RUSTC) --print file-names --crate-name $(1) --crate-type proc-macro - Date: Sat, 3 Oct 2026 18:28:27 +0200 Subject: [PATCH 1271/2286] lib: Move SipHash into lib/crypto/ The addition of SipHash to lib/ predated the existence of the lib/crypto/ directory. Since SipHash is a cryptographic algorithm, move it from the top-level lib/ directory to lib/crypto/. (This does mean HalfSipHash comes with it too, as it's in the same files. But lib/crypto/ still seems like the proper place for both.) Specifically: - Move both siphash.c and siphash_kunit.c. Keep the header as-is for now. - Drop the dedicated MAINTAINERS entry for SipHash, as per the suggestion from Jason (https://lore.kernel.org/r/asB7QVFMlgKgbje2@zx2c4.com/). The C files now just fold up to the "CRYPTO LIBRARY" entry. Add the header explicitly, as it otherwise would be left unmaintained. - Move the kconfig option that controls the KUnit test. Put "CRYPTO_LIB" in its name and update the prompt and help text to make it consistent with the other crypto library test options. - Enable the KUnit test in lib/crypto/.kunitconfig. - Link to siphash.rst from the appropriate place in libcrypto-hash.rst. - Use obj-y instead of lib-y, for consistency with the fact that currently lib/crypto/ doesn't use kbuild's support for library file goals. We could keep it as lib-y, but virtually every kernel needs SipHash support anyway (with vsprintf, printk, IPv4, IPv6, etc. depending on it) so there is no practical difference in this case. Acked-by: Ard Biesheuvel Acked-by: Jason A. Donenfeld Link: https://patch.msgid.link/20261003162827.155527-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- Documentation/crypto/libcrypto-hash.rst | 5 +++++ MAINTAINERS | 8 +------- lib/Kconfig.debug | 11 ----------- lib/Makefile | 2 +- lib/crypto/.kunitconfig | 1 + lib/crypto/Makefile | 6 ++++++ lib/{ => crypto}/siphash.c | 0 lib/crypto/tests/Kconfig | 9 +++++++++ lib/crypto/tests/Makefile | 1 + lib/{ => crypto}/tests/siphash_kunit.c | 0 lib/tests/Makefile | 1 - tools/testing/selftests/bpf/progs/test_siphash.h | 2 +- 12 files changed, 25 insertions(+), 21 deletions(-) rename lib/{ => crypto}/siphash.c (100%) rename lib/{ => crypto}/tests/siphash_kunit.c (100%) diff --git a/Documentation/crypto/libcrypto-hash.rst b/Documentation/crypto/libcrypto-hash.rst index fa4c54236af6e5..d1e2d2ff06f4a6 100644 --- a/Documentation/crypto/libcrypto-hash.rst +++ b/Documentation/crypto/libcrypto-hash.rst @@ -80,6 +80,11 @@ SHA-3 The SHA-3 API is documented in :ref:`sha3`. +SipHash +------- + +The SipHash API is documented in Documentation/security/siphash.rst. + SM3 --- diff --git a/MAINTAINERS b/MAINTAINERS index 9dc69113f47a46..f4d49df0774bb9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6980,6 +6980,7 @@ S: Maintained T: git https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-next T: git https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git libcrypto-fixes F: Documentation/crypto/libcrypto* +F: include/linux/siphash.h F: lib/crypto/ F: scripts/crypto/ @@ -25113,13 +25114,6 @@ F: drivers/gpio/gpio-siox.c F: drivers/siox/* F: include/trace/events/siox.h -SIPHASH PRF ROUTINES -M: Jason A. Donenfeld -S: Maintained -F: include/linux/siphash.h -F: lib/siphash.c -F: lib/tests/siphash_kunit.c - SIS 190 ETHERNET DRIVER M: Francois Romieu L: netdev@vger.kernel.org diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 134b15a44625e8..0f37a552318de1 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -3075,17 +3075,6 @@ config HW_BREAKPOINT_KUNIT_TEST source "lib/crypto/tests/Kconfig" -config SIPHASH_KUNIT_TEST - tristate "Perform selftest on siphash functions" if !KUNIT_ALL_TESTS - depends on KUNIT - default KUNIT_ALL_TESTS - help - Enable this option to test the kernel's siphash () hash - functions on boot (or module load). - - This is intended to help people writing architecture-specific - optimized versions. If unsure, say N. - config USERCOPY_KUNIT_TEST tristate "KUnit Test for user/kernel boundary protections" depends on KUNIT diff --git a/lib/Makefile b/lib/Makefile index dfab958327c5cc..e1eb91d62a30ac 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -38,7 +38,7 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \ maple_tree.o idr.o extable.o irq_regs.o argv_split.o \ flex_proportions.o ratelimit.o \ is_single_threaded.o plist.o decompress.o kobject_uevent.o \ - earlycpio.o seq_buf.o siphash.o dec_and_lock.o \ + earlycpio.o seq_buf.o dec_and_lock.o \ nmi_backtrace.o win_minmax.o memcat_p.o \ buildid.o objpool.o iomem_copy.o sys_info.o diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig index 60e0a77f98890e..6f218cd2d0c3b3 100644 --- a/lib/crypto/.kunitconfig +++ b/lib/crypto/.kunitconfig @@ -19,4 +19,5 @@ CONFIG_CRYPTO_LIB_SHA1_KUNIT_TEST=y CONFIG_CRYPTO_LIB_SHA256_KUNIT_TEST=y CONFIG_CRYPTO_LIB_SHA512_KUNIT_TEST=y CONFIG_CRYPTO_LIB_SHA3_KUNIT_TEST=y +CONFIG_CRYPTO_LIB_SIPHASH_KUNIT_TEST=y CONFIG_CRYPTO_LIB_SM3_KUNIT_TEST=y diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index d683b8520f555d..832d170cab941a 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -376,6 +376,12 @@ endif # CONFIG_CRYPTO_LIB_SHA3_ARCH ################################################################################ +# SipHash support is always built-in because it's used by core components such +# as printk (for pointer hashing) and networking. +obj-y += siphash.o + +################################################################################ + obj-$(CONFIG_CRYPTO_LIB_SM3) += libsm3.o libsm3-y := sm3.o ifeq ($(CONFIG_CRYPTO_LIB_SM3_ARCH),y) diff --git a/lib/siphash.c b/lib/crypto/siphash.c similarity index 100% rename from lib/siphash.c rename to lib/crypto/siphash.c diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig index e121114624dabe..67026ef1e8f668 100644 --- a/lib/crypto/tests/Kconfig +++ b/lib/crypto/tests/Kconfig @@ -151,6 +151,15 @@ config CRYPTO_LIB_SHA3_KUNIT_TEST including SHA3-224, SHA3-256, SHA3-384, SHA3-512, SHAKE128 and SHAKE256. +config CRYPTO_LIB_SIPHASH_KUNIT_TEST + tristate "KUnit tests for SipHash" if !KUNIT_ALL_TESTS + depends on KUNIT + default KUNIT_ALL_TESTS + # SipHash support is always built-in, so there's no kconfig option for + # it that needs to be depended on here. + help + KUnit tests for the SipHash cryptographically secure PRF. + config CRYPTO_LIB_SM3_KUNIT_TEST tristate "KUnit tests for SM3" if !KUNIT_ALL_TESTS depends on KUNIT && CRYPTO_LIB_SM3 diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile index c97c1d78784a75..f639d76a8fb73d 100644 --- a/lib/crypto/tests/Makefile +++ b/lib/crypto/tests/Makefile @@ -17,4 +17,5 @@ obj-$(CONFIG_CRYPTO_LIB_SHA1_KUNIT_TEST) += sha1_kunit.o obj-$(CONFIG_CRYPTO_LIB_SHA256_KUNIT_TEST) += sha224_kunit.o sha256_kunit.o obj-$(CONFIG_CRYPTO_LIB_SHA512_KUNIT_TEST) += sha384_kunit.o sha512_kunit.o obj-$(CONFIG_CRYPTO_LIB_SHA3_KUNIT_TEST) += sha3_kunit.o +obj-$(CONFIG_CRYPTO_LIB_SIPHASH_KUNIT_TEST) += siphash_kunit.o obj-$(CONFIG_CRYPTO_LIB_SM3_KUNIT_TEST) += sm3_kunit.o diff --git a/lib/tests/siphash_kunit.c b/lib/crypto/tests/siphash_kunit.c similarity index 100% rename from lib/tests/siphash_kunit.c rename to lib/crypto/tests/siphash_kunit.c diff --git a/lib/tests/Makefile b/lib/tests/Makefile index 3cac3b63a7522c..1a3d39db268f3a 100644 --- a/lib/tests/Makefile +++ b/lib/tests/Makefile @@ -46,7 +46,6 @@ obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o obj-$(CONFIG_SEQ_BUF_KUNIT_TEST) += seq_buf_kunit.o -obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o obj-$(CONFIG_SLUB_KUNIT_TEST) += slub_kunit.o obj-$(CONFIG_TEST_SORT) += test_sort.o CFLAGS_stackinit_kunit.o += $(call cc-disable-warning, switch-unreachable) diff --git a/tools/testing/selftests/bpf/progs/test_siphash.h b/tools/testing/selftests/bpf/progs/test_siphash.h index 5d3a7ec3678082..9a85670a9deae7 100644 --- a/tools/testing/selftests/bpf/progs/test_siphash.h +++ b/tools/testing/selftests/bpf/progs/test_siphash.h @@ -22,7 +22,7 @@ static inline u64 rol64(u64 word, unsigned int shift) #define SIPHASH_CONST_2 0x6c7967656e657261ULL #define SIPHASH_CONST_3 0x7465646279746573ULL -/* lib/siphash.c */ +/* lib/crypto/siphash.c */ #define SIPROUND SIPHASH_PERMUTATION(v0, v1, v2, v3) #define PREAMBLE(len) \ From 5d59024a6cc9aa9300da59fbf7e2c29f1f2393c6 Mon Sep 17 00:00:00 2001 From: Ricardo Robaina Date: Fri, 3 Jul 2026 09:25:49 -0300 Subject: [PATCH 1272/2286] audit: add FSOPEN record to log filesystem name Modern mount tools (util-linux >= 2.39.1) use the new mount API (fsopen, fsconfig, fsmount, move_mount) instead of the legacy mount(2) syscall. The generic SYSCALL audit record logs the fsopen syscall but does not capture the filesystem name string, creating an audit gap for filesystem mount operations. Add an FSOPEN auxiliary record that logs the dereferenced filesystem name string passed to fsopen(2). type=SYSCALL ... : arch=x86_64 syscall=fsopen ... a1=FSOPEN_CLOEXEC type=FSOPEN ... : fs_name="tmpfs" Link: https://github.com/linux-audit/audit-kernel/issues/152 Signed-off-by: Ricardo Robaina Reviewed-by: Richard Guy Briggs [PM: Christian Brauner commented "Seems fine"] Signed-off-by: Paul Moore --- fs/fsopen.c | 3 +++ include/linux/audit.h | 10 ++++++++++ include/uapi/linux/audit.h | 1 + kernel/auditsc.c | 13 +++++++++++++ 4 files changed, 27 insertions(+) diff --git a/fs/fsopen.c b/fs/fsopen.c index ae19e51365986f..70024870fefa2e 100644 --- a/fs/fsopen.c +++ b/fs/fsopen.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "internal.h" #include "mount.h" @@ -134,6 +135,8 @@ SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags) if (IS_ERR(fs_name)) return PTR_ERR(fs_name); + audit_log_fsopen(fs_name); + fs_type = get_fs_type(fs_name); kfree(fs_name); if (!fs_type) diff --git a/include/linux/audit.h b/include/linux/audit.h index 45abb3722d304c..077b2667180d73 100644 --- a/include/linux/audit.h +++ b/include/linux/audit.h @@ -449,6 +449,7 @@ extern void __audit_tk_injoffset(struct timespec64 offset); extern void __audit_ntp_log(const struct audit_ntp_data *ad); extern void __audit_log_nfcfg(const char *name, u8 af, unsigned int nentries, enum audit_nfcfgop op, gfp_t gfp); +extern void __audit_log_fsopen(const char *fs_name); static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp) { @@ -598,6 +599,12 @@ static inline void audit_log_nfcfg(const char *name, u8 af, __audit_log_nfcfg(name, af, nentries, op, gfp); } +static inline void audit_log_fsopen(const char *fs_name) +{ + if (!audit_dummy_context()) + __audit_log_fsopen(fs_name); +} + extern int audit_n_rules; extern int audit_signals; #else /* CONFIG_AUDITSYSCALL */ @@ -730,6 +737,9 @@ static inline void audit_log_nfcfg(const char *name, u8 af, enum audit_nfcfgop op, gfp_t gfp) { } +static inline void audit_log_fsopen(const char *fs_name) +{ } + #define audit_n_rules 0 #define audit_signals 0 #endif /* CONFIG_AUDITSYSCALL */ diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index e8f5ce677df734..abae0524746e40 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -122,6 +122,7 @@ #define AUDIT_OPENAT2 1337 /* Record showing openat2 how args */ #define AUDIT_DM_CTRL 1338 /* Device Mapper target control */ #define AUDIT_DM_EVENT 1339 /* Device Mapper events */ +#define AUDIT_FSOPEN 1340 /* Record showing fsopen fs_name arg */ #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */ #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */ diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 2b9ce0b525111f..37933e31ae7948 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -2883,6 +2883,19 @@ void __audit_log_nfcfg(const char *name, u8 af, unsigned int nentries, } EXPORT_SYMBOL_GPL(__audit_log_nfcfg); +void __audit_log_fsopen(const char *fs_name) +{ + struct audit_buffer *ab; + + ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_FSOPEN); + if (!ab) + return; + + audit_log_format(ab, "fs_name="); + audit_log_untrustedstring(ab, fs_name); + audit_log_end(ab); +} + static void audit_log_task(struct audit_buffer *ab) { kuid_t auid, uid; From fe366c131a13a5e126cee05f677afc8b731964d2 Mon Sep 17 00:00:00 2001 From: Brigham Campbell Date: Sat, 3 Oct 2026 22:01:13 -0600 Subject: [PATCH 1273/2286] .gitignore: ignore pacman package signatures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore package signatures which are generated by makepkg when package signing is enabled. Signed-off-by: Brigham Campbell Acked-by: Thomas Weißschuh Link: https://patch.msgid.link/20261003-ignore-pacman-signature-v1-1-d16a9a026f55@brighamcampbell.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 948c760b270456..4bb3946f1b68bb 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ modules.order # # pacman files (make pacman-pkg) # +/*.pkg.*.sig /PKGBUILD /pacman/ From a7d32fa14bc74d1786a2f0513b6f0815bd4be311 Mon Sep 17 00:00:00 2001 From: Jan Stancek Date: Thu, 1 Oct 2026 09:11:10 +0200 Subject: [PATCH 1274/2286] .gitignore: drop Module.markers commit fc5377668c3d ("tracing: Remove markers") deleted the marker code and the modpost output. Follow-up commit 8723eaeff59a ("Kbuild: clean up marker") removed it from clean and mrproper targets. Both are from 2009, so the file is hopefully no longer in anyone's tree, drop it from .gitignore as well. Fixes: 8723eaeff59a ("Kbuild: clean up marker") Signed-off-by: Jan Stancek Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/8cbe913b9f80dc6fea73a9e3d51429db2b38905f.1790838401.git.jstancek@redhat.com Signed-off-by: Nathan Chancellor --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4bb3946f1b68bb..f0351b8a74d0e0 100644 --- a/.gitignore +++ b/.gitignore @@ -76,7 +76,6 @@ modules.order /vmlinux-gdb.py /vmlinuz /System.map -/Module.markers /modules.builtin /modules.builtin.modinfo /modules.builtin.ranges From 4c226fa3238a941b925a266c5bdc5f494fa2b751 Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Wed, 16 Sep 2026 14:53:46 +0800 Subject: [PATCH 1275/2286] drivers/perf: hisi: Consolidate uncore PMU cpuhp states Every HiSilicon uncore PMU driver allocates its own static cpuhp state even though they all register identical online/offline callbacks. This wastes slots in the cpuhp state enum and needlessly fragments the hotplug state space. Move the cpuhp state setup into the shared hisi_uncore_pmu framework: register a single dynamic state (CPUHP_AP_ONLINE_DYN) at framework module init and export it for all HiSilicon uncore PMU drivers to use. Drop the now-unused static CPUHP_AP_PERF_ARM_HISI_* entries from include/linux/cpuhotplug.h. The L3C PMU driver keeps a private dynamic state because it has its own hotplug callbacks for migrating multiple per-CPU interrupt affinities, but switches from its former static slot to CPUHP_AP_ONLINE_DYN for consistency. No intended functional changes. Signed-off-by: Yushan Wang Reviewed-by: Robin Murphy Signed-off-by: Will Deacon --- drivers/perf/hisilicon/hisi_uncore_cpa_pmu.c | 34 +++--------------- drivers/perf/hisilicon/hisi_uncore_ddrc_pmu.c | 34 +++--------------- drivers/perf/hisilicon/hisi_uncore_hha_pmu.c | 34 +++--------------- drivers/perf/hisilicon/hisi_uncore_l3c_pmu.c | 21 +++++------ drivers/perf/hisilicon/hisi_uncore_mn_pmu.c | 35 ++---------------- drivers/perf/hisilicon/hisi_uncore_noc_pmu.c | 35 ++---------------- drivers/perf/hisilicon/hisi_uncore_pa_pmu.c | 34 +++--------------- drivers/perf/hisilicon/hisi_uncore_pmu.c | 25 +++++++++++++ drivers/perf/hisilicon/hisi_uncore_pmu.h | 2 ++ drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c | 34 +++--------------- drivers/perf/hisilicon/hisi_uncore_uc_pmu.c | 36 ++----------------- include/linux/cpuhotplug.h | 6 ---- 12 files changed, 67 insertions(+), 263 deletions(-) diff --git a/drivers/perf/hisilicon/hisi_uncore_cpa_pmu.c b/drivers/perf/hisilicon/hisi_uncore_cpa_pmu.c index b879b81adfddd4..83967725c51ca2 100644 --- a/drivers/perf/hisilicon/hisi_uncore_cpa_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_cpa_pmu.c @@ -295,7 +295,7 @@ static int hisi_cpa_pmu_probe(struct platform_device *pdev) /* Power Management should be disabled before using CPA PMU. */ hisi_cpa_pmu_disable_pm(cpa_pmu); - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE, + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &cpa_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug\n", ret); @@ -307,7 +307,7 @@ static int hisi_cpa_pmu_probe(struct platform_device *pdev) if (ret) { dev_err(cpa_pmu->dev, "PMU register failed\n"); cpuhp_state_remove_instance_nocalls( - CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE, &cpa_pmu->node); + hisi_uncore_pmu_cpuhp_state, &cpa_pmu->node); hisi_cpa_pmu_enable_pm(cpa_pmu); return ret; } @@ -321,7 +321,7 @@ static void hisi_cpa_pmu_remove(struct platform_device *pdev) struct hisi_pmu *cpa_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&cpa_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &cpa_pmu->node); hisi_cpa_pmu_enable_pm(cpa_pmu); } @@ -336,33 +336,7 @@ static struct platform_driver hisi_cpa_pmu_driver = { .remove = hisi_cpa_pmu_remove, }; -static int __init hisi_cpa_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE, - "AP_PERF_ARM_HISI_CPA_ONLINE", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret) { - pr_err("setup hotplug failed: %d\n", ret); - return ret; - } - - ret = platform_driver_register(&hisi_cpa_pmu_driver); - if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE); - - return ret; -} -module_init(hisi_cpa_pmu_module_init); - -static void __exit hisi_cpa_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_cpa_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE); -} -module_exit(hisi_cpa_pmu_module_exit); +module_platform_driver(hisi_cpa_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC CPA PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_ddrc_pmu.c b/drivers/perf/hisilicon/hisi_uncore_ddrc_pmu.c index 21c494881ca04b..e905160523ee29 100644 --- a/drivers/perf/hisilicon/hisi_uncore_ddrc_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_ddrc_pmu.c @@ -401,7 +401,7 @@ static int hisi_ddrc_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE, + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &ddrc_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug;\n", ret); @@ -414,7 +414,7 @@ static int hisi_ddrc_pmu_probe(struct platform_device *pdev) if (ret) { dev_err(ddrc_pmu->dev, "DDRC PMU register failed!\n"); cpuhp_state_remove_instance_nocalls( - CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE, &ddrc_pmu->node); + hisi_uncore_pmu_cpuhp_state, &ddrc_pmu->node); } return ret; @@ -425,7 +425,7 @@ static void hisi_ddrc_pmu_remove(struct platform_device *pdev) struct hisi_pmu *ddrc_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&ddrc_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &ddrc_pmu->node); } @@ -501,33 +501,7 @@ static struct platform_driver hisi_ddrc_pmu_driver = { .remove = hisi_ddrc_pmu_remove, }; -static int __init hisi_ddrc_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE, - "AP_PERF_ARM_HISI_DDRC_ONLINE", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret) { - pr_err("DDRC PMU: setup hotplug, ret = %d\n", ret); - return ret; - } - - ret = platform_driver_register(&hisi_ddrc_pmu_driver); - if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE); - - return ret; -} -module_init(hisi_ddrc_pmu_module_init); - -static void __exit hisi_ddrc_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_ddrc_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE); -} -module_exit(hisi_ddrc_pmu_module_exit); +module_platform_driver(hisi_ddrc_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC DDRC uncore PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_hha_pmu.c b/drivers/perf/hisilicon/hisi_uncore_hha_pmu.c index 97cfaa586a87a7..93a250b7abb8ec 100644 --- a/drivers/perf/hisilicon/hisi_uncore_hha_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_hha_pmu.c @@ -490,7 +490,7 @@ static int hisi_hha_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE, + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &hha_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug\n", ret); @@ -503,7 +503,7 @@ static int hisi_hha_pmu_probe(struct platform_device *pdev) if (ret) { dev_err(hha_pmu->dev, "HHA PMU register failed!\n"); cpuhp_state_remove_instance_nocalls( - CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE, &hha_pmu->node); + hisi_uncore_pmu_cpuhp_state, &hha_pmu->node); } return ret; @@ -514,7 +514,7 @@ static void hisi_hha_pmu_remove(struct platform_device *pdev) struct hisi_pmu *hha_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&hha_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &hha_pmu->node); } @@ -528,33 +528,7 @@ static struct platform_driver hisi_hha_pmu_driver = { .remove = hisi_hha_pmu_remove, }; -static int __init hisi_hha_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE, - "AP_PERF_ARM_HISI_HHA_ONLINE", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret) { - pr_err("HHA PMU: Error setup hotplug, ret = %d;\n", ret); - return ret; - } - - ret = platform_driver_register(&hisi_hha_pmu_driver); - if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE); - - return ret; -} -module_init(hisi_hha_pmu_module_init); - -static void __exit hisi_hha_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_hha_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE); -} -module_exit(hisi_hha_pmu_module_exit); +module_platform_driver(hisi_hha_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC HHA uncore PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_l3c_pmu.c b/drivers/perf/hisilicon/hisi_uncore_l3c_pmu.c index 56a88fb0d3c2c0..e40506e09702d3 100644 --- a/drivers/perf/hisilicon/hisi_uncore_l3c_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_l3c_pmu.c @@ -93,6 +93,8 @@ struct hisi_l3c_pmu_ext { bool support_ext; }; +static enum cpuhp_state hisi_l3c_pmu_cpuhp_state; + static bool support_ext(struct hisi_l3c_pmu *pmu) { struct hisi_l3c_pmu_ext *l3c_pmu_ext = pmu->l3c_pmu.dev_info->private; @@ -844,8 +846,7 @@ static int hisi_l3c_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE, - &l3c_pmu->node); + ret = cpuhp_state_add_instance(hisi_l3c_pmu_cpuhp_state, &l3c_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug\n", ret); return ret; @@ -857,7 +858,7 @@ static int hisi_l3c_pmu_probe(struct platform_device *pdev) if (ret) { dev_err(l3c_pmu->dev, "L3C PMU register failed!\n"); cpuhp_state_remove_instance_nocalls( - CPUHP_AP_PERF_ARM_HISI_L3_ONLINE, &l3c_pmu->node); + hisi_l3c_pmu_cpuhp_state, &l3c_pmu->node); } return ret; @@ -868,7 +869,7 @@ static void hisi_l3c_pmu_remove(struct platform_device *pdev) struct hisi_pmu *l3c_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&l3c_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_l3c_pmu_cpuhp_state, &l3c_pmu->node); } @@ -938,20 +939,20 @@ static int hisi_l3c_pmu_offline_cpu(unsigned int cpu, struct hlist_node *node) static int __init hisi_l3c_pmu_module_init(void) { - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE, + int ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "AP_PERF_ARM_HISI_L3_ONLINE", hisi_l3c_pmu_online_cpu, hisi_l3c_pmu_offline_cpu); - if (ret) { + if (ret < 0) { pr_err("L3C PMU: Error setup hotplug, ret = %d\n", ret); return ret; } + hisi_l3c_pmu_cpuhp_state = ret; + ret = platform_driver_register(&hisi_l3c_pmu_driver); if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE); + cpuhp_remove_multi_state(hisi_l3c_pmu_cpuhp_state); return ret; } @@ -960,7 +961,7 @@ module_init(hisi_l3c_pmu_module_init); static void __exit hisi_l3c_pmu_module_exit(void) { platform_driver_unregister(&hisi_l3c_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_L3_ONLINE); + cpuhp_remove_multi_state(hisi_l3c_pmu_cpuhp_state); } module_exit(hisi_l3c_pmu_module_exit); diff --git a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c index 246cc0333099df..ad898153ff49b8 100644 --- a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c @@ -13,9 +13,6 @@ #include "hisi_uncore_pmu.h" -/* Dynamic CPU hotplug state used by MN PMU */ -static enum cpuhp_state hisi_mn_pmu_online; - /* MN register definition */ #define HISI_MN_DYNAMIC_CTRL_REG 0x400 #define HISI_MN_DYNAMIC_CTRL_EN BIT(0) @@ -291,7 +288,7 @@ static int hisi_mn_pmu_dev_init(struct platform_device *pdev, static void hisi_mn_pmu_remove_cpuhp(void *hotplug_node) { - cpuhp_state_remove_instance_nocalls(hisi_mn_pmu_online, hotplug_node); + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, hotplug_node); } static void hisi_mn_pmu_unregister(void *pmu) @@ -320,7 +317,7 @@ static int hisi_mn_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(hisi_mn_pmu_online, &mn_pmu->node); + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &mn_pmu->node); if (ret) return dev_err_probe(&pdev->dev, ret, "Failed to register cpu hotplug\n"); @@ -376,33 +373,7 @@ static struct platform_driver hisi_mn_pmu_driver = { .probe = hisi_mn_pmu_probe, }; -static int __init hisi_mn_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/mn:online", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret < 0) { - pr_err("hisi_mn_pmu: Failed to setup MN PMU hotplug: %d\n", ret); - return ret; - } - hisi_mn_pmu_online = ret; - - ret = platform_driver_register(&hisi_mn_pmu_driver); - if (ret) - cpuhp_remove_multi_state(hisi_mn_pmu_online); - - return ret; -} -module_init(hisi_mn_pmu_module_init); - -static void __exit hisi_mn_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_mn_pmu_driver); - cpuhp_remove_multi_state(hisi_mn_pmu_online); -} -module_exit(hisi_mn_pmu_module_exit); +module_platform_driver(hisi_mn_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC MN uncore PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_noc_pmu.c b/drivers/perf/hisilicon/hisi_uncore_noc_pmu.c index 616f4af57db75e..abf44fa46d171c 100644 --- a/drivers/perf/hisilicon/hisi_uncore_noc_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_noc_pmu.c @@ -41,9 +41,6 @@ HISI_PMU_EVENT_ATTR_EXTRACTOR(ch, config1, 2, 0); HISI_PMU_EVENT_ATTR_EXTRACTOR(tt_en, config1, 3, 3); -/* Dynamic CPU hotplug state used by this PMU driver */ -static enum cpuhp_state hisi_noc_pmu_cpuhp_state; - struct hisi_noc_pmu_regs { u32 version; u32 pmu_ctrl; @@ -324,7 +321,7 @@ static int hisi_noc_pmu_dev_init(struct platform_device *pdev, struct hisi_pmu * static void hisi_noc_pmu_remove_cpuhp_instance(void *hotplug_node) { - cpuhp_state_remove_instance_nocalls(hisi_noc_pmu_cpuhp_state, hotplug_node); + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, hotplug_node); } static void hisi_noc_pmu_unregister_pmu(void *pmu) @@ -353,7 +350,7 @@ static int hisi_noc_pmu_probe(struct platform_device *pdev) if (ret) return ret; - ret = cpuhp_state_add_instance(hisi_noc_pmu_cpuhp_state, &noc_pmu->node); + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &noc_pmu->node); if (ret) return dev_err_probe(dev, ret, "Fail to register cpuhp instance\n"); @@ -408,33 +405,7 @@ static struct platform_driver hisi_noc_pmu_driver = { .probe = hisi_noc_pmu_probe, }; -static int __init hisi_noc_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/noc:online", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret < 0) { - pr_err("hisi_noc_pmu: Fail to setup cpuhp callbacks, ret = %d\n", ret); - return ret; - } - hisi_noc_pmu_cpuhp_state = ret; - - ret = platform_driver_register(&hisi_noc_pmu_driver); - if (ret) - cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state); - - return ret; -} -module_init(hisi_noc_pmu_module_init); - -static void __exit hisi_noc_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_noc_pmu_driver); - cpuhp_remove_multi_state(hisi_noc_pmu_cpuhp_state); -} -module_exit(hisi_noc_pmu_module_exit); +module_platform_driver(hisi_noc_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC Uncore NoC PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_pa_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pa_pmu.c index 80108c63cb6050..3d1e0af64260a2 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pa_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_pa_pmu.c @@ -468,7 +468,7 @@ static int hisi_pa_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE, + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &pa_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug\n", ret); @@ -479,7 +479,7 @@ static int hisi_pa_pmu_probe(struct platform_device *pdev) ret = perf_pmu_register(&pa_pmu->pmu, name, -1); if (ret) { dev_err(pa_pmu->dev, "PMU register failed, ret = %d\n", ret); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &pa_pmu->node); return ret; } @@ -493,7 +493,7 @@ static void hisi_pa_pmu_remove(struct platform_device *pdev) struct hisi_pmu *pa_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&pa_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &pa_pmu->node); } @@ -515,33 +515,7 @@ static struct platform_driver hisi_pa_pmu_driver = { .remove = hisi_pa_pmu_remove, }; -static int __init hisi_pa_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE, - "AP_PERF_ARM_HISI_PA_ONLINE", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret) { - pr_err("PA PMU: cpuhp state setup failed, ret = %d\n", ret); - return ret; - } - - ret = platform_driver_register(&hisi_pa_pmu_driver); - if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE); - - return ret; -} -module_init(hisi_pa_pmu_module_init); - -static void __exit hisi_pa_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_pa_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_PA_ONLINE); -} -module_exit(hisi_pa_pmu_module_exit); +module_platform_driver(hisi_pa_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon Protocol Adapter uncore PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.c b/drivers/perf/hisilicon/hisi_uncore_pmu.c index 77390d033d087d..346c87594614db 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.c @@ -23,6 +23,9 @@ #define HISI_MAX_PERIOD(nr) (GENMASK_ULL((nr) - 1, 0)) +enum cpuhp_state hisi_uncore_pmu_cpuhp_state; +EXPORT_SYMBOL_NS_GPL(hisi_uncore_pmu_cpuhp_state, "HISI_PMU"); + /* * PMU event attributes */ @@ -618,5 +621,27 @@ void hisi_pmu_init(struct hisi_pmu *hisi_pmu, struct module *module) } EXPORT_SYMBOL_NS_GPL(hisi_pmu_init, "HISI_PMU"); +static int __init hisi_uncore_pmu_init(void) +{ + int ret; + + ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, "perf/hisi/uncore:online", + hisi_uncore_pmu_online_cpu, + hisi_uncore_pmu_offline_cpu); + if (ret < 0) + return ret; + + hisi_uncore_pmu_cpuhp_state = ret; + + return 0; +} +module_init(hisi_uncore_pmu_init); + +static void __exit hisi_uncore_pmu_exit(void) +{ + cpuhp_remove_multi_state(hisi_uncore_pmu_cpuhp_state); +} +module_exit(hisi_uncore_pmu_exit); + MODULE_DESCRIPTION("HiSilicon SoC uncore Performance Monitor driver framework"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/perf/hisilicon/hisi_uncore_pmu.h b/drivers/perf/hisilicon/hisi_uncore_pmu.h index 3ffe6acda65391..e5a4b38988658f 100644 --- a/drivers/perf/hisilicon/hisi_uncore_pmu.h +++ b/drivers/perf/hisilicon/hisi_uncore_pmu.h @@ -171,4 +171,6 @@ int hisi_uncore_pmu_init_irq(struct hisi_pmu *hisi_pmu, void hisi_uncore_pmu_init_topology(struct hisi_pmu *hisi_pmu, struct device *dev); void hisi_pmu_init(struct hisi_pmu *hisi_pmu, struct module *module); + +extern enum cpuhp_state hisi_uncore_pmu_cpuhp_state; #endif /* __HISI_UNCORE_PMU_H__ */ diff --git a/drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c b/drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c index cd32d606df0548..d01340ff2e5ce5 100644 --- a/drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_sllc_pmu.c @@ -503,7 +503,7 @@ static int hisi_sllc_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &sllc_pmu->node); if (ret) { dev_err(&pdev->dev, "Error %d registering hotplug\n", ret); @@ -515,7 +515,7 @@ static int hisi_sllc_pmu_probe(struct platform_device *pdev) ret = perf_pmu_register(&sllc_pmu->pmu, name, -1); if (ret) { dev_err(sllc_pmu->dev, "PMU register failed, ret = %d\n", ret); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &sllc_pmu->node); return ret; } @@ -530,7 +530,7 @@ static void hisi_sllc_pmu_remove(struct platform_device *pdev) struct hisi_pmu *sllc_pmu = platform_get_drvdata(pdev); perf_pmu_unregister(&sllc_pmu->pmu); - cpuhp_state_remove_instance_nocalls(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, &sllc_pmu->node); } @@ -551,33 +551,7 @@ static struct platform_driver hisi_sllc_pmu_driver = { .remove = hisi_sllc_pmu_remove, }; -static int __init hisi_sllc_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, - "AP_PERF_ARM_HISI_SLLC_ONLINE", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret) { - pr_err("SLLC PMU: cpuhp state setup failed, ret = %d\n", ret); - return ret; - } - - ret = platform_driver_register(&hisi_sllc_pmu_driver); - if (ret) - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE); - - return ret; -} -module_init(hisi_sllc_pmu_module_init); - -static void __exit hisi_sllc_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_sllc_pmu_driver); - cpuhp_remove_multi_state(CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE); -} -module_exit(hisi_sllc_pmu_module_exit); +module_platform_driver(hisi_sllc_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SLLC uncore PMU driver"); diff --git a/drivers/perf/hisilicon/hisi_uncore_uc_pmu.c b/drivers/perf/hisilicon/hisi_uncore_uc_pmu.c index e8186b6e168747..b4d017a5d688b3 100644 --- a/drivers/perf/hisilicon/hisi_uncore_uc_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_uc_pmu.c @@ -13,9 +13,6 @@ #include "hisi_uncore_pmu.h" -/* Dynamic CPU hotplug state used by UC PMU */ -static enum cpuhp_state hisi_uc_pmu_online; - /* UC register definition */ #define HISI_UC_INT_MASK_REG 0x0800 #define HISI_UC_INT_STS_REG 0x0808 @@ -488,7 +485,7 @@ static int hisi_uc_pmu_dev_probe(struct platform_device *pdev, static void hisi_uc_pmu_remove_cpuhp_instance(void *hotplug_node) { - cpuhp_state_remove_instance_nocalls(hisi_uc_pmu_online, hotplug_node); + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, hotplug_node); } static void hisi_uc_pmu_unregister_pmu(void *pmu) @@ -518,7 +515,7 @@ static int hisi_uc_pmu_probe(struct platform_device *pdev) if (!name) return -ENOMEM; - ret = cpuhp_state_add_instance(hisi_uc_pmu_online, &uc_pmu->node); + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &uc_pmu->node); if (ret) return dev_err_probe(&pdev->dev, ret, "Error registering hotplug\n"); @@ -559,34 +556,7 @@ static struct platform_driver hisi_uc_pmu_driver = { .probe = hisi_uc_pmu_probe, }; -static int __init hisi_uc_pmu_module_init(void) -{ - int ret; - - ret = cpuhp_setup_state_multi(CPUHP_AP_ONLINE_DYN, - "perf/hisi/uc:online", - hisi_uncore_pmu_online_cpu, - hisi_uncore_pmu_offline_cpu); - if (ret < 0) { - pr_err("UC PMU: Error setup hotplug, ret = %d\n", ret); - return ret; - } - hisi_uc_pmu_online = ret; - - ret = platform_driver_register(&hisi_uc_pmu_driver); - if (ret) - cpuhp_remove_multi_state(hisi_uc_pmu_online); - - return ret; -} -module_init(hisi_uc_pmu_module_init); - -static void __exit hisi_uc_pmu_module_exit(void) -{ - platform_driver_unregister(&hisi_uc_pmu_driver); - cpuhp_remove_multi_state(hisi_uc_pmu_online); -} -module_exit(hisi_uc_pmu_module_exit); +module_platform_driver(hisi_uc_pmu_driver); MODULE_IMPORT_NS("HISI_PMU"); MODULE_DESCRIPTION("HiSilicon SoC UC uncore PMU driver"); diff --git a/include/linux/cpuhotplug.h b/include/linux/cpuhotplug.h index feb32949aeea6a..1b6dba159bb23c 100644 --- a/include/linux/cpuhotplug.h +++ b/include/linux/cpuhotplug.h @@ -209,12 +209,6 @@ enum cpuhp_state { CPUHP_AP_PERF_S390_SF_ONLINE, CPUHP_AP_PERF_ARM_CCI_ONLINE, CPUHP_AP_PERF_ARM_CCN_ONLINE, - CPUHP_AP_PERF_ARM_HISI_CPA_ONLINE, - CPUHP_AP_PERF_ARM_HISI_DDRC_ONLINE, - CPUHP_AP_PERF_ARM_HISI_HHA_ONLINE, - CPUHP_AP_PERF_ARM_HISI_L3_ONLINE, - CPUHP_AP_PERF_ARM_HISI_PA_ONLINE, - CPUHP_AP_PERF_ARM_HISI_SLLC_ONLINE, CPUHP_AP_PERF_ARM_HISI_PCIE_PMU_ONLINE, CPUHP_AP_PERF_ARM_HNS3_PMU_ONLINE, CPUHP_AP_PERF_ARM_L2X0_ONLINE, From 38de567676bf66d5e39a533c5955a80cd5d3a89d Mon Sep 17 00:00:00 2001 From: Yushan Wang Date: Wed, 16 Sep 2026 14:53:47 +0800 Subject: [PATCH 1276/2286] drivers/perf: hisi: Add support for uncore ITS PMU The Interrupt Translation Service (ITS) PMU counts interrupts routed to the ITS by category (LPI/SGI/PPI event counts and latencies) and also collects micro-op statistics for the ITS itself. Add a driver for the ITS PMU on the HiSilicon uncore PMU framework. Like the NoC PMU it has no overflow interrupt, so the interrupt enable/disable callbacks are empty stubs. The PMU supports filtering counters by a 32-bit interrupt id (int_id), composed of a 16-bit DeviceID/PEID in the upper half and a 16-bit EventID/IntID in the lower half. The INT_ID filter is global to the PMU instance, so only events with the same filter configuration may run at the same time; events with different filters will be time-multiplexed. Document the int_id and int_en format attributes in Documentation/admin-guide/perf/hisi-pmu.rst. Signed-off-by: Yushan Wang Signed-off-by: Will Deacon --- Documentation/admin-guide/perf/hisi-pmu.rst | 16 + drivers/perf/hisilicon/Makefile | 2 +- drivers/perf/hisilicon/hisi_uncore_its_pmu.c | 385 +++++++++++++++++++ 3 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 drivers/perf/hisilicon/hisi_uncore_its_pmu.c diff --git a/Documentation/admin-guide/perf/hisi-pmu.rst b/Documentation/admin-guide/perf/hisi-pmu.rst index d56b2d69070927..8cb4711c9c37e0 100644 --- a/Documentation/admin-guide/perf/hisi-pmu.rst +++ b/Documentation/admin-guide/perf/hisi-pmu.rst @@ -128,6 +128,22 @@ channel with this option. The current supported channels are as follows: 7. tt_en: NoC PMU supports counting only transactions that have tracetag set if this option is set. See the 2nd list for more information about tracetag. +8. int_id: ITS PMU supports filtering by interrupt id, which is defined by +hardware. Interrupt id takes up to 32 bits, and can be divided into 2 parts: + +- Upper 16 bits: DeviceID if counting LPI, PEID if counting SGI/PPI. +- Lower 16 bits: EventID if counting LPI, IntID if counting SGI/PPI. + +int_id is a global configuration for each PMU instance: the filter applies to +all counters, so the PMU can only run events that agree on it. An unfiltered +event (int_en not set) cannot run concurrently with a filtered one, and two +filtered events can run concurrently only if they filter the same int_id. +perf time-multiplexes any events that cannot coexist, rather than corrupting +the counts. + +9. int_en: A one-bit flag to tell if int_id is used to filter the statistics. It +allows filtering 0 DeviceID and EventID. + For HiSilicon uncore PMU v3 whose identifier is 0x40, some uncore PMUs are further divided into parts for finer granularity of tracing, each part has its own dedicated PMU, and all such PMUs together cover the monitoring job of events diff --git a/drivers/perf/hisilicon/Makefile b/drivers/perf/hisilicon/Makefile index 186be3d02238b1..5f28cfdb8a72d2 100644 --- a/drivers/perf/hisilicon/Makefile +++ b/drivers/perf/hisilicon/Makefile @@ -2,7 +2,7 @@ obj-$(CONFIG_HISI_PMU) += hisi_uncore_pmu.o hisi_uncore_l3c_pmu.o \ hisi_uncore_hha_pmu.o hisi_uncore_ddrc_pmu.o hisi_uncore_sllc_pmu.o \ hisi_uncore_pa_pmu.o hisi_uncore_cpa_pmu.o hisi_uncore_uc_pmu.o \ - hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o + hisi_uncore_noc_pmu.o hisi_uncore_mn_pmu.o hisi_uncore_its_pmu.o obj-$(CONFIG_HISI_PCIE_PMU) += hisi_pcie_pmu.o obj-$(CONFIG_HNS3_PMU) += hns3_pmu.o diff --git a/drivers/perf/hisilicon/hisi_uncore_its_pmu.c b/drivers/perf/hisilicon/hisi_uncore_its_pmu.c new file mode 100644 index 00000000000000..66311657db892d --- /dev/null +++ b/drivers/perf/hisilicon/hisi_uncore_its_pmu.c @@ -0,0 +1,385 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for HiSilicon Uncore ITS PMU device + * + * Copyright (c) 2026 HiSilicon Technologies Co., Ltd. + * Author: Yushan Wang + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hisi_uncore_pmu.h" + +#define ITS_PMU_VERSION 0x21000 +#define ITS_PMU_GLOBAL_CTRL 0x21004 +#define ITS_PMU_GLOBAL_CTRL_PMU_EN BIT(0) +#define ITS_PMU_COUNTER_CTRL 0x21008 +#define ITS_PMU_EVENT_CTRL 0x2100c +#define ITS_PMU_COUNTER0 0x21010 + +#define ITS_PMU_INT_ID_MASK 0x20008 +#define ITS_PMU_INT_ID_CTRL 0x20084 + +#define ITS_PMU_NR_COUNTERS 4 + +#define ITS_PMU_EVENT_CNTRn(cntr0, n) ((cntr0) + 8 * (n)) +#define ITS_PMU_CNTR_CTRL_MASK(n) GENMASK(8 * ((n) + 1) - 1, 8 * (n)) +#define ITS_PMU_CNTR_EVENT_CFG(n, e) ((e) << ((n) * 8)) +#define ITS_PMU_EVENT_CTRL_TYPE GENMASK(12, 0) + +HISI_PMU_EVENT_ATTR_EXTRACTOR(int_id, config1, 31, 0); +HISI_PMU_EVENT_ATTR_EXTRACTOR(int_en, config1, 32, 32); + +struct hisi_its_pmu_regs { + u32 version; + u32 pmu_ctrl; + u32 event_ctrl0; + u32 event_cntr0; + u32 cntr_ctrl; +}; + +static void hisi_its_pmu_write_evtype(struct hisi_pmu *pmu, int idx, u32 type) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + u32 reg; + + reg = readl(pmu->base + reg_info->event_ctrl0); + reg &= ~ITS_PMU_CNTR_CTRL_MASK(idx); + reg |= ITS_PMU_CNTR_EVENT_CFG(idx, type); + writel(reg, pmu->base + reg_info->event_ctrl0); +} + +static u64 hisi_its_pmu_read_counter(struct hisi_pmu *pmu, + struct hw_perf_event *hwc) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + + return readq(pmu->base + ITS_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx)); +} + +static void hisi_its_pmu_write_counter(struct hisi_pmu *pmu, + struct hw_perf_event *hwc, u64 val) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + + writeq(val, pmu->base + ITS_PMU_EVENT_CNTRn(reg_info->event_cntr0, hwc->idx)); +} + +static void hisi_its_pmu_enable_counter(struct hisi_pmu *pmu, + struct hw_perf_event *hwc) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + u32 reg; + + reg = readl(pmu->base + reg_info->cntr_ctrl); + reg |= BIT(hwc->idx); + writel(reg, pmu->base + reg_info->cntr_ctrl); +} + +static void hisi_its_pmu_disable_counter(struct hisi_pmu *pmu, + struct hw_perf_event *hwc) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + u32 reg; + + reg = readl(pmu->base + reg_info->cntr_ctrl); + reg &= ~BIT(hwc->idx); + writel(reg, pmu->base + reg_info->cntr_ctrl); +} + +static void hisi_its_pmu_enable_counter_int(struct hisi_pmu *pmu, + struct hw_perf_event *hwc) +{ + /* No overflow interrupt; counters rely on the framework's priming. */ +} + +static void hisi_its_pmu_disable_counter_int(struct hisi_pmu *pmu, + struct hw_perf_event *hwc) +{ +} + +static void hisi_its_pmu_start_counters(struct hisi_pmu *pmu) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + u32 reg; + + reg = readl(pmu->base + reg_info->pmu_ctrl); + reg |= ITS_PMU_GLOBAL_CTRL_PMU_EN; + writel(reg, pmu->base + reg_info->pmu_ctrl); +} + +static void hisi_its_pmu_stop_counters(struct hisi_pmu *pmu) +{ + struct hisi_its_pmu_regs *reg_info = pmu->dev_info->private; + u32 reg; + + reg = readl(pmu->base + reg_info->pmu_ctrl); + reg &= ~ITS_PMU_GLOBAL_CTRL_PMU_EN; + writel(reg, pmu->base + reg_info->pmu_ctrl); +} + +static void hisi_its_pmu_enable_filter(struct perf_event *event) +{ + struct hisi_pmu *pmu = to_hisi_pmu(event->pmu); + u32 int_id = hisi_get_int_id(event); + u32 int_en = hisi_get_int_en(event); + + if (!int_en) + return; + + writel(int_id, pmu->base + ITS_PMU_INT_ID_CTRL); + /* Write 0 to this register to enable filtering with int_id. */ + writel(0, pmu->base + ITS_PMU_INT_ID_MASK); +} + +static void hisi_its_pmu_disable_filter(struct perf_event *event) +{ + struct hisi_pmu *pmu = to_hisi_pmu(event->pmu); + u32 int_en = hisi_get_int_en(event); + + if (!int_en) + return; + + /* + * The INT_ID filter is global, leave it alone if there are still + * active counters. + */ + if (bitmap_weight(pmu->pmu_events.used_mask, pmu->num_counters) > 1) + return; + + writel(0, pmu->base + ITS_PMU_INT_ID_CTRL); + writel(-1U, pmu->base + ITS_PMU_INT_ID_MASK); +} + +/* + * The INT_ID filter is global to the PMU. Events can coexist only if they + * agree on it: both unfiltered, or both filtering the same int_id. + */ +static bool hisi_its_pmu_check_global_filter(struct perf_event *curr, + struct perf_event *new) +{ + u32 curr_en = hisi_get_int_en(curr); + u32 new_en = hisi_get_int_en(new); + + if (curr_en != new_en) + return false; + + if (curr_en) + return hisi_get_int_id(curr) == hisi_get_int_id(new); + + return true; +} + +static int hisi_its_pmu_get_event_idx(struct perf_event *event) +{ + struct hisi_pmu *pmu = to_hisi_pmu(event->pmu); + struct hisi_pmu_hwevents *pmu_events = &pmu->pmu_events; + int cur_idx; + + cur_idx = find_first_bit(pmu_events->used_mask, pmu->num_counters); + if (cur_idx != pmu->num_counters && + !hisi_its_pmu_check_global_filter(pmu_events->hw_events[cur_idx], event)) + return -EAGAIN; + + return hisi_uncore_pmu_get_event_idx(event); +} + +static const struct hisi_uncore_ops hisi_uncore_its_ops = { + .write_evtype = hisi_its_pmu_write_evtype, + .get_event_idx = hisi_its_pmu_get_event_idx, + .read_counter = hisi_its_pmu_read_counter, + .write_counter = hisi_its_pmu_write_counter, + .enable_counter = hisi_its_pmu_enable_counter, + .disable_counter = hisi_its_pmu_disable_counter, + .enable_counter_int = hisi_its_pmu_enable_counter_int, + .disable_counter_int = hisi_its_pmu_disable_counter_int, + .start_counters = hisi_its_pmu_start_counters, + .stop_counters = hisi_its_pmu_stop_counters, + .enable_filter = hisi_its_pmu_enable_filter, + .disable_filter = hisi_its_pmu_disable_filter, +}; + +static struct attribute *hisi_its_pmu_format_attrs[] = { + HISI_PMU_FORMAT_ATTR(event, "config:0-7"), + HISI_PMU_FORMAT_ATTR(int_id, "config1:0-31"), + HISI_PMU_FORMAT_ATTR(int_en, "config1:32-32"), + NULL +}; + +static const struct attribute_group hisi_its_pmu_format_group = { + .name = "format", + .attrs = hisi_its_pmu_format_attrs, +}; + +static struct attribute *hisi_its_pmu_events_attrs[] = { + HISI_PMU_EVENT_ATTR(lpi_num, 0xc0), + HISI_PMU_EVENT_ATTR(lpi_time, 0x80), + HISI_PMU_EVENT_ATTR(sgi_num, 0xc1), + HISI_PMU_EVENT_ATTR(sgi_time, 0x81), + HISI_PMU_EVENT_ATTR(ppi_num, 0xc2), + HISI_PMU_EVENT_ATTR(ppi_time, 0x82), + HISI_PMU_EVENT_ATTR(sl3_lpi_num, 0xc3), + HISI_PMU_EVENT_ATTR(sl3_sgi_num, 0xc4), + HISI_PMU_EVENT_ATTR(sl3_ppi_num, 0xc5), + HISI_PMU_EVENT_ATTR(sl0_ddr_read, 0xc9), + HISI_PMU_EVENT_ATTR(sl0_ddr_time, 0x89), + HISI_PMU_EVENT_ATTR(sl1_ddr_read, 0xca), + HISI_PMU_EVENT_ATTR(sl1_ddr_time, 0x8a), + HISI_PMU_EVENT_ATTR(sl2_ddr_read, 0xcb), + HISI_PMU_EVENT_ATTR(sl2_ddr_time, 0x8b), + HISI_PMU_EVENT_ATTR(cycles, 0xcc), + NULL +}; + +static const struct attribute_group hisi_its_pmu_events_group = { + .name = "events", + .attrs = hisi_its_pmu_events_attrs, +}; + +static const struct attribute_group *hisi_its_pmu_attr_groups[] = { + &hisi_its_pmu_format_group, + &hisi_its_pmu_events_group, + &hisi_pmu_cpumask_attr_group, + &hisi_pmu_identifier_group, + NULL +}; + +static int hisi_its_pmu_dev_init(struct platform_device *pdev, + struct hisi_pmu *pmu) +{ + struct hisi_its_pmu_regs *reg_info; + + hisi_uncore_pmu_init_topology(pmu, &pdev->dev); + + if (pmu->topo.scl_id < 0) + return dev_err_probe(&pdev->dev, -EINVAL, "failed to get scl-id\n"); + + if (pmu->topo.index_id < 0) + return dev_err_probe(&pdev->dev, -EINVAL, "failed to get idx-id\n"); + + pmu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pmu->base)) + return dev_err_probe(&pdev->dev, PTR_ERR(pmu->base), + "fail to remap io memory\n"); + + pmu->dev_info = device_get_match_data(&pdev->dev); + if (!pmu->dev_info) + return -ENODEV; + + pmu->pmu_events.attr_groups = pmu->dev_info->attr_groups; + pmu->counter_bits = pmu->dev_info->counter_bits; + pmu->check_event = pmu->dev_info->check_event; + pmu->num_counters = ITS_PMU_NR_COUNTERS; + pmu->ops = &hisi_uncore_its_ops; + pmu->dev = &pdev->dev; + pmu->on_cpu = -1; + + reg_info = pmu->dev_info->private; + pmu->identifier = readl(pmu->base + reg_info->version); + + /* Reset the global INT_ID filter to disabled. */ + writel(0, pmu->base + ITS_PMU_INT_ID_CTRL); + writel(-1U, pmu->base + ITS_PMU_INT_ID_MASK); + + return 0; +} + +static void hisi_its_pmu_remove_cpuhp_instance(void *hotplug_node) +{ + cpuhp_state_remove_instance_nocalls(hisi_uncore_pmu_cpuhp_state, + hotplug_node); +} + +static void hisi_its_pmu_unregister_pmu(void *pmu) +{ + perf_pmu_unregister(pmu); +} + +static int hisi_its_pmu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hisi_pmu *pmu; + char *name; + int ret; + + pmu = devm_kzalloc(dev, sizeof(*pmu), GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + /* + * HiSilicon Uncore PMU framework needs to get common hisi_pmu device + * from device's drvdata. + */ + platform_set_drvdata(pdev, pmu); + + ret = hisi_its_pmu_dev_init(pdev, pmu); + if (ret) + return ret; + + ret = cpuhp_state_add_instance(hisi_uncore_pmu_cpuhp_state, &pmu->node); + if (ret) + return dev_err_probe(dev, ret, "Fail to register cpuhp instance\n"); + + ret = devm_add_action_or_reset(dev, hisi_its_pmu_remove_cpuhp_instance, + &pmu->node); + if (ret) + return ret; + + hisi_pmu_init(pmu, THIS_MODULE); + + name = devm_kasprintf(dev, GFP_KERNEL, "hisi_scl%d_its%d", + pmu->topo.scl_id, pmu->topo.index_id); + if (!name) + return -ENOMEM; + + ret = perf_pmu_register(&pmu->pmu, name, -1); + if (ret) + return dev_err_probe(dev, ret, "Fail to register PMU\n"); + + return devm_add_action_or_reset(dev, hisi_its_pmu_unregister_pmu, + &pmu->pmu); +} + +static struct hisi_its_pmu_regs hisi_its_v1_pmu_regs = { + .version = ITS_PMU_VERSION, + .pmu_ctrl = ITS_PMU_GLOBAL_CTRL, + .event_ctrl0 = ITS_PMU_EVENT_CTRL, + .event_cntr0 = ITS_PMU_COUNTER0, + .cntr_ctrl = ITS_PMU_COUNTER_CTRL, +}; + +static const struct hisi_pmu_dev_info hisi_its_v1 = { + .attr_groups = hisi_its_pmu_attr_groups, + .counter_bits = 48, + .check_event = ITS_PMU_EVENT_CTRL_TYPE, + .private = &hisi_its_v1_pmu_regs, +}; + +static const struct acpi_device_id hisi_its_pmu_ids[] = { + { "HISI0591", (kernel_ulong_t)&hisi_its_v1 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_its_pmu_ids); + +static struct platform_driver hisi_its_pmu_driver = { + .driver = { + .name = "hisi_its_pmu", + .acpi_match_table = hisi_its_pmu_ids, + .suppress_bind_attrs = true, + }, + .probe = hisi_its_pmu_probe, +}; + +module_platform_driver(hisi_its_pmu_driver); + +MODULE_IMPORT_NS("HISI_PMU"); +MODULE_DESCRIPTION("HiSilicon SoC Uncore ITS PMU driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yushan Wang "); From 8a76246afe045057a8cffacbf7ce6ebeac912706 Mon Sep 17 00:00:00 2001 From: Yifan Wu Date: Wed, 16 Sep 2026 14:53:48 +0800 Subject: [PATCH 1277/2286] drivers/perf: hisi: Add cycle event for new MN PMU The HiSilicon new MN (Miscellaneous Node) PMU exposes a cycle event in addition to the existing event set. Add a v2 event list that includes the cycles event (0x0F) and a new hisi_mn_v2 dev_info matched by the new ACPI id HISI0224. Rename the existing event list, attribute group and attr_groups array to *_v1 so the two MN PMU versions can coexist. Signed-off-by: Yifan Wu Signed-off-by: Yushan Wang Signed-off-by: Will Deacon --- drivers/perf/hisilicon/hisi_uncore_mn_pmu.c | 63 ++++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c index ad898153ff49b8..1ded8a6b235802 100644 --- a/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c +++ b/drivers/perf/hisilicon/hisi_uncore_mn_pmu.c @@ -188,7 +188,7 @@ static const struct attribute_group hisi_mn_pmu_format_group = { .attrs = hisi_mn_pmu_format_attr, }; -static struct attribute *hisi_mn_pmu_events_attr[] = { +static struct attribute *hisi_mn_pmu_events_attr_v1[] = { HISI_PMU_EVENT_ATTR(req_eobarrier_num, 0x00), HISI_PMU_EVENT_ATTR(req_ecbarrier_num, 0x01), HISI_PMU_EVENT_ATTR(req_dvmop_num, 0x02), @@ -215,14 +215,55 @@ static struct attribute *hisi_mn_pmu_events_attr[] = { NULL }; -static const struct attribute_group hisi_mn_pmu_events_group = { +static const struct attribute_group hisi_mn_pmu_events_group_v1 = { .name = "events", - .attrs = hisi_mn_pmu_events_attr, + .attrs = hisi_mn_pmu_events_attr_v1, }; -static const struct attribute_group *hisi_mn_pmu_attr_groups[] = { +static const struct attribute_group *hisi_mn_pmu_attr_groups_v1[] = { &hisi_mn_pmu_format_group, - &hisi_mn_pmu_events_group, + &hisi_mn_pmu_events_group_v1, + &hisi_pmu_cpumask_attr_group, + &hisi_pmu_identifier_group, + NULL +}; + +static struct attribute *hisi_mn_pmu_events_attr_v2[] = { + HISI_PMU_EVENT_ATTR(req_eobarrier_num, 0x00), + HISI_PMU_EVENT_ATTR(req_ecbarrier_num, 0x01), + HISI_PMU_EVENT_ATTR(req_dvmop_num, 0x02), + HISI_PMU_EVENT_ATTR(req_dvmsync_num, 0x03), + HISI_PMU_EVENT_ATTR(req_retry_num, 0x04), + HISI_PMU_EVENT_ATTR(req_writenosnp_num, 0x05), + HISI_PMU_EVENT_ATTR(req_readnosnp_num, 0x06), + HISI_PMU_EVENT_ATTR(snp_dvm_num, 0x07), + HISI_PMU_EVENT_ATTR(snp_dvmsync_num, 0x08), + HISI_PMU_EVENT_ATTR(l3t_req_dvm_num, 0x09), + HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_num, 0x0A), + HISI_PMU_EVENT_ATTR(mn_req_dvm_num, 0x0B), + HISI_PMU_EVENT_ATTR(mn_req_dvmsync_num, 0x0C), + HISI_PMU_EVENT_ATTR(pa_req_dvm_num, 0x0D), + HISI_PMU_EVENT_ATTR(pa_req_dvmsync_num, 0x0E), + HISI_PMU_EVENT_ATTR(cycles, 0x0F), + HISI_PMU_EVENT_ATTR(snp_dvm_latency, 0x80), + HISI_PMU_EVENT_ATTR(snp_dvmsync_latency, 0x81), + HISI_PMU_EVENT_ATTR(l3t_req_dvm_latency, 0x82), + HISI_PMU_EVENT_ATTR(l3t_req_dvmsync_latency, 0x83), + HISI_PMU_EVENT_ATTR(mn_req_dvm_latency, 0x84), + HISI_PMU_EVENT_ATTR(mn_req_dvmsync_latency, 0x85), + HISI_PMU_EVENT_ATTR(pa_req_dvm_latency, 0x86), + HISI_PMU_EVENT_ATTR(pa_req_dvmsync_latency, 0x87), + NULL +}; + +static const struct attribute_group hisi_mn_pmu_events_group_v2 = { + .name = "events", + .attrs = hisi_mn_pmu_events_attr_v2, +}; + +static const struct attribute_group *hisi_mn_pmu_attr_groups_v2[] = { + &hisi_mn_pmu_format_group, + &hisi_mn_pmu_events_group_v2, &hisi_pmu_cpumask_attr_group, &hisi_pmu_identifier_group, NULL @@ -347,14 +388,22 @@ static struct hisi_mn_pmu_regs hisi_mn_v1_pmu_regs = { }; static const struct hisi_pmu_dev_info hisi_mn_v1 = { - .attr_groups = hisi_mn_pmu_attr_groups, + .attr_groups = hisi_mn_pmu_attr_groups_v1, + .counter_bits = 48, + .check_event = HISI_MN_EVTYPE_MASK, + .private = &hisi_mn_v1_pmu_regs, +}; + +static const struct hisi_pmu_dev_info hisi_mn_v2 = { + .attr_groups = hisi_mn_pmu_attr_groups_v2, .counter_bits = 48, .check_event = HISI_MN_EVTYPE_MASK, .private = &hisi_mn_v1_pmu_regs, }; static const struct acpi_device_id hisi_mn_pmu_acpi_match[] = { - { "HISI0222", (kernel_ulong_t) &hisi_mn_v1 }, + { "HISI0222", (kernel_ulong_t)&hisi_mn_v1 }, + { "HISI0224", (kernel_ulong_t)&hisi_mn_v2 }, { } }; MODULE_DEVICE_TABLE(acpi, hisi_mn_pmu_acpi_match); From b22dcc11429866e07dc54299255e8ca13c26d38a Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:01 -0700 Subject: [PATCH 1278/2286] perf/cxl: Program the requested event group on configurable counters cxl_pmu_get_event_idx() sets *counter_idx for a configurable counter but never *event_idx. hwc->event_base stays 0, so cxl_pmu_event_start() programs event group 0 no matter which group the user asked for. Return the matched capability's event_idx. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Jonathan Cameron Signed-off-by: Dave Jiang Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 2c8a03cc11e4d4..2a5635e4e39f86 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -588,7 +588,10 @@ static const struct attribute_group *cxl_pmu_attr_groups[] = { NULL }; -/* If counter_idx == NULL, don't try to allocate a counter. */ +/* + * If counter_idx == NULL, don't try to allocate a counter. Callers that + * allocate pass both counter_idx and event_idx. + */ static int cxl_pmu_get_event_idx(struct perf_event *event, int *counter_idx, int *event_idx) { @@ -616,7 +619,7 @@ static int cxl_pmu_get_event_idx(struct perf_event *event, int *counter_idx, pmu_ev = cxl_pmu_find_config_counter_ev_cap(info, vid, gid, mask); if (!IS_ERR(pmu_ev)) { - if (!counter_idx) + if (!counter_idx || !event_idx) return 0; bitmap_andnot(configurable_and_free, info->conf_counter_bm, @@ -627,6 +630,7 @@ static int cxl_pmu_get_event_idx(struct perf_event *event, int *counter_idx, return -EINVAL; *counter_idx = i; + *event_idx = pmu_ev->event_idx; return 0; } From 7406d6cf76b1dad53084f3988dc65ab967209344 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:02 -0700 Subject: [PATCH 1279/2286] perf/cxl: Clear stale event fields before reprogramming a counter cxl_pmu_event_start() ORs the event group id, event mask, edge and invert selections into a configurable counter's config register without clearing them first, and cxl_pmu_event_stop() leaves them set. Reuse the counter for another event and the new selection lands on top of the old one, so the counter counts the wrong events with stale edge and invert behaviour. Use FIELD_MODIFY() so each field is replaced rather than accumulated. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Link: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 2a5635e4e39f86..8232dc566bd5fb 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -759,17 +759,17 @@ static void cxl_pmu_event_start(struct perf_event *event, int flags) cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_INT_ON_OVRFLW, 1); cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_FREEZE_ON_OVRFLW, 1); cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_ENABLE, 1); - cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EDGE, - cxl_pmu_config1_get_edge(event) ? 1 : 0); - cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_INVERT, - cxl_pmu_config1_get_invert(event) ? 1 : 0); + FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EDGE, &cfg, + cxl_pmu_config1_get_edge(event) ? 1 : 0); + FIELD_MODIFY(CXL_PMU_COUNTER_CFG_INVERT, &cfg, + cxl_pmu_config1_get_invert(event) ? 1 : 0); /* Fixed purpose counters have next two fields RO */ if (test_bit(hwc->idx, info->conf_counter_bm)) { - cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK, - hwc->event_base); - cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_EVENTS_MSK, - cxl_pmu_config_get_mask(event)); + FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EVENT_GRP_ID_IDX_MSK, &cfg, + hwc->event_base); + FIELD_MODIFY(CXL_PMU_COUNTER_CFG_EVENTS_MSK, &cfg, + cxl_pmu_config_get_mask(event)); } cfg &= ~CXL_PMU_COUNTER_CFG_THRESHOLD_MSK; /* From 0326269ed73529cdf821664b6176814cde9ca374 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:03 -0700 Subject: [PATCH 1280/2286] perf/cxl: Fix the counter overflow delta fixup The counter is masked to counter_width, so the subtraction in __cxl_pmu_read() throws away the bit that records the wrap. A delta of one whole period reads back as 0, the same as no events at all, and only the overflow status tells the two apart - which is why __cxl_pmu_read() takes an overflow argument. The fixup keys off the delta rather than the operands: delta = (new_cnt - prev_cnt) & GENMASK_ULL(counter_width - 1, 0); if (overflow && delta < GENMASK_ULL(counter_width - 1, 0)) delta += (1UL << counter_width); so it cannot tell which of these it is looking at: event_start polled read wrap ctr = 0 .......... ctr = P/2 ......... mask -> 0 (+r) prev = 0 prev = P/2 IRQ reads new = r prev = 0 (no read yet): new >= prev, fell short -> add period prev = P/2 (polled): new < prev, spans wrap -> add nothing Both rows are the same interrupt and the old guard adds a period in both, so a mid-period read makes the event over-count. 'perf stat -I' hits that. Condition the fixup on new_cnt >= prev_cnt, the one case the subtraction cannot express. Dropping it outright would break the first row. It stays exact whatever the residual r is, which matters because some events increment by more than 1 per cycle (CXL r4.0 8.2.7.2.1, Threshold) and can step past 0 as they wrap. Use mask + 1 for the period rather than a shift. It is 0 for a 64-bit counter, and avoids the old 1UL << counter_width - undefined for width 64, and for >= 32 on 32-bit kernels. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 8232dc566bd5fb..4b1827d74bdeeb 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -802,7 +802,7 @@ static void __cxl_pmu_read(struct perf_event *event, bool overflow) { struct cxl_pmu_info *info = pmu_to_cxl_pmu_info(event->pmu); struct hw_perf_event *hwc = &event->hw; - u64 new_cnt, prev_cnt, delta; + u64 new_cnt, prev_cnt, delta, mask; do { prev_cnt = local64_read(&hwc->prev_count); @@ -810,12 +810,16 @@ static void __cxl_pmu_read(struct perf_event *event, bool overflow) } while (local64_cmpxchg(&hwc->prev_count, prev_cnt, new_cnt) != prev_cnt); /* - * If we know an overflow occur then take that into account. - * Note counter is not reset as that would lose events + * The mask discards the bit that says the counter wrapped, so a delta of + * one whole period reads back as 0 - the same as no events at all. Only + * the overflow status separates them, and new_cnt >= prev_cnt is that + * case, so add the period back. mask + 1 is 2^counter_width, which comes + * out as 0 for a 64-bit counter and avoids an undefined 1 << 64. */ - delta = (new_cnt - prev_cnt) & GENMASK_ULL(info->counter_width - 1, 0); - if (overflow && delta < GENMASK_ULL(info->counter_width - 1, 0)) - delta += (1UL << info->counter_width); + mask = GENMASK_ULL(info->counter_width - 1, 0); + delta = (new_cnt - prev_cnt) & mask; + if (overflow && new_cnt >= prev_cnt) + delta += mask + 1; local64_add(delta, &event->count); } From af06fba45746058ba8f983a0ac2f457a3404edba Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:04 -0700 Subject: [PATCH 1281/2286] perf/cxl: Accept an overflow interrupt on MSI message number 0 cxl_pmu_probe() rejects the PMU when info->irq <= 0, but at that point the field still holds the MSI/MSI-X message number the device signals overflow on, not a Linux virq. That number is 0-based, and -1 means no interrupt support. Message number 0 is valid and pci_irq_vector() takes a 0-based index, so a compliant device signalling on the first vector fails to probe. Reject only the no-interrupt case, matching how the CXL mailbox and event interrupts handle it. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Jonathan Cameron Reviewed-by: Davidlohr Bueso Acked-by: Richard Cheng Signed-off-by: Dave Jiang Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 4b1827d74bdeeb..e9628bc88c78a2 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -986,7 +986,7 @@ static int cxl_pmu_probe(struct device *dev) .capabilities = PERF_PMU_CAP_NO_EXCLUDE, }; - if (info->irq <= 0) + if (info->irq < 0) return -EINVAL; rc = pci_irq_vector(pdev, info->irq); From 97fe5d94b7cebff1199df936287f2be9eee749df Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:05 -0700 Subject: [PATCH 1282/2286] perf/cxl: Split the MSI vector out of info->irq info->irq means two different things depending on how far probe has got. cxl_pmu_parse_caps() sets it to the MSI/MSI-X message number from the CPMU Capability register. cxl_pmu_probe() passes that to pci_irq_vector() and then overwrites it with the Linux virq, which is what the hotplug callbacks hand to irq_set_affinity(). Both are ints called irq, so nothing catches a mix-up, and the valid ranges differ. 0 is a fine message number but never a valid virq. Give the message number its own field. No functional change. Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Robin Murphy Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index e9628bc88c78a2..1b1bdb485d7cd5 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -111,6 +111,7 @@ struct cxl_pmu_info { struct hlist_node node; bool filter_hdm; bool filter_crb; + int msi_vec; int irq; }; @@ -150,9 +151,9 @@ static int cxl_pmu_parse_caps(struct device *dev, struct cxl_pmu_info *info) info->filter_crb = FIELD_GET(CXL_PMU_CAP_FILTERS_SUP_MSK, val) & CXL_PMU_FILTER_CHAN_RANK_BANK; if (FIELD_GET(CXL_PMU_CAP_INT, val)) - info->irq = FIELD_GET(CXL_PMU_CAP_MSI_N_MSK, val); + info->msi_vec = FIELD_GET(CXL_PMU_CAP_MSI_N_MSK, val); else - info->irq = -1; + info->msi_vec = -1; /* First handle fixed function counters; note if configurable counters found */ for (i = 0; i < info->num_counters; i++) { @@ -986,10 +987,10 @@ static int cxl_pmu_probe(struct device *dev) .capabilities = PERF_PMU_CAP_NO_EXCLUDE, }; - if (info->irq < 0) + if (info->msi_vec < 0) return -EINVAL; - rc = pci_irq_vector(pdev, info->irq); + rc = pci_irq_vector(pdev, info->msi_vec); if (rc < 0) return rc; irq = rc; From 28b2b667f48f42a2adb491192b2a95fca07943e3 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:06 -0700 Subject: [PATCH 1283/2286] cxl/pci: Add the PMUs after configuring events cxl_pci_probe() adds the CPMU instances before configuring the event interrupts. Both take an MSI/MSI-X vector the device names in its own capability registers, and the spec lets a device point several capabilities at one message number, so the two can collide. They do not fail the same way. A failed devm_cxl_pmu_add() only breaks out of the loop, while a failed cxl_event_config() aborts probe and the memory device never appears. Going PMUs-first therefore lets a peripheral facility take the vector and deny it to one that matters. Configure events first, so a collision costs the PMU rather than the memdev. Return 0 explicitly afterwards, since rc still holds any PMU error that cxl_event_config() used to overwrite on the way past. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Reviewed-by: Alison Schofield Signed-off-by: Will Deacon --- drivers/cxl/pci.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index c7c91e8dc51dc9..b6dcfcf6eb5b7c 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -892,6 +892,10 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (rc) dev_dbg(&pdev->dev, "No CXL FWCTL setup\n"); + rc = cxl_event_config(host_bridge, mds, irq_avail); + if (rc) + return rc; + pmu_count = cxl_count_regblock(pdev, CXL_REGLOC_RBI_PMU); if (pmu_count < 0) return pmu_count; @@ -918,13 +922,10 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) } } - rc = cxl_event_config(host_bridge, mds, irq_avail); - if (rc) - return rc; - pci_save_state(pdev); - return rc; + /* A missing PMU is not fatal, the memdev is still usable */ + return 0; } static const struct pci_device_id cxl_mem_pci_tbl[] = { From b899fd3487b0d4066d60ebcf5b73e77c9617de54 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:07 -0700 Subject: [PATCH 1284/2286] perf/cxl: Don't share the overflow interrupt, and keep it pinned The PMU pins its overflow interrupt to info->on_cpu in the hotplug callbacks, but requests it with only IRQF_SHARED | IRQF_NO_THREAD. Without IRQF_NOBALANCING, irqbalance or a userspace smp_affinity write can move it elsewhere, and cxl_pmu_irq() then runs local64_cmpxchg() and local64_add() on hwc->prev_count and event->count at the same time as the managing CPU. local64_t is only atomic against same-CPU access, so the counts corrupt. The flag alone does not fix that while the line is shared. __setup_irq() only acts on it for the first action on a line, and a co-owner has no reason to want our affinity - it would keep taking the interrupt wherever its own points, running our handler on the wrong CPU. Drop IRQF_SHARED and add IRQF_NOBALANCING. The spec only recommends a distinct Interrupt Message Number per CPMU instance (CXL r4.0 8.2.7.1.1), so a device may put several on one vector, or share with the mailbox or event log. Such a device now loses a PMU rather than silently miscounting - and only the PMU, since the preceding patch adds the CPMUs after the event configuration. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Jonathan Cameron Reviewed-by: Davidlohr Bueso Signed-off-by: Dave Jiang Link: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 1b1bdb485d7cd5..f6c4a1b598d8c3 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -896,7 +896,7 @@ static irqreturn_t cxl_pmu_irq(int irq, void *data) overflowed = readq(base + CXL_PMU_OVERFLOW_REG); - /* Interrupt may be shared, so maybe it isn't ours */ + /* Nothing overflowed, so the device did not raise this */ if (!overflowed) return IRQ_NONE; @@ -999,7 +999,14 @@ static int cxl_pmu_probe(struct device *dev) if (!irq_name) return -ENOMEM; - rc = devm_request_irq(dev, irq, cxl_pmu_irq, IRQF_SHARED | IRQF_NO_THREAD, + /* + * The handler must run on info->on_cpu, so the interrupt cannot be + * shared - IRQF_NOBALANCING is only honoured for the first action on a + * line, and a co-owner would keep taking the interrupt wherever its own + * affinity points. + */ + rc = devm_request_irq(dev, irq, cxl_pmu_irq, + IRQF_NO_THREAD | IRQF_NOBALANCING, irq_name, info); if (rc) return rc; From 2e4fe234c72d65a09bbfd421240a0cfd7c8046a2 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:08 -0700 Subject: [PATCH 1285/2286] perf/cxl: Unfreeze counters after handling an overflow interrupt The counters run with Freeze on Overflow set, so one overflow freezes every counter in the block (CXL r4.0 8.2.7.2.1), and a frozen counter "remains frozen until explicitly unfrozen by software" (8.2.7.1.3, Table 8-183). cxl_pmu_irq() reads the overflowed counters and clears the overflow status but never unfreezes, so everything stays frozen until the next pmu_enable() and events in that window are lost. Unfreeze after clearing the status, unless the PMU has been disabled in the meantime - cxl_pmu_disable() freezes the block deliberately. Track the enabled state and leave it frozen in that case. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Jonathan Cameron Signed-off-by: Dave Jiang Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index f6c4a1b598d8c3..5e46da55281a95 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -113,6 +113,8 @@ struct cxl_pmu_info { bool filter_crb; int msi_vec; int irq; + /* Set between pmu_enable() and pmu_disable(), read by the IRQ handler */ + bool enabled; }; #define pmu_to_cxl_pmu_info(_pmu) container_of(_pmu, struct cxl_pmu_info, pmu) @@ -701,6 +703,7 @@ static void cxl_pmu_enable(struct pmu *pmu) void __iomem *base = info->base; /* Can assume frozen at this stage */ + WRITE_ONCE(info->enabled, true); writeq(0, base + CXL_PMU_FREEZE_REG); } @@ -709,6 +712,7 @@ static void cxl_pmu_disable(struct pmu *pmu) struct cxl_pmu_info *info = pmu_to_cxl_pmu_info(pmu); void __iomem *base = info->base; + WRITE_ONCE(info->enabled, false); /* * Whilst bits above number of counters are RsvdZ * they are unlikely to be repurposed given @@ -915,6 +919,21 @@ static irqreturn_t cxl_pmu_irq(int irq, void *data) writeq(overflowed, base + CXL_PMU_OVERFLOW_REG); + /* + * An overflow freezes every counter in the CPMU, so unfreeze once the + * overflowed ones have been read and their status cleared. Otherwise + * they stay frozen until the next pmu_enable() and events are lost. + * + * Not while the PMU is disabled, so as not to undo an intentional freeze. + * The check is advisory, not exclusive: pmu_disable() normally runs on + * info->on_cpu with interrupts off, where the pinned handler cannot + * preempt it. In the one window where it does not - the migration in + * cxl_pmu_offline_cpu() - the counters are legitimately running again, + * so unfreezing is correct there anyway. + */ + if (READ_ONCE(info->enabled)) + writeq(0, base + CXL_PMU_FREEZE_REG); + return IRQ_HANDLED; } From 1c5499e67303a9393388197fb14b39ed06a3dc55 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:09 -0700 Subject: [PATCH 1286/2286] perf/cxl: Validate the hardware-reported counter width cxl_pmu_parse_caps() takes the Counter Width straight from the CPMU Capability register without a bounds check. The Counter Data register is 64 bits wide, so a device reporting 0 or more than 64 is reporting nonsense, and GENMASK_ULL(width - 1, 0) in the read path then shifts out of range. That is undefined behaviour, and a UBSAN splat where it is enabled. Reject a counter width outside 1..64 at probe. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Reviewed-by: Jonathan Cameron Signed-off-by: Dave Jiang Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 5e46da55281a95..b09006f2bd9ff9 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -147,6 +147,15 @@ static int cxl_pmu_parse_caps(struct device *dev, struct cxl_pmu_info *info) info->num_counters = FIELD_GET(CXL_PMU_CAP_NUM_COUNTERS_MSK, val) + 1; info->counter_width = FIELD_GET(CXL_PMU_CAP_COUNTER_WIDTH_MSK, val); + /* + * The Counter Data register is 64 bits wide, so a Counter Width of 0 or + * >64 is invalid. Reject it rather than let GENMASK_ULL(width - 1, 0) in + * the read path shift out of range. + */ + if (info->counter_width == 0 || info->counter_width > 64) { + dev_err(dev, "Invalid counter width %d\n", info->counter_width); + return -ENODEV; + } info->num_event_capabilities = FIELD_GET(CXL_PMU_CAP_NUM_EVN_CAP_REG_SUP_MSK, val) + 1; info->filter_hdm = FIELD_GET(CXL_PMU_CAP_FILTERS_SUP_MSK, val) & CXL_PMU_FILTER_HDM; From 984920c893f0e6832351bc03deeb5ef9a42fbdbb Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:10 -0700 Subject: [PATCH 1287/2286] perf/cxl: Don't log through pmu.dev in the overflow interrupt handler perf_pmu_unregister() frees pmu->dev without clearing the pointer, and cxl_pmu_probe() orders its devm actions so that teardown runs it before free_irq(). Nothing in between masks the interrupt, and cxl_pmu_event_stop() leaves the overflow status bit set, so an overflow taken just before teardown is still delivered and still reaches the dev_dbg() in the !event branch - by which point info->pmu.dev is freed. Log through info->pmu.parent instead, which is devm-managed and outlives every teardown action. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260715191454.459673-1-dave@stgolabs.net?part=1 Assisted-by: Claude:claude-opus-4-8 Acked-by: Jonathan Cameron Signed-off-by: Dave Jiang Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index b09006f2bd9ff9..6cc8217bfd7a85 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -918,7 +918,7 @@ static irqreturn_t cxl_pmu_irq(int irq, void *data) struct perf_event *event = info->hw_events[i]; if (!event) { - dev_dbg(info->pmu.dev, + dev_dbg(info->pmu.parent, "overflow but on non enabled counter %d\n", i); continue; } From 05d82cf377f706d0b566b572bc66e3a2057bd68e Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Wed, 5 Aug 2026 08:59:11 -0700 Subject: [PATCH 1288/2286] perf/cxl: Clear stale overflow status before using a counter cxl_pmu_event_stop() leaves the counter's bit set in the overflow status register and only cxl_pmu_irq() ever clears it, so a counter handed from one event to the next starts with the previous owner's overflow pending. The perf core stops and starts back to back with interrupts off, so an interrupt latched over the handover lands on the new event. prev_count is 0 by then, so the read path adds a full period: 2^counter_width phantom counts. Clear the bit in cxl_pmu_event_start(), before the config write arms Interrupt on Overflow. Clear the whole register at probe too, for counters no event ever owns - the driver never touched it, so firmware or a previous kernel can leave a bit set there. Fixes: 5d7107c72796 ("perf: CXL Performance Monitoring Unit driver") Reported-by: Richard Cheng Closes: https://lore.kernel.org/linux-cxl/anAVpO--i5heVfz9@MWDK4CY14F/ Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Signed-off-by: Will Deacon --- drivers/perf/cxl_pmu.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/perf/cxl_pmu.c b/drivers/perf/cxl_pmu.c index 6cc8217bfd7a85..7e4113717e011e 100644 --- a/drivers/perf/cxl_pmu.c +++ b/drivers/perf/cxl_pmu.c @@ -796,6 +796,15 @@ static void cxl_pmu_event_start(struct perf_event *event, int flags) */ cfg |= FIELD_PREP(CXL_PMU_COUNTER_CFG_THRESHOLD_MSK, cxl_pmu_config1_get_threshold(event)); + + /* + * Drop any overflow the previous owner of this counter left pending; + * cxl_pmu_event_stop() does not. Otherwise an interrupt latched over + * the handover gets charged to this event. Do it before arming Interrupt + * on Overflow below, and note RW1C leaves the other counters alone. + */ + writeq(BIT_ULL(hwc->idx), base + CXL_PMU_OVERFLOW_REG); + writeq(cfg, base + CXL_PMU_COUNTER_CFG_REG(hwc->idx)); local64_set(&hwc->prev_count, 0); @@ -1027,6 +1036,14 @@ static int cxl_pmu_probe(struct device *dev) if (!irq_name) return -ENOMEM; + /* + * Same for counters no event owns yet: clear whatever firmware or a + * previous kernel left set before the handler goes live. Bits above the + * implemented counters are reserved, so only write those. + */ + writeq(GENMASK_ULL(info->num_counters - 1, 0), + info->base + CXL_PMU_OVERFLOW_REG); + /* * The handler must run on info->on_cpu, so the interrupt cannot be * shared - IRQF_NOBALANCING is only honoured for the first action on a From ce512db53e0d66d65cdc986405369c54ba64b272 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 1 Sep 2026 17:36:15 +0100 Subject: [PATCH 1289/2286] perf/core: Export perf_event_itrace_started() Since Arm SPE and CoreSight drivers can be built as modules, export perf_event_itrace_started() so that it can be used by these modules. Signed-off-by: Leo Yan Acked-by: Peter Zijlstra (Intel) Reviewed-by: James Clark Signed-off-by: Will Deacon --- kernel/events/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/events/core.c b/kernel/events/core.c index db7b76d6b68aa5..994f06e5b712a3 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -10563,6 +10563,7 @@ void perf_event_itrace_started(struct perf_event *event) { WRITE_ONCE(event->attach_state, event->attach_state | PERF_ATTACH_ITRACE); } +EXPORT_SYMBOL_GPL(perf_event_itrace_started); static void perf_log_itrace_start(struct perf_event *event) { From 87f78017e8c86b5b5e526818a85f92902217931e Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 1 Sep 2026 17:36:16 +0100 Subject: [PATCH 1290/2286] perf: arm_spe: Suppress redundant ITRACE start records PERF_RECORD_ITRACE_START establishes the initial thread context at the beginning of a trace. Afterwards, decoders track the thread context using context packets or recorded sched_switch events. Call perf_event_itrace_started() when event starts to prevent perf core from emitting redundant PERF_RECORD_ITRACE_START records on later schedule-ins. Signed-off-by: Leo Yan Reviewed-by: James Clark Signed-off-by: Will Deacon --- drivers/perf/arm_spe_pmu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c index b70f3fedf0282b..40314ef8b731e1 100644 --- a/drivers/perf/arm_spe_pmu.c +++ b/drivers/perf/arm_spe_pmu.c @@ -890,6 +890,8 @@ static void arm_spe_pmu_start(struct perf_event *event, int flags) return; } + perf_event_itrace_started(event); + reg = arm_spe_event_to_pmsfcr(event); write_sysreg_s(reg, SYS_PMSFCR_EL1); From 8f8301eaa400d38c9e6ff7884cad777be64969b4 Mon Sep 17 00:00:00 2001 From: Leo Yan Date: Tue, 1 Sep 2026 17:36:17 +0100 Subject: [PATCH 1291/2286] coresight: perf: Suppress ITRACE start records PERF_RECORD_ITRACE_START establishes the initial thread context at the beginning of a trace. Afterwards, decoders track the thread context using context packets. Call perf_event_itrace_started() after successfully starting the event to prevent perf core from emitting redundant PERF_RECORD_ITRACE_START records when the event is scheduled in again. When per-thread mode traces multiple child threads, tooling should enable context ID tracing instead to identify each thread. Signed-off-by: Leo Yan Reviewed-by: James Clark Signed-off-by: Will Deacon --- drivers/hwtracing/coresight/coresight-etm-perf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 09b21a711a8764..80502e8b084074 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -613,6 +613,8 @@ static void etm_event_start(struct perf_event *event, int flags) perf_report_aux_output_id(event, hw_id); } + perf_event_itrace_started(event); + out: /* Tell the perf core the event is alive */ event->hw.state = 0; From 3a4067658363e34bf4e1d7039e5b52951fcb4012 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 20 Aug 2026 21:56:11 +0800 Subject: [PATCH 1292/2286] perf/dwc_pcie: Fix PCI device reference leak in probe pci_get_domain_bus_and_slot() returns a referenced PCI device. When the subsequent RAS DES capability lookup fails, dwc_pcie_pmu_probe() returns -ENODEV without releasing that reference. Repeated failed probes can therefore keep the PCI device allocated after removal. Declare the looked-up device with __free(pci_dev_put), so the temporary reference is released on every return path. Keeping the reference scoped to the whole probe also covers all later uses of the device during initialization. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 7f35b429802a ("perf/dwc_pcie: fix duplicate pci_dev devices") Suggested-by: Markus Elfring Signed-off-by: Ruoyu Wang Signed-off-by: Will Deacon --- drivers/perf/dwc_pcie_pmu.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/perf/dwc_pcie_pmu.c b/drivers/perf/dwc_pcie_pmu.c index 6b9ed59de26eb6..eb86e7468c49b5 100644 --- a/drivers/perf/dwc_pcie_pmu.c +++ b/drivers/perf/dwc_pcie_pmu.c @@ -747,16 +747,16 @@ static struct notifier_block dwc_pcie_pmu_nb = { static int dwc_pcie_pmu_probe(struct platform_device *plat_dev) { - struct pci_dev *pdev; + u32 sbdf = plat_dev->id; + struct pci_dev *pdev __free(pci_dev_put) = + pci_get_domain_bus_and_slot(sbdf >> 16, + PCI_BUS_NUM(sbdf & 0xffff), + sbdf & 0xff); struct dwc_pcie_pmu *pcie_pmu; char *name; - u32 sbdf; u16 vsec; int ret; - sbdf = plat_dev->id; - pdev = pci_get_domain_bus_and_slot(sbdf >> 16, PCI_BUS_NUM(sbdf & 0xffff), - sbdf & 0xff); if (!pdev) { pr_err("No pdev found for the sbdf 0x%x\n", sbdf); return -ENODEV; @@ -766,7 +766,6 @@ static int dwc_pcie_pmu_probe(struct platform_device *plat_dev) if (!vsec) return -ENODEV; - pci_dev_put(pdev); name = devm_kasprintf(&plat_dev->dev, GFP_KERNEL, "dwc_rootport_%x", sbdf); if (!name) return -ENOMEM; From 7e17084177aa6c76231c6ee8cdbbcd24f40c86cc Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 25 Sep 2026 15:04:30 +0200 Subject: [PATCH 1293/2286] perf: marvell: avoid large stack variables The probe function in this driver constructs multiple large structures on the stack, which ends up causing a size warning: drivers/perf/marvell_cn10k_ddr_pmu.c:1168:12: error: stack frame size (1296) exceeds limit (1280) in 'cn10k_ddr_perf_probe' [-Werror,-Wframe-larger-than] 1168 | static int cn10k_ddr_perf_probe(struct platform_device *pdev) | ^ Avoid this warning and simplify the code by turning these into static const structures and directly assigning them without the extra copy. Fixes: 9b551a2446b2 ("perf: marvell: Add CN20K DDR PMU support") Signed-off-by: Arnd Bergmann Signed-off-by: Will Deacon --- drivers/perf/marvell_cn10k_ddr_pmu.c | 85 +++++++++++++++------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/drivers/perf/marvell_cn10k_ddr_pmu.c b/drivers/perf/marvell_cn10k_ddr_pmu.c index 0a8cb61664084e..9b68d6c1c31cdc 100644 --- a/drivers/perf/marvell_cn10k_ddr_pmu.c +++ b/drivers/perf/marvell_cn10k_ddr_pmu.c @@ -1165,6 +1165,47 @@ static const struct ddr_pmu_platform_data odyssey_ddr_pmu_pdata = { }; #endif +static const struct pmu cn10k_pmu = { + .module = THIS_MODULE, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + .task_ctx_nr = perf_invalid_context, + .attr_groups = cn10k_attr_groups, + .event_init = cn10k_ddr_perf_event_init, + .add = cn10k_ddr_perf_event_add, + .del = cn10k_ddr_perf_event_del, + .start = cn10k_ddr_perf_event_start, + .stop = cn10k_ddr_perf_event_stop, + .read = cn10k_ddr_perf_event_update, + .pmu_enable = cn10k_ddr_perf_pmu_enable, + .pmu_disable = cn10k_ddr_perf_pmu_disable, +}; + +static const struct pmu ody_pmu = { + .module = THIS_MODULE, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + .task_ctx_nr = perf_invalid_context, + .attr_groups = odyssey_attr_groups, + .event_init = cn10k_ddr_perf_event_init, + .add = cn10k_ddr_perf_event_add, + .del = cn10k_ddr_perf_event_del, + .start = cn10k_ddr_perf_event_start, + .stop = cn10k_ddr_perf_event_stop, + .read = cn10k_ddr_perf_event_update, +}; + +static const struct pmu cn20k_pmu = { + .module = THIS_MODULE, + .capabilities = PERF_PMU_CAP_NO_EXCLUDE, + .task_ctx_nr = perf_invalid_context, + .attr_groups = cn20k_attr_groups, + .event_init = cn10k_ddr_perf_event_init, + .add = cn10k_ddr_perf_event_add, + .del = cn10k_ddr_perf_event_del, + .start = cn10k_ddr_perf_event_start, + .stop = cn10k_ddr_perf_event_stop, + .read = cn10k_ddr_perf_event_update, +}; + static int cn10k_ddr_perf_probe(struct platform_device *pdev) { const struct ddr_pmu_platform_data *dev_data; @@ -1202,56 +1243,20 @@ static int cn10k_ddr_perf_probe(struct platform_device *pdev) /* Setup the PMU counter to work in manual mode */ writeq_relaxed(OP_MODE_CTRL_VAL_MANUAL, ddr_pmu->base + ddr_pmu->p_data->cnt_op_mode_ctrl); + ddr_pmu->pmu = cn10k_pmu; - ddr_pmu->pmu = (struct pmu) { - .module = THIS_MODULE, - .capabilities = PERF_PMU_CAP_NO_EXCLUDE, - .task_ctx_nr = perf_invalid_context, - .attr_groups = cn10k_attr_groups, - .event_init = cn10k_ddr_perf_event_init, - .add = cn10k_ddr_perf_event_add, - .del = cn10k_ddr_perf_event_del, - .start = cn10k_ddr_perf_event_start, - .stop = cn10k_ddr_perf_event_stop, - .read = cn10k_ddr_perf_event_update, - .pmu_enable = cn10k_ddr_perf_pmu_enable, - .pmu_disable = cn10k_ddr_perf_pmu_disable, - }; } if (silicon_flags & IS_ODY) { ddr_pmu->ops = &ddr_pmu_ody_ops; - - ddr_pmu->pmu = (struct pmu) { - .module = THIS_MODULE, - .capabilities = PERF_PMU_CAP_NO_EXCLUDE, - .task_ctx_nr = perf_invalid_context, - .attr_groups = odyssey_attr_groups, - .event_init = cn10k_ddr_perf_event_init, - .add = cn10k_ddr_perf_event_add, - .del = cn10k_ddr_perf_event_del, - .start = cn10k_ddr_perf_event_start, - .stop = cn10k_ddr_perf_event_stop, - .read = cn10k_ddr_perf_event_update, - }; + ddr_pmu->pmu = ody_pmu; } if (silicon_flags & IS_CN20K) { ddr_pmu->ops = &ddr_pmu_ody_ops; - - ddr_pmu->pmu = (struct pmu) { - .module = THIS_MODULE, - .capabilities = PERF_PMU_CAP_NO_EXCLUDE, - .task_ctx_nr = perf_invalid_context, - .attr_groups = cn20k_attr_groups, - .event_init = cn10k_ddr_perf_event_init, - .add = cn10k_ddr_perf_event_add, - .del = cn10k_ddr_perf_event_del, - .start = cn10k_ddr_perf_event_start, - .stop = cn10k_ddr_perf_event_stop, - .read = cn10k_ddr_perf_event_update, - }; + ddr_pmu->pmu = cn20k_pmu; } + /* Choose this cpu to collect perf data */ ddr_pmu->cpu = raw_smp_processor_id(); From 1ab73b0f96a5e0ef641f41476139027aad9de2b6 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:42 +0200 Subject: [PATCH 1294/2286] fsverity: report validation errors through fserror to fsnotify Reported verification errors to fsnotify through recently added fserror interface. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn Link: https://patch.msgid.link/20261002223705.2175542-2-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/verity/verify.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/verity/verify.c b/fs/verity/verify.c index 4004a1d428751f..db8c350234bb71 100644 --- a/fs/verity/verify.c +++ b/fs/verity/verify.c @@ -9,6 +9,7 @@ #include #include +#include #define FS_VERITY_MAX_PENDING_BLOCKS 2 @@ -205,6 +206,8 @@ static bool verify_data_block(struct fsverity_info *vi, if (memchr_inv(dblock->data, 0, params->block_size)) { fsverity_err(inode, "FILE CORRUPTED! Data past EOF is not zeroed"); + fserror_report_data_lost(inode, data_pos, + params->block_size, GFP_NOFS); return false; } return true; @@ -312,6 +315,7 @@ static bool verify_data_block(struct fsverity_info *vi, data_pos, level - 1, params->hash_alg->name, hsize, want_hash, params->hash_alg->name, hsize, level == 0 ? dblock->real_hash : real_hash); + fserror_report_data_lost(inode, data_pos, params->block_size, GFP_NOFS); error: for (; level > 0; level--) { kunmap_local(hblocks[level - 1].addr); From 7a59a56fe4f3eda430c5aeda3c4bb4913da2395c Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:43 +0200 Subject: [PATCH 1295/2286] fsverity: expose ensure_fsverity_info() This function will be used by XFS's scrub to force fsverity activation, therefore, to read fsverity context. Reviewed-by: Darrick J. Wong Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20261002223705.2175542-3-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/verity/open.c | 26 ++++++++++++++++++++++++-- include/linux/fsverity.h | 2 ++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/fs/verity/open.c b/fs/verity/open.c index d0c56a7faa3bb1..4f06697f5bf4d0 100644 --- a/fs/verity/open.c +++ b/fs/verity/open.c @@ -347,7 +347,28 @@ int fsverity_get_descriptor(struct inode *inode, return 0; } -static int ensure_verity_info(struct inode *inode) +/** + * fsverity_ensure_verity_info() - cache verity info if it's not already cached + * @inode: the inode for which verity info should be cached + * + * Ensure this inode has verity info attached to it, it's assumed the inode + * already has fsverity enabled. Read fsverity descriptor and creates verity + * based on that. + * + * This needs to be called at least once before any of the inode's data + * can be verified (and thus read at all) or the inode's fsverity digest + * retrieved. fsverity_file_open() calls this already, which handles + * normal file accesses. If a filesystem does any internal (i.e. not + * associated with a file descriptor) reads of the file's data or + * fsverity digest, it must call this explicitly before doing so. + * + * In case filesystem supports both fscrypt and fsverity, this should be called + * after fscrypt's encryption key is set up. Otherwise, the fsverity metadata is + * still encrypted. See fscrypt_file_open(). + * + * Return: 0 on success, -errno on failure + */ +int fsverity_ensure_verity_info(struct inode *inode) { struct fsverity_info *vi = fsverity_get_info(inode), *found; struct fsverity_descriptor *desc; @@ -383,12 +404,13 @@ static int ensure_verity_info(struct inode *inode) kfree(desc); return err; } +EXPORT_SYMBOL_GPL(fsverity_ensure_verity_info); int __fsverity_file_open(struct inode *inode, struct file *filp) { if (filp->f_mode & FMODE_WRITE) return -EPERM; - return ensure_verity_info(inode); + return fsverity_ensure_verity_info(inode); } EXPORT_SYMBOL_GPL(__fsverity_file_open); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 6c467ded9751fc..3c3250f6f2725d 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -317,6 +317,8 @@ static inline int fsverity_file_open(struct inode *inode, struct file *filp) return 0; } +int fsverity_ensure_verity_info(struct inode *inode); + void fsverity_cleanup_inode(struct inode *inode); struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index); From ad7be74b38d83a31cd76c9cdc1bcc4cddbf03bd6 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:44 +0200 Subject: [PATCH 1296/2286] fsverity: pass digest size and hash of the all-zeroes block to ->write Let filesystem iterate over hashes in the block and check if these are hashes of zeroed data blocks. XFS will use this to decide if it want to store tree block full of these hashes. Signed-off-by: Andrey Albershteyn Reviewed-by: "Darrick J. Wong" Acked-by: Eric Biggers Acked-by: David Sterba Link: https://patch.msgid.link/20261002223705.2175542-4-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/btrfs/verity.c | 6 +++++- fs/ext4/verity.c | 4 +++- fs/f2fs/verity.c | 4 +++- fs/verity/enable.c | 4 +++- include/linux/fsverity.h | 6 +++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 4e0ab584227419..c572acacb4d41e 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -779,11 +779,15 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, * @buf: Merkle tree block to write * @pos: the position of the block in the Merkle tree (in bytes) * @size: the Merkle tree block size (in bytes) + * @zero_digest: the hash of the all-zeroes block + * @digest_size: size of zero_digest, in bytes * * Returns 0 on success or negative error code on failure */ static int btrfs_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { struct inode *inode = file_inode(file); loff_t merkle_pos = merkle_file_pos(inode); diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c index ca61da53f313fa..347945ac23a4d5 100644 --- a/fs/ext4/verity.c +++ b/fs/ext4/verity.c @@ -374,7 +374,9 @@ static void ext4_readahead_merkle_tree(struct inode *inode, pgoff_t index, } static int ext4_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { pos += ext4_verity_metadata_pos(file_inode(file)); diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c index 39f4825154452a..cc4158fc841dff 100644 --- a/fs/f2fs/verity.c +++ b/fs/f2fs/verity.c @@ -272,7 +272,9 @@ static void f2fs_readahead_merkle_tree(struct inode *inode, pgoff_t index, } static int f2fs_write_merkle_tree_block(struct file *file, const void *buf, - u64 pos, unsigned int size) + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size) { pos += f2fs_verity_metadata_pos(file_inode(file)); diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 42dfed1ce0ce66..ad4ff71d7dd95b 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -50,7 +50,9 @@ static int write_merkle_tree_block(struct file *file, const u8 *buf, int err; err = inode->i_sb->s_vop->write_merkle_tree_block(file, buf, pos, - params->block_size); + params->block_size, + params->zero_digest, + params->digest_size); if (err) fsverity_err(inode, "Error %d writing Merkle tree block %lu", err, index); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 3c3250f6f2725d..9e7d946676b966 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -124,6 +124,8 @@ struct fsverity_operations { * @buf: the Merkle tree block to write * @pos: the position of the block in the Merkle tree (in bytes) * @size: the Merkle tree block size (in bytes) + * @zero_digest: the hash of the all-zeroes block + * @digest_size: size of zero_digest, in bytes * * This is only called between ->begin_enable_verity() and * ->end_enable_verity(). @@ -131,7 +133,9 @@ struct fsverity_operations { * Return: 0 on success, -errno on failure */ int (*write_merkle_tree_block)(struct file *file, const void *buf, - u64 pos, unsigned int size); + u64 pos, unsigned int size, + const u8 *zero_digest, + unsigned int digest_size); }; #ifdef CONFIG_FS_VERITY From 9e74c01463f608d72f2da55d0ffe870f03edd124 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:45 +0200 Subject: [PATCH 1297/2286] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity This is the same function to read from pageache. XFS will also need this, so move this to core fsverity. Note that f2fs and ext4 functions diverged a bit, as ext4 operated over folios and f2fs operated over pages. The common one will operate over folios. Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Acked-by: Eric Biggers Signed-off-by: Andrey Albershteyn Link: https://patch.msgid.link/20261002223705.2175542-5-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/ext4/verity.c | 32 +++----------------------------- fs/f2fs/verity.c | 30 +----------------------------- fs/verity/pagecache.c | 33 +++++++++++++++++++++++++++++++++ include/linux/fsverity.h | 2 ++ 4 files changed, 39 insertions(+), 58 deletions(-) diff --git a/fs/ext4/verity.c b/fs/ext4/verity.c index 347945ac23a4d5..ac5c133f552925 100644 --- a/fs/ext4/verity.c +++ b/fs/ext4/verity.c @@ -34,32 +34,6 @@ static inline loff_t ext4_verity_metadata_pos(const struct inode *inode) return round_up(inode->i_size, 65536); } -/* - * Read some verity metadata from the inode. __vfs_read() can't be used because - * we need to read beyond i_size. - */ -static int pagecache_read(struct inode *inode, void *buf, size_t count, - loff_t pos) -{ - while (count) { - struct folio *folio; - size_t n; - - folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT, - NULL); - if (IS_ERR(folio)) - return PTR_ERR(folio); - - n = memcpy_from_file_folio(buf, folio, pos, count); - folio_put(folio); - - buf += n; - pos += n; - count -= n; - } - return 0; -} - /* * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY. * kernel_write() can't be used because the file descriptor is readonly. @@ -311,8 +285,8 @@ static int ext4_get_verity_descriptor_location(struct inode *inode, goto bad; desc_size_pos -= sizeof(desc_size_disk); - err = pagecache_read(inode, &desc_size_disk, sizeof(desc_size_disk), - desc_size_pos); + err = fsverity_pagecache_read(inode, &desc_size_disk, + sizeof(desc_size_disk), desc_size_pos); if (err) return err; desc_size = le32_to_cpu(desc_size_disk); @@ -352,7 +326,7 @@ static int ext4_get_verity_descriptor(struct inode *inode, void *buf, if (buf_size) { if (desc_size > buf_size) return -ERANGE; - err = pagecache_read(inode, buf, desc_size, desc_pos); + err = fsverity_pagecache_read(inode, buf, desc_size, desc_pos); if (err) return err; } diff --git a/fs/f2fs/verity.c b/fs/f2fs/verity.c index cc4158fc841dff..f3a4617656db9b 100644 --- a/fs/f2fs/verity.c +++ b/fs/f2fs/verity.c @@ -37,34 +37,6 @@ static inline loff_t f2fs_verity_metadata_pos(const struct inode *inode) return round_up(inode->i_size, 65536); } -/* - * Read some verity metadata from the inode. __vfs_read() can't be used because - * we need to read beyond i_size. - */ -static int pagecache_read(struct inode *inode, void *buf, size_t count, - loff_t pos) -{ - while (count) { - size_t n = min_t(size_t, count, - PAGE_SIZE - offset_in_page(pos)); - struct page *page; - - page = read_mapping_page(inode->i_mapping, pos >> PAGE_SHIFT, - NULL); - if (IS_ERR(page)) - return PTR_ERR(page); - - memcpy_from_page(buf, page, offset_in_page(pos), n); - - put_page(page); - - buf += n; - pos += n; - count -= n; - } - return 0; -} - /* * Write some verity metadata to the inode for FS_IOC_ENABLE_VERITY. * kernel_write() can't be used because the file descriptor is readonly. @@ -250,7 +222,7 @@ static int f2fs_get_verity_descriptor(struct inode *inode, void *buf, if (buf_size) { if (size > buf_size) return -ERANGE; - res = pagecache_read(inode, buf, size, pos); + res = fsverity_pagecache_read(inode, buf, size, pos); if (res) return res; } diff --git a/fs/verity/pagecache.c b/fs/verity/pagecache.c index 99f5f53eea985f..85dac384019479 100644 --- a/fs/verity/pagecache.c +++ b/fs/verity/pagecache.c @@ -78,3 +78,36 @@ void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len, vi->tree_params.digest_size); } EXPORT_SYMBOL_GPL(fsverity_fill_zerohash); + +/** + * fsverity_pagecache_read() - read verity metadata via the pagecache + * @inode: copy from this inode's address space + * @buf: buffer to copy to + * @count: number of bytes to copy + * @pos: byte offset in the file to copy from + * + * Read some verity metadata from the inode. __vfs_read() can't be used because + * we need to read beyond i_size. + */ +int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count, + loff_t pos) +{ + while (count) { + struct folio *folio; + size_t n; + + folio = read_mapping_folio(inode->i_mapping, pos >> PAGE_SHIFT, + NULL); + if (IS_ERR(folio)) + return PTR_ERR(folio); + + n = memcpy_from_file_folio(buf, folio, pos, count); + folio_put(folio); + + buf += n; + pos += n; + count -= n; + } + return 0; +} +EXPORT_SYMBOL_GPL(fsverity_pagecache_read); diff --git a/include/linux/fsverity.h b/include/linux/fsverity.h index 9e7d946676b966..f9433332c274bb 100644 --- a/include/linux/fsverity.h +++ b/include/linux/fsverity.h @@ -328,5 +328,7 @@ void fsverity_cleanup_inode(struct inode *inode); struct page *generic_read_merkle_tree_page(struct inode *inode, pgoff_t index); void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index, unsigned long nr_pages); +int fsverity_pagecache_read(struct inode *inode, void *buf, size_t count, + loff_t pos); #endif /* _LINUX_FSVERITY_H */ From 85cdd23fcae7fb71f4832b2a6f89db0ed121db28 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:46 +0200 Subject: [PATCH 1298/2286] fsverity: don't allow setting DAX file attribute on fsverity files When fsverity is enabled on the file, with FS_IOC_ENABLE_VERITY ioctl(), it checks if file has DAX enabled and fails if that's true. However, the opposite case is not checked. Note, that the only other filesystem supporting DAX and fsverity is ext4, and ext4 does check for this case. Signed-off-by: Andrey Albershteyn Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Reviewed-by: Eric Biggers Link: https://patch.msgid.link/20261002223705.2175542-6-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/file_attr.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/file_attr.c b/fs/file_attr.c index bfb00d256dd560..391edd1d0ce391 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -235,10 +235,15 @@ static int fileattr_set_prepare(struct inode *inode, /* * It is only valid to set the DAX flag on regular files and * directories on filesystems. + * + * DAX and fsverity are incompatible. */ - if ((fa->fsx_xflags & FS_XFLAG_DAX) && - !(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) - return -EINVAL; + if (fa->fsx_xflags & FS_XFLAG_DAX) { + if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))) + return -EINVAL; + if (old_ma->fsx_xflags & FS_XFLAG_VERITY) + return -EINVAL; + } /* Extent size hints of zero turn off the flags. */ if (fa->fsx_extsize == 0) From f9e450420da2624c93ce9585deebfabb87932774 Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Sat, 3 Oct 2026 00:36:47 +0200 Subject: [PATCH 1299/2286] fsverity: hoist statx reporting of fs-verity flag All filesystems, supporting fsverity, report this status by checking inode flag. Also, BTRFS was missing stat->attributes_mask, which is fixed now. Fixes: 146054090b08 ("btrfs: initial fsverity support") Cc: stable@vger.kernel.org Signed-off-by: Andrey Albershteyn Acked-by: Eric Biggers Reviewed-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Link: https://patch.msgid.link/20261002223705.2175542-7-aalbersh@kernel.org Signed-off-by: Eric Biggers --- fs/btrfs/inode.c | 3 --- fs/ext4/inode.c | 5 +---- fs/f2fs/file.c | 5 +---- fs/stat.c | 6 +++++- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 93ef3cec191e36..c34d0198b9880c 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8236,7 +8236,6 @@ static int btrfs_getattr(struct mnt_idmap *idmap, struct inode *inode = d_inode(path->dentry); u32 blocksize = btrfs_sb(inode->i_sb)->sectorsize; u32 bi_flags = BTRFS_I(inode)->flags; - u32 bi_ro_flags = BTRFS_I(inode)->ro_flags; stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = BTRFS_I(inode)->i_otime_sec; @@ -8249,8 +8248,6 @@ static int btrfs_getattr(struct mnt_idmap *idmap, stat->attributes |= STATX_ATTR_IMMUTABLE; if (bi_flags & BTRFS_INODE_NODUMP) stat->attributes |= STATX_ATTR_NODUMP; - if (bi_ro_flags & BTRFS_INODE_RO_VERITY) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_APPEND | STATX_ATTR_COMPRESSED | diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 26f0f9714f03cf..206c6245c6192b 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -6318,15 +6318,12 @@ int ext4_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_IMMUTABLE; if (flags & EXT4_NODUMP_FL) stat->attributes |= STATX_ATTR_NODUMP; - if (flags & EXT4_VERITY_FL) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_APPEND | STATX_ATTR_COMPRESSED | STATX_ATTR_ENCRYPTED | STATX_ATTR_IMMUTABLE | - STATX_ATTR_NODUMP | - STATX_ATTR_VERITY); + STATX_ATTR_NODUMP); generic_fillattr(idmap, request_mask, inode, stat); return 0; diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index edc352569e87e3..dc1cc0965532de 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1075,15 +1075,12 @@ int f2fs_getattr(struct mnt_idmap *idmap, const struct path *path, stat->attributes |= STATX_ATTR_IMMUTABLE; if (flags & F2FS_NODUMP_FL) stat->attributes |= STATX_ATTR_NODUMP; - if (IS_VERITY(inode)) - stat->attributes |= STATX_ATTR_VERITY; stat->attributes_mask |= (STATX_ATTR_COMPRESSED | STATX_ATTR_APPEND | STATX_ATTR_ENCRYPTED | STATX_ATTR_IMMUTABLE | - STATX_ATTR_NODUMP | - STATX_ATTR_VERITY); + STATX_ATTR_NODUMP); generic_fillattr(idmap, request_mask, inode, stat); diff --git a/fs/stat.c b/fs/stat.c index c461c305423408..e03954c36b33e1 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -203,8 +203,12 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat, if (IS_DAX(inode)) stat->attributes |= STATX_ATTR_DAX; + if (IS_VERITY(inode)) + stat->attributes |= STATX_ATTR_VERITY; + stat->attributes_mask |= (STATX_ATTR_AUTOMOUNT | - STATX_ATTR_DAX); + STATX_ATTR_DAX | + STATX_ATTR_VERITY); idmap = mnt_idmap(path->mnt); if (inode->i_op->getattr) { From 290fa0470d6d6c95bf4b22daba2cf37b05a88227 Mon Sep 17 00:00:00 2001 From: Stian Halseth Date: Sun, 4 Oct 2026 21:28:12 +0200 Subject: [PATCH 1300/2286] lib/crypto: sparc/aes-xts: Add optimization using the AES opcodes Implement aes_xts_encrypt_arch() and aes_xts_decrypt_arch() with the AES opcodes, for AES-128 and AES-256, two blocks per iteration. AES-192, which IEEE 1619 does not specify, and data that is not 8-byte aligned are left to the generic code. The tweak is kept as little-endian words, so that multiplying it by x is a 128-bit shift done with addcc and the VIS3 addxc. A little-endian store and an ordinary load through a stack slot give it in the byte order of the data, and the slot is cleared on return. The routines open a register window, as %g4-%g6 belong to the kernel. With that, xts-aes-lib no longer has to be kept off SPARC. Register it there again, with the priority x86 and RISC-V use, so that it also outranks an xts(ecb-aes-sparc64) instance (priority 300). On a T7-1 (M7), MiB/s unless noted: xts(ecb-aes-sparc64) xts-aes-lib AF_ALG, 64 KiB, AES-128 415 1092 AF_ALG, 64 KiB, AES-256 390 936 dm-crypt on brd, AES-256: sequential read 1399 2559 sequential write 1472 3480 4 KiB write latency, QD1 (us) 40.6 32.7 dm-crypt now matches aes-cbc-essiv on the same ramdisk. On a T4-1, AF_ALG AES-256 goes from 238 to 544 MiB/s, and AES-128 from 252 to 616. Tested with CONFIG_CRYPTO_SELFTESTS_FULL, and against OpenSSL over random keys, tweaks and lengths, on both machines. Link: https://github.com/sparclinux/issues/issues/106 Signed-off-by: Stian Halseth Link: https://patch.msgid.link/20261004192812.4145406-1-stian@itx.no Signed-off-by: Eric Biggers --- arch/sparc/include/asm/opcodes.h | 6 ++ crypto/aes.c | 9 +- lib/crypto/sparc/aes.h | 75 ++++++++++++++ lib/crypto/sparc/aes_asm.S | 165 +++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+), 7 deletions(-) diff --git a/arch/sparc/include/asm/opcodes.h b/arch/sparc/include/asm/opcodes.h index ebfda6eb49b26e..f1cb1a26be8fca 100644 --- a/arch/sparc/include/asm/opcodes.h +++ b/arch/sparc/include/asm/opcodes.h @@ -96,5 +96,11 @@ .word 0xbbb02303; #define MOVXTOD_G7_F62 \ .word 0xbfb02307; +#define MOVXTOD_L4_F56 \ + .word 0xb3b02314; +#define MOVXTOD_L5_F58 \ + .word 0xb7b02315; +#define ADDXC_L1_L1_L1 \ + .word 0xa3b44231; #endif /* _SPARC_ASM_OPCODES_H */ diff --git a/crypto/aes.c b/crypto/aes.c index 73f3aa856be4ea..9508a975d8eafe 100644 --- a/crypto/aes.c +++ b/crypto/aes.c @@ -702,17 +702,12 @@ static struct skcipher_alg skcipher_algs[] = { .decrypt = crypto_aes_xctr_crypt, }, #endif - /* - * Don't register library-based "xts(aes)" on architectures where it - * might block a "better" implementation from being instantiated via the - * "xts" template. This exclusion is temporary and will go away when - * the library AES-XTS is optimized for SPARC. - */ -#if IS_ENABLED(CONFIG_CRYPTO_XTS) && !IS_ENABLED(CONFIG_SPARC) +#if IS_ENABLED(CONFIG_CRYPTO_XTS) { .base.cra_name = "xts(aes)", .base.cra_driver_name = "xts-aes-lib", .base.cra_priority = (IS_ENABLED(CONFIG_RISCV) || + IS_ENABLED(CONFIG_SPARC) || IS_ENABLED(CONFIG_X86)) ? 500 : 110, .base.cra_blocksize = AES_BLOCK_SIZE, .base.cra_ctxsize = sizeof(struct aes_xts_key), diff --git a/lib/crypto/sparc/aes.h b/lib/crypto/sparc/aes.h index e354aa507ee070..e1a060cf49539d 100644 --- a/lib/crypto/sparc/aes.h +++ b/lib/crypto/sparc/aes.h @@ -133,6 +133,81 @@ static void aes_decrypt_arch(const struct aes_key *key, } } +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) +void aes_sparc64_xts_encrypt_128(const u64 *key, const u64 *input, u64 *output, + size_t len, u64 tweak[2]); +void aes_sparc64_xts_encrypt_256(const u64 *key, const u64 *input, u64 *output, + size_t len, u64 tweak[2]); +void aes_sparc64_xts_decrypt_128(const u64 *key_end, const u64 *input, + u64 *output, size_t len, u64 tweak[2]); +void aes_sparc64_xts_decrypt_256(const u64 *key_end, const u64 *input, + u64 *output, size_t len, u64 tweak[2]); + +/* len is always a positive multiple of AES_BLOCK_SIZE here. */ +static __always_inline bool +aes_xts_crypt_sparc64(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont, bool enc) +{ + const struct aes_key *k = &key->main_key; + const u64 *rk = k->k.sparc_rndkeys; + const u64 *rk_end = rk + 2 * (k->nrounds + 1); + const u64 *in = (const u64 *)src; + u64 *out = (u64 *)dst; + u64 t[2]; + + /* The assembly code has no AES-192 and needs 8-byte aligned data. */ + if (!static_branch_likely(&have_aes_opcodes) || + k->len == AES_KEYSIZE_192 || + !IS_ALIGNED((uintptr_t)dst | (uintptr_t)src, 8)) + return false; + + if (cont) + memcpy(t, tweak, sizeof(t)); + else + aes_encrypt_arch(&key->tweak_key, (u8 *)t, tweak); + + if (k->len == AES_KEYSIZE_128) { + if (enc) { + aes_sparc64_load_encrypt_keys_128(rk); + aes_sparc64_xts_encrypt_128(rk, in, out, len, t); + } else { + aes_sparc64_load_decrypt_keys_128(rk); + aes_sparc64_xts_decrypt_128(rk_end, in, out, len, t); + } + } else { + if (enc) { + aes_sparc64_load_encrypt_keys_256(rk); + aes_sparc64_xts_encrypt_256(rk, in, out, len, t); + } else { + aes_sparc64_load_decrypt_keys_256(rk); + aes_sparc64_xts_decrypt_256(rk_end, in, out, len, t); + } + } + fprs_write(0); + + memcpy(tweak, t, sizeof(t)); + memzero_explicit(t, sizeof(t)); + return true; +} + +#define aes_xts_encrypt_arch aes_xts_encrypt_arch +static bool aes_xts_encrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_sparc64(dst, src, len, tweak, key, cont, true); +} + +#define aes_xts_decrypt_arch aes_xts_decrypt_arch +static bool aes_xts_decrypt_arch(u8 *dst, const u8 *src, size_t len, + u8 tweak[AES_BLOCK_SIZE], + const struct aes_xts_key *key, bool cont) +{ + return aes_xts_crypt_sparc64(dst, src, len, tweak, key, cont, false); +} +#endif /* CONFIG_CRYPTO_LIB_AES_XTS */ + #define aes_mod_init_arch aes_mod_init_arch static void aes_mod_init_arch(void) { diff --git a/lib/crypto/sparc/aes_asm.S b/lib/crypto/sparc/aes_asm.S index f291174a72a1d0..fd90b129972fbf 100644 --- a/lib/crypto/sparc/aes_asm.S +++ b/lib/crypto/sparc/aes_asm.S @@ -2,6 +2,7 @@ #include #include #include +#include #define ENCRYPT_TWO_ROUNDS(KEY_BASE, I0, I1, T0, T1) \ AES_EROUND01(KEY_BASE + 0, I0, I1, T0) \ @@ -1541,3 +1542,167 @@ ENTRY(aes_sparc64_ctr_crypt_256) retl stx %g7, [%o4 + 0x08] ENDPROC(aes_sparc64_ctr_crypt_256) + +#if IS_ENABLED(CONFIG_CRYPTO_LIB_AES_XTS) + /* XTS. The tweak is kept in %l0/%l1 as little-endian words, so that + * multiplying it by x is a 128-bit shift. XTS_TWEAK_BE stores it to + * a stack slot little-endian and reloads it big-endian, the byte order + * of the data. The slot is cleared before returning. + * + * A register window is needed because %g4-%g6 belong to the kernel. + * %l6 points at the slot and %l7 holds 8: an access with an immediate + * ASI cannot also take an immediate offset. + */ + +#define XTS_DOUBLE \ + srax %l1, 63, %l2; \ + and %l2, 0x87, %l2; \ + addcc %l0, %l0, %l0; \ + ADDXC_L1_L1_L1 \ + xor %l0, %l2, %l0; + +#define XTS_TWEAK_BE(A, B) \ + stxa %l0, [%l6] ASI_PL; \ + stxa %l1, [%l6 + %l7] ASI_PL; \ + ldx [%l6 + 0x00], A; \ + ldx [%l6 + 0x08], B; + + /* %i0=key (encrypt) or &key[key_len] (decrypt), %i1=input, + * %i2=output, %i3=len, %i4=tweak + */ +.macro __aes_xts_crypt enc, keylen + save %sp, -192, %sp +.if \keylen == 256 +.if \enc + mov %i0, %o0 +.else + sub %i0, 0xf0, %o0 +.endif +.endif + mov 8, %l7 + add %sp, 2047 + 176, %l6 + ldxa [%i4] ASI_PL, %l0 + ldxa [%i4 + %l7] ASI_PL, %l1 +.if \enc + ldx [%i0 + 0x00], %g1 + subcc %i3, 0x10, %i3 + be,pn %xcc, 10f + ldx [%i0 + 0x08], %g2 +.else + ldx [%i0 - 0x10], %g1 + subcc %i3, 0x10, %i3 + be,pn %xcc, 10f + ldx [%i0 - 0x08], %g2 +.endif +1: XTS_TWEAK_BE(%g3, %g7) + XTS_DOUBLE + XTS_TWEAK_BE(%l4, %l5) + XTS_DOUBLE + ldx [%i1 + 0x00], %o5 + xor %o5, %g3, %o5 + xor %o5, %g1, %o5 + MOVXTOD_O5_F0 + ldx [%i1 + 0x08], %o5 + xor %o5, %g7, %o5 + xor %o5, %g2, %o5 + MOVXTOD_O5_F2 + ldx [%i1 + 0x10], %o5 + xor %o5, %l4, %o5 + xor %o5, %g1, %o5 + MOVXTOD_O5_F4 + ldx [%i1 + 0x18], %o5 + xor %o5, %l5, %o5 + xor %o5, %g2, %o5 + MOVXTOD_O5_F6 +.if \enc && \keylen == 128 + ENCRYPT_128_2(8, 0, 2, 4, 6, 56, 58, 60, 62) +.elseif \enc + ENCRYPT_256_2(8, 0, 2, 4, 6) +.elseif \keylen == 128 + DECRYPT_128_2(8, 0, 2, 4, 6, 56, 58, 60, 62) +.else + DECRYPT_256_2(8, 0, 2, 4, 6) +.endif + MOVXTOD_G3_F60 + MOVXTOD_G7_F62 + MOVXTOD_L4_F56 + MOVXTOD_L5_F58 + fxor %f0, %f60, %f0 + fxor %f2, %f62, %f2 + fxor %f4, %f56, %f4 + fxor %f6, %f58, %f6 + std %f0, [%i2 + 0x00] + std %f2, [%i2 + 0x08] + std %f4, [%i2 + 0x10] + std %f6, [%i2 + 0x18] + subcc %i3, 0x20, %i3 + add %i1, 0x20, %i1 + brgz %i3, 1b + add %i2, 0x20, %i2 + brlz,pt %i3, 11f + nop +10: +.if \enc && \keylen == 256 + ldd [%o0 + 0xd0], %f56 + ldd [%o0 + 0xd8], %f58 + ldd [%o0 + 0xe0], %f60 + ldd [%o0 + 0xe8], %f62 +.elseif \keylen == 256 + ldd [%o0 + 0x18], %f56 + ldd [%o0 + 0x10], %f58 + ldd [%o0 + 0x08], %f60 + ldd [%o0 + 0x00], %f62 +.endif + XTS_TWEAK_BE(%g3, %g7) + XTS_DOUBLE + ldx [%i1 + 0x00], %o5 + xor %o5, %g3, %o5 + xor %o5, %g1, %o5 + MOVXTOD_O5_F0 + ldx [%i1 + 0x08], %o5 + xor %o5, %g7, %o5 + xor %o5, %g2, %o5 + MOVXTOD_O5_F2 +.if \enc && \keylen == 128 + ENCRYPT_128(8, 0, 2, 4, 6) +.elseif \enc + ENCRYPT_256(8, 0, 2, 4, 6) +.elseif \keylen == 128 + DECRYPT_128(8, 0, 2, 4, 6) +.else + DECRYPT_256(8, 0, 2, 4, 6) +.endif + MOVXTOD_G3_F4 + MOVXTOD_G7_F6 + fxor %f0, %f4, %f0 + fxor %f2, %f6, %f2 + std %f0, [%i2 + 0x00] + std %f2, [%i2 + 0x08] +11: stxa %l0, [%i4] ASI_PL + stxa %l1, [%i4 + %l7] ASI_PL + stx %g0, [%l6 + 0x00] + stx %g0, [%l6 + 0x08] + ret + restore +.endm + + .align 32 +ENTRY(aes_sparc64_xts_encrypt_128) + __aes_xts_crypt 1, 128 +ENDPROC(aes_sparc64_xts_encrypt_128) + + .align 32 +ENTRY(aes_sparc64_xts_encrypt_256) + __aes_xts_crypt 1, 256 +ENDPROC(aes_sparc64_xts_encrypt_256) + + .align 32 +ENTRY(aes_sparc64_xts_decrypt_128) + __aes_xts_crypt 0, 128 +ENDPROC(aes_sparc64_xts_decrypt_128) + + .align 32 +ENTRY(aes_sparc64_xts_decrypt_256) + __aes_xts_crypt 0, 256 +ENDPROC(aes_sparc64_xts_decrypt_256) +#endif /* CONFIG_CRYPTO_LIB_AES_XTS */ From d4b7b0c77142b3bc535192fa65130474a129c487 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Wed, 2 Sep 2026 15:22:41 +0800 Subject: [PATCH 1301/2286] perf: xgene: initialize lock before requesting IRQ xgene_pmu_probe() requests the PMU IRQ before initializing xgene_pmu->lock. request_irq() enables IRQ handling before it returns, so xgene_pmu_isr() can run in this window and acquire the uninitialized raw spinlock. Initialize the lock before requesting the IRQ so the handler always sees a valid lock. Fixes: 832c927d119b ("perf: xgene: Add APM X-Gene SoC Performance Monitoring Unit driver") Cc: stable@vger.kernel.org Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Signed-off-by: Will Deacon --- drivers/perf/xgene_pmu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/perf/xgene_pmu.c b/drivers/perf/xgene_pmu.c index ab3623fab914c4..0aa2b605fd53d4 100644 --- a/drivers/perf/xgene_pmu.c +++ b/drivers/perf/xgene_pmu.c @@ -1873,6 +1873,8 @@ static int xgene_pmu_probe(struct platform_device *pdev) if (irq < 0) return -EINVAL; + raw_spin_lock_init(&xgene_pmu->lock); + rc = devm_request_irq(&pdev->dev, irq, xgene_pmu_isr, IRQF_NOBALANCING | IRQF_NO_THREAD, dev_name(&pdev->dev), xgene_pmu); @@ -1881,8 +1883,6 @@ static int xgene_pmu_probe(struct platform_device *pdev) xgene_pmu->irq = irq; - raw_spin_lock_init(&xgene_pmu->lock); - /* Check for active MCBs and MCUs */ rc = xgene_pmu_probe_active_mcb_mcu_l3c(xgene_pmu, pdev); if (rc) { From 848468c86f716e4552bd478d2f3c3f5d81bec7ac Mon Sep 17 00:00:00 2001 From: "hpp.iscas" Date: Sat, 5 Sep 2026 21:43:17 +0800 Subject: [PATCH 1302/2286] perf/marvell: cn10k_tad: Publish the OF module alias The TAD PMU platform driver has ACPI and OF match tables, but only the ACPI table is published. A Device Tree platform device emits an OF modalias, so the ACPI alias does not select this module for it. Publish the OF table inside CONFIG_OF and retain ACPI support. Fixes: 036a7584bede ("drivers: perf: Add LLC-TAD perf counter support") Signed-off-by: hpp.iscas Signed-off-by: Will Deacon --- drivers/perf/marvell_cn10k_tad_pmu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/perf/marvell_cn10k_tad_pmu.c b/drivers/perf/marvell_cn10k_tad_pmu.c index 54909d0031b772..301be4d92b1bb4 100644 --- a/drivers/perf/marvell_cn10k_tad_pmu.c +++ b/drivers/perf/marvell_cn10k_tad_pmu.c @@ -424,6 +424,7 @@ static const struct of_device_id tad_pmu_of_match[] = { { .compatible = "marvell,cn10k-tad-pmu", .data = &tad_pmu_data }, {}, }; +MODULE_DEVICE_TABLE(of, tad_pmu_of_match); #endif #ifdef CONFIG_ACPI From 652ab8ee0d27f21a5f402a0b6ac227c8bbfdd64c Mon Sep 17 00:00:00 2001 From: Myria Sarvay Date: Sat, 12 Sep 2026 18:03:35 +1000 Subject: [PATCH 1303/2286] drivers/perf: apple_m1: Add macOS 27 M2 Event This patch adds another known PMU event that can be found under /usr/share/kpep on macOS 27 for M2 macs. INST_SIMD_ALU_VEC purports to count "Retired non-load/store vector Advanced SIMD instructions", and as we already noted, can only be registered on counter 7. Though this event only shows up on the M2 kpep records, it can be set on M1 CPUs, and records something. Link: https://github.com/0ax1/apmc/blob/9cff99a9df2ae055f1e3065c64f633e38bb6beec/counters/m2.md Signed-off-by: Myria Sarvay Signed-off-by: Will Deacon --- drivers/perf/apple_m1_cpu_pmu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/perf/apple_m1_cpu_pmu.c b/drivers/perf/apple_m1_cpu_pmu.c index 81b6f1a6234995..1f73c8a7746230 100644 --- a/drivers/perf/apple_m1_cpu_pmu.c +++ b/drivers/perf/apple_m1_cpu_pmu.c @@ -81,7 +81,7 @@ enum m1_pmu_events { M1_PMU_PERFCTR_INST_SIMD_ALU = 0x9a, M1_PMU_PERFCTR_INST_LDST = 0x9b, M1_PMU_PERFCTR_INST_BARRIER = 0x9c, - M1_PMU_PERFCTR_UNKNOWN_9f = 0x9f, + M1_PMU_PERFCTR_INST_SIMD_ALU_VEC = 0x9f, M1_PMU_PERFCTR_L1D_TLB_ACCESS = 0xa0, M1_PMU_PERFCTR_L1D_TLB_MISS = 0xa1, M1_PMU_PERFCTR_L1D_CACHE_MISS_ST = 0xa2, @@ -150,7 +150,7 @@ static const u16 m1_pmu_event_affinity[M1_PMU_PERFCTR_LAST + 1] = { [M1_PMU_PERFCTR_INST_SIMD_ALU] = BIT(7), [M1_PMU_PERFCTR_INST_LDST] = BIT(7), [M1_PMU_PERFCTR_INST_BARRIER] = ONLY_5_6_7, - [M1_PMU_PERFCTR_UNKNOWN_9f] = BIT(7), + [M1_PMU_PERFCTR_INST_SIMD_ALU_VEC] = BIT(7), [M1_PMU_PERFCTR_L1D_CACHE_MISS_LD_NONSPEC] = ONLY_5_6_7, [M1_PMU_PERFCTR_L1D_CACHE_MISS_ST_NONSPEC] = ONLY_5_6_7, [M1_PMU_PERFCTR_L1D_TLB_MISS_NONSPEC] = ONLY_5_6_7, From 02c5d7bfa8724789f6620f7787532666edc891a1 Mon Sep 17 00:00:00 2001 From: Mehmet Koseoglu Date: Thu, 1 Oct 2026 13:19:15 +0300 Subject: [PATCH 1304/2286] rust: cpufreq: reject NULL from cpufreq_cpu_get() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cpufreq_cpu_get() returns either a referenced policy or NULL. PolicyCpu::from_cpu() passed its return value to from_err_ptr(), which rejects ERR_PTR values but accepts NULL. If the lookup fails, Policy::from_raw_mut() therefore constructs a mutable reference from NULL. Dropping the resulting PolicyCpu then passes the invalid pointer to cpufreq_cpu_put(), causing an oops in kobject_put(). Reject NULL with NonNull before constructing the Policy reference. Return ENODEV instead. A KUnit negative-control run reproduced the oops with the original conversion. The same test passed with this change. The reproducer is available on request. Fixes: 6ebdd7c93177 ("rust: cpufreq: Extend abstractions for policy and driver ops") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Mehmet Koseoglu Reviewed-by: Onur Özkan Reviewed-by: spidermana Signed-off-by: Viresh Kumar --- rust/kernel/cpufreq.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/rust/kernel/cpufreq.rs b/rust/kernel/cpufreq.rs index affa2b9490efc9..b75d692846a25e 100644 --- a/rust/kernel/cpufreq.rs +++ b/rust/kernel/cpufreq.rs @@ -14,7 +14,13 @@ use crate::{ cpumask, device::{Bound, Device}, devres, - error::{code::*, from_err_ptr, from_result, to_result, Result, VTABLE_DEFAULT_ERROR}, + error::{ + code::*, + from_result, + to_result, + Result, + VTABLE_DEFAULT_ERROR, // + }, ffi::{c_char, c_ulong}, prelude::*, types::ForeignOwnable, @@ -29,7 +35,10 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut}, pin::Pin, - ptr, + ptr::{ + self, + NonNull, // + }, }; use macros::vtable; @@ -687,12 +696,13 @@ struct PolicyCpu<'a>(&'a mut Policy); impl<'a> PolicyCpu<'a> { fn from_cpu(cpu: CpuId) -> Result { // SAFETY: It is safe to call `cpufreq_cpu_get` for any valid CPU. - let ptr = from_err_ptr(unsafe { bindings::cpufreq_cpu_get(u32::from(cpu)) })?; + let ptr = + NonNull::new(unsafe { bindings::cpufreq_cpu_get(u32::from(cpu)) }).ok_or(ENODEV)?; Ok(Self( - // SAFETY: The `ptr` is guaranteed to be valid and remains valid for the lifetime of - // the returned reference. - unsafe { Policy::from_raw_mut(ptr) }, + // SAFETY: `ptr` is non-NULL and `cpufreq_cpu_get()` took a reference on it, so it is + // valid for writing and remains valid for the lifetime of the returned reference. + unsafe { Policy::from_raw_mut(ptr.as_ptr()) }, )) } } From e5b33fa47792375ca7fc49ccadee87d8e4b72929 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 13:44:38 +0800 Subject: [PATCH 1305/2286] cpufreq: sparc-us2e: fix frequency table index copy-paste error In us2e_freq_cpu_init(), the last two frequency writes both use index [2] instead of [3] and [4] respectively, and the terminator uses [3] instead of [5]. This is a copy-paste error where the index was not incremented, causing the divider-6 and divider-8 entries to overwrite the already-written divider-4 entry. As a result, only three frequency steps (div 1, 2, and 4) are actually available to the cpufreq core, while the intended dividers 6 and 8 are silently lost. The struct us2e_freq_percpu_info::table[6] has room for 5 entries plus a terminator, matching the 5 hardware dividers. Fix the indices so all five frequency steps are correctly populated: table[0]=div1, table[1]=div2, table[2]=div4, table[3]=div6, table[4]=div8, table[5]=TABLE_END. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Xueqin Luo Signed-off-by: Viresh Kumar --- drivers/cpufreq/sparc-us2e-cpufreq.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/cpufreq/sparc-us2e-cpufreq.c b/drivers/cpufreq/sparc-us2e-cpufreq.c index a68706406b8805..5cda391ad03bbf 100644 --- a/drivers/cpufreq/sparc-us2e-cpufreq.c +++ b/drivers/cpufreq/sparc-us2e-cpufreq.c @@ -282,12 +282,11 @@ static int us2e_freq_cpu_init(struct cpufreq_policy *policy) table[1].frequency = clock_tick / 2; table[2].driver_data = 2; table[2].frequency = clock_tick / 4; - table[2].driver_data = 3; - table[2].frequency = clock_tick / 6; - table[2].driver_data = 4; - table[2].frequency = clock_tick / 8; - table[2].driver_data = 5; - table[3].frequency = CPUFREQ_TABLE_END; + table[3].driver_data = 3; + table[3].frequency = clock_tick / 6; + table[4].driver_data = 4; + table[4].frequency = clock_tick / 8; + table[5].frequency = CPUFREQ_TABLE_END; policy->cpuinfo.transition_latency = 0; policy->cur = clock_tick; From f0a750dad7da1c653e85f0698063235453b43831 Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 15:30:08 +0800 Subject: [PATCH 1306/2286] cpufreq: tegra194: fix double-pointer error in get_cpu_ndiv ndiv is already a u64 pointer, passing &ndiv to smp_call_function_single() results in a u64** being written to instead of the caller's u64 variable, so the caller always reads back an uninitialized ndiv. Drop the spurious '&'. Fixes: 0839ed1fd7ac ("cpufreq: tegra194: add soc data to support multiple soc") Signed-off-by: Xueqin Luo Reviewed-by: Sumit Gupta Signed-off-by: Viresh Kumar --- drivers/cpufreq/tegra194-cpufreq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/tegra194-cpufreq.c b/drivers/cpufreq/tegra194-cpufreq.c index c6375e14d445d7..1eb96e9f3310db 100644 --- a/drivers/cpufreq/tegra194-cpufreq.c +++ b/drivers/cpufreq/tegra194-cpufreq.c @@ -367,7 +367,7 @@ static void tegra194_get_cpu_ndiv_sysreg(void *ndiv) static int tegra194_get_cpu_ndiv(u32 cpu, u32 cpuid, u32 clusterid, u64 *ndiv) { - return smp_call_function_single(cpu, tegra194_get_cpu_ndiv_sysreg, &ndiv, true); + return smp_call_function_single(cpu, tegra194_get_cpu_ndiv_sysreg, ndiv, true); } static void tegra194_set_cpu_ndiv_sysreg(void *data) From fdbe4cae71c8196cd7681ad47254b5b0560d3c1e Mon Sep 17 00:00:00 2001 From: Xueqin Luo Date: Mon, 31 Aug 2026 17:11:59 +0800 Subject: [PATCH 1307/2286] cpufreq: sti: avoid NULL dereference in dev_err() ddata.cpu is NULL-checked, then immediately passed to dev_err(). Replace with pr_err() to avoid dereferencing NULL. Fixes: ab0ea257fc58 ("cpufreq: st: Provide runtime initialised driver for ST's platforms") Signed-off-by: Xueqin Luo Signed-off-by: Viresh Kumar --- drivers/cpufreq/sti-cpufreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/sti-cpufreq.c b/drivers/cpufreq/sti-cpufreq.c index b15b3142b5fec3..144ac0f1d85d79 100644 --- a/drivers/cpufreq/sti-cpufreq.c +++ b/drivers/cpufreq/sti-cpufreq.c @@ -263,7 +263,7 @@ static int __init sti_cpufreq_init(void) ddata.cpu = get_cpu_device(0); if (!ddata.cpu) { - dev_err(ddata.cpu, "Failed to get device for CPU0\n"); + pr_err("Failed to get device for CPU0\n"); goto skip_voltage_scaling; } @@ -281,7 +281,7 @@ static int __init sti_cpufreq_init(void) goto register_cpufreq_dt; skip_voltage_scaling: - dev_err(ddata.cpu, "Not doing voltage scaling\n"); + pr_err("Not doing voltage scaling\n"); register_cpufreq_dt: platform_device_register_simple("cpufreq-dt", -1, NULL, 0); From 53732733c7af437b0ed710a1df91a67a0d3dfc07 Mon Sep 17 00:00:00 2001 From: Frank Zhang Date: Fri, 4 Sep 2026 15:56:35 +0800 Subject: [PATCH 1308/2286] rust: rcpufreq_dt: add module alias Add the missing `platform:cpufreq-dt` module alias so that the driver can be automatically loaded when the platform device is registered. Fixes: 06149d8f2216 ("cpufreq: Add Rust-based cpufreq-dt driver") Cc: stable@vger.kernel.org Signed-off-by: Frank Zhang Signed-off-by: Viresh Kumar --- drivers/cpufreq/rcpufreq_dt.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/rcpufreq_dt.rs b/drivers/cpufreq/rcpufreq_dt.rs index d7ead60bf8c244..2b1f18b7a1e777 100644 --- a/drivers/cpufreq/rcpufreq_dt.rs +++ b/drivers/cpufreq/rcpufreq_dt.rs @@ -225,4 +225,5 @@ module_platform_driver! { authors: ["Viresh Kumar "], description: "Generic CPUFreq DT driver", license: "GPL v2", + alias: ["platform:cpufreq-dt"], } From 3b585d7fa3a1256c8bc3b9bb3b1dcfc3b2ff5d91 Mon Sep 17 00:00:00 2001 From: Sumeet Pawnikar Date: Sun, 13 Sep 2026 00:41:53 +0530 Subject: [PATCH 1309/2286] cpufreq: Use %pe to print error pointers symbolically Replace PTR_ERR() and %ld with %pe and pass the original pointer directly to pr_err() and pr_warn(). The %pe format specifier prints a symbolic error name (e.g. -ENOMEM) when CONFIG_SYMBOLIC_ERRNAME is enabled, otherwise it falls back gracefully and prints the raw integer value. This makes messages more readable without any functional change. Signed-off-by: Sumeet Pawnikar Signed-off-by: Viresh Kumar --- drivers/cpufreq/bmips-cpufreq.c | 4 ++-- drivers/cpufreq/cppc_cpufreq.c | 4 ++-- drivers/cpufreq/qoriq-cpufreq.c | 3 +-- drivers/cpufreq/s3c64xx-cpufreq.c | 5 ++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/cpufreq/bmips-cpufreq.c b/drivers/cpufreq/bmips-cpufreq.c index a8e35bc75fb2b3..389fff5e6f6529 100644 --- a/drivers/cpufreq/bmips-cpufreq.c +++ b/drivers/cpufreq/bmips-cpufreq.c @@ -132,8 +132,8 @@ static int bmips_cpufreq_init(struct cpufreq_policy *policy) freq_table = bmips_cpufreq_get_freq_table(policy); if (IS_ERR(freq_table)) { - pr_err("%s: couldn't determine frequency table (%ld).\n", - BMIPS_CPUFREQ_NAME, PTR_ERR(freq_table)); + pr_err("%s: couldn't determine frequency table (%pe).\n", + BMIPS_CPUFREQ_NAME, freq_table); return PTR_ERR(freq_table); } diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 80893844353ca1..f767898ebfb571 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -230,8 +230,8 @@ static void cppc_fie_kworker_init(void) kworker_fie = kthread_run_worker(0, "cppc_fie"); if (IS_ERR(kworker_fie)) { - pr_warn("%s: failed to create kworker_fie: %ld\n", __func__, - PTR_ERR(kworker_fie)); + pr_warn("%s: failed to create kworker_fie: %pe\n", __func__, + kworker_fie); fie_disabled = FIE_DISABLED; kworker_fie = NULL; return; diff --git a/drivers/cpufreq/qoriq-cpufreq.c b/drivers/cpufreq/qoriq-cpufreq.c index 42edb41ad45950..a21b6f01218ac5 100644 --- a/drivers/cpufreq/qoriq-cpufreq.c +++ b/drivers/cpufreq/qoriq-cpufreq.c @@ -57,8 +57,7 @@ static u32 get_bus_freq(void) /* get platform freq by its clock name */ pltclk = clk_get(NULL, "cg-pll0-div1"); if (IS_ERR(pltclk)) { - pr_err("%s: can't get bus frequency %ld\n", - __func__, PTR_ERR(pltclk)); + pr_err("%s: can't get bus frequency %pe\n", __func__, pltclk); return PTR_ERR(pltclk); } diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index 9cef7152807626..9a01592425ee6e 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -152,15 +152,14 @@ static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) policy->clk = clk_get(NULL, "armclk"); if (IS_ERR(policy->clk)) { - pr_err("Unable to obtain ARMCLK: %ld\n", - PTR_ERR(policy->clk)); + pr_err("Unable to obtain ARMCLK: %pe\n", policy->clk); return PTR_ERR(policy->clk); } #ifdef CONFIG_REGULATOR vddarm = regulator_get(NULL, "vddarm"); if (IS_ERR(vddarm)) { - pr_err("Failed to obtain VDDARM: %ld\n", PTR_ERR(vddarm)); + pr_err("Failed to obtain VDDARM: %pe\n", vddarm); pr_err("Only frequency scaling available\n"); vddarm = NULL; } else { From 23f91a51425ed3d4a5c42978fc25f1087611e4ba Mon Sep 17 00:00:00 2001 From: Kefeng Wang Date: Mon, 21 Sep 2026 15:10:30 +0800 Subject: [PATCH 1310/2286] xfs: fix NOFS state corruption in btree split worker xfs_btree_split_worker() calls xfs_trans_set_context() and xfs_trans_clear_context() on the caller's transaction, overwriting tp->t_pflags with the worker's NOFS state. When the caller already has PF_MEMALLOC_NOFS set (e.g. xfs_end_ioend_write, xfs_dio_write_end_io), the corrupted tp->t_pflags causes xfs_trans_free() to erroneously clear the caller's NOFS protection. Use memalloc_nofs_save/restore with a local variable instead so tp->t_pflags is never touched. Closes: https://sashiko.dev/#/patchset/20260902131653.1338227-1-wangkefeng.wang@huawei.com Fixes: 756b1c343333 ("xfs: use current->journal_info for detecting transaction recursion") Signed-off-by: Kefeng Wang Reviewed-by: Christoph Hellwig Reviewed-by: Brian Foster Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_btree.c b/fs/xfs/libxfs/xfs_btree.c index 60ef7f08b1d300..7effebe297ea0b 100644 --- a/fs/xfs/libxfs/xfs_btree.c +++ b/fs/xfs/libxfs/xfs_btree.c @@ -3010,6 +3010,7 @@ xfs_btree_split_worker( struct xfs_btree_split_args, work); unsigned long pflags; unsigned long new_pflags = 0; + unsigned int nofs_flags; /* * we are in a transaction context here, but may also be doing work @@ -3021,12 +3022,17 @@ xfs_btree_split_worker( new_pflags |= PF_MEMALLOC | PF_KSWAPD; current_set_flags_nested(&pflags, new_pflags); - xfs_trans_set_context(args->cur->bc_tp); + + /* + * Don't use xfs_trans_set_context() here: it would overwrite the + * caller's saved NOFS state in tp->t_pflags. Use a local scope. + */ + nofs_flags = memalloc_nofs_save(); args->result = __xfs_btree_split(args->cur, args->level, args->ptrp, args->key, args->curp, args->stat); - xfs_trans_clear_context(args->cur->bc_tp); + memalloc_nofs_restore(nofs_flags); current_restore_flags_nested(&pflags, new_pflags); /* From da483b8a9c2c3e7b23655699680feb57a194000c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:57:37 -0700 Subject: [PATCH 1311/2286] xfs: fix missing xfs_qm_adjust_dqlimits call in quotacheck repair LOLLM noticed that everywhere else in the kernel, a call to xfs_qm_adjust_dqlimits precedes every call to xfs_qm_adjust_dqtimers. In particular, mount-time quotacheck does this, but online quotacheck does not. Looking at xfs_qm_adjust_dqlimits, that function is in charge of conveying default limits to a dquot if that dquot's limits have been zeroed. That's quite possible in a repair, so we actually need to do that. However, there's a pre-existing pattern in the kernel -- for non-root dquots, first we call xfs_qm_adjust_dqlimits to set the dquot's limits to the defaults if they are zero, and then xfs_qm_adjust_dqtimers to start grace periods if the dquot's usage is above the softlimit. The grace period decision cannot be made correctly if we forget to import the default limits. Therefore, let's combine both into a single xfs_qm_adjust_dqenforcement helper that takes care of both pieces, which fixes quotacheck and makes it hard to repeat this mistake. Cc: stable@vger.kernel.org # v6.9 Fixes: 96ed2ae4a9b06b ("xfs: repair dquots based on live quotacheck results") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 5 +---- fs/xfs/scrub/quotacheck_repair.c | 3 +-- fs/xfs/xfs_dquot.c | 20 +++++++++++++++----- fs/xfs/xfs_dquot.h | 2 +- fs/xfs/xfs_qm.c | 6 +----- fs/xfs/xfs_trans_dquot.c | 6 +----- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 59302e8afc7ef0..89f7ea4f92ef4a 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -248,10 +248,7 @@ xrep_quota_item( dq->q_flags |= XFS_DQFLAG_DIRTY; xfs_trans_dqjoin(sc->tp, dq); - if (dq->q_id) { - xfs_qm_adjust_dqlimits(dq); - xfs_qm_adjust_dqtimers(dq); - } + xfs_qm_adjust_dqenforcement(dq); xfs_trans_log_dquot(sc->tp, dq); return xfs_trans_roll(&sc->tp); diff --git a/fs/xfs/scrub/quotacheck_repair.c b/fs/xfs/scrub/quotacheck_repair.c index dbb522e1513b0b..48ee08df302a42 100644 --- a/fs/xfs/scrub/quotacheck_repair.c +++ b/fs/xfs/scrub/quotacheck_repair.c @@ -110,8 +110,7 @@ xqcheck_commit_dquot( /* Commit the dirty dquot to disk. */ dq->q_flags |= XFS_DQFLAG_DIRTY; - if (dq->q_id) - xfs_qm_adjust_dqtimers(dq); + xfs_qm_adjust_dqenforcement(dq); xfs_trans_log_dquot(xqc->sc->tp, dq); return xrep_trans_commit(xqc->sc); diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c index e696ee36c2e8d2..6d22ead562da58 100644 --- a/fs/xfs/xfs_dquot.c +++ b/fs/xfs/xfs_dquot.c @@ -115,18 +115,15 @@ xfs_qm_dqdestroy( * We overwrite the dquot limits only if they are zero and this * is not the root dquot. */ -void +static void xfs_qm_adjust_dqlimits( struct xfs_dquot *dq) { struct xfs_mount *mp = dq->q_mount; struct xfs_quotainfo *q = mp->m_quotainfo; - struct xfs_def_quota *defq; + struct xfs_def_quota *defq = xfs_get_defquota(q, xfs_dquot_type(dq)); int prealloc = 0; - ASSERT(dq->q_id); - defq = xfs_get_defquota(q, xfs_dquot_type(dq)); - if (!dq->q_blk.softlimit) { dq->q_blk.softlimit = defq->blk.soft; prealloc = 1; @@ -223,6 +220,19 @@ xfs_qm_adjust_dqtimers( xfs_qm_adjust_res_timer(dq->q_mount, &dq->q_rtb, &defq->rtb); } +/* Adjust enforcement limits and timers after a change in usage. */ +void +xfs_qm_adjust_dqenforcement( + struct xfs_dquot *dq) +{ + if (dq->q_id == 0) + return; + + xfs_qm_adjust_dqlimits(dq); + xfs_qm_adjust_dqtimers(dq); + dq->q_flags |= XFS_DQFLAG_DIRTY; +} + /* * initialize a buffer full of dquots and log the whole thing */ diff --git a/fs/xfs/xfs_dquot.h b/fs/xfs/xfs_dquot.h index bbb824adca82ce..28c09704acc261 100644 --- a/fs/xfs/xfs_dquot.h +++ b/fs/xfs/xfs_dquot.h @@ -205,7 +205,7 @@ void xfs_qm_dqdestroy(struct xfs_dquot *dqp); int xfs_qm_dqflush(struct xfs_dquot *dqp, struct xfs_buf *bp); void xfs_qm_dqunpin_wait(struct xfs_dquot *dqp); void xfs_qm_adjust_dqtimers(struct xfs_dquot *d); -void xfs_qm_adjust_dqlimits(struct xfs_dquot *d); +void xfs_qm_adjust_dqenforcement(struct xfs_dquot *d); xfs_dqid_t xfs_qm_id_for_quotatype(struct xfs_inode *ip, xfs_dqtype_t type); int xfs_qm_dqget(struct xfs_mount *mp, xfs_dqid_t id, diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 54d00d543b513a..008fed8624be2c 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -1294,11 +1294,7 @@ xfs_qm_quotacheck_dqadjust( * * There are no timers for the default values set in the root dquot. */ - if (dqp->q_id) { - xfs_qm_adjust_dqlimits(dqp); - xfs_qm_adjust_dqtimers(dqp); - } - + xfs_qm_adjust_dqenforcement(dqp); dqp->q_flags |= XFS_DQFLAG_DIRTY; out_unlock: mutex_unlock(&dqp->q_qlock); diff --git a/fs/xfs/xfs_trans_dquot.c b/fs/xfs/xfs_trans_dquot.c index 1606c614f205ae..93ec876792cc76 100644 --- a/fs/xfs/xfs_trans_dquot.c +++ b/fs/xfs/xfs_trans_dquot.c @@ -566,11 +566,7 @@ xfs_trans_apply_dquot_deltas( * Get any default limits in use. * Start/reset the timer(s) if needed. */ - if (dqp->q_id) { - xfs_qm_adjust_dqlimits(dqp); - xfs_qm_adjust_dqtimers(dqp); - } - + xfs_qm_adjust_dqenforcement(dqp); dqp->q_flags |= XFS_DQFLAG_DIRTY; /* * add this to the list of items to get logged From 244425cf63d7e3a0ff5736b41f0e0afe807f7031 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:57:53 -0700 Subject: [PATCH 1312/2286] xfs: online quotacheck must dirty dquot if enforcement adjustments needed LOLLM noticed that we have no way to force xchk_commit_dquot to call xfs_qm_adjust_dqenforcement if nothing else is wrong with the dquot. Therefore, add a new predicate to force the dirty flag if the dquot has zero limits and there are default limits; or if the grace period timer needs adjusting. Cc: stable@vger.kernel.org # v6.9 Fixes: 96ed2ae4a9b06b ("xfs: repair dquots based on live quotacheck results") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quotacheck_repair.c | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/fs/xfs/scrub/quotacheck_repair.c b/fs/xfs/scrub/quotacheck_repair.c index 48ee08df302a42..b8334edf380c7f 100644 --- a/fs/xfs/scrub/quotacheck_repair.c +++ b/fs/xfs/scrub/quotacheck_repair.c @@ -39,6 +39,54 @@ * dquot is locked. */ +static bool +xqcheck_dqres_force_dirty( + const struct xfs_dquot_res *res, + const struct xfs_quota_limits *qlim) +{ + /* zero limits mean that we should set the default limits */ + if (res->softlimit == 0 && qlim->soft != 0) + return true; + if (res->hardlimit == 0 && qlim->hard != 0) + return true; + + /* do we need to adjust the timer setting? */ + if ((res->softlimit && res->count > res->softlimit) || + (res->hardlimit && res->count > res->hardlimit)) { + if (!res->timer) + return true; + } else { + if (res->timer) + return true; + } + + return false; +} + +/* Decide if we need to adjust the dquot limits or timers */ +static bool +xqcheck_dquot_force_dirty( + const struct xfs_dquot *dq) +{ + struct xfs_quotainfo *qi = dq->q_mount->m_quotainfo; + struct xfs_def_quota *defq; + + /* root dquot does not enforce limits */ + if (dq->q_id == 0) + return false; + + defq = xfs_get_defquota(qi, xfs_dquot_type(dq)); + + if (xqcheck_dqres_force_dirty(&dq->q_blk, &defq->blk)) + return true; + if (xqcheck_dqres_force_dirty(&dq->q_ino, &defq->ino)) + return true; + if (xqcheck_dqres_force_dirty(&dq->q_rtb, &defq->rtb)) + return true; + + return false; +} + /* Commit new counters to a dquot. */ static int xqcheck_commit_dquot( @@ -91,6 +139,9 @@ xqcheck_commit_dquot( dirty = true; } + if (!dirty && xqcheck_dquot_force_dirty(dq)) + dirty = true; + xcdq.flags |= (XQCHECK_DQUOT_REPAIR_SCANNED | XQCHECK_DQUOT_WRITTEN); error = xfarray_store(counts, dq->q_id, &xcdq); if (error == -EFBIG) { From 0b9081a774ee7b9dff1e9ac3765b73c1d6bba5b2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:58:08 -0700 Subject: [PATCH 1313/2286] xfs: fix buffer overruns in xfs_ioc_attr_list When we converted the al_offset array in struct xfs_attrlist into a VLA, the size of the object shrank by 4 bytes. Unfortunately, the buffer size validation in the attrlist ioctl wasn't updated to notice this, so the al_offset[0] assignment blindly writes off the end of the buffer. LOLLM noticed the omitted check and complained. Cc: stable@vger.kernel.org # v6.5 Fixes: 371baf5c9750a2 ("xfs: convert flex-array declarations in struct xfs_attrlist*") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_handle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_handle.c b/fs/xfs/xfs_handle.c index 0689cade8f74c2..fd9d4d8258fff2 100644 --- a/fs/xfs/xfs_handle.c +++ b/fs/xfs/xfs_handle.c @@ -409,7 +409,7 @@ xfs_ioc_attr_list( void *buffer; int error; - if (bufsize < sizeof(struct xfs_attrlist) || + if (bufsize < struct_size(alist, al_offset, 1) || bufsize > XFS_XATTR_LIST_MAX) return -EINVAL; From 07c73d1f0be610a8c14a2d73ed5cbda8ea9749d7 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:58:24 -0700 Subject: [PATCH 1314/2286] xfs: clean up after failed metafile relinking Once we start a metadir update to link in a file, we have to commit or cancel it, just like any other operation. LOLLM pointed out that I forgot that, so fix it. This fixes a bug in xfs_repair. However, in commit e80fbe1ad8eff7, we made xfs_metadir_cancel a static function within xfs_metadir.c, so we can't just add a xfs_metadir_cancel call to xfs_dqinode_metadir_link. Instead, create a new xfs_metadir_link_file helper in xfs_metadir.c that takes only the xfs_metadir_update object, and handles everything from start to finish. This enables us to make xfs_metadir_commit a static function too. Fixes: e80fbe1ad8eff7 ("xfs: use metadir for quota inodes") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_dquot_buf.c | 13 +------------ fs/xfs/libxfs/xfs_metadir.c | 30 +++++++++++++++++++++++++++--- fs/xfs/libxfs/xfs_metadir.h | 5 +---- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c index 77954d1d924cc3..6c8a9afdfb1c0f 100644 --- a/fs/xfs/libxfs/xfs_dquot_buf.c +++ b/fs/xfs/libxfs/xfs_dquot_buf.c @@ -454,19 +454,8 @@ xfs_dqinode_metadir_link( .path = xfs_dqinode_path(type), .ip = ip, }; - int error; - error = xfs_metadir_start_link(&upd); - if (error) - return error; - - error = xfs_metadir_link(&upd); - if (error) - return error; - - xfs_trans_log_inode(upd.tp, upd.ip, XFS_ILOG_CORE); - - return xfs_metadir_commit(&upd); + return xfs_metadir_link_file(&upd); } #endif /* __KERNEL__ */ diff --git a/fs/xfs/libxfs/xfs_metadir.c b/fs/xfs/libxfs/xfs_metadir.c index 7c6b086b73db61..1ff26e55b1864f 100644 --- a/fs/xfs/libxfs/xfs_metadir.c +++ b/fs/xfs/libxfs/xfs_metadir.c @@ -317,7 +317,7 @@ xfs_metadir_create( * Begin the process of linking a metadata file by allocating transactions * and locking whatever resources we're going to need. */ -int +static int xfs_metadir_start_link( struct xfs_metadir_update *upd) { @@ -364,7 +364,7 @@ xfs_metadir_start_link( * The path (up to the final component) must already exist, but the final * component must not already exist. */ -int +static int xfs_metadir_link( struct xfs_metadir_update *upd) { @@ -409,7 +409,7 @@ xfs_metadir_link( #endif /* ! __KERNEL__ */ /* Commit a metadir update and unlock/drop all resources. */ -int +static int xfs_metadir_commit( struct xfs_metadir_update *upd) { @@ -499,3 +499,27 @@ xfs_metadir_mkdir( return xfs_metadir_create_file(&upd, S_IFDIR, NULL, NULL, ipp); } + +#ifndef __KERNEL__ +/* Link a metadata file into a metadata directory. */ +int +xfs_metadir_link_file( + struct xfs_metadir_update *upd) +{ + int error; + + error = xfs_metadir_start_link(upd); + if (error) + return error; + + error = xfs_metadir_link(upd); + if (error) { + xfs_metadir_cancel(upd, error); + return error; + } + + xfs_trans_log_inode(upd->tp, upd->ip, XFS_ILOG_CORE); + + return xfs_metadir_commit(upd); +} +#endif /* ! __KERNEL__ */ diff --git a/fs/xfs/libxfs/xfs_metadir.h b/fs/xfs/libxfs/xfs_metadir.h index e434b9d1c93200..b64f9fc5ca7867 100644 --- a/fs/xfs/libxfs/xfs_metadir.h +++ b/fs/xfs/libxfs/xfs_metadir.h @@ -38,10 +38,7 @@ int xfs_metadir_create_file(struct xfs_metadir_update *upd, umode_t mode, xfs_metadir_createfn create, void *priv, struct xfs_inode **ipp); -int xfs_metadir_start_link(struct xfs_metadir_update *upd); -int xfs_metadir_link(struct xfs_metadir_update *upd); - -int xfs_metadir_commit(struct xfs_metadir_update *upd); +int xfs_metadir_link_file(struct xfs_metadir_update *upd); int xfs_metadir_mkdir(struct xfs_inode *dp, const char *path, struct xfs_inode **ipp); From 035d27cb2ae7f9a2f2e2625671086995c6b45433 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:58:39 -0700 Subject: [PATCH 1315/2286] xfs: pass xfs_trans_resv object to reservation calculation helpers xfs_calc_namespace_reservations computes the directory tree related transaction reservations for a given xfs_trans_resv object. The helpers it relies on, however, read the live one from the xfs_mount even if we're doing this for minlogsize calculations. In practice this shouldn't be a big deal since the minlogsize and live reservation objects don't differ in a meaningful way, but LOLLM complained about the inconsistency so let's fix it anyway. Fixes: 7dba4a5fe1c5cd ("xfs: extend transaction reservations for parent attributes") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_trans_resv.c | 38 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c index 3151e97ca8ff6d..1c20a7c27aa1bf 100644 --- a/fs/xfs/libxfs/xfs_trans_resv.c +++ b/fs/xfs/libxfs/xfs_trans_resv.c @@ -606,10 +606,10 @@ static inline unsigned int xfs_calc_pptr_replace_overhead(void) */ STATIC uint xfs_calc_rename_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { unsigned int overhead = XFS_DQUOT_LOGRES; - struct xfs_trans_resv *resp = M_RES(mp); unsigned int t1, t2, t3 = 0; t1 = xfs_calc_inode_res(mp, 5) + @@ -715,10 +715,10 @@ xfs_link_log_count( */ STATIC uint xfs_calc_link_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { unsigned int overhead = XFS_DQUOT_LOGRES; - struct xfs_trans_resv *resp = M_RES(mp); unsigned int t1, t2, t3 = 0; overhead += xfs_calc_iunlink_remove_reservation(mp); @@ -777,10 +777,10 @@ xfs_remove_log_count( */ STATIC uint xfs_calc_remove_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { unsigned int overhead = XFS_DQUOT_LOGRES; - struct xfs_trans_resv *resp = M_RES(mp); unsigned int t1, t2, t3 = 0; overhead += xfs_calc_iunlink_add_reservation(mp); @@ -862,9 +862,9 @@ xfs_icreate_log_count( STATIC uint xfs_calc_icreate_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { - struct xfs_trans_resv *resp = M_RES(mp); unsigned int overhead = XFS_DQUOT_LOGRES; unsigned int t1, t2, t3 = 0; @@ -911,9 +911,10 @@ xfs_mkdir_log_count( */ STATIC uint xfs_calc_mkdir_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { - return xfs_calc_icreate_reservation(mp); + return xfs_calc_icreate_reservation(mp, resp); } static inline unsigned int @@ -940,9 +941,10 @@ xfs_symlink_log_count( */ STATIC uint xfs_calc_symlink_reservation( - struct xfs_mount *mp) + struct xfs_mount *mp, + struct xfs_trans_resv *resp) { - return xfs_calc_icreate_reservation(mp) + + return xfs_calc_icreate_reservation(mp, resp) + xfs_calc_buf_res(1, XFS_SYMLINK_MAXLEN); } @@ -1265,27 +1267,27 @@ xfs_calc_namespace_reservations( { ASSERT(resp->tr_attrsetm.tr_logres > 0); - resp->tr_rename.tr_logres = xfs_calc_rename_reservation(mp); + resp->tr_rename.tr_logres = xfs_calc_rename_reservation(mp, resp); resp->tr_rename.tr_logcount = xfs_rename_log_count(mp, resp); resp->tr_rename.tr_logflags |= XFS_TRANS_PERM_LOG_RES; - resp->tr_link.tr_logres = xfs_calc_link_reservation(mp); + resp->tr_link.tr_logres = xfs_calc_link_reservation(mp, resp); resp->tr_link.tr_logcount = xfs_link_log_count(mp, resp); resp->tr_link.tr_logflags |= XFS_TRANS_PERM_LOG_RES; - resp->tr_remove.tr_logres = xfs_calc_remove_reservation(mp); + resp->tr_remove.tr_logres = xfs_calc_remove_reservation(mp, resp); resp->tr_remove.tr_logcount = xfs_remove_log_count(mp, resp); resp->tr_remove.tr_logflags |= XFS_TRANS_PERM_LOG_RES; - resp->tr_symlink.tr_logres = xfs_calc_symlink_reservation(mp); + resp->tr_symlink.tr_logres = xfs_calc_symlink_reservation(mp, resp); resp->tr_symlink.tr_logcount = xfs_symlink_log_count(mp, resp); resp->tr_symlink.tr_logflags |= XFS_TRANS_PERM_LOG_RES; - resp->tr_create.tr_logres = xfs_calc_icreate_reservation(mp); + resp->tr_create.tr_logres = xfs_calc_icreate_reservation(mp, resp); resp->tr_create.tr_logcount = xfs_icreate_log_count(mp, resp); resp->tr_create.tr_logflags |= XFS_TRANS_PERM_LOG_RES; - resp->tr_mkdir.tr_logres = xfs_calc_mkdir_reservation(mp); + resp->tr_mkdir.tr_logres = xfs_calc_mkdir_reservation(mp, resp); resp->tr_mkdir.tr_logcount = xfs_mkdir_log_count(mp, resp); resp->tr_mkdir.tr_logflags |= XFS_TRANS_PERM_LOG_RES; } From a800b926346c3aa2482421f5c0272280ee7d32e8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:58:55 -0700 Subject: [PATCH 1316/2286] xfs: fix xfs_rename_space_res for non-pptr filesystems We don't need to reserve space for a parent pointer update for an existing target if parent pointers are disabled. Fix this regression (which LOLLM noticed) so that rename reservations go back to what they were before parent pointers. Cc: stable@vger.kernel.org # v6.10 Fixes: 5a8338c88284df ("xfs: Add parent pointers to rename") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_trans_space.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_trans_space.c b/fs/xfs/libxfs/xfs_trans_space.c index c4cd547033e584..7edd0d86f0bdb2 100644 --- a/fs/xfs/libxfs/xfs_trans_space.c +++ b/fs/xfs/libxfs/xfs_trans_space.c @@ -127,10 +127,10 @@ xfs_rename_space_res( if (has_whiteout) ret += xfs_parent_calc_space_res(mp, src_namelen); ret += 2 * xfs_parent_calc_space_res(mp, target_namelen); - } - if (target_exists) - ret += xfs_parent_calc_space_res(mp, target_namelen); + if (target_exists) + ret += xfs_parent_calc_space_res(mp, target_namelen); + } return ret; } From aafe21c23ad8263251085291019d8860ddc51da9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:59:11 -0700 Subject: [PATCH 1317/2286] xfs: fix ondisk symlink target validation in xrep_dinode_check_dfork LOLLM noticed that online repair of a broken symlink file could fail unnecessarily if a local-format symlink target isn't null terminated. The ondisk target isn't required to be null terminated, but repair enforces that anyway because it uses the validator for the incore symlink target. (The incore buffer is always null-terminated). Fix this by reverting the changes to xfs_symlink_shortform_verify and adding an ondisk-specific helper in inode_repair.c. Cc: stable@vger.kernel.org # v6.8 Fixes: e744cef2060559 ("xfs: zap broken inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Reviewed-by: Dave Chinner Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_inode_fork.c | 4 +--- fs/xfs/libxfs/xfs_symlink_remote.c | 8 ++++++-- fs/xfs/libxfs/xfs_symlink_remote.h | 2 +- fs/xfs/scrub/inode_repair.c | 26 +++++++++++++++++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/fs/xfs/libxfs/xfs_inode_fork.c b/fs/xfs/libxfs/xfs_inode_fork.c index 606a36526ce245..486fe7ab8b6110 100644 --- a/fs/xfs/libxfs/xfs_inode_fork.c +++ b/fs/xfs/libxfs/xfs_inode_fork.c @@ -683,9 +683,7 @@ xfs_ifork_verify_local_data( break; } case S_IFLNK: { - struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK); - - fa = xfs_symlink_shortform_verify(ifp->if_data, ifp->if_bytes); + fa = xfs_symlink_shortform_verify(ip); break; } default: diff --git a/fs/xfs/libxfs/xfs_symlink_remote.c b/fs/xfs/libxfs/xfs_symlink_remote.c index b0dc3888bf1b40..0201a3d59b1a24 100644 --- a/fs/xfs/libxfs/xfs_symlink_remote.c +++ b/fs/xfs/libxfs/xfs_symlink_remote.c @@ -208,11 +208,15 @@ xfs_symlink_local_to_remote( */ xfs_failaddr_t xfs_symlink_shortform_verify( - void *sfp, - int64_t size) + struct xfs_inode *ip) { + struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK); + char *sfp = (char *)ifp->if_data; + int size = ifp->if_bytes; char *endp = sfp + size; + ASSERT(ifp->if_format == XFS_DINODE_FMT_LOCAL); + /* * Zero length symlinks should never occur in memory as they are * never allowed to exist on disk. diff --git a/fs/xfs/libxfs/xfs_symlink_remote.h b/fs/xfs/libxfs/xfs_symlink_remote.h index c1672fe1f17bb2..3f6590602473d0 100644 --- a/fs/xfs/libxfs/xfs_symlink_remote.h +++ b/fs/xfs/libxfs/xfs_symlink_remote.h @@ -18,7 +18,7 @@ bool xfs_symlink_hdr_ok(xfs_ino_t ino, uint32_t offset, void xfs_symlink_local_to_remote(struct xfs_trans *tp, struct xfs_buf *bp, struct xfs_inode *ip, struct xfs_ifork *ifp, void *priv); -xfs_failaddr_t xfs_symlink_shortform_verify(void *sfp, int64_t size); +xfs_failaddr_t xfs_symlink_shortform_verify(struct xfs_inode *ip); int xfs_symlink_remote_read(struct xfs_inode *ip, char *link); int xfs_symlink_write_target(struct xfs_trans *tp, struct xfs_inode *ip, xfs_ino_t owner, const char *target_path, int pathlen, diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index b87c2214623383..fab4015f6a9506 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1024,6 +1024,30 @@ xrep_dinode_bad_metabt_fork( return false; } +static xfs_failaddr_t +xrep_symlink_shortform_verify( + void *sfp, + int64_t size) +{ + /* + * Zero length symlinks should never occur in memory as they are + * never allowed to exist on disk. + */ + if (!size) + return __this_address; + + /* No negative sizes or overly long symlink targets. */ + if (size < 0 || size > XFS_SYMLINK_MAXLEN) + return __this_address; + + /* No NULLs in the target either. */ + if (memchr(sfp, 0, size)) + return __this_address; + + /* ondisk symlink target isn't null terminated, unlike incore */ + return NULL; +} + /* * Check the data fork for things that will fail the ifork verifiers or the * ifork formatters. @@ -1099,7 +1123,7 @@ xrep_dinode_check_dfork( return true; /* symlink structure must pass verification. */ if (S_ISLNK(mode) && - xfs_symlink_shortform_verify(dfork_ptr, data_size) != NULL) + xrep_symlink_shortform_verify(dfork_ptr, data_size) != NULL) return true; break; case XFS_DINODE_FMT_EXTENTS: From 23067b4f98f536ff3341fde04d52a331b2ebdf76 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:59:26 -0700 Subject: [PATCH 1318/2286] xfs: add missing healthmon trace strings LOLLM noticed that we don't have trace strings for all known healthmon types and domains. Fix that. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trace.h | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h index 6aa379c2cf0cd1..0fc8927339b588 100644 --- a/fs/xfs/xfs_trace.h +++ b/fs/xfs/xfs_trace.h @@ -6030,32 +6030,54 @@ DEFINE_HEALTHMON_EVENT(xfs_healthmon_detach); DEFINE_HEALTHMON_EVENT(xfs_healthmon_report_unmount); #define XFS_HEALTHMON_TYPE_STRINGS \ + { XFS_HEALTHMON_RUNNING, "run" }, \ { XFS_HEALTHMON_LOST, "lost" }, \ { XFS_HEALTHMON_UNMOUNT, "unmount" }, \ + { XFS_HEALTHMON_SHUTDOWN, "shutdown" }, \ { XFS_HEALTHMON_SICK, "sick" }, \ { XFS_HEALTHMON_CORRUPT, "corrupt" }, \ { XFS_HEALTHMON_HEALTHY, "healthy" }, \ - { XFS_HEALTHMON_SHUTDOWN, "shutdown" } + { XFS_HEALTHMON_MEDIA_ERROR, "media" }, \ + { XFS_HEALTHMON_BUFREAD, "bufread" }, \ + { XFS_HEALTHMON_BUFWRITE, "bufwrite" }, \ + { XFS_HEALTHMON_DIOREAD, "dioread" }, \ + { XFS_HEALTHMON_DIOWRITE, "diowrite" }, \ + { XFS_HEALTHMON_DATALOST, "datalost" } #define XFS_HEALTHMON_DOMAIN_STRINGS \ { XFS_HEALTHMON_MOUNT, "mount" }, \ { XFS_HEALTHMON_FS, "fs" }, \ { XFS_HEALTHMON_AG, "ag" }, \ { XFS_HEALTHMON_INODE, "inode" }, \ - { XFS_HEALTHMON_RTGROUP, "rtgroup" } + { XFS_HEALTHMON_RTGROUP, "rtgroup" }, \ + { XFS_HEALTHMON_DATADEV, "datadev" }, \ + { XFS_HEALTHMON_RTDEV, "rtdev" }, \ + { XFS_HEALTHMON_LOGDEV, "logdev" }, \ + { XFS_HEALTHMON_FILERANGE, "filerange" } +TRACE_DEFINE_ENUM(XFS_HEALTHMON_RUNNING); TRACE_DEFINE_ENUM(XFS_HEALTHMON_LOST); -TRACE_DEFINE_ENUM(XFS_HEALTHMON_SHUTDOWN); TRACE_DEFINE_ENUM(XFS_HEALTHMON_UNMOUNT); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_SHUTDOWN); TRACE_DEFINE_ENUM(XFS_HEALTHMON_SICK); TRACE_DEFINE_ENUM(XFS_HEALTHMON_CORRUPT); TRACE_DEFINE_ENUM(XFS_HEALTHMON_HEALTHY); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_MEDIA_ERROR); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_BUFREAD); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_BUFWRITE); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_DIOREAD); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_DIOWRITE); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_DATALOST); TRACE_DEFINE_ENUM(XFS_HEALTHMON_MOUNT); TRACE_DEFINE_ENUM(XFS_HEALTHMON_FS); TRACE_DEFINE_ENUM(XFS_HEALTHMON_AG); TRACE_DEFINE_ENUM(XFS_HEALTHMON_INODE); TRACE_DEFINE_ENUM(XFS_HEALTHMON_RTGROUP); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_DATADEV); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_RTDEV); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_LOGDEV); +TRACE_DEFINE_ENUM(XFS_HEALTHMON_FILERANGE); DECLARE_EVENT_CLASS(xfs_healthmon_event_class, TP_PROTO(const struct xfs_healthmon *hm, From 329750292e9dfbb594b375d7b30cf5ddb58b7bb9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:59:42 -0700 Subject: [PATCH 1319/2286] xfarray: don't crash when sorting if array element crosses a folio LOLLM points out that if an array element crosses a folio boundary, xfile_get_folio returns a NULL folio pointer. If this happens, si->folio is also set to NULL, and calling folio_pos/folio_address will just crash the kernel. Teach this function to handle this condition by falling back to reading the array element into scratchpad memory. Cc: stable@vger.kernel.org # v6.6 Fixes: cf36f4f64c2d4e ("xfs: cache pages used for xfarray quicksort convergence") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/xfarray.c | 69 ++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index 2ce24bfe4c0fab..5ea3ce4bf8d846 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -794,6 +794,46 @@ xfarray_sort_scan_done( si->folio = NULL; } +static int +xfarray_sort_load_folio( + struct xfarray_sortinfo *si, + xfarray_idx_t idx, + loff_t idx_pos) +{ + struct folio *folio; + loff_t next_pos; + + folio = xfile_get_folio(si->array->xfile, idx_pos, si->array->obj_size, + XFILE_ALLOC); + if (IS_ERR(folio)) + return PTR_ERR(folio); + si->folio = folio; + + /* No folio? Get the caller to read into the scratchpad. */ + if (!si->folio) + return 0; + + si->first_folio_idx = xfarray_idx(si->array, + folio_pos(si->folio) + si->array->obj_size - 1); + + next_pos = folio_next_pos(si->folio); + si->last_folio_idx = xfarray_idx(si->array, next_pos - 1); + if (xfarray_pos(si->array, si->last_folio_idx + 1) > next_pos) + si->last_folio_idx--; + + /* + * If this folio still doesn't cover the desired element, it must cross + * a folio boundary. Get the caller to read into the scratchpad. + */ + if (idx < si->first_folio_idx || idx > si->last_folio_idx) { + xfarray_sort_scan_done(si); + return 0; + } + + trace_xfarray_sort_scan(si, idx); + return 0; +} + /* * Cache the folio backing the start of the given array element. If the array * element is contained entirely within the folio, return a pointer to the @@ -819,33 +859,18 @@ xfarray_sort_scan( (idx < si->first_folio_idx || idx > si->last_folio_idx)) xfarray_sort_scan_done(si); - /* Grab the first folio that backs this array element. */ + /* Grab the folio that backs this array element. */ if (!si->folio) { - struct folio *folio; - loff_t next_pos; - - folio = xfile_get_folio(si->array->xfile, idx_pos, - si->array->obj_size, XFILE_ALLOC); - if (IS_ERR(folio)) - return PTR_ERR(folio); - si->folio = folio; - - si->first_folio_idx = xfarray_idx(si->array, - folio_pos(si->folio) + si->array->obj_size - 1); - - next_pos = folio_next_pos(si->folio); - si->last_folio_idx = xfarray_idx(si->array, next_pos - 1); - if (xfarray_pos(si->array, si->last_folio_idx + 1) > next_pos) - si->last_folio_idx--; - - trace_xfarray_sort_scan(si, idx); + error = xfarray_sort_load_folio(si, idx, idx_pos); + if (error) + return error; } /* - * If this folio still doesn't cover the desired element, it must cross - * a folio boundary. Read into the scratchpad and we're done. + * If we don't have a folio mapping the entire array element, read into + * the scratchpad and we're done. */ - if (idx < si->first_folio_idx || idx > si->last_folio_idx) { + if (!si->folio) { void *temp = xfarray_scratch(si->array); error = xfile_load(si->array->xfile, temp, si->array->obj_size, From 3a94b91aef22b6091cdad17160105070cbf2d509 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 22:59:57 -0700 Subject: [PATCH 1320/2286] xfarray: don't allow users to unset in the middle of an array Now that we've merged online repair and removed some clunky parts of the original online checking code, the only user of xfarray_unset is the free space btree repair code, and it only needs to be able to remove records from the end of the array. Let's remove all the code that handles "unset" array elements that are not at the end, because we can just reduce the array element count. Remove the "store anywhere" function because it was only ever used by the callers who used unset to remove elements in the middle of the array. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- .../xfs/xfs-online-fsck-design.rst | 13 +-- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/xfarray.c | 108 ++---------------- fs/xfs/scrub/xfarray.h | 6 +- 4 files changed, 15 insertions(+), 114 deletions(-) diff --git a/Documentation/filesystems/xfs/xfs-online-fsck-design.rst b/Documentation/filesystems/xfs/xfs-online-fsck-design.rst index 3d9233f403dbb1..14767ce9fad43f 100644 --- a/Documentation/filesystems/xfs/xfs-online-fsck-design.rst +++ b/Documentation/filesystems/xfs/xfs-online-fsck-design.rst @@ -1973,8 +1973,7 @@ provide loading and storing of array elements at arbitrary array indices. Gaps are defined to be null records, and null records are defined to be a sequence of all zero bytes. Null records are detected by calling ``xfarray_element_is_null``. -They are created either by calling ``xfarray_unset`` to null out an existing -record or by never storing anything to an array index. +They are created by never storing anything to an array index. The second type of caller handles records that are not indexed by position and do not require multiple updates to a record. @@ -1991,9 +1990,7 @@ The typical use case here is constructing space extent reference counts from reverse mapping information. Records can be put in the bag in any order, they can be removed from the bag at any time, and uniqueness of records is left to callers. -The ``xfarray_store_anywhere`` function is used to insert a record in any -null record slot in the bag; and the ``xfarray_unset`` function removes a -record from the bag. +Note: Bags are now implemented with in-memory btrees for faster access. Iterating Array Elements ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -2643,11 +2640,7 @@ generate refcount information from reverse mapping records. refcount record associating the block number range that we just walked to the size of the bag. -The bag-like structure in this case is a type 2 xfarray as discussed in the -:ref:`xfarray access patterns` section. -Reverse mappings are added to the bag using ``xfarray_store_anywhere`` and -removed via ``xfarray_unset``. -Bag members are examined through ``xfarray_iter`` loops. +The bag-like structure in this case is an in-memory btree. Case Study: Rebuilding File Fork Mapping Indices ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 2398e381959771..b6c5292ec80ea8 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -517,7 +517,7 @@ xrep_abt_reserve_space( * records (but doesn't break the sorting order), so we must * go around the loop once more to re-run _bload_init. */ - error = xfarray_unset(ra->free_records, record_nr); + error = xfarray_trim(ra->free_records, 1); if (error) break; ra->nr_real_records--; diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index 5ea3ce4bf8d846..685824efdb93f6 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -140,55 +140,20 @@ xfarray_load( xfarray_pos(array, idx)); } -/* Is this array element potentially unset? */ -static inline bool -xfarray_is_unset( - struct xfarray *array, - loff_t pos) -{ - void *temp = xfarray_scratch(array); - int error; - - if (array->unset_slots == 0) - return false; - - error = xfile_load(array->xfile, temp, array->obj_size, pos); - if (!error && xfarray_element_is_null(array, temp)) - return true; - - return false; -} - -/* - * Unset an array element. If @idx is the last element in the array, the - * array will be truncated. Otherwise, the entry will be zeroed. - */ +/* Remove the elements at the end of an array. */ int -xfarray_unset( - struct xfarray *array, - xfarray_idx_t idx) +xfarray_trim( + struct xfarray *array, + unsigned long long nr) { - void *temp = xfarray_scratch(array); - loff_t pos = xfarray_pos(array, idx); - int error; + loff_t new_eof; - if (idx >= array->nr) + if (nr > array->nr) return -ENODATA; - if (idx == array->nr - 1) { - array->nr--; - return 0; - } - - if (xfarray_is_unset(array, pos)) - return 0; - - memset(temp, 0, array->obj_size); - error = xfile_store(array->xfile, temp, array->obj_size, pos); - if (error) - return error; - - array->unset_slots++; + array->nr -= nr; + new_eof = xfarray_pos(array, array->nr); + xfile_discard(array->xfile, new_eof, MAX_LFS_FILESIZE - new_eof); return 0; } @@ -227,43 +192,6 @@ xfarray_element_is_null( return !memchr_inv(ptr, 0, array->obj_size); } -/* - * Store an element anywhere in the array that is unset. If there are no - * unset slots, append the element to the array. - */ -int -xfarray_store_anywhere( - struct xfarray *array, - const void *ptr) -{ - void *temp = xfarray_scratch(array); - loff_t endpos = xfarray_pos(array, array->nr); - loff_t pos; - int error; - - /* Find an unset slot to put it in. */ - for (pos = 0; - pos < endpos && array->unset_slots > 0; - pos += array->obj_size) { - error = xfile_load(array->xfile, temp, array->obj_size, - pos); - if (error || !xfarray_element_is_null(array, temp)) - continue; - - error = xfile_store(array->xfile, ptr, array->obj_size, - pos); - if (error) - return error; - - array->unset_slots--; - return 0; - } - - /* No unset slots found; attach it on the end. */ - array->unset_slots = 0; - return xfarray_append(array, ptr); -} - /* Return length of array. */ uint64_t xfarray_length( @@ -677,26 +605,10 @@ xfarray_qsort_pivot( /* Load the selected xfarray records into the pivot array. */ for (i = 0; i < XFARRAY_QSORT_PIVOT_NR; i++) { - xfarray_idx_t idx; - recp = xfarray_pivot_array_rec(parray, pivot_rec_sz, i); idxp = xfarray_pivot_array_idx(parray, pivot_rec_sz, i); - /* No unset records; load directly into the array. */ - if (likely(si->array->unset_slots == 0)) { - error = xfarray_sort_load(si, *idxp, recp); - if (error) - return error; - continue; - } - - /* - * Load non-null records into the scratchpad without changing - * the xfarray_idx_t in the pivot array. - */ - idx = *idxp; - xfarray_sort_bump_loads(si); - error = xfarray_load_next(si->array, &idx, recp); + error = xfarray_sort_load(si, *idxp, recp); if (error) return error; } diff --git a/fs/xfs/scrub/xfarray.h b/fs/xfs/scrub/xfarray.h index 5eeeeed13ae24a..d55225c7885b25 100644 --- a/fs/xfs/scrub/xfarray.h +++ b/fs/xfs/scrub/xfarray.h @@ -27,9 +27,6 @@ struct xfarray { /* Maximum possible array size. */ xfarray_idx_t max_nr; - /* Number of unset slots in the array below @nr. */ - uint64_t unset_slots; - /* Size of an array element. */ size_t obj_size; @@ -41,9 +38,8 @@ int xfarray_create(const char *descr, unsigned long long required_capacity, size_t obj_size, struct xfarray **arrayp); void xfarray_destroy(struct xfarray *array); int xfarray_load(struct xfarray *array, xfarray_idx_t idx, void *ptr); -int xfarray_unset(struct xfarray *array, xfarray_idx_t idx); +int xfarray_trim(struct xfarray *array, unsigned long long nr); int xfarray_store(struct xfarray *array, xfarray_idx_t idx, const void *ptr); -int xfarray_store_anywhere(struct xfarray *array, const void *ptr); bool xfarray_element_is_null(struct xfarray *array, const void *ptr); void xfarray_truncate(struct xfarray *array); unsigned long long xfarray_bytes(struct xfarray *array); From 76ac15cafb29ef11098e3db7fb955922f48c823a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:00:13 -0700 Subject: [PATCH 1321/2286] xfs: don't allow sorting sparse arrays Now that we've reduced the functionality of xfarray_unset, let's add a new safeguard: no sorting of xfarrays with sparse holes in them. It's not clear what that even means, and nobody actually does this, so we're really just eliminating subtle logic bombs. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/xfarray.c | 14 ++++++++++++++ fs/xfs/scrub/xfarray.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index 685824efdb93f6..7f85855588ed54 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -152,6 +152,9 @@ xfarray_trim( return -ENODATA; array->nr -= nr; + if (!array->nr) + array->possibly_sparse = false; + new_eof = xfarray_pos(array, array->nr); xfile_discard(array->xfile, new_eof, MAX_LFS_FILESIZE - new_eof); return 0; @@ -179,6 +182,8 @@ xfarray_store( if (ret) return ret; + if (idx > array->nr) + array->possibly_sparse = true; array->nr = max(array->nr, idx + 1); return 0; } @@ -853,6 +858,14 @@ xfarray_sort( return 0; if (array->nr >= QSORT_MAX_RECS) return -E2BIG; + if (array->possibly_sparse) { + /* + * What does it mean to sort an array with holes in it? + * Currently none of the users need this ability. + */ + ASSERT(!array->possibly_sparse); + return -EINVAL; + } error = xfarray_sortinfo_alloc(array, cmp_fn, flags, &si); if (error) @@ -1006,4 +1019,5 @@ xfarray_truncate( { xfile_discard(array->xfile, 0, MAX_LFS_FILESIZE); array->nr = 0; + array->possibly_sparse = false; } diff --git a/fs/xfs/scrub/xfarray.h b/fs/xfs/scrub/xfarray.h index d55225c7885b25..05ff65b09fcf41 100644 --- a/fs/xfs/scrub/xfarray.h +++ b/fs/xfs/scrub/xfarray.h @@ -32,6 +32,9 @@ struct xfarray { /* log2 of array element size, if possible. */ int obj_size_log; + + /* Might there be sparse holes in this array? */ + bool possibly_sparse; }; int xfarray_create(const char *descr, unsigned long long required_capacity, From ebd528d1987e133fd33f51f51d76294c9a4cc196 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:00:29 -0700 Subject: [PATCH 1322/2286] xfs: simply the free space btree repair code Now that the xfarray always knows how many valid records there are stored inside of it, get rid of the shadow variable. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index b6c5292ec80ea8..6e835240536181 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -108,9 +108,6 @@ struct xrep_abt { struct xfs_scrub *sc; - /* Number of non-null records in @free_records. */ - uint64_t nr_real_records; - /* get_records()'s position in the free space record array. */ xfarray_idx_t array_cur; @@ -403,7 +400,6 @@ xrep_abt_find_freespace( if (error) goto err_agfl; - ra->nr_real_records = xfarray_length(ra->free_records); err_agfl: xfs_trans_brelse(sc->tp, agfl_bp); err: @@ -446,15 +442,17 @@ xrep_abt_reserve_space( uint64_t required; unsigned int desired; unsigned int len; + const uint64_t nr_records = + xfarray_length(ra->free_records); /* Compute how many blocks we'll need. */ error = xfs_btree_bload_compute_geometry(cnt_cur, - &ra->new_cntbt.bload, ra->nr_real_records); + &ra->new_cntbt.bload, nr_records); if (error) break; error = xfs_btree_bload_compute_geometry(bno_cur, - &ra->new_bnobt.bload, ra->nr_real_records); + &ra->new_bnobt.bload, nr_records); if (error) break; @@ -470,7 +468,7 @@ xrep_abt_reserve_space( desired = required - allocated; /* We need space but there's none left; bye! */ - if (ra->nr_real_records == 0) { + if (nr_records == 0) { error = -ENOSPC; break; } @@ -520,7 +518,6 @@ xrep_abt_reserve_space( error = xfarray_trim(ra->free_records, 1); if (error) break; - ra->nr_real_records--; record_nr--; } while (1); From ddeb5fe2d8a8645d033d868117571649184d64ab Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:00:44 -0700 Subject: [PATCH 1323/2286] xfarray: warn against sorting arrays with identical elements LOLLM complains about a potential underflow here if xfarray_qsort_push is called with lo==0. However, this isn't possible in most cases because filesystem metadata records cannot be identical. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/xfarray.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index 7f85855588ed54..c94f355607790e 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -682,6 +682,18 @@ xfarray_qsort_push( return -EFSCORRUPTED; } + /* + * Avoid the integer underflow below in (lo - 1). This shouldn't + * be possible because the pivot is the median of nine distinct + * filesystem metadata records, so at least four records will be less + * than the pivot, which means the pivot will not be in the low end of + * the range by the time we get here. + */ + if (lo == 0) { + ASSERT(lo != 0); + return -EFSCORRUPTED; + } + si->max_stack_used = max_t(uint8_t, si->max_stack_used, si->stack_depth + 2); From aa5dba2d217f0df5e14ac6222861a42f10d0aabc Mon Sep 17 00:00:00 2001 From: Aditya Prakash Srivastava Date: Mon, 14 Sep 2026 09:31:51 +0000 Subject: [PATCH 1324/2286] xfs: prevent close() from hanging on frozen filesystems When a file is closed, xfs_file_release() attempts to trim speculative post-EOF blocks. This requires allocating a transaction, which blocks indefinitely if the filesystem is frozen. Fix the hang by wrapping the preallocation cleanup block with sb_start_write_trylock() and xfs_ilock_nowait() to bypass the trim best-effort when the filesystem is frozen or locking fails. Suggested-by: Darrick J. Wong Signed-off-by: Aditya Prakash Srivastava Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index d8202da15aca91..dd6d2e08faff9e 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -1872,17 +1872,21 @@ xfs_file_release( return 0; /* - * If we can't get the iolock just skip truncating the blocks past EOF - * because we could deadlock with the mmap_lock otherwise. We'll get - * another chance to drop them once the last reference to the inode is - * dropped, so we'll never leak blocks permanently. + * If we can't get the iolock or if the filesystem is frozen, just skip + * truncating the blocks past EOF because we could deadlock with the + * mmap_lock or hang the close() call. We'll get another chance to drop + * them once the last reference to the inode is dropped, so we'll never + * leak blocks permanently. */ if (!xfs_iflags_test(ip, XFS_EOFBLOCKS_RELEASED) && - xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) { - if (xfs_can_free_eofblocks(ip) && - !xfs_iflags_test_and_set(ip, XFS_EOFBLOCKS_RELEASED)) - xfs_free_eofblocks(ip); - xfs_iunlock(ip, XFS_IOLOCK_EXCL); + sb_start_write_trylock(mp->m_super)) { + if (xfs_ilock_nowait(ip, XFS_IOLOCK_EXCL)) { + if (xfs_can_free_eofblocks(ip) && + !xfs_iflags_test_and_set(ip, XFS_EOFBLOCKS_RELEASED)) + xfs_free_eofblocks(ip); + xfs_iunlock(ip, XFS_IOLOCK_EXCL); + } + sb_end_write(mp->m_super); } return 0; From cfde1b04ead80e8e3c2148745a478662b81d8d92 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 9 Sep 2026 09:05:26 +0300 Subject: [PATCH 1325/2286] xfs: convert all !XFS_RT stubs to inline functions Avoid random compiler warnings about unused variables when CONFIG_XFS_RT is not set. Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_rtalloc.h | 49 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/fs/xfs/xfs_rtalloc.h b/fs/xfs/xfs_rtalloc.h index 78a690b489ed59..1d5108861acdca 100644 --- a/fs/xfs/xfs_rtalloc.h +++ b/fs/xfs/xfs_rtalloc.h @@ -46,10 +46,34 @@ int xfs_rtalloc_reinit_frextents(struct xfs_mount *mp); int xfs_growfs_check_rtgeom(const struct xfs_mount *mp, xfs_rfsblock_t dblocks, xfs_rfsblock_t rblocks, xfs_agblock_t rextsize); #else -# define xfs_growfs_rt(mp,in) (-ENOSYS) -# define xfs_rtalloc_reinit_frextents(m) (0) -# define xfs_rtmount_readsb(mp) (0) -# define xfs_rtmount_freesb(mp) ((void)0) +static inline int +xfs_growfs_rt( + struct xfs_mount *mp, + struct xfs_growfs_rt *in) +{ + return -ENOSYS; +} + +static inline int +xfs_rtalloc_reinit_frextents( + struct xfs_mount *mp) +{ + return 0; +} + +static inline int +xfs_rtmount_readsb( + struct xfs_mount *mp) +{ + return 0; +} + +static inline void +xfs_rtmount_freesb( + struct xfs_mount *mp) +{ +} + static inline int /* error */ xfs_rtmount_init( xfs_mount_t *mp) /* file system mount structure */ @@ -60,8 +84,21 @@ xfs_rtmount_init( xfs_warn(mp, "Not built with CONFIG_XFS_RT"); return -ENOSYS; } -# define xfs_rtmount_inodes(m) (((mp)->m_sb.sb_rblocks == 0)? 0 : (-ENOSYS)) -# define xfs_rtunmount_inodes(m) + +static inline int +xfs_rtmount_inodes( + struct xfs_mount *mp) +{ + if (mp->m_sb.sb_rblocks) + return -ENOSYS; + return 0; +} + +static inline void +xfs_rtunmount_inodes( + struct xfs_mount *mp) +{ +} static inline int xfs_growfs_check_rtgeom(const struct xfs_mount *mp, From c8394b07a5348a0814ff4a00371b157972b60e5e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:00 +0200 Subject: [PATCH 1326/2286] xfs: remove an outdated comment above xfs_file_ioctl The return code sign flipping at the method boundary is long gone in XFS, so remove this comment documenting an exception from it. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 96ca3e480cb9fe..4a8921f38a082f 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1209,12 +1209,6 @@ xfs_ioctl_fs_counts( #define XFS_IOC_ALLOCSP64 _IOW ('X', 36, struct xfs_flock64) #define XFS_IOC_FREESP64 _IOW ('X', 37, struct xfs_flock64) -/* - * Note: some of the ioctl's return positive numbers as a - * byte count indicating success, such as readlink_by_handle. - * So we don't "sign flip" like most other routines. This means - * true errors need to be returned as a negative value. - */ long xfs_file_ioctl( struct file *filp, From aa8ac8bd7c18deff3d556e6ae83f68d6b8951c4f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:01 +0200 Subject: [PATCH 1327/2286] xfs: rename xfs_ioc_swapext to xfs_swapext The usual convention is that the ioc_ prefix is used for direct ioctl handlers that take a user pointer. The current xfs_ioc_swapext does not fit that pattern, so rename it. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 8 ++++---- fs/xfs/xfs_ioctl.h | 4 +--- fs/xfs/xfs_ioctl32.c | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 4a8921f38a082f..314eeec0234171 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -962,10 +962,10 @@ xfs_ioc_getbmap( } int -xfs_ioc_swapext( - xfs_swapext_t *sxp) +xfs_swapext( + struct xfs_swapext *sxp) { - xfs_inode_t *ip, *tip; + struct xfs_inode *ip, *tip; /* Pull information for the target fd */ CLASS(fd, f)((int)sxp->sx_fdtarget); @@ -1341,7 +1341,7 @@ xfs_file_ioctl( error = mnt_want_write_file(filp); if (error) return error; - error = xfs_ioc_swapext(&sxp); + error = xfs_swapext(&sxp); mnt_drop_write_file(filp); return error; } diff --git a/fs/xfs/xfs_ioctl.h b/fs/xfs/xfs_ioctl.h index f5ed5cf9d3df65..e57d8f5148bf7f 100644 --- a/fs/xfs/xfs_ioctl.h +++ b/fs/xfs/xfs_ioctl.h @@ -10,9 +10,7 @@ struct xfs_bstat; struct xfs_ibulk; struct xfs_inogrp; -int -xfs_ioc_swapext( - xfs_swapext_t *sxp); +int xfs_swapext(struct xfs_swapext *sxp); extern int xfs_fileattr_get( diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index c66e192448a89d..250df2bbf21417 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -475,7 +475,7 @@ xfs_file_compat_ioctl( error = mnt_want_write_file(filp); if (error) return error; - error = xfs_ioc_swapext(&sxp); + error = xfs_swapext(&sxp); mnt_drop_write_file(filp); return error; } From 1520a0f399c0a847bdba82cced32045ce0fcc7eb Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:02 +0200 Subject: [PATCH 1328/2286] xfs: rename xfs_ioctl_fs_counts to xfs_ioc_fs_counts Match the naming scheme of most other ioctl handlers. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 314eeec0234171..e0da8dfc9ae67d 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1183,7 +1183,7 @@ xfs_ioctl_getset_resblocks( } static int -xfs_ioctl_fs_counts( +xfs_ioc_fs_counts( struct xfs_mount *mp, struct xfs_fsop_counts __user *uarg) { @@ -1347,7 +1347,7 @@ xfs_file_ioctl( } case XFS_IOC_FSCOUNTS: - return xfs_ioctl_fs_counts(mp, arg); + return xfs_ioc_fs_counts(mp, arg); case XFS_IOC_SET_RESBLKS: case XFS_IOC_GET_RESBLKS: From bf3935662d5b134a203daab870450491225017c2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:03 +0200 Subject: [PATCH 1329/2286] xfs: rename xfs_ioctl_getset_resblocks to xfs_ioc_getset_resblocks Match the naming scheme of most other ioctl handlers. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index e0da8dfc9ae67d..479947456b1f02 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1144,7 +1144,7 @@ xfs_fs_eofblocks_from_user( } static int -xfs_ioctl_getset_resblocks( +xfs_ioc_getset_resblocks( struct file *filp, unsigned int cmd, void __user *arg) @@ -1351,7 +1351,7 @@ xfs_file_ioctl( case XFS_IOC_SET_RESBLKS: case XFS_IOC_GET_RESBLKS: - return xfs_ioctl_getset_resblocks(filp, cmd, arg); + return xfs_ioc_getset_resblocks(filp, cmd, arg); case XFS_IOC_FSGROWFSDATA: { struct xfs_growfs_data in; From a0e6888bbce20ba670f01b09bba5fec6c9f71692 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:04 +0200 Subject: [PATCH 1330/2286] xfs: split out the handler for XFS_IOC_DIOINFO Split out a helper for XFS_IOC_DIOINFO to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 47 +++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 479947456b1f02..48cee2bf6691ff 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1200,6 +1200,32 @@ xfs_ioc_fs_counts( return 0; } +static int +xfs_ioc_dioinfo( + struct file *file, + void __user *arg) +{ + struct kstat st; + struct dioattr da; + int error; + + error = vfs_getattr(&file->f_path, &st, STATX_DIOALIGN, 0); + if (error) + return error; + + /* + * Some userspace directly feeds the return value to posix_memalign, + * which fails for values that are smaller than the pointer size. + * Round up the value to not break userspace. + */ + da.d_mem = roundup(st.dio_mem_align, sizeof(void *)); + da.d_miniosz = st.dio_offset_align; + da.d_maxiosz = INT_MAX & ~(da.d_miniosz - 1); + if (copy_to_user(arg, &da, sizeof(da))) + return -EFAULT; + return 0; +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1238,26 +1264,9 @@ xfs_file_ioctl( "%s should use fallocate; XFS_IOC_{ALLOC,FREE}SP ioctl unsupported", current->comm); return -ENOTTY; - case XFS_IOC_DIOINFO: { - struct kstat st; - struct dioattr da; - - error = vfs_getattr(&filp->f_path, &st, STATX_DIOALIGN, 0); - if (error) - return error; - /* - * Some userspace directly feeds the return value to - * posix_memalign, which fails for values that are smaller than - * the pointer size. Round up the value to not break userspace. - */ - da.d_mem = roundup(st.dio_mem_align, sizeof(void *)); - da.d_miniosz = st.dio_offset_align; - da.d_maxiosz = INT_MAX & ~(da.d_miniosz - 1); - if (copy_to_user(arg, &da, sizeof(da))) - return -EFAULT; - return 0; - } + case XFS_IOC_DIOINFO: + return xfs_ioc_dioinfo(filp, arg); case XFS_IOC_FSBULKSTAT_SINGLE: case XFS_IOC_FSBULKSTAT: From 6b7fe7fb912357c6514af772cb61c3cfb8f8e5c3 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:05 +0200 Subject: [PATCH 1331/2286] xfs: split out the handlers for XFS_IOC_.*HANDLE Split out helpers for XFS_IOC_.*HANDLE to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 65 ++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 48cee2bf6691ff..a038df0196f70d 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1226,6 +1226,42 @@ xfs_ioc_dioinfo( return 0; } +static int +xfs_ioc_find_handle( + unsigned int cmd, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (copy_from_user(&hreq, arg, sizeof(hreq))) + return -EFAULT; + return xfs_find_handle(cmd, &hreq); +} + +static int +xfs_ioc_open_by_handle( + struct file *file, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (copy_from_user(&hreq, arg, sizeof(hreq))) + return -EFAULT; + return xfs_open_by_handle(file, &hreq); +} + +static int +xfs_ioc_readlink_by_handle( + struct file *file, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (copy_from_user(&hreq, arg, sizeof(hreq))) + return -EFAULT; + return xfs_readlink_by_handle(file, &hreq); +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1314,31 +1350,14 @@ xfs_file_ioctl( case XFS_IOC_FD_TO_HANDLE: case XFS_IOC_PATH_TO_HANDLE: - case XFS_IOC_PATH_TO_FSHANDLE: { - xfs_fsop_handlereq_t hreq; - - if (copy_from_user(&hreq, arg, sizeof(hreq))) - return -EFAULT; - return xfs_find_handle(cmd, &hreq); - } - case XFS_IOC_OPEN_BY_HANDLE: { - xfs_fsop_handlereq_t hreq; - - if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t))) - return -EFAULT; - return xfs_open_by_handle(filp, &hreq); - } - - case XFS_IOC_READLINK_BY_HANDLE: { - xfs_fsop_handlereq_t hreq; - - if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t))) - return -EFAULT; - return xfs_readlink_by_handle(filp, &hreq); - } + case XFS_IOC_PATH_TO_FSHANDLE: + return xfs_ioc_find_handle(cmd, arg); + case XFS_IOC_OPEN_BY_HANDLE: + return xfs_ioc_open_by_handle(filp, arg); + case XFS_IOC_READLINK_BY_HANDLE: + return xfs_ioc_readlink_by_handle(filp, arg); case XFS_IOC_ATTRLIST_BY_HANDLE: return xfs_attrlist_by_handle(filp, arg); - case XFS_IOC_ATTRMULTI_BY_HANDLE: return xfs_attrmulti_by_handle(filp, arg); From eae65218e571b963198fd5c7671dc1272293ab1e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:06 +0200 Subject: [PATCH 1332/2286] xfs: split out the handler for XFS_IOC_SWAPEXT Split out a helper for XFS_IOC_SWAPEXT to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index a038df0196f70d..ecee39e2ac1943 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1262,6 +1262,25 @@ xfs_ioc_readlink_by_handle( return xfs_readlink_by_handle(file, &hreq); } +static int +xfs_ioc_swapext( + struct file *file, + void __user *arg) +{ + struct xfs_swapext sxp; + int error; + + if (copy_from_user(&sxp, arg, sizeof(sxp))) + return -EFAULT; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_swapext(&sxp); + mnt_drop_write_file(file); + return error; +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1361,18 +1380,8 @@ xfs_file_ioctl( case XFS_IOC_ATTRMULTI_BY_HANDLE: return xfs_attrmulti_by_handle(filp, arg); - case XFS_IOC_SWAPEXT: { - struct xfs_swapext sxp; - - if (copy_from_user(&sxp, arg, sizeof(xfs_swapext_t))) - return -EFAULT; - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_swapext(&sxp); - mnt_drop_write_file(filp); - return error; - } + case XFS_IOC_SWAPEXT: + return xfs_ioc_swapext(filp, arg); case XFS_IOC_FSCOUNTS: return xfs_ioc_fs_counts(mp, arg); From 74aed426ad16fa4eb539c918d35b5f40a6cfe952 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:07 +0200 Subject: [PATCH 1333/2286] xfs: split out the handlers for XFS_IOC_FSGROWFS* Split out helpers for XFS_IOC_FSGROWFS* to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 107 ++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index ecee39e2ac1943..92bdf0e37f3fb8 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1281,6 +1281,66 @@ xfs_ioc_swapext( return error; } +static int +xfs_ioc_growfs_data( + struct file *file, + struct xfs_mount *mp, + void __user *arg) +{ + struct xfs_growfs_data in; + int error; + + if (copy_from_user(&in, arg, sizeof(in))) + return -EFAULT; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_growfs_data(mp, &in); + mnt_drop_write_file(file); + return error; +} + +static int +xfs_ioc_growfs_log( + struct file *file, + struct xfs_mount *mp, + void __user *arg) +{ + struct xfs_growfs_log in; + int error; + + if (copy_from_user(&in, arg, sizeof(in))) + return -EFAULT; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_growfs_log(mp, &in); + mnt_drop_write_file(file); + return error; +} + +static int +xfs_ioc_growfs_rt( + struct file *file, + struct xfs_mount *mp, + void __user *arg) +{ + struct xfs_growfs_rt in; + int error; + + if (copy_from_user(&in, arg, sizeof(in))) + return -EFAULT; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_growfs_rt(mp, &in); + mnt_drop_write_file(file); + return error; +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1390,47 +1450,12 @@ xfs_file_ioctl( case XFS_IOC_GET_RESBLKS: return xfs_ioc_getset_resblocks(filp, cmd, arg); - case XFS_IOC_FSGROWFSDATA: { - struct xfs_growfs_data in; - - if (copy_from_user(&in, arg, sizeof(in))) - return -EFAULT; - - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_growfs_data(mp, &in); - mnt_drop_write_file(filp); - return error; - } - - case XFS_IOC_FSGROWFSLOG: { - struct xfs_growfs_log in; - - if (copy_from_user(&in, arg, sizeof(in))) - return -EFAULT; - - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_growfs_log(mp, &in); - mnt_drop_write_file(filp); - return error; - } - - case XFS_IOC_FSGROWFSRT: { - xfs_growfs_rt_t in; - - if (copy_from_user(&in, arg, sizeof(in))) - return -EFAULT; - - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_growfs_rt(mp, &in); - mnt_drop_write_file(filp); - return error; - } + case XFS_IOC_FSGROWFSDATA: + return xfs_ioc_growfs_data(filp, mp, arg); + case XFS_IOC_FSGROWFSLOG: + return xfs_ioc_growfs_log(filp, mp, arg); + case XFS_IOC_FSGROWFSRT: + return xfs_ioc_growfs_rt(filp, mp, arg); case XFS_IOC_GOINGDOWN: { uint32_t in; From df33e313c20b619212fcc0949ce5bd9aa00512c2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:08 +0200 Subject: [PATCH 1334/2286] xfs: split out the handler for XFS_IOC_GOINGDOWN Split out a helper for XFS_IOC_GOINGDOWN to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 92bdf0e37f3fb8..1580f8d1299ca5 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1341,6 +1341,20 @@ xfs_ioc_growfs_rt( return error; } +static int +xfs_ioc_goingdown( + struct xfs_mount *mp, + uint32_t __user *arg) +{ + uint32_t in; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (get_user(in, arg)) + return -EFAULT; + return xfs_fs_goingdown(mp, in); +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1457,18 +1471,8 @@ xfs_file_ioctl( case XFS_IOC_FSGROWFSRT: return xfs_ioc_growfs_rt(filp, mp, arg); - case XFS_IOC_GOINGDOWN: { - uint32_t in; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - if (get_user(in, (uint32_t __user *)arg)) - return -EFAULT; - - return xfs_fs_goingdown(mp, in); - } - + case XFS_IOC_GOINGDOWN: + return xfs_ioc_goingdown(mp, arg); case XFS_IOC_ERROR_INJECTION: { xfs_error_injection_t in; From 86eede9385a3d988110a6af6c89d9709728c1845 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:09 +0200 Subject: [PATCH 1335/2286] xfs: split out the handler for XFS_IOC_ERROR_INJECTION Split out a helper for XFS_IOC_ERROR_INJECTION to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 1580f8d1299ca5..a226b33a74b517 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1355,6 +1355,20 @@ xfs_ioc_goingdown( return xfs_fs_goingdown(mp, in); } +static int +xfs_ioc_error_injection( + struct xfs_mount *mp, + struct xfs_error_injection __user *arg) +{ + struct xfs_error_injection in; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (copy_from_user(&in, arg, sizeof(in))) + return -EFAULT; + return xfs_errortag_add(mp, in.errtag); +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1473,22 +1487,11 @@ xfs_file_ioctl( case XFS_IOC_GOINGDOWN: return xfs_ioc_goingdown(mp, arg); - case XFS_IOC_ERROR_INJECTION: { - xfs_error_injection_t in; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - if (copy_from_user(&in, arg, sizeof(in))) - return -EFAULT; - - return xfs_errortag_add(mp, in.errtag); - } - + case XFS_IOC_ERROR_INJECTION: + return xfs_ioc_error_injection(mp, arg); case XFS_IOC_ERROR_CLEARALL: if (!capable(CAP_SYS_ADMIN)) return -EPERM; - return xfs_errortag_clearall(mp); case XFS_IOC_FREE_EOFBLOCKS: { From 3483bf12f74e7228f5196bcf6e4bcd90733185b2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:10 +0200 Subject: [PATCH 1336/2286] xfs: split out the handler for XFS_IOC_FREE_EOFBLOCKS Split out a helper for XFS_IOC_FREE_EOFBLOCKS to keep the stack variables out of xfs_file_ioctl and to clean up the main ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl.c | 52 ++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index a226b33a74b517..10673cf7f2d6fd 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1369,6 +1369,33 @@ xfs_ioc_error_injection( return xfs_errortag_add(mp, in.errtag); } +static int +xfs_ioc_free_eofblocks( + struct xfs_mount *mp, + struct xfs_fs_eofblocks __user *arg) +{ + struct xfs_fs_eofblocks eofb; + struct xfs_icwalk icw; + int error; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (xfs_is_readonly(mp)) + return -EROFS; + + if (copy_from_user(&eofb, arg, sizeof(eofb))) + return -EFAULT; + + error = xfs_fs_eofblocks_from_user(&eofb, &icw); + if (error) + return error; + + trace_xfs_ioc_free_eofblocks(mp, &icw, _RET_IP_); + + guard(super_write)(mp->m_super); + return xfs_blockgc_free_space(mp, &icw); +} + /* * These long-unused ioctls were removed from the official ioctl API in 5.17, * but retain these definitions so that we can log warnings about them. @@ -1388,7 +1415,6 @@ xfs_file_ioctl( struct xfs_inode *ip = XFS_I(inode); struct xfs_mount *mp = ip->i_mount; void __user *arg = (void __user *)p; - int error; trace_xfs_file_ioctl(ip); @@ -1494,28 +1520,8 @@ xfs_file_ioctl( return -EPERM; return xfs_errortag_clearall(mp); - case XFS_IOC_FREE_EOFBLOCKS: { - struct xfs_fs_eofblocks eofb; - struct xfs_icwalk icw; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - if (xfs_is_readonly(mp)) - return -EROFS; - - if (copy_from_user(&eofb, arg, sizeof(eofb))) - return -EFAULT; - - error = xfs_fs_eofblocks_from_user(&eofb, &icw); - if (error) - return error; - - trace_xfs_ioc_free_eofblocks(mp, &icw, _RET_IP_); - - guard(super_write)(mp->m_super); - return xfs_blockgc_free_space(mp, &icw); - } + case XFS_IOC_FREE_EOFBLOCKS: + return xfs_ioc_free_eofblocks(mp, arg); case XFS_IOC_EXCHANGE_RANGE: return xfs_ioc_exchange_range(filp, arg); From 88879b56264f5144fdf89d510bfa618459af2c34 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:11 +0200 Subject: [PATCH 1337/2286] xfs: split out the handlers for XFS_IOC_FSGROWFS_*_32 Split out helpers for XFS_IOC_FSGROWFS_*_32 to keep the stack variables out of xfs_file_compat_ioctl and to clean up the main compat ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 76 ++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index 250df2bbf21417..2162eda017430f 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -44,26 +44,46 @@ xfs_compat_ioc_fsgeometry_v1( return 0; } -STATIC int -xfs_compat_growfs_data_copyin( - struct xfs_growfs_data *in, - compat_xfs_growfs_data_t __user *arg32) +static int +xfs_compat_ioc_growfs_data( + struct file *file, + struct xfs_mount *mp, + struct compat_xfs_growfs_data __user *arg32) { - if (get_user(in->newblocks, &arg32->newblocks) || - get_user(in->imaxpct, &arg32->imaxpct)) + struct xfs_growfs_data in = { }; + int error; + + if (get_user(in.newblocks, &arg32->newblocks) || + get_user(in.imaxpct, &arg32->imaxpct)) return -EFAULT; - return 0; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_growfs_data(mp, &in); + mnt_drop_write_file(file); + return error; } -STATIC int -xfs_compat_growfs_rt_copyin( - struct xfs_growfs_rt *in, - compat_xfs_growfs_rt_t __user *arg32) +static int +xfs_compat_ioc_growfs_rt( + struct file *file, + struct xfs_mount *mp, + struct compat_xfs_growfs_rt __user *arg32) { - if (get_user(in->newblocks, &arg32->newblocks) || - get_user(in->extsize, &arg32->extsize)) + struct xfs_growfs_rt in = {}; + int error; + + if (get_user(in.newblocks, &arg32->newblocks) || + get_user(in.extsize, &arg32->extsize)) return -EFAULT; - return 0; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_growfs_rt(mp, &in); + mnt_drop_write_file(file); + return error; } STATIC int @@ -434,30 +454,10 @@ xfs_file_compat_ioctl( #if defined(BROKEN_X86_ALIGNMENT) case XFS_IOC_FSGEOMETRY_V1_32: return xfs_compat_ioc_fsgeometry_v1(ip->i_mount, arg); - case XFS_IOC_FSGROWFSDATA_32: { - struct xfs_growfs_data in; - - if (xfs_compat_growfs_data_copyin(&in, arg)) - return -EFAULT; - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_growfs_data(ip->i_mount, &in); - mnt_drop_write_file(filp); - return error; - } - case XFS_IOC_FSGROWFSRT_32: { - struct xfs_growfs_rt in; - - if (xfs_compat_growfs_rt_copyin(&in, arg)) - return -EFAULT; - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_growfs_rt(ip->i_mount, &in); - mnt_drop_write_file(filp); - return error; - } + case XFS_IOC_FSGROWFSDATA_32: + return xfs_compat_ioc_growfs_data(filp, ip->i_mount, arg); + case XFS_IOC_FSGROWFSRT_32: + return xfs_compat_ioc_growfs_rt(filp, ip->i_mount, arg); #endif /* long changes size, but xfs only copiese out 32 bits */ case XFS_IOC_GETVERSION_32: From 9deaf8c5f3fe34f2e28496a32811fa4f33328490 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:12 +0200 Subject: [PATCH 1338/2286] xfs: cleanup XFS_IOC_GETVERSION_32 handling Move the cmd assignment into the function call, fix a spelling error in the comment and move the comment next to the call. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index 2162eda017430f..ede890d4cbc38c 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -459,10 +459,9 @@ xfs_file_compat_ioctl( case XFS_IOC_FSGROWFSRT_32: return xfs_compat_ioc_growfs_rt(filp, ip->i_mount, arg); #endif - /* long changes size, but xfs only copiese out 32 bits */ case XFS_IOC_GETVERSION_32: - cmd = _NATIVE_IOC(cmd, long); - return xfs_file_ioctl(filp, cmd, p); + /* long changes size, but xfs only copies out 32 bits */ + return xfs_file_ioctl(filp, _NATIVE_IOC(cmd, long), p); case XFS_IOC_SWAPEXT_32: { struct xfs_swapext sxp; struct compat_xfs_swapext __user *sxu = arg; From b547092c957ea896eb6642e9f388cb1166215a18 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:13 +0200 Subject: [PATCH 1339/2286] xfs: split out the handlers for XFS_IOC_SWAPEXT_32 Split out a helper for XFS_IOC_SWAPEXT_32 to keep the stack variables out of xfs_file_compat_ioctl and to clean up the main compat ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index ede890d4cbc38c..a6e3b35db6e20b 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -158,6 +158,27 @@ xfs_ioctl32_bstat_copyin( return 0; } +static int +xfs_compat_ioc_swapext( + struct file *file, + struct compat_xfs_swapext __user *sxu) +{ + struct xfs_swapext sxp; + int error; + + /* Bulk copy in up to the sx_stat field, then copy bstat */ + if (copy_from_user(&sxp, sxu, offsetof(struct xfs_swapext, sx_stat)) || + xfs_ioctl32_bstat_copyin(&sxp.sx_stat, &sxu->sx_stat)) + return -EFAULT; + + error = mnt_want_write_file(file); + if (error) + return error; + error = xfs_swapext(&sxp); + mnt_drop_write_file(file); + return error; +} + /* XFS_IOC_FSBULKSTAT and friends */ STATIC int @@ -446,7 +467,6 @@ xfs_file_compat_ioctl( struct inode *inode = file_inode(filp); struct xfs_inode *ip = XFS_I(inode); void __user *arg = compat_ptr(p); - int error; trace_xfs_file_compat_ioctl(ip); @@ -462,22 +482,8 @@ xfs_file_compat_ioctl( case XFS_IOC_GETVERSION_32: /* long changes size, but xfs only copies out 32 bits */ return xfs_file_ioctl(filp, _NATIVE_IOC(cmd, long), p); - case XFS_IOC_SWAPEXT_32: { - struct xfs_swapext sxp; - struct compat_xfs_swapext __user *sxu = arg; - - /* Bulk copy in up to the sx_stat field, then copy bstat */ - if (copy_from_user(&sxp, sxu, - offsetof(struct xfs_swapext, sx_stat)) || - xfs_ioctl32_bstat_copyin(&sxp.sx_stat, &sxu->sx_stat)) - return -EFAULT; - error = mnt_want_write_file(filp); - if (error) - return error; - error = xfs_swapext(&sxp); - mnt_drop_write_file(filp); - return error; - } + case XFS_IOC_SWAPEXT_32: + return xfs_compat_ioc_swapext(filp, arg); case XFS_IOC_FSBULKSTAT_32: case XFS_IOC_FSBULKSTAT_SINGLE_32: case XFS_IOC_FSINUMBERS_32: From 325506469b6409ba52d2b3b44c751166f42bd710 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:14 +0200 Subject: [PATCH 1340/2286] xfs: split out the handlers for XFS_IOC_*_BY_HANDLE_32 Split out helpers for XFS_IOC_*_BY_HANDLE_32 to keep the stack variables out of xfs_file_compat_ioctl and to clean up the main compat ioctl handler flow. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 65 +++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index a6e3b35db6e20b..688eb3300495d6 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -379,6 +379,43 @@ xfs_compat_handlereq_to_dentry( compat_ptr(hreq->ihandle), hreq->ihandlen); } +static int +xfs_compat_ioc_find_handle( + unsigned int cmd, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (xfs_compat_handlereq_copyin(&hreq, arg)) + return -EFAULT; + return xfs_find_handle(_NATIVE_IOC(cmd, struct xfs_fsop_handlereq), + &hreq); +} + +static int +xfs_compat_ioc_open_by_handle( + struct file *file, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (xfs_compat_handlereq_copyin(&hreq, arg)) + return -EFAULT; + return xfs_open_by_handle(file, &hreq); +} + +static int +xfs_compat_ioc_readlink_by_handle( + struct file *file, + void __user *arg) +{ + struct xfs_fsop_handlereq hreq; + + if (xfs_compat_handlereq_copyin(&hreq, arg)) + return -EFAULT; + return xfs_readlink_by_handle(file, &hreq); +} + STATIC int xfs_compat_attrlist_by_handle( struct file *parfilp, @@ -490,28 +527,12 @@ xfs_file_compat_ioctl( return xfs_compat_ioc_fsbulkstat(filp, cmd, arg); case XFS_IOC_FD_TO_HANDLE_32: case XFS_IOC_PATH_TO_HANDLE_32: - case XFS_IOC_PATH_TO_FSHANDLE_32: { - struct xfs_fsop_handlereq hreq; - - if (xfs_compat_handlereq_copyin(&hreq, arg)) - return -EFAULT; - cmd = _NATIVE_IOC(cmd, struct xfs_fsop_handlereq); - return xfs_find_handle(cmd, &hreq); - } - case XFS_IOC_OPEN_BY_HANDLE_32: { - struct xfs_fsop_handlereq hreq; - - if (xfs_compat_handlereq_copyin(&hreq, arg)) - return -EFAULT; - return xfs_open_by_handle(filp, &hreq); - } - case XFS_IOC_READLINK_BY_HANDLE_32: { - struct xfs_fsop_handlereq hreq; - - if (xfs_compat_handlereq_copyin(&hreq, arg)) - return -EFAULT; - return xfs_readlink_by_handle(filp, &hreq); - } + case XFS_IOC_PATH_TO_FSHANDLE_32: + return xfs_compat_ioc_find_handle(cmd, arg); + case XFS_IOC_OPEN_BY_HANDLE_32: + return xfs_compat_ioc_open_by_handle(filp, arg); + case XFS_IOC_READLINK_BY_HANDLE_32: + return xfs_compat_ioc_readlink_by_handle(filp, arg); case XFS_IOC_ATTRLIST_BY_HANDLE_32: return xfs_compat_attrlist_by_handle(filp, arg); case XFS_IOC_ATTRMULTI_BY_HANDLE_32: From c1aa787691000432d957cdabb6bbd59cf9d6017e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 29 Jul 2026 15:03:15 +0200 Subject: [PATCH 1341/2286] xfs: remove an extra cast in xfs_file_compat_ioctl The ioctl argument is already available as an unsigned long in the p variable, so use that directly instead of casting back from p, which has the same value but was casted to a void pointer before. Signed-off-by: Christoph Hellwig Reviewed-by: "Darrick J. Wong" Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioctl32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_ioctl32.c b/fs/xfs/xfs_ioctl32.c index 688eb3300495d6..f6875a2cc7065d 100644 --- a/fs/xfs/xfs_ioctl32.c +++ b/fs/xfs/xfs_ioctl32.c @@ -539,6 +539,6 @@ xfs_file_compat_ioctl( return xfs_compat_attrmulti_by_handle(filp, arg); default: /* try the native version */ - return xfs_file_ioctl(filp, cmd, (unsigned long)arg); + return xfs_file_ioctl(filp, cmd, p); } } From 85057d98b37d10460863b1265373718f737abc59 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:47 -0500 Subject: [PATCH 1342/2286] xfs: remove unused args argument from xfs_attr_node_lookup() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/xfs/libxfs/xfs_attr.c b/fs/xfs/libxfs/xfs_attr.c index b3f7b2c34ad766..8fb3adc3dd2d14 100644 --- a/fs/xfs/libxfs/xfs_attr.c +++ b/fs/xfs/libxfs/xfs_attr.c @@ -59,8 +59,7 @@ STATIC void xfs_attr_restore_rmt_blk(struct xfs_da_args *args); static int xfs_attr_node_try_addname(struct xfs_attr_intent *attr); STATIC int xfs_attr_node_addname_find_attr(struct xfs_attr_intent *attr); STATIC int xfs_attr_node_remove_attr(struct xfs_attr_intent *attr); -STATIC int xfs_attr_node_lookup(struct xfs_da_args *args, - struct xfs_da_state *state); +STATIC int xfs_attr_node_lookup(struct xfs_da_state *state); int xfs_inode_hasattr( @@ -709,7 +708,7 @@ int xfs_attr_node_removename_setup( int error; xfs_attr_item_init_da_state(attr); - error = xfs_attr_node_lookup(args, attr->xattri_da_state); + error = xfs_attr_node_lookup(attr->xattri_da_state); if (error != -EEXIST) goto out; error = 0; @@ -985,7 +984,7 @@ xfs_attr_lookup( } state = xfs_da_state_alloc(args); - error = xfs_attr_node_lookup(args, state); + error = xfs_attr_node_lookup(state); xfs_da_state_free(state); return error; } @@ -1386,7 +1385,6 @@ xfs_attr_leaf_get( /* Return EEXIST if attr is found, or ENOATTR if not. */ STATIC int xfs_attr_node_lookup( - struct xfs_da_args *args, struct xfs_da_state *state) { int retval, error; @@ -1417,7 +1415,7 @@ xfs_attr_node_addname_find_attr( * to where it should go. */ xfs_attr_item_init_da_state(attr); - error = xfs_attr_node_lookup(args, attr->xattri_da_state); + error = xfs_attr_node_lookup(attr->xattri_da_state); switch (error) { case -ENOATTR: if (args->op_flags & XFS_DA_OP_REPLACE) @@ -1588,7 +1586,7 @@ xfs_attr_node_get( * Search to see if name exists, and get back a pointer to it. */ state = xfs_da_state_alloc(args); - error = xfs_attr_node_lookup(args, state); + error = xfs_attr_node_lookup(state); if (error != -EEXIST) goto out_release; From fddd62f65beeee4ffeb719baecd0ce0a9728366d Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:48 -0500 Subject: [PATCH 1343/2286] xfs: remove unused rsvd argument from xfs_bmap_add_attrfork() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr.c | 2 +- fs/xfs/libxfs/xfs_bmap.c | 3 +-- fs/xfs/libxfs/xfs_bmap.h | 2 +- fs/xfs/scrub/inode_repair.c | 2 +- fs/xfs/scrub/orphanage.c | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/xfs/libxfs/xfs_attr.c b/fs/xfs/libxfs/xfs_attr.c index 8fb3adc3dd2d14..c4bb59633ce856 100644 --- a/fs/xfs/libxfs/xfs_attr.c +++ b/fs/xfs/libxfs/xfs_attr.c @@ -1013,7 +1013,7 @@ xfs_attr_add_fork( if (xfs_inode_has_attr_fork(ip)) goto trans_cancel; - error = xfs_bmap_add_attrfork(tp, ip, size, rsvd); + error = xfs_bmap_add_attrfork(tp, ip, size); if (error) goto trans_cancel; diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index d64defeda645ed..ca147dfaed05b4 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -1026,8 +1026,7 @@ int /* error code */ xfs_bmap_add_attrfork( struct xfs_trans *tp, struct xfs_inode *ip, /* incore inode pointer */ - int size, /* space new attribute needs */ - int rsvd) /* xact may use reserved blks */ + int size) /* space new attribute needs */ { struct xfs_mount *mp = tp->t_mountp; int logflags; /* logging flags */ diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index d5f2729305fada..60f6df4ce0579a 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -181,7 +181,7 @@ void xfs_trim_extent(struct xfs_bmbt_irec *irec, xfs_fileoff_t bno, xfs_filblks_t len); unsigned int xfs_bmap_compute_attr_offset(struct xfs_mount *mp); int xfs_bmap_add_attrfork(struct xfs_trans *tp, struct xfs_inode *ip, - int size, int rsvd); + int size); void xfs_bmap_local_to_extents_empty(struct xfs_trans *tp, struct xfs_inode *ip, int whichfork); int xfs_bmap_local_to_extents(struct xfs_trans *tp, struct xfs_inode *ip, diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index fab4015f6a9506..b140174744f7a2 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1973,7 +1973,7 @@ xrep_inode_pptr( return 0; return xfs_bmap_add_attrfork(sc->tp, ip, - sizeof(struct xfs_attr_sf_hdr), true); + sizeof(struct xfs_attr_sf_hdr)); } /* Fix COW extent size hint problems. */ diff --git a/fs/xfs/scrub/orphanage.c b/fs/xfs/scrub/orphanage.c index 21e31eeaa042f9..d8e1457f0d5e1a 100644 --- a/fs/xfs/scrub/orphanage.c +++ b/fs/xfs/scrub/orphanage.c @@ -550,7 +550,7 @@ xrep_adoption_move( if (!xfs_inode_has_attr_fork(sc->ip) && xfs_has_parent(sc->mp)) { int sf_size = xrep_adoption_attr_sizeof(adopt); - error = xfs_bmap_add_attrfork(sc->tp, sc->ip, sf_size, true); + error = xfs_bmap_add_attrfork(sc->tp, sc->ip, sf_size); if (error) return error; } From ff772b0058369bc4d9151158b06750b7d0c2e867 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:49 -0500 Subject: [PATCH 1344/2286] xfs: remove unused mp and ops arguments from xfbtree_rec_bytes() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_btree_mem.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_btree_mem.c b/fs/xfs/libxfs/xfs_btree_mem.c index 1d83a4251ceeb6..3d5c7a014d4fff 100644 --- a/fs/xfs/libxfs/xfs_btree_mem.c +++ b/fs/xfs/libxfs/xfs_btree_mem.c @@ -73,9 +73,7 @@ xfbtree_destroy( /* Compute the number of bytes available for records. */ static inline unsigned int -xfbtree_rec_bytes( - struct xfs_mount *mp, - const struct xfs_btree_ops *ops) +xfbtree_rec_bytes(void) { return XMBUF_BLOCKSIZE - XFS_BTREE_LBLOCK_CRC_LEN; } @@ -118,7 +116,7 @@ xfbtree_init( const struct xfs_btree_ops *ops) { unsigned long long owner = xfbt->owner; - unsigned int blocklen = xfbtree_rec_bytes(mp, ops); + unsigned int blocklen = xfbtree_rec_bytes(); unsigned int keyptr_len; int error; From eae899c91e67067dfd9d39a0a8e9f92d378eed96 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:50 -0500 Subject: [PATCH 1345/2286] xfs: remove unused mp argument from xfs_exchmaps_check_forks() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_exchmaps.c | 1 - fs/xfs/libxfs/xfs_exchmaps.h | 3 +-- fs/xfs/xfs_exchrange.c | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c index 6a66b6075e0af4..ccb97da3765f22 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.c +++ b/fs/xfs/libxfs/xfs_exchmaps.c @@ -135,7 +135,6 @@ xmi_has_postop_work(const struct xfs_exchmaps_intent *xmi) /* Check all mappings to make sure we can actually exchange them. */ int xfs_exchmaps_check_forks( - struct xfs_mount *mp, const struct xfs_exchmaps_req *req) { struct xfs_ifork *ifp1, *ifp2; diff --git a/fs/xfs/libxfs/xfs_exchmaps.h b/fs/xfs/libxfs/xfs_exchmaps.h index fa822dff202ade..055b8dbabd5bc1 100644 --- a/fs/xfs/libxfs/xfs_exchmaps.h +++ b/fs/xfs/libxfs/xfs_exchmaps.h @@ -115,8 +115,7 @@ void xfs_exchmaps_upgrade_extent_counts(struct xfs_trans *tp, int xfs_exchmaps_finish_one(struct xfs_trans *tp, struct xfs_exchmaps_intent *xmi); -int xfs_exchmaps_check_forks(struct xfs_mount *mp, - const struct xfs_exchmaps_req *req); +int xfs_exchmaps_check_forks(const struct xfs_exchmaps_req *req); void xfs_exchange_mappings(struct xfs_trans *tp, const struct xfs_exchmaps_req *req); diff --git a/fs/xfs/xfs_exchrange.c b/fs/xfs/xfs_exchrange.c index fafb4e3f065c75..a1001315d4a7bf 100644 --- a/fs/xfs/xfs_exchrange.c +++ b/fs/xfs/xfs_exchrange.c @@ -238,7 +238,7 @@ xfs_exchrange_mappings( trace_xfs_exchrange_before(ip2, 2); trace_xfs_exchrange_before(ip1, 1); - error = xfs_exchmaps_check_forks(mp, &req); + error = xfs_exchmaps_check_forks(&req); if (error) goto out_trans_cancel; From 43ded2832aad8d9f7517ff806f78331fd484b689 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:51 -0500 Subject: [PATCH 1346/2286] xfs: remove unused mp argument from xfs_inobt_rec_check_count() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ialloc.c | 2 +- fs/xfs/libxfs/xfs_ialloc_btree.c | 1 - fs/xfs/libxfs/xfs_ialloc_btree.h | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index 58dac4d505ba63..19b513b1169269 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -615,7 +615,7 @@ xfs_inobt_insert_sprec( trace_xfs_irec_merge_post(pag, nrec); - error = xfs_inobt_rec_check_count(mp, nrec); + error = xfs_inobt_rec_check_count(nrec); if (error) goto error; diff --git a/fs/xfs/libxfs/xfs_ialloc_btree.c b/fs/xfs/libxfs/xfs_ialloc_btree.c index 1376e8630449af..1f0bace2f144ee 100644 --- a/fs/xfs/libxfs/xfs_ialloc_btree.c +++ b/fs/xfs/libxfs/xfs_ialloc_btree.c @@ -687,7 +687,6 @@ xfs_inobt_irec_to_allocmask( */ int xfs_inobt_rec_check_count( - struct xfs_mount *mp, struct xfs_inobt_rec_incore *rec) { int inocount = 0; diff --git a/fs/xfs/libxfs/xfs_ialloc_btree.h b/fs/xfs/libxfs/xfs_ialloc_btree.h index 300edf5bc00949..e04c63c66f397f 100644 --- a/fs/xfs/libxfs/xfs_ialloc_btree.h +++ b/fs/xfs/libxfs/xfs_ialloc_btree.h @@ -57,10 +57,9 @@ unsigned int xfs_inobt_maxrecs(struct xfs_mount *mp, unsigned int blocklen, uint64_t xfs_inobt_irec_to_allocmask(const struct xfs_inobt_rec_incore *irec); #if defined(DEBUG) || defined(XFS_WARN) -int xfs_inobt_rec_check_count(struct xfs_mount *, - struct xfs_inobt_rec_incore *); +int xfs_inobt_rec_check_count(struct xfs_inobt_rec_incore *); #else -#define xfs_inobt_rec_check_count(mp, rec) 0 +#define xfs_inobt_rec_check_count(rec) 0 #endif /* DEBUG */ int xfs_finobt_calc_reserves(struct xfs_perag *perag, struct xfs_trans *tp, From 0477212922924bd39d201e7813293469d927d125 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:52 -0500 Subject: [PATCH 1347/2286] xfs: remove unused mp argument from xfs_parent_finish() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_metadir.c | 2 +- fs/xfs/libxfs/xfs_parent.h | 1 - fs/xfs/xfs_inode.c | 18 +++++++++--------- fs/xfs/xfs_symlink.c | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/fs/xfs/libxfs/xfs_metadir.c b/fs/xfs/libxfs/xfs_metadir.c index 1ff26e55b1864f..79b37cb272734b 100644 --- a/fs/xfs/libxfs/xfs_metadir.c +++ b/fs/xfs/libxfs/xfs_metadir.c @@ -163,7 +163,7 @@ xfs_metadir_teardown( trace_xfs_metadir_teardown(upd, error); if (upd->ppargs) { - xfs_parent_finish(upd->dp->i_mount, upd->ppargs); + xfs_parent_finish(upd->ppargs); upd->ppargs = NULL; } diff --git a/fs/xfs/libxfs/xfs_parent.h b/fs/xfs/libxfs/xfs_parent.h index 8eb4de9c5f1a9c..1dd3968a78b1ef 100644 --- a/fs/xfs/libxfs/xfs_parent.h +++ b/fs/xfs/libxfs/xfs_parent.h @@ -72,7 +72,6 @@ xfs_parent_start( /* Finish a parent pointer update by freeing the context object. */ static inline void xfs_parent_finish( - struct xfs_mount *mp, struct xfs_parent_args *ppargs) { if (ppargs) diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 621513d7215eff..9165b161f0c662 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -755,7 +755,7 @@ xfs_create( *ipp = du.ip; xfs_iunlock(du.ip, XFS_ILOCK_EXCL); xfs_iunlock(dp, XFS_ILOCK_EXCL); - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); return 0; out_trans_cancel: @@ -772,7 +772,7 @@ xfs_create( xfs_irele(du.ip); } out_parent: - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); out_release_dquots: xfs_qm_dqrele(udqp); xfs_qm_dqrele(gdqp); @@ -972,7 +972,7 @@ xfs_link( error = xfs_trans_commit(tp); xfs_iunlock(tdp, XFS_ILOCK_EXCL); xfs_iunlock(sip, XFS_ILOCK_EXCL); - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); return error; error_return: @@ -980,7 +980,7 @@ xfs_link( xfs_iunlock(tdp, XFS_ILOCK_EXCL); xfs_iunlock(sip, XFS_ILOCK_EXCL); out_parent: - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); std_return: if (error == -ENOSPC && nospace_error) error = nospace_error; @@ -1985,7 +1985,7 @@ xfs_remove( xfs_iunlock(ip, XFS_ILOCK_EXCL); xfs_iunlock(dp, XFS_ILOCK_EXCL); - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); return 0; out_trans_cancel: @@ -1994,7 +1994,7 @@ xfs_remove( xfs_iunlock(ip, XFS_ILOCK_EXCL); xfs_iunlock(dp, XFS_ILOCK_EXCL); out_parent: - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); std_return: return error; } @@ -2357,11 +2357,11 @@ xfs_rename( out_unlock: xfs_iunlock_rename(inodes, num_inodes); out_tgt_ppargs: - xfs_parent_finish(mp, du_tgt.ppargs); + xfs_parent_finish(du_tgt.ppargs); out_wip_ppargs: - xfs_parent_finish(mp, du_wip.ppargs); + xfs_parent_finish(du_wip.ppargs); out_src_ppargs: - xfs_parent_finish(mp, du_src.ppargs); + xfs_parent_finish(du_src.ppargs); out_release_wip: if (du_wip.ip) xfs_irele(du_wip.ip); diff --git a/fs/xfs/xfs_symlink.c b/fs/xfs/xfs_symlink.c index 5585ac7f4d16bf..cc13819df6f258 100644 --- a/fs/xfs/xfs_symlink.c +++ b/fs/xfs/xfs_symlink.c @@ -219,7 +219,7 @@ xfs_symlink( *ipp = du.ip; xfs_iunlock(du.ip, XFS_ILOCK_EXCL); xfs_iunlock(dp, XFS_ILOCK_EXCL); - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); return 0; out_trans_cancel: @@ -236,7 +236,7 @@ xfs_symlink( xfs_irele(du.ip); } out_parent: - xfs_parent_finish(mp, du.ppargs); + xfs_parent_finish(du.ppargs); out_release_dquots: xfs_qm_dqrele(udqp); xfs_qm_dqrele(gdqp); From de8ddee448b89c20199e74fdb306c67a7163331d Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:53 -0500 Subject: [PATCH 1348/2286] xfs: remove unused tp argument from xfs_rmap_update_hook() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rmap.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rmap.c b/fs/xfs/libxfs/xfs_rmap.c index 34d218de21a956..37780a0526d8ea 100644 --- a/fs/xfs/libxfs/xfs_rmap.c +++ b/fs/xfs/libxfs/xfs_rmap.c @@ -904,7 +904,6 @@ xfs_rmap_hook_enable(void) /* Call downstream hooks for a reverse mapping update. */ static inline void xfs_rmap_update_hook( - struct xfs_trans *tp, struct xfs_group *xg, enum xfs_rmap_intent_type op, xfs_agblock_t startblock, @@ -952,7 +951,7 @@ xfs_rmap_hook_setup( xfs_hook_setup(&hook->rmap_hook, mod_fn); } #else -# define xfs_rmap_update_hook(t, p, o, s, b, u, oi) do { } while (0) +# define xfs_rmap_update_hook(p, o, s, b, u, oi) do { } while (0) #endif /* CONFIG_XFS_LIVE_HOOKS */ /* @@ -975,7 +974,7 @@ xfs_rmap_free( return 0; cur = xfs_rmapbt_init_cursor(mp, tp, agbp, pag); - xfs_rmap_update_hook(tp, pag_group(pag), XFS_RMAP_UNMAP, bno, len, + xfs_rmap_update_hook(pag_group(pag), XFS_RMAP_UNMAP, bno, len, false, oinfo); error = xfs_rmap_unmap(cur, bno, len, false, oinfo); @@ -1220,7 +1219,7 @@ xfs_rmap_alloc( return 0; cur = xfs_rmapbt_init_cursor(mp, tp, agbp, pag); - xfs_rmap_update_hook(tp, pag_group(pag), XFS_RMAP_MAP, bno, len, false, + xfs_rmap_update_hook(pag_group(pag), XFS_RMAP_MAP, bno, len, false, oinfo); error = xfs_rmap_map(cur, bno, len, false, oinfo); @@ -2721,7 +2720,7 @@ xfs_rmap_finish_one( if (error) return error; - xfs_rmap_update_hook(tp, ri->ri_group, ri->ri_type, bno, + xfs_rmap_update_hook(ri->ri_group, ri->ri_type, bno, ri->ri_bmap.br_blockcount, unwritten, &oinfo); return 0; } From b68d2e88b930fff2f3e15a3f0bf8fc5b2bd18d21 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:54 -0500 Subject: [PATCH 1349/2286] xfs: remove unused mp argument from xfs_rtrefcount_broot_space_calc() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 6 +++--- fs/xfs/libxfs/xfs_rtrefcount_btree.h | 3 +-- fs/xfs/scrub/rtrefcount_repair.c | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index dcc89b8e149b05..7df33b5edfda56 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -311,7 +311,7 @@ xfs_rtrefcountbt_broot_realloc( unsigned int old_size = ifp->if_broot_bytes; const unsigned int level = cur->bc_nlevels - 1; - new_size = xfs_rtrefcount_broot_space_calc(mp, level, new_numrecs); + new_size = xfs_rtrefcount_broot_space_calc(level, new_numrecs); /* Handle the nop case quietly. */ if (new_size == old_size) @@ -661,7 +661,7 @@ xfs_iformat_rtrefcount( } broot = xfs_broot_alloc(xfs_ifork_ptr(ip, XFS_DATA_FORK), - xfs_rtrefcount_broot_space_calc(mp, level, numrecs)); + xfs_rtrefcount_broot_space_calc(level, numrecs)); if (broot) xfs_rtrefcountbt_from_disk(ip, dfp, dsize, broot); return 0; @@ -751,7 +751,7 @@ xfs_rtrefcountbt_create( /* Initialize the empty incore btree root. */ broot = xfs_broot_realloc(ifp, - xfs_rtrefcount_broot_space_calc(mp, 0, 0)); + xfs_rtrefcount_broot_space_calc(0, 0)); if (broot) xfs_btree_init_block(mp, broot, &xfs_rtrefcountbt_ops, 0, 0, I_INO(ip)); diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.h b/fs/xfs/libxfs/xfs_rtrefcount_btree.h index a99b7a8aec8659..aeef004ffdc91e 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.h +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.h @@ -129,7 +129,6 @@ xfs_rtrefcount_broot_ptr_addr( */ static inline size_t xfs_rtrefcount_broot_space_calc( - struct xfs_mount *mp, unsigned int level, unsigned int nrecs) { @@ -148,7 +147,7 @@ xfs_rtrefcount_broot_space_calc( static inline size_t xfs_rtrefcount_broot_space(struct xfs_mount *mp, struct xfs_rtrefcount_root *bb) { - return xfs_rtrefcount_broot_space_calc(mp, be16_to_cpu(bb->bb_level), + return xfs_rtrefcount_broot_space_calc(be16_to_cpu(bb->bb_level), be16_to_cpu(bb->bb_numrecs)); } diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index 2b939960c7ddcd..c78a6d2990c586 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -596,8 +596,7 @@ xrep_rtrefc_iroot_size( unsigned int nr_this_level, void *priv) { - return xfs_rtrefcount_broot_space_calc(cur->bc_mp, level, - nr_this_level); + return xfs_rtrefcount_broot_space_calc(level, nr_this_level); } /* From 7d67c917ec5a138181117238cda47427a6a6f438 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:55 -0500 Subject: [PATCH 1350/2286] xfs: remove unused mp argument from xfs_rtrefcount_broot_space() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrefcount_btree.c | 2 +- fs/xfs/libxfs/xfs_rtrefcount_btree.h | 2 +- fs/xfs/scrub/inode_repair.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index 7df33b5edfda56..e91ff14577f9ad 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -602,7 +602,7 @@ xfs_rtrefcountbt_from_disk( unsigned int maxrecs; unsigned int rblocklen; - rblocklen = xfs_rtrefcount_broot_space(mp, dblock); + rblocklen = xfs_rtrefcount_broot_space(dblock); xfs_btree_init_block(mp, rblock, &xfs_rtrefcountbt_ops, 0, 0, I_INO(ip)); diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.h b/fs/xfs/libxfs/xfs_rtrefcount_btree.h index aeef004ffdc91e..9ab6ecf90ba515 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.h +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.h @@ -145,7 +145,7 @@ xfs_rtrefcount_broot_space_calc( * btree root block. */ static inline size_t -xfs_rtrefcount_broot_space(struct xfs_mount *mp, struct xfs_rtrefcount_root *bb) +xfs_rtrefcount_broot_space(struct xfs_rtrefcount_root *bb) { return xfs_rtrefcount_broot_space_calc(be16_to_cpu(bb->bb_level), be16_to_cpu(bb->bb_numrecs)); diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index b140174744f7a2..c84ef980e891e9 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1429,7 +1429,7 @@ xrep_dinode_ensure_forkoff( break; case XFS_METAFILE_RTREFCOUNT: rcdr = XFS_DFORK_PTR(dip, XFS_DATA_FORK); - dfork_min = xfs_rtrefcount_broot_space(sc->mp, rcdr); + dfork_min = xfs_rtrefcount_broot_space(rcdr); break; default: dfork_min = 0; From 29a4e6856da71e239724ab8e8395a4e08351f94e Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:56 -0500 Subject: [PATCH 1351/2286] xfs: remove unused mp argument from xfs_rtrmap_broot_space_calc() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtrmap_btree.c | 6 +++--- fs/xfs/libxfs/xfs_rtrmap_btree.h | 3 +-- fs/xfs/scrub/rtrmap_repair.c | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index a15e460a1ec7f4..5901d7efd3f676 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -412,7 +412,7 @@ xfs_rtrmapbt_broot_realloc( unsigned int old_size = ifp->if_broot_bytes; const unsigned int level = cur->bc_nlevels - 1; - new_size = xfs_rtrmap_broot_space_calc(mp, level, new_numrecs); + new_size = xfs_rtrmap_broot_space_calc(level, new_numrecs); /* Handle the nop case quietly. */ if (new_size == old_size) @@ -895,7 +895,7 @@ xfs_iformat_rtrmap( } broot = xfs_broot_alloc(xfs_ifork_ptr(ip, XFS_DATA_FORK), - xfs_rtrmap_broot_space_calc(mp, level, numrecs)); + xfs_rtrmap_broot_space_calc(level, numrecs)); if (broot) xfs_rtrmapbt_from_disk(ip, dfp, dsize, broot); return 0; @@ -980,7 +980,7 @@ xfs_rtrmapbt_create( ASSERT(ifp->if_bytes == 0); /* Initialize the empty incore btree root. */ - broot = xfs_broot_realloc(ifp, xfs_rtrmap_broot_space_calc(mp, 0, 0)); + broot = xfs_broot_realloc(ifp, xfs_rtrmap_broot_space_calc(0, 0)); if (broot) xfs_btree_init_block(mp, broot, &xfs_rtrmapbt_ops, 0, 0, I_INO(ip)); diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.h b/fs/xfs/libxfs/xfs_rtrmap_btree.h index e328fd62a149d1..c59a144b4bbfbe 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.h +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.h @@ -140,7 +140,6 @@ xfs_rtrmap_broot_ptr_addr( */ static inline size_t xfs_rtrmap_broot_space_calc( - struct xfs_mount *mp, unsigned int level, unsigned int nrecs) { @@ -159,7 +158,7 @@ xfs_rtrmap_broot_space_calc( static inline size_t xfs_rtrmap_broot_space(struct xfs_mount *mp, struct xfs_rtrmap_root *bb) { - return xfs_rtrmap_broot_space_calc(mp, be16_to_cpu(bb->bb_level), + return xfs_rtrmap_broot_space_calc(be16_to_cpu(bb->bb_level), be16_to_cpu(bb->bb_numrecs)); } diff --git a/fs/xfs/scrub/rtrmap_repair.c b/fs/xfs/scrub/rtrmap_repair.c index a2b72e61edf5fe..5cfa4470c57c0f 100644 --- a/fs/xfs/scrub/rtrmap_repair.c +++ b/fs/xfs/scrub/rtrmap_repair.c @@ -693,7 +693,7 @@ xrep_rtrmap_iroot_size( unsigned int nr_this_level, void *priv) { - return xfs_rtrmap_broot_space_calc(cur->bc_mp, level, nr_this_level); + return xfs_rtrmap_broot_space_calc(level, nr_this_level); } /* From 560e444e61944117819b3c23b141d1a6a53f3be7 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:57 -0500 Subject: [PATCH 1352/2286] xfs: remove unused mp argument from xfs_calc_default_atomic_ioend_reservation() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_trans_resv.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c index 1c20a7c27aa1bf..b09c88aec185ea 100644 --- a/fs/xfs/libxfs/xfs_trans_resv.c +++ b/fs/xfs/libxfs/xfs_trans_resv.c @@ -1294,7 +1294,6 @@ xfs_calc_namespace_reservations( STATIC void xfs_calc_default_atomic_ioend_reservation( - struct xfs_mount *mp, struct xfs_trans_resv *resp) { /* Pick a default that will scale reasonably for the log size. */ @@ -1400,7 +1399,7 @@ xfs_trans_resv_calc( * Now that we've finished computing the static reservations, we can * compute the dynamic reservation for atomic writes. */ - xfs_calc_default_atomic_ioend_reservation(mp, resp); + xfs_calc_default_atomic_ioend_reservation(resp); } /* @@ -1510,7 +1509,7 @@ xfs_calc_atomic_write_log_geometry( ASSERT(blockcount > 0); - xfs_calc_default_atomic_ioend_reservation(mp, M_RES(mp)); + xfs_calc_default_atomic_ioend_reservation(M_RES(mp)); per_intent = xfs_calc_atomic_write_ioend_geometry(mp, &step_size); @@ -1547,7 +1546,7 @@ xfs_calc_atomic_write_reservation( * use the defaults. */ if (blockcount == 0) { - xfs_calc_default_atomic_ioend_reservation(mp, M_RES(mp)); + xfs_calc_default_atomic_ioend_reservation(M_RES(mp)); return 0; } From c06c1e1b886a7deaa47bd4d89dae6e91f86318cb Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:58 -0500 Subject: [PATCH 1353/2286] xfs: remove unused mp argument from xfs_verify_dablk() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_types.c | 1 - fs/xfs/libxfs/xfs_types.h | 2 +- fs/xfs/scrub/bmap.c | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_types.c b/fs/xfs/libxfs/xfs_types.c index 67c947a47f1478..b5b0b6334d111a 100644 --- a/fs/xfs/libxfs/xfs_types.c +++ b/fs/xfs/libxfs/xfs_types.c @@ -222,7 +222,6 @@ xfs_verify_icount( /* Sanity-checking of dir/attr block offsets. */ bool xfs_verify_dablk( - struct xfs_mount *mp, xfs_fileoff_t dabno) { xfs_dablk_t max_dablk = -1U; diff --git a/fs/xfs/libxfs/xfs_types.h b/fs/xfs/libxfs/xfs_types.h index f6f4f2d4b5dbf5..d217220d2054d4 100644 --- a/fs/xfs/libxfs/xfs_types.h +++ b/fs/xfs/libxfs/xfs_types.h @@ -277,7 +277,7 @@ bool xfs_verify_rtbno(struct xfs_mount *mp, xfs_rtblock_t rtbno); bool xfs_verify_rtbext(struct xfs_mount *mp, xfs_rtblock_t rtbno, xfs_filblks_t len); bool xfs_verify_icount(struct xfs_mount *mp, unsigned long long icount); -bool xfs_verify_dablk(struct xfs_mount *mp, xfs_fileoff_t off); +bool xfs_verify_dablk(xfs_fileoff_t off); void xfs_icount_range(struct xfs_mount *mp, unsigned long long *min, unsigned long long *max); bool xfs_verify_fileoff(struct xfs_mount *mp, xfs_fileoff_t off); diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 4f3c7f681bd921..868f502a44f2cf 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -453,18 +453,17 @@ xchk_bmap_dirattr_extent( struct xchk_bmap_info *info, struct xfs_bmbt_irec *irec) { - struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t off; if (!S_ISDIR(VFS_I(ip)->i_mode) && info->whichfork != XFS_ATTR_FORK) return; - if (!xfs_verify_dablk(mp, irec->br_startoff)) + if (!xfs_verify_dablk(irec->br_startoff)) xchk_fblock_set_corrupt(info->sc, info->whichfork, irec->br_startoff); off = irec->br_startoff + irec->br_blockcount - 1; - if (!xfs_verify_dablk(mp, off)) + if (!xfs_verify_dablk(off)) xchk_fblock_set_corrupt(info->sc, info->whichfork, off); } From 00139cc68ac3f4008441d900d3f27f9317804d2c Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:28:59 -0500 Subject: [PATCH 1354/2286] xfs: remove unused mp argument from xfs_verify_fileoff() Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_types.c | 5 ++--- fs/xfs/libxfs/xfs_types.h | 2 +- fs/xfs/scrub/inode_repair.c | 2 +- fs/xfs/scrub/quota.c | 2 +- fs/xfs/scrub/quota_repair.c | 4 ++-- fs/xfs/xfs_inode.c | 2 +- fs/xfs/xfs_super.c | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/fs/xfs/libxfs/xfs_types.c b/fs/xfs/libxfs/xfs_types.c index b5b0b6334d111a..32e6c0aaf46703 100644 --- a/fs/xfs/libxfs/xfs_types.c +++ b/fs/xfs/libxfs/xfs_types.c @@ -232,7 +232,6 @@ xfs_verify_dablk( /* Check that a file block offset does not exceed the maximum. */ bool xfs_verify_fileoff( - struct xfs_mount *mp, xfs_fileoff_t off) { return off <= XFS_MAX_FILEOFF; @@ -248,8 +247,8 @@ xfs_verify_fileext( if (off + len <= off) return false; - if (!xfs_verify_fileoff(mp, off)) + if (!xfs_verify_fileoff(off)) return false; - return xfs_verify_fileoff(mp, off + len - 1); + return xfs_verify_fileoff(off + len - 1); } diff --git a/fs/xfs/libxfs/xfs_types.h b/fs/xfs/libxfs/xfs_types.h index d217220d2054d4..adae8114968014 100644 --- a/fs/xfs/libxfs/xfs_types.h +++ b/fs/xfs/libxfs/xfs_types.h @@ -280,7 +280,7 @@ bool xfs_verify_icount(struct xfs_mount *mp, unsigned long long icount); bool xfs_verify_dablk(xfs_fileoff_t off); void xfs_icount_range(struct xfs_mount *mp, unsigned long long *min, unsigned long long *max); -bool xfs_verify_fileoff(struct xfs_mount *mp, xfs_fileoff_t off); +bool xfs_verify_fileoff(xfs_fileoff_t off); bool xfs_verify_fileext(struct xfs_mount *mp, xfs_fileoff_t off, xfs_fileoff_t len); diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index c84ef980e891e9..c65912d8765904 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -933,7 +933,7 @@ xrep_dinode_bad_bmbt_fork( fkp = xfs_bmdr_key_addr(dfp, i); fileoff = be64_to_cpu(fkp->br_startoff); - if (!xfs_verify_fileoff(sc->mp, fileoff)) + if (!xfs_verify_fileoff(fileoff)) return true; fpp = xfs_bmdr_ptr_addr(dfp, i, dmxr); diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c index 222812fe202c21..8c6ba1240fd53a 100644 --- a/fs/xfs/scrub/quota.c +++ b/fs/xfs/scrub/quota.c @@ -89,7 +89,7 @@ xchk_quota_item_bmap( int nmaps = 1; int error; - if (!xfs_verify_fileoff(mp, offset)) { + if (!xfs_verify_fileoff(offset)) { xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); return 0; } diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 89f7ea4f92ef4a..40bb85e8f90927 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -116,8 +116,8 @@ xrep_quota_item_bmap( int error; /* The computed file offset should always be valid. */ - if (!xfs_verify_fileoff(mp, offset)) { - ASSERT(xfs_verify_fileoff(mp, offset)); + if (!xfs_verify_fileoff(offset)) { + ASSERT(xfs_verify_fileoff(offset)); return -EFSCORRUPTED; } dq->q_fileoffset = offset; diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 9165b161f0c662..15b62574b8d486 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -1064,7 +1064,7 @@ xfs_itruncate_extents_flags( * the page cache can't scale that far. */ first_unmap_block = XFS_B_TO_FSB(mp, (xfs_ufsize_t)new_size); - if (!xfs_verify_fileoff(mp, first_unmap_block)) { + if (!xfs_verify_fileoff(first_unmap_block)) { WARN_ON_ONCE(first_unmap_block > XFS_MAX_FILEOFF); return 0; } diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index b24db75eaedc5f..2edc2a4978835f 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -1889,7 +1889,7 @@ xfs_fs_fill_super( * Avoid integer overflow by comparing the maximum bmbt offset to the * maximum pagecache offset in units of fs blocks. */ - if (!xfs_verify_fileoff(mp, XFS_B_TO_FSBT(mp, MAX_LFS_FILESIZE))) { + if (!xfs_verify_fileoff(XFS_B_TO_FSBT(mp, MAX_LFS_FILESIZE))) { xfs_warn(mp, "MAX_LFS_FILESIZE block offset (%llu) exceeds extent map maximum (%llu)!", XFS_B_TO_FSBT(mp, MAX_LFS_FILESIZE), From f0dd4f4d989e645a8fda001a3fb7fe4538a93f08 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Mon, 14 Sep 2026 17:29:00 -0500 Subject: [PATCH 1355/2286] xfs: remove unused mp argument from xfs_verify_fileext() (Follows from last patch) Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_bmap.c | 2 +- fs/xfs/libxfs/xfs_rmap.c | 4 ++-- fs/xfs/libxfs/xfs_types.c | 1 - fs/xfs/libxfs/xfs_types.h | 3 +-- fs/xfs/scrub/bmap.c | 6 ++---- fs/xfs/scrub/bmap_repair.c | 4 ++-- fs/xfs/xfs_bmap_item.c | 2 +- fs/xfs/xfs_exchmaps_item.c | 4 ++-- fs/xfs/xfs_rmap_item.c | 2 +- 9 files changed, 12 insertions(+), 16 deletions(-) diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index ca147dfaed05b4..ae91f63455c5de 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -6087,7 +6087,7 @@ xfs_bmap_validate_extent_raw( int whichfork, struct xfs_bmbt_irec *irec) { - if (!xfs_verify_fileext(mp, irec->br_startoff, irec->br_blockcount)) + if (!xfs_verify_fileext(irec->br_startoff, irec->br_blockcount)) return __this_address; if (rtfile && whichfork == XFS_DATA_FORK) { diff --git a/fs/xfs/libxfs/xfs_rmap.c b/fs/xfs/libxfs/xfs_rmap.c index 37780a0526d8ea..14aef87837a9cc 100644 --- a/fs/xfs/libxfs/xfs_rmap.c +++ b/fs/xfs/libxfs/xfs_rmap.c @@ -260,7 +260,7 @@ xfs_rmap_check_irec( /* Check for a valid fork offset, if applicable. */ if (is_inode && !is_bmbt && - !xfs_verify_fileext(mp, irec->rm_offset, irec->rm_blockcount)) + !xfs_verify_fileext(irec->rm_offset, irec->rm_blockcount)) return __this_address; return NULL; @@ -310,7 +310,7 @@ xfs_rtrmap_check_inode_irec( return __this_address; if (!xfs_verify_rgbext(rtg, irec->rm_startblock, irec->rm_blockcount)) return __this_address; - if (!xfs_verify_fileext(mp, irec->rm_offset, irec->rm_blockcount)) + if (!xfs_verify_fileext(irec->rm_offset, irec->rm_blockcount)) return __this_address; return NULL; } diff --git a/fs/xfs/libxfs/xfs_types.c b/fs/xfs/libxfs/xfs_types.c index 32e6c0aaf46703..f195a04dbf666f 100644 --- a/fs/xfs/libxfs/xfs_types.c +++ b/fs/xfs/libxfs/xfs_types.c @@ -240,7 +240,6 @@ xfs_verify_fileoff( /* Check that a range of file block offsets do not exceed the maximum. */ bool xfs_verify_fileext( - struct xfs_mount *mp, xfs_fileoff_t off, xfs_fileoff_t len) { diff --git a/fs/xfs/libxfs/xfs_types.h b/fs/xfs/libxfs/xfs_types.h index adae8114968014..19dd5e7c8b1279 100644 --- a/fs/xfs/libxfs/xfs_types.h +++ b/fs/xfs/libxfs/xfs_types.h @@ -281,7 +281,6 @@ bool xfs_verify_dablk(xfs_fileoff_t off); void xfs_icount_range(struct xfs_mount *mp, unsigned long long *min, unsigned long long *max); bool xfs_verify_fileoff(xfs_fileoff_t off); -bool xfs_verify_fileext(struct xfs_mount *mp, xfs_fileoff_t off, - xfs_fileoff_t len); +bool xfs_verify_fileext(xfs_fileoff_t off, xfs_fileoff_t len); #endif /* __XFS_TYPES_H__ */ diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 868f502a44f2cf..c190590bc56216 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -485,7 +485,7 @@ xchk_bmap_iextent( xchk_fblock_set_corrupt(info->sc, info->whichfork, irec->br_startoff); - if (!xfs_verify_fileext(mp, irec->br_startoff, irec->br_blockcount)) + if (!xfs_verify_fileext(irec->br_startoff, irec->br_blockcount)) xchk_fblock_set_corrupt(info->sc, info->whichfork, irec->br_startoff); @@ -876,8 +876,6 @@ xchk_bmap_iextent_delalloc( struct xchk_bmap_info *info, struct xfs_bmbt_irec *irec) { - struct xfs_mount *mp = info->sc->mp; - /* * Check for out-of-order extents. This record could have come * from the incore list, for which there is no ordering check. @@ -887,7 +885,7 @@ xchk_bmap_iextent_delalloc( xchk_fblock_set_corrupt(info->sc, info->whichfork, irec->br_startoff); - if (!xfs_verify_fileext(mp, irec->br_startoff, irec->br_blockcount)) + if (!xfs_verify_fileext(irec->br_startoff, irec->br_blockcount)) xchk_fblock_set_corrupt(info->sc, info->whichfork, irec->br_startoff); diff --git a/fs/xfs/scrub/bmap_repair.c b/fs/xfs/scrub/bmap_repair.c index eabffba47776e9..03af6cb92fcf5e 100644 --- a/fs/xfs/scrub/bmap_repair.c +++ b/fs/xfs/scrub/bmap_repair.c @@ -211,7 +211,7 @@ xrep_bmap_check_fork_rmap( /* Check the file offset range. */ if (!(rec->rm_flags & XFS_RMAP_BMBT_BLOCK) && - !xfs_verify_fileext(sc->mp, rec->rm_offset, rec->rm_blockcount)) + !xfs_verify_fileext(rec->rm_offset, rec->rm_blockcount)) return -EFSCORRUPTED; /* No contradictory flags. */ @@ -389,7 +389,7 @@ xrep_bmap_check_rtfork_rmap( return -EFSCORRUPTED; /* Check the file offsets and physical extents. */ - if (!xfs_verify_fileext(sc->mp, rec->rm_offset, rec->rm_blockcount)) + if (!xfs_verify_fileext(rec->rm_offset, rec->rm_blockcount)) return -EFSCORRUPTED; /* Check that this is within the rtgroup. */ diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c index aa5b41629747d2..62be62342fea17 100644 --- a/fs/xfs/xfs_bmap_item.c +++ b/fs/xfs/xfs_bmap_item.c @@ -442,7 +442,7 @@ xfs_bui_validate( if (!xfs_verify_ino(mp, map->me_owner)) return false; - if (!xfs_verify_fileext(mp, map->me_startoff, map->me_len)) + if (!xfs_verify_fileext(map->me_startoff, map->me_len)) return false; if (map->me_flags & XFS_BMAP_EXTENT_REALTIME) diff --git a/fs/xfs/xfs_exchmaps_item.c b/fs/xfs/xfs_exchmaps_item.c index dd5d92ca1010fe..dd104bf778baf8 100644 --- a/fs/xfs/xfs_exchmaps_item.c +++ b/fs/xfs/xfs_exchmaps_item.c @@ -341,10 +341,10 @@ xfs_xmi_validate( !xfs_verify_ino(mp, xlf->xmi_inode2)) return false; - if (!xfs_verify_fileext(mp, xlf->xmi_startoff1, xlf->xmi_blockcount)) + if (!xfs_verify_fileext(xlf->xmi_startoff1, xlf->xmi_blockcount)) return false; - if (!xfs_verify_fileext(mp, xlf->xmi_startoff2, xlf->xmi_blockcount)) + if (!xfs_verify_fileext(xlf->xmi_startoff2, xlf->xmi_blockcount)) return false; if (xlf->xmi_flags & XFS_EXCHMAPS_SET_SIZES) { diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c index 000cff1ce324f0..be2d5d6fe86337 100644 --- a/fs/xfs/xfs_rmap_item.c +++ b/fs/xfs/xfs_rmap_item.c @@ -494,7 +494,7 @@ xfs_rui_validate_map( !xfs_verify_ino(mp, map->me_owner)) return false; - if (!xfs_verify_fileext(mp, map->me_startoff, map->me_len)) + if (!xfs_verify_fileext(map->me_startoff, map->me_len)) return false; if (isrt) From 81caec1321282a29b40742c76188df9f31a00f7f Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 22 Sep 2026 18:17:08 -0500 Subject: [PATCH 1356/2286] xfs: share the AG rmap btree with the rt rmap btree The AG rmap btree and the realtime rmap btree have several identical key and record ops. Share these to eliminate copied code. Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rmap_btree.c | 16 +-- fs/xfs/libxfs/xfs_rmap_btree.h | 26 ++++ fs/xfs/libxfs/xfs_rtrmap_btree.c | 230 +++---------------------------- 3 files changed, 51 insertions(+), 221 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rmap_btree.c b/fs/xfs/libxfs/xfs_rmap_btree.c index 10b3272238ebcb..5b283a5ddd130d 100644 --- a/fs/xfs/libxfs/xfs_rmap_btree.c +++ b/fs/xfs/libxfs/xfs_rmap_btree.c @@ -170,7 +170,7 @@ static inline __be64 ondisk_rec_offset_to_key(const union xfs_btree_rec *rec) return rec->rmap.rm_offset & ~cpu_to_be64(XFS_RMAP_OFF_UNWRITTEN); } -STATIC void +void xfs_rmapbt_init_key_from_rec( union xfs_btree_key *key, const union xfs_btree_rec *rec) @@ -187,7 +187,7 @@ xfs_rmapbt_init_key_from_rec( * the startblock for all records, and if the record is for a data/attr * fork mapping, we add blockcount-1 to the offset too. */ -STATIC void +void xfs_rmapbt_init_high_key_from_rec( union xfs_btree_key *key, const union xfs_btree_rec *rec) @@ -209,7 +209,7 @@ xfs_rmapbt_init_high_key_from_rec( key->rmap.rm_offset = cpu_to_be64(off); } -STATIC void +void xfs_rmapbt_init_rec_from_cur( struct xfs_btree_cur *cur, union xfs_btree_rec *rec) @@ -243,7 +243,7 @@ static inline uint64_t offset_keymask(uint64_t offset) return offset & ~XFS_RMAP_OFF_UNWRITTEN; } -STATIC int +int xfs_rmapbt_cmp_key_with_cur( struct xfs_btree_cur *cur, const union xfs_btree_key *key) @@ -257,7 +257,7 @@ xfs_rmapbt_cmp_key_with_cur( offset_keymask(xfs_rmap_irec_offset_pack(rec))); } -STATIC int +int xfs_rmapbt_cmp_two_keys( struct xfs_btree_cur *cur, const union xfs_btree_key *k1, @@ -390,7 +390,7 @@ const struct xfs_buf_ops xfs_rmapbt_buf_ops = { .verify_struct = xfs_rmapbt_verify, }; -STATIC int +int xfs_rmapbt_keys_inorder( struct xfs_btree_cur *cur, const union xfs_btree_key *k1, @@ -420,7 +420,7 @@ xfs_rmapbt_keys_inorder( return 0; } -STATIC int +int xfs_rmapbt_recs_inorder( struct xfs_btree_cur *cur, const union xfs_btree_rec *r1, @@ -450,7 +450,7 @@ xfs_rmapbt_recs_inorder( return 0; } -STATIC enum xbtree_key_contig +enum xbtree_key_contig xfs_rmapbt_keys_contiguous( struct xfs_btree_cur *cur, const union xfs_btree_key *key1, diff --git a/fs/xfs/libxfs/xfs_rmap_btree.h b/fs/xfs/libxfs/xfs_rmap_btree.h index 119b1567cd0ee8..7071dac745e1b8 100644 --- a/fs/xfs/libxfs/xfs_rmap_btree.h +++ b/fs/xfs/libxfs/xfs_rmap_btree.h @@ -11,6 +11,8 @@ struct xfs_btree_cur; struct xfs_mount; struct xbtree_afakeroot; struct xfbtree; +union xfs_btree_key; +union xfs_btree_rec; /* rmaps only exist on crc enabled filesystems */ #define XFS_RMAP_BLOCK_LEN XFS_BTREE_SBLOCK_CRC_LEN @@ -69,4 +71,28 @@ struct xfs_btree_cur *xfs_rmapbt_mem_cursor(struct xfs_perag *pag, int xfs_rmapbt_mem_init(struct xfs_mount *mp, struct xfbtree *xfbtree, struct xfs_buftarg *btp, xfs_agnumber_t agno); +/* + * Key and record btree ops. The rmap on-disk key/record format is identical + * for the AG rmap btree and the realtime rmap btree, so these are shared by + * both. + */ +void xfs_rmapbt_init_key_from_rec(union xfs_btree_key *key, + const union xfs_btree_rec *rec); +void xfs_rmapbt_init_high_key_from_rec(union xfs_btree_key *key, + const union xfs_btree_rec *rec); +void xfs_rmapbt_init_rec_from_cur(struct xfs_btree_cur *cur, + union xfs_btree_rec *rec); +int xfs_rmapbt_cmp_key_with_cur(struct xfs_btree_cur *cur, + const union xfs_btree_key *key); +int xfs_rmapbt_cmp_two_keys(struct xfs_btree_cur *cur, + const union xfs_btree_key *k1, const union xfs_btree_key *k2, + const union xfs_btree_key *mask); +int xfs_rmapbt_keys_inorder(struct xfs_btree_cur *cur, + const union xfs_btree_key *k1, const union xfs_btree_key *k2); +int xfs_rmapbt_recs_inorder(struct xfs_btree_cur *cur, + const union xfs_btree_rec *r1, const union xfs_btree_rec *r2); +enum xbtree_key_contig xfs_rmapbt_keys_contiguous(struct xfs_btree_cur *cur, + const union xfs_btree_key *key1, const union xfs_btree_key *key2, + const union xfs_btree_key *mask); + #endif /* __XFS_RMAP_BTREE_H__ */ diff --git a/fs/xfs/libxfs/xfs_rtrmap_btree.c b/fs/xfs/libxfs/xfs_rtrmap_btree.c index 5901d7efd3f676..2f00d0698ae17a 100644 --- a/fs/xfs/libxfs/xfs_rtrmap_btree.c +++ b/fs/xfs/libxfs/xfs_rtrmap_btree.c @@ -20,6 +20,7 @@ #include "xfs_btree_staging.h" #include "xfs_metafile.h" #include "xfs_rmap.h" +#include "xfs_rmap_btree.h" #include "xfs_rtrmap_btree.h" #include "xfs_trace.h" #include "xfs_cksum.h" @@ -113,60 +114,6 @@ xfs_rtrmapbt_get_dmaxrecs( return xfs_rtrmapbt_droot_maxrecs(cur->bc_ino.forksize, level == 0); } -/* - * Convert the ondisk record's offset field into the ondisk key's offset field. - * Fork and bmbt are significant parts of the rmap record key, but written - * status is merely a record attribute. - */ -static inline __be64 ondisk_rec_offset_to_key(const union xfs_btree_rec *rec) -{ - return rec->rmap.rm_offset & ~cpu_to_be64(XFS_RMAP_OFF_UNWRITTEN); -} - -STATIC void -xfs_rtrmapbt_init_key_from_rec( - union xfs_btree_key *key, - const union xfs_btree_rec *rec) -{ - key->rmap.rm_startblock = rec->rmap.rm_startblock; - key->rmap.rm_owner = rec->rmap.rm_owner; - key->rmap.rm_offset = ondisk_rec_offset_to_key(rec); -} - -STATIC void -xfs_rtrmapbt_init_high_key_from_rec( - union xfs_btree_key *key, - const union xfs_btree_rec *rec) -{ - uint64_t off; - int adj; - - adj = be32_to_cpu(rec->rmap.rm_blockcount) - 1; - - key->rmap.rm_startblock = rec->rmap.rm_startblock; - be32_add_cpu(&key->rmap.rm_startblock, adj); - key->rmap.rm_owner = rec->rmap.rm_owner; - key->rmap.rm_offset = ondisk_rec_offset_to_key(rec); - if (XFS_RMAP_NON_INODE_OWNER(be64_to_cpu(rec->rmap.rm_owner)) || - XFS_RMAP_IS_BMBT_BLOCK(be64_to_cpu(rec->rmap.rm_offset))) - return; - off = be64_to_cpu(key->rmap.rm_offset); - off = (XFS_RMAP_OFF(off) + adj) | (off & ~XFS_RMAP_OFF_MASK); - key->rmap.rm_offset = cpu_to_be64(off); -} - -STATIC void -xfs_rtrmapbt_init_rec_from_cur( - struct xfs_btree_cur *cur, - union xfs_btree_rec *rec) -{ - rec->rmap.rm_startblock = cpu_to_be32(cur->bc_rec.r.rm_startblock); - rec->rmap.rm_blockcount = cpu_to_be32(cur->bc_rec.r.rm_blockcount); - rec->rmap.rm_owner = cpu_to_be64(cur->bc_rec.r.rm_owner); - rec->rmap.rm_offset = cpu_to_be64( - xfs_rmap_irec_offset_pack(&cur->bc_rec.r)); -} - STATIC void xfs_rtrmapbt_init_ptr_from_cur( struct xfs_btree_cur *cur, @@ -175,69 +122,6 @@ xfs_rtrmapbt_init_ptr_from_cur( ptr->l = 0; } -/* - * Mask the appropriate parts of the ondisk key field for a key comparison. - * Fork and bmbt are significant parts of the rmap record key, but written - * status is merely a record attribute. - */ -static inline uint64_t offset_keymask(uint64_t offset) -{ - return offset & ~XFS_RMAP_OFF_UNWRITTEN; -} - -STATIC int -xfs_rtrmapbt_cmp_key_with_cur( - struct xfs_btree_cur *cur, - const union xfs_btree_key *key) -{ - struct xfs_rmap_irec *rec = &cur->bc_rec.r; - const struct xfs_rmap_key *kp = &key->rmap; - - return cmp_int(be32_to_cpu(kp->rm_startblock), rec->rm_startblock) ?: - cmp_int(be64_to_cpu(kp->rm_owner), rec->rm_owner) ?: - cmp_int(offset_keymask(be64_to_cpu(kp->rm_offset)), - offset_keymask(xfs_rmap_irec_offset_pack(rec))); -} - -STATIC int -xfs_rtrmapbt_cmp_two_keys( - struct xfs_btree_cur *cur, - const union xfs_btree_key *k1, - const union xfs_btree_key *k2, - const union xfs_btree_key *mask) -{ - const struct xfs_rmap_key *kp1 = &k1->rmap; - const struct xfs_rmap_key *kp2 = &k2->rmap; - int d; - - /* Doesn't make sense to mask off the physical space part */ - ASSERT(!mask || mask->rmap.rm_startblock); - - d = cmp_int(be32_to_cpu(kp1->rm_startblock), - be32_to_cpu(kp2->rm_startblock)); - if (d) - return d; - - if (!mask || mask->rmap.rm_owner) { - d = cmp_int(be64_to_cpu(kp1->rm_owner), - be64_to_cpu(kp2->rm_owner)); - if (d) - return d; - } - - if (!mask || mask->rmap.rm_offset) { - /* Doesn't make sense to allow offset but not owner */ - ASSERT(!mask || mask->rmap.rm_owner); - - d = cmp_int(offset_keymask(be64_to_cpu(kp1->rm_offset)), - offset_keymask(be64_to_cpu(kp2->rm_offset))); - if (d) - return d; - } - - return 0; -} - static xfs_failaddr_t xfs_rtrmapbt_verify( struct xfs_buf *bp) @@ -304,86 +188,6 @@ const struct xfs_buf_ops xfs_rtrmapbt_buf_ops = { .verify_struct = xfs_rtrmapbt_verify, }; -STATIC int -xfs_rtrmapbt_keys_inorder( - struct xfs_btree_cur *cur, - const union xfs_btree_key *k1, - const union xfs_btree_key *k2) -{ - uint32_t x; - uint32_t y; - uint64_t a; - uint64_t b; - - x = be32_to_cpu(k1->rmap.rm_startblock); - y = be32_to_cpu(k2->rmap.rm_startblock); - if (x < y) - return 1; - else if (x > y) - return 0; - a = be64_to_cpu(k1->rmap.rm_owner); - b = be64_to_cpu(k2->rmap.rm_owner); - if (a < b) - return 1; - else if (a > b) - return 0; - a = offset_keymask(be64_to_cpu(k1->rmap.rm_offset)); - b = offset_keymask(be64_to_cpu(k2->rmap.rm_offset)); - if (a <= b) - return 1; - return 0; -} - -STATIC int -xfs_rtrmapbt_recs_inorder( - struct xfs_btree_cur *cur, - const union xfs_btree_rec *r1, - const union xfs_btree_rec *r2) -{ - uint32_t x; - uint32_t y; - uint64_t a; - uint64_t b; - - x = be32_to_cpu(r1->rmap.rm_startblock); - y = be32_to_cpu(r2->rmap.rm_startblock); - if (x < y) - return 1; - else if (x > y) - return 0; - a = be64_to_cpu(r1->rmap.rm_owner); - b = be64_to_cpu(r2->rmap.rm_owner); - if (a < b) - return 1; - else if (a > b) - return 0; - a = offset_keymask(be64_to_cpu(r1->rmap.rm_offset)); - b = offset_keymask(be64_to_cpu(r2->rmap.rm_offset)); - if (a <= b) - return 1; - return 0; -} - -STATIC enum xbtree_key_contig -xfs_rtrmapbt_keys_contiguous( - struct xfs_btree_cur *cur, - const union xfs_btree_key *key1, - const union xfs_btree_key *key2, - const union xfs_btree_key *mask) -{ - ASSERT(!mask || mask->rmap.rm_startblock); - - /* - * We only support checking contiguity of the physical space component. - * If any callers ever need more specificity than that, they'll have to - * implement it here. - */ - ASSERT(!mask || (!mask->rmap.rm_owner && !mask->rmap.rm_offset)); - - return xbtree_key_contig(be32_to_cpu(key1->rmap.rm_startblock), - be32_to_cpu(key2->rmap.rm_startblock)); -} - static inline void xfs_rtrmapbt_move_ptrs( struct xfs_mount *mp, @@ -486,16 +290,16 @@ const struct xfs_btree_ops xfs_rtrmapbt_ops = { .get_minrecs = xfs_rtrmapbt_get_minrecs, .get_maxrecs = xfs_rtrmapbt_get_maxrecs, .get_dmaxrecs = xfs_rtrmapbt_get_dmaxrecs, - .init_key_from_rec = xfs_rtrmapbt_init_key_from_rec, - .init_high_key_from_rec = xfs_rtrmapbt_init_high_key_from_rec, - .init_rec_from_cur = xfs_rtrmapbt_init_rec_from_cur, + .init_key_from_rec = xfs_rmapbt_init_key_from_rec, + .init_high_key_from_rec = xfs_rmapbt_init_high_key_from_rec, + .init_rec_from_cur = xfs_rmapbt_init_rec_from_cur, .init_ptr_from_cur = xfs_rtrmapbt_init_ptr_from_cur, - .cmp_key_with_cur = xfs_rtrmapbt_cmp_key_with_cur, + .cmp_key_with_cur = xfs_rmapbt_cmp_key_with_cur, .buf_ops = &xfs_rtrmapbt_buf_ops, - .cmp_two_keys = xfs_rtrmapbt_cmp_two_keys, - .keys_inorder = xfs_rtrmapbt_keys_inorder, - .recs_inorder = xfs_rtrmapbt_recs_inorder, - .keys_contiguous = xfs_rtrmapbt_keys_contiguous, + .cmp_two_keys = xfs_rmapbt_cmp_two_keys, + .keys_inorder = xfs_rmapbt_keys_inorder, + .recs_inorder = xfs_rmapbt_recs_inorder, + .keys_contiguous = xfs_rmapbt_keys_contiguous, .broot_realloc = xfs_rtrmapbt_broot_realloc, }; @@ -595,16 +399,16 @@ const struct xfs_btree_ops xfs_rtrmapbt_mem_ops = { .free_block = xfbtree_free_block, .get_minrecs = xfbtree_get_minrecs, .get_maxrecs = xfbtree_get_maxrecs, - .init_key_from_rec = xfs_rtrmapbt_init_key_from_rec, - .init_high_key_from_rec = xfs_rtrmapbt_init_high_key_from_rec, - .init_rec_from_cur = xfs_rtrmapbt_init_rec_from_cur, + .init_key_from_rec = xfs_rmapbt_init_key_from_rec, + .init_high_key_from_rec = xfs_rmapbt_init_high_key_from_rec, + .init_rec_from_cur = xfs_rmapbt_init_rec_from_cur, .init_ptr_from_cur = xfbtree_init_ptr_from_cur, - .cmp_key_with_cur = xfs_rtrmapbt_cmp_key_with_cur, + .cmp_key_with_cur = xfs_rmapbt_cmp_key_with_cur, .buf_ops = &xfs_rtrmapbt_mem_buf_ops, - .cmp_two_keys = xfs_rtrmapbt_cmp_two_keys, - .keys_inorder = xfs_rtrmapbt_keys_inorder, - .recs_inorder = xfs_rtrmapbt_recs_inorder, - .keys_contiguous = xfs_rtrmapbt_keys_contiguous, + .cmp_two_keys = xfs_rmapbt_cmp_two_keys, + .keys_inorder = xfs_rmapbt_keys_inorder, + .recs_inorder = xfs_rmapbt_recs_inorder, + .keys_contiguous = xfs_rmapbt_keys_contiguous, }; /* Create a cursor for an in-memory btree. */ From 8439f269aae2b37830b2a6af45f9b111a7856b17 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 22 Sep 2026 18:17:09 -0500 Subject: [PATCH 1357/2286] xfs: share the AG refcount btree with the rt refcount btree The AG refcount btree and the realtime refcount btree have several identical key and record ops. Share these to eliminate copied code. Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_refcount_btree.c | 16 ++-- fs/xfs/libxfs/xfs_refcount_btree.h | 26 ++++++ fs/xfs/libxfs/xfs_rtrefcount_btree.c | 113 +++------------------------ 3 files changed, 43 insertions(+), 112 deletions(-) diff --git a/fs/xfs/libxfs/xfs_refcount_btree.c b/fs/xfs/libxfs/xfs_refcount_btree.c index 7e5f92c1ac56fd..2c6148a979942b 100644 --- a/fs/xfs/libxfs/xfs_refcount_btree.c +++ b/fs/xfs/libxfs/xfs_refcount_btree.c @@ -127,7 +127,7 @@ xfs_refcountbt_get_maxrecs( return cur->bc_mp->m_refc_mxr[level != 0]; } -STATIC void +void xfs_refcountbt_init_key_from_rec( union xfs_btree_key *key, const union xfs_btree_rec *rec) @@ -135,7 +135,7 @@ xfs_refcountbt_init_key_from_rec( key->refc.rc_startblock = rec->refc.rc_startblock; } -STATIC void +void xfs_refcountbt_init_high_key_from_rec( union xfs_btree_key *key, const union xfs_btree_rec *rec) @@ -147,7 +147,7 @@ xfs_refcountbt_init_high_key_from_rec( key->refc.rc_startblock = cpu_to_be32(x); } -STATIC void +void xfs_refcountbt_init_rec_from_cur( struct xfs_btree_cur *cur, union xfs_btree_rec *rec) @@ -174,7 +174,7 @@ xfs_refcountbt_init_ptr_from_cur( ptr->s = agf->agf_refcount_root; } -STATIC int +int xfs_refcountbt_cmp_key_with_cur( struct xfs_btree_cur *cur, const union xfs_btree_key *key) @@ -188,7 +188,7 @@ xfs_refcountbt_cmp_key_with_cur( return cmp_int(be32_to_cpu(kp->rc_startblock), start); } -STATIC int +int xfs_refcountbt_cmp_two_keys( struct xfs_btree_cur *cur, const union xfs_btree_key *k1, @@ -283,7 +283,7 @@ const struct xfs_buf_ops xfs_refcountbt_buf_ops = { .verify_struct = xfs_refcountbt_verify, }; -STATIC int +int xfs_refcountbt_keys_inorder( struct xfs_btree_cur *cur, const union xfs_btree_key *k1, @@ -293,7 +293,7 @@ xfs_refcountbt_keys_inorder( be32_to_cpu(k2->refc.rc_startblock); } -STATIC int +int xfs_refcountbt_recs_inorder( struct xfs_btree_cur *cur, const union xfs_btree_rec *r1, @@ -304,7 +304,7 @@ xfs_refcountbt_recs_inorder( be32_to_cpu(r2->refc.rc_startblock); } -STATIC enum xbtree_key_contig +enum xbtree_key_contig xfs_refcountbt_keys_contiguous( struct xfs_btree_cur *cur, const union xfs_btree_key *key1, diff --git a/fs/xfs/libxfs/xfs_refcount_btree.h b/fs/xfs/libxfs/xfs_refcount_btree.h index beb93bef6a8141..40eedfae6846ec 100644 --- a/fs/xfs/libxfs/xfs_refcount_btree.h +++ b/fs/xfs/libxfs/xfs_refcount_btree.h @@ -15,6 +15,8 @@ struct xfs_btree_cur; struct xfs_mount; struct xfs_perag; struct xbtree_afakeroot; +union xfs_btree_key; +union xfs_btree_rec; /* * Btree block header size @@ -69,4 +71,28 @@ unsigned int xfs_refcountbt_maxlevels_ondisk(void); int __init xfs_refcountbt_init_cur_cache(void); void xfs_refcountbt_destroy_cur_cache(void); +/* + * Key and record btree ops. The refcount on-disk key/record format is + * identical for the AG refcount btree and the realtime refcount btree, so + * these are shared by both. + */ +void xfs_refcountbt_init_key_from_rec(union xfs_btree_key *key, + const union xfs_btree_rec *rec); +void xfs_refcountbt_init_high_key_from_rec(union xfs_btree_key *key, + const union xfs_btree_rec *rec); +void xfs_refcountbt_init_rec_from_cur(struct xfs_btree_cur *cur, + union xfs_btree_rec *rec); +int xfs_refcountbt_cmp_key_with_cur(struct xfs_btree_cur *cur, + const union xfs_btree_key *key); +int xfs_refcountbt_cmp_two_keys(struct xfs_btree_cur *cur, + const union xfs_btree_key *k1, const union xfs_btree_key *k2, + const union xfs_btree_key *mask); +int xfs_refcountbt_keys_inorder(struct xfs_btree_cur *cur, + const union xfs_btree_key *k1, const union xfs_btree_key *k2); +int xfs_refcountbt_recs_inorder(struct xfs_btree_cur *cur, + const union xfs_btree_rec *r1, const union xfs_btree_rec *r2); +enum xbtree_key_contig xfs_refcountbt_keys_contiguous(struct xfs_btree_cur *cur, + const union xfs_btree_key *key1, const union xfs_btree_key *key2, + const union xfs_btree_key *mask); + #endif /* __XFS_REFCOUNT_BTREE_H__ */ diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c index e91ff14577f9ad..697f5e62268573 100644 --- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c +++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c @@ -20,6 +20,7 @@ #include "xfs_btree_staging.h" #include "xfs_rtrefcount_btree.h" #include "xfs_refcount.h" +#include "xfs_refcount_btree.h" #include "xfs_trace.h" #include "xfs_cksum.h" #include "xfs_error.h" @@ -113,41 +114,6 @@ xfs_rtrefcountbt_get_dmaxrecs( return xfs_rtrefcountbt_droot_maxrecs(cur->bc_ino.forksize, level == 0); } -STATIC void -xfs_rtrefcountbt_init_key_from_rec( - union xfs_btree_key *key, - const union xfs_btree_rec *rec) -{ - key->refc.rc_startblock = rec->refc.rc_startblock; -} - -STATIC void -xfs_rtrefcountbt_init_high_key_from_rec( - union xfs_btree_key *key, - const union xfs_btree_rec *rec) -{ - __u32 x; - - x = be32_to_cpu(rec->refc.rc_startblock); - x += be32_to_cpu(rec->refc.rc_blockcount) - 1; - key->refc.rc_startblock = cpu_to_be32(x); -} - -STATIC void -xfs_rtrefcountbt_init_rec_from_cur( - struct xfs_btree_cur *cur, - union xfs_btree_rec *rec) -{ - const struct xfs_refcount_irec *irec = &cur->bc_rec.rc; - uint32_t start; - - start = xfs_refcount_encode_startblock(irec->rc_startblock, - irec->rc_domain); - rec->refc.rc_startblock = cpu_to_be32(start); - rec->refc.rc_blockcount = cpu_to_be32(cur->bc_rec.rc.rc_blockcount); - rec->refc.rc_refcount = cpu_to_be32(cur->bc_rec.rc.rc_refcount); -} - STATIC void xfs_rtrefcountbt_init_ptr_from_cur( struct xfs_btree_cur *cur, @@ -156,33 +122,6 @@ xfs_rtrefcountbt_init_ptr_from_cur( ptr->l = 0; } -STATIC int -xfs_rtrefcountbt_cmp_key_with_cur( - struct xfs_btree_cur *cur, - const union xfs_btree_key *key) -{ - const struct xfs_refcount_key *kp = &key->refc; - const struct xfs_refcount_irec *irec = &cur->bc_rec.rc; - uint32_t start; - - start = xfs_refcount_encode_startblock(irec->rc_startblock, - irec->rc_domain); - return cmp_int(be32_to_cpu(kp->rc_startblock), start); -} - -STATIC int -xfs_rtrefcountbt_cmp_two_keys( - struct xfs_btree_cur *cur, - const union xfs_btree_key *k1, - const union xfs_btree_key *k2, - const union xfs_btree_key *mask) -{ - ASSERT(!mask || mask->refc.rc_startblock); - - return cmp_int(be32_to_cpu(k1->refc.rc_startblock), - be32_to_cpu(k2->refc.rc_startblock)); -} - static xfs_failaddr_t xfs_rtrefcountbt_verify( struct xfs_buf *bp) @@ -249,40 +188,6 @@ const struct xfs_buf_ops xfs_rtrefcountbt_buf_ops = { .verify_struct = xfs_rtrefcountbt_verify, }; -STATIC int -xfs_rtrefcountbt_keys_inorder( - struct xfs_btree_cur *cur, - const union xfs_btree_key *k1, - const union xfs_btree_key *k2) -{ - return be32_to_cpu(k1->refc.rc_startblock) < - be32_to_cpu(k2->refc.rc_startblock); -} - -STATIC int -xfs_rtrefcountbt_recs_inorder( - struct xfs_btree_cur *cur, - const union xfs_btree_rec *r1, - const union xfs_btree_rec *r2) -{ - return be32_to_cpu(r1->refc.rc_startblock) + - be32_to_cpu(r1->refc.rc_blockcount) <= - be32_to_cpu(r2->refc.rc_startblock); -} - -STATIC enum xbtree_key_contig -xfs_rtrefcountbt_keys_contiguous( - struct xfs_btree_cur *cur, - const union xfs_btree_key *key1, - const union xfs_btree_key *key2, - const union xfs_btree_key *mask) -{ - ASSERT(!mask || mask->refc.rc_startblock); - - return xbtree_key_contig(be32_to_cpu(key1->refc.rc_startblock), - be32_to_cpu(key2->refc.rc_startblock)); -} - static inline void xfs_rtrefcountbt_move_ptrs( struct xfs_mount *mp, @@ -383,16 +288,16 @@ const struct xfs_btree_ops xfs_rtrefcountbt_ops = { .get_minrecs = xfs_rtrefcountbt_get_minrecs, .get_maxrecs = xfs_rtrefcountbt_get_maxrecs, .get_dmaxrecs = xfs_rtrefcountbt_get_dmaxrecs, - .init_key_from_rec = xfs_rtrefcountbt_init_key_from_rec, - .init_high_key_from_rec = xfs_rtrefcountbt_init_high_key_from_rec, - .init_rec_from_cur = xfs_rtrefcountbt_init_rec_from_cur, + .init_key_from_rec = xfs_refcountbt_init_key_from_rec, + .init_high_key_from_rec = xfs_refcountbt_init_high_key_from_rec, + .init_rec_from_cur = xfs_refcountbt_init_rec_from_cur, .init_ptr_from_cur = xfs_rtrefcountbt_init_ptr_from_cur, - .cmp_key_with_cur = xfs_rtrefcountbt_cmp_key_with_cur, + .cmp_key_with_cur = xfs_refcountbt_cmp_key_with_cur, .buf_ops = &xfs_rtrefcountbt_buf_ops, - .cmp_two_keys = xfs_rtrefcountbt_cmp_two_keys, - .keys_inorder = xfs_rtrefcountbt_keys_inorder, - .recs_inorder = xfs_rtrefcountbt_recs_inorder, - .keys_contiguous = xfs_rtrefcountbt_keys_contiguous, + .cmp_two_keys = xfs_refcountbt_cmp_two_keys, + .keys_inorder = xfs_refcountbt_keys_inorder, + .recs_inorder = xfs_refcountbt_recs_inorder, + .keys_contiguous = xfs_refcountbt_keys_contiguous, .broot_realloc = xfs_rtrefcountbt_broot_realloc, }; From 6654fca702b28b87cd365242498262080b1c223f Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Wed, 16 Sep 2026 08:43:35 +0800 Subject: [PATCH 1358/2286] xfs: move xfs_sync_sb_buf() out of libxfs into xfs_ioctl.c xfs_sync_sb_buf() dereferences mp->m_sb_bp and mp->m_rtsb_bp, which only exist in the kernel's struct xfs_mount, not in xfsprogs' libxfs. Keeping it in libxfs is a porting hazard. Its only caller, xfs_ioc_setlabel(), is kernel-only, so move it there and make it static. Fixes: c1351fb48eee ("xfs: don't hold buffer locks across sync transaction commit in xfs_sync_sb_buf") Suggested-by: Darrick J. Wong Suggested-by: Christoph Hellwig Signed-off-by: Yun Zhou Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_sb.c | 40 ---------------------------------------- fs/xfs/libxfs/xfs_sb.h | 1 - fs/xfs/xfs_ioctl.c | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index f0341adbb8790c..d2ef7b20a49ffa 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -1460,46 +1460,6 @@ xfs_update_secondary_sbs( return saved_error ? saved_error : error; } -/* - * Same behavior as xfs_sync_sb, except that it is always synchronous and it - * also writes the superblock buffer to disk sector 0 immediately. - */ -int -xfs_sync_sb_buf( - struct xfs_mount *mp, - bool update_rtsb) -{ - struct xfs_trans *tp; - int error; - - error = xfs_trans_alloc(mp, &M_RES(mp)->tr_sb, 0, 0, 0, &tp); - if (error) - return error; - - xfs_log_sb(tp); - if (update_rtsb) - xfs_log_rtsb(tp, xfs_trans_getsb(tp)); - xfs_trans_set_sync(tp); - error = xfs_trans_commit(tp); - if (error) - return error; - - /* Re-acquire and write the sb and rtsb to disk. */ - xfs_buf_lock(mp->m_sb_bp); - error = xfs_bwrite(mp->m_sb_bp); - xfs_buf_unlock(mp->m_sb_bp); - if (error) - return error; - - if (update_rtsb && mp->m_rtsb_bp) { - xfs_buf_lock(mp->m_rtsb_bp); - error = xfs_bwrite(mp->m_rtsb_bp); - xfs_buf_unlock(mp->m_rtsb_bp); - } - - return error; -} - void xfs_fs_geometry( struct xfs_mount *mp, diff --git a/fs/xfs/libxfs/xfs_sb.h b/fs/xfs/libxfs/xfs_sb.h index 34d0dd374e9b0b..77de65922213b1 100644 --- a/fs/xfs/libxfs/xfs_sb.h +++ b/fs/xfs/libxfs/xfs_sb.h @@ -15,7 +15,6 @@ struct xfs_perag; extern void xfs_log_sb(struct xfs_trans *tp); extern int xfs_sync_sb(struct xfs_mount *mp, bool wait); -extern int xfs_sync_sb_buf(struct xfs_mount *mp, bool update_rtsb); extern void xfs_sb_mount_common(struct xfs_mount *mp, struct xfs_sb *sbp); void xfs_sb_mount_rextsize(struct xfs_mount *mp, struct xfs_sb *sbp); void xfs_mount_sb_set_rextsize(struct xfs_mount *mp, diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 10673cf7f2d6fd..c0fc9b34f3933c 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1035,6 +1035,46 @@ xfs_ioc_getlabel( return 0; } +/* + * Same behavior as xfs_sync_sb, except that it is always synchronous and it + * also writes the superblock buffer to disk sector 0 immediately. + */ +static int +xfs_sync_sb_buf( + struct xfs_mount *mp, + bool update_rtsb) +{ + struct xfs_trans *tp; + int error; + + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_sb, 0, 0, 0, &tp); + if (error) + return error; + + xfs_log_sb(tp); + if (update_rtsb) + xfs_log_rtsb(tp, xfs_trans_getsb(tp)); + xfs_trans_set_sync(tp); + error = xfs_trans_commit(tp); + if (error) + return error; + + /* Re-acquire and write the sb and rtsb to disk. */ + xfs_buf_lock(mp->m_sb_bp); + error = xfs_bwrite(mp->m_sb_bp); + xfs_buf_unlock(mp->m_sb_bp); + if (error) + return error; + + if (update_rtsb && mp->m_rtsb_bp) { + xfs_buf_lock(mp->m_rtsb_bp); + error = xfs_bwrite(mp->m_rtsb_bp); + xfs_buf_unlock(mp->m_rtsb_bp); + } + + return error; +} + static int xfs_ioc_setlabel( struct file *filp, From 6dcc4ef892f266c5ecc97c69adfd8717d7d4be7c Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Mon, 28 Sep 2026 12:12:34 +0200 Subject: [PATCH 1359/2286] s390/configs: Enable NVMe-oF RDMA and TCP in defconfig NVMe-oF with RDMA and TCP transports is enabled in distributions both in host and target mode. Enable it for defconfigs as well to align them with distributions and simplify testing. As part of this also enable NVMe native multipath support which is also enabled in distributions and required for I/O policy support. Signed-off-by: Niklas Schnelle Reviewed-by: Halil Pasic Signed-off-by: Heiko Carstens --- arch/s390/configs/debug_defconfig | 14 ++++++++++++++ arch/s390/configs/defconfig | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/arch/s390/configs/debug_defconfig b/arch/s390/configs/debug_defconfig index 3dae7147433305..3892d9dd75d9eb 100644 --- a/arch/s390/configs/debug_defconfig +++ b/arch/s390/configs/debug_defconfig @@ -451,6 +451,20 @@ CONFIG_BLK_DEV_RAM_SIZE=32768 CONFIG_VIRTIO_BLK=y CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_NVME=m +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_RDMA=m +CONFIG_NVME_TCP=m +CONFIG_NVME_TCP_TLS=y +CONFIG_NVME_HOST_AUTH=y +CONFIG_NVME_TARGET=m +CONFIG_NVME_TARGET_DEBUGFS=y +CONFIG_NVME_TARGET_PASSTHRU=y +CONFIG_NVME_TARGET_LOOP=m +CONFIG_NVME_TARGET_RDMA=m +CONFIG_NVME_TARGET_TCP=m +CONFIG_NVME_TARGET_TCP_TLS=y +CONFIG_NVME_TARGET_AUTH=y CONFIG_BLK_DEV_NULL_BLK=m CONFIG_ENCLOSURE_SERVICES=m CONFIG_GENWQE=m diff --git a/arch/s390/configs/defconfig b/arch/s390/configs/defconfig index 6f5722634b4d39..95720e8ad80dd0 100644 --- a/arch/s390/configs/defconfig +++ b/arch/s390/configs/defconfig @@ -441,6 +441,20 @@ CONFIG_BLK_DEV_RAM_SIZE=32768 CONFIG_VIRTIO_BLK=y CONFIG_BLK_DEV_RBD=m CONFIG_BLK_DEV_NVME=m +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_VERBOSE_ERRORS=y +CONFIG_NVME_RDMA=m +CONFIG_NVME_TCP=m +CONFIG_NVME_TCP_TLS=y +CONFIG_NVME_HOST_AUTH=y +CONFIG_NVME_TARGET=m +CONFIG_NVME_TARGET_DEBUGFS=y +CONFIG_NVME_TARGET_PASSTHRU=y +CONFIG_NVME_TARGET_LOOP=m +CONFIG_NVME_TARGET_RDMA=m +CONFIG_NVME_TARGET_TCP=m +CONFIG_NVME_TARGET_TCP_TLS=y +CONFIG_NVME_TARGET_AUTH=y CONFIG_BLK_DEV_NULL_BLK=m CONFIG_ENCLOSURE_SERVICES=m CONFIG_GENWQE=m From f63eb4ee4a30fc5d8de07a229d92310e3c8d560c Mon Sep 17 00:00:00 2001 From: Christian Borntraeger Date: Tue, 29 Sep 2026 15:58:29 +0200 Subject: [PATCH 1360/2286] s390/vmcp: Initialize full session info to zero The cma_alloc bit as well as the response can contain old data. cma_release will not free the kmalloced memory in that case and the response code might leak kernel stack data to CAP_SYS_ADMIN users. Cc: stable@vger.kernel.org Signed-off-by: Christian Borntraeger Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- drivers/s390/char/vmcp.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/s390/char/vmcp.c b/drivers/s390/char/vmcp.c index b277f9d0c321a9..2cf94f23af2a67 100644 --- a/drivers/s390/char/vmcp.c +++ b/drivers/s390/char/vmcp.c @@ -104,13 +104,11 @@ static int vmcp_open(struct inode *inode, struct file *file) if (!capable(CAP_SYS_ADMIN)) return -EPERM; - session = kmalloc_obj(*session); + session = kzalloc_obj(*session); if (!session) return -ENOMEM; session->bufsize = PAGE_SIZE; - session->response = NULL; - session->resp_size = 0; mutex_init(&session->mutex); file->private_data = session; return nonseekable_open(inode, file); From 5e9b339f323216d71f3ede8a0a427abf7859c486 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Fri, 2 Oct 2026 12:52:36 +0200 Subject: [PATCH 1361/2286] s390/zcrypt: Fix and improve zcrypt reply message verification checks Add or improve checks related to buffer sizes and reply sizes to the handling of replies from the crypto cards for CCA, EP11 (AP message type 6) and ICA (AP type 50) messages. The verification code related to reply field length was not designed well and thus firmware deficiencies could lead to unexpected behavior in the zcrypt device driver. Thus improve the code to more closely inspect especially length fields at message replies. Add length validation before accessing reply message structures in zcrypt_msgtype6_receive(), zcrypt_msgtype6_receive_ep11(), and zcrypt_msgtype50_receive() to prevent out-of-bounds reads and potential kernel memory disclosure. Also rework these three functions to validate reply lengths more carefully before copying data back into the request buffer. Use size_t for length calculations, reject inconsistent reply sizes, and add defensive handling for short invalid replies. For XCRB replies, validate both reply segments and derive the effective message length from the covered range instead of trusting only the second segment. Additionally improve some of the later invoked evaluation functions which further check the payload. Add comments about length assumptions and for the type50 processing rework the payload processing completely with removing a misplaced BUG_ON(). Fixes: 5e55a488c87a ("s390/zcrypt: Separate msgtype implementation from card modules.") Signed-off-by: Harald Freudenberger Cc: stable@vger.kernel.org Reviewed-by: Holger Dengler Signed-off-by: Heiko Carstens --- drivers/s390/crypto/zcrypt_msgtype50.c | 95 ++++++++---- drivers/s390/crypto/zcrypt_msgtype6.c | 202 ++++++++++++++++++------- 2 files changed, 218 insertions(+), 79 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_msgtype50.c b/drivers/s390/crypto/zcrypt_msgtype50.c index d6fc2d8e7fad9a..5907fb2a6920b6 100644 --- a/drivers/s390/crypto/zcrypt_msgtype50.c +++ b/drivers/s390/crypto/zcrypt_msgtype50.c @@ -152,6 +152,11 @@ struct type80_hdr { unsigned char reserved3[8]; } __packed; +struct type80_reply { + struct type80_hdr hdr; + char data[]; +} __packed; + int get_rsa_modex_fc(struct ica_rsa_modexpo *mex, int *fcode) { if (!mex->inputdatalength) @@ -340,32 +345,43 @@ static int ICACRT_msg_to_type50CRT_msg(struct zcrypt_queue *zq, * @data: pointer to user output data * @length: size of user output data * - * Returns 0 on success or -EFAULT. + * Returns 0 on success or neg. errno value on failure. */ static int convert_type80(struct zcrypt_queue *zq, struct ap_message *reply, char __user *outputdata, unsigned int outputdatalength) { - struct type80_hdr *t80h = reply->msg; - unsigned char *data; + struct type80_reply *msg = reply->msg; + size_t payload_len; + + /* + * reply->len is always >= sizeof(struct type80_reply) here and + * outputdatalength (the modulus size) is guaranteed to be equal to + * inputdatalength and 0 < outputdatalength <= CEX3A_MAX_MOD_SIZE, + * also payload size may be >= outputdatalength. + */ + + payload_len = reply->len - sizeof(msg->hdr); - if (t80h->len < sizeof(*t80h) + outputdatalength) { - /* The result is too short, the CEXxA card may not do that.. */ + if (outputdatalength > payload_len) { + /* We expect at least outputdatalength bytes, broken card ? */ zq->online = 0; pr_err("Crypto dev=%02x.%04x code=0x%02x => online=0 rc=EAGAIN\n", AP_QID_CARD(zq->queue->qid), - AP_QID_QUEUE(zq->queue->qid), t80h->code); + AP_QID_QUEUE(zq->queue->qid), msg->hdr.code); ZCRYPT_DBF_ERR("%s dev=%02x.%04x code=0x%02x => online=0 rc=EAGAIN\n", __func__, AP_QID_CARD(zq->queue->qid), - AP_QID_QUEUE(zq->queue->qid), t80h->code); + AP_QID_QUEUE(zq->queue->qid), msg->hdr.code); ap_send_online_uevent(&zq->queue->ap_dev, zq->online); return -EAGAIN; } - BUG_ON(t80h->len > CEX3A_MAX_RESPONSE_SIZE); - data = reply->msg + t80h->len - outputdatalength; - if (copy_to_user(outputdata, data, outputdatalength)) + + if (copy_to_user(outputdata, + msg->data + payload_len - outputdatalength, + outputdatalength)) return -EFAULT; + return 0; } @@ -374,14 +390,19 @@ static int convert_response(struct zcrypt_queue *zq, char __user *outputdata, unsigned int outputdatalength) { - /* Response type byte is the second byte in the response. */ - unsigned char rtype = ((unsigned char *)reply->msg)[1]; + struct type80_reply *msg = reply->msg; + + /* reply->len is always >= sizeof(struct error_hdr) here */ - switch (rtype) { + switch (msg->hdr.type) { case TYPE82_RSP_CODE: case TYPE88_RSP_CODE: return convert_error(zq, reply); case TYPE80_RSP_CODE: + if (msg->hdr.code) + return convert_error(zq, reply); + if (reply->len < sizeof(struct type80_reply)) + return -EINVAL; return convert_type80(zq, reply, outputdata, outputdatalength); default: /* Unknown response type, this should NEVER EVER happen */ @@ -389,11 +410,11 @@ static int convert_response(struct zcrypt_queue *zq, pr_err("Crypto dev=%02x.%04x unknown response type 0x%02x => online=0 rc=EAGAIN\n", AP_QID_CARD(zq->queue->qid), AP_QID_QUEUE(zq->queue->qid), - (int)rtype); + (int)msg->hdr.type); ZCRYPT_DBF_ERR( "%s dev=%02x.%04x unknown response type 0x%02x => online=0 rc=EAGAIN\n", __func__, AP_QID_CARD(zq->queue->qid), - AP_QID_QUEUE(zq->queue->qid), (int)rtype); + AP_QID_QUEUE(zq->queue->qid), (int)msg->hdr.type); ap_send_online_uevent(&zq->queue->ap_dev, zq->online); return -EAGAIN; } @@ -416,26 +437,46 @@ static void zcrypt_msgtype50_receive(struct ap_queue *aq, .reply_code = REP82_ERROR_MACHINE_FAILURE, }; struct type80_hdr *t80h; - int len; + size_t len; /* Copy the reply message to the request message buffer. */ if (!reply) goto out; /* ap_msg->rc indicates the error */ + t80h = reply->msg; - if (t80h->type == TYPE80_RSP_CODE) { - len = t80h->len; - if (len > reply->bufsize || len > msg->bufsize || - len != reply->len) { - pr_debug("len mismatch => EMSGSIZE\n"); - msg->rc = -EMSGSIZE; + + if (reply->len < sizeof(*t80h) || + t80h->type != TYPE80_RSP_CODE) { + if (reply->len < sizeof(error_reply)) { + /* total broken reply, use static error reply instead */ + memcpy(msg->msg, &error_reply, sizeof(error_reply)); + msg->len = sizeof(error_reply); goto out; + } else { + /* malformed reply, convert function will handle this */ + len = reply->len; + goto copy_len_and_out; } - memcpy(msg->msg, reply->msg, len); - msg->len = len; - } else { - memcpy(msg->msg, reply->msg, sizeof(error_reply)); - msg->len = sizeof(error_reply); } + + len = t80h->len; + +copy_len_and_out: + if (len != reply->len) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu rpl.len %zu mismatch, msg.rc=%d\n", + len, reply->len, msg->rc); + goto out; + } + if (len > reply->bufsize || len > msg->bufsize) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu exceeds buf %zu/%zu, msg.rc=%d\n", + len, reply->bufsize, msg->bufsize, msg->rc); + goto out; + } + memcpy(msg->msg, reply->msg, len); + msg->len = len; + out: complete(&msg->response.work); } diff --git a/drivers/s390/crypto/zcrypt_msgtype6.c b/drivers/s390/crypto/zcrypt_msgtype6.c index 3df1d676de5d8e..5962224bfb8ba2 100644 --- a/drivers/s390/crypto/zcrypt_msgtype6.c +++ b/drivers/s390/crypto/zcrypt_msgtype6.c @@ -766,6 +766,15 @@ static int convert_type86_rng(struct zcrypt_queue *zq, if (msg->cprbx.ccp_rtcode != 0 || msg->cprbx.ccp_rscode != 0) return -EINVAL; + /* + * Note that offset2 and count2 have already been checked in + * zcrypt_msgtype6_receive(). So only check for valid count2 + * and for not exceeding the hard coded rng buffer size. + */ + if (!msg->fmt2.count2) + return -EINVAL; + if (msg->fmt2.count2 > ZCRYPT_RNG_BUFFER_SIZE) + return -EMSGSIZE; memcpy(buffer, data + msg->fmt2.offset2, msg->fmt2.count2); return msg->fmt2.count2; } @@ -777,11 +786,17 @@ static int convert_response_ica(struct zcrypt_queue *zq, { struct type86x_reply *msg = reply->msg; + /* reply->len is always >= sizeof(struct error_hdr) here */ + switch (msg->hdr.type) { case TYPE82_RSP_CODE: case TYPE88_RSP_CODE: return convert_error(zq, reply); case TYPE86_RSP_CODE: + if (msg->hdr.reply_code) + return convert_error(zq, reply); + if (reply->len < sizeof(struct type86x_reply)) + return -EINVAL; if (msg->cprbx.ccp_rtcode && msg->cprbx.ccp_rscode == 0x14f && outputdatalength > 256) { @@ -792,8 +807,6 @@ static int convert_response_ica(struct zcrypt_queue *zq, return -EINVAL; } } - if (msg->hdr.reply_code) - return convert_error(zq, reply); if (msg->cprbx.cprb_ver_id == 0x02) return convert_type86_ica(zq, reply, outputdata, outputdatalength); @@ -820,6 +833,8 @@ static int convert_response_xcrb(bool userspace, struct zcrypt_queue *zq, { struct type86x_reply *msg = reply->msg; + /* reply->len is always >= sizeof(struct error_hdr) here */ + switch (msg->hdr.type) { case TYPE82_RSP_CODE: case TYPE88_RSP_CODE: @@ -827,9 +842,16 @@ static int convert_response_xcrb(bool userspace, struct zcrypt_queue *zq, return convert_error(zq, reply); case TYPE86_RSP_CODE: if (msg->hdr.reply_code) { - xcrb->status = msg->fmt2.apfs; + if (reply->len < sizeof(struct type86_fmt2_msg)) + xcrb->status = 0x0008044DL; + else + xcrb->status = msg->fmt2.apfs; return convert_error(zq, reply); } + if (reply->len < sizeof(struct type86_fmt2_msg) + + offsetof(struct CPRBX, cprb_ver_id) + + sizeof(msg->cprbx.cprb_ver_id)) + return -EINVAL; if (msg->cprbx.cprb_ver_id == 0x02) return convert_type86_xcrb(userspace, zq, reply, xcrb); fallthrough; /* wrong cprb version is an unknown response */ @@ -854,6 +876,8 @@ static int convert_response_ep11_xcrb(bool userspace, struct zcrypt_queue *zq, { struct type86_ep11_reply *msg = reply->msg; + /* reply->len is always >= sizeof(struct error_hdr) here */ + switch (msg->hdr.type) { case TYPE82_RSP_CODE: case TYPE87_RSP_CODE: @@ -861,6 +885,8 @@ static int convert_response_ep11_xcrb(bool userspace, struct zcrypt_queue *zq, case TYPE86_RSP_CODE: if (msg->hdr.reply_code) return convert_error(zq, reply); + if (reply->len < sizeof(struct type86_ep11_reply)) + return -EINVAL; if (msg->cprbx.cprb_ver_id == 0x04) return convert_type86_ep11_xcrb(userspace, zq, reply, xcrb); fallthrough; /* wrong cprb version is an unknown resp */ @@ -885,6 +911,8 @@ static int convert_response_rng(struct zcrypt_queue *zq, { struct type86x_reply *msg = reply->msg; + /* reply->len is always >= sizeof(struct error_hdr) here */ + switch (msg->hdr.type) { case TYPE82_RSP_CODE: case TYPE88_RSP_CODE: @@ -892,6 +920,8 @@ static int convert_response_rng(struct zcrypt_queue *zq, case TYPE86_RSP_CODE: if (msg->hdr.reply_code) return -EINVAL; + if (reply->len < sizeof(struct type86x_reply)) + return -EINVAL; if (msg->cprbx.cprb_ver_id == 0x02) return convert_type86_rng(zq, reply, data); fallthrough; /* wrong cprb version is an unknown response */ @@ -928,48 +958,85 @@ static void zcrypt_msgtype6_receive(struct ap_queue *aq, }; struct ap_response_type *resp_type = &msg->response; struct type86x_reply *t86r; - int len; + size_t minlen, len; /* Copy the reply message to the request message buffer. */ if (!reply) goto out; /* ap_msg->rc indicates the error */ + t86r = reply->msg; - if (t86r->hdr.type == TYPE86_RSP_CODE && - t86r->cprbx.cprb_ver_id == 0x02) { - switch (resp_type->type) { - case CEXXC_RESPONSE_TYPE_ICA: - len = sizeof(struct type86x_reply) + t86r->length; - if (len > reply->bufsize || len > msg->bufsize || - len != reply->len) { - pr_debug("len mismatch => EMSGSIZE\n"); - msg->rc = -EMSGSIZE; - goto out; - } - memcpy(msg->msg, reply->msg, len); - msg->len = len; - break; - case CEXXC_RESPONSE_TYPE_XCRB: - if (t86r->fmt2.count2) - len = t86r->fmt2.offset2 + t86r->fmt2.count2; - else - len = t86r->fmt2.offset1 + t86r->fmt2.count1; - if (len > reply->bufsize || len > msg->bufsize || - len != reply->len) { - pr_debug("len mismatch => EMSGSIZE\n"); + minlen = sizeof(t86r->hdr) + sizeof(t86r->fmt2) + + offsetof(struct CPRBX, cprb_ver_id) + + sizeof(t86r->cprbx.cprb_ver_id); + + if (reply->len < minlen || + t86r->hdr.type != TYPE86_RSP_CODE || + t86r->cprbx.cprb_ver_id != 0x02) { + if (reply->len < sizeof(error_reply)) { + /* total broken reply, use static error reply instead */ + memcpy(msg->msg, &error_reply, sizeof(error_reply)); + msg->len = sizeof(error_reply); + goto out; + } else { + /* malformed reply, convert function will handle this */ + len = reply->len; + goto copy_len_and_out; + } + } + + switch (resp_type->type) { + case CEXXC_RESPONSE_TYPE_ICA: + if (reply->len < sizeof(struct type86x_reply)) { + msg->rc = -EMSGSIZE; + pr_debug("rpl.len %zu < struct type86_reply, msg.rc=%d\n", + reply->len, msg->rc); + goto out; + } + len = sizeof(struct type86x_reply) + (size_t)t86r->length; + break; + case CEXXC_RESPONSE_TYPE_XCRB: + len = (size_t)t86r->fmt2.offset1 + (size_t)t86r->fmt2.count1; + if (len > reply->len) { + msg->rc = -EMSGSIZE; + pr_debug("offset1 %u count1 %u rpl.len %zu mismatch, msg.rc=%d\n", + t86r->fmt2.offset1, t86r->fmt2.count1, + reply->len, msg->rc); + goto out; + } + if (t86r->fmt2.count2) { + len = (size_t)t86r->fmt2.offset2 + + (size_t)t86r->fmt2.count2; + if (len > reply->len) { msg->rc = -EMSGSIZE; + pr_debug("offset2 %u count2 %u rpl.len %zu mismatch, msg.rc=%d\n", + t86r->fmt2.offset2, t86r->fmt2.count2, + reply->len, msg->rc); goto out; } - memcpy(msg->msg, reply->msg, len); - msg->len = len; - break; - default: - memcpy(msg->msg, &error_reply, sizeof(error_reply)); - msg->len = sizeof(error_reply); } - } else { - memcpy(msg->msg, reply->msg, sizeof(error_reply)); + break; + default: + memcpy(msg->msg, &error_reply, sizeof(error_reply)); msg->len = sizeof(error_reply); + goto out; + } + +copy_len_and_out: + if (len != reply->len) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu rpl.len %zu mismatch, msg.rc=%d\n", + len, reply->len, msg->rc); + goto out; + } + if (len > reply->bufsize || len > msg->bufsize) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu exceeds buf %zu/%zu, msg.rc=%d\n", + len, reply->bufsize, msg->bufsize, msg->rc); + goto out; } + memcpy(msg->msg, reply->msg, len); + msg->len = len; + out: complete(&resp_type->work); } @@ -992,34 +1059,65 @@ static void zcrypt_msgtype6_receive_ep11(struct ap_queue *aq, }; struct ap_response_type *resp_type = &msg->response; struct type86_ep11_reply *t86r; - int len; + size_t minlen, len; /* Copy the reply message to the request message buffer. */ if (!reply) goto out; /* ap_msg->rc indicates the error */ + t86r = reply->msg; - if (t86r->hdr.type == TYPE86_RSP_CODE && - t86r->cprbx.cprb_ver_id == 0x04) { - switch (resp_type->type) { - case CEXXC_RESPONSE_TYPE_EP11: - len = t86r->fmt2.offset1 + t86r->fmt2.count1; - if (len > reply->bufsize || len > msg->bufsize || - len != reply->len) { - pr_debug("len mismatch => EMSGSIZE\n"); - msg->rc = -EMSGSIZE; - goto out; - } - memcpy(msg->msg, reply->msg, len); - msg->len = len; - break; - default: + minlen = sizeof(t86r->hdr) + sizeof(t86r->fmt2) + + offsetof(struct ep11_cprb, cprb_ver_id) + + sizeof(t86r->cprbx.cprb_ver_id); + + if (reply->len < minlen || + t86r->hdr.type != TYPE86_RSP_CODE || + t86r->cprbx.cprb_ver_id != 0x04) { + if (reply->len < sizeof(error_reply)) { + /* total broken reply, use static error reply instead */ memcpy(msg->msg, &error_reply, sizeof(error_reply)); msg->len = sizeof(error_reply); + goto out; + } else { + /* malformed reply, convert function will handle this */ + len = reply->len; + goto copy_len_and_out; } - } else { - memcpy(msg->msg, reply->msg, sizeof(error_reply)); + } + + switch (resp_type->type) { + case CEXXC_RESPONSE_TYPE_EP11: + len = (size_t)t86r->fmt2.offset1 + (size_t)t86r->fmt2.count1; + if (len > reply->len) { + msg->rc = -EMSGSIZE; + pr_debug("offset1 %u count1 %u rpl.len %zu mismatch, msg.rc=%d\n", + t86r->fmt2.offset1, t86r->fmt2.count1, + reply->len, msg->rc); + goto out; + } + break; + default: + memcpy(msg->msg, &error_reply, sizeof(error_reply)); msg->len = sizeof(error_reply); + goto out; + } + +copy_len_and_out: + if (len != reply->len) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu rpl.len %zu mismatch, msg.rc=%d\n", + len, reply->len, msg->rc); + goto out; + } + if (len > reply->bufsize || len > msg->bufsize) { + msg->rc = -EMSGSIZE; + pr_debug("len %zu exceeds buf %zu/%zu, msg.rc=%d\n", + len, reply->bufsize, msg->bufsize, msg->rc); + goto out; } + memcpy(msg->msg, reply->msg, len); + msg->len = len; + out: complete(&resp_type->work); } From 74a31be974dde1d12ed9db131b413c519f8fc429 Mon Sep 17 00:00:00 2001 From: Shirong Zhao Date: Mon, 5 Oct 2026 13:58:25 +0800 Subject: [PATCH 1362/2286] s390/fs3270: Return -ENOTTY for unknown ioctls in fs3270_ioctl() fs3270_ioctl() initializes rc to 0 and its cmd switch has no default branch, so an unrecognized ioctl command returns success without doing anything. Follow the ioctl convention and reject unknown commands with -ENOTTY. [hca@linux.ibm.com: shortened commit description] Signed-off-by: Shirong Zhao Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- drivers/s390/char/fs3270.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c index f8e2cc17ac53be..25f0a85d92fa75 100644 --- a/drivers/s390/char/fs3270.c +++ b/drivers/s390/char/fs3270.c @@ -355,6 +355,9 @@ static long fs3270_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (copy_to_user(argp, &iocb, sizeof(struct raw3270_iocb))) rc = -EFAULT; break; + default: + rc = -ENOTTY; + break; } mutex_unlock(&fs3270_mutex); return rc; From 5ed6d84e9dfe4ace8a96ab2c7b486f0ffe3e67fc Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:15:36 +0200 Subject: [PATCH 1363/2286] xfs: clean up xfs_fsync_flush_log a bit The log_flushed parameter still fits on the previous line, so move it up. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_file.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index d164de6ff98b15..7d0c8a197575cf 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -118,8 +118,7 @@ xfs_fsync_flush_log( if (!seq) return 0; - return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, - log_flushed); + return xfs_log_force_seq(ip->i_mount, seq, XFS_LOG_SYNC, log_flushed); } STATIC int From fe8b1951cd812ec592164cd43ee7a0ce49a79622 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:15:37 +0200 Subject: [PATCH 1364/2286] xfs: optimize cache flushing for CIL commits on multi-device file systems While the flush before writing a start record needs to cover data on the data and possibly RT devices, the flush before writing the commit record iclog only needs to ensure previous iclogs are stable on disk. Split out a new XLOG_ICL_NEED_FLUSH_LOG flag out of XLOG_ICL_NEED_FLUSH to signal that only the log device needs to be flushed. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 6 ++++-- fs/xfs/xfs_log_cil.c | 9 ++++++--- fs/xfs/xfs_log_priv.h | 10 ++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index f4f81d893e8cb2..893ea899a0a875 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -1619,12 +1619,14 @@ xlog_write_iclog( if (iclog->ic_flags & XLOG_ICL_NEED_FLUSH) { if (xlog_flush_data_caches(log)) goto shutdown; - iclog->ic_bio.bi_opf |= REQ_PREFLUSH; } + if (iclog->ic_flags & (XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FLUSH_LOG)) + iclog->ic_bio.bi_opf |= REQ_PREFLUSH; if (iclog->ic_flags & XLOG_ICL_NEED_FUA) iclog->ic_bio.bi_opf |= REQ_FUA; - iclog->ic_flags &= ~(XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FUA); + iclog->ic_flags &= ~(XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FLUSH_LOG | + XLOG_ICL_NEED_FUA); if (is_vmalloc_addr(iclog->ic_header)) { if (!bio_add_vmalloc(&iclog->ic_bio, iclog->ic_header, count)) diff --git a/fs/xfs/xfs_log_cil.c b/fs/xfs/xfs_log_cil.c index 9446ac44ba8851..b54a421cab6363 100644 --- a/fs/xfs/xfs_log_cil.c +++ b/fs/xfs/xfs_log_cil.c @@ -1562,10 +1562,13 @@ xlog_cil_push_work( } /* - * We need to issue a pre-flush so that the ordering for this - * checkpoint is correctly preserved down to stable storage. + * We need to issue a pre-flush on the device containing the log + * so that the ordering for this checkpoint is correctly + * preserved down to stable storage. + * There is no need for an extra flush on devices that only + * contain data or non-log metadata. */ - ctx->commit_iclog->ic_flags |= XLOG_ICL_NEED_FLUSH; + ctx->commit_iclog->ic_flags |= XLOG_ICL_NEED_FLUSH_LOG; } /* diff --git a/fs/xfs/xfs_log_priv.h b/fs/xfs/xfs_log_priv.h index 6d9673c41cdf31..529720a4984b1a 100644 --- a/fs/xfs/xfs_log_priv.h +++ b/fs/xfs/xfs_log_priv.h @@ -71,12 +71,14 @@ enum xlog_iclog_state { /* * In core log flags */ -#define XLOG_ICL_NEED_FLUSH (1u << 0) /* iclog needs REQ_PREFLUSH */ -#define XLOG_ICL_NEED_FUA (1u << 1) /* iclog needs REQ_FUA */ +#define XLOG_ICL_NEED_FLUSH (1u << 0) /* all devs need REQ_PREFLUSH */ +#define XLOG_ICL_NEED_FLUSH_LOG (1u << 1) /* logdev needs REQ_PREFLUSH */ +#define XLOG_ICL_NEED_FUA (1u << 2) /* iclog needs REQ_FUA */ #define XLOG_ICL_STRINGS \ - { XLOG_ICL_NEED_FLUSH, "XLOG_ICL_NEED_FLUSH" }, \ - { XLOG_ICL_NEED_FUA, "XLOG_ICL_NEED_FUA" } + { XLOG_ICL_NEED_FLUSH, "XLOG_ICL_NEED_FLUSH" }, \ + { XLOG_ICL_NEED_FLUSH_LOG, "XLOG_ICL_NEED_FLUSH_LOG" }, \ + { XLOG_ICL_NEED_FUA, "XLOG_ICL_NEED_FUA" } /* From ac81739a869c24a42db15282a44de68623376f27 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:15:38 +0200 Subject: [PATCH 1365/2286] xfs: flush multiple device caches in parallel in xlog_write_iclog When xlog_write_iclog needs to flush the cache for more than one devices, the current implementations does this sequentially, which adds up the flush latency for all devices. Switch to kicking off all cache flushes in parallel so that only the longest latency bounds the time of the log I/O. This removes the REQ_PREFLUSH optimization for the log device, but as that flag is never passed on to the device and just very slightly reduce the latency by queueing the following write from a lower-level context it is trivially shadowed by the latency improvements of the parallel flush commands. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_log.c | 82 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c index 893ea899a0a875..74b324f915a183 100644 --- a/fs/xfs/xfs_log.c +++ b/fs/xfs/xfs_log.c @@ -1544,6 +1544,47 @@ xlog_bio_end_io( &iclog->ic_end_io_work); } +struct xlog_flush_done { + atomic_t pending; + blk_status_t status; + struct completion done; +}; + +static void +xlog_flush_done( + struct xlog_flush_done *done) +{ + if (atomic_dec_and_test(&done->pending)) + complete(&done->done); +} + +static void +xlog_flush_end_io( + struct bio *bio) +{ + struct xlog_flush_done *done = bio->bi_private; + + if (bio->bi_status) + cmpxchg(&done->status, 0, bio->bi_status); + xlog_flush_done(done); + bio_put(bio); +} + +static void +xlog_flush_async( + struct xlog_flush_done *done, + struct block_device *bdev) +{ + struct bio *bio; + + bio = bio_alloc(bdev, 0, REQ_OP_WRITE | REQ_PREFLUSH | REQ_SYNC, + GFP_NOFS); + bio->bi_private = done; + bio->bi_end_io = xlog_flush_end_io; + atomic_inc(&done->pending); + submit_bio(bio); +} + /* * When using multiple devices, we also need to flush the data and RT device * caches first to ensure that all metadata writeback covered by the LSN in @@ -1557,16 +1598,36 @@ xlog_bio_end_io( */ static int xlog_flush_data_caches( - struct xlog *log) + struct xfs_mount *mp, + bool *did_flush) { - struct xfs_mount *mp = log->l_mp; + struct xlog_flush_done done = { + .pending = ATOMIC_INIT(1), + .done = COMPLETION_INITIALIZER_ONSTACK(done.done), + }; - if (log->l_targ != mp->m_ddev_targp) { - if (blkdev_issue_flush(mp->m_ddev_targp->bt_bdev)) - return -EIO; + *did_flush = false; + if (mp->m_ddev_targp != mp->m_logdev_targp && + bdev_write_cache(mp->m_ddev_targp->bt_bdev)) { + xlog_flush_async(&done, mp->m_ddev_targp->bt_bdev); + *did_flush = true; + } + if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp && + bdev_write_cache(mp->m_rtdev_targp->bt_bdev)) { + xlog_flush_async(&done, mp->m_rtdev_targp->bt_bdev); + *did_flush = true; } - if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp) { - if (blkdev_issue_flush(mp->m_rtdev_targp->bt_bdev)) + + if (*did_flush) { + /* + * If we flushed any other device, also use an async flush for + * the log device so that all flushes happen in parallel. + */ + xlog_flush_async(&done, mp->m_logdev_targp->bt_bdev); + + xlog_flush_done(&done); + wait_for_completion(&done.done); + if (done.status) return -EIO; } @@ -1580,6 +1641,8 @@ xlog_write_iclog( uint64_t bno, unsigned int count) { + bool did_flush = false; + ASSERT(bno < log->l_logBBsize); trace_xlog_iclog_write(iclog, _RET_IP_); @@ -1617,10 +1680,11 @@ xlog_write_iclog( iclog->ic_bio.bi_private = iclog; if (iclog->ic_flags & XLOG_ICL_NEED_FLUSH) { - if (xlog_flush_data_caches(log)) + if (xlog_flush_data_caches(log->l_mp, &did_flush)) goto shutdown; } - if (iclog->ic_flags & (XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FLUSH_LOG)) + if (!did_flush && + (iclog->ic_flags & (XLOG_ICL_NEED_FLUSH | XLOG_ICL_NEED_FLUSH_LOG))) iclog->ic_bio.bi_opf |= REQ_PREFLUSH; if (iclog->ic_flags & XLOG_ICL_NEED_FUA) iclog->ic_bio.bi_opf |= REQ_FUA; From f98d007017acf0eaec2e116ff9c0ece735f61bb3 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:01:00 -0700 Subject: [PATCH 1366/2286] xfs: compute the correct fdblocks/frextents for repair when they're negative LOLLM observed that if either fdblocks or frextents are negative, we'll set the CORRUPT flag and exit immediately. This doesn't work for a repair because we still have to walk the filesystem metadata to compute the correct summary counters so that we can install the new values. Currently we just write zeroes into the summary counters, which isn't right. Cc: stable@vger.kernel.org # v6.9 Fixes: 4ed080cd7cb077 ("xfs: repair summary counters") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/fscounters.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index 916699f3da7b1f..cd96393a9e6fcf 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -541,7 +541,10 @@ xchk_fscounters( return -EDEADLOCK; xchk_set_corrupt(sc); - return 0; + + /* Need to compute all the fields in fsc for a repair */ + if (!xchk_could_repair(sc)) + return 0; } /* See if icount is obviously wrong. */ From 2ffc0619c9c2881443c5fffb10db8f2bc0aa2ce8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:01:16 -0700 Subject: [PATCH 1367/2286] xfs: mark cow extents bad if there's no rmap mapping for them LOLLM noticed that we fail to mark cow staging extents as bad if there's a cow staging extent but no rmap record for the cow staging event. Close this gap. Cc: stable@vger.kernel.org # v6.8 Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/cow_repair.c | 84 +++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index 8dd9c0266e2169..e49d9396edc527 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -200,29 +200,35 @@ xrep_cow_mark_missing_staging_rmap( void *priv) { struct xrep_cow *xc = priv; - xfs_agblock_t rec_bno; - xfs_extlen_t rec_len; - unsigned int adj; - - if (rec->rm_owner == XFS_RMAP_OWN_COW) - return 0; + const xfs_agblock_t rec_end = + rec->rm_startblock + rec->rm_blockcount; + int error = 0; - rec_bno = rec->rm_startblock; - rec_len = rec->rm_blockcount; - if (rec_bno < xc->irec_startbno) { - adj = xc->irec_startbno - rec_bno; - rec_len -= adj; - rec_bno += adj; + /* Was there a gap since the last rmap? */ + if (xc->next_bno < rec->rm_startblock) { + error = xrep_cow_mark_file_range(xc, + xfs_gbno_to_fsb(cur->bc_group, xc->next_bno), + rec->rm_startblock - xc->next_bno); + if (error) + return error; } - if (rec_bno + rec_len > xc->irec_startbno + xc->irec.br_blockcount) { - adj = (rec_bno + rec_len) - - (xc->irec_startbno + xc->irec.br_blockcount); - rec_len -= adj; + if (rec->rm_owner != XFS_RMAP_OWN_COW) { + const xfs_agblock_t bad_bno = + max(xc->irec_startbno, rec->rm_startblock); + const xfs_agblock_t irec_end = + xc->irec_startbno + xc->irec.br_blockcount; + const xfs_extlen_t bad_end = min(irec_end, rec_end); + + error = xrep_cow_mark_file_range(xc, + xfs_gbno_to_fsb(cur->bc_group, bad_bno), + bad_end - bad_bno); + if (error) + return error; } - return xrep_cow_mark_file_range(xc, - xfs_gbno_to_fsb(cur->bc_group, rec_bno), rec_len); + xc->next_bno = max(xc->next_bno, rec_end); + return error; } /* @@ -263,10 +269,12 @@ xrep_cow_find_bad( struct xfs_perag *pag; struct xfs_scrub *sc = xc->sc; xfs_agnumber_t agno; + xfs_agblock_t irec_end; int error; agno = XFS_FSB_TO_AGNO(sc->mp, xc->irec.br_startblock); xc->irec_startbno = XFS_FSB_TO_AGBNO(sc->mp, xc->irec.br_startblock); + irec_end = xc->irec_startbno + xc->irec.br_blockcount; pag = xfs_perag_get(sc->mp, agno); if (!pag) @@ -278,7 +286,7 @@ xrep_cow_find_bad( /* Mark any CoW fork extents that are shared. */ rc_low.rc_startblock = xc->irec_startbno; - rc_high.rc_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + rc_high.rc_startblock = irec_end - 1; rc_low.rc_domain = rc_high.rc_domain = XFS_REFC_DOMAIN_SHARED; error = xfs_refcount_query_range(sc->sa.refc_cur, &rc_low, &rc_high, xrep_cow_mark_shared_staging, xc); @@ -287,7 +295,7 @@ xrep_cow_find_bad( /* Make sure there are CoW staging extents for the whole mapping. */ rc_low.rc_startblock = xc->irec_startbno; - rc_high.rc_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + rc_high.rc_startblock = irec_end - 1; rc_low.rc_domain = rc_high.rc_domain = XFS_REFC_DOMAIN_COW; xc->next_bno = xc->irec_startbno; error = xfs_refcount_query_range(sc->sa.refc_cur, &rc_low, &rc_high, @@ -295,11 +303,10 @@ xrep_cow_find_bad( if (error) goto out_sa; - if (xc->next_bno < xc->irec_startbno + xc->irec.br_blockcount) { + if (xc->next_bno < irec_end) { error = xrep_cow_mark_file_range(xc, xfs_agbno_to_fsb(pag, xc->next_bno), - xc->irec_startbno + xc->irec.br_blockcount - - xc->next_bno); + irec_end - xc->next_bno); if (error) goto out_sa; } @@ -308,11 +315,20 @@ xrep_cow_find_bad( rm_low.rm_startblock = xc->irec_startbno; memset(&rm_high, 0xFF, sizeof(rm_high)); rm_high.rm_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + xc->next_bno = xc->irec_startbno; error = xfs_rmap_query_range(sc->sa.rmap_cur, &rm_low, &rm_high, xrep_cow_mark_missing_staging_rmap, xc); if (error) goto out_sa; + if (xc->next_bno < irec_end) { + error = xrep_cow_mark_file_range(xc, + xfs_agbno_to_fsb(pag, xc->next_bno), + irec_end - xc->next_bno); + if (error) + goto out_sa; + } + /* * If userspace is forcing us to rebuild the CoW fork or someone turned * on the debugging knob, replace everything in the CoW fork. @@ -344,9 +360,11 @@ xrep_cow_find_bad_rt( struct xfs_rmap_irec rm_high = { 0 }; struct xfs_scrub *sc = xc->sc; struct xfs_rtgroup *rtg; + xfs_agblock_t irec_end; int error = 0; xc->irec_startbno = xfs_rtb_to_rgbno(sc->mp, xc->irec.br_startblock); + irec_end = xc->irec_startbno + xc->irec.br_blockcount; rtg = xfs_rtgroup_get(sc->mp, xfs_rtb_to_rgno(sc->mp, xc->irec.br_startblock)); @@ -360,7 +378,7 @@ xrep_cow_find_bad_rt( /* Mark any CoW fork extents that are shared. */ rc_low.rc_startblock = xc->irec_startbno; - rc_high.rc_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + rc_high.rc_startblock = irec_end - 1; rc_low.rc_domain = rc_high.rc_domain = XFS_REFC_DOMAIN_SHARED; error = xfs_refcount_query_range(sc->sr.refc_cur, &rc_low, &rc_high, xrep_cow_mark_shared_staging, xc); @@ -369,7 +387,7 @@ xrep_cow_find_bad_rt( /* Make sure there are CoW staging extents for the whole mapping. */ rc_low.rc_startblock = xc->irec_startbno; - rc_high.rc_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + rc_high.rc_startblock = irec_end - 1; rc_low.rc_domain = rc_high.rc_domain = XFS_REFC_DOMAIN_COW; xc->next_bno = xc->irec_startbno; error = xfs_refcount_query_range(sc->sr.refc_cur, &rc_low, &rc_high, @@ -377,11 +395,10 @@ xrep_cow_find_bad_rt( if (error) goto out_sr; - if (xc->next_bno < xc->irec_startbno + xc->irec.br_blockcount) { + if (xc->next_bno < irec_end) { error = xrep_cow_mark_file_range(xc, xfs_rgbno_to_rtb(rtg, xc->next_bno), - xc->irec_startbno + xc->irec.br_blockcount - - xc->next_bno); + irec_end - xc->next_bno); if (error) goto out_sr; } @@ -389,12 +406,21 @@ xrep_cow_find_bad_rt( /* Mark any area has an rmap that isn't a COW staging extent. */ rm_low.rm_startblock = xc->irec_startbno; memset(&rm_high, 0xFF, sizeof(rm_high)); - rm_high.rm_startblock = xc->irec_startbno + xc->irec.br_blockcount - 1; + rm_high.rm_startblock = irec_end - 1; + xc->next_bno = xc->irec_startbno; error = xfs_rmap_query_range(sc->sr.rmap_cur, &rm_low, &rm_high, xrep_cow_mark_missing_staging_rmap, xc); if (error) goto out_sr; + if (xc->next_bno < irec_end) { + error = xrep_cow_mark_file_range(xc, + xfs_rgbno_to_rtb(rtg, xc->next_bno), + irec_end - xc->next_bno); + if (error) + goto out_sr; + } + /* * If userspace is forcing us to rebuild the CoW fork or someone * turned on the debugging knob, replace everything in the From f8d0d90f2e24b1f4a14796ae63cebfd820cadfcd Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:01:31 -0700 Subject: [PATCH 1368/2286] xfs: clear the symlink zapped flag if inline symlink is ok LOLLM noticed that we fail to clear the SYMLINK_ZAPPED flag if an inline symlink checks out ok, even after a repair. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: d9041681dd2f53 ("xfs: set inode sick state flags when we zap either ondisk fork") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/symlink.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/xfs/scrub/symlink.c b/fs/xfs/scrub/symlink.c index 91d40b9fb5c6d2..ed9ca7f737c3cc 100644 --- a/fs/xfs/scrub/symlink.c +++ b/fs/xfs/scrub/symlink.c @@ -70,21 +70,20 @@ xchk_symlink( return 0; } - /* Inline symlink? */ if (ifp->if_format == XFS_DINODE_FMT_LOCAL) { + /* Inline symlink? */ if (len > xfs_inode_data_fork_size(ip) || len > strnlen(ifp->if_data, xfs_inode_data_fork_size(ip))) xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); - return 0; + } else { + /* Remote symlink; must read the contents. */ + error = xfs_symlink_remote_read(sc->ip, sc->buf); + if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) + return error; + if (strnlen(sc->buf, XFS_SYMLINK_MAXLEN) < len) + xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); } - /* Remote symlink; must read the contents. */ - error = xfs_symlink_remote_read(sc->ip, sc->buf); - if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, 0, &error)) - return error; - if (strnlen(sc->buf, XFS_SYMLINK_MAXLEN) < len) - xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); - /* If a remote symlink is clean, it is clearly not zapped. */ xchk_mark_healthy_if_clean(sc, XFS_SICK_INO_SYMLINK_ZAPPED); return 0; From faa9aa9e846332aaad78096a2cebfeafe68a7776 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:01:47 -0700 Subject: [PATCH 1369/2286] xfs: reinitialize dquot block if non-first dquot can't load LOLLM noticed that the early exit logic in xrep_quota_block is defective because it only looks in the first ondisk dquot for corruption that would prevent dquots from loading. We need to scan all the dquots in the block, not just the first one. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 40bb85e8f90927..74eb66f9b890c8 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -294,7 +294,6 @@ xrep_quota_block( xfs_dqid_t id) { struct xfs_dqblk *dqblk; - struct xfs_disk_dquot *ddq; struct xfs_quotainfo *qi = sc->mp->m_quotainfo; struct xfs_def_quota *defq = xfs_get_defquota(qi, dqtype); struct xfs_buf *bp = NULL; @@ -316,14 +315,21 @@ xrep_quota_block( break; case 0: dqblk = bp->b_addr; - ddq = &dqblk[0].dd_diskdq; + error = 0; /* * If there's nothing that would impede a dqiterate, we're * done. */ - if ((ddq->d_type & XFS_DQTYPE_REC_MASK) == dqtype && - id == be32_to_cpu(ddq->d_id)) { + for (i = 0; i < qi->qi_dqperchunk; i++, dqblk++) { + struct xfs_disk_dquot *ddq = &dqblk->dd_diskdq; + + if ((ddq->d_type & XFS_DQTYPE_REC_MASK) != dqtype || + id + i != be32_to_cpu(ddq->d_id)) + error++; + } + + if (!error) { xfs_trans_brelse(sc->tp, bp); return 0; } @@ -336,7 +342,7 @@ xrep_quota_block( dqblk = bp->b_addr; bp->b_ops = &xfs_dquot_buf_ops; for (i = 0; i < qi->qi_dqperchunk; i++, dqblk++) { - ddq = &dqblk->dd_diskdq; + struct xfs_disk_dquot *ddq = &dqblk->dd_diskdq; trace_xrep_disk_dquot(sc->mp, dqtype, id + i); From 65b89ac3e8c2f041384f715a71b9d113edfa32e5 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:02:02 -0700 Subject: [PATCH 1370/2286] xfs: fix inode btree repair when a cluster is larger than a chunk LOLLM noticed that on filesystems where an inode cluster buffer is larger than an inode chunk, we can run through the loop body in xrep_ibt_process_cluster more than once, and the number of inodes that we pass to xrep_ibt_cluster_record is incorrectly large. In this case we can run right off the end of the buffer. Fix this by moving the nr_inodes computation inside the loop. Note that this is only likely to happen on filesystems with (say) 512b inodes and 64k fsblocks. Cc: stable@vger.kernel.org # v6.8 Fixes: dbfbf3bdf639a2 ("xfs: repair inode btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/ialloc_repair.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/ialloc_repair.c b/fs/xfs/scrub/ialloc_repair.c index 46b1c8ac5543fb..43846b3bbf4ed4 100644 --- a/fs/xfs/scrub/ialloc_repair.c +++ b/fs/xfs/scrub/ialloc_repair.c @@ -293,12 +293,8 @@ xrep_ibt_process_cluster( struct xfs_ino_geometry *igeo = M_IGEO(mp); xfs_agino_t cluster_ag_base; xfs_agino_t irec_index; - unsigned int nr_inodes; int error; - nr_inodes = min_t(unsigned int, igeo->inodes_per_cluster, - XFS_INODES_PER_CHUNK); - /* * Grab the inode cluster buffer. This is safe to do with a broken * inobt because imap_to_bp directly maps the buffer without touching @@ -317,6 +313,10 @@ xrep_ibt_process_cluster( for (irec_index = 0; irec_index < igeo->inodes_per_cluster; irec_index += XFS_INODES_PER_CHUNK) { + unsigned int nr_inodes = + min_t(unsigned int, XFS_INODES_PER_CHUNK, + igeo->inodes_per_cluster - irec_index); + error = xrep_ibt_cluster_record(ri, cluster_ag_base + irec_index, cluster_bp, nr_inodes); From 7e55f84434f3a63d59feb8f89076d280ea9aa8e5 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:02:18 -0700 Subject: [PATCH 1371/2286] xfs: fix integer overflow problem when setting large free areas LOLLM points out that the min_t comparison in xrep_rtbitmap_mark_free operates on a 64-bit bitmap word offset quantity and a 32-bit words-per-block ratio. Unfortunately, I should have done the comparison with the (64-bit) xrep_wordoff_t type, but used the 32-bit word count type. Oops. Cc: stable@vger.kernel.org # v6.14 Fixes: 8defee8dff2b20 ("xfs: online repair of realtime bitmaps for a realtime group") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtbitmap_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index 442a17bf972029..f63aaaff783d50 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -276,7 +276,7 @@ xrep_rtbitmap_mark_free( xrep_wordoff_t rem; xrep_wordcnt_t wordcnt; - wordcnt = min_t(xrep_wordcnt_t, nextwordoff - wordoff, + wordcnt = min_t(xrep_wordoff_t, nextwordoff - wordoff, bufwsize); /* From 017dd7a84ff4cd2275eb62f4ee6d63c705bac107 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:02:34 -0700 Subject: [PATCH 1372/2286] xfs: fix buffer overflow in corrupt inline attr structure LOLLM noticed a buffer overrun if the xattr scrubber encounters a file with a shortform attr fork with sf->count > 0 but if_bytes == sizeof(struct xfs_attr_sf_hdr). Basically, we have enough data to have a header with a nonzero count, but no space for any of those shortform entries. In this case, the xfs_attr_sf_nextentry() call will run off the end of if_data and blow up. Cc: stable@vger.kernel.org # v6.4 Fixes: ae0506eba78fd1 ("xfs: check used space of shortform xattr structures") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr.c | 11 +++++++++-- fs/xfs/scrub/attr_repair.c | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c index d5226939c81072..7600c711362583 100644 --- a/fs/xfs/scrub/attr.c +++ b/fs/xfs/scrub/attr.c @@ -590,12 +590,17 @@ xchk_xattr_check_sf( } for (i = 0; i < sf->count; i++) { - unsigned char *name = sfe->nameval; - unsigned char *value = &sfe->nameval[sfe->namelen]; + unsigned char *name; + unsigned char *value; if (xchk_should_terminate(sc, &error)) return error; + if ((unsigned char *)(sfe + 1) >= end) { + xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0); + break; + } + next = xfs_attr_sf_nextentry(sfe); if ((unsigned char *)next > end) { xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0); @@ -618,6 +623,7 @@ xchk_xattr_check_sf( break; } + name = sfe->nameval; if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)name - (char *)sf, sfe->namelen)) { @@ -625,6 +631,7 @@ xchk_xattr_check_sf( break; } + value = &sfe->nameval[sfe->namelen]; if (!xchk_xattr_set_map(sc, ab->usedmap, (char *)value - (char *)sf, sfe->valuelen)) { diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 28f92e9ba72b24..ac433a5467516d 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -477,6 +477,9 @@ xrep_xattr_recover_sf( if (xchk_should_terminate(sc, &error)) return error; + if ((unsigned char *)(sfe + 1) >= end) + break; + next = xfs_attr_sf_nextentry(sfe); if ((unsigned char *)next > end) break; From b339cc253019518c61b8b150fb7ea727fdae59f4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:02:49 -0700 Subject: [PATCH 1373/2286] xfs: actually report corrupt xattrs as XFAIL during cross-referencing Some LLM reports that xchk_dir_parent_pointer really ought to report xattr corruption errors as XFAIL (cross-referencing failed) when a directory scrub tries to find the parent pointer for a dirent name. Let's do that. Cc: stable@vger.kernel.org # v6.10 Fixes: 61b3f0df5c2358 ("xfs: check dirents have parent pointers") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 19d974c7e2b7a9..4ab7e646924828 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -155,6 +155,8 @@ xchk_dir_parent_pointer( &sd->pptr_args); if (error == -ENOATTR) xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0); + else + xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error); return 0; } From 559c9d4259aa4f0a3fc251c4c9ef6aa0c2d08116 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:03:05 -0700 Subject: [PATCH 1374/2286] xfs: improve dir block reporting when cross-referencing dirents to pptrs When we're scrubbing a directory, we could pass the file block number of the dirent that we're scanning to the parent pointer cross-referencing functions. This improves the accuracy of the scrub tracing data if something goes wrong. In practice this matters very little, but the next patch fixes a truncation bug that affects the same tracing, so I decided to clean everything up all at once. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 4ab7e646924828..a63eebf39fd496 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -144,6 +144,7 @@ xchk_dir_lock_child( STATIC int xchk_dir_parent_pointer( struct xchk_dir *sd, + xfs_dablk_t offset, const struct xfs_name *name, struct xfs_inode *ip) { @@ -154,9 +155,10 @@ xchk_dir_parent_pointer( error = xfs_parent_lookup(sc->tp, ip, name, &sd->pptr_rec, &sd->pptr_args); if (error == -ENOATTR) - xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, 0); + xchk_fblock_xref_set_corrupt(sc, XFS_DATA_FORK, offset); else - xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, &error); + xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, offset, + &error); return 0; } @@ -165,7 +167,7 @@ xchk_dir_parent_pointer( STATIC int xchk_dir_check_pptr_fast( struct xchk_dir *sd, - xfs_dir2_dataptr_t dapos, + xfs_dablk_t offset, const struct xfs_name *name, struct xfs_inode *ip) { @@ -180,7 +182,7 @@ xchk_dir_check_pptr_fast( /* No self-referential non-dot or dotdot dirents. */ if (ip == sc->ip) { - xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, 0); + xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); return -ECANCELED; } @@ -197,19 +199,19 @@ xchk_dir_check_pptr_fast( error = xfblob_storename(sd->dir_names, &save_de.name_cookie, name); - if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, + if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, offset, &error)) return error; error = xfarray_append(sd->dir_entries, &save_de); - if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, 0, + if (!xchk_fblock_xref_process_error(sc, XFS_DATA_FORK, offset, &error)) return error; return 0; } - error = xchk_dir_parent_pointer(sd, name, ip); + error = xchk_dir_parent_pointer(sd, offset, name, ip); xfs_iunlock(ip, lockmode); return error; } @@ -300,7 +302,7 @@ xchk_dir_actor( xchk_dir_check_ftype(sc, offset, ip, name->type); if (xfs_has_parent(mp)) { - error = xchk_dir_check_pptr_fast(sd, dapos, name, ip); + error = xchk_dir_check_pptr_fast(sd, offset, name, ip); if (error) goto out_rele; } @@ -1025,7 +1027,7 @@ xchk_dir_slow_dirent( goto out_unlock; check_pptr: - error = xchk_dir_parent_pointer(sd, xname, ip); + error = xchk_dir_parent_pointer(sd, 0, xname, ip); out_unlock: xfs_iunlock(ip, lockmode); out_rele: From f60a2ef8d8a7ac6dedf4d0c3325cc3fc2fe0d948 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:03:20 -0700 Subject: [PATCH 1375/2286] xfs: fix unnecessary dapos truncation in xchk_dir_walk LOLLM points out that the all the implementations of xchk_dirent_fn can handle a signed @dapos parameter, so the truncation here breaks the information captured in scrub tracepoints if the directory is very large. I don't think it ever made sense to do the truncation for the VFS readdir code for programs that can handle 64-bit offsets, but commit 15440319767942 has been around for 17 years without complaints so I'll leave that alone. Stumbled-over: 15440319767942 ("[XFS] truncate readdir offsets to signed 32 bit values") Cc: stable@vger.kernel.org # v6.4 Fixes: 4c233b5c4f29df ("xfs: streamline the directory iteration code for scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/readdir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/readdir.c b/fs/xfs/scrub/readdir.c index d68bf20b4a148d..970d43030704e1 100644 --- a/fs/xfs/scrub/readdir.c +++ b/fs/xfs/scrub/readdir.c @@ -238,7 +238,7 @@ xchk_dir_walk_leaf( dep = bp->b_addr + offset; length = xfs_dir2_data_entsize(mp, dep->namelen); - dapos = xfs_dir2_byte_to_dataptr(curoff) & 0x7fffffff; + dapos = xfs_dir2_byte_to_dataptr(curoff); ino = be64_to_cpu(dep->inumber); name.name = dep->name; name.len = dep->namelen; From e53c620b3670fc0c162b5f768a10a3a17d0c04f6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:03:36 -0700 Subject: [PATCH 1376/2286] xfs: actually lock the metafile reservation when adjusting after repair LOLLM reported that xrep_reset_metafile_resv performs unlocked loads and stores to the metadata file space reservation counters and thus can race with other threads. Fix this by taking the lock. Cc: hch@lst.de Cc: stable@vger.kernel.org # v6.15 Fixes: 1df8d75030b787 ("xfs: make metabtree reservations global") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/repair.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index c2a437416227a8..956aa75218aa24 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -1377,12 +1377,14 @@ xrep_reset_metafile_resv( { struct xfs_mount *mp = sc->mp; int64_t delta; - int error; + int error = 0; + + mutex_lock(&mp->m_metafile_resv_lock); delta = mp->m_metafile_resv_used + mp->m_metafile_resv_avail - mp->m_metafile_resv_target; if (delta == 0) - return 0; + goto out_resv_lock; /* * Too many blocks have been reserved, transfer some from the incore @@ -1398,7 +1400,7 @@ xrep_reset_metafile_resv( mp->m_metafile_resv_avail -= give_back; } - return 0; + goto out_resv_lock; } /* @@ -1412,14 +1414,18 @@ xrep_reset_metafile_resv( if (delta == 0) { xfs_warn(sc->mp, "Insufficient free space to reset metabtree reservation after repair."); - return 0; + error = 0; + goto out_resv_lock; } error = xfs_dec_fdblocks(mp, delta, true); } if (error) - return error; + goto out_resv_lock; xfs_mod_sb_delalloc(mp, delta); mp->m_metafile_resv_avail += delta; - return 0; + +out_resv_lock: + mutex_unlock(&mp->m_metafile_resv_lock); + return error; } From d7c204d4ea7ef0cf0dac908388984c2936f1f94e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Tue, 22 Sep 2026 23:03:52 -0700 Subject: [PATCH 1377/2286] xfs: avoid cross-rtgroup reaping after a repair LOLLM notices that the extents stored in a xrtb_bitmap (aka xfs_rtblock bitmap) can span multiple rtgroups due to two circumstances. The first is that the size of an rtgroup is an exact power of two, which means that rtgroups are adjacent in the segmented xfs_rtblock_t address space. This is fairly common to reduce the amount of multiplication and division needed to handle space on the rt volume. The second is that (unlike in the original rtgroups design), rtgroups do not have fixed-location metadata like AGs do, which means that there's nothing to force a break between rtgroups. Therefore, we must loop through the rtgroups in xreap_rtmeta_extent to avoid running off the end of an rtgroup while reaping. Cc: stable@vger.kernel.org # v6.14 Fixes: fd97fe1112088c ("xfs: fix CoW forks for realtime files") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 59 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index 0dfe61bafc6a03..a251aedb9192c7 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -1080,26 +1080,23 @@ xreap_configure_rgcow_limits( XFS_RTGLOCK_RMAP | \ XFS_RTGLOCK_REFCOUNT) -/* - * Break a rt file metadata extent into sub-extents by fate (crosslinked, not - * crosslinked), and dispose of each sub-extent separately. The extent must - * be aligned to a realtime extent. - */ -STATIC int -xreap_rtmeta_extent( - uint64_t rtbno, +static int +xreap_rtgroup_extent( + struct xreap_state *rs, + xfs_rtblock_t rtbno, uint64_t len, - void *priv) + xfs_rgblock_t *done) { - struct xreap_state *rs = priv; struct xfs_scrub *sc = rs->sc; xfs_rgblock_t rgbno = xfs_rtb_to_rgbno(sc->mp, rtbno); - xfs_rgblock_t rgbno_next = rgbno + len; + xfs_rgblock_t rgbno_next; int error = 0; ASSERT(sc->ip != NULL); ASSERT(!sc->sr.rtg); + *done = 0; + /* * We're reaping blocks after repairing file metadata, which means that * we have to init the xchk_ag structure ourselves. @@ -1110,6 +1107,7 @@ xreap_rtmeta_extent( xfs_rtgroup_lock(sc->sr.rtg, XREAP_RTGLOCK_ALL); + rgbno_next = rgbno + min(len, (uint64_t)rtg_blocks(sc->sr.rtg) - rgbno); while (rgbno < rgbno_next) { xfs_extlen_t rglen; bool crosslinked; @@ -1136,6 +1134,7 @@ xreap_rtmeta_extent( } rgbno += rglen; + *done += rglen; } out_unlock: @@ -1145,6 +1144,44 @@ xreap_rtmeta_extent( return error; } +/* + * Break a rt file metadata extent into sub-extents by fate (crosslinked, not + * crosslinked), and dispose of each sub-extent separately. The extent must + * be aligned to a realtime extent. + */ +STATIC int +xreap_rtmeta_extent( + uint64_t rtbno, + uint64_t len, + void *priv) +{ + struct xreap_state *rs = priv; + int error; + + /* + * Space from adjacent rtgroups can be stored as a single bitmap extent + * because rtgroups do not have fixed-location metadata such as a + * per-group superblock that cannot be reaped. Handle these overlaps + * by only reaping within a single rtgroup at a time. + * + * In the somewhat unlikely event that an rtgroup size is not a power + * of two, the segmented nature of xfs_rtblock_t prevents space in + * adjacent rtgroups from merging in the bitmap. + */ + while (len > 0) { + xfs_rgblock_t done = 0; + + error = xreap_rtgroup_extent(rs, rtbno, len, &done); + if (error) + return error; + + rtbno += done; + len -= done; + } + + return 0; +} + /* * Dispose of every block of every rt metadata extent in the bitmap. * Do not use this to dispose of the mappings in an ondisk inode fork. From b3837dc0c73143f86549c3808e0323a47fe739ad Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Thu, 24 Sep 2026 17:16:28 +0200 Subject: [PATCH 1378/2286] xfs: add xfs_error_report the ability to display an error code Users could opt to request an error code to be printed now. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_error.c | 16 +++++++++++----- fs/xfs/xfs_error.h | 9 +++++---- fs/xfs/xfs_exchmaps_item.c | 9 ++++++--- fs/xfs/xfs_inode_item.c | 3 ++- fs/xfs/xfs_log_recover.c | 3 ++- fs/xfs/xfs_trans.c | 2 +- 6 files changed, 27 insertions(+), 15 deletions(-) diff --git a/fs/xfs/xfs_error.c b/fs/xfs/xfs_error.c index 9ece70fd94ca12..c943717f1dd730 100644 --- a/fs/xfs/xfs_error.c +++ b/fs/xfs/xfs_error.c @@ -241,16 +241,21 @@ void xfs_error_report( const char *tag, int level, + int error, struct xfs_mount *mp, const char *filename, int linenum, xfs_failaddr_t failaddr) { if (level <= xfs_error_level) { - xfs_alert_tag(mp, XFS_PTAG_ERROR_REPORT, - "Internal error %s at line %d of file %s. Caller %pS", - tag, linenum, filename, failaddr); - + if (error) + xfs_alert_tag(mp, XFS_PTAG_ERROR_REPORT, +"Internal error %s (%d) at line %d of file %s. Caller %pS", + tag, error, linenum, filename, failaddr); + else + xfs_alert_tag(mp, XFS_PTAG_ERROR_REPORT, +"Internal error %s at line %d of file %s. Caller %pS", + tag, linenum, filename, failaddr); xfs_stack_trace(); } } @@ -268,7 +273,8 @@ xfs_corruption_error( { if (buf && level <= xfs_error_level) xfs_hex_dump(buf, bufsize); - xfs_error_report(tag, level, mp, filename, linenum, failaddr); + xfs_error_report(tag, level, -EFSCORRUPTED, mp, + filename, linenum, failaddr); xfs_alert(mp, "Corruption detected. Unmount and run xfs_repair"); } diff --git a/fs/xfs/xfs_error.h b/fs/xfs/xfs_error.h index 05fc1d1cf5213b..3c76d0dad51cb5 100644 --- a/fs/xfs/xfs_error.h +++ b/fs/xfs/xfs_error.h @@ -8,8 +8,9 @@ struct xfs_mount; -void xfs_error_report(const char *tag, int level, struct xfs_mount *mp, - const char *filename, int linenum, xfs_failaddr_t failaddr); +void xfs_error_report(const char *tag, int level, int error, + struct xfs_mount *mp, const char *filename, + int linenum, xfs_failaddr_t failaddr); void xfs_corruption_error(const char *tag, int level, struct xfs_mount *mp, const void *buf, size_t bufsize, const char *filename, int linenum, xfs_failaddr_t failaddr); @@ -20,8 +21,8 @@ void xfs_verifier_error(struct xfs_buf *bp, int error, xfs_failaddr_t failaddr); void xfs_inode_verifier_error(struct xfs_inode *ip, int error, const char *name, const void *buf, size_t bufsz, xfs_failaddr_t failaddr); -#define XFS_ERROR_REPORT(e, lvl, mp) \ - xfs_error_report(e, lvl, mp, __FILE__, __LINE__, __return_address) +#define XFS_ERROR_REPORT(e, lvl, error, mp) \ + xfs_error_report(e, lvl, error, mp, __FILE__, __LINE__, __return_address) #define XFS_CORRUPTION_ERROR(e, lvl, mp, buf, bufsize) \ xfs_corruption_error(e, lvl, mp, buf, bufsize, \ __FILE__, __LINE__, __return_address) diff --git a/fs/xfs/xfs_exchmaps_item.c b/fs/xfs/xfs_exchmaps_item.c index dd104bf778baf8..23381cb264c4ed 100644 --- a/fs/xfs/xfs_exchmaps_item.c +++ b/fs/xfs/xfs_exchmaps_item.c @@ -573,13 +573,15 @@ xlog_recover_xmi_commit_pass2( len = sizeof(struct xfs_xmi_log_format); if (item->ri_buf[0].iov_len != len) { - XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, log->l_mp); + XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, + -EFSCORRUPTED, log->l_mp); return -EFSCORRUPTED; } xmi_formatp = item->ri_buf[0].iov_base; if (xmi_formatp->__pad != 0) { - XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, log->l_mp); + XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, + -EFSCORRUPTED, log->l_mp); return -EFSCORRUPTED; } @@ -614,7 +616,8 @@ xlog_recover_xmd_commit_pass2( xmd_formatp = item->ri_buf[0].iov_base; if (item->ri_buf[0].iov_len != sizeof(struct xfs_xmd_log_format)) { - XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, log->l_mp); + XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, + -EFSCORRUPTED, log->l_mp); return -EFSCORRUPTED; } diff --git a/fs/xfs/xfs_inode_item.c b/fs/xfs/xfs_inode_item.c index 99d6ecccdaa777..a29c90f0650fc1 100644 --- a/fs/xfs/xfs_inode_item.c +++ b/fs/xfs/xfs_inode_item.c @@ -1229,7 +1229,8 @@ xfs_inode_item_format_convert( struct xfs_inode_log_format_32 *in_f32 = buf->iov_base; if (buf->iov_len != sizeof(*in_f32)) { - XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, NULL); + XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, + -EFSCORRUPTED, NULL); return -EFSCORRUPTED; } diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c index cf0d610265fe76..4576179796681e 100644 --- a/fs/xfs/xfs_log_recover.c +++ b/fs/xfs/xfs_log_recover.c @@ -2948,7 +2948,8 @@ xlog_recover_process( * fatal log corruption failure. */ if (xfs_has_crc(log->l_mp)) { - XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, log->l_mp); + XFS_ERROR_REPORT(__func__, XFS_ERRLEVEL_LOW, + -EFSCORRUPTED, log->l_mp); return -EFSCORRUPTED; } } diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 1b36cf12d4e3ed..5c522790e5be7b 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -971,7 +971,7 @@ xfs_trans_cancel( * here. */ if (dirty && !xfs_is_shutdown(mp)) { - XFS_ERROR_REPORT("xfs_trans_cancel", XFS_ERRLEVEL_LOW, mp); + XFS_ERROR_REPORT("xfs_trans_cancel", XFS_ERRLEVEL_LOW, 0, mp); xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE); } #ifdef DEBUG From f4922f6fb12b18ff1efe7cf5a46c187a07b7f9ea Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Thu, 24 Sep 2026 17:16:29 +0200 Subject: [PATCH 1379/2286] xfs: introduce xfs_trans_cancel_error() Once in a while it's useful to know exactly what kind of error caused a transaction to be cancelled. Currently xfs_trans_cancel() is unable to print out this information. So rename old xfs_trans_cancel to xfs_trans_cancel_error() which will accept an error code to be printed and add a new inline xfs_trans_cancel() to be used on places where an error number report is not explicitly needed (and also avoid dozens of file modifications to the new API). Signed-off-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_trans.c | 8 +++++--- fs/xfs/xfs_trans.h | 9 ++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/fs/xfs/xfs_trans.c b/fs/xfs/xfs_trans.c index 5c522790e5be7b..8f0c308f312f38 100644 --- a/fs/xfs/xfs_trans.c +++ b/fs/xfs/xfs_trans.c @@ -943,8 +943,9 @@ xfs_trans_commit( * xfs_trans_commit(). */ void -xfs_trans_cancel( - struct xfs_trans *tp) +xfs_trans_cancel_error( + struct xfs_trans *tp, + int error) { struct xfs_mount *mp = tp->t_mountp; struct xlog *log = mp->m_log; @@ -971,7 +972,8 @@ xfs_trans_cancel( * here. */ if (dirty && !xfs_is_shutdown(mp)) { - XFS_ERROR_REPORT("xfs_trans_cancel", XFS_ERRLEVEL_LOW, 0, mp); + XFS_ERROR_REPORT("xfs_trans_cancel", XFS_ERRLEVEL_LOW, + error, mp); xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_INCORE); } #ifdef DEBUG diff --git a/fs/xfs/xfs_trans.h b/fs/xfs/xfs_trans.h index eb83c5dac0320f..edb0cfff4d3cec 100644 --- a/fs/xfs/xfs_trans.h +++ b/fs/xfs/xfs_trans.h @@ -237,7 +237,7 @@ void xfs_trans_log_inode(xfs_trans_t *, struct xfs_inode *, uint); int xfs_trans_commit(struct xfs_trans *); int xfs_trans_roll(struct xfs_trans **); int xfs_trans_roll_inode(struct xfs_trans **, struct xfs_inode *); -void xfs_trans_cancel(xfs_trans_t *); +void xfs_trans_cancel_error(struct xfs_trans *, int); int xfs_trans_ail_init(struct xfs_mount *); void xfs_trans_ail_destroy(struct xfs_mount *); @@ -280,4 +280,11 @@ xfs_trans_clear_context( memalloc_nofs_restore(tp->t_pflags); } +static inline void +xfs_trans_cancel( + struct xfs_trans *tp) +{ + return xfs_trans_cancel_error(tp, 0); +} + #endif /* __XFS_TRANS_H__ */ From 90c4dd2fa8fc3485551023e855c093bd9b675256 Mon Sep 17 00:00:00 2001 From: Carlos Maiolino Date: Thu, 24 Sep 2026 17:16:30 +0200 Subject: [PATCH 1380/2286] xfs: make xfs_iomap_write_direct() report an error to xfs_trans_cancel I've been in the situation a few times where the transaction got cancelled by some reason. Most of time because bmapi_write failed and I start a guessing game trying to understand why. So if xfs_iomap_write_direct fails and it causes the transaction to be cancelled, send the error to xfs_trans_cancel so the error gets logged into the kernel log buffer. Signed-off-by: Carlos Maiolino Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 7c6238fed61e44..4906a4828d1101 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -358,7 +358,7 @@ xfs_iomap_write_direct( return error; out_trans_cancel: - xfs_trans_cancel(tp); + xfs_trans_cancel_error(tp, error); goto out_unlock; } From 8fdbd353e6928492fdc46c359aabd2bfb7630fba Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 5 Oct 2026 04:47:23 -0600 Subject: [PATCH 1381/2286] Revert "docs: landlock: fix ENOMSG condition in create_ruleset kernel-doc" This reverts commit 2ef355c4e68648b9ba77d39c1b0129d06c0c18a2. This issue was fixed independently by commit dfec62ac8b246 ("landlock: Enforce namespace use restrictions"); I should never have really applied this one in the first place. Signed-off-by: Jonathan Corbet --- security/landlock/syscalls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 400a2f928de313..1d02d57f4c48c4 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -204,7 +204,7 @@ const int landlock_abi_version = 11; * scoped; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; - * - %ENOMSG: empty handled_access_fs, handled_access_net, and scoped fields. + * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. * * .. kernel-doc:: include/uapi/linux/landlock.h * :identifiers: landlock_create_ruleset_flags From 2eb8f7b0375111ee23de4e731ef737c411297088 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:29 +0200 Subject: [PATCH 1382/2286] file: let dup_fd() leave the punched hole behind close_range(CLOSE_RANGE_UNSHARE) passed the range it is about to close to dup_fd() so the clone is done without that range. This only works when the last open descriptor falls into the range. A range in the middle of the table is copied like everything else. That's wasteful. Don't copy them. Descriptors in a skipped range stay open in the source fdtable. They are never copied into the new table and no reference is taken on them. Figuring out the size of the table follows the same rule. That drops the special-case it has now. This also means we stop calling ->flush() on fds from a table they were never part of. With the range at the top of the table that was already mostly the case. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-1-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 60 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/fs/file.c b/fs/file.c index 628ca07dc4b179..95dbdfee555a6e 100644 --- a/fs/file.c +++ b/fs/file.c @@ -352,27 +352,51 @@ static inline bool fd_is_open(unsigned int fd, const struct fdtable *fdt) return test_bit(fd, fdt->open_fds); } +/* Bits of [range->from, range->to] that fall into word @i of a bitmap. */ +static unsigned long fd_range_word(struct fd_range *range, unsigned int i) +{ + unsigned int first = i * BITS_PER_LONG; + unsigned int last = first + BITS_PER_LONG - 1; + + if (range->to < first || range->from > last) + return 0; + return GENMASK(min(range->to, last) - first, + max(range->from, first) - first); +} + +/* Bits of word @i that dup_fd() leaves behind. */ +static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *punch_hole) +{ + if (!punch_hole) + return 0; + return fd_range_word(punch_hole, i); +} + /* * Note that a sane fdtable size always has to be a multiple of * BITS_PER_LONG, since we have bitmaps that are sized by this. * * punch_hole is optional - when close_range() is asked to unshare - * and close, we don't need to copy descriptors in that range, so - * a smaller cloned descriptor table might suffice if the last - * currently opened descriptor falls into that range. + * and close, dup_fd() leaves the descriptors in that range behind, + * so the cloned table only has to reach the last open descriptor + * outside of it. */ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punch_hole) { unsigned int last = find_last_bit(fdt->open_fds, fdt->max_fds); + unsigned int i; if (last == fdt->max_fds) return NR_OPEN_DEFAULT; - if (punch_hole && punch_hole->to >= last && punch_hole->from <= last) { - last = find_last_bit(fdt->open_fds, punch_hole->from); - if (last == punch_hole->from) - return NR_OPEN_DEFAULT; + /* Only words up to the last open descriptor can hold a kept one. */ + i = last / BITS_PER_LONG + 1; + while (i--) { + unsigned long dropped = dup_fd_dropped_word(i, punch_hole); + + if (fdt->open_fds[i] & ~dropped) + return (i + 1) * BITS_PER_LONG; } - return ALIGN(last + 1, BITS_PER_LONG); + return NR_OPEN_DEFAULT; } /* @@ -384,7 +408,8 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho { struct files_struct *newf; struct file **old_fds, **new_fds; - unsigned int open_files, i; + unsigned int open_files, fd; + unsigned long dropped = 0; struct fdtable *old_fdt, *new_fdt; newf = kmem_cache_alloc(files_cachep, GFP_KERNEL); @@ -451,13 +476,18 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho * * Instead of trying to placate userspace racing with itself, we * ref the file if we see it and mark the fd slot as unused otherwise. + * Descriptors dup_fd() is asked to leave behind get the same treatment. */ - for (i = open_files; i != 0; i--) { + for (fd = 0; fd < open_files; fd++) { struct file *f = rcu_dereference_raw(*old_fds++); - if (f) { + + if (!(fd % BITS_PER_LONG)) + dropped = dup_fd_dropped_word(fd / BITS_PER_LONG, punch_hole); + if (f && !(dropped & BIT_MASK(fd))) { get_file(f); } else { - __clear_open_fd(open_files - i, new_fdt); + f = NULL; + __clear_open_fd(fd, new_fdt); } rcu_assign_pointer(*new_fds++, f); } @@ -848,10 +878,12 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, swap(cur_fds, fds); } - if (flags & CLOSE_RANGE_CLOEXEC) + if (flags & CLOSE_RANGE_CLOEXEC) { __range_cloexec(cur_fds, fd, max_fd); - else + } else if (!fds) { + /* If we unshared, dup_fd() left the range behind already. */ __range_close(cur_fds, fd, max_fd); + } if (fds) { /* From c5787c34ebcc79cf3793851d2c0d75e66fc15f75 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:30 +0200 Subject: [PATCH 1383/2286] selftests/core: test the hole CLOSE_RANGE_UNSHARE leaves behind Cover a range in the middle of a full word: - the descriptors in it are gone from the clone and the ones around it are still there - the slots are handed out again, the word is not left marked full - the table the child cloned from is untouched Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-2-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/core/close_range_test.c | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c index f14eca63f20c40..afae3462502d4e 100644 --- a/tools/testing/selftests/core/close_range_test.c +++ b/tools/testing/selftests/core/close_range_test.c @@ -236,6 +236,50 @@ TEST(close_range_unshare_capped) EXPECT_EQ(0, WEXITSTATUS(status)); } +TEST(close_range_unshare_hole) +{ + int i, status; + pid_t pid; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + /* Fill the first two words of the table. */ + for (i = 3; i < 128; i++) + ASSERT_GE(dup2(0, i), 0); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + /* Punch a hole into the second word, behind a full first one. */ + if (sys_close_range(70, 80, CLOSE_RANGE_UNSHARE)) + exit(EXIT_FAILURE); + + for (i = 3; i < 128; i++) { + bool closed = i >= 70 && i <= 80; + + if (closed == (fcntl(i, F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + /* A stale full bit on word 1 would hand out 128, not 70. */ + if (dup(0) != 70) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The shared table the child unshared from is untouched. */ + for (i = 3; i < 128; i++) + EXPECT_NE(-1, fcntl(i, F_GETFD)); +} + TEST(close_range_cloexec) { int i, ret; From 4f98366982971b414310181170de714790ee2830 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:31 +0200 Subject: [PATCH 1384/2286] file: rename dup_fd()'s punch_hole to range dup_fd() will be taught what to do with the range that was passed to it. It won't always be dropped. Rename it to a plain "range" from "punch_hole". close_range() keeps a pointer named drop for the one case it has today. No functional changes. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-3-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/fs/file.c b/fs/file.c index 95dbdfee555a6e..6234548be88bfb 100644 --- a/fs/file.c +++ b/fs/file.c @@ -365,23 +365,23 @@ static unsigned long fd_range_word(struct fd_range *range, unsigned int i) } /* Bits of word @i that dup_fd() leaves behind. */ -static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *punch_hole) +static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *range) { - if (!punch_hole) + if (!range) return 0; - return fd_range_word(punch_hole, i); + return fd_range_word(range, i); } /* * Note that a sane fdtable size always has to be a multiple of * BITS_PER_LONG, since we have bitmaps that are sized by this. * - * punch_hole is optional - when close_range() is asked to unshare + * range is optional - when close_range() is asked to unshare * and close, dup_fd() leaves the descriptors in that range behind, * so the cloned table only has to reach the last open descriptor * outside of it. */ -static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punch_hole) +static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *range) { unsigned int last = find_last_bit(fdt->open_fds, fdt->max_fds); unsigned int i; @@ -391,7 +391,7 @@ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punc /* Only words up to the last open descriptor can hold a kept one. */ i = last / BITS_PER_LONG + 1; while (i--) { - unsigned long dropped = dup_fd_dropped_word(i, punch_hole); + unsigned long dropped = dup_fd_dropped_word(i, range); if (fdt->open_fds[i] & ~dropped) return (i + 1) * BITS_PER_LONG; @@ -402,9 +402,9 @@ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *punc /* * Allocate a new descriptor table and copy contents from the passed in * instance. Returns a pointer to cloned table on success, ERR_PTR() - * on failure. For 'punch_hole' see sane_fdtable_size(). + * on failure. For 'range' see sane_fdtable_size(). */ -struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_hole) +struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *range) { struct files_struct *newf; struct file **old_fds, **new_fds; @@ -431,7 +431,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho spin_lock(&oldf->file_lock); old_fdt = files_fdtable(oldf); - open_files = sane_fdtable_size(old_fdt, punch_hole); + open_files = sane_fdtable_size(old_fdt, range); /* * Check whether we need to allocate a larger fd array and fd set. @@ -455,7 +455,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho */ spin_lock(&oldf->file_lock); old_fdt = files_fdtable(oldf); - open_files = sane_fdtable_size(old_fdt, punch_hole); + open_files = sane_fdtable_size(old_fdt, range); } copy_fd_bitmaps(new_fdt, old_fdt, open_files / BITS_PER_LONG); @@ -482,7 +482,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *punch_ho struct file *f = rcu_dereference_raw(*old_fds++); if (!(fd % BITS_PER_LONG)) - dropped = dup_fd_dropped_word(fd / BITS_PER_LONG, punch_hole); + dropped = dup_fd_dropped_word(fd / BITS_PER_LONG, range); if (f && !(dropped & BIT_MASK(fd))) { get_file(f); } else { @@ -858,7 +858,7 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, return -EINVAL; if ((flags & CLOSE_RANGE_UNSHARE) && atomic_read(&cur_fds->count) > 1) { - struct fd_range range = {fd, max_fd}, *punch_hole = ⦥ + struct fd_range range = {fd, max_fd}, *drop = ⦥ /* * If the caller requested all fds to be made cloexec we always @@ -866,9 +866,9 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, * use them. */ if (flags & CLOSE_RANGE_CLOEXEC) - punch_hole = NULL; + drop = NULL; - fds = dup_fd(cur_fds, punch_hole); + fds = dup_fd(cur_fds, drop); if (IS_ERR(fds)) return PTR_ERR(fds); /* From 6395f928f321ba6f057c101675af121b0eb9d9eb Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:32 +0200 Subject: [PATCH 1385/2286] file: let dup_fd() drop everything outside of the range Add FD_RANGE_EXCEPT. It turns the meaning of range around. Instead of indicating that the descriptors in the range are the ones that are left out of the copy they indicate the range that makes it into the copy. All other files are left behind. The clone only has to reach the last open descriptor inside the range. Nothing passes the flag yet. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-4-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 15 ++++++++++----- include/linux/fdtable.h | 6 ++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/fs/file.c b/fs/file.c index 6234548be88bfb..ae7d021398dda5 100644 --- a/fs/file.c +++ b/fs/file.c @@ -367,19 +367,24 @@ static unsigned long fd_range_word(struct fd_range *range, unsigned int i) /* Bits of word @i that dup_fd() leaves behind. */ static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *range) { + unsigned long dropped; + if (!range) return 0; - return fd_range_word(range, i); + dropped = fd_range_word(range, i); + if (range->flags & FD_RANGE_EXCEPT) + dropped = ~dropped; + return dropped; } /* * Note that a sane fdtable size always has to be a multiple of * BITS_PER_LONG, since we have bitmaps that are sized by this. * - * range is optional - when close_range() is asked to unshare - * and close, dup_fd() leaves the descriptors in that range behind, - * so the cloned table only has to reach the last open descriptor - * outside of it. + * range is optional. When close_range() is asked to unshare dup_fd() + * will leave any files behind according to the range and its flags. The + * cloned table only has to reach the last open descriptor that is + * carried over. */ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *range) { diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index c45306a9f00723..d6c6c7a3400d94 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -101,8 +101,14 @@ struct task_struct; void put_files_struct(struct files_struct *fs); int unshare_files(void); +enum fd_range_flags { + /* Leave behind all descriptors outside of the specified range. */ + FD_RANGE_EXCEPT = (1U << 0), +}; + struct fd_range { unsigned int from, to; + enum fd_range_flags flags; }; struct files_struct *dup_fd(struct files_struct *, struct fd_range *) __latent_entropy; void do_close_on_exec(struct files_struct *); From 48629a611531379bc5b792dc6f63ac9bb56ca537 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:33 +0200 Subject: [PATCH 1386/2286] close_range: turn the flags into an enum Turn the close_range() flags into an enum. Makes debugging a lot easier. No functional changes. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-5-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/close_range.h | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/include/uapi/linux/close_range.h b/include/uapi/linux/close_range.h index 2d804281554c5c..b1b637d74deb2b 100644 --- a/include/uapi/linux/close_range.h +++ b/include/uapi/linux/close_range.h @@ -2,11 +2,24 @@ #ifndef _UAPI_LINUX_CLOSE_RANGE_H #define _UAPI_LINUX_CLOSE_RANGE_H -/* Unshare the file descriptor table before closing file descriptors. */ -#define CLOSE_RANGE_UNSHARE (1U << 1) +/* + * A macro of one of these names defined before this header is parsed, by + * a libc or by a program's own fallback, would replace the enumerator. + */ +#undef CLOSE_RANGE_UNSHARE +#undef CLOSE_RANGE_CLOEXEC -/* Set the FD_CLOEXEC bit instead of closing the file descriptor. */ -#define CLOSE_RANGE_CLOEXEC (1U << 2) +enum close_range_flags { + /* Unshare the file descriptor table before closing file descriptors. */ + CLOSE_RANGE_UNSHARE = (1U << 1), + + /* Set the FD_CLOEXEC bit instead of closing the file descriptor. */ + CLOSE_RANGE_CLOEXEC = (1U << 2), +}; + +/* Keep #ifdef working and let glibc skip its own definitions. */ +#define CLOSE_RANGE_UNSHARE CLOSE_RANGE_UNSHARE +#define CLOSE_RANGE_CLOEXEC CLOSE_RANGE_CLOEXEC #endif /* _UAPI_LINUX_CLOSE_RANGE_H */ From 6939539684ce6ee5cea49c9f2b22177a27c73c67 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:34 +0200 Subject: [PATCH 1387/2286] file: add CLOSE_RANGE_EXCEPT close_range() operates on [fd, max_fd]. Add CLOSE_RANGE_EXCEPT. This new flag instructs it to operate on all file descriptors outside of the range instead. Without any other flags that means it closes everything except the file descriptors in the specified range. Together with CLOSE_RANGE_CLOEXEC it marks every file descriptor except the ones in the range as close-on-exec. With CLOSE_RANGE_UNSHARE dup_fd() never takes a reference on what is dropped. A task between clone(CLONE_FILES | CLONE_VM | CLONE_VFORK) and execve() that has the descriptors for the child in one window can shed the rest of the shared table in one call: close_range(lo, hi, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT) Nothing outside of the window is referenced by the child at any point. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-6-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 76 +++++++++++++++++++++++++------- include/uapi/linux/close_range.h | 5 +++ 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/fs/file.c b/fs/file.c index ae7d021398dda5..39651c7a992e5b 100644 --- a/fs/file.c +++ b/fs/file.c @@ -794,34 +794,67 @@ static inline unsigned last_fd(struct fdtable *fdt) } static inline void __range_cloexec(struct files_struct *cur_fds, - unsigned int fd, unsigned int max_fd) + struct fd_range *range) { struct fdtable *fdt; + unsigned int last; - /* make sure we're using the correct maximum value */ spin_lock(&cur_fds->file_lock); fdt = files_fdtable(cur_fds); - max_fd = min(last_fd(fdt), max_fd); - if (fd <= max_fd) - bitmap_set(fdt->close_on_exec, fd, max_fd - fd + 1); + /* make sure we're using the correct maximum value */ + last = last_fd(fdt); + if (!(range->flags & FD_RANGE_EXCEPT)) { + if (range->from <= last) + bitmap_set(fdt->close_on_exec, range->from, + min(range->to, last) - range->from + 1); + } else { + if (range->from > 0) + bitmap_set(fdt->close_on_exec, 0, + min(range->from - 1, last) + 1); + if (range->to < last) + bitmap_set(fdt->close_on_exec, range->to + 1, + last - range->to); + } spin_unlock(&cur_fds->file_lock); } -static inline void __range_close(struct files_struct *files, unsigned int fd, - unsigned int max_fd) +/* Next open descriptor in [fd, max_fd] that @range selects. */ +static inline unsigned int next_fd_to_close(struct fdtable *fdt, + unsigned int fd, unsigned int max_fd, + struct fd_range *range) +{ + fd = find_next_bit(fdt->open_fds, max_fd + 1, fd); + /* Hop over the window the range keeps. */ + if ((range->flags & FD_RANGE_EXCEPT) && + fd >= range->from && fd <= range->to) { + if (range->to >= max_fd) + return max_fd + 1; + fd = find_next_bit(fdt->open_fds, max_fd + 1, range->to + 1); + } + return fd; +} + +static inline void __range_close(struct files_struct *files, + struct fd_range *range) { struct file *file; struct fdtable *fdt; - unsigned n; + unsigned int fd, max_fd; spin_lock(&files->file_lock); fdt = files_fdtable(files); - n = last_fd(fdt); - max_fd = min(max_fd, n); + if (range->flags & FD_RANGE_EXCEPT) { + /* Outside of the range means the whole table. */ + fd = 0; + max_fd = last_fd(fdt); + } else { + fd = range->from; + max_fd = min(range->to, last_fd(fdt)); + } - for (fd = find_next_bit(fdt->open_fds, max_fd + 1, fd); + for (fd = next_fd_to_close(fdt, fd, max_fd, range); fd <= max_fd; - fd = find_next_bit(fdt->open_fds, max_fd + 1, fd + 1)) { + fd = next_fd_to_close(fdt, fd + 1, max_fd, range)) { file = file_close_fd_locked(files, fd); if (file) { spin_unlock(&files->file_lock); @@ -849,21 +882,30 @@ static inline void __range_close(struct files_struct *files, unsigned int fd, * This closes a range of file descriptors. All file descriptors * from @fd up to and including @max_fd are closed. * Currently, errors to close a given file descriptor are ignored. + * + * With CLOSE_RANGE_EXCEPT the range names what to leave alone instead: + * every open file descriptor outside of [@fd, @max_fd] is closed, or + * marked close-on-exec with CLOSE_RANGE_CLOEXEC. */ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, unsigned int, flags) { struct task_struct *me = current; struct files_struct *cur_fds = me->files, *fds = NULL; + struct fd_range range = {fd, max_fd}; - if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC)) + if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC | + CLOSE_RANGE_EXCEPT)) return -EINVAL; if (fd > max_fd) return -EINVAL; + if (flags & CLOSE_RANGE_EXCEPT) + range.flags |= FD_RANGE_EXCEPT; + if ((flags & CLOSE_RANGE_UNSHARE) && atomic_read(&cur_fds->count) > 1) { - struct fd_range range = {fd, max_fd}, *drop = ⦥ + struct fd_range *drop = ⦥ /* * If the caller requested all fds to be made cloexec we always @@ -884,10 +926,10 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, } if (flags & CLOSE_RANGE_CLOEXEC) { - __range_cloexec(cur_fds, fd, max_fd); + __range_cloexec(cur_fds, &range); } else if (!fds) { - /* If we unshared, dup_fd() left the range behind already. */ - __range_close(cur_fds, fd, max_fd); + /* If we unshared, dup_fd() already left behind what we'd close. */ + __range_close(cur_fds, &range); } if (fds) { diff --git a/include/uapi/linux/close_range.h b/include/uapi/linux/close_range.h index b1b637d74deb2b..39eddb3ab6138f 100644 --- a/include/uapi/linux/close_range.h +++ b/include/uapi/linux/close_range.h @@ -8,6 +8,7 @@ */ #undef CLOSE_RANGE_UNSHARE #undef CLOSE_RANGE_CLOEXEC +#undef CLOSE_RANGE_EXCEPT enum close_range_flags { /* Unshare the file descriptor table before closing file descriptors. */ @@ -15,11 +16,15 @@ enum close_range_flags { /* Set the FD_CLOEXEC bit instead of closing the file descriptor. */ CLOSE_RANGE_CLOEXEC = (1U << 2), + + /* Act on every file descriptor outside of the given range instead. */ + CLOSE_RANGE_EXCEPT = (1U << 3), }; /* Keep #ifdef working and let glibc skip its own definitions. */ #define CLOSE_RANGE_UNSHARE CLOSE_RANGE_UNSHARE #define CLOSE_RANGE_CLOEXEC CLOSE_RANGE_CLOEXEC +#define CLOSE_RANGE_EXCEPT CLOSE_RANGE_EXCEPT #endif /* _UAPI_LINUX_CLOSE_RANGE_H */ From 4ee7588bbbfa67ebea345e1e36fbb370872c1e53 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:35 +0200 Subject: [PATCH 1388/2286] selftests/core: test CLOSE_RANGE_EXCEPT Cover the inverted range: - with plain close the window stays and everything outside of it goes, stdio included - a window at the top, one that cannot hold a descriptor and one of a single descriptor keep just what they name, and so does the unshare form on a table that is not shared - with CLOSE_RANGE_CLOEXEC everything outside of the window is marked and the window is not, in place and in a clone, for a window in the middle, at the bottom, at the top and above the table - with CLOSE_RANGE_UNSHARE the table the child cloned from is untouched, a window near the top of the table comes back whole, one at the bottom keeps stdio, one that cannot hold a descriptor keeps nothing, and the slots left behind are handed out again from the bottom - the bounds are checked before the range is turned around The extra cases came out of walking the window positions that __range_close(), __range_cloexec() and dup_fd() tell apart: at the bottom, in the middle, at the top, above the table, a single descriptor and none, on a shared and on a private table. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-7-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/core/close_range_test.c | 426 ++++++++++++++++++ 1 file changed, 426 insertions(+) diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c index afae3462502d4e..caeb2f1ea800a5 100644 --- a/tools/testing/selftests/core/close_range_test.c +++ b/tools/testing/selftests/core/close_range_test.c @@ -36,6 +36,14 @@ static inline int sys_close_range(unsigned int fd, unsigned int max_fd, return syscall(__NR_close_range, fd, max_fd, flags); } +static void clear_cloexec(const int *fds, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) + fcntl(fds[i], F_SETFD, 0); +} + TEST(core_close_range) { int i, ret; @@ -637,6 +645,424 @@ TEST(close_range_cloexec_unshare_syzbot) EXPECT_EQ(close(fd3), 0); } +TEST(close_range_except) +{ + int i, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); + + /* The bounds are checked before the range is turned around. */ + EXPECT_EQ(-1, sys_close_range(open_fds[20], open_fds[10], + CLOSE_RANGE_EXCEPT)); + EXPECT_EQ(EINVAL, errno); + + /* Everything above open_fds[50] goes. */ + ASSERT_EQ(0, sys_close_range(0, open_fds[50], CLOSE_RANGE_EXCEPT)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(i <= 50, fcntl(open_fds[i], F_GETFD) != -1); + + /* A window in the middle takes stdio with it, so do that in a fork. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i <= 50; i++) { + bool kept = i >= 10 && i <= 20; + + if (kept != (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + if (fcntl(STDERR_FILENO, F_GETFD) != -1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The fork had a table of its own. */ + for (i = 0; i <= 50; i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + +TEST(close_range_except_cloexec) +{ + int i, ret; + int open_fds[101]; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool inside = i >= 10 && i <= 20; + int flags = fcntl(open_fds[i], F_GETFD); + + EXPECT_NE(-1, flags); + EXPECT_EQ(inside ? 0 : FD_CLOEXEC, flags & FD_CLOEXEC); + } + + /* stdio sits outside of the window too. */ + EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC); + + /* A window that starts at 0 marks only what lies above it. */ + clear_cloexec(open_fds, ARRAY_SIZE(open_fds)); + ASSERT_EQ(0, fcntl(STDERR_FILENO, F_SETFD, 0)); + ASSERT_EQ(0, sys_close_range(0, open_fds[20], + CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(i <= 20 ? 0 : FD_CLOEXEC, + fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC); + EXPECT_EQ(0, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC); + + /* One at the top marks only what lies below it, stdio included. */ + clear_cloexec(open_fds, ARRAY_SIZE(open_fds)); + ASSERT_EQ(0, sys_close_range(open_fds[80], UINT_MAX, + CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(i < 80 ? FD_CLOEXEC : 0, + fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC); + EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC); + + /* One that cannot hold a descriptor marks everything. */ + clear_cloexec(open_fds, ARRAY_SIZE(open_fds)); + ASSERT_EQ(0, fcntl(STDERR_FILENO, F_SETFD, 0)); + ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX, + CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(FD_CLOEXEC, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC); + EXPECT_EQ(FD_CLOEXEC, fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC); +} + +TEST(close_range_except_cloexec_unshare) +{ + int i, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything marks nothing. */ + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(0, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool inside = i >= 10 && i <= 20; + int flags = fcntl(open_fds[i], F_GETFD); + + if (flags == -1) + exit(EXIT_FAILURE); + if ((flags & FD_CLOEXEC) != (inside ? 0 : FD_CLOEXEC)) + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The shared table the child unshared from is untouched. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(0, fcntl(open_fds[i], F_GETFD) & FD_CLOEXEC); +} + +TEST(close_range_except_bounds) +{ + int i, c, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT"); + } + ASSERT_EQ(0, ret); + + struct { + unsigned int fd, max_fd, flags; + } cases[] = { + /* A window at the top drops everything below it. */ + { open_fds[50], UINT_MAX, CLOSE_RANGE_EXCEPT }, + /* One that cannot hold a descriptor keeps nothing. */ + { UINT_MAX, UINT_MAX, CLOSE_RANGE_EXCEPT }, + /* One of a single descriptor keeps just that. */ + { open_fds[30], open_fds[30], CLOSE_RANGE_EXCEPT }, + /* The unshare form on a table that is not shared acts in place. */ + { open_fds[10], open_fds[20], + CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT }, + }; + + /* Each of them takes stdio with it, so do that in a fork. */ + for (c = 0; c < ARRAY_SIZE(cases); c++) { + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(cases[c].fd, cases[c].max_fd, + cases[c].flags); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + unsigned int fd = open_fds[i]; + bool kept = fd >= cases[c].fd && + fd <= cases[c].max_fd; + + if (kept != (fcntl(fd, F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + if (fcntl(STDERR_FILENO, F_GETFD) != -1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + } + + /* Each fork had a table of its own. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + +TEST(close_range_except_unshare) +{ + int i, ret, status; + pid_t pid; + int open_fds[200]; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + /* Odd slots are close-on-exec, which makes no difference here. */ + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_EXCEPT"); + } + ASSERT_EQ(0, ret); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + /* The window sits near the top, so the clone is sized off its end. */ + ret = sys_close_range(open_fds[150], open_fds[160], + CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool kept = i >= 150 && i <= 160; + + if (kept != (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + if (fcntl(STDERR_FILENO, F_GETFD) != -1) + exit(EXIT_FAILURE); + + /* What was left behind is handed out again, from the bottom. */ + if (dup(open_fds[150]) != 0) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* A window at the bottom keeps just that, stdio included. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(0, open_fds[10], + CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + if ((i <= 10) != (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + if (fcntl(STDERR_FILENO, F_GETFD) == -1) + exit(EXIT_FAILURE); + + /* The first slot left behind is the next one handed out. */ + if (dup(0) != open_fds[10] + 1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* A window that cannot hold a descriptor keeps nothing. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(UINT_MAX, UINT_MAX, + CLOSE_RANGE_UNSHARE | CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + if (fcntl(open_fds[i], F_GETFD) != -1) + exit(EXIT_FAILURE); + + if (fcntl(STDERR_FILENO, F_GETFD) != -1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The shared table the child unshared from is untouched. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + TEST(close_range_bitmap_corruption) { pid_t pid; From 4587f99cc2c87079167b18e284f64b66cafade76 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:36 +0200 Subject: [PATCH 1389/2286] file: let dup_fd() drop only close-on-exec descriptors Add FD_RANGE_CLOEXEC_ONLY. When set dup_fd() leaves everything behind except for fds that are close-on-exec. Without FD_RANGE_EXCEPT the clone loses the close-on-exec descriptors in the range. With it the clone keeps the range and loses the close-on-exec descriptors everywhere else. Descriptors without the flag are carried over either way. Nothing passes the flag yet. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-8-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 43 ++++++++++++++++++++++++++++++++--------- include/linux/fdtable.h | 3 +++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/fs/file.c b/fs/file.c index 39651c7a992e5b..8952efd2cf3aff 100644 --- a/fs/file.c +++ b/fs/file.c @@ -365,7 +365,8 @@ static unsigned long fd_range_word(struct fd_range *range, unsigned int i) } /* Bits of word @i that dup_fd() leaves behind. */ -static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *range) +static unsigned long dup_fd_dropped_word(struct fdtable *fdt, unsigned int i, + struct fd_range *range) { unsigned long dropped; @@ -374,6 +375,8 @@ static unsigned long dup_fd_dropped_word(unsigned int i, struct fd_range *range) dropped = fd_range_word(range, i); if (range->flags & FD_RANGE_EXCEPT) dropped = ~dropped; + if (range->flags & FD_RANGE_CLOEXEC_ONLY) + dropped &= fdt->close_on_exec[i]; return dropped; } @@ -393,15 +396,37 @@ static unsigned int sane_fdtable_size(struct fdtable *fdt, struct fd_range *rang if (last == fdt->max_fds) return NR_OPEN_DEFAULT; - /* Only words up to the last open descriptor can hold a kept one. */ - i = last / BITS_PER_LONG + 1; - while (i--) { - unsigned long dropped = dup_fd_dropped_word(i, range); + if (!range) + return ALIGN(last + 1, BITS_PER_LONG); + + if (range->flags & FD_RANGE_CLOEXEC_ONLY) { + /* The close-on-exec bits decide what is dropped, walk the words. */ + i = last / BITS_PER_LONG + 1; + while (i--) { + unsigned long dropped = dup_fd_dropped_word(fdt, i, range); + + if (fdt->open_fds[i] & ~dropped) + return (i + 1) * BITS_PER_LONG; + } + return NR_OPEN_DEFAULT; + } - if (fdt->open_fds[i] & ~dropped) - return (i + 1) * BITS_PER_LONG; + if (range->flags & FD_RANGE_EXCEPT) { + /* Only the range is carried over. */ + if (last > range->to) { + last = find_last_bit(fdt->open_fds, range->to + 1); + if (last > range->to) + return NR_OPEN_DEFAULT; + } + if (last < range->from) + return NR_OPEN_DEFAULT; + } else if (last >= range->from && last <= range->to) { + /* The last open descriptor goes, the kept ones sit below the range. */ + last = find_last_bit(fdt->open_fds, range->from); + if (last == range->from) + return NR_OPEN_DEFAULT; } - return NR_OPEN_DEFAULT; + return ALIGN(last + 1, BITS_PER_LONG); } /* @@ -487,7 +512,7 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *range) struct file *f = rcu_dereference_raw(*old_fds++); if (!(fd % BITS_PER_LONG)) - dropped = dup_fd_dropped_word(fd / BITS_PER_LONG, range); + dropped = dup_fd_dropped_word(old_fdt, fd / BITS_PER_LONG, range); if (f && !(dropped & BIT_MASK(fd))) { get_file(f); } else { diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index d6c6c7a3400d94..afdfaad381df16 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -104,6 +104,9 @@ int unshare_files(void); enum fd_range_flags { /* Leave behind all descriptors outside of the specified range. */ FD_RANGE_EXCEPT = (1U << 0), + + /* Only select descriptors that have close-on-exec set. */ + FD_RANGE_CLOEXEC_ONLY = (1U << 1), }; struct fd_range { From 06074fbbf092b1a6292c108eef2b94f43333ea1c Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:37 +0200 Subject: [PATCH 1390/2286] file: add CLOSE_RANGE_CLOEXEC_ONLY CLOSE_RANGE_CLOEXEC marks a range close-on-exec. Nothing happens to these fds unless an exec happens. Add CLOSE_RANGE_CLOEXEC_ONLY which closes the close-on-exec file descriptors in the range. When combined with CLOSE_RANGE_EXCEPT it names the close-on-exec file descriptors that are supposed to survive. Every close-on-exec fd outside of the range is closed. File descriptors without that flag are left alone. Whatever the caller deliberately passes down, stdio, LISTEN_FDS, an inherited pipe, remains where it is. The handful of close-on-exec descriptors the caller still needs for the exec such as the executable, an error pipe, sit in a specific range. That is what a task between clone(CLONE_FILES) and execve() actually wants: clone(CLONE_FILES | CLONE_VM | CLONE_VFORK) child: close_range(lo, hi, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT) child: rearrange descriptors in the now private table child: execve() Between the clone and the close_range() the child holds no reference of its own on any file because copy_files() only bumps the fdtable refcount. So a close() in the parent takes effect immediately. The unshare also never takes a reference on the fds it leaves behind either. The range is expressed in the parent's numbering. So a caller that cannot name the file descriptors it keeps contiguously picks a range wide enough to cover them, unshares, and tidies up with a second close_range() on the table it now owns alone. That one is cheap. To keep nothing, name a range that cannot hold an open descriptor, e.g. close_range(~0U, ~0U, ...). CLOSE_RANGE_CLOEXEC and CLOSE_RANGE_CLOEXEC_ONLY are mutually exclusive. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-9-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 30 +++++++++++++++++++++++++++--- include/uapi/linux/close_range.h | 5 +++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/fs/file.c b/fs/file.c index 8952efd2cf3aff..37f0ba740c393b 100644 --- a/fs/file.c +++ b/fs/file.c @@ -843,18 +843,29 @@ static inline void __range_cloexec(struct files_struct *cur_fds, spin_unlock(&cur_fds->file_lock); } +/* Next open descriptor in [fd, max_fd], or the next close-on-exec one. */ +static inline unsigned int next_open_fd(struct fdtable *fdt, unsigned int fd, + unsigned int max_fd, + struct fd_range *range) +{ + if (range->flags & FD_RANGE_CLOEXEC_ONLY) + return find_next_and_bit(fdt->open_fds, fdt->close_on_exec, + max_fd + 1, fd); + return find_next_bit(fdt->open_fds, max_fd + 1, fd); +} + /* Next open descriptor in [fd, max_fd] that @range selects. */ static inline unsigned int next_fd_to_close(struct fdtable *fdt, unsigned int fd, unsigned int max_fd, struct fd_range *range) { - fd = find_next_bit(fdt->open_fds, max_fd + 1, fd); + fd = next_open_fd(fdt, fd, max_fd, range); /* Hop over the window the range keeps. */ if ((range->flags & FD_RANGE_EXCEPT) && fd >= range->from && fd <= range->to) { if (range->to >= max_fd) return max_fd + 1; - fd = find_next_bit(fdt->open_fds, max_fd + 1, range->to + 1); + fd = next_open_fd(fdt, range->to + 1, max_fd, range); } return fd; } @@ -911,6 +922,12 @@ static inline void __range_close(struct files_struct *files, * With CLOSE_RANGE_EXCEPT the range names what to leave alone instead: * every open file descriptor outside of [@fd, @max_fd] is closed, or * marked close-on-exec with CLOSE_RANGE_CLOEXEC. + * + * With CLOSE_RANGE_CLOEXEC_ONLY only file descriptors that have + * close-on-exec set are closed. Together with CLOSE_RANGE_EXCEPT the + * range names the close-on-exec file descriptors to keep. To keep none + * of them, name a range that cannot hold an open file descriptor, e.g. + * close_range(~0U, ~0U, ...). */ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, unsigned int, flags) @@ -920,7 +937,12 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, struct fd_range range = {fd, max_fd}; if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC | - CLOSE_RANGE_EXCEPT)) + CLOSE_RANGE_EXCEPT | CLOSE_RANGE_CLOEXEC_ONLY)) + return -EINVAL; + + /* One marks close-on-exec, the other closes what is marked. */ + if (hweight32(flags & (CLOSE_RANGE_CLOEXEC | + CLOSE_RANGE_CLOEXEC_ONLY)) > 1) return -EINVAL; if (fd > max_fd) @@ -928,6 +950,8 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, if (flags & CLOSE_RANGE_EXCEPT) range.flags |= FD_RANGE_EXCEPT; + if (flags & CLOSE_RANGE_CLOEXEC_ONLY) + range.flags |= FD_RANGE_CLOEXEC_ONLY; if ((flags & CLOSE_RANGE_UNSHARE) && atomic_read(&cur_fds->count) > 1) { struct fd_range *drop = ⦥ diff --git a/include/uapi/linux/close_range.h b/include/uapi/linux/close_range.h index 39eddb3ab6138f..7da9ed95258a2b 100644 --- a/include/uapi/linux/close_range.h +++ b/include/uapi/linux/close_range.h @@ -9,6 +9,7 @@ #undef CLOSE_RANGE_UNSHARE #undef CLOSE_RANGE_CLOEXEC #undef CLOSE_RANGE_EXCEPT +#undef CLOSE_RANGE_CLOEXEC_ONLY enum close_range_flags { /* Unshare the file descriptor table before closing file descriptors. */ @@ -19,12 +20,16 @@ enum close_range_flags { /* Act on every file descriptor outside of the given range instead. */ CLOSE_RANGE_EXCEPT = (1U << 3), + + /* Only close file descriptors that have the FD_CLOEXEC bit set. */ + CLOSE_RANGE_CLOEXEC_ONLY = (1U << 4), }; /* Keep #ifdef working and let glibc skip its own definitions. */ #define CLOSE_RANGE_UNSHARE CLOSE_RANGE_UNSHARE #define CLOSE_RANGE_CLOEXEC CLOSE_RANGE_CLOEXEC #define CLOSE_RANGE_EXCEPT CLOSE_RANGE_EXCEPT +#define CLOSE_RANGE_CLOEXEC_ONLY CLOSE_RANGE_CLOEXEC_ONLY #endif /* _UAPI_LINUX_CLOSE_RANGE_H */ From 0a0f88fcb434f58fc5b444e7c9eeae791cdbbeda Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 16:15:38 +0200 Subject: [PATCH 1391/2286] selftests/core: test CLOSE_RANGE_CLOEXEC_ONLY Cover closing what is marked: - close-on-exec descriptors in the range go, the ones outside stay, and a range above the table closes nothing - with CLOSE_RANGE_EXCEPT it is the other way around, and the kept ones keep their flag, for a window in the middle, at the top and at the bottom - descriptors without close-on-exec are never touched, in or out of the range - a range that cannot hold an open descriptor keeps nothing, one that covers everything keeps everything, in place and in a clone - the unshare form leaves the table it was cloned from alone, with and without CLOSE_RANGE_EXCEPT - the unshare form keeps the descriptors without the flag that sit in the range it drops from, and hands the dropped slots out again - a kept range above the last descriptor without close-on-exec still comes back, so the clone is sized off the range too Also check that asking for CLOSE_RANGE_CLOEXEC at the same time is refused, whatever else is asked for, and that the bounds are still checked. The extra cases came out of the same walk with the close-on-exec mask on top: a marked and an unmarked descriptor on each side of every window position, and the size of the clone when only unmarked ones are left in the range it drops from. Link: https://patch.msgid.link/20260921-work-file-close_range_except-v1-10-c20d0b49270d@kernel.org Reviewed-by: Jann Horn Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/core/close_range_test.c | 481 ++++++++++++++++++ 1 file changed, 481 insertions(+) diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c index caeb2f1ea800a5..20ecb65e529b01 100644 --- a/tools/testing/selftests/core/close_range_test.c +++ b/tools/testing/selftests/core/close_range_test.c @@ -1063,6 +1063,487 @@ TEST(close_range_except_unshare) EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); } +TEST(close_range_cloexec_only) +{ + int i, ret; + int open_fds[101]; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + /* Odd slots are close-on-exec, even ones are not. */ + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_CLOEXEC_ONLY); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool closed = i % 2 && i >= 10 && i <= 20; + + EXPECT_EQ(!closed, fcntl(open_fds[i], F_GETFD) != -1); + } + + /* A range above the table closes nothing. */ + ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool closed = i % 2 && i >= 10 && i <= 20; + + EXPECT_EQ(!closed, fcntl(open_fds[i], F_GETFD) != -1); + } + + /* Do what an exec would do to the rest. */ + ASSERT_EQ(0, sys_close_range(0, UINT_MAX, CLOSE_RANGE_CLOEXEC_ONLY)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(!(i % 2), fcntl(open_fds[i], F_GETFD) != -1); +} + +TEST(close_range_cloexec_only_except) +{ + int i, ret; + int open_fds[101]; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool kept = !(i % 2) || (i >= 10 && i <= 20); + int flags = i % 2 ? FD_CLOEXEC : 0; + + /* The kept ones keep their flag, so exec still drops them. */ + EXPECT_EQ(kept ? flags : -1, fcntl(open_fds[i], F_GETFD)); + } + + /* A range that cannot hold an open descriptor keeps nothing. */ + ASSERT_EQ(0, sys_close_range(UINT_MAX, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT)); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_EQ(!(i % 2), fcntl(open_fds[i], F_GETFD) != -1); +} + +TEST(close_range_cloexec_only_except_bounds) +{ + int i, c, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + struct { + unsigned int fd, max_fd; + } cases[] = { + /* A window at the top keeps the marked ones in it. */ + { open_fds[80], UINT_MAX }, + /* One at the bottom keeps the marked ones in it. */ + { 0, open_fds[20] }, + /* One that cannot hold a descriptor keeps none of them. */ + { UINT_MAX, UINT_MAX }, + }; + + for (c = 0; c < ARRAY_SIZE(cases); c++) { + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(cases[c].fd, cases[c].max_fd, + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + unsigned int fd = open_fds[i]; + bool kept = !(i % 2) || (fd >= cases[c].fd && + fd <= cases[c].max_fd); + int flags = i % 2 ? FD_CLOEXEC : 0; + + if (fcntl(fd, F_GETFD) != (kept ? flags : -1)) + exit(EXIT_FAILURE); + } + + /* stdio is neither marked nor gone. */ + if (fcntl(STDERR_FILENO, F_GETFD) & FD_CLOEXEC) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + } + + /* Each fork had a table of its own. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + +TEST(close_range_cloexec_only_unshare) +{ + int i, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + ASSERT_NE(-1, fcntl(open_fds[i], F_GETFD)); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool closed = i % 2 && i >= 10 && i <= 20; + + if (closed == (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* A range at the top keeps the descriptors without the flag in it. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[50], UINT_MAX, + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool closed = i % 2 && i >= 50; + + if (closed == (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + /* The first slot left behind is the next one handed out. */ + if (dup(0) != open_fds[51]) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The shared table the child unshared from is untouched. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + +TEST(close_range_cloexec_only_except_unshare) +{ + int i, ret, status; + pid_t pid; + int open_fds[101]; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY | (i % 2 ? O_CLOEXEC : 0)); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + /* A range covering everything keeps everything. */ + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + ASSERT_NE(-1, fcntl(open_fds[i], F_GETFD)); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[10], open_fds[20], + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool kept = !(i % 2) || (i >= 10 && i <= 20); + int flags = i % 2 ? FD_CLOEXEC : 0; + + if (fcntl(open_fds[i], F_GETFD) != (kept ? flags : -1)) + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* A window that cannot hold a descriptor keeps none of the marked. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(UINT_MAX, UINT_MAX, + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + if ((i % 2) == (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + + if (fcntl(STDERR_FILENO, F_GETFD) == -1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* One that covers everything keeps everything, in a clone too. */ + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + if (fcntl(open_fds[i], F_GETFD) != (i % 2 ? FD_CLOEXEC : 0)) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); + + /* The shared table the child unshared from is untouched. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) + EXPECT_NE(-1, fcntl(open_fds[i], F_GETFD)); +} + +TEST(close_range_cloexec_only_except_unshare_sizing) +{ + int i, ret, status; + pid_t pid; + int open_fds[200]; + struct __clone_args args = { + .flags = CLONE_FILES, + .exit_signal = SIGCHLD, + }; + + /* All close-on-exec, so the kept range alone sizes the clone. */ + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + int fd; + + fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0) { + if (errno == ENOENT) + SKIP(return, "Skipping test since /dev/null does not exist"); + } + + open_fds[i] = fd; + } + + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + pid = sys_clone3(&args, sizeof(args)); + ASSERT_GE(pid, 0); + + if (pid == 0) { + ret = sys_close_range(open_fds[150], open_fds[160], + CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT); + if (ret) + exit(EXIT_FAILURE); + + for (i = 0; i < ARRAY_SIZE(open_fds); i++) { + bool kept = i >= 150 && i <= 160; + + if (kept != (fcntl(open_fds[i], F_GETFD) != -1)) + exit(EXIT_FAILURE); + } + + /* Nothing set close-on-exec on stdio. */ + if (fcntl(STDERR_FILENO, F_GETFD) == -1) + exit(EXIT_FAILURE); + + exit(EXIT_SUCCESS); + } + + EXPECT_EQ(waitpid(pid, &status, 0), pid); + EXPECT_EQ(true, WIFEXITED(status)); + EXPECT_EQ(0, WEXITSTATUS(status)); +} + +TEST(close_range_cloexec_only_einval) +{ + int ret; + + /* A range covering everything keeps everything, so this only probes. */ + ret = sys_close_range(0, UINT_MAX, + CLOSE_RANGE_CLOEXEC_ONLY | CLOSE_RANGE_EXCEPT); + if (ret < 0) { + if (errno == ENOSYS) + SKIP(return, "close_range() syscall not supported"); + if (errno == EINVAL) + SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC_ONLY"); + } + ASSERT_EQ(0, ret); + + EXPECT_EQ(-1, sys_close_range(3, UINT_MAX, CLOSE_RANGE_CLOEXEC | + CLOSE_RANGE_CLOEXEC_ONLY)); + EXPECT_EQ(EINVAL, errno); + + /* The other flags do not make the pair acceptable. */ + EXPECT_EQ(-1, sys_close_range(3, UINT_MAX, CLOSE_RANGE_UNSHARE | + CLOSE_RANGE_CLOEXEC | + CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT)); + EXPECT_EQ(EINVAL, errno); + + /* The bounds are checked with the new flag too. */ + EXPECT_EQ(-1, sys_close_range(4, 3, CLOSE_RANGE_CLOEXEC_ONLY | + CLOSE_RANGE_EXCEPT)); + EXPECT_EQ(EINVAL, errno); +} + TEST(close_range_bitmap_corruption) { pid_t pid; From 5c2b4ab7f437c5ef9c0f825a461b67ba16ab4066 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 26 Aug 2026 18:06:40 +0200 Subject: [PATCH 1392/2286] powerpc: remove coredump support A task that dumps open spufs context adds a bunch of extra elf notes describing the SPU state. It is the only reason do_coredump() unshares the file descriptor table. We could make this conditional on spufs but eh. Nothing can consume those notes anymore. gdb dropped Cell Broadband Engine debugging in 9.1 and binutils removed it in 2.34. That's about 6 years ago. So no program can actually read an SPU note out of a core file and probably never did in recent history. Note that the IBM Cell blades that shipped the Cell processor were removed in commit 05bf59fbeef3 ("powerpc/cell: Remove support for IBM Cell Blades"). The PlayStation 3 is the only platform left and nothing there produces or reads these notes. So remove it. Link: https://patch.msgid.link/20260826-work-spufs-coredump-v1-1-579e72a7ab66@kernel.org Acked-by: Arnd Bergmann Signed-off-by: Christian Brauner (Amutable) --- arch/powerpc/Kconfig | 1 - arch/powerpc/include/asm/elf.h | 6 - arch/powerpc/include/asm/spu.h | 3 - arch/powerpc/platforms/cell/Kconfig | 1 - arch/powerpc/platforms/cell/spu_syscalls.c | 20 -- arch/powerpc/platforms/cell/spufs/Makefile | 1 - arch/powerpc/platforms/cell/spufs/coredump.c | 183 ------------------- arch/powerpc/platforms/cell/spufs/file.c | 114 ------------ arch/powerpc/platforms/cell/spufs/spufs.h | 12 -- arch/powerpc/platforms/cell/spufs/syscalls.c | 4 - 10 files changed, 345 deletions(-) delete mode 100644 arch/powerpc/platforms/cell/spufs/coredump.c diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index 2580e27e432874..40c874fe2f53ac 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -160,7 +160,6 @@ config PPC select ARCH_HAS_UBSAN select ARCH_HAS_VDSO_ARCH_DATA select ARCH_HAVE_NMI_SAFE_CMPXCHG - select ARCH_HAVE_EXTRA_ELF_NOTES if SPU_BASE select ARCH_KEEP_MEMBLOCK select ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE if PPC_RADIX_MMU select ARCH_MIGHT_HAVE_PC_PARPORT diff --git a/arch/powerpc/include/asm/elf.h b/arch/powerpc/include/asm/elf.h index bb4b94444d3e8a..5dc8c4923eb1df 100644 --- a/arch/powerpc/include/asm/elf.h +++ b/arch/powerpc/include/asm/elf.h @@ -123,12 +123,6 @@ extern int arch_setup_additional_pages(struct linux_binprm *bprm, (0x7ff >> (PAGE_SHIFT - 12)) : \ (0x3ffff >> (PAGE_SHIFT - 12))) -#ifdef CONFIG_SPU_BASE -/* Notes used in ET_CORE. Note name is "SPU//". */ -#define NT_SPU 1 - -#endif /* CONFIG_SPU_BASE */ - #ifdef CONFIG_PPC64 #define get_cache_geometry(level) \ diff --git a/arch/powerpc/include/asm/spu.h b/arch/powerpc/include/asm/spu.h index 96ad4510c89542..7152285b6268fd 100644 --- a/arch/powerpc/include/asm/spu.h +++ b/arch/powerpc/include/asm/spu.h @@ -210,15 +210,12 @@ extern long spu_sys_callback(struct spu_syscall_block *s); /* syscalls implemented in spufs */ struct file; -struct coredump_params; struct spufs_calls { long (*create_thread)(const char __user *name, unsigned int flags, umode_t mode, struct file *neighbor); long (*spu_run)(struct file *filp, __u32 __user *unpc, __u32 __user *ustatus); - int (*coredump_extra_notes_size)(void); - int (*coredump_extra_notes_write)(struct coredump_params *cprm); void (*notify_spus_active)(void); struct module *owner; }; diff --git a/arch/powerpc/platforms/cell/Kconfig b/arch/powerpc/platforms/cell/Kconfig index db65bfcd1e7498..6bd26815c33103 100644 --- a/arch/powerpc/platforms/cell/Kconfig +++ b/arch/powerpc/platforms/cell/Kconfig @@ -10,7 +10,6 @@ config SPU_FS tristate "SPU file system" default m depends on PPC_CELL - depends on COREDUMP select SPU_BASE help The SPU file system is used to access Synergistic Processing diff --git a/arch/powerpc/platforms/cell/spu_syscalls.c b/arch/powerpc/platforms/cell/spu_syscalls.c index 000894e07b027d..8be81207e886a1 100644 --- a/arch/powerpc/platforms/cell/spu_syscalls.c +++ b/arch/powerpc/platforms/cell/spu_syscalls.c @@ -88,26 +88,6 @@ SYSCALL_DEFINE3(spu_run,int, fd, __u32 __user *, unpc, __u32 __user *, ustatus) return calls->spu_run(fd_file(arg), unpc, ustatus); } -#ifdef CONFIG_COREDUMP -int elf_coredump_extra_notes_size(void) -{ - CLASS(spufs_calls, calls)(); - if (!calls) - return 0; - - return calls->coredump_extra_notes_size(); -} - -int elf_coredump_extra_notes_write(struct coredump_params *cprm) -{ - CLASS(spufs_calls, calls)(); - if (!calls) - return 0; - - return calls->coredump_extra_notes_write(cprm); -} -#endif - void notify_spus_active(void) { struct spufs_calls *calls; diff --git a/arch/powerpc/platforms/cell/spufs/Makefile b/arch/powerpc/platforms/cell/spufs/Makefile index 52e4c80ec8d031..60319d4ff25a6a 100644 --- a/arch/powerpc/platforms/cell/spufs/Makefile +++ b/arch/powerpc/platforms/cell/spufs/Makefile @@ -4,7 +4,6 @@ obj-$(CONFIG_SPU_FS) += spufs.o spufs-y += inode.o file.o context.o syscalls.o spufs-y += sched.o backing_ops.o hw_ops.o run.o gang.o spufs-y += switch.o fault.o lscsa_alloc.o -spufs-$(CONFIG_COREDUMP) += coredump.o # magic for the trace events CFLAGS_sched.o := -I$(src) diff --git a/arch/powerpc/platforms/cell/spufs/coredump.c b/arch/powerpc/platforms/cell/spufs/coredump.c deleted file mode 100644 index 301ee7d8b7df02..00000000000000 --- a/arch/powerpc/platforms/cell/spufs/coredump.c +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * SPU core dump code - * - * (C) Copyright 2006 IBM Corp. - * - * Author: Dwayne Grant McConnell - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "spufs.h" - -static int spufs_ctx_note_size(struct spu_context *ctx, int dfd) -{ - int i, sz, total = 0; - char *name; - char fullname[80]; - - for (i = 0; spufs_coredump_read[i].name != NULL; i++) { - name = spufs_coredump_read[i].name; - sz = spufs_coredump_read[i].size; - - sprintf(fullname, "SPU/%d/%s", dfd, name); - - total += sizeof(struct elf_note); - total += roundup(strlen(fullname) + 1, 4); - total += roundup(sz, 4); - } - - return total; -} - -static int match_context(const void *v, struct file *file, unsigned fd) -{ - struct spu_context *ctx; - if (file->f_op != &spufs_context_fops) - return 0; - ctx = SPUFS_I(file_inode(file))->i_ctx; - if (ctx->flags & SPU_CREATE_NOSCHED) - return 0; - return fd + 1; -} - -/* - * The additional architecture-specific notes for Cell are various - * context files in the spu context. - * - * This function iterates over all open file descriptors and sees - * if they are a directory in spufs. In that case we use spufs - * internal functionality to dump them without needing to actually - * open the files. - */ -/* - * descriptor table is not shared, so files can't change or go away. - */ -static struct spu_context *coredump_next_context(int *fd) -{ - struct spu_context *ctx = NULL; - struct file *file; - int n = iterate_fd(current->files, *fd, match_context, NULL); - if (!n) - return NULL; - *fd = n - 1; - - file = fget_raw(*fd); - if (file) { - ctx = SPUFS_I(file_inode(file))->i_ctx; - get_spu_context(ctx); - fput(file); - } - - return ctx; -} - -int spufs_coredump_extra_notes_size(void) -{ - struct spu_context *ctx; - int size = 0, rc, fd; - - fd = 0; - while ((ctx = coredump_next_context(&fd)) != NULL) { - rc = spu_acquire_saved(ctx); - if (rc) { - put_spu_context(ctx); - break; - } - - rc = spufs_ctx_note_size(ctx, fd); - spu_release_saved(ctx); - if (rc < 0) { - put_spu_context(ctx); - break; - } - - size += rc; - - /* start searching the next fd next time */ - fd++; - put_spu_context(ctx); - } - - return size; -} - -static int spufs_arch_write_note(struct spu_context *ctx, int i, - struct coredump_params *cprm, int dfd) -{ - size_t sz = spufs_coredump_read[i].size; - char fullname[80]; - struct elf_note en; - int ret; - - sprintf(fullname, "SPU/%d/%s", dfd, spufs_coredump_read[i].name); - en.n_namesz = strlen(fullname) + 1; - en.n_descsz = sz; - en.n_type = NT_SPU; - - if (!dump_emit(cprm, &en, sizeof(en))) - return -EIO; - if (!dump_emit(cprm, fullname, en.n_namesz)) - return -EIO; - if (!dump_align(cprm, 4)) - return -EIO; - - if (spufs_coredump_read[i].dump) { - ret = spufs_coredump_read[i].dump(ctx, cprm); - if (ret < 0) - return ret; - } else { - char buf[32]; - - ret = snprintf(buf, sizeof(buf), "0x%.16llx", - spufs_coredump_read[i].get(ctx)); - if (ret >= sizeof(buf)) - return sizeof(buf); - - /* count trailing the NULL: */ - if (!dump_emit(cprm, buf, ret + 1)) - return -EIO; - } - - dump_skip_to(cprm, roundup(cprm->pos - ret + sz, 4)); - return 0; -} - -int spufs_coredump_extra_notes_write(struct coredump_params *cprm) -{ - struct spu_context *ctx; - int fd, j, rc; - - fd = 0; - while ((ctx = coredump_next_context(&fd)) != NULL) { - rc = spu_acquire_saved(ctx); - if (rc) - return rc; - - for (j = 0; spufs_coredump_read[j].name != NULL; j++) { - rc = spufs_arch_write_note(ctx, j, cprm, fd); - if (rc) { - spu_release_saved(ctx); - return rc; - } - } - - spu_release_saved(ctx); - - /* start searching the next fd next time */ - fd++; - } - - return 0; -} diff --git a/arch/powerpc/platforms/cell/spufs/file.c b/arch/powerpc/platforms/cell/spufs/file.c index de7494748fecd6..98c47bafaf6781 100644 --- a/arch/powerpc/platforms/cell/spufs/file.c +++ b/arch/powerpc/platforms/cell/spufs/file.c @@ -9,7 +9,6 @@ #undef DEBUG -#include #include #include #include @@ -130,14 +129,6 @@ static ssize_t spufs_attr_write(struct file *file, const char __user *buf, return ret; } -static ssize_t spufs_dump_emit(struct coredump_params *cprm, void *buf, - size_t size) -{ - if (!dump_emit(cprm, buf, size)) - return -EIO; - return size; -} - #define DEFINE_SPUFS_SIMPLE_ATTRIBUTE(__fops, __get, __set, __fmt) \ static int __fops ## _open(struct inode *inode, struct file *file) \ { \ @@ -180,12 +171,6 @@ spufs_mem_release(struct inode *inode, struct file *file) return 0; } -static ssize_t -spufs_mem_dump(struct spu_context *ctx, struct coredump_params *cprm) -{ - return spufs_dump_emit(cprm, ctx->ops->get_ls(ctx), LS_SIZE); -} - static ssize_t spufs_mem_read(struct file *file, char __user *buffer, size_t size, loff_t *pos) @@ -466,13 +451,6 @@ spufs_regs_open(struct inode *inode, struct file *file) return 0; } -static ssize_t -spufs_regs_dump(struct spu_context *ctx, struct coredump_params *cprm) -{ - return spufs_dump_emit(cprm, ctx->csa.lscsa->gprs, - sizeof(ctx->csa.lscsa->gprs)); -} - static ssize_t spufs_regs_read(struct file *file, char __user *buffer, size_t size, loff_t *pos) @@ -523,13 +501,6 @@ static const struct file_operations spufs_regs_fops = { .llseek = generic_file_llseek, }; -static ssize_t -spufs_fpcr_dump(struct spu_context *ctx, struct coredump_params *cprm) -{ - return spufs_dump_emit(cprm, &ctx->csa.lscsa->fpcr, - sizeof(ctx->csa.lscsa->fpcr)); -} - static ssize_t spufs_fpcr_read(struct file *file, char __user * buffer, size_t size, loff_t * pos) @@ -953,15 +924,6 @@ spufs_signal1_release(struct inode *inode, struct file *file) return 0; } -static ssize_t spufs_signal1_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - if (!ctx->csa.spu_chnlcnt_RW[3]) - return 0; - return spufs_dump_emit(cprm, &ctx->csa.spu_chnldata_RW[3], - sizeof(ctx->csa.spu_chnldata_RW[3])); -} - static ssize_t __spufs_signal1_read(struct spu_context *ctx, char __user *buf, size_t len) { @@ -1086,15 +1048,6 @@ spufs_signal2_release(struct inode *inode, struct file *file) return 0; } -static ssize_t spufs_signal2_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - if (!ctx->csa.spu_chnlcnt_RW[4]) - return 0; - return spufs_dump_emit(cprm, &ctx->csa.spu_chnldata_RW[4], - sizeof(ctx->csa.spu_chnldata_RW[4])); -} - static ssize_t __spufs_signal2_read(struct spu_context *ctx, char __user *buf, size_t len) { @@ -1924,15 +1877,6 @@ static const struct file_operations spufs_caps_fops = { .release = single_release, }; -static ssize_t spufs_mbox_info_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - if (!(ctx->csa.prob.mb_stat_R & 0x0000ff)) - return 0; - return spufs_dump_emit(cprm, &ctx->csa.prob.pu_mb_R, - sizeof(ctx->csa.prob.pu_mb_R)); -} - static ssize_t spufs_mbox_info_read(struct file *file, char __user *buf, size_t len, loff_t *pos) { @@ -1962,15 +1906,6 @@ static const struct file_operations spufs_mbox_info_fops = { .llseek = generic_file_llseek, }; -static ssize_t spufs_ibox_info_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - if (!(ctx->csa.prob.mb_stat_R & 0xff0000)) - return 0; - return spufs_dump_emit(cprm, &ctx->csa.priv2.puint_mb_R, - sizeof(ctx->csa.priv2.puint_mb_R)); -} - static ssize_t spufs_ibox_info_read(struct file *file, char __user *buf, size_t len, loff_t *pos) { @@ -2005,13 +1940,6 @@ static size_t spufs_wbox_info_cnt(struct spu_context *ctx) return (4 - ((ctx->csa.prob.mb_stat_R & 0x00ff00) >> 8)) * sizeof(u32); } -static ssize_t spufs_wbox_info_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - return spufs_dump_emit(cprm, &ctx->csa.spu_mailbox_data, - spufs_wbox_info_cnt(ctx)); -} - static ssize_t spufs_wbox_info_read(struct file *file, char __user *buf, size_t len, loff_t *pos) { @@ -2059,15 +1987,6 @@ static void spufs_get_dma_info(struct spu_context *ctx, } } -static ssize_t spufs_dma_info_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - struct spu_dma_info info; - - spufs_get_dma_info(ctx, &info); - return spufs_dump_emit(cprm, &info, sizeof(info)); -} - static ssize_t spufs_dma_info_read(struct file *file, char __user *buf, size_t len, loff_t *pos) { @@ -2112,15 +2031,6 @@ static void spufs_get_proxydma_info(struct spu_context *ctx, } } -static ssize_t spufs_proxydma_info_dump(struct spu_context *ctx, - struct coredump_params *cprm) -{ - struct spu_proxydma_info info; - - spufs_get_proxydma_info(ctx, &info); - return spufs_dump_emit(cprm, &info, sizeof(info)); -} - static ssize_t spufs_proxydma_info_read(struct file *file, char __user *buf, size_t len, loff_t *pos) { @@ -2580,27 +2490,3 @@ const struct spufs_tree_descr spufs_dir_debug_contents[] = { { ".ctx", &spufs_ctx_fops, 0444, }, {}, }; - -const struct spufs_coredump_reader spufs_coredump_read[] = { - { "regs", spufs_regs_dump, NULL, sizeof(struct spu_reg128[128])}, - { "fpcr", spufs_fpcr_dump, NULL, sizeof(struct spu_reg128) }, - { "lslr", NULL, spufs_lslr_get, 19 }, - { "decr", NULL, spufs_decr_get, 19 }, - { "decr_status", NULL, spufs_decr_status_get, 19 }, - { "mem", spufs_mem_dump, NULL, LS_SIZE, }, - { "signal1", spufs_signal1_dump, NULL, sizeof(u32) }, - { "signal1_type", NULL, spufs_signal1_type_get, 19 }, - { "signal2", spufs_signal2_dump, NULL, sizeof(u32) }, - { "signal2_type", NULL, spufs_signal2_type_get, 19 }, - { "event_mask", NULL, spufs_event_mask_get, 19 }, - { "event_status", NULL, spufs_event_status_get, 19 }, - { "mbox_info", spufs_mbox_info_dump, NULL, sizeof(u32) }, - { "ibox_info", spufs_ibox_info_dump, NULL, sizeof(u32) }, - { "wbox_info", spufs_wbox_info_dump, NULL, 4 * sizeof(u32)}, - { "dma_info", spufs_dma_info_dump, NULL, sizeof(struct spu_dma_info)}, - { "proxydma_info", spufs_proxydma_info_dump, - NULL, sizeof(struct spu_proxydma_info)}, - { "object-id", NULL, spufs_object_id_get, 19 }, - { "npc", NULL, spufs_npc_get, 19 }, - { NULL }, -}; diff --git a/arch/powerpc/platforms/cell/spufs/spufs.h b/arch/powerpc/platforms/cell/spufs/spufs.h index d33787c57c39a2..612b5075d0ecb8 100644 --- a/arch/powerpc/platforms/cell/spufs/spufs.h +++ b/arch/powerpc/platforms/cell/spufs/spufs.h @@ -232,13 +232,9 @@ extern const struct spufs_tree_descr spufs_dir_debug_contents[]; /* system call implementation */ extern struct spufs_calls spufs_calls; -struct coredump_params; long spufs_run_spu(struct spu_context *ctx, u32 *npc, u32 *status); long spufs_create(const struct path *nd, struct dentry *dentry, unsigned int flags, umode_t mode, struct file *filp); -/* ELF coredump callbacks for writing SPU ELF notes */ -extern int spufs_coredump_extra_notes_size(void); -extern int spufs_coredump_extra_notes_write(struct coredump_params *cprm); extern const struct file_operations spufs_context_fops; @@ -335,14 +331,6 @@ void spufs_stop_callback(struct spu *spu, int irq); void spufs_mfc_callback(struct spu *spu); void spufs_dma_callback(struct spu *spu, int type); -struct spufs_coredump_reader { - char *name; - ssize_t (*dump)(struct spu_context *ctx, struct coredump_params *cprm); - u64 (*get)(struct spu_context *ctx); - size_t size; -}; -extern const struct spufs_coredump_reader spufs_coredump_read[]; - extern int spu_init_csa(struct spu_state *csa); extern void spu_fini_csa(struct spu_state *csa); extern int spu_save(struct spu_state *prev, struct spu *spu); diff --git a/arch/powerpc/platforms/cell/spufs/syscalls.c b/arch/powerpc/platforms/cell/spufs/syscalls.c index ea4ba1b6ce6a96..b6de37150e734d 100644 --- a/arch/powerpc/platforms/cell/spufs/syscalls.c +++ b/arch/powerpc/platforms/cell/spufs/syscalls.c @@ -82,8 +82,4 @@ struct spufs_calls spufs_calls = { .spu_run = do_spu_run, .notify_spus_active = do_notify_spus_active, .owner = THIS_MODULE, -#ifdef CONFIG_COREDUMP - .coredump_extra_notes_size = spufs_coredump_extra_notes_size, - .coredump_extra_notes_write = spufs_coredump_extra_notes_write, -#endif }; From f192beb5479a3a87541f3cf8d845abc8ee9959bd Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:47:59 +0200 Subject: [PATCH 1393/2286] fs: don't open-code file_close_fd() in close_fd() close_fd() takes the lock, calls file_close_fd_locked() and drops the lock, which is exactly what file_close_fd() does. Use it. No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-1-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/file.c b/fs/file.c index 37f0ba740c393b..5ba7a5bdb113fe 100644 --- a/fs/file.c +++ b/fs/file.c @@ -792,16 +792,13 @@ struct file *file_close_fd_locked(struct files_struct *files, unsigned fd) int close_fd(unsigned fd) { - struct files_struct *files = current->files; struct file *file; - spin_lock(&files->file_lock); - file = file_close_fd_locked(files, fd); - spin_unlock(&files->file_lock); + file = file_close_fd(fd); if (!file) return -EBADF; - return filp_close(file, files); + return filp_close(file, current->files); } EXPORT_SYMBOL(close_fd); From 3f0993fbe42be1213a4bff4c06fd5dfa048387a3 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 26 Aug 2026 18:06:41 +0200 Subject: [PATCH 1394/2286] coredump: stop unsharing the file descriptor table We currently unshare the file descriptor table before we write the coredump. The only code that ever looked at the file descriptor table was the cell spufs coredump support. It walked all file descriptors to find the spufs contexts it would have to dump as extra elf notes. Now that we killed spufs coredumping stop doing that and update the comments referencing spufs as they ave become stale. Link: https://patch.msgid.link/20260826-work-spufs-coredump-v1-2-579e72a7ab66@kernel.org Acked-by: Arnd Bergmann Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_elf.c | 4 ++-- fs/coredump.c | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 06d0df1053823e..db32bb40a86704 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -2029,7 +2029,7 @@ static int elf_core_dump(struct coredump_params *cprm) { size_t sz = info.size; - /* For cell spufs and x86 xstate */ + /* For x86 xstate */ sz += elf_coredump_extra_notes_size(); phdr4note = kmalloc_obj(*phdr4note); @@ -2093,7 +2093,7 @@ static int elf_core_dump(struct coredump_params *cprm) if (!write_note_info(&info, cprm)) goto end_coredump; - /* For cell spufs and x86 xstate */ + /* For x86 xstate */ if (elf_coredump_extra_notes_write(cprm)) goto end_coredump; diff --git a/fs/coredump.c b/fs/coredump.c index 6114839f5178b0..3e78941f281e36 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -1118,11 +1118,6 @@ static void do_coredump(struct core_name *cn, struct coredump_params *cprm, if (cn->mask & COREDUMP_REJECT) return; - /* get us an unshared descriptor table; almost always a no-op */ - /* The cell spufs coredump code reads the file descriptor tables */ - if (unshare_files()) - return; - if ((cn->mask & COREDUMP_KERNEL) && !coredump_write(cn, cprm, binfmt)) return; From 31116e528f19545220250209fc5ee71a5a2295ec Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:00 +0200 Subject: [PATCH 1395/2286] fs: add switch_files_struct() Add switch_files_struct() to install another table on a task. It consumes the reference to the new table and puts the old one. Convert every place that switches a descriptor table except unshare_files(). No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-2-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 23 +++++++++++------------ include/linux/fdtable.h | 1 + kernel/fork.c | 6 ++---- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/fs/file.c b/fs/file.c index 5ba7a5bdb113fe..462a9991334a48 100644 --- a/fs/file.c +++ b/fs/file.c @@ -575,16 +575,18 @@ void put_files_struct(struct files_struct *files) } } -void exit_files(struct task_struct *tsk) +/* Install @files on @tsk, consuming the reference, and put the old table. */ +void switch_files_struct(struct task_struct *tsk, struct files_struct *files) { - struct files_struct * files = tsk->files; + scoped_guard(task_lock, tsk) + swap(tsk->files, files); + put_files_struct(files); +} - if (files) { - task_lock(tsk); - tsk->files = NULL; - task_unlock(tsk); - put_files_struct(files); - } +void exit_files(struct task_struct *tsk) +{ + if (tsk->files) + switch_files_struct(tsk, NULL); } struct files_struct init_files = { @@ -983,10 +985,7 @@ SYSCALL_DEFINE3(close_range, unsigned int, fd, unsigned int, max_fd, * We're done closing the files we were supposed to. Time to install * the new file descriptor table and drop the old one. */ - task_lock(me); - me->files = cur_fds; - task_unlock(me); - put_files_struct(fds); + switch_files_struct(me, cur_fds); } return 0; diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index afdfaad381df16..270ee830f206e6 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -100,6 +100,7 @@ static inline bool close_on_exec(unsigned int fd, const struct files_struct *fil struct task_struct; void put_files_struct(struct files_struct *fs); +void switch_files_struct(struct task_struct *tsk, struct files_struct *files); int unshare_files(void); enum fd_range_flags { /* Leave behind all descriptors outside of the specified range. */ diff --git a/kernel/fork.c b/kernel/fork.c index a5934a3176346b..f09ca97411a2b6 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3323,10 +3323,8 @@ int ksys_unshare(unsigned long unshare_flags) if (new_fs) new_fs = switch_fs_struct(new_fs); - if (new_fd) { - guard(task_lock)(current); - swap(current->files, new_fd); - } + if (new_fd) + switch_files_struct(current, no_free_ptr(new_fd)); if (new_cred) { /* Install the new user namespace */ From ae6c2e72e5ee53e03b17f2793c067fc9d41bdfe9 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:01 +0200 Subject: [PATCH 1396/2286] fs: move unshare_fd() to fs/file.c Move unshare_fd() where the rest of the descriptor table lifecycle helpers live. No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-3-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 18 ++++++++++++++++++ include/linux/fdtable.h | 1 + kernel/fork.c | 18 ------------------ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/fs/file.c b/fs/file.c index 462a9991334a48..6edc355b7df0ba 100644 --- a/fs/file.c +++ b/fs/file.c @@ -531,6 +531,24 @@ struct files_struct *dup_fd(struct files_struct *oldf, struct fd_range *range) return newf; } +/* + * Unshare file descriptor table if it is being shared + */ +int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) +{ + struct files_struct *fd = current->files; + + if ((unshare_flags & CLONE_FILES) && + (fd && atomic_read(&fd->count) > 1)) { + fd = dup_fd(fd, NULL); + if (IS_ERR(fd)) + return PTR_ERR(fd); + *new_fdp = fd; + } + + return 0; +} + static struct fdtable *close_files(struct files_struct * files) { /* diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index 270ee830f206e6..2bd8adb194b307 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -102,6 +102,7 @@ struct task_struct; void put_files_struct(struct files_struct *fs); void switch_files_struct(struct task_struct *tsk, struct files_struct *files); int unshare_files(void); +int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp); enum fd_range_flags { /* Leave behind all descriptors outside of the specified range. */ FD_RANGE_EXCEPT = (1U << 0), diff --git a/kernel/fork.c b/kernel/fork.c index f09ca97411a2b6..9daf6e94bd5d3e 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3210,24 +3210,6 @@ static int unshare_fs(unsigned long unshare_flags, struct fs_struct **new_fsp) return 0; } -/* - * Unshare file descriptor table if it is being shared - */ -static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) -{ - struct files_struct *fd = current->files; - - if ((unshare_flags & CLONE_FILES) && - (fd && atomic_read(&fd->count) > 1)) { - fd = dup_fd(fd, NULL); - if (IS_ERR(fd)) - return PTR_ERR(fd); - *new_fdp = fd; - } - - return 0; -} - /* * unshare allows a process to 'unshare' part of the process * context which was originally shared using clone. copy_* From 5fe5e24a8ff851da9d81a7bc91839e20455c9508 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:02 +0200 Subject: [PATCH 1397/2286] fs: remove unshare_files() exec is the only caller left since commit 433967cab51e ("coredump: stop unsharing the file descriptor table"). All it does is call unshare_fd() with CLONE_FILES and install the copy. Kill the pointless helper and open-code it. No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-4-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 5 ++++- include/linux/fdtable.h | 1 - kernel/fork.c | 24 ------------------------ 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index d3081c8f7c10c0..977778f44cfc33 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1124,6 +1124,7 @@ static struct file *bprm_identity_file(const struct linux_binprm *bprm) int begin_new_exec(struct linux_binprm * bprm) { struct task_struct *me = current; + struct files_struct *files = NULL; int retval; /* A pending PT_INTERP substitution this format cannot consume. */ @@ -1160,9 +1161,11 @@ int begin_new_exec(struct linux_binprm * bprm) io_uring_task_cancel(); /* Ensure the files table is not shared. */ - retval = unshare_files(); + retval = unshare_fd(CLONE_FILES, &files); if (retval) goto out; + if (files) + switch_files_struct(me, files); /* * We have to apply CLOEXEC before we change whether the process is diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index 2bd8adb194b307..2b5b0e73000112 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -101,7 +101,6 @@ struct task_struct; void put_files_struct(struct files_struct *fs); void switch_files_struct(struct task_struct *tsk, struct files_struct *files); -int unshare_files(void); int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp); enum fd_range_flags { /* Leave behind all descriptors outside of the specified range. */ diff --git a/kernel/fork.c b/kernel/fork.c index 9daf6e94bd5d3e..10be4a0ecb3f19 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -3339,30 +3339,6 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags) return ksys_unshare(unshare_flags); } -/* - * Helper to unshare the files of the current task. - * We don't want to expose copy_files internals to - * the exec layer of the kernel. - */ - -int unshare_files(void) -{ - struct task_struct *task = current; - struct files_struct *old, *copy = NULL; - int error; - - error = unshare_fd(CLONE_FILES, ©); - if (error || !copy) - return error; - - old = task->files; - task_lock(task); - task->files = copy; - task_unlock(task); - put_files_struct(old); - return 0; -} - static int sysctl_max_threads(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { From 2b256ebe82872a85c91cbb26e9edd314923e0fbc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:03 +0200 Subject: [PATCH 1398/2286] fs: add filp_close_sync() Currently close() already does a synchronous release of the last reference since the task is about to return to userspace and the deferral through task work buys nothing. Add a filp_close_sync() helper. We'll use that in the next patches. No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-5-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/internal.h | 1 + fs/open.c | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/fs/internal.h b/fs/internal.h index c658c8a5ebd569..8812de210d3f18 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -198,6 +198,7 @@ extern struct file *do_file_open_root(const struct path *, extern struct open_how build_open_how(int flags, umode_t mode); extern int build_open_flags(const struct open_how *how, struct open_flags *op); struct file *file_close_fd_locked(struct files_struct *files, unsigned fd); +int filp_close_sync(struct file *filp, fl_owner_t id); int do_ftruncate(struct file *file, loff_t length, unsigned int flags); int chmod_common(const struct path *path, umode_t mode); diff --git a/fs/open.c b/fs/open.c index 6b1c14e684a93b..998e42ac319abf 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1537,6 +1537,19 @@ int filp_close(struct file *filp, fl_owner_t id) } EXPORT_SYMBOL(filp_close); +/* Like filp_close() but the last reference is put right here. */ +int filp_close_sync(struct file *filp, fl_owner_t id) +{ + int retval; + + /* Kernel threads must never put their final reference here. */ + VFS_WARN_ON_ONCE(current->flags & PF_KTHREAD); + retval = filp_flush(filp, id); + fput_close_sync(filp); + + return retval; +} + /* * Careful here! We test whether the file pointer is NULL before * releasing the fd. This ensures that one clone task can't release @@ -1551,13 +1564,11 @@ SYSCALL_DEFINE1(close, unsigned int, fd) if (!file) return -EBADF; - retval = filp_flush(file, current->files); - /* * We're returning to user space. Don't bother * with any delayed fput() cases. */ - fput_close_sync(file); + retval = filp_close_sync(file, current->files); if (likely(retval == 0)) return 0; From c8d9b40eabf07d822f212bd012e717f3947c4d7b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:04 +0200 Subject: [PATCH 1399/2286] fs: make close_files() synchronous When the last reference to a descriptor table is dropped close_files() closes every file but punts the actual work to task work. For an exiting task that task work only runs in exit_task_work(). Before commit 4a9d4b024a31 ("switch fput to task_work_add") fput() was synchronous everywhere and exit released its files in exit_files(). The deferral made fput() safe from any context. And exit_files() offloaded to task work as a side-effect. And that has downsides. Oleg and Neil noticed that some time ago. A task that exits with a big descriptor table ends up queueing a very large number of files on task work. That leaves a list for any later task_work_cancel() to search under ->pi_lock and costs a lot of atomics too. Let close_files() close right away. Flush and put each file inline the way close(2) does. The final __fput() runs during the table walk now instead of from task_work_run() in exit_task_work(). One difference is the order: task work ran the final __fput()s in reverse and now they run in table order. Every put of a dying table is synchronous now: - exit_files() - copy_process() - close_range(CLOSE_RANGE_UNSHARE) - unshare(2) - exec Kernel threads don't own a file descriptor table and exec already splats were they to exec. kthreadd and every kthread share init_files and init_task pins that forever. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-6-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/file.c b/fs/file.c index 6edc355b7df0ba..3031b14a9c6508 100644 --- a/fs/file.c +++ b/fs/file.c @@ -549,7 +549,7 @@ int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) return 0; } -static struct fdtable *close_files(struct files_struct * files) +static struct fdtable *close_files(struct files_struct *files) { /* * It is safe to dereference the fd table without RCU or @@ -569,7 +569,7 @@ static struct fdtable *close_files(struct files_struct * files) if (set & 1) { struct file *file = fdt->fd[i]; if (file) { - filp_close(file, files); + filp_close_sync(file, files); cond_resched(); } } From 056069eb2b6614a02538defa3a23fea9a2ec0d79 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:05 +0200 Subject: [PATCH 1400/2286] fs: make close_range() synchronous __range_close() closes through filp_close() so every file the caller held the last reference to is punted to task work. That costs one cmpxchg per file plus a list entry for any later task_work_cancel() to search under ->pi_lock. close_range(2) exists to close many descriptors in one go fast. So convert it to the same synchronous treatment as close(2) and close_files(). Flush and put each file inline while ->file_lock is dropped. close_range(2) now behaves like close(2). Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-7-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/file.c b/fs/file.c index 3031b14a9c6508..af79c6d6df3b0f 100644 --- a/fs/file.c +++ b/fs/file.c @@ -911,7 +911,7 @@ static inline void __range_close(struct files_struct *files, file = file_close_fd_locked(files, fd); if (file) { spin_unlock(&files->file_lock); - filp_close(file, files); + filp_close_sync(file, files); cond_resched(); spin_lock(&files->file_lock); fdt = files_fdtable(files); From c6516bea4bb54963391f698c721e99eb4bb21241 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:06 +0200 Subject: [PATCH 1401/2286] fs: rename do_close_on_exec() to close_cloexec_files() Rename the helper and align it with close_files(). No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-8-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 2 +- fs/file.c | 2 +- include/linux/fdtable.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 977778f44cfc33..1d9163155d16e2 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1179,7 +1179,7 @@ int begin_new_exec(struct linux_binprm * bprm) * This must happen after the point of no return, and after unsharing * the FD table. */ - do_close_on_exec(me->files); + close_cloexec_files(me->files); /* * Must be called _before_ exec_mmap() as bprm->mm is diff --git a/fs/file.c b/fs/file.c index af79c6d6df3b0f..b2974a4d305358 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1029,7 +1029,7 @@ struct file *file_close_fd(unsigned int fd) return file; } -void do_close_on_exec(struct files_struct *files) +void close_cloexec_files(struct files_struct *files) { unsigned i; struct fdtable *fdt; diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h index 2b5b0e73000112..8c28fdd15f31e5 100644 --- a/include/linux/fdtable.h +++ b/include/linux/fdtable.h @@ -115,7 +115,7 @@ struct fd_range { enum fd_range_flags flags; }; struct files_struct *dup_fd(struct files_struct *, struct fd_range *) __latent_entropy; -void do_close_on_exec(struct files_struct *); +void close_cloexec_files(struct files_struct *); int iterate_fd(struct files_struct *, unsigned, int (*)(const void *, struct file *, unsigned), const void *); From e07feeeb5b8db35ee02c2361e90a9d622d3050ee Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:07 +0200 Subject: [PATCH 1402/2286] fs: make close_cloexec_files() synchronous Punting file closing to task work during exec slows down exec significantly when its done with a bunch of file descriptors. We can do this in-band instead. Flush already runs synchronous. Jann moved close-on-exec in e780259b54e6 ("exec: do_close_on_exec() before taking exec_update_lock") outside of exec_update_lock. The only lock that's still held now is cred_guard_mutex. It's deprecated and has five takers (1) exec (2) ptrace_attach() (3) seccomp() with SECCOMP_FILTER_FLAG_TSYNC (4) writes to /proc//attr/* (5) lsm_set_self_attr() Four of them take the task's own cred_guard_mutex. When close_cloexec_files() runs, de_thread() ensured that the calling task is the only one alive in its thread-group. That leaves ptrace() waiting on cred_guard_mutex of the tracee going through exec. exec already sleeps under cred_guard_mutex in de_thread() when it reads binary and interpreter. So while we add wait-time to an attaching ptracer no new lock dependency is added. vfork() als waits but that's a dup_fd() copy of the fdtable and rarely holds the last reference. If that's an issue we can always change that later. Link: https://lore.kernel.org/CAGudoHEsGP1P+sAWaw_tbh1NesJhSeww8869uzmaqtgk8F43=Q@mail.gmail.com Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-9-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/exec.c | 6 +++--- fs/file.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 1d9163155d16e2..075a744421e140 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1173,9 +1173,9 @@ int begin_new_exec(struct linux_binprm * bprm) * trying to access the should-be-closed file descriptors of a process * undergoing exec(2). * - * This can block on filesystem ->flush() handlers, including waiting - * for FUSE daemons, so do it before exec_mmap takes the - * exec_update_lock. + * This can block on filesystem ->flush() and ->release() handlers, + * including waiting for FUSE daemons, so do it before exec_mmap + * takes the exec_update_lock. * This must happen after the point of no return, and after unsharing * the FD table. */ diff --git a/fs/file.c b/fs/file.c index b2974a4d305358..49a6e308f56187 100644 --- a/fs/file.c +++ b/fs/file.c @@ -1056,7 +1056,7 @@ void close_cloexec_files(struct files_struct *files) rcu_assign_pointer(fdt->fd[fd], NULL); __put_unused_fd(files, fd); spin_unlock(&files->file_lock); - filp_close(file, files); + filp_close_sync(file, files); cond_resched(); spin_lock(&files->file_lock); } From c2fe52e23c12aef43c0522196c28f9e2721634ae Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:08 +0200 Subject: [PATCH 1403/2286] coredump: drop core_state->dumper The core_state->dumper field isn't used anymore. Only its ->next pointer is. The current task is always the dumping thread and the ->task pointer is never read. Replace it with a plain pointer to the list of parked threads. Historically, core_state->dumper was used. Its ->task pointer was read. by fill_note_info() started at &core_state->dumper to ensure that the dumping thread came first in the ELF thread notes. That changed in commit 4b0e21d64253 ("[elf][regset] simplify thread list handling in fill_note_info()"). The first iteration was taken out of the loop. So it's been unused ever since. No functional changes. Suggested-by: NeilBrown Link: https://lore.kernel.org/178900159210.207413.8292125177519817528@noble.neil.brown.name Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-10-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_elf.c | 2 +- fs/binfmt_elf_fdpic.c | 2 +- fs/coredump.c | 7 +++---- include/linux/sched/signal.h | 2 +- kernel/exit.c | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index db32bb40a86704..e4bba9b6fecb8d 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -1875,7 +1875,7 @@ static int fill_note_info(struct elfhdr *elf, int phdrs, return 0; info->thread->task = dump_task; - for (ct = dump_task->signal->core_state->dumper.next; ct; ct = ct->next) { + for (ct = dump_task->signal->core_state->tasks; ct; ct = ct->next) { t = kzalloc_flex(*t, notes, info->thread_notes); if (unlikely(!t)) return 0; diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 068c46875c7442..a2633330c59b9c 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -1504,7 +1504,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm) if (!psinfo) goto end_coredump; - for (ct = current->signal->core_state->dumper.next; + for (ct = current->signal->core_state->tasks; ct; ct = ct->next) { tmp = elf_dump_thread_status(cprm->siginfo->si_signo, ct->task, &thread_status_size); diff --git a/fs/coredump.c b/fs/coredump.c index 3e78941f281e36..80ba8a403d7beb 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -526,8 +526,7 @@ static int coredump_wait(int exit_code, struct core_state *core_state) int core_waiters = -EBUSY; init_completion(&core_state->startup); - core_state->dumper.task = tsk; - core_state->dumper.next = NULL; + core_state->tasks = NULL; core_waiters = zap_threads(tsk, core_state, exit_code); if (core_waiters > 0) { @@ -540,7 +539,7 @@ static int coredump_wait(int exit_code, struct core_state *core_state) * all the thread context (extended register state, like * fpu etc) gets copied to the memory. */ - ptr = core_state->dumper.next; + ptr = core_state->tasks; while (ptr != NULL) { wait_task_inactive(ptr->task, TASK_ANY); ptr = ptr->next; @@ -558,7 +557,7 @@ static void coredump_finish(bool core_dumped) spin_lock_irq(¤t->sighand->siglock); if (core_dumped && !__fatal_signal_pending(current)) current->signal->group_exit_code |= 0x80; - next = current->signal->core_state->dumper.next; + next = current->signal->core_state->tasks; current->signal->core_state = NULL; spin_unlock_irq(¤t->sighand->siglock); diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index d45a5476b97dec..14b55d00d6059d 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -80,7 +80,7 @@ struct core_thread { struct core_state { atomic_t nr_threads; - struct core_thread dumper; + struct core_thread *tasks; struct completion startup; }; diff --git a/kernel/exit.c b/kernel/exit.c index 4e028f15759788..3df1fffc6674e2 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -435,12 +435,12 @@ static void coredump_task_exit(struct task_struct *tsk, self.task = tsk; if (self.task->flags & PF_SIGNALED) - self.next = xchg(&core_state->dumper.next, &self); + self.next = xchg(&core_state->tasks, &self); else self.task = NULL; /* * Implies mb(), the result of xchg() must be visible - * to core_state->dumper. + * to the dumper. */ if (atomic_dec_and_test(&core_state->nr_threads)) complete(&core_state->startup); From 5d075bd77ffbced7863d31e22cb2e7107e1ab957 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:09 +0200 Subject: [PATCH 1404/2286] sched: add wait_var_event_state() All wait_var_event() sleep in a fixed task state. For coredumps we need a variant that takes the state from the caller the way wait_event_state() does. This allows us to continue sleeping with TASK_FREEZABLE. That's certainly also a useful addition for other places. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-11-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- include/linux/wait_bit.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/linux/wait_bit.h b/include/linux/wait_bit.h index 553d7b23e3adfd..af077ed4caf6a1 100644 --- a/include/linux/wait_bit.h +++ b/include/linux/wait_bit.h @@ -432,6 +432,32 @@ do { \ __ret; \ }) +/** + * wait_var_event_state - wait for a variable to be updated and notified + * @var: the address of variable being waited on + * @condition: the condition to wait for + * @state: the task state to sleep in, %TASK_UNINTERRUPTIBLE etc. + * + * Wait for a @condition to be true, only re-checking when a wake up is + * received for the given @var (an arbitrary kernel address which need + * not be directly related to the given condition, but usually is). + * + * Returns 0 if the condition became true, or %-ERESTARTSYS if a signal + * arrived which @state allows to interrupt. + * + * The condition should normally use smp_load_acquire() or a similarly + * ordered access to ensure that any changes to memory made before the + * condition became true will be visible after the wait completes. + */ +#define wait_var_event_state(var, condition, state) \ +({ \ + int __ret = 0; \ + might_sleep(); \ + if (!(condition)) \ + __ret = ___wait_var_event(var, condition, (state), 0, 0, schedule()); \ + __ret; \ +}) + /** * wait_var_event_any_lock - wait for a variable to be updated under a lock * @var: the address of the variable being waited on From c26b947a9a31a32cf53a48c55cc7a4aa288aedf7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:10 +0200 Subject: [PATCH 1405/2286] coredump: replace the startup completion with a thread count coredump_wait() sets core_state->nr_threads to the number of tasks killed and waits for the last thread to enter coredump_task_exit() to signal completion. Let's just wait on the count directly. The exiting tasks can use atomic_dec_and_wake_up() and the dumping task sleeps in wait_var_event_state(). The dumping task must remain freezable since commit f5d39b020809 ("freezer,sched: Rewrite core freezer logic"). So keep the wait TASK_UNINTERRUPTIBLE|TASK_FREEZABLE. Drop the completion and rename nr_threads to threads_remaining. No functional changes. Suggested-by: NeilBrown Link: https://lore.kernel.org/178899497961.207413.10554121774377911612@noble.neil.brown.name Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-12-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 9 +++++---- include/linux/sched/signal.h | 4 ++-- kernel/exit.c | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 80ba8a403d7beb..11c18be497b326 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -514,7 +515,7 @@ static int zap_threads(struct task_struct *tsk, nr = zap_process(signal, exit_code); clear_tsk_thread_flag(tsk, TIF_SIGPENDING); tsk->flags |= PF_DUMPCORE; - atomic_set(&core_state->nr_threads, nr); + atomic_set(&core_state->threads_remaining, nr); } spin_unlock_irq(&tsk->sighand->siglock); return nr; @@ -525,15 +526,15 @@ static int coredump_wait(int exit_code, struct core_state *core_state) struct task_struct *tsk = current; int core_waiters = -EBUSY; - init_completion(&core_state->startup); core_state->tasks = NULL; core_waiters = zap_threads(tsk, core_state, exit_code); if (core_waiters > 0) { struct core_thread *ptr; - wait_for_completion_state(&core_state->startup, - TASK_UNINTERRUPTIBLE|TASK_FREEZABLE); + wait_var_event_state(&core_state->threads_remaining, + !atomic_read_acquire(&core_state->threads_remaining), + TASK_UNINTERRUPTIBLE|TASK_FREEZABLE); /* * Wait for all the threads to become inactive, so that * all the thread context (extended register state, like diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index 14b55d00d6059d..e039e29cd8c589 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -79,9 +79,9 @@ struct core_thread { }; struct core_state { - atomic_t nr_threads; + /* Threads the dumper still waits for. */ + atomic_t threads_remaining; struct core_thread *tasks; - struct completion startup; }; /* diff --git a/kernel/exit.c b/kernel/exit.c index 3df1fffc6674e2..55dbea3b242e1c 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -442,8 +443,7 @@ static void coredump_task_exit(struct task_struct *tsk, * Implies mb(), the result of xchg() must be visible * to the dumper. */ - if (atomic_dec_and_test(&core_state->nr_threads)) - complete(&core_state->startup); + atomic_dec_and_wake_up(&core_state->threads_remaining); for (;;) { set_current_state(TASK_IDLE|TASK_FREEZABLE); @@ -917,7 +917,7 @@ static void synchronize_group_exit(struct task_struct *tsk, long code) * Serialize with any possible pending coredump. * We must hold siglock around checking core_state * and setting PF_POSTCOREDUMP. The core-inducing thread - * will increment ->nr_threads for each thread in the + * will increment ->threads_remaining for each thread in the * group without PF_POSTCOREDUMP set. */ tsk->flags |= PF_POSTCOREDUMP; From 296a98c1fa50778c3b31eebed1a3f903f9684349 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:56 +0200 Subject: [PATCH 1406/2286] fork: release the files of a failed fork after sched_cancel_fork() Reorder copy_process() cleanup so sched_fork() taking scx_fork_rwsem scx_pre_fork() is safe and isn't held around exiting files. Right now, a fork that fails while another thread closed the bpf link fd will deadlock against its own read side. It also blocks every fork on the system: copy_process() holds scx_fork_rwsem for read exit_files() close_files() filp_close_sync() bpf_scx_unreg() kthread_flush_work() waits for the disable work scx_root_disable() percpu_down_write(&scx_fork_rwsem) Simply release the child's files after sched_cancel_fork() dropped the lock. The task starts out as a copy of its parent so p->files points to the parent's table until copy_files() replaces it. exit_files() must not run for a fork that failed before that. Clear p->files up front so exit_files() is a no-op for those and make copy_files() set it explicitly for CLONE_FILES. Reported-by: Chris Mason Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-7-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- kernel/fork.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/kernel/fork.c b/kernel/fork.c index 10be4a0ecb3f19..50f5b3e2ca87d9 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1676,6 +1676,7 @@ static int copy_files(u64 clone_flags, struct task_struct *tsk, if (clone_flags & CLONE_FILES) { atomic_inc(&oldf->count); + tsk->files = oldf; return 0; } @@ -2199,6 +2200,8 @@ __latent_entropy struct task_struct *copy_process( INIT_LIST_HEAD(&p->sibling); rcu_copy_process(p); p->vfork_done = NULL; + /* Set by copy_files(), exit_files() on the error path skips NULL. */ + p->files = NULL; spin_lock_init(&p->alloc_lock); init_sigpending(&p->pending); @@ -2300,7 +2303,7 @@ __latent_entropy struct task_struct *copy_process( goto bad_fork_cleanup_semundo; retval = copy_fs(clone_flags, p, args->umh); if (retval) - goto bad_fork_cleanup_files; + goto bad_fork_cleanup_semundo; retval = copy_sighand(clone_flags, p); if (retval) goto bad_fork_cleanup_fs; @@ -2613,8 +2616,6 @@ __latent_entropy struct task_struct *copy_process( __cleanup_sighand(p->sighand); bad_fork_cleanup_fs: exit_fs(p); /* blocking */ -bad_fork_cleanup_files: - exit_files(p); /* blocking */ bad_fork_cleanup_semundo: exit_sem(p); bad_fork_cleanup_security: @@ -2625,6 +2626,8 @@ __latent_entropy struct task_struct *copy_process( perf_event_free_task(p); bad_fork_sched_cancel_fork: sched_cancel_fork(p); + /* ->release() of a file may need scx_fork_rwsem for write. */ + exit_files(p); /* blocking */ bad_fork_cleanup_policy: lockdep_free_task(p); #ifdef CONFIG_NUMA From ad016f0131ebfec250a3360ea75d35128b3f4aa8 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:44:57 +0200 Subject: [PATCH 1407/2286] exit: hang up the tty before closing the files do_exit() closes the task's files in exit_files() and hangs up the controlling tty of a session leader in disassociate_ctty(1) after that. Since commit d99d38540bf0 ("fs: make close_files() synchronous") the final __fput() of every file runs inside exit_files(). So when the session leader holds the last open of its tty the tty is released before disassociate_ctty() runs. tty_release() clears signal->tty for the whole session in session_clear_tty() once the count drops to zero and sends no signal doing so. disassociate_ctty(1) then finds neither a tty nor a tty_old_pgrp and does nothing. The foreground process group loses its SIGHUP: do_exit() exit_files() close_files() tty_release() tty->count == 0 session_clear_tty() signal->tty = NULL, no signal disassociate_ctty(1) get_current_tty() NULL signal->tty_old_pgrp NULL, nothing sent That only affects real ttys. For a pty the master's open keeps the slave's count above zero. And it only affects a foreground job that holds no descriptor to the tty anymore while its session leader exits. Everything else is unchanged. The DTR drop on the last close happens in tty_port_shutdown() regardless, stopped jobs get their SIGHUP from kill_orphaned_pgrp() and signal->tty is cleared either way. Before that commit the final __fput() ran from exit_task_work() which comes after disassociate_ctty(). That order isn't old. Until v3.14 exit_task_work() came right after exit_files() and before v3.6 fput() was synchronous, so the tty was always released first. Commit c39df5fa37b0 ("exit: call disassociate_ctty() before exit_task_namespaces()") moved disassociate_ctty() up to fix a pppd crash and in front of exit_task_work() as a side effect. The hangup in this case has worked since then and that's eleven years of userspace being able to rely on it. Hang the tty up before closing the files. This is the ordinary hangup with the file still open: __tty_hangup() swaps in hung_up_tty_fops and tty_release() runs from the close afterwards as it does when a modem drops the line. disassociate_ctty() stays in front of exit_task_namespaces() which the pppd fix needs. Reported-by: Chris Mason Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-8-8e4adb1619e6@kernel.org Acked-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- kernel/exit.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 55dbea3b242e1c..9ed5eb03d0e199 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -1003,10 +1003,11 @@ void __noreturn do_exit(long code) exit_sem(tsk); exit_shm(tsk); - exit_files(tsk); - exit_fs(tsk); + /* Hang the tty up before the last close of it can clear the session. */ if (group_dead) disassociate_ctty(1); + exit_files(tsk); + exit_fs(tsk); exit_nsproxy_namespaces(tsk); exit_task_work(tsk); exit_thread(tsk); From 6758ef542c4f555fff3edeb1bd29a315baaccea5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 10 Sep 2026 17:48:11 +0200 Subject: [PATCH 1408/2286] coredump: factor out coredump_wait_inactive() Factor out a new coredump_wait_inactive() helper that COREDUMP_CLOSE_FILES can consume in a bit. No functional changes. Link: https://patch.msgid.link/20260910-work-coredump-unlock-self-v4-13-a5c1800dc930@kernel.org Reviewed-by: NeilBrown Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 11c18be497b326..6f4c6a900dc594 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -521,6 +521,22 @@ static int zap_threads(struct task_struct *tsk, return nr; } +static void coredump_wait_inactive(struct core_state *core_state) +{ + struct core_thread *ptr; + + wait_var_event_state(&core_state->threads_remaining, + !atomic_read_acquire(&core_state->threads_remaining), + TASK_UNINTERRUPTIBLE | TASK_FREEZABLE); + /* + * Wait for all the threads to become inactive, so that + * all the thread context (extended register state, like + * fpu etc) gets copied to the memory. + */ + for (ptr = core_state->tasks; ptr; ptr = ptr->next) + wait_task_inactive(ptr->task, TASK_ANY); +} + static int coredump_wait(int exit_code, struct core_state *core_state) { struct task_struct *tsk = current; @@ -529,23 +545,8 @@ static int coredump_wait(int exit_code, struct core_state *core_state) core_state->tasks = NULL; core_waiters = zap_threads(tsk, core_state, exit_code); - if (core_waiters > 0) { - struct core_thread *ptr; - - wait_var_event_state(&core_state->threads_remaining, - !atomic_read_acquire(&core_state->threads_remaining), - TASK_UNINTERRUPTIBLE|TASK_FREEZABLE); - /* - * Wait for all the threads to become inactive, so that - * all the thread context (extended register state, like - * fpu etc) gets copied to the memory. - */ - ptr = core_state->tasks; - while (ptr != NULL) { - wait_task_inactive(ptr->task, TASK_ANY); - ptr = ptr->next; - } - } + if (core_waiters > 0) + coredump_wait_inactive(core_state); return core_waiters; } From a019713118305c3d7a2c380590f7d206d25b21ae Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 21 Sep 2026 15:45:06 +0200 Subject: [PATCH 1409/2286] fs: close files from the highest descriptor down close_files(), __range_close() and close_cloexec_files() walk the descriptor table from the lowest descriptor up. They used to call filp_close(), which left the final __fput() to task work. task work is a LIFO list, so the releases ran after the walk had finished and in the opposite direction, highest descriptor first. This dumb ordering is relevant for a bunch of broken but long-standing cases. It matters whenever the ->flush() or ->release() of one file waits for something that only the release of another file of the same table provides. Then one of the two orders deadlocks and the other one doesn't: exit, fd 3 is one end of a pipe peer ------------------------------- ---- splice(socket -> pipe) pipe_lock() waits for data or EOF close_files() fd 3: pipe_release() mutex_lock(&pipe->mutex) held by the peer fd 5: the socket, not reached would be the peer's EOF Programs create the thing that guards or wakes another thing first. Hence, it gets the lower descriptor which is the layout that breaks: (1) a tap device released before the AF_LLC socket that holds a reference to it (2) an unlinked fsdax file evicted before the pipe that holds its vmspliced pages (3) an overlayfs directory whose release queues up behind an unlink that waits for a splice into the same directory The exiting task is unkillable in all of them. All of that crap can obviously also become a bug if you reorder the file descriptors. Continue walking all three tables from the highest descriptor down. That restores the order the deferred puts had. ->flush() moves with the release. So it now runs highest descriptor first as well. None of this fixes the underlying defects. For every one of these pairs the mirrored layout deadlocked before and deadlocks again now: (1') splice() holding pipe->mutex across unbounded socket and tty I/O (2') AF_LLC keeping a netdev reference without a NETDEV_UNREGISTER handler (3') uninterruptible wait in dax_break_layout_final() (4') ovl_splice_write() sleeping under the inode lock It all predates the synchronous close and each should really get fixed. Reported-by: Chris Mason Link: https://patch.msgid.link/20260921-work-coredump-fixes-v3-17-8e4adb1619e6@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/file.c | 119 +++++++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/fs/file.c b/fs/file.c index 49a6e308f56187..ca2d92262c320a 100644 --- a/fs/file.c +++ b/fs/file.c @@ -364,7 +364,7 @@ static unsigned long fd_range_word(struct fd_range *range, unsigned int i) max(range->from, first) - first); } -/* Bits of word @i that dup_fd() leaves behind. */ +/* Bits of word @i that dup_fd() leaves behind and __range_close() closes. */ static unsigned long dup_fd_dropped_word(struct fdtable *fdt, unsigned int i, struct fd_range *range) { @@ -557,24 +557,21 @@ static struct fdtable *close_files(struct files_struct *files) * files structure. */ struct fdtable *fdt = rcu_dereference_raw(files->fdt); - unsigned int i, j = 0; + unsigned int j = fdt->max_fds / BITS_PER_LONG; + + /* Highest fd first, the order the deferred puts ran in. */ + while (j--) { + unsigned long set = fdt->open_fds[j]; - for (;;) { - unsigned long set; - i = j * BITS_PER_LONG; - if (i >= fdt->max_fds) - break; - set = fdt->open_fds[j++]; while (set) { - if (set & 1) { - struct file *file = fdt->fd[i]; - if (file) { - filp_close_sync(file, files); - cond_resched(); - } + unsigned int bit = __fls(set); + struct file *file = fdt->fd[j * BITS_PER_LONG + bit]; + + set ^= 1UL << bit; + if (file) { + filp_close_sync(file, files); + cond_resched(); } - i++; - set >>= 1; } } @@ -860,31 +857,42 @@ static inline void __range_cloexec(struct files_struct *cur_fds, spin_unlock(&cur_fds->file_lock); } -/* Next open descriptor in [fd, max_fd], or the next close-on-exec one. */ -static inline unsigned int next_open_fd(struct fdtable *fdt, unsigned int fd, - unsigned int max_fd, - struct fd_range *range) -{ - if (range->flags & FD_RANGE_CLOEXEC_ONLY) - return find_next_and_bit(fdt->open_fds, fdt->close_on_exec, - max_fd + 1, fd); - return find_next_bit(fdt->open_fds, max_fd + 1, fd); -} - -/* Next open descriptor in [fd, max_fd] that @range selects. */ -static inline unsigned int next_fd_to_close(struct fdtable *fdt, - unsigned int fd, unsigned int max_fd, +/* Highest open descriptor below @n that @range selects, or @n. */ +static inline unsigned int last_fd_to_close(struct fdtable *fdt, unsigned int n, struct fd_range *range) { - fd = next_open_fd(fdt, fd, max_fd, range); - /* Hop over the window the range keeps. */ - if ((range->flags & FD_RANGE_EXCEPT) && - fd >= range->from && fd <= range->to) { - if (range->to >= max_fd) - return max_fd + 1; - fd = next_open_fd(fdt, range->to + 1, max_fd, range); + unsigned int i, lo = 0; + + if (!(range->flags & FD_RANGE_EXCEPT)) + lo = range->from / BITS_PER_LONG; + for (i = n ? (n - 1) / BITS_PER_LONG + 1 : 0; i-- > lo; ) { + unsigned long set = fdt->open_fds[i]; + + if (!set) { + /* Skip the empty stretch at find_last_bit() speed. */ + unsigned int last = find_last_bit(fdt->open_fds, i * BITS_PER_LONG); + + if (last >= i * BITS_PER_LONG) + break; + i = last / BITS_PER_LONG + 1; + continue; + } + /* Hop below the kept window in one step. */ + if ((range->flags & FD_RANGE_EXCEPT) && + i * BITS_PER_LONG >= range->from && + i * BITS_PER_LONG + BITS_PER_LONG - 1 <= range->to) { + if (!range->from) + break; + i = (range->from - 1) / BITS_PER_LONG + 1; + continue; + } + set &= dup_fd_dropped_word(fdt, i, range); + if (i == (n - 1) / BITS_PER_LONG) + set &= BITMAP_LAST_WORD_MASK(n); + if (set) + return i * BITS_PER_LONG + __fls(set); } - return fd; + return n; } static inline void __range_close(struct files_struct *files, @@ -892,22 +900,19 @@ static inline void __range_close(struct files_struct *files, { struct file *file; struct fdtable *fdt; - unsigned int fd, max_fd; + unsigned int fd, n; spin_lock(&files->file_lock); fdt = files_fdtable(files); - if (range->flags & FD_RANGE_EXCEPT) { + if (range->flags & FD_RANGE_EXCEPT) /* Outside of the range means the whole table. */ - fd = 0; - max_fd = last_fd(fdt); - } else { - fd = range->from; - max_fd = min(range->to, last_fd(fdt)); - } + n = fdt->max_fds; + else + n = min(range->to, last_fd(fdt)) + 1; - for (fd = next_fd_to_close(fdt, fd, max_fd, range); - fd <= max_fd; - fd = next_fd_to_close(fdt, fd + 1, max_fd, range)) { + /* Highest fd first, see close_files(). */ + while ((fd = last_fd_to_close(fdt, n, range)) < n) { + n = fd; file = file_close_fd_locked(files, fd); if (file) { spin_unlock(&files->file_lock); @@ -1036,20 +1041,22 @@ void close_cloexec_files(struct files_struct *files) /* exec unshares first */ spin_lock(&files->file_lock); - for (i = 0; ; i++) { + fdt = files_fdtable(files); + /* Highest fd first, see close_files(). */ + for (i = fdt->max_fds / BITS_PER_LONG; i--; ) { unsigned long set; - unsigned fd = i * BITS_PER_LONG; + fdt = files_fdtable(files); - if (fd >= fdt->max_fds) - break; set = fdt->close_on_exec[i]; if (!set) continue; fdt->close_on_exec[i] = 0; - for ( ; set ; fd++, set >>= 1) { + while (set) { + unsigned int bit = __fls(set); + unsigned fd = i * BITS_PER_LONG + bit; struct file *file; - if (!(set & 1)) - continue; + + set ^= 1UL << bit; file = fdt->fd[fd]; if (!file) continue; From c110639a1c9f36ce5382e9e70fccaa3f922ef35d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:02 +0200 Subject: [PATCH 1410/2286] xfs: centralize setting of buf_ops/buf_type/magic for rtblocks Add two tables for the buf_ops and buf_type, and derive the magic from the buf_ops to make have a single source of truth for the different RT block variants. This cleans up the existing code and makes adding another type of block/file easier. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtbitmap.c | 40 +++++++++++++++++++++--------------- fs/xfs/libxfs/xfs_rtbitmap.h | 15 ++------------ 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c index e62786e90f7b3b..3f7877220e05eb 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.c +++ b/fs/xfs/libxfs/xfs_rtbitmap.c @@ -125,6 +125,26 @@ const struct xfs_buf_ops xfs_rtsummary_buf_ops = { .verify_struct = xfs_rtbuf_verify, }; +static const struct xfs_buf_ops *xfs_rtblock_buf_ops[XFS_RTGI_MAX] = { + [XFS_RTGI_SUMMARY] = &xfs_rtsummary_buf_ops, + [XFS_RTGI_BITMAP] = &xfs_rtbitmap_buf_ops, +}; + +const struct xfs_buf_ops * +xfs_rtblock_ops( + struct xfs_mount *mp, + enum xfs_rtg_inodes type) +{ + if (!xfs_has_rtgroups(mp)) + return &xfs_rtbuf_ops; + return xfs_rtblock_buf_ops[type]; +} + +static enum xfs_blft xfs_rtblock_buf_types[XFS_RTGI_MAX] = { + [XFS_RTGI_SUMMARY] = XFS_BLFT_RTSUMMARY_BUF, + [XFS_RTGI_BITMAP] = XFS_BLFT_RTBITMAP_BUF, +}; + /* Release cached rt bitmap and summary buffers. */ void xfs_rtbuf_cache_relse( @@ -158,7 +178,6 @@ xfs_rtbuf_get( xfs_fileoff_t *coffp; /* cached block number */ struct xfs_buf *bp; /* block buffer, result */ struct xfs_bmbt_irec map; - enum xfs_blft buf_type; int nmap = 1; int error; @@ -166,12 +185,10 @@ xfs_rtbuf_get( case XFS_RTGI_SUMMARY: cbpp = &args->sumbp; coffp = &args->sumoff; - buf_type = XFS_BLFT_RTSUMMARY_BUF; break; case XFS_RTGI_BITMAP: cbpp = &args->rbmbp; coffp = &args->rbmoff; - buf_type = XFS_BLFT_RTBITMAP_BUF; break; default: return -EINVAL; @@ -222,7 +239,7 @@ xfs_rtbuf_get( } } - xfs_trans_buf_set_type(args->tp, bp, buf_type); + xfs_trans_buf_set_type(args->tp, bp, xfs_rtblock_buf_types[type]); *cbpp = bp; *coffp = block; return 0; @@ -1375,16 +1392,8 @@ xfs_rtfile_initialize_block( struct xfs_buf *bp; void *bufdata; const size_t copylen = mp->m_blockwsize << XFS_WORDLOG; - enum xfs_blft buf_type; int error; - if (type == XFS_RTGI_BITMAP) - buf_type = XFS_BLFT_RTBITMAP_BUF; - else if (type == XFS_RTGI_SUMMARY) - buf_type = XFS_BLFT_RTSUMMARY_BUF; - else - return -EINVAL; - error = xfs_trans_alloc(mp, &M_RES(mp)->tr_growrtzero, 0, 0, 0, &tp); if (error) return error; @@ -1399,16 +1408,13 @@ xfs_rtfile_initialize_block( } bufdata = bp->b_addr; - xfs_trans_buf_set_type(tp, bp, buf_type); + xfs_trans_buf_set_type(tp, bp, xfs_rtblock_buf_types[type]); bp->b_ops = xfs_rtblock_ops(mp, type); if (xfs_has_rtgroups(mp)) { struct xfs_rtbuf_blkinfo *hdr = bp->b_addr; - if (type == XFS_RTGI_BITMAP) - hdr->rt_magic = cpu_to_be32(XFS_RTBITMAP_MAGIC); - else - hdr->rt_magic = cpu_to_be32(XFS_RTSUMMARY_MAGIC); + hdr->rt_magic = bp->b_ops->magic[1]; hdr->rt_owner = cpu_to_be64(I_INO(ip)); hdr->rt_blkno = cpu_to_be64(XFS_FSB_TO_DADDR(mp, fsbno)); hdr->rt_lsn = 0; diff --git a/fs/xfs/libxfs/xfs_rtbitmap.h b/fs/xfs/libxfs/xfs_rtbitmap.h index 22e5d9cd95f47c..375cc48e1a53ee 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.h +++ b/fs/xfs/libxfs/xfs_rtbitmap.h @@ -354,19 +354,6 @@ xfs_suminfo_add( return info->old; } -static inline const struct xfs_buf_ops * -xfs_rtblock_ops( - struct xfs_mount *mp, - enum xfs_rtg_inodes type) -{ - if (xfs_has_rtgroups(mp)) { - if (type == XFS_RTGI_SUMMARY) - return &xfs_rtsummary_buf_ops; - return &xfs_rtbitmap_buf_ops; - } - return &xfs_rtbuf_ops; -} - /* * Functions for walking free space rtextents in the realtime bitmap. */ @@ -419,6 +406,8 @@ xfs_filblks_t xfs_rtbitmap_blockcount_len(struct xfs_mount *mp, xfs_filblks_t xfs_rtsummary_blockcount(struct xfs_mount *mp, unsigned int *rsumlevels); +const struct xfs_buf_ops *xfs_rtblock_ops(struct xfs_mount *mp, + enum xfs_rtg_inodes type); int xfs_rtfile_initialize_blocks(struct xfs_rtgroup *rtg, enum xfs_rtg_inodes type, xfs_fileoff_t offset_fsb, xfs_fileoff_t end_fsb, void *data); From 724b5e3a549233bf71e3ff5a90255e8c04dfed1e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:03 +0200 Subject: [PATCH 1411/2286] xfs: factor out a xfs_rtfile_initialize_buf helper Share the code to initialize the header and buf ops for rtfile blocks into a single helper. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtbitmap.c | 38 +++++++++++++++++++++------------ fs/xfs/libxfs/xfs_rtbitmap.h | 3 +++ fs/xfs/scrub/rtbitmap_repair.c | 15 +------------ fs/xfs/scrub/rtsummary_repair.c | 15 +------------ 4 files changed, 29 insertions(+), 42 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c index 3f7877220e05eb..5cadde81b5dad3 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.c +++ b/fs/xfs/libxfs/xfs_rtbitmap.c @@ -1378,6 +1378,27 @@ xfs_rtfile_alloc_blocks( return error; } +void +xfs_rtfile_initialize_buf( + struct xfs_rtgroup *rtg, + enum xfs_rtg_inodes type, + struct xfs_buf *bp, + struct xfs_trans *tp) +{ + bp->b_ops = xfs_rtblock_ops(bp->b_mount, type); + if (tp) + xfs_trans_buf_set_type(tp, bp, xfs_rtblock_buf_types[type]); + if (xfs_has_rtgroups(bp->b_mount)) { + struct xfs_rtbuf_blkinfo *hdr = bp->b_addr; + + hdr->rt_magic = bp->b_ops->magic[1]; + hdr->rt_owner = cpu_to_be64(I_INO(rtg->rtg_inodes[type])); + hdr->rt_blkno = cpu_to_be64(xfs_buf_daddr(bp)); + hdr->rt_lsn = 0; + uuid_copy(&hdr->rt_uuid, &bp->b_mount->m_sb.sb_meta_uuid); + } +} + /* Get a buffer for the block. */ static int xfs_rtfile_initialize_block( @@ -1408,21 +1429,10 @@ xfs_rtfile_initialize_block( } bufdata = bp->b_addr; - xfs_trans_buf_set_type(tp, bp, xfs_rtblock_buf_types[type]); - bp->b_ops = xfs_rtblock_ops(mp, type); - - if (xfs_has_rtgroups(mp)) { - struct xfs_rtbuf_blkinfo *hdr = bp->b_addr; - - hdr->rt_magic = bp->b_ops->magic[1]; - hdr->rt_owner = cpu_to_be64(I_INO(ip)); - hdr->rt_blkno = cpu_to_be64(XFS_FSB_TO_DADDR(mp, fsbno)); - hdr->rt_lsn = 0; - uuid_copy(&hdr->rt_uuid, &mp->m_sb.sb_meta_uuid); - - bufdata += sizeof(*hdr); - } + xfs_rtfile_initialize_buf(rtg, type, bp, tp); + if (xfs_has_rtgroups(mp)) + bufdata += sizeof(struct xfs_rtbuf_blkinfo); if (data) memcpy(bufdata, data, copylen); else diff --git a/fs/xfs/libxfs/xfs_rtbitmap.h b/fs/xfs/libxfs/xfs_rtbitmap.h index 375cc48e1a53ee..4a87e1fd3e99e9 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.h +++ b/fs/xfs/libxfs/xfs_rtbitmap.h @@ -408,6 +408,9 @@ xfs_filblks_t xfs_rtsummary_blockcount(struct xfs_mount *mp, const struct xfs_buf_ops *xfs_rtblock_ops(struct xfs_mount *mp, enum xfs_rtg_inodes type); +void xfs_rtfile_initialize_buf(struct xfs_rtgroup *rtg, + enum xfs_rtg_inodes type, struct xfs_buf *bp, + struct xfs_trans *tp); int xfs_rtfile_initialize_blocks(struct xfs_rtgroup *rtg, enum xfs_rtg_inodes type, xfs_fileoff_t offset_fsb, xfs_fileoff_t end_fsb, void *data); diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index f63aaaff783d50..34f5017b4ecba1 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -383,21 +383,8 @@ xrep_rtbitmap_prep_buf( if (error) return error; - if (xfs_has_rtgroups(sc->mp)) { - struct xfs_rtbuf_blkinfo *hdr = bp->b_addr; - - hdr->rt_magic = cpu_to_be32(XFS_RTBITMAP_MAGIC); - hdr->rt_owner = cpu_to_be64(I_INO(sc->ip)); - hdr->rt_blkno = cpu_to_be64(xfs_buf_daddr(bp)); - hdr->rt_lsn = 0; - uuid_copy(&hdr->rt_uuid, &sc->mp->m_sb.sb_meta_uuid); - bp->b_ops = &xfs_rtbitmap_buf_ops; - } else { - bp->b_ops = &xfs_rtbuf_ops; - } - + xfs_rtfile_initialize_buf(sc->sr.rtg, XFS_RTGI_BITMAP, bp, sc->tp); rtb->prep_wordoff += mp->m_blockwsize; - xfs_trans_buf_set_type(sc->tp, bp, XFS_BLFT_RTBITMAP_BUF); return 0; } diff --git a/fs/xfs/scrub/rtsummary_repair.c b/fs/xfs/scrub/rtsummary_repair.c index ed763290aec1e5..e91dbad25d6de3 100644 --- a/fs/xfs/scrub/rtsummary_repair.c +++ b/fs/xfs/scrub/rtsummary_repair.c @@ -87,21 +87,8 @@ xrep_rtsummary_prep_buf( if (error) return error; - if (xfs_has_rtgroups(sc->mp)) { - struct xfs_rtbuf_blkinfo *hdr = bp->b_addr; - - hdr->rt_magic = cpu_to_be32(XFS_RTSUMMARY_MAGIC); - hdr->rt_owner = cpu_to_be64(I_INO(sc->ip)); - hdr->rt_blkno = cpu_to_be64(xfs_buf_daddr(bp)); - hdr->rt_lsn = 0; - uuid_copy(&hdr->rt_uuid, &sc->mp->m_sb.sb_meta_uuid); - bp->b_ops = &xfs_rtsummary_buf_ops; - } else { - bp->b_ops = &xfs_rtbuf_ops; - } - + xfs_rtfile_initialize_buf(sc->sr.rtg, XFS_RTGI_SUMMARY, bp, sc->tp); rts->prep_wordoff += mp->m_blockwsize; - xfs_trans_buf_set_type(sc->tp, bp, XFS_BLFT_RTSUMMARY_BUF); return 0; } From 46db7dec8bae04b2721c9d3e9e4fc75c14654754 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:04 +0200 Subject: [PATCH 1412/2286] xfs: add a xfs_rtblock_payload helper Add a helper to calculate the rtblock payload start with or without the self-describing metadata header. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_rtbitmap.c | 9 ++------- fs/xfs/libxfs/xfs_rtbitmap.h | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c index 5cadde81b5dad3..01536f4fb3868c 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.c +++ b/fs/xfs/libxfs/xfs_rtbitmap.c @@ -1411,7 +1411,6 @@ xfs_rtfile_initialize_block( struct xfs_inode *ip = rtg->rtg_inodes[type]; struct xfs_trans *tp; struct xfs_buf *bp; - void *bufdata; const size_t copylen = mp->m_blockwsize << XFS_WORDLOG; int error; @@ -1427,16 +1426,12 @@ xfs_rtfile_initialize_block( xfs_trans_cancel(tp); return error; } - bufdata = bp->b_addr; xfs_rtfile_initialize_buf(rtg, type, bp, tp); - - if (xfs_has_rtgroups(mp)) - bufdata += sizeof(struct xfs_rtbuf_blkinfo); if (data) - memcpy(bufdata, data, copylen); + memcpy(xfs_rtblock_payload(bp), data, copylen); else - memset(bufdata, 0, copylen); + memset(xfs_rtblock_payload(bp), 0, copylen); xfs_trans_log_buf(tp, bp, 0, mp->m_sb.sb_blocksize - 1); return xfs_trans_commit(tp); } diff --git a/fs/xfs/libxfs/xfs_rtbitmap.h b/fs/xfs/libxfs/xfs_rtbitmap.h index 4a87e1fd3e99e9..750d74fbf4eda4 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.h +++ b/fs/xfs/libxfs/xfs_rtbitmap.h @@ -20,6 +20,16 @@ struct xfs_rtalloc_args { xfs_fileoff_t sumoff; /* summary block number */ }; +/* Return the payload of the buffer after the optional header. */ +static inline void * +xfs_rtblock_payload( + struct xfs_buf *bp) +{ + if (!xfs_has_rtgroups(bp->b_mount)) + return bp->b_addr; + return bp->b_addr + sizeof(struct xfs_rtbuf_blkinfo); +} + static inline xfs_rtblock_t xfs_rtx_to_rtb( struct xfs_rtgroup *rtg, @@ -221,14 +231,7 @@ xfs_rbmblock_wordptr( struct xfs_rtalloc_args *args, unsigned int index) { - struct xfs_mount *mp = args->mp; - union xfs_rtword_raw *words; - struct xfs_rtbuf_blkinfo *hdr = args->rbmbp->b_addr; - - if (xfs_has_rtgroups(mp)) - words = (union xfs_rtword_raw *)(hdr + 1); - else - words = args->rbmbp->b_addr; + union xfs_rtword_raw *words = xfs_rtblock_payload(args->rbmbp); return words + index; } @@ -312,13 +315,7 @@ xfs_rsumblock_infoptr( struct xfs_rtalloc_args *args, unsigned int index) { - union xfs_suminfo_raw *info; - struct xfs_rtbuf_blkinfo *hdr = args->sumbp->b_addr; - - if (xfs_has_rtgroups(args->mp)) - info = (union xfs_suminfo_raw *)(hdr + 1); - else - info = args->sumbp->b_addr; + union xfs_suminfo_raw *info = xfs_rtblock_payload(args->sumbp); return info + index; } From dfe2d2d92df34f6ca9e19548fcc26b2e52a38552 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:05 +0200 Subject: [PATCH 1413/2286] xfs: cleanup xfs_verify_media_error Move all the reporting logic into xfs_verify_report_losses, and pass the enum xfs_device instead of the group type to it. This keeps all the reporting related conditions and translation confined to one place. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_verify_media.c | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c index b75c81f8fcc037..e8d40824fffa74 100644 --- a/fs/xfs/xfs_verify_media.c +++ b/fs/xfs/xfs_verify_media.c @@ -84,11 +84,14 @@ xfs_verify_report_data_lost( return 0; } -/* Walk reverse mappings to look for all file data loss */ -static int +/* + * Report data loss on the physical sectors, and if possible, walk the reverse + * mappings to also report the loss to the files mapped to these sectors. + */ +static void xfs_verify_report_losses( struct xfs_mount *mp, - enum xfs_group_type type, + enum xfs_device dev, xfs_daddr_t daddr, u64 bblen) { @@ -97,13 +100,25 @@ xfs_verify_report_losses( xfs_fsblock_t start_bno, end_bno; uint32_t start_gno, end_gno; int error; + enum xfs_group_type type; + + xfs_healthmon_report_media(mp, dev, daddr, bblen); + if (!xfs_has_rmapbt(mp)) + return; - if (type == XG_TYPE_RTG) { + switch (dev) { + case XFS_DEV_RT: start_bno = xfs_daddr_to_rtb(mp, daddr); end_bno = xfs_daddr_to_rtb(mp, daddr + bblen - 1); - } else { + type = XG_TYPE_RTG; + break; + case XFS_DEV_DATA: start_bno = XFS_DADDR_TO_FSB(mp, daddr); end_bno = XFS_DADDR_TO_FSB(mp, daddr + bblen - 1); + type = XG_TYPE_AG; + break; + default: + return; } tp = xfs_trans_alloc_empty(mp); @@ -164,7 +179,6 @@ xfs_verify_report_losses( } xfs_trans_cancel(tp); - return 0; } /* @@ -236,25 +250,11 @@ xfs_verify_media_error( case BLK_STS_PROTECTION: case BLK_STS_IOERR: case BLK_STS_MEDIUM: + if (me->me_flags & XFS_VERIFY_MEDIA_REPORT) + xfs_verify_report_losses(mp, me->me_dev, daddr, + bio_bbcount); break; default: - return; - } - - if (!(me->me_flags & XFS_VERIFY_MEDIA_REPORT)) - return; - - xfs_healthmon_report_media(mp, me->me_dev, daddr, bio_bbcount); - - if (!xfs_has_rmapbt(mp)) - return; - - switch (me->me_dev) { - case XFS_DEV_DATA: - xfs_verify_report_losses(mp, XG_TYPE_AG, daddr, bio_bbcount); - break; - case XFS_DEV_RT: - xfs_verify_report_losses(mp, XG_TYPE_RTG, daddr, bio_bbcount); break; } } From 89572b7fdc7bf10806e1bcde0d9aeccc0d8b3fdb Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:06 +0200 Subject: [PATCH 1414/2286] xfs: use bdev_rw_virt in xfs_verify_media Use bdev_rw_virt for reading data from disk. This simply uses an on-stack bio and bio_vec and removes the need to allocate an bio and reset it for each iteration. Because bdev_rw_virt returns a negative errno, pass that on to xfs_verify_media_error to avoid a double conversion. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_verify_media.c | 41 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c index e8d40824fffa74..516763219d3f0c 100644 --- a/fs/xfs/xfs_verify_media.c +++ b/fs/xfs/xfs_verify_media.c @@ -229,8 +229,10 @@ xfs_verify_media_error( struct xfs_buftarg *btp, xfs_daddr_t daddr, unsigned int bio_bbcount, - blk_status_t bio_status) + int error) { + blk_status_t bio_status = errno_to_blk_status(error); + trace_xfs_verify_media_error(mp, me, btp->bt_dev, daddr, bio_bbcount, bio_status); @@ -239,7 +241,7 @@ xfs_verify_media_error( * successfully verify any bytes at all. */ if (me->me_start_daddr == daddr) - me->me_ioerror = -blk_status_to_errno(bio_status); + me->me_ioerror = -error; /* * PI validation failures, medium errors, or general IO errors are @@ -266,7 +268,6 @@ xfs_verify_media( struct xfs_verify_media *me) { struct xfs_buftarg *btp = NULL; - struct bio *bio; struct folio *folio; xfs_daddr_t dev_start = 0; xfs_daddr_t dev_end = 0; @@ -342,33 +343,16 @@ xfs_verify_media( trace_xfs_verify_media(mp, me, btp->bt_dev, daddr, bbcount, folio); - bio = bio_alloc(btp->bt_bdev, 1, REQ_OP_READ, GFP_KERNEL); - if (!bio) { - error = -ENOMEM; - goto out_folio; - } - - while (bbcount > 0) { + for (;;) { unsigned int bio_bbcount; - blk_status_t bio_status; - - bio_reset(bio, btp->bt_bdev, REQ_OP_READ); - bio->bi_iter.bi_sector = daddr; - bio_add_folio_nofail(bio, folio, - min(bbcount << SECTOR_SHIFT, folio_size(folio)), - 0); - /* - * Save the length of the bio before we submit it, because we - * need the original daddr and length for reporting IO errors - * if the bio fails. - */ - bio_bbcount = bio->bi_iter.bi_size >> SECTOR_SHIFT; - submit_bio_wait(bio); - bio_status = bio->bi_status; - if (bio_status != BLK_STS_OK) { + bio_bbcount = min(bbcount, folio_size(folio) >> SECTOR_SHIFT); + error = bdev_rw_virt(btp->bt_bdev, daddr, folio_address(folio), + bio_bbcount << SECTOR_SHIFT, + REQ_OP_READ); + if (error) { xfs_verify_media_error(mp, me, btp, daddr, bio_bbcount, - bio_status); + error); error = 0; break; } @@ -396,10 +380,7 @@ xfs_verify_media( cond_resched(); } - bio_put(bio); -out_folio: folio_put(folio); - if (error) return error; From b5a96a9877a5c7426054ccf82528d5de795cb25a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:07 +0200 Subject: [PATCH 1415/2286] xfs: lift setting the NOFS context to xfs_end_io No point in clearing and restoring it for every processed bio. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioend.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index e70be5b86f0b7a..258c6130bee8ca 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -175,16 +175,8 @@ xfs_end_ioend_write( bool is_zoned = xfs_is_zoned_inode(ip); xfs_off_t offset = ioend->io_offset; size_t size = ioend->io_size; - unsigned int nofs_flag; int error; - /* - * We can allocate memory here while doing writeback on behalf of - * memory reclaim. To avoid memory allocation deadlocks set the - * task-wide nofs context for the following operations. - */ - nofs_flag = memalloc_nofs_save(); - /* * Just clean up the in-memory structures if the fs has been shut down. */ @@ -241,7 +233,6 @@ xfs_end_ioend_write( if (is_zoned) xfs_ioend_put_open_zones(ioend); iomap_finish_ioends(ioend, error); - memalloc_nofs_restore(nofs_flag); } /* @@ -266,6 +257,7 @@ xfs_end_io( container_of(work, struct xfs_inode, i_ioend_work); struct iomap_ioend *ioend; struct list_head tmp; + unsigned int nofs_flag; unsigned long flags; spin_lock_irqsave(&ip->i_ioend_lock, flags); @@ -273,6 +265,13 @@ xfs_end_io( spin_unlock_irqrestore(&ip->i_ioend_lock, flags); iomap_sort_ioends(&tmp); + + /* + * We can allocate memory here while doing writeback on behalf of + * memory reclaim. To avoid memory allocation deadlocks set the + * task-wide nofs context for the following operations. + */ + nofs_flag = memalloc_nofs_save(); while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, io_list))) { list_del_init(&ioend->io_list); @@ -280,6 +279,7 @@ xfs_end_io( xfs_end_ioend_write(ioend); cond_resched(); } + memalloc_nofs_restore(nofs_flag); } void From 0c94f5499d694a99c2fae3106daaec66971b8b1a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:08 +0200 Subject: [PATCH 1416/2286] xfs: split xfs_end_ioend_write Split out a separate helper for the zoned I/O completions as the code flow is pretty different already and will become even more so soon. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioend.c | 63 ++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index 258c6130bee8ca..f0e01ac34de8cc 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -147,11 +147,40 @@ xfs_ioend_submit_read( } static void -xfs_ioend_put_open_zones( +xfs_end_ioend_write_zoned( struct iomap_ioend *ioend) { - struct iomap_ioend *tmp; + struct xfs_inode *ip = XFS_I(ioend->io_inode); + struct xfs_open_zone *oz = ioend->io_private; + struct iomap_ioend *tmp; + int error = -EIO; + + ASSERT(!(ioend->io_flags & IOMAP_IOEND_SHARED)); + + if (xfs_is_shutdown(ip->i_mount)) + goto done; + /* + * Zoned writes update the in-core open zone accounting before I/O + * submission. A failed write leaves that state inconsistent, so shut + * down the filesystem instead of letting later writers wait forever for + * open zone space to become available. + */ + error = blk_status_to_errno(ioend->io_bio.bi_status); + if (unlikely(error)) { + xfs_force_shutdown(ip->i_mount, SHUTDOWN_META_IO_ERROR); + goto done; + } + + error = xfs_zoned_end_io(ip, ioend->io_offset, ioend->io_size, + ioend->io_sector, oz, NULLFSBLOCK); + if (error) + goto done; + + if (!(ioend->io_flags & IOMAP_IOEND_DIRECT) && + xfs_ioend_is_append(ioend)) + error = xfs_setfilesize(ip, ioend->io_offset, ioend->io_size); +done: /* * Put the open zone for all ioends merged into this one (if any). */ @@ -162,8 +191,9 @@ xfs_ioend_put_open_zones( * The main ioend might not have an open zone if the submission failed * before xfs_zone_alloc_and_submit got called. */ - if (ioend->io_private) - xfs_open_zone_put(ioend->io_private); + if (oz) + xfs_open_zone_put(oz); + iomap_finish_ioends(ioend, error); } static void @@ -172,7 +202,6 @@ xfs_end_ioend_write( { struct xfs_inode *ip = XFS_I(ioend->io_inode); struct xfs_mount *mp = ip->i_mount; - bool is_zoned = xfs_is_zoned_inode(ip); xfs_off_t offset = ioend->io_offset; size_t size = ioend->io_size; int error; @@ -194,19 +223,7 @@ xfs_end_ioend_write( */ error = blk_status_to_errno(ioend->io_bio.bi_status); if (unlikely(error)) { - /* - * Zoned writes update the in-core open zone accounting before - * I/O submission. A failed write leaves that state - * inconsistent, so shut down the filesystem instead of letting - * later writers wait forever for open zone space to become - * available. - */ - if (is_zoned) { - xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR); - goto done; - } if (ioend->io_flags & IOMAP_IOEND_SHARED) { - ASSERT(!is_zoned); xfs_reflink_cancel_cow_range(ip, offset, size, true); xfs_bmap_punch_delalloc_range(ip, XFS_DATA_FORK, offset, offset + size, NULL); @@ -217,10 +234,7 @@ xfs_end_ioend_write( /* * Success: commit the COW or unwritten blocks if needed. */ - if (is_zoned) - error = xfs_zoned_end_io(ip, offset, size, ioend->io_sector, - ioend->io_private, NULLFSBLOCK); - else if (ioend->io_flags & IOMAP_IOEND_SHARED) + if (ioend->io_flags & IOMAP_IOEND_SHARED) error = xfs_reflink_end_cow(ip, offset, size); else if (ioend->io_flags & IOMAP_IOEND_UNWRITTEN) error = xfs_iomap_write_unwritten(ip, offset, size, false); @@ -230,8 +244,6 @@ xfs_end_ioend_write( xfs_ioend_is_append(ioend)) error = xfs_setfilesize(ip, offset, size); done: - if (is_zoned) - xfs_ioend_put_open_zones(ioend); iomap_finish_ioends(ioend, error); } @@ -276,7 +288,10 @@ xfs_end_io( io_list))) { list_del_init(&ioend->io_list); iomap_ioend_try_merge(ioend, &tmp); - xfs_end_ioend_write(ioend); + if (xfs_is_zoned_inode(ip)) + xfs_end_ioend_write_zoned(ioend); + else + xfs_end_ioend_write(ioend); cond_resched(); } memalloc_nofs_restore(nofs_flag); From b74d34fa3cc7e8f35f1b969c68837ae99566749b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:09 +0200 Subject: [PATCH 1417/2286] xfs: factor out a xfs_zoned_fill_srcmap helper The code filling the srcmap in xfs_zoned_buffered_write_iomap_begin isn't terribly complicated, but fairly deeply indented and will become a bit more complex soon. Factor it into a helper and add some comments. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.c | 53 ++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 4906a4828d1101..a43769b506f899 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1588,6 +1588,37 @@ xfs_bmapi_reserve_delalloc( return error; } +static int +xfs_zoned_fill_srcmap( + struct xfs_inode *ip, + xfs_fileoff_t offset_fsb, + xfs_fileoff_t *end_fsb, + unsigned flags, + struct iomap *srcmap) +{ + struct xfs_bmbt_irec smap; + struct xfs_iext_cursor scur; + + if (!xfs_iext_lookup_extent(ip, &ip->i_df, offset_fsb, &scur, &smap)) + smap.br_startoff = *end_fsb; /* fake hole until EOF */ + if (smap.br_startoff > offset_fsb) { + /* + * There is a hole at @offset_fsb. Only map until the end of + * the of it. + */ + *end_fsb = min(*end_fsb, smap.br_startoff); + return 0; + } + + /* + * There is a data fork mapping, only map until the end of it. + */ + *end_fsb = min(*end_fsb, smap.br_startoff + smap.br_blockcount); + xfs_trim_extent(&smap, offset_fsb, *end_fsb - offset_fsb); + return xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0, + xfs_iomap_inode_sequence(ip, 0)); +} + static int xfs_zoned_buffered_write_iomap_begin( struct inode *inode, @@ -1655,24 +1686,10 @@ xfs_zoned_buffered_write_iomap_begin( if (!IS_ALIGNED(offset, mp->m_sb.sb_blocksize) || !IS_ALIGNED(offset + count, mp->m_sb.sb_blocksize) || (flags & IOMAP_ZERO)) { - struct xfs_bmbt_irec smap; - struct xfs_iext_cursor scur; - - if (!xfs_iext_lookup_extent(ip, &ip->i_df, offset_fsb, &scur, - &smap)) - smap.br_startoff = end_fsb; /* fake hole until EOF */ - if (smap.br_startoff > offset_fsb) { - end_fsb = min(end_fsb, smap.br_startoff); - } else { - end_fsb = min(end_fsb, - smap.br_startoff + smap.br_blockcount); - xfs_trim_extent(&smap, offset_fsb, - end_fsb - offset_fsb); - error = xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0, - xfs_iomap_inode_sequence(ip, 0)); - if (error) - goto out_unlock; - } + error = xfs_zoned_fill_srcmap(ip, offset_fsb, &end_fsb, flags, + srcmap); + if (error) + goto out_unlock; } if (!ip->i_cowfp) From b942c6919ac39870f8327d3123a2912d96e7e617 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 24 Sep 2026 11:57:10 +0200 Subject: [PATCH 1418/2286] xfs: calculate end_fsb later in xfs_zoned_fill_srcmap xfs_trim_extent doesn't need the updated end_fsb as it already is limited by the smap range. Move the end_fsb calculation later to prepare additional reasons that could reduce the smap range. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_iomap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index a43769b506f899..329c745e312045 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -1613,8 +1613,8 @@ xfs_zoned_fill_srcmap( /* * There is a data fork mapping, only map until the end of it. */ - *end_fsb = min(*end_fsb, smap.br_startoff + smap.br_blockcount); xfs_trim_extent(&smap, offset_fsb, *end_fsb - offset_fsb); + *end_fsb = min(*end_fsb, smap.br_startoff + smap.br_blockcount); return xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0, xfs_iomap_inode_sequence(ip, 0)); } From 7f662eb1807ed732ac7d5e119753c8185f241bb8 Mon Sep 17 00:00:00 2001 From: Qing Ming Date: Fri, 2 Oct 2026 17:11:28 -0400 Subject: [PATCH 1419/2286] dlm: validate node weights before building member array DLM node weights are parsed as signed integers and copied into the member list. make_member_array() uses their sum as the allocation count, but only positive weights contribute entries to the array. A negative weight can therefore reduce the allocation without reducing the number of writes. The issue was found through static analysis of the configfs input and member array construction. With two recovery members weighted -31 and 32, the sum is 1, so recovery allocates one int and then writes the positive member's node ID 32 times. A reproducer using dlm_controld and dlm_tool triggered the same report on a KASAN kernel: BUG: KASAN: slab-out-of-bounds in dlm_recover_members [dlm] Write of size 4 by task dlm_recoverd Call Trace: dlm_recover_members dlm_recoverd kthread ret_from_fork Reject negative weights at the configfs input boundary. Also detect overflow when adding non-negative weights to the signed allocation count, and propagate array construction errors to the recovery path. Fixes: e7fd41792fc0 ("[DLM] The core of the DLM for GFS2/CLVM") Signed-off-by: Qing Ming Assisted-by: Codex:gpt-5 Acked-by: Alexander Aring Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/config.c | 9 ++++++++- fs/dlm/member.c | 17 +++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/fs/dlm/config.c b/fs/dlm/config.c index 6c5c3f049b33a0..6ebc0fa9a2fa79 100644 --- a/fs/dlm/config.c +++ b/fs/dlm/config.c @@ -857,10 +857,17 @@ static ssize_t node_weight_show(struct config_item *item, char *buf) static ssize_t node_weight_store(struct config_item *item, const char *buf, size_t len) { - int rc = kstrtoint(buf, 0, &config_item_to_node(item)->weight); + struct dlm_node *node = config_item_to_node(item); + int weight; + int rc; + rc = kstrtoint(buf, 0, &weight); if (rc) return rc; + if (weight < 0) + return -EINVAL; + + node->weight = weight; return len; } diff --git a/fs/dlm/member.c b/fs/dlm/member.c index f84233a0fe4ad0..3a1c558ce9f6d4 100644 --- a/fs/dlm/member.c +++ b/fs/dlm/member.c @@ -402,7 +402,7 @@ void dlm_clear_members_gone(struct dlm_ls *ls) clear_memb_list(&ls->ls_nodes_gone, NULL); } -static void make_member_array(struct dlm_ls *ls) +static int make_member_array(struct dlm_ls *ls) { struct dlm_member *memb; int i, w, x = 0, total = 0, all_zero = 0, *array; @@ -411,8 +411,8 @@ static void make_member_array(struct dlm_ls *ls) ls->ls_node_array = NULL; list_for_each_entry(memb, &ls->ls_nodes, list) { - if (memb->weight) - total += memb->weight; + if (check_add_overflow(total, memb->weight, &total)) + return -EOVERFLOW; } /* all nodes revert to weight of 1 if all have weight 0 */ @@ -422,10 +422,9 @@ static void make_member_array(struct dlm_ls *ls) all_zero = 1; } - ls->ls_total_weight = total; array = kmalloc_objs(*array, total, GFP_NOFS); if (!array) - return; + return -ENOMEM; list_for_each_entry(memb, &ls->ls_nodes, list) { if (!all_zero && !memb->weight) @@ -442,7 +441,10 @@ static void make_member_array(struct dlm_ls *ls) array[x++] = memb->nodeid; } + ls->ls_total_weight = total; ls->ls_node_array = array; + + return 0; } /* send a status request to all members just to establish comms connections */ @@ -617,7 +619,10 @@ int dlm_recover_members(struct dlm_ls *ls, struct dlm_recover *rv, int *neg_out) } ls->ls_low_nodeid = low; - make_member_array(ls); + error = make_member_array(ls); + if (error) + return error; + *neg_out = neg; error = ping_members(ls, rv->seq); From cc4a16c721323f879b0615ebca12a6b2d6120b72 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 2 Oct 2026 17:11:29 -0400 Subject: [PATCH 1420/2286] dlm: clean up a type in waiters_read() This doesn't affect runtime but "rv" stores error codes and so declare it as ssize_t instead of size_t. Signed-off-by: Dan Carpenter Acked-by: Alexander Aring Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/debug_fs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/dlm/debug_fs.c b/fs/dlm/debug_fs.c index 700a0cbb2f1484..f52a51b1382a41 100644 --- a/fs/dlm/debug_fs.c +++ b/fs/dlm/debug_fs.c @@ -596,7 +596,8 @@ static ssize_t waiters_read(struct file *file, char __user *userbuf, { struct dlm_ls *ls = file->private_data; struct dlm_lkb *lkb; - size_t len = DLM_DEBUG_BUF_LEN, pos = 0, ret, rv; + size_t len = DLM_DEBUG_BUF_LEN, pos = 0, ret; + ssize_t rv; mutex_lock(&debug_buf_lock); ret = dlm_lock_recovery_try(ls); From 82bcb7b05ad90de126e9c09a7b46a81be799620e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 2 Oct 2026 17:11:30 -0400 Subject: [PATCH 1421/2286] dlm: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Acked-by: Alexander Aring Signed-off-by: Alexander Aring Signed-off-by: David Teigland --- fs/dlm/ast.c | 2 +- fs/dlm/lock.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/dlm/ast.c b/fs/dlm/ast.c index 0fe8d80ce5e8d3..7dd38e15bd25c1 100644 --- a/fs/dlm/ast.c +++ b/fs/dlm/ast.c @@ -81,7 +81,7 @@ bool dlm_may_skip_callback(struct dlm_lkb *lkb, uint32_t flags, int mode, * Suppress some redundant basts here, do more on removal. * Don't even add a bast if the callback just before it * is a bast for the same mode or a more restrictive mode. - * (the addional > PR check is needed for PR/CW inversion) + * (the additional > PR check is needed for PR/CW inversion) */ if (lkb->lkb_last_cb_mode != -1 && lkb->lkb_last_cb_flags & DLM_CB_BAST) { diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c index 2609e4fdeba847..c715e171bab7e9 100644 --- a/fs/dlm/lock.c +++ b/fs/dlm/lock.c @@ -92,7 +92,7 @@ static void do_purge(struct dlm_ls *ls, int nodeid, int pid); static void deactivate_rsb(struct kref *kref); /* - * Lock compatibilty matrix - thanks Steve + * Lock compatibility matrix - thanks Steve * UN = Unlocked state. Not really a state, used as a flag * PD = Padding. Used to make the matrix a nice power of two in size * Other states are the same as the VMS DLM. From 6a50ce0af5c91fd665d65d357af8ae55db1df7d0 Mon Sep 17 00:00:00 2001 From: Mohamad Raizudeen Date: Mon, 5 Oct 2026 20:33:24 +0530 Subject: [PATCH 1422/2286] lib/crypto: chacha20poly1305: zeroize state in __chacha20poly1305_decrypt The `__chacha20poly1305_decrypt` function does not zeroize the chacha state, unlike its encrypt counterpart. The regular `chacha20poly1305_decrypt` function handles this by manually calling chacha_zeroize_state(). However, `xchacha20poly1305_decrypt` returns the result directly without clearing the state, leaving the derived chacha20 subkey on the stack. Because this function is EXPORT_SYMBOL()ed, this is an ABI robustness improvement. To ensure consistent and safe cleanup for any future callers, move the chacha_zeroize_state() call into `__chacha20poly1305_decrypt()` itself, so the helper cleans up after its own state just like `__chacha20poly1305_encrypt()` does. Additionally, moved the src_len check to the callers so the state is not initialized on invalid inputs, matching the logic in chacha20poly1305_crypt_sg_inplace(). Suggested-by: Ard Biesheuvel Signed-off-by: Mohamad Raizudeen Link: https://patch.msgid.link/20261005150324.5621-1-raizudeen.kerneldev@gmail.com Signed-off-by: Eric Biggers --- lib/crypto/chacha20poly1305.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/crypto/chacha20poly1305.c b/lib/crypto/chacha20poly1305.c index ea42a28f4ff7d1..c3995ac8acb7c2 100644 --- a/lib/crypto/chacha20poly1305.c +++ b/lib/crypto/chacha20poly1305.c @@ -137,9 +137,6 @@ __chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, __le64 lens[2]; } b; - if (unlikely(src_len < POLY1305_DIGEST_SIZE)) - return false; - chacha20_crypt(chacha_state, b.block0, pad0, sizeof(b.block0)); poly1305_init(&poly1305_state, b.block0); @@ -162,6 +159,7 @@ __chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, if (likely(!ret)) chacha20_crypt(chacha_state, dst, src, dst_len); + chacha_zeroize_state(chacha_state); memzero_explicit(&b, sizeof(b)); return !ret; @@ -177,6 +175,9 @@ bool chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, __le64 iv[2]; bool ret; + if (unlikely(src_len < POLY1305_DIGEST_SIZE)) + return false; + chacha_load_key(k, key); iv[0] = 0; @@ -186,7 +187,6 @@ bool chacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, ret = __chacha20poly1305_decrypt(dst, src, src_len, ad, ad_len, &chacha_state); - chacha_zeroize_state(&chacha_state); memzero_explicit(iv, sizeof(iv)); memzero_explicit(k, sizeof(k)); return ret; @@ -200,6 +200,9 @@ bool xchacha20poly1305_decrypt(u8 *dst, const u8 *src, const size_t src_len, { struct chacha_state chacha_state; + if (unlikely(src_len < POLY1305_DIGEST_SIZE)) + return false; + xchacha_init(&chacha_state, key, nonce); return __chacha20poly1305_decrypt(dst, src, src_len, ad, ad_len, &chacha_state); From d4dfe010220320801fc97ae7a6befa3aaf4cb2d1 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Mon, 5 Oct 2026 08:31:06 +0900 Subject: [PATCH 1423/2286] nilfs2: simplify nilfs_mdt_writeback() The writepages() callback for metadata files, nilfs_mdt_writeback(), calls nilfs_mdt_write_folio() for each dirty folio via writeback_iter(), where read-only superblock checks, folio dirty state clearing on error, and triggering of the log writer via nilfs_construct_segment() are performed. This helper function is used nowhere else. Executing these operations per folio is inefficient: - Checking if the filesystem has fallen back to read-only mode is repeated redundantly. - Calling nilfs_construct_segment() inside the per-folio loop causes redundant log writer invocations for a single sync request, where only the first invocation performs actual log writing. Unfold nilfs_mdt_write_folio() into nilfs_mdt_writeback() and consolidate these operations at the function level. The read-only check is moved to the top of nilfs_mdt_writeback(), replacing per-folio nilfs_clear_folio_dirty() calls with a single nilfs_clear_dirty_pages() call to discard dirty pages across the mapping at once. Additionally, nilfs_construct_segment() is invoked once when 'sync_mode' is WB_SYNC_ALL and the mapping is tagged dirty. With these operations consolidated outside the loop, the remaining per-folio actions (calling folio_redirty_for_writepage() on already dirty folios and unlocking them) serve no purpose, so the folio iteration itself is removed. Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/mdt.c | 40 +++++++++++----------------------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/fs/nilfs2/mdt.c b/fs/nilfs2/mdt.c index b50c88b6518399..77dc2be2ce652b 100644 --- a/fs/nilfs2/mdt.c +++ b/fs/nilfs2/mdt.c @@ -391,51 +391,33 @@ int nilfs_mdt_fetch_dirty(struct inode *inode) return test_bit(NILFS_I_DIRTY, &ii->i_state); } -static int nilfs_mdt_write_folio(struct folio *folio, +static int nilfs_mdt_writeback(struct address_space *mapping, struct writeback_control *wbc) { - struct inode *inode = folio->mapping->host; - struct super_block *sb; + struct inode *inode = mapping->host; int err = 0; - if (inode && sb_rdonly(inode->i_sb)) { + if (!inode) + return 0; + + if (sb_rdonly(inode->i_sb)) { /* * It means that filesystem was remounted in read-only * mode because of error or metadata corruption. But we * have dirty folios that try to be flushed in background. - * So, here we simply discard this dirty folio. + * So, here we simply discard these dirty folios. */ - nilfs_clear_folio_dirty(folio, false); - folio_unlock(folio); + nilfs_clear_dirty_pages(mapping, false); return -EROFS; } - folio_redirty_for_writepage(wbc, folio); - folio_unlock(folio); - - if (!inode) - return 0; - - sb = inode->i_sb; - - if (wbc->sync_mode == WB_SYNC_ALL) - err = nilfs_construct_segment(sb); + if (wbc->sync_mode == WB_SYNC_ALL && + mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) + err = nilfs_construct_segment(inode->i_sb); return err; } -static int nilfs_mdt_writeback(struct address_space *mapping, - struct writeback_control *wbc) -{ - struct folio *folio = NULL; - int error; - - while ((folio = writeback_iter(mapping, wbc, folio, &error))) - error = nilfs_mdt_write_folio(folio, wbc); - - return error; -} - static const struct address_space_operations def_mdt_aops = { .dirty_folio = block_dirty_folio, .invalidate_folio = block_invalidate_folio, From cce713d008bdcca5e46541de86eab79b80d394bc Mon Sep 17 00:00:00 2001 From: Alexander Koskovich Date: Mon, 5 Oct 2026 17:47:27 +0000 Subject: [PATCH 1424/2286] cpufreq: Add Milos to cpufreq-dt-platdev blocklist The Qualcomm Milos platform uses the qcom-cpufreq-hw driver so add it to the cpufreq-dt-platdev driver's blocklist. Signed-off-by: Alexander Koskovich Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 371ca1fe902091..574c5bec483122 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -155,6 +155,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "nvidia,tegra234", }, { .compatible = "qcom,apq8096", }, + { .compatible = "qcom,milos", }, { .compatible = "qcom,msm8909", }, { .compatible = "qcom,msm8996", }, { .compatible = "qcom,msm8998", }, From 4c6668ecebc61bb0a53d4c6e95509bc960cf6bbc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:10:45 -0700 Subject: [PATCH 1425/2286] xfs: adjust checks for cross-AG reaping issues While analyzing the reaping code for the previous patch, I studied the xfs_fsblock_t code to reconstruct how this code works. AGs have a superblock at the start of each AG which means that regions in the xfsb_bitmap (aka xfs_fsblock_t bitmap) cannot cross an AG boundary, so we don't need a loop here like we do for xfs_rtblock_t bitmaps. However, the checks here are not quite right -- there can be quite a lot of reserved space in COW forks, which means that the bitmap extent actually can exceed XFS_MAX_BMBT_EXTLEN. We also don't have an explicit debugging check that we never cross AGs, so add that here. Cc: stable@vger.kernel.org # v6.8 Fixes: 66da11280f7ecd ("xfs: reintroduce reaping of file metadata blocks to xrep_reap_extents") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index a251aedb9192c7..235e5e7e16c415 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -796,9 +796,9 @@ xreap_fsmeta_extent( xfs_agblock_t agbno_next = agbno + len; int error = 0; - ASSERT(len <= XFS_MAX_BMBT_EXTLEN); ASSERT(sc->ip != NULL); ASSERT(!sc->sa.pag); + ASSERT(agno == XFS_FSB_TO_AGNO(sc->mp, fsbno + len - 1)); /* * We're reaping blocks after repairing file metadata, which means that From 436ae68743a6974021fefa21fd3093d9bf8e66e4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:11:01 -0700 Subject: [PATCH 1426/2286] xfs: actually check XFS_DIFLAG2_METADATA for metadata btree data forks LOLLM noticed that xrep_dinode_check_dfork doesn't require that the inode has the METADATA flag set in flags2. Add that check. Cc: stable@vger.kernel.org # v6.14 Fixes: 702c90f4516223 ("xfs: support file data forks containing metadata btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index c65912d8765904..47557a30a5f269 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1012,6 +1012,11 @@ xrep_dinode_bad_metabt_fork( if (whichfork != XFS_DATA_FORK) return true; + if (!xfs_has_metadir(sc->mp)) + return true; + if (!(dip->di_flags2 & cpu_to_be64(XFS_DIFLAG2_METADATA))) + return true; + switch (be16_to_cpu(dip->di_metatype)) { case XFS_METAFILE_RTRMAP: return xrep_dinode_bad_rtrmapbt_fork(sc, dip, dfork_size); From 6b8d60784b61a17bd591cb4c6e8aa9ed29c24f5c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:11:17 -0700 Subject: [PATCH 1427/2286] xfs: fix xchk_stats_estimate_bufsize to allow for the trailing null LOLLM complains that xchk_stats_estimate_bufsize underestimates the buffer size by 1 byte because scnprintf wants to store a null byte after the newline in each line. Each call to scnprintf overwrites the null left by the previous call, but we still need the null byte for the last line in the file. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index 3339cae4b39d14..da2a40a91156f4 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -168,7 +168,8 @@ xchk_stats_estimate_bufsize( ret += field_width + 1; } - return ret; + /* null byte for scnprintf */ + return ret + 1; } /* Clear all counters. */ From 0c61016006ab08f8346f9e7b1e38559bd4ea499e Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:11:32 -0700 Subject: [PATCH 1428/2286] xfs: cross-reference the primary superblock Make sure we cross-reference the primary superblock against the rest of the AG 0 space metadata. Drop the unused @bp parameter of xchk_superblock_xref to avoid complaints about uninitialized variables. Cc: stable@vger.kernel.org # v4.16 Fixes: 52dc4b44af7419 ("xfs: cross-reference with the bnobt") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index fa5d32ec020aec..1dff6cb1b93628 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -35,8 +35,7 @@ xchk_setup_agheader( /* Cross-reference with the other btrees. */ STATIC void xchk_superblock_xref( - struct xfs_scrub *sc, - struct xfs_buf *bp) + struct xfs_scrub *sc) { struct xfs_mount *mp = sc->mp; xfs_agnumber_t agno = sc->sm->sm_agno; @@ -106,16 +105,12 @@ xchk_superblock( struct xfs_dsb *sb; struct xfs_perag *pag; size_t sblen; - xfs_agnumber_t agno; + xfs_agnumber_t agno = sc->sm->sm_agno; uint32_t v2_ok; __be32 features_mask; int error; __be16 vernum_mask; - agno = sc->sm->sm_agno; - if (agno == 0) - return 0; - /* * Grab an active reference to the perag structure. If we can't get * it, we're racing with something that's tearing down the AG, so @@ -125,6 +120,9 @@ xchk_superblock( if (!pag) return -ENOENT; + if (agno == 0) + goto out_xref; + error = xfs_sb_read_secondary(mp, sc->tp, agno, &bp); /* * The superblock verifier can return several different error codes @@ -430,7 +428,8 @@ xchk_superblock( if (memchr_inv((char *)sb + sblen, 0, BBTOB(bp->b_length) - sblen)) xchk_block_set_corrupt(sc, bp); - xchk_superblock_xref(sc, bp); +out_xref: + xchk_superblock_xref(sc); out_pag: xfs_perag_put(pag); return error; From 33dfad5a7bbf5c7a4626404eaf3dcdaf94fed0e9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:11:48 -0700 Subject: [PATCH 1429/2286] xfs: write the rt super immediately during repair xrep_rgsuperblock has an unfortunate flaw -- even if the repair transaction commits and is persisted to disk, a crash before the log bwrite()s the buffer to disk means that the filesystem will not mount after recovery. The xfs_mount keeps the rtsb buffer live for the duration of the mount (scrubbers hold a file open on the fs) so let's write the buffer out immediately to shorten that window. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 6e2abe5dc27cd5..2db92cb89cc614 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -91,12 +91,28 @@ xrep_rgsuperblock( struct xfs_scrub *sc) { struct xfs_buf *sb_bp; + struct xfs_buf *rtsb_bp; + int error; ASSERT(rtg_rgno(sc->sr.rtg) == 0); sb_bp = xfs_trans_getsb(sc->tp); xfs_log_sb(sc->tp); - xfs_log_rtsb(sc->tp, sb_bp); - return 0; + rtsb_bp = xfs_log_rtsb(sc->tp, sb_bp); + if (!rtsb_bp) + return 0; + + /* synchronous transaction to flush/release the buffer log item */ + xfs_trans_set_sync(sc->tp); + error = xrep_trans_commit(sc); + if (error) + return error; + + /* write the rt super out immediately */ + xfs_buf_lock(rtsb_bp); + xfs_buf_hold(rtsb_bp); + error = xfs_bwrite(rtsb_bp); + xfs_buf_relse(rtsb_bp); + return error; } #endif /* CONFIG_XFS_ONLINE_REPAIR */ From cd16b947cac1463d6f3eeab17247a9db7aa10c38 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:12:04 -0700 Subject: [PATCH 1430/2286] xfs: read rt superblock from disk during scrub The rtgroup superblock scrubber isn't that useful right now because the disk contents could have been corrupted after mount time. Perform an uncached re-read of the rtsuper buffer to perform a fresh verification, and schedule a repair if it doesn't pass muster. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rgsuper.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 2db92cb89cc614..102bd92e9003db 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -46,6 +46,7 @@ int xchk_rgsuperblock( struct xfs_scrub *sc) { + struct xfs_buf *bp = NULL; xfs_rgnumber_t rgno = sc->sm->sm_agno; unsigned int flags; int error; @@ -78,9 +79,17 @@ xchk_rgsuperblock( return error; /* - * Since we already validated the rt superblock at mount time, we don't - * need to check its contents again. All we need is to cross-reference. + * Read the rt super from disk in case it's been corrupted since mount + * time. Crashing with a bad rt super may prevent remount, so we want + * to fix these things ASAP. */ + error = xfs_buf_read_uncached(sc->mp->m_rtdev_targp, XFS_RTSB_DADDR, + sc->mp->m_sb.sb_blocksize >> BBSHIFT, &bp, + &xfs_rtsb_buf_ops); + if (!xchk_process_rt_error(sc, 0, 0, &error)) + return error; + xfs_buf_relse(bp); + xchk_rgsuperblock_xref(sc); return 0; } From 02722664508c1f7368663db6fc2410541c75379d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:12:19 -0700 Subject: [PATCH 1431/2286] xfs: write the primary super immediately during repair xrep_superblock has an unfortunate flaw -- it won't even try to repair the primary super block, even though we have a live copy in memory that we could log and write to disk right now. We should do this, because a crash will render the filesystem unmountable on recovery. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 3 +-- fs/xfs/scrub/agheader_repair.c | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 1dff6cb1b93628..43f3c3a5060b51 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -56,8 +56,7 @@ xchk_superblock_xref( xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS); xchk_xref_is_not_shared(sc, agbno, 1); xchk_xref_is_not_cow_staging(sc, agbno, 1); - - /* scrub teardown will take care of sc->sa for us */ + xchk_ag_free(sc, &sc->sa); } /* diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index ff1b4b361cf2e0..2b0cd7d1ad0389 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -44,12 +44,35 @@ xrep_superblock( struct xfs_mount *mp = sc->mp; struct xfs_buf *bp; xfs_agnumber_t agno; - int error; + int error = 0; - /* Don't try to repair AG 0's sb; let xfs_repair deal with it. */ agno = sc->sm->sm_agno; - if (agno == 0) - return -EOPNOTSUPP; + if (agno == 0) { + /* Last chance to abort before we start committing fixes. */ + if (xchk_should_terminate(sc, &error)) + return error; + + bp = xfs_trans_getsb(sc->tp); + + /* Format the incore superblock into the primary sb buffer */ + xfs_buf_zero(bp, 0, BBTOB(bp->b_length)); + xfs_sb_to_disk(bp->b_addr, &mp->m_sb); + + xfs_log_sb(sc->tp); + + /* synchronous transaction to flush/release the buffer log item */ + xfs_trans_set_sync(sc->tp); + error = xrep_trans_commit(sc); + if (error) + return error; + + /* write the super out immediately */ + xfs_buf_lock(bp); + xfs_buf_hold(bp); + error = xfs_bwrite(bp); + xfs_buf_relse(bp); + return error; + } error = xfs_sb_get_secondary(mp, sc->tp, agno, &bp); if (error) From 612d2e7506c3553b1deedc694846d1c0364ae446 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:12:35 -0700 Subject: [PATCH 1432/2286] xfs: read primary superblock from disk during scrub The primary superblock scrubber isn't that useful right now because the disk contents could have been corrupted after mount time. Perform an uncached re-read of the super buffer to perform a fresh verification, and schedule a repair if it doesn't pass muster. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index 43f3c3a5060b51..a765d3e5e0686b 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -119,8 +119,40 @@ xchk_superblock( if (!pag) return -ENOENT; - if (agno == 0) + if (agno == 0) { + /* + * Reread the primary super from disk in case it's become + * corrupted enough since mount time to fail the verifier. + * Crashing now with a bad primary super will prevent remount + * (which might recover the primary super from the log) so we + * want to trigger a repair to write the incore primary + * superblock out to disk ASAP. + * + * Note that we don't check the geometry (like we do for a + * secondary super) because the incore copy is the source of + * truth while the filesystem is mounted. + * + * If the read races with a write, we end up writing the + * primary super unnecessarily, but that's not a big deal. + */ + error = xfs_buf_read_uncached(sc->mp->m_ddev_targp, + XFS_SB_DADDR, BTOBB(mp->m_sb.sb_sectsize), &bp, + &xfs_sb_buf_ops); + switch (error) { + case -EINVAL: /* also -EWRONGFS */ + case -ENOSYS: + case -EFBIG: + error = -EFSCORRUPTED; + fallthrough; + default: + break; + } + if (!xchk_process_error(sc, agno, XFS_SB_BLOCK(mp), &error)) + goto out_pag; + + xfs_buf_relse(bp); goto out_xref; + } error = xfs_sb_read_secondary(mp, sc->tp, agno, &bp); /* From 49d300a51442bd64b627645b7b0b1cd66e492905 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:13:06 -0700 Subject: [PATCH 1433/2286] xfs: jump out of xchk_bmap_check_rmap if fatal signals LOLLM observes that we never check for fatal signals inside the loop that checks file fork mappings against the rmapbt. This can lead to lengthy delays in responding to a ^C, so add some bailout checks. Cc: stable@vger.kernel.org # v4.17 Fixes: 5e777b62b0bcb6 ("xfs: bmap scrubber should do rmap xref with bmap for sparse files") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index c190590bc56216..4dff40dd426ae5 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -625,6 +625,10 @@ xchk_bmap_check_rmap( struct xfs_ifork *ifp; struct xfs_scrub *sc = sbcri->sc; bool have_map; + int error = 0; + + if (xchk_should_terminate(sc, &error)) + return error; /* Is this even the right fork? */ if (rec->rm_owner != I_INO(sc->ip)) @@ -656,6 +660,9 @@ xchk_bmap_check_rmap( */ check_rec = *rec; while (have_map) { + if (xchk_should_terminate(sc, &error)) + return error; + if (irec.br_startoff != check_rec.rm_offset) xchk_fblock_set_corrupt(sc, sbcri->whichfork, check_rec.rm_offset); From f61c91a2e5994fa81141d6c06dadae1fed206018 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:13:22 -0700 Subject: [PATCH 1434/2286] xfs: mark overfull dabtree node blocks as corrupt LOLLM observes that we need to range-check the dabtree node record count before using it to index an array. Fix that. Cc: stable@vger.kernel.org # v4.15 Fixes: 7c4a07a424c18d ("xfs: scrub directory/attribute btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dabtree.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/xfs/scrub/dabtree.c b/fs/xfs/scrub/dabtree.c index d4e5afbde85613..72cd880a5745e7 100644 --- a/fs/xfs/scrub/dabtree.c +++ b/fs/xfs/scrub/dabtree.c @@ -439,6 +439,10 @@ xchk_da_btree_block( node = blk->bp->b_addr; xfs_da3_node_hdr_from_disk(ip->i_mount, &nodehdr, node); btree = nodehdr.btree; + if (nodehdr.count > dargs->geo->node_ents) { + xchk_da_set_corrupt(ds, level); + goto out_freebp; + } *pmaxrecs = nodehdr.count; blk->hashval = be32_to_cpu(btree[*pmaxrecs - 1].hashval); if (level == 0) { From 43030b72a11a532e7e3c21ac84a8625425b4201b Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Thu, 24 Sep 2026 21:13:38 -0700 Subject: [PATCH 1435/2286] xfs: check fork type against dabtree block magic number LOLLM complains that we fail to flag dabtree blocks that should never be associated with the file fork -- data forks can't point to xattr leaves, and attr forks can't point to dirent blocks. Add the missing checks. Cc: stable@vger.kernel.org # v4.15 Fixes: 7c4a07a424c18d ("xfs: scrub directory/attribute btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dabtree.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/xfs/scrub/dabtree.c b/fs/xfs/scrub/dabtree.c index 72cd880a5745e7..639228601cc6bc 100644 --- a/fs/xfs/scrub/dabtree.c +++ b/fs/xfs/scrub/dabtree.c @@ -410,6 +410,10 @@ xchk_da_btree_block( XFS_BLFT_ATTR_LEAF_BUF); blk->magic = XFS_ATTR_LEAF_MAGIC; blk->hashval = xfs_attr_leaf_lasthash(blk->bp, pmaxrecs); + if (dargs->whichfork != XFS_ATTR_FORK) { + xchk_da_set_corrupt(ds, level); + goto out_freebp; + } if (ds->tree_level != 0) xchk_da_set_corrupt(ds, level); break; @@ -419,6 +423,10 @@ xchk_da_btree_block( XFS_BLFT_DIR_LEAFN_BUF); blk->magic = XFS_DIR2_LEAFN_MAGIC; blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs); + if (dargs->whichfork != XFS_DATA_FORK) { + xchk_da_set_corrupt(ds, level); + goto out_freebp; + } if (ds->tree_level != 0) xchk_da_set_corrupt(ds, level); break; @@ -428,6 +436,10 @@ xchk_da_btree_block( XFS_BLFT_DIR_LEAF1_BUF); blk->magic = XFS_DIR2_LEAF1_MAGIC; blk->hashval = xfs_dir2_leaf_lasthash(ip, blk->bp, pmaxrecs); + if (dargs->whichfork != XFS_DATA_FORK) { + xchk_da_set_corrupt(ds, level); + goto out_freebp; + } if (ds->tree_level != 0) xchk_da_set_corrupt(ds, level); break; From 07732dea4f5d0410e1566b81a58a1154bf9dcb29 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:16:36 -0700 Subject: [PATCH 1436/2286] xfs: abort dirtree repair if the live update hook dies LOLLM notices that if the live update hook for the directory tree repair code hits an error, it will set dl->aborted and stops working. If the repair code ever sees that condition, it ought to bail out immediately instead of continuing with obsolete data. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 1d1eafcf6eb598..5a5857ee71d82d 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -813,6 +813,11 @@ xrep_dirtree( */ mutex_lock(&dl->lock); do { + if (dl->aborted) { + error = -EIO; + break; + } + /* * Decide what we're going to do, then do it. An -ESTALE * return here means the scan results are invalid and we have From d83ca4e7cc65dce6a02fd3427cefd174517657a3 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:16:51 -0700 Subject: [PATCH 1437/2286] xfs: bail out of directory tree repairs on error LOLLM complains that we ignore errors returned by xrep_dirpath_retain_parent and can set the dotdot entry to random stack garbage. Fix that by passing errors all the way out. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 57 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 5a5857ee71d82d..b856abb6bbe9e8 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -94,7 +94,7 @@ xrep_dirtree_delete_all_paths( } /* Since this is the surviving path, set the dotdot entry to this value. */ -STATIC void +STATIC int xrep_dirpath_retain_parent( struct xchk_dirtree *dl, struct xchk_dirpath *path) @@ -104,18 +104,20 @@ xrep_dirpath_retain_parent( error = xfarray_load(dl->path_steps, path->first_step, &step); if (error) - return; + return error; dl->parent_ino = be64_to_cpu(step.pptr_rec.p_ino); + return 0; } /* Find the one surviving path so we know how to set dotdot. */ -STATIC void +STATIC int xrep_dirtree_find_surviving_path( struct xchk_dirtree *dl, struct xchk_dirtree_outcomes *oc) { struct xchk_dirpath *path; + int error; bool foundit = false; xchk_dirtree_for_each_path(dl, path) { @@ -124,7 +126,10 @@ xrep_dirtree_find_surviving_path( case XCHK_DIRPATH_LOOP: case XCHK_DIRPATH_OK: if (!foundit) { - xrep_dirpath_retain_parent(dl, path); + error = xrep_dirpath_retain_parent(dl, path); + if (error) + return error; + foundit = true; continue; } @@ -136,16 +141,19 @@ xrep_dirtree_find_surviving_path( } ASSERT(oc->suspect + oc->good == 1); + + return 0; } /* Delete all paths except for the one good one. */ -STATIC void +STATIC int xrep_dirtree_keep_one_good_path( struct xchk_dirtree *dl, struct xchk_dirtree_outcomes *oc) { struct xchk_dirpath *path; bool foundit = false; + int error; xchk_dirtree_for_each_path(dl, path) { switch (path->outcome) { @@ -157,7 +165,10 @@ xrep_dirtree_keep_one_good_path( break; case XCHK_DIRPATH_OK: if (!foundit) { - xrep_dirpath_retain_parent(dl, path); + error = xrep_dirpath_retain_parent(dl, path); + if (error) + return error; + foundit = true; continue; } @@ -172,23 +183,28 @@ xrep_dirtree_keep_one_good_path( ASSERT(oc->suspect == 0); ASSERT(oc->good < 2); + return 0; } /* Delete all paths except for one suspect one. */ -STATIC void +STATIC int xrep_dirtree_keep_one_suspect_path( struct xchk_dirtree *dl, struct xchk_dirtree_outcomes *oc) { struct xchk_dirpath *path; bool foundit = false; + int error = 0; xchk_dirtree_for_each_path(dl, path) { switch (path->outcome) { case XCHK_DIRPATH_CORRUPT: case XCHK_DIRPATH_LOOP: if (!foundit) { - xrep_dirpath_retain_parent(dl, path); + error = xrep_dirpath_retain_parent(dl, path); + if (error) + return error; + foundit = true; continue; } @@ -206,13 +222,14 @@ xrep_dirtree_keep_one_suspect_path( ASSERT(oc->suspect == 1); ASSERT(oc->good == 0); + return error; } /* * Figure out what to do with the paths we tried to find. Returns -EDEADLOCK * if the scan results have become stale. */ -STATIC void +STATIC int xrep_dirtree_decide_fate( struct xchk_dirtree *dl, struct xchk_dirtree_outcomes *oc) @@ -222,36 +239,32 @@ xrep_dirtree_decide_fate( /* Parentless directories should not have any paths at all. */ if (xchk_dirtree_parentless(dl)) { xrep_dirtree_delete_all_paths(dl, oc); - return; + return 0; } /* One path is exactly the number of paths we want. */ - if (oc->good + oc->suspect == 1) { - xrep_dirtree_find_surviving_path(dl, oc); - return; - } + if (oc->good + oc->suspect == 1) + return xrep_dirtree_find_surviving_path(dl, oc); /* Zero paths means we should reattach the subdir to the orphanage. */ if (oc->good + oc->suspect == 0) { if (dl->sc->orphanage) oc->needs_adoption = true; - return; + return 0; } /* * Otherwise, this subdirectory has too many parents. If there's at * least one good path, keep it and delete the others. */ - if (oc->good > 0) { - xrep_dirtree_keep_one_good_path(dl, oc); - return; - } + if (oc->good > 0) + return xrep_dirtree_keep_one_good_path(dl, oc); /* * There are no good paths and there are too many suspect paths. * Keep the first suspect path and delete the rest. */ - xrep_dirtree_keep_one_suspect_path(dl, oc); + return xrep_dirtree_keep_one_suspect_path(dl, oc); } /* @@ -824,7 +837,9 @@ xrep_dirtree( * to walk again. */ if (!dl->stale) { - xrep_dirtree_decide_fate(dl, &oc); + error = xrep_dirtree_decide_fate(dl, &oc); + if (error) + break; trace_xrep_dirtree_decided_fate(dl, &oc); From 6c64d650460b455980a6023d43bc6c8859c1cfd2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:17:07 -0700 Subject: [PATCH 1438/2286] xfs: fix revalidation of xchk_dquot_iter cached mappings LOLLM noticed that the purpose of xchk_dquot_iter_revalidate_bmap is to revalidate the quota file mapping after we've (presumably) done something to the quota file contents. However, the "yes this is valid" code isn't quite strong enough -- the mapping must cover at least the block at fileoff, not just end after fileoff. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: 21d7500929c8a0 ("xfs: improve dquot iteration for scrub") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dqiterate.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/xfs/scrub/dqiterate.c b/fs/xfs/scrub/dqiterate.c index 079dc4e691a01a..76393c888484ff 100644 --- a/fs/xfs/scrub/dqiterate.c +++ b/fs/xfs/scrub/dqiterate.c @@ -58,6 +58,7 @@ xchk_dquot_iter_revalidate_bmap( * no need to reread the bmbt. */ if (cursor->bmap.br_startoff != NULLFILEOFF && + cursor->bmap.br_startoff <= fileoff && cursor->if_seq == ifp->if_seq && cursor->bmap.br_startoff + cursor->bmap.br_blockcount > fileoff) return 0; From 22a8a7501c0146dba4ce99b8256975ffe945125d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:17:23 -0700 Subject: [PATCH 1439/2286] xfs: don't repair fs summary counters with garbage LOLLM noticed that if we scan the filesystem and come up with totally garbage numbers for the summary counters, we don't actually mark the scan incomplete. Because we don't do this, repair can install the garbage values into the filesystem, making things worse. Don't do that. Cc: stable@vger.kernel.org # v6.9 Fixes: 4ed080cd7cb077 ("xfs: repair summary counters") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/fscounters.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index cd96393a9e6fcf..b3fb1b4227659d 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -368,8 +368,10 @@ xchk_fscount_aggregate_agcounts( /* Bail out if the values we compute are totally nonsense. */ if (fsc->icount < fsc->icount_min || fsc->icount > fsc->icount_max || fsc->fdblocks > mp->m_sb.sb_dblocks || - fsc->ifree > fsc->icount_max) + fsc->ifree > fsc->icount_max) { + xchk_set_incomplete(sc); return -EFSCORRUPTED; + } /* * If ifree > icount then we probably had some perturbation in the From 342c2e00b3a99099a7088f7b8c8f075670c603a9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:17:38 -0700 Subject: [PATCH 1440/2286] xfs: allow softlimit with no hardlimit in quota scrub LOLLM observes that the sysadmin can set a user's soft limit to a nonzero value and the hard limit to zero, so those aren't corruptions. Cc: stable@vger.kernel.org # v4.15 Fixes: c2fc338c87a31f ("xfs: scrub quota information") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota.c | 8 ++++---- fs/xfs/scrub/quota_repair.c | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/xfs/scrub/quota.c b/fs/xfs/scrub/quota.c index 8c6ba1240fd53a..83092181b30dd1 100644 --- a/fs/xfs/scrub/quota.c +++ b/fs/xfs/scrub/quota.c @@ -182,21 +182,21 @@ xchk_quota_item( * suspect, which is why we flag it for review. * * Complain about corruption if the soft limit is greater than - * the hard limit. + * the hard limit and there's a hard limit set. */ if (dq->q_blk.hardlimit > mp->m_sb.sb_dblocks) xchk_fblock_set_warning(sc, XFS_DATA_FORK, offset); - if (dq->q_blk.softlimit > dq->q_blk.hardlimit) + if (dq->q_blk.hardlimit && dq->q_blk.softlimit > dq->q_blk.hardlimit) xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); if (dq->q_ino.hardlimit > M_IGEO(mp)->maxicount) xchk_fblock_set_warning(sc, XFS_DATA_FORK, offset); - if (dq->q_ino.softlimit > dq->q_ino.hardlimit) + if (dq->q_ino.hardlimit && dq->q_ino.softlimit > dq->q_ino.hardlimit) xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); if (dq->q_rtb.hardlimit > mp->m_sb.sb_rblocks) xchk_fblock_set_warning(sc, XFS_DATA_FORK, offset); - if (dq->q_rtb.softlimit > dq->q_rtb.hardlimit) + if (dq->q_rtb.hardlimit && dq->q_rtb.softlimit > dq->q_rtb.hardlimit) xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, offset); /* Check the resource counts. */ diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 74eb66f9b890c8..6a2b3271264cc7 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -193,17 +193,17 @@ xrep_quota_item( goto out_unlock_dquot; /* Check the limits. */ - if (dq->q_blk.softlimit > dq->q_blk.hardlimit) { + if (dq->q_blk.hardlimit && dq->q_blk.softlimit > dq->q_blk.hardlimit) { dq->q_blk.softlimit = dq->q_blk.hardlimit; dirty = true; } - if (dq->q_ino.softlimit > dq->q_ino.hardlimit) { + if (dq->q_ino.hardlimit && dq->q_ino.softlimit > dq->q_ino.hardlimit) { dq->q_ino.softlimit = dq->q_ino.hardlimit; dirty = true; } - if (dq->q_rtb.softlimit > dq->q_rtb.hardlimit) { + if (dq->q_rtb.hardlimit && dq->q_rtb.softlimit > dq->q_rtb.hardlimit) { dq->q_rtb.softlimit = dq->q_rtb.hardlimit; dirty = true; } From fe67a493c7a76cf75556152af24285a114f69d34 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:17:54 -0700 Subject: [PATCH 1441/2286] xfs: assign an owner to scrub_stats_fops LOLLM complains that file_operations structs for debugfs files are supposed to have an owner set so that the module can't get unloaded if someone's sitting on a debugfs file long after unmount/rmmod. Fix that. Cc: stable@vger.kernel.org # v6.6 Fixes: d7a74cad8f4513 ("xfs: track usage statistics of online fsck") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/stats.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/stats.c b/fs/xfs/scrub/stats.c index da2a40a91156f4..6934d76cfd156b 100644 --- a/fs/xfs/scrub/stats.c +++ b/fs/xfs/scrub/stats.c @@ -301,6 +301,7 @@ xchk_scrub_stats_read( } static const struct file_operations scrub_stats_fops = { + .owner = THIS_MODULE, .open = simple_open, .read = xchk_scrub_stats_read, }; @@ -328,6 +329,7 @@ xchk_clear_scrub_stats_write( } static const struct file_operations clear_scrub_stats_fops = { + .owner = THIS_MODULE, .open = simple_open, .write = xchk_clear_scrub_stats_write, }; From 8c4f172c246717b1420f138a159ed15c315a58bd Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:18:10 -0700 Subject: [PATCH 1442/2286] xfs: fix lost errno returned from xfarray_foliosort LOLLM noticed that we stop the sort early but don't actually pass along the error code, which could enable repair code to continue running with an unsorted array. Fix that. Cc: stable@vger.kernel.org # v6.9 Fixes: ee13fc67205b98 ("xfs: convert xfarray_pagesort to deal with large folios") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/xfarray.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/xfarray.c b/fs/xfs/scrub/xfarray.c index c94f355607790e..310262c0aa9219 100644 --- a/fs/xfs/scrub/xfarray.c +++ b/fs/xfs/scrub/xfarray.c @@ -905,8 +905,10 @@ xfarray_sort( * problems, we're done. */ ret = xfarray_foliosort(si, lo, hi); - if (ret < 0) + if (ret < 0) { + error = ret; goto out_free; + } if (ret == 1) { si->stack_depth--; continue; From 63341d4af16fe850716b454fcd18990590d07d97 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:18:25 -0700 Subject: [PATCH 1443/2286] xfs: fix skipped inode mask handling in iscan xfs_highbit64() returns -1 if none of the bits in its argument are set. In this particular callsite, a totally zeroed mask means that no inodes were skipped, so executing the if-body is incorrect. Fix that. Cc: stable@vger.kernel.org # v6.9 Fixes: 82334a79c6eb1c ("xfs: iscan batching should handle unallocated inodes too") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/iscan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/iscan.c b/fs/xfs/scrub/iscan.c index 03f72ba9e7a3a0..33cfce603b5b0d 100644 --- a/fs/xfs/scrub/iscan.c +++ b/fs/xfs/scrub/iscan.c @@ -545,7 +545,7 @@ xchk_iscan_finish_batch( mutex_lock(&iscan->lock); - if (iscan->__batch_ino != NULLFSINO) { + if (iscan->__batch_ino != NULLFSINO && iscan->__skipped_inomask) { highest_skipped = iscan->__batch_ino + xfs_highbit64(iscan->__skipped_inomask); iscan->__visited_ino = max(iscan->__visited_ino, From 833f98b3be45bc7c6ce4c87bdc4892f546ab72ee Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:18:41 -0700 Subject: [PATCH 1444/2286] xfs: don't proceed with xattr repair if the live update fails LOLLM observes that we ought to make one last check for failed live updates before replacing the xattr structure. Let's be cautious here. Cc: stable@vger.kernel.org # v6.10 Fixes: e5d7ce0364d8ee ("xfs: replay unlocked parent pointer updates that accrue during xattr repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index ac433a5467516d..387ad909e20bb9 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1477,6 +1477,9 @@ xrep_xattr_rebuild_tree( if (error) return error; + if (rx->live_update_aborted) + return -EIO; + /* * Exchange the blocks mapped by the tempfile's attr fork with the file * being repaired. The old attr blocks will then be attached to the From c8226f3d1a7eafe472addc232689e9c9a0c36a1c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 27 Sep 2026 23:18:57 -0700 Subject: [PATCH 1445/2286] xfs: don't fix the directory tree if live update fails LOLLM complains that we don't double-check dl->aborted one last time to before committing repairs to the filesystem. This is important because the live update handler could have set ->aborted if it encountered a runtime error. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index b856abb6bbe9e8..717ebbe4f91916 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -440,6 +440,11 @@ xrep_dirtree_unlink( error = -ESTALE; goto out_trans_cancel; } + if (dl->aborted) { + mutex_unlock(&dl->lock); + error = -EIO; + goto out_trans_cancel; + } xrep_dirpath_set_outcome(dl, path, XREP_DIRPATH_DELETING); mutex_unlock(&dl->lock); From f04361fb8694bff98ec61e2e2030a1993426fdca Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 29 Sep 2026 18:13:05 -0500 Subject: [PATCH 1446/2286] xfs: validate rc_domain in xchk_rtrefcount_mergeable xchk_refcount_mergeable validates that the domains match, but this was omitted from the realtime version of the check; fix it. Fixes: c27929670de144 ("xfs: scrub the realtime refcount btree") Cc: stable@vger.kernel.org # v6.14 Signed-off-by: Eric Sandeen Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtrefcount.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 3d916d71a135dc..460e3f094adf13 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -379,6 +379,8 @@ xchk_rtrefcount_mergeable( if (r1->rc_blockcount == 0) return false; + if (r1->rc_domain != r2->rc_domain) + return false; if (r1->rc_startblock + r1->rc_blockcount != r2->rc_startblock) return false; if (r1->rc_refcount != r2->rc_refcount) From a9a984a1e6ea1314a3285402fec9cf8e9ef0e355 Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Mon, 5 Oct 2026 08:01:15 -0400 Subject: [PATCH 1447/2286] xfs: set minleft correctly for sparse chunk errortag allocation The errortag instrumentation for forced sparse chunk allocation jumps straight to the allocation path without setting args.minleft. minleft is unconditionally set to ->inobt_maxlevels for the normal allocation path. Lift the assignment to the initial args setup so it covers all possible paths. Assisted-by: LLM Fixes: 1cdadee11f8d ("xfs: randomly do sparse inode allocations in DEBUG mode") Signed-off-by: Brian Foster Reviewed-by: Mark Tinguely Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ialloc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index 19b513b1169269..e31611cabd4d2f 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -733,6 +733,10 @@ xfs_ialloc_ag_alloc( igeo->maxicount) return -ENOSPC; args.minlen = args.maxlen = igeo->ialloc_blks; + + /* Allow space for the inode btree to split. */ + args.minleft = igeo->inobt_maxlevels; + /* * First try to allocate inodes contiguous with the last-allocated * chunk of inodes. If the filesystem is striped, this will fill @@ -764,8 +768,6 @@ xfs_ialloc_ag_alloc( args.alignment = 1; args.minalignslop = igeo->cluster_align - 1; - /* Allow space for the inode btree to split. */ - args.minleft = igeo->inobt_maxlevels; error = xfs_alloc_vextent_exact_bno(&args, xfs_agbno_to_fsb(pag, args.agbno)); if (error) @@ -804,10 +806,6 @@ xfs_ialloc_ag_alloc( * Allocate a fixed-size extent of inodes. */ args.prod = 1; - /* - * Allow space for the inode btree to split. - */ - args.minleft = igeo->inobt_maxlevels; error = xfs_alloc_vextent_near_bno(&args, xfs_agbno_to_fsb(pag, be32_to_cpu(agi->agi_root))); From ad4c2c4630b743c6a3bc1198faaadffd35c94268 Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Mon, 5 Oct 2026 08:01:16 -0400 Subject: [PATCH 1448/2286] xfs: support additional levels in the agfl minimum calculation xfs_alloc_min_freelist() calculates the worst case AGFL block requirement for a full split plus partial refill for each alloc btree. An upcoming patch needs to calculate the requirement for multiple level increases, so add an optional extra levels parameter and factor out a wrapper function for the common case of a single split. No functional changes. Assisted-by: LLM Signed-off-by: Brian Foster Reviewed-by: Darrick J. Wong Reviewed-by: Mark Tinguely Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_alloc.c | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index f762dcce8d133d..b2525c58051b51 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -2439,17 +2439,22 @@ xfs_alloc_longest_free_extent( /* * Compute the minimum length of the AGFL in the given AG. If @pag is NULL, - * return the largest possible minimum length. + * return the largest possible minimum length. The base calculation accounts + * for a single full split per btree. @extra_levels adds additional split + * levels to compute the prospective AGFL requirement increase for + * multi-allocation transactions. */ -unsigned int -xfs_alloc_min_freelist( +static unsigned int +__xfs_alloc_min_freelist( struct xfs_mount *mp, - struct xfs_perag *pag) + struct xfs_perag *pag, + unsigned int extra_levels) { /* AG btrees have at least 1 level. */ const unsigned int bno_level = pag ? pag->pagf_bno_level : 1; const unsigned int cnt_level = pag ? pag->pagf_cnt_level : 1; const unsigned int rmap_level = pag ? pag->pagf_rmap_level : 1; + const unsigned int levels = 1 + extra_levels; unsigned int min_free; ASSERT(mp->m_alloc_maxlevels > 0); @@ -2476,15 +2481,25 @@ xfs_alloc_min_freelist( */ /* space needed by-bno freespace btree */ - min_free = min(bno_level + 1, mp->m_alloc_maxlevels) * 2 - 2; + min_free = min(bno_level + levels, mp->m_alloc_maxlevels) * 2 - 2; /* space needed by-size freespace btree */ - min_free += min(cnt_level + 1, mp->m_alloc_maxlevels) * 2 - 2; + min_free += min(cnt_level + levels, mp->m_alloc_maxlevels) * 2 - 2; /* space needed reverse mapping used space btree */ - if (xfs_has_rmapbt(mp)) - min_free += min(rmap_level + 1, mp->m_rmap_maxlevels) * 2 - 2; + if (xfs_has_rmapbt(mp)) { + min_free += min(rmap_level + levels, + mp->m_rmap_maxlevels) * 2 - 2; + } return min_free; } +unsigned int +xfs_alloc_min_freelist( + struct xfs_mount *mp, + struct xfs_perag *pag) +{ + return __xfs_alloc_min_freelist(mp, pag, 0); +} + /* * Check if the operation we are fixing up the freelist for should go ahead or * not. If we are freeing blocks, we always allow it, otherwise the allocation From 831f4c28b5dca287422613fe1c1bf4ff1ad13cd1 Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Mon, 5 Oct 2026 08:01:17 -0400 Subject: [PATCH 1449/2286] xfs: calculate AGFL max to support multiple-alloc transactions Rework xfs_alloc_min_freelist() to return an (optional) max value along with the historical min freelist value. The max value includes an extra level in the btree based calculation to account for btree splits in the first allocation of a multi-alloc sequence. This value reflects the worst case AGFL requirement that can be expected across multiple allocations within a single AG and single transaction. For example, consider the case of allocating an inode chunk and then an inobt block on inode record insertion. While here, rename the need variable in xfs_alloc_fix_freelist() for clarity with upcoming changes. Note that no callers calculate the max AGFL value as of yet. No functional changes in this patch. Assisted-by: LLM Signed-off-by: Brian Foster Reviewed-by: Mark Tinguely Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_alloc.c | 35 ++++++++++++++++++++++------------- fs/xfs/libxfs/xfs_alloc.h | 4 ++-- fs/xfs/libxfs/xfs_bmap.c | 5 +++-- fs/xfs/libxfs/xfs_ialloc.c | 4 +++- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index b2525c58051b51..af63926cc9ee28 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -2445,7 +2445,7 @@ xfs_alloc_longest_free_extent( * multi-allocation transactions. */ static unsigned int -__xfs_alloc_min_freelist( +__xfs_alloc_freelist( struct xfs_mount *mp, struct xfs_perag *pag, unsigned int extra_levels) @@ -2492,12 +2492,21 @@ __xfs_alloc_min_freelist( return min_free; } -unsigned int -xfs_alloc_min_freelist( +/* + * Compute the minimum and maximum length of the AGFL in the given AG. The max + * value in this context refers to the max requirement the AG might see in a + * multi-alloc transaction. If @pag is NULL, return the largest possible values. + */ +void +xfs_alloc_freelist( struct xfs_mount *mp, - struct xfs_perag *pag) + struct xfs_perag *pag, + unsigned int *min_free, + unsigned int *max_free) { - return __xfs_alloc_min_freelist(mp, pag, 0); + *min_free = __xfs_alloc_freelist(mp, pag, 0); + if (max_free) + *max_free = __xfs_alloc_freelist(mp, pag, 1); } /* @@ -2849,7 +2858,7 @@ xfs_alloc_fix_freelist( struct xfs_buf *agflbp = NULL; struct xfs_alloc_arg targs; /* local allocation arguments */ xfs_agblock_t bno; /* freelist block */ - xfs_extlen_t need; /* total blocks needed in freelist */ + xfs_extlen_t min_free;/* total blocks needed in freelist */ int error = 0; /* deferred ops (AGFL block frees) require permanent transactions */ @@ -2877,8 +2886,8 @@ xfs_alloc_fix_freelist( goto out_agbp_relse; } - need = xfs_alloc_min_freelist(mp, pag); - if (!xfs_alloc_space_available(args, need, alloc_flags | + xfs_alloc_freelist(mp, pag, &min_free, NULL); + if (!xfs_alloc_space_available(args, min_free, alloc_flags | XFS_ALLOC_FLAG_CHECK)) goto out_agbp_relse; @@ -2901,8 +2910,8 @@ xfs_alloc_fix_freelist( xfs_agfl_reset(tp, agbp, pag); /* If there isn't enough total space or single-extent, reject it. */ - need = xfs_alloc_min_freelist(mp, pag); - if (!xfs_alloc_space_available(args, need, alloc_flags)) + xfs_alloc_freelist(mp, pag, &min_free, NULL); + if (!xfs_alloc_space_available(args, min_free, alloc_flags)) goto out_agbp_relse; if (IS_ENABLED(CONFIG_XFS_DEBUG) && args->alloc_minlen_only) { @@ -2944,7 +2953,7 @@ xfs_alloc_fix_freelist( else targs.oinfo = XFS_RMAP_OINFO_AG; while (!(alloc_flags & XFS_ALLOC_FLAG_NOSHRINK) && - pag->pagf_flcount > need) { + pag->pagf_flcount > min_free) { error = xfs_alloc_get_freelist(pag, tp, agbp, &bno, 0); if (error) goto out_agbp_relse; @@ -2978,9 +2987,9 @@ xfs_alloc_fix_freelist( goto out_agbp_relse; /* Make the freelist longer if it's too short. */ - while (pag->pagf_flcount < need) { + while (pag->pagf_flcount < min_free) { targs.agbno = 0; - targs.maxlen = need - pag->pagf_flcount; + targs.maxlen = min_free - pag->pagf_flcount; targs.resv = XFS_AG_RESV_AGFL; /* Allocate as many blocks as possible at once. */ diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h index 50ef79a1ed41a1..44a10f4a22a23e 100644 --- a/fs/xfs/libxfs/xfs_alloc.h +++ b/fs/xfs/libxfs/xfs_alloc.h @@ -71,8 +71,8 @@ unsigned int xfs_alloc_ag_max_usable(struct xfs_mount *mp); xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, xfs_extlen_t need, xfs_extlen_t reserved); -unsigned int xfs_alloc_min_freelist(struct xfs_mount *mp, - struct xfs_perag *pag); +void xfs_alloc_freelist(struct xfs_mount *mp, struct xfs_perag *pag, + unsigned int *min_free, unsigned int *max_free); int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, struct xfs_buf *agfbp, xfs_agblock_t *bnop, int btreeblk); int xfs_alloc_put_freelist(struct xfs_perag *pag, struct xfs_trans *tp, diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index ae91f63455c5de..d6be6734bd906e 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -3149,6 +3149,7 @@ xfs_bmap_longest_free_extent( xfs_extlen_t *blen) { xfs_extlen_t longest; + unsigned int min_free; int error = 0; if (!xfs_perag_initialised_agf(pag)) { @@ -3158,8 +3159,8 @@ xfs_bmap_longest_free_extent( return error; } - longest = xfs_alloc_longest_free_extent(pag, - xfs_alloc_min_freelist(pag_mount(pag), pag), + xfs_alloc_freelist(pag_mount(pag), pag, &min_free, NULL); + longest = xfs_alloc_longest_free_extent(pag, min_free, xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); if (*blen < longest) *blen = longest; diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index e31611cabd4d2f..e705f681ed4424 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -3074,6 +3074,7 @@ xfs_ialloc_calc_rootino( { struct xfs_ino_geometry *igeo = M_IGEO(mp); xfs_agblock_t first_bno; + unsigned int min_free; /* * Pre-calculate the geometry of AG 0. We know what it looks like @@ -3092,7 +3093,8 @@ xfs_ialloc_calc_rootino( first_bno += 1; /* ...the initial AGFL... */ - first_bno += xfs_alloc_min_freelist(mp, NULL); + xfs_alloc_freelist(mp, NULL, &min_free, NULL); + first_bno += min_free; /* ...the free inode btree root... */ if (xfs_has_finobt(mp)) From a644af916cf14daba9a4fd6413565c7bd34b5bdc Mon Sep 17 00:00:00 2001 From: Brian Foster Date: Mon, 5 Oct 2026 08:01:18 -0400 Subject: [PATCH 1450/2286] xfs: incorporate increased AGFL min requirement for minleft allocs Matt Fleming reports a filesystem shutdown due to inobt block allocation failure during sparse chunk allocation. Inode creation can involve multiple allocations in a transaction: the initial chunk allocation and inode btree blocks via inobt record insertion. This is expected to be safe by using the minleft parameter on the chunk allocation to guarantee the selected AG has blocks available for a followup inobt insertion. The sequence that leads to this failure is that the alloc and inode btrees are all full (require a full split on next insertion) and the AG has just enough free space to satisfy a sparse chunk allocation with minleft set (i.e. 7 blocks in this example). The chunk allocation splits a free extent, triggers full allocbt splits, and consumes 4 free blocks for the chunk and 4 AGFL blocks for the btrees. Next, the inobt record insertion triggers an inobt split. The AG has enough free blocks, but the allocbt splits caused by the chunk allocation have increased the min AGFL requirement for the AG due to btree level increases. The AGFL requirement as calculated by xfs_alloc_fix_freelist() is: free + AGFL - res - minfree - minleft = avail This evaluates to the following on initial chunk allocation: 2514 + 8 - 2505 - 8 - 2 = 7 ... and then after the chunk allocation but before the inobt block allocation: 2510 + 4 - 2505 - 12 - 0 = -3 This causes the inobt alloc to fail despite minleft being set in the first allocation. The error path cancels the dirty transaction and shuts down the fs. The problem here is that while minleft ensures free blocks are available for the inobt insert, it is not sufficient to cover the increase of the AGFL min free requirement. To address this, first have xfs_alloc_freelist() return both min and max freelist values. The min value is the current AGFL requirement and remains used for actual AGFL sizing. The max value calculates the worst case AGFL requirement after potential allocbt splits during the current allocation. Incorporate the max value into space availability checks for AG selection and the longest free extent calculation. The latter is necessary because callers like the bmap layer can size allocation requests based on the longest free extent. Without this, aligned allocs can end up oversized, prematurely fail, and fall back to non-aligned to make up the difference. This ensures the selected AG has enough blocks for both the caller's minleft value and the worst case AGFL increase. In the example above, the initial calculation now evaluates to 3 blocks available instead of 7 and the inode allocation fails gracefully with -ENOSPC. Assisted-by: LLM Reported-by: Matt Fleming Signed-off-by: Brian Foster Reviewed-by: Mark Tinguely Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_alloc.c | 58 +++++++++++++++++++++++++++------------ fs/xfs/libxfs/xfs_alloc.h | 3 +- fs/xfs/libxfs/xfs_bmap.c | 6 ++-- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/fs/xfs/libxfs/xfs_alloc.c b/fs/xfs/libxfs/xfs_alloc.c index af63926cc9ee28..ae221a158764c5 100644 --- a/fs/xfs/libxfs/xfs_alloc.c +++ b/fs/xfs/libxfs/xfs_alloc.c @@ -2397,31 +2397,36 @@ xfs_alloc_compute_maxlevels( } /* - * Find the length of the longest extent in an AG. The 'need' parameter - * specifies how much space we're going to need for the AGFL and the - * 'reserved' parameter tells us how many blocks in this AG are reserved for + * Find the length of the longest extent in an AG. The @min_agfl and @max_agfl + * parameters specify how much space we're going to need for the AGFL and the + * @reserved parameter tells us how many blocks in this AG are reserved for * other callers. */ xfs_extlen_t xfs_alloc_longest_free_extent( struct xfs_perag *pag, - xfs_extlen_t need, + xfs_extlen_t min_agfl, + xfs_extlen_t max_agfl, xfs_extlen_t reserved) { xfs_extlen_t delta = 0; /* - * If the AGFL needs a recharge, we'll have to subtract that from the - * longest extent. + * If the AGFL needs a recharge, subtract that from the longest extent + * because AGFL refill happens before the alloc. */ - if (need > pag->pagf_flcount) - delta = need - pag->pagf_flcount; + if (min_agfl > pag->pagf_flcount) + delta = min_agfl - pag->pagf_flcount; /* - * If we cannot maintain others' reservations with space from the - * not-longest freesp extents, we'll have to subtract /that/ from - * the longest extent too. + * Extra AGFL blocks beyond the min are reserved by ->minleft during + * allocation. Similar to reserved, these blocks are not available to + * this allocation. Check if we can preserve the combined total without + * the longest extent. If not, deduct the necessary blocks from the + * longest extent. */ + if (max_agfl > min_agfl) + reserved += max_agfl - min_agfl; if (pag->pagf_freeblks - pag->pagf_longest < reserved) delta += reserved - (pag->pagf_freeblks - pag->pagf_longest); @@ -2519,6 +2524,7 @@ static bool xfs_alloc_space_available( struct xfs_alloc_arg *args, xfs_extlen_t min_free, + xfs_extlen_t max_free, int flags) { struct xfs_perag *pag = args->pag; @@ -2526,15 +2532,32 @@ xfs_alloc_space_available( xfs_extlen_t reservation; /* blocks that are still reserved */ int available; xfs_extlen_t agflcount; + xfs_extlen_t minleft; if (flags & XFS_ALLOC_FLAG_FREEING) return true; reservation = xfs_ag_resv_needed(pag, args->resv); + /* + * minleft implies a multi-alloc transaction. If set, the first alloc + * might cause btree splits that increase the AGFL requirement for the + * next. This worst case requirement is calculated in max_free. + * + * We don't prepopulate the AGFL because we don't know in advance if + * splits will occur. Instead, add the delta to minleft so it is + * accounted for in AG selection. This ensures the AG has enough space + * for the caller's minleft plus that needed to repopulate the AGFL on + * the next alloc if splits do occur. + */ + minleft = args->minleft; + if (minleft) + minleft += max_free - min_free; + /* do we have enough contiguous free space for the allocation? */ alloc_len = args->minlen + (args->alignment - 1) + args->minalignslop; - longest = xfs_alloc_longest_free_extent(pag, min_free, reservation); + longest = xfs_alloc_longest_free_extent(pag, min_free, + minleft ? max_free : min_free, reservation); if (longest < alloc_len) return false; @@ -2545,7 +2568,7 @@ xfs_alloc_space_available( */ agflcount = min_t(xfs_extlen_t, pag->pagf_flcount, min_free); available = (int)(pag->pagf_freeblks + agflcount - - reservation - min_free - args->minleft); + reservation - min_free - minleft); if (available < (int)max(args->total, alloc_len)) return false; @@ -2859,6 +2882,7 @@ xfs_alloc_fix_freelist( struct xfs_alloc_arg targs; /* local allocation arguments */ xfs_agblock_t bno; /* freelist block */ xfs_extlen_t min_free;/* total blocks needed in freelist */ + xfs_extlen_t max_free; /* max freelist requirement */ int error = 0; /* deferred ops (AGFL block frees) require permanent transactions */ @@ -2886,8 +2910,8 @@ xfs_alloc_fix_freelist( goto out_agbp_relse; } - xfs_alloc_freelist(mp, pag, &min_free, NULL); - if (!xfs_alloc_space_available(args, min_free, alloc_flags | + xfs_alloc_freelist(mp, pag, &min_free, &max_free); + if (!xfs_alloc_space_available(args, min_free, max_free, alloc_flags | XFS_ALLOC_FLAG_CHECK)) goto out_agbp_relse; @@ -2910,8 +2934,8 @@ xfs_alloc_fix_freelist( xfs_agfl_reset(tp, agbp, pag); /* If there isn't enough total space or single-extent, reject it. */ - xfs_alloc_freelist(mp, pag, &min_free, NULL); - if (!xfs_alloc_space_available(args, min_free, alloc_flags)) + xfs_alloc_freelist(mp, pag, &min_free, &max_free); + if (!xfs_alloc_space_available(args, min_free, max_free, alloc_flags)) goto out_agbp_relse; if (IS_ENABLED(CONFIG_XFS_DEBUG) && args->alloc_minlen_only) { diff --git a/fs/xfs/libxfs/xfs_alloc.h b/fs/xfs/libxfs/xfs_alloc.h index 44a10f4a22a23e..5812c9b5e6098b 100644 --- a/fs/xfs/libxfs/xfs_alloc.h +++ b/fs/xfs/libxfs/xfs_alloc.h @@ -70,7 +70,8 @@ unsigned int xfs_alloc_set_aside(struct xfs_mount *mp); unsigned int xfs_alloc_ag_max_usable(struct xfs_mount *mp); xfs_extlen_t xfs_alloc_longest_free_extent(struct xfs_perag *pag, - xfs_extlen_t need, xfs_extlen_t reserved); + xfs_extlen_t min_free, xfs_extlen_t max_free, + xfs_extlen_t reserved); void xfs_alloc_freelist(struct xfs_mount *mp, struct xfs_perag *pag, unsigned int *min_free, unsigned int *max_free); int xfs_alloc_get_freelist(struct xfs_perag *pag, struct xfs_trans *tp, diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index d6be6734bd906e..9bffde1484a743 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -3150,6 +3150,7 @@ xfs_bmap_longest_free_extent( { xfs_extlen_t longest; unsigned int min_free; + unsigned int max_free; int error = 0; if (!xfs_perag_initialised_agf(pag)) { @@ -3159,8 +3160,9 @@ xfs_bmap_longest_free_extent( return error; } - xfs_alloc_freelist(pag_mount(pag), pag, &min_free, NULL); - longest = xfs_alloc_longest_free_extent(pag, min_free, + /* bmap allocs always have minleft set, so account for max_free */ + xfs_alloc_freelist(pag_mount(pag), pag, &min_free, &max_free); + longest = xfs_alloc_longest_free_extent(pag, min_free, max_free, xfs_ag_resv_needed(pag, XFS_AG_RESV_NONE)); if (*blen < longest) *blen = longest; From 197f678cccbaaf8a5f52a91e6fc914d4c57be7f7 Mon Sep 17 00:00:00 2001 From: Hans Holmberg Date: Mon, 5 Oct 2026 13:59:17 +0200 Subject: [PATCH 1451/2286] xfs: don't let racing writers consume the free zones reserved for GC xfs_try_open_zone() checks the free zone count to avoid grabbing zones required for GC forward progress, but does so without decreasing the counter, letting multiple user writers through to call xfs_open_zone() and collectively gobble up all free zones, stalling gc and eventually leading to user threads getting stuck waiting for free space. Fold the check and the accounting into a single atomic claim in xfs_open_zone() instead. The free zone count is decremented before the zone is looked up and given back if none could be grabbed, so the reserve can no longer be raced away. User allocations only claim a zone if that leaves the reserve behind, while GC just needs one free zone and thus can still use the zones that are kept for it. Also add a warning if we end up not finding a free zone in the xarray, as this should never happen. Fixes: 4e4d52075577 ("xfs: add the zoned space allocator") Signed-off-by: Hans Holmberg Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_zones.h | 6 ++++++ fs/xfs/xfs_zone_alloc.c | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/fs/xfs/libxfs/xfs_zones.h b/fs/xfs/libxfs/xfs_zones.h index c16089c9a652da..1391d99b31de51 100644 --- a/fs/xfs/libxfs/xfs_zones.h +++ b/fs/xfs/libxfs/xfs_zones.h @@ -30,6 +30,12 @@ struct blk_zone; #define XFS_OPEN_GC_ZONES 1U #define XFS_MIN_OPEN_ZONES (XFS_OPEN_GC_ZONES + 1U) +/* + * Never open a zone for user data unless this many zones are free, so that GC + * can always open the target zone it needs to make forward progress. + */ +#define XFS_MIN_FREE_GC_ZONES (XFS_GC_ZONES - XFS_OPEN_GC_ZONES) + /* * For zoned devices that do not have a limit on the number of open zones, and * for regular devices using the zoned allocator, use the most common SMR disks diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c index 5f0af0c2c5e509..f827ea3d899a9c 100644 --- a/fs/xfs/xfs_zone_alloc.c +++ b/fs/xfs/xfs_zone_alloc.c @@ -438,6 +438,26 @@ xfs_init_open_zone( return oz; } +/* + * Claim one of the free zones. User allocations may not dip into the pool + * reserved for guaranteeing GC forward progress. + */ +static bool +xfs_claim_free_zone( + struct xfs_zone_info *zi, + bool is_gc) +{ + int min_free = is_gc ? 1 : XFS_MIN_FREE_GC_ZONES; + int free = atomic_read(&zi->zi_nr_free_zones); + + do { + if (free < min_free) + return false; + } while (!atomic_try_cmpxchg(&zi->zi_nr_free_zones, &free, free - 1)); + + return true; +} + /* * Find a completely free zone, open it, and return a reference. */ @@ -451,6 +471,9 @@ xfs_open_zone( XA_STATE (xas, &mp->m_groups[XG_TYPE_RTG].xa, 0); struct xfs_group *xg; + if (!xfs_claim_free_zone(zi, is_gc)) + return NULL; + /* * Pick the free zone with lowest index. Zones in the beginning of the * address space typically provides higher bandwidth than those at the @@ -461,11 +484,19 @@ xfs_open_zone( if (atomic_inc_not_zero(&xg->xg_active_ref)) goto found; xas_unlock(&xas); + + /* + * We should not end up here as we claimed a free zone before looking + * through the free zone list. The free zone counter and the number + * of zones marked as free must be out of sync. + */ + WARN_ON_ONCE(1); + + atomic_inc(&zi->zi_nr_free_zones); return NULL; found: xas_clear_mark(&xas, XFS_RTG_FREE); - atomic_dec(&zi->zi_nr_free_zones); xas_unlock(&xas); set_current_state(TASK_RUNNING); @@ -484,9 +515,6 @@ xfs_try_open_zone( if (zi->zi_nr_open_zones >= mp->m_max_open_zones - XFS_OPEN_GC_ZONES) return NULL; - if (atomic_read(&zi->zi_nr_free_zones) < - XFS_GC_ZONES - XFS_OPEN_GC_ZONES) - return NULL; /* * Increment the open zone count to reserve our slot before dropping From ca75b38b1a3c2eabe81d231dae4ea180cf6287cd Mon Sep 17 00:00:00 2001 From: James Fairweather Date: Sat, 3 Oct 2026 18:28:53 -0400 Subject: [PATCH 1452/2286] platform/chrome: cros_usbpd_notify: Only use drvdata when parent is GOOG0004 cros_usbpd_notify_probe_acpi() takes the EC device pointer from dev_get_drvdata(dev->parent) and assumes it is a struct cros_ec_device. That only holds when GOOG0003 is a child of GOOG0004. On older devices without that hierarchy, such as Google Nami, GOOG0003 and GOOG0004 are siblings under the ACPI EC (PNP0C09): PNP0C09:00 (acpi-ec) |-- GOOG0003:00 (cros-usbpd-notify-acpi) `-- GOOG0004:00 (cros_ec_lpcs) Before commit db65a06d10b3 ("ACPI: EC: Convert the driver to a platform one") the parent platform device had no driver data, so the driver warned and continued without an EC pointer, as intended for these devices. Since that commit the ACPI EC driver stores its struct acpi_ec there, so the driver treats a struct acpi_ec as a struct cros_ec_device and calls cros_ec_cmd() on it for every USB-C PD host event. Plugging or unplugging a charger then intermittently oopses, followed by soft lockups and a hung system: BUG: unable to handle page fault for address: ffffffff9cf03220 #PF: supervisor write access in kernel mode Hardware name: Google Nami/Nami, BIOS 09/19/2019 Workqueue: kacpi_notify acpi_os_execute_deferred RIP: 0010:native_queued_spin_lock_slowpath+0x29d/0x330 Call Trace: _raw_spin_lock_irqsave+0x59/0x80 __mutex_lock.constprop.0+0x129/0x930 cros_ec_cmd_xfer+0x2a/0xf0 [cros_ec_proto] cros_ec_cmd_xfer_status+0x1a/0x90 [cros_ec_proto] cros_ec_cmd+0xb7/0x140 [cros_ec_proto] cros_usbpd_get_event_and_notify+0x42/0xc0 [cros_usbpd_notify] acpi_ev_notify_dispatch+0x4e/0x70 acpi_os_execute_deferred+0x1a/0x30 Only read the parent's driver data when the parent's ACPI node is GOOG0004, and otherwise continue without an EC pointer. While at it, drop the reference taken by fwnode_get_parent(). Fixes: db65a06d10b3 ("ACPI: EC: Convert the driver to a platform one") Fixes: e4ee0bb077cd ("platform/chrome: cros_usbpd_notify: defer probe when parent EC driver isn't ready") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 checkpatch Signed-off-by: James Fairweather Reported-by: Sergey Tiraspolsky Closes: https://lore.kernel.org/all/20261003204623.18124-1-stiraspo@gmail.com Tested-by: Sergey Tiraspolsky Link: https://lore.kernel.org/r/20261003222855.23707-1-james.a.fairweather@gmail.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_usbpd_notify.c | 39 +++++++++++---------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/platform/chrome/cros_usbpd_notify.c b/drivers/platform/chrome/cros_usbpd_notify.c index 6f5eea4938cb43..25da31ec19969d 100644 --- a/drivers/platform/chrome/cros_usbpd_notify.c +++ b/drivers/platform/chrome/cros_usbpd_notify.c @@ -110,26 +110,29 @@ static int cros_usbpd_notify_probe_acpi(struct platform_device *pdev) if (!pdnotify) return -ENOMEM; - /* Get the EC device pointer needed to talk to the EC. */ - ec_dev = dev_get_drvdata(dev->parent); - if (!ec_dev) { - /* - * We continue even for older devices which don't have the - * correct device hierarchy, namely, GOOG0003 is a child - * of GOOG0004. If GOOG0003 is a child of GOOG0004 and we - * can't get a pointer to the Chrome EC device, defer the - * probe function. - */ - parent_fwnode = fwnode_get_parent(dev->fwnode); - if (parent_fwnode) { - parent_adev = to_acpi_device_node(parent_fwnode); - if (parent_adev && - acpi_dev_hid_match(parent_adev, CREC_DRV_NAME)) { - return -EPROBE_DEFER; - } + /* + * Get the EC device pointer needed to talk to the EC. The parent's + * driver data is only a struct cros_ec_device when GOOG0003 is a + * child of GOOG0004. On older devices without that hierarchy the + * parent may be bound to an unrelated driver (e.g. the ACPI EC + * driver for PNP0C09), so don't touch its driver data and continue + * without an EC pointer. If GOOG0003 is a child of GOOG0004 and we + * can't get a pointer to the Chrome EC device yet, defer the probe. + */ + ec_dev = NULL; + parent_fwnode = fwnode_get_parent(dev->fwnode); + parent_adev = to_acpi_device_node(parent_fwnode); + if (parent_adev && acpi_dev_hid_match(parent_adev, CREC_DRV_NAME)) { + ec_dev = dev_get_drvdata(dev->parent); + if (!ec_dev) { + fwnode_handle_put(parent_fwnode); + return -EPROBE_DEFER; } - dev_warn(dev, "Couldn't get Chrome EC device pointer.\n"); } + fwnode_handle_put(parent_fwnode); + + if (!ec_dev) + dev_warn(dev, "Couldn't get Chrome EC device pointer.\n"); pdnotify->dev = dev; pdnotify->ec = ec_dev; From a288bad9c0a8d52113f7e013d56f1c631a2dedd4 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:20:55 +0100 Subject: [PATCH 1453/2286] netfs: Use uoff_t instead of unsigned long long and loff_t Use uoff_t instead of unsigned long long and loff_t for file positions that can't be negative. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-2-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/afs/file.c | 6 +-- fs/afs/internal.h | 2 +- fs/cachefiles/interface.c | 10 ++-- fs/cachefiles/internal.h | 4 +- fs/cachefiles/io.c | 24 +++++----- fs/ceph/addr.c | 4 +- fs/netfs/buffered_read.c | 22 ++++----- fs/netfs/buffered_write.c | 10 ++-- fs/netfs/direct_read.c | 2 +- fs/netfs/direct_write.c | 8 ++-- fs/netfs/fscache_cookie.c | 8 ++-- fs/netfs/fscache_io.c | 8 ++-- fs/netfs/internal.h | 10 ++-- fs/netfs/iterator.c | 2 +- fs/netfs/misc.c | 8 ++-- fs/netfs/objects.c | 2 +- fs/netfs/read_collect.c | 4 +- fs/netfs/read_pgpriv2.c | 8 ++-- fs/netfs/read_retry.c | 2 +- fs/netfs/write_collect.c | 10 ++-- fs/netfs/write_issue.c | 10 ++-- fs/netfs/write_retry.c | 2 +- include/linux/fscache-cache.h | 2 +- include/linux/fscache.h | 36 +++++++-------- include/linux/netfs.h | 74 ++++++++++++++--------------- include/trace/events/cachefiles.h | 30 ++++++------ include/trace/events/fscache.h | 10 ++-- include/trace/events/netfs.h | 77 +++++++++++++++---------------- 28 files changed, 197 insertions(+), 198 deletions(-) diff --git a/fs/afs/file.c b/fs/afs/file.c index 0467742bfeee34..bdb4ca0ef8da73 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -413,7 +413,7 @@ static int afs_init_request(struct netfs_io_request *rreq, struct file *file) return 0; } -static int afs_check_write_begin(struct file *file, loff_t pos, unsigned len, +static int afs_check_write_begin(struct file *file, uoff_t pos, unsigned len, struct folio **foliop, void **_fsdata) { struct afs_vnode *vnode = AFS_FS_I(file_inode(file)); @@ -434,7 +434,7 @@ static void afs_free_request(struct netfs_io_request *rreq) * Also, estimate the number of 512 bytes blocks used, rounded up to nearest 1K * for consistency with other AFS clients. */ -void afs_set_i_size(struct afs_vnode *vnode, loff_t new_i_size) +void afs_set_i_size(struct afs_vnode *vnode, uoff_t new_i_size) { struct inode *inode = &vnode->netfs.inode; loff_t i_size; @@ -451,7 +451,7 @@ void afs_set_i_size(struct afs_vnode *vnode, loff_t new_i_size) fscache_update_cookie(afs_vnode_cache(vnode), NULL, &new_i_size); } -static void afs_update_i_size(struct inode *inode, loff_t new_i_size) +static void afs_update_i_size(struct inode *inode, uoff_t new_i_size) { afs_set_i_size(AFS_FS_I(inode), new_i_size); } diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 330654ed16ecea..d58273c00fbc7d 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -1171,7 +1171,7 @@ extern int afs_open(struct inode *, struct file *); extern int afs_release(struct inode *, struct file *); void afs_fetch_data_async_rx(struct work_struct *work); void afs_fetch_data_immediate_cancel(struct afs_call *call); -void afs_set_i_size(struct afs_vnode *vnode, loff_t new_i_size); +void afs_set_i_size(struct afs_vnode *vnode, uoff_t new_i_size); /* * flock.c diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 50a000310a8c54..a160d5c3e74c0e 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -111,7 +111,7 @@ static int cachefiles_adjust_size(struct cachefiles_object *object) struct iattr newattrs; struct file *file = object->file; uint64_t ni_size; - loff_t oi_size; + uoff_t oi_size; int ret; ni_size = object->cookie->object_size; @@ -225,11 +225,11 @@ static bool cachefiles_lookup_cookie(struct fscache_cookie *cookie) * any unused granules. */ static bool cachefiles_shorten_object(struct cachefiles_object *object, - struct file *file, loff_t new_size) + struct file *file, uoff_t new_size) { struct cachefiles_cache *cache = object->volume->cache; struct inode *inode = file_inode(file); - loff_t i_size, dio_size; + uoff_t i_size, dio_size; int ret; dio_size = round_up(new_size, CACHEFILES_DIO_BLOCK_SIZE); @@ -271,14 +271,14 @@ static bool cachefiles_shorten_object(struct cachefiles_object *object, * Resize the backing object. */ static void cachefiles_resize_cookie(struct netfs_cache_resources *cres, - loff_t new_size) + uoff_t new_size) { struct cachefiles_object *object = cachefiles_cres_object(cres); struct cachefiles_cache *cache = object->volume->cache; struct fscache_cookie *cookie = object->cookie; const struct cred *saved_cred; struct file *file = cachefiles_cres_file(cres); - loff_t old_size = cookie->object_size; + uoff_t old_size = cookie->object_size; _enter("%llu->%llu", old_size, new_size); diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index c93324e0f98c76..60bd801ada04f3 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -203,11 +203,11 @@ extern bool cachefiles_begin_operation(struct netfs_cache_resources *cres, enum fscache_want_state want_state); extern int __cachefiles_prepare_write(struct cachefiles_object *object, struct file *file, - loff_t *_start, size_t *_len, size_t upper_len, + uoff_t *_start, size_t *_len, size_t upper_len, bool no_space_allocated_yet); extern int __cachefiles_write(struct cachefiles_object *object, struct file *file, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv); diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 9540ec25b3cb6c..7de8069d15b6b5 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -19,7 +19,7 @@ struct cachefiles_kiocb { struct kiocb iocb; refcount_t ki_refcnt; - loff_t start; + uoff_t start; union { size_t skipped; size_t len; @@ -73,7 +73,7 @@ static void cachefiles_read_complete(struct kiocb *iocb, long ret) * Initiate a read from the cache. */ static int cachefiles_read(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -197,8 +197,8 @@ static int cachefiles_read(struct netfs_cache_resources *cres, * of data starts and how long it is. */ static int cachefiles_query_occupancy(struct netfs_cache_resources *cres, - loff_t start, size_t len, size_t granularity, - loff_t *_data_start, size_t *_data_len) + uoff_t start, size_t len, size_t granularity, + uoff_t *_data_start, size_t *_data_len) { struct cachefiles_object *object; struct file *file; @@ -280,7 +280,7 @@ static void cachefiles_write_complete(struct kiocb *iocb, long ret) */ int __cachefiles_write(struct cachefiles_object *object, struct file *file, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -357,7 +357,7 @@ int __cachefiles_write(struct cachefiles_object *object, } static int cachefiles_write(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -377,7 +377,7 @@ static int cachefiles_write(struct netfs_cache_resources *cres, static inline enum netfs_io_source cachefiles_do_prepare_read(struct netfs_cache_resources *cres, - loff_t start, size_t *_len, loff_t i_size, + uoff_t start, size_t *_len, loff_t i_size, unsigned long *_flags, ino_t netfs_ino) { enum cachefiles_prepare_read_trace why; @@ -483,7 +483,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, * boundary as appropriate. */ static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subrequest *subreq, - unsigned long long i_size) + uoff_t i_size) { return cachefiles_do_prepare_read(&subreq->rreq->cache_resources, subreq->start, &subreq->len, i_size, @@ -495,7 +495,7 @@ static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subrequest * */ int __cachefiles_prepare_write(struct cachefiles_object *object, struct file *file, - loff_t *_start, size_t *_len, size_t upper_len, + uoff_t *_start, size_t *_len, size_t upper_len, bool no_space_allocated_yet) { struct cachefiles_cache *cache = object->volume->cache; @@ -577,8 +577,8 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, } static int cachefiles_prepare_write(struct netfs_cache_resources *cres, - loff_t *_start, size_t *_len, size_t upper_len, - loff_t i_size, bool no_space_allocated_yet) + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet) { struct cachefiles_object *object = cachefiles_cres_object(cres); struct cachefiles_cache *cache = object->volume->cache; @@ -628,7 +628,7 @@ static void cachefiles_issue_write(struct netfs_io_subrequest *subreq) struct netfs_io_stream *stream = &wreq->io_streams[subreq->stream_nr]; const struct cred *saved_cred; size_t off, pre, post, len = subreq->len; - loff_t start = subreq->start; + uoff_t start = subreq->start; int ret; _enter("W=%x[%x] %llx-%llx", diff --git a/fs/ceph/addr.c b/fs/ceph/addr.c index e598b2d424ec16..42b55ce30a327b 100644 --- a/fs/ceph/addr.c +++ b/fs/ceph/addr.c @@ -65,7 +65,7 @@ (CONGESTION_ON_THRESH(congestion_kb) - \ (CONGESTION_ON_THRESH(congestion_kb) >> 2)) -static int ceph_netfs_check_write_begin(struct file *file, loff_t pos, unsigned int len, +static int ceph_netfs_check_write_begin(struct file *file, uoff_t pos, unsigned int len, struct folio **foliop, void **_fsdata); static inline struct ceph_snap_context *page_snap_context(struct page *page) @@ -1868,7 +1868,7 @@ ceph_find_incompatible(struct folio *folio) return NULL; } -static int ceph_netfs_check_write_begin(struct file *file, loff_t pos, unsigned int len, +static int ceph_netfs_check_write_begin(struct file *file, uoff_t pos, unsigned int len, struct folio **foliop, void **_fsdata) { struct inode *inode = file_inode(file); diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index e6506942aeda19..628e31f56cf1cd 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -10,9 +10,9 @@ #include "internal.h" static void netfs_cache_expand_readahead(struct netfs_io_request *rreq, - unsigned long long *_start, - unsigned long long *_len, - unsigned long long i_size) + uoff_t *_start, + uoff_t *_len, + uoff_t i_size) { struct netfs_cache_resources *cres = &rreq->cache_resources; @@ -139,7 +139,7 @@ static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq) static enum netfs_io_source netfs_cache_prepare_read(struct netfs_io_request *rreq, struct netfs_io_subrequest *subreq, - loff_t i_size) + uoff_t i_size) { struct netfs_cache_resources *cres = &rreq->cache_resources; enum netfs_io_source source; @@ -269,9 +269,9 @@ static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq, static void netfs_read_to_pagecache(struct netfs_io_request *rreq) { struct folio_queue *fq = rreq->buffer.tail; - unsigned long long start = rreq->start; unsigned int offset = 0; ssize_t size = rreq->len; + uoff_t start = rreq->start; int ret = 0, slot = 0; do { @@ -293,8 +293,8 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) source = netfs_cache_prepare_read(rreq, subreq, rreq->i_size); subreq->source = source; if (source == NETFS_DOWNLOAD_FROM_SERVER) { - unsigned long long zero_point = netfs_read_zero_point(rreq->inode); - unsigned long long zp = umin(zero_point, rreq->i_size); + uoff_t zero_point = netfs_read_zero_point(rreq->inode); + uoff_t zp = umin(zero_point, rreq->i_size); size_t len = subreq->len; if (unlikely(rreq->origin == NETFS_READ_SINGLE)) @@ -653,11 +653,11 @@ EXPORT_SYMBOL(netfs_read_folio); * If any of these criteria are met, then zero out the unwritten parts * of the folio and return true. Otherwise, return false. */ -static bool netfs_skip_folio_read(struct folio *folio, loff_t pos, size_t len, +static bool netfs_skip_folio_read(struct folio *folio, uoff_t pos, size_t len, bool always_fill) { struct inode *inode = folio_inode(folio); - loff_t i_size = i_size_read(inode); + uoff_t i_size = i_size_read(inode); size_t offset = offset_in_folio(folio, pos); size_t plen = folio_size(folio); @@ -722,7 +722,7 @@ static bool netfs_skip_folio_read(struct folio *folio, loff_t pos, size_t len, */ int netfs_write_begin(struct netfs_inode *ctx, struct file *file, struct address_space *mapping, - loff_t pos, unsigned int len, struct folio **_folio, + uoff_t pos, unsigned int len, struct folio **_folio, void **_fsdata) { struct netfs_io_request *rreq; @@ -818,7 +818,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, struct netfs_io_request *rreq; struct address_space *mapping = folio->mapping; struct netfs_inode *ctx = netfs_inode(mapping->host); - unsigned long long start = folio_pos(folio); + uoff_t start = folio_pos(folio); size_t flen = folio_size(folio); int ret; diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index ecf119b4f91660..500ebaead08c11 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -17,7 +17,7 @@ * as possible to hold as much of the remaining length as possible in one go. */ static struct folio *netfs_grab_folio_for_write(struct address_space *mapping, - loff_t pos, size_t part) + uoff_t pos, size_t part) { pgoff_t index = pos / PAGE_SIZE; fgf_t fgp_flags = FGP_WRITEBEGIN; @@ -35,9 +35,9 @@ static struct folio *netfs_grab_folio_for_write(struct address_space *mapping, * the values actually are. */ void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode, - loff_t pos, size_t copied) + uoff_t pos, size_t copied) { - loff_t i_size, end = pos + copied; + uoff_t i_size, end = pos + copied; blkcnt_t add; size_t gap; @@ -102,7 +102,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter, struct folio *folio = NULL, *writethrough = NULL; unsigned int bdp_flags = (iocb->ki_flags & IOCB_NOWAIT) ? BDP_ASYNC : 0; ssize_t written = 0, ret, ret2; - loff_t pos = iocb->ki_pos; + uoff_t pos = iocb->ki_pos; size_t max_chunk = mapping_max_folio_size(mapping); bool maybe_trouble = false; @@ -134,7 +134,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter, enum netfs_folio_trace trace; struct netfs_folio *finfo; struct netfs_group *group; - unsigned long long fpos; + uoff_t fpos; size_t flen; size_t offset; /* Offset into pagecache folio */ size_t part; /* Bytes to write to folio */ diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index 6a8fb0d55e040e..aa10af5171a860 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -47,8 +47,8 @@ static void netfs_prepare_dio_read_iterator(struct netfs_io_subrequest *subreq) */ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) { - unsigned long long start = rreq->start; ssize_t size = rreq->len; + uoff_t start = rreq->start; int ret; do { diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c index 8be74706d85e2a..cc46b7d9321f1b 100644 --- a/fs/netfs/direct_write.c +++ b/fs/netfs/direct_write.c @@ -225,9 +225,9 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter * struct netfs_group *netfs_group) { struct netfs_io_request *wreq; - unsigned long long start = iocb->ki_pos; - unsigned long long end = start + iov_iter_count(iter); ssize_t ret, n; + uoff_t start = iocb->ki_pos; + uoff_t end = start + iov_iter_count(iter); size_t len = iov_iter_count(iter); bool async = !is_sync_kiocb(iocb); @@ -336,8 +336,8 @@ ssize_t netfs_unbuffered_write_iter(struct kiocb *iocb, struct iov_iter *from) struct inode *inode = mapping->host; struct netfs_inode *ictx = netfs_inode(inode); ssize_t ret; - loff_t pos = iocb->ki_pos; - unsigned long long end = pos + iov_iter_count(from) - 1; + uoff_t pos = iocb->ki_pos; + uoff_t end = pos + iov_iter_count(from) - 1; _enter("%llx,%zx,%llx", pos, iov_iter_count(from), i_size_read(inode)); diff --git a/fs/netfs/fscache_cookie.c b/fs/netfs/fscache_cookie.c index 3d56fc73435ffb..5a226f9cbdeaa0 100644 --- a/fs/netfs/fscache_cookie.c +++ b/fs/netfs/fscache_cookie.c @@ -327,7 +327,7 @@ static struct fscache_cookie *fscache_alloc_cookie( u8 advice, const void *index_key, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { struct fscache_cookie *cookie; @@ -452,7 +452,7 @@ struct fscache_cookie *__fscache_acquire_cookie( u8 advice, const void *index_key, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { struct fscache_cookie *cookie; @@ -663,7 +663,7 @@ static void fscache_unuse_cookie_locked(struct fscache_cookie *cookie) * Stop using the cookie for I/O. */ void __fscache_unuse_cookie(struct fscache_cookie *cookie, - const void *aux_data, const loff_t *object_size) + const void *aux_data, const uoff_t *object_size) { unsigned int debug_id = cookie->debug_id; unsigned int r = refcount_read(&cookie->ref); @@ -1049,7 +1049,7 @@ static void fscache_perform_invalidation(struct fscache_cookie *cookie) * Invalidate an object. */ void __fscache_invalidate(struct fscache_cookie *cookie, - const void *aux_data, loff_t new_size, + const void *aux_data, uoff_t new_size, unsigned int flags) { bool is_caching; diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c index 37f05b4d34699d..8bca63721eeb03 100644 --- a/fs/netfs/fscache_io.c +++ b/fs/netfs/fscache_io.c @@ -162,7 +162,7 @@ EXPORT_SYMBOL(__fscache_begin_write_operation); struct fscache_write_request { struct netfs_cache_resources cache_resources; struct address_space *mapping; - loff_t start; + uoff_t start; size_t len; bool set_bits; bool using_pgpriv2; @@ -171,7 +171,7 @@ struct fscache_write_request { }; void __fscache_clear_page_bits(struct address_space *mapping, - loff_t start, size_t len) + uoff_t start, size_t len) { pgoff_t first = start / PAGE_SIZE; pgoff_t last = (start + len - 1) / PAGE_SIZE; @@ -208,7 +208,7 @@ static void fscache_wreq_done(void *priv, ssize_t transferred_or_error) void __fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool cond) @@ -267,7 +267,7 @@ EXPORT_SYMBOL(__fscache_write_to_cache); /* * Change the size of a backing object. */ -void __fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size) +void __fscache_resize_cookie(struct fscache_cookie *cookie, uoff_t new_size) { struct netfs_cache_resources cres; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 786af76da98c25..1bc5e5828d0868 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -33,7 +33,7 @@ int netfs_prefetch_for_write(struct file *file, struct folio *folio, * buffered_write.c */ void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode, - loff_t pos, size_t copied); + uoff_t pos, size_t copied); /* * main.c @@ -88,7 +88,7 @@ int netfs_clear_stale_pre_isize(struct inode *inode, uoff_t from, */ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct file *file, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_io_origin origin); void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); void netfs_clear_subrequests(struct netfs_io_request *rreq); @@ -205,11 +205,11 @@ void netfs_write_collection_worker(struct work_struct *work); */ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, struct file *file, - loff_t start, + uoff_t start, enum netfs_io_origin origin); void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start); + uoff_t start); void netfs_reissue_write(struct netfs_io_stream *stream, struct netfs_io_subrequest *subreq, struct iov_iter *source); @@ -217,7 +217,7 @@ void netfs_issue_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream); size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start, size_t len, bool to_eof); + uoff_t start, size_t len, bool to_eof); struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len); int netfs_advance_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc, struct folio *folio, size_t copied, bool to_page_end, diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c index b375567e0520ed..eb1efb17f53aaa 100644 --- a/fs/netfs/iterator.c +++ b/fs/netfs/iterator.c @@ -209,7 +209,7 @@ static size_t netfs_limit_xarray(const struct iov_iter *iter, size_t start_offse { struct folio *folio; unsigned int nsegs = 0; - loff_t pos = iter->xarray_start + iter->iov_offset; + uoff_t pos = iter->xarray_start + iter->iov_offset; pgoff_t index = pos / PAGE_SIZE; size_t span = 0, n = iter->count; diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 523057390d6f4b..073257e15e4b81 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -194,7 +194,7 @@ void netfs_clear_inode_writeback(struct inode *inode, const void *aux) struct fscache_cookie *cookie = netfs_i_cookie(netfs_inode(inode)); if (inode_state_read_once(inode) & I_PINNING_NETFS_WB) { - loff_t i_size = i_size_read(inode); + uoff_t i_size = i_size_read(inode); fscache_unuse_cookie(cookie, aux, &i_size); } } @@ -219,8 +219,8 @@ void netfs_invalidate_folio(struct folio *folio, size_t offset, size_t length) _enter("{%lx},%zx,%zx", folio->index, offset, length); if (offset == 0 && length == flen) { - unsigned long long i_size, remote_i_size, zero_point; - unsigned long long fpos = folio_pos(folio), end; + uoff_t i_size, remote_i_size, zero_point; + uoff_t fpos = folio_pos(folio), end; netfs_read_sizes(inode, &i_size, &remote_i_size, &zero_point); end = umin(fpos + flen, i_size); @@ -306,7 +306,7 @@ bool netfs_release_folio(struct folio *folio, gfp_t gfp) { struct inode *inode = folio_inode(folio); struct netfs_inode *ctx = netfs_inode(inode); - unsigned long long i_size, remote_i_size, zero_point, end; + uoff_t i_size, remote_i_size, zero_point, end; if (folio_test_dirty(folio)) return false; diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index ad549daa9c794b..fcedd00fa86a2d 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -16,7 +16,7 @@ static void netfs_free_request(struct work_struct *work); */ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping, struct file *file, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_io_origin origin) { static atomic_t debug_ids; diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index c5bfaf2c6f2592..3f0cac1267111f 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -169,8 +169,8 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, unsigned int *notes) { struct folio_queue *folioq = rreq->buffer.tail; - unsigned long long collected_to = rreq->collected_to; unsigned int slot = rreq->buffer.first_tail_slot; + uoff_t collected_to = rreq->collected_to; if (rreq->cleaned_to >= rreq->collected_to) return; @@ -195,7 +195,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, for (;;) { struct folio *folio; - unsigned long long fpos, fend; + uoff_t fpos, fend; size_t fsize; folio = folioq_folio(folioq, slot); diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index a4b7bb88cbdb60..4d50585f69a608 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -20,7 +20,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio { struct netfs_io_stream *cache = &creq->io_streams[1]; size_t fsize = folio_size(folio), flen = fsize; - loff_t fpos = folio_pos(folio), i_size; + uoff_t fpos = folio_pos(folio), i_size; bool to_eof = false; _enter(""); @@ -175,8 +175,8 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq) bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) { struct folio_queue *folioq = creq->buffer.tail; - unsigned long long collected_to = creq->collected_to; unsigned int slot = creq->buffer.first_tail_slot; + uoff_t collected_to = creq->collected_to; bool made_progress = false; if (slot >= folioq_nr_slots(folioq)) { @@ -186,7 +186,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) for (;;) { struct folio *folio; - unsigned long long fpos, fend; + uoff_t fpos, fend; size_t fsize, flen; folio = folioq_folio(folioq, slot); @@ -199,7 +199,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) fsize = folio_size(folio); flen = fsize; - fend = min_t(unsigned long long, fpos + flen, creq->i_size); + fend = min_t(uoff_t, fpos + flen, creq->i_size); trace_netfs_collect_folio(creq, folio, fend, collected_to); diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 4f6a36c6e214f7..46810d29f0c0e3 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -75,7 +75,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq) do { struct netfs_io_subrequest *from, *to, *tmp; struct iov_iter source; - unsigned long long start, len; + uoff_t start, len; size_t part; bool boundary = false, subreq_superfluous = false; diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 210eb8f3958d77..100a5038c61e39 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -67,7 +67,7 @@ int netfs_folio_written_back(struct folio *folio) /* Streaming writes cannot be redirtied whilst under writeback, * so discard the streaming record. */ - unsigned long long fend; + uoff_t fend; fend = folio_pos(folio) + finfo->dirty_offset + finfo->dirty_len; spin_lock(&ictx->inode.i_lock); @@ -115,8 +115,8 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, unsigned int *notes) { struct folio_queue *folioq = wreq->buffer.tail; - unsigned long long collected_to = wreq->collected_to; unsigned int slot = wreq->buffer.first_tail_slot; + uoff_t collected_to = wreq->collected_to; if (WARN_ON_ONCE(!folioq)) { pr_err("[!] Writeback unlock found empty rolling buffer!\n"); @@ -140,7 +140,7 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, for (;;) { struct folio *folio; struct netfs_folio *finfo; - unsigned long long fpos, fend; + uoff_t fpos, fend; size_t fsize, flen; folio = folioq_folio(folioq, slot); @@ -154,7 +154,7 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, finfo = netfs_folio_info(folio); flen = finfo ? finfo->dirty_offset + finfo->dirty_len : fsize; - fend = min_t(unsigned long long, fpos + flen, wreq->i_size); + fend = min_t(uoff_t, fpos + flen, wreq->i_size); trace_netfs_collect_folio(wreq, folio, fend, collected_to); @@ -201,8 +201,8 @@ static void netfs_collect_write_results(struct netfs_io_request *wreq) { struct netfs_io_subrequest *front, *remove; struct netfs_io_stream *stream; - unsigned long long collected_to, issued_to; unsigned int notes; + uoff_t collected_to, issued_to; int s; _enter("%llx-%llx", wreq->start, wreq->start + wreq->len); diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 851f6f93ad45ab..3b33ad5b69c39c 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -89,7 +89,7 @@ static void netfs_kill_dirty_pages(struct address_space *mapping, */ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, struct file *file, - loff_t start, + uoff_t start, enum netfs_io_origin origin) { struct netfs_io_request *wreq; @@ -156,7 +156,7 @@ EXPORT_SYMBOL(netfs_prepare_write_failed); */ void netfs_prepare_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start) + uoff_t start) { struct netfs_io_subrequest *subreq; struct iov_iter *wreq_iter = &wreq->buffer.iter; @@ -279,7 +279,7 @@ void netfs_issue_write(struct netfs_io_request *wreq, */ size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, - loff_t start, size_t len, bool to_eof) + uoff_t start, size_t len, bool to_eof) { struct netfs_io_subrequest *subreq = stream->construct; size_t part; @@ -330,7 +330,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, struct netfs_folio *finfo; size_t iter_off = 0; size_t fsize = folio_size(folio), flen = fsize, foff = 0; - loff_t fpos = folio_pos(folio), i_size; + uoff_t fpos = folio_pos(folio), i_size; bool to_eof = false, streamw = false; bool debug = false; @@ -721,7 +721,7 @@ static int netfs_write_folio_single(struct netfs_io_request *wreq, struct netfs_io_stream *stream; size_t iter_off = 0; size_t fsize = folio_size(folio), flen; - loff_t fpos = folio_pos(folio); + uoff_t fpos = folio_pos(folio); ssize_t ret; bool to_eof = false; bool no_debug = false; diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index 058bc7a166a59f..6cd584242af282 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -55,7 +55,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq, do { struct netfs_io_subrequest *subreq = NULL, *from, *to, *tmp; struct iov_iter source; - unsigned long long start, len; + uoff_t start, len; size_t part; bool boundary = false; diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h index 4c91a019972b6a..ee524c863fa9be 100644 --- a/include/linux/fscache-cache.h +++ b/include/linux/fscache-cache.h @@ -67,7 +67,7 @@ struct fscache_cache_ops { /* Change the size of a data object */ void (*resize_cookie)(struct netfs_cache_resources *cres, - loff_t new_size); + uoff_t new_size); /* Invalidate an object */ bool (*invalidate_cookie)(struct fscache_cookie *cookie); diff --git a/include/linux/fscache.h b/include/linux/fscache.h index 58fdb9605425dc..e19fca38382b9b 100644 --- a/include/linux/fscache.h +++ b/include/linux/fscache.h @@ -112,7 +112,7 @@ struct fscache_cookie { struct list_head proc_link; /* Link in proc list */ struct list_head commit_link; /* Link in commit queue */ struct work_struct work; /* Commit/relinq/withdraw work */ - loff_t object_size; /* Size of the netfs object */ + uoff_t object_size; /* Size of the netfs object */ unsigned long unused_at; /* Time at which unused (jiffies) */ unsigned long flags; #define FSCACHE_COOKIE_RELINQUISHED 0 /* T if cookie has been relinquished */ @@ -163,22 +163,22 @@ extern struct fscache_cookie *__fscache_acquire_cookie( u8, const void *, size_t, const void *, size_t, - loff_t); + uoff_t); extern void __fscache_use_cookie(struct fscache_cookie *, bool); -extern void __fscache_unuse_cookie(struct fscache_cookie *, const void *, const loff_t *); +extern void __fscache_unuse_cookie(struct fscache_cookie *, const void *, const uoff_t *); extern void __fscache_relinquish_cookie(struct fscache_cookie *, bool); -extern void __fscache_resize_cookie(struct fscache_cookie *, loff_t); -extern void __fscache_invalidate(struct fscache_cookie *, const void *, loff_t, unsigned int); +extern void __fscache_resize_cookie(struct fscache_cookie *, uoff_t); +extern void __fscache_invalidate(struct fscache_cookie *, const void *, uoff_t, unsigned int); extern int __fscache_begin_read_operation(struct netfs_cache_resources *, struct fscache_cookie *); extern int __fscache_begin_write_operation(struct netfs_cache_resources *, struct fscache_cookie *); void __fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool cond); -extern void __fscache_clear_page_bits(struct address_space *, loff_t, size_t); +extern void __fscache_clear_page_bits(struct address_space *, uoff_t, size_t); /** * fscache_acquire_volume - Register a volume as desiring caching services @@ -249,7 +249,7 @@ struct fscache_cookie *fscache_acquire_cookie(struct fscache_volume *volume, size_t index_key_len, const void *aux_data, size_t aux_data_len, - loff_t object_size) + uoff_t object_size) { if (!fscache_volume_valid(volume)) return NULL; @@ -286,7 +286,7 @@ static inline void fscache_use_cookie(struct fscache_cookie *cookie, */ static inline void fscache_unuse_cookie(struct fscache_cookie *cookie, const void *aux_data, - const loff_t *object_size) + const uoff_t *object_size) { if (fscache_cookie_valid(cookie)) __fscache_unuse_cookie(cookie, aux_data, object_size); @@ -327,7 +327,7 @@ static inline void *fscache_get_aux(struct fscache_cookie *cookie) */ static inline void fscache_update_aux(struct fscache_cookie *cookie, - const void *aux_data, const loff_t *object_size) + const void *aux_data, const uoff_t *object_size) { void *p = fscache_get_aux(cookie); @@ -343,7 +343,7 @@ extern atomic_t fscache_n_updates; static inline void __fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_data, - const loff_t *object_size) + const uoff_t *object_size) { #ifdef CONFIG_FSCACHE_STATS atomic_inc(&fscache_n_updates); @@ -369,7 +369,7 @@ void __fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_data */ static inline void fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_data, - const loff_t *object_size) + const uoff_t *object_size) { if (fscache_cookie_enabled(cookie)) __fscache_update_cookie(cookie, aux_data, object_size); @@ -386,7 +386,7 @@ void fscache_update_cookie(struct fscache_cookie *cookie, const void *aux_data, * description. */ static inline -void fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size) +void fscache_resize_cookie(struct fscache_cookie *cookie, uoff_t new_size) { if (fscache_cookie_enabled(cookie)) __fscache_resize_cookie(cookie, new_size); @@ -413,7 +413,7 @@ void fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size) */ static inline void fscache_invalidate(struct fscache_cookie *cookie, - const void *aux_data, loff_t size, unsigned int flags) + const void *aux_data, uoff_t size, unsigned int flags) { if (fscache_cookie_enabled(cookie)) __fscache_invalidate(cookie, aux_data, size, flags); @@ -502,7 +502,7 @@ static inline void fscache_end_operation(struct netfs_cache_resources *cres) */ static inline int fscache_read(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -561,7 +561,7 @@ int fscache_begin_write_operation(struct netfs_cache_resources *cres, */ static inline int fscache_write(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv) @@ -581,7 +581,7 @@ int fscache_write(struct netfs_cache_resources *cres, * waiting. */ static inline void fscache_clear_page_bits(struct address_space *mapping, - loff_t start, size_t len, + uoff_t start, size_t len, bool caching) { if (caching) @@ -615,7 +615,7 @@ static inline void fscache_clear_page_bits(struct address_space *mapping, */ static inline void fscache_write_to_cache(struct fscache_cookie *cookie, struct address_space *mapping, - loff_t start, size_t len, loff_t i_size, + uoff_t start, size_t len, uoff_t i_size, netfs_io_terminated_t term_func, void *term_func_priv, bool using_pgpriv2, bool caching) diff --git a/include/linux/netfs.h b/include/linux/netfs.h index fe6275e55fc279..12dd286e65c88a 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -62,8 +62,8 @@ struct netfs_inode { struct fscache_cookie *cache; #endif struct list_head wb_queue; /* Queue of processes wanting to do writeback */ - loff_t _remote_i_size; /* Size of the remote file */ - loff_t _zero_point; /* Size after which we assume there's no data + uoff_t _remote_i_size; /* Size of the remote file */ + uoff_t _zero_point; /* Size after which we assume there's no data * on the server */ spinlock_t lock; /* Lock covering wb_queue */ atomic_t io_count; /* Number of outstanding reqs */ @@ -142,7 +142,7 @@ struct netfs_io_stream { void (*issue_write)(struct netfs_io_subrequest *subreq); /* Collection tracking */ struct list_head subrequests; /* Contributory I/O operations */ - unsigned long long collected_to; /* Position we've collected results to */ + uoff_t collected_to; /* Position we've collected results to */ size_t transferred; /* The amount transferred from this stream */ unsigned short error; /* Aggregate error for the stream */ enum netfs_io_source source; /* Where to read from/write to */ @@ -177,7 +177,7 @@ struct netfs_io_subrequest { struct work_struct work; struct list_head rreq_link; /* Link in rreq->subrequests */ struct iov_iter io_iter; /* Iterator for this subrequest */ - unsigned long long start; /* Where to start the I/O */ + uoff_t start; /* Where to start the I/O */ size_t len; /* Size of the I/O */ size_t transferred; /* Amount of data transferred */ refcount_t ref; @@ -243,17 +243,17 @@ struct netfs_io_request { void *netfs_priv; /* Private data for the netfs */ void *netfs_priv2; /* Private data for the netfs */ struct bio_vec *direct_bv; /* DIO buffer list (when handling iovec-iter) */ - unsigned long long submitted; /* Amount submitted for I/O so far */ - unsigned long long len; /* Length of the request */ + uoff_t submitted; /* Amount submitted for I/O so far */ + uoff_t len; /* Length of the request */ size_t transferred; /* Amount to be indicated as transferred */ size_t progress_at; /* Report read progress when hit this much read */ long error; /* 0 or error that occurred */ - unsigned long long i_size; /* Size of the file */ - unsigned long long start; /* Start position */ + uoff_t i_size; /* Size of the file */ + uoff_t start; /* Start position */ atomic64_t issued_to; /* Write issuer folio cursor */ - unsigned long long collected_to; /* Point we've collected to */ - unsigned long long cleaned_to; /* Position we've cleaned folios to */ - unsigned long long abandon_to; /* Position to abandon folios to */ + uoff_t collected_to; /* Point we've collected to */ + uoff_t cleaned_to; /* Position we've cleaned folios to */ + uoff_t abandon_to; /* Position to abandon folios to */ const struct folio *no_unlock_folio; /* Don't unlock this folio after read */ gfp_t gfp; /* GFP flags to use */ unsigned int direct_bv_count; /* Number of elements in direct_bv[] */ @@ -299,12 +299,12 @@ struct netfs_request_ops { int (*prepare_read)(struct netfs_io_subrequest *subreq); void (*issue_read)(struct netfs_io_subrequest *subreq); bool (*is_still_valid)(struct netfs_io_request *rreq); - int (*check_write_begin)(struct file *file, loff_t pos, unsigned len, + int (*check_write_begin)(struct file *file, uoff_t pos, unsigned len, struct folio **foliop, void **_fsdata); void (*done)(struct netfs_io_request *rreq); /* Modification handling */ - void (*update_i_size)(struct inode *inode, loff_t i_size); + void (*update_i_size)(struct inode *inode, uoff_t i_size); void (*post_modify)(struct inode *inode); /* Write request handling */ @@ -332,7 +332,7 @@ struct netfs_cache_ops { /* Read data from the cache */ int (*read)(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, enum netfs_read_from_hole read_hole, netfs_io_terminated_t term_func, @@ -340,7 +340,7 @@ struct netfs_cache_ops { /* Write data to the cache */ int (*write)(struct netfs_cache_resources *cres, - loff_t start_pos, + uoff_t start_pos, struct iov_iter *iter, netfs_io_terminated_t term_func, void *term_func_priv); @@ -350,15 +350,15 @@ struct netfs_cache_ops { /* Expand readahead request */ void (*expand_readahead)(struct netfs_cache_resources *cres, - unsigned long long *_start, - unsigned long long *_len, - unsigned long long i_size); + uoff_t *_start, + uoff_t *_len, + uoff_t i_size); /* Prepare a read operation, shortening it to a cached/uncached * boundary as appropriate. */ enum netfs_io_source (*prepare_read)(struct netfs_io_subrequest *subreq, - unsigned long long i_size); + uoff_t i_size); /* Prepare a write subrequest, working out if we're allowed to do it * and finding out the maximum amount of data to gather before @@ -371,15 +371,15 @@ struct netfs_cache_ops { * actually do. */ int (*prepare_write)(struct netfs_cache_resources *cres, - loff_t *_start, size_t *_len, size_t upper_len, - loff_t i_size, bool no_space_allocated_yet); + uoff_t *_start, size_t *_len, size_t upper_len, + uoff_t i_size, bool no_space_allocated_yet); /* Query the occupancy of the cache in a region, returning where the * next chunk of data starts and how long it is. */ int (*query_occupancy)(struct netfs_cache_resources *cres, - loff_t start, size_t len, size_t granularity, - loff_t *_data_start, size_t *_data_len); + uoff_t start, size_t len, size_t granularity, + uoff_t *_data_start, size_t *_data_len); }; /* High-level read API. */ @@ -412,7 +412,7 @@ struct readahead_control; void netfs_readahead(struct readahead_control *); int netfs_read_folio(struct file *, struct folio *); int netfs_write_begin(struct netfs_inode *, struct file *, - struct address_space *, loff_t pos, unsigned int len, + struct address_space *, uoff_t pos, unsigned int len, struct folio **, void **fsdata); int netfs_writepages(struct address_space *mapping, struct writeback_control *wbc); @@ -490,10 +490,10 @@ static inline struct netfs_inode *netfs_inode(struct inode *inode) * cmpxchg8b without the need of the lock prefix). For SMP compiles and 64bit * archs it makes no difference if preempt is enabled or not. */ -static inline unsigned long long netfs_read_remote_i_size(const struct inode *inode) +static inline uoff_t netfs_read_remote_i_size(const struct inode *inode) { const struct netfs_inode *ictx = container_of(inode, struct netfs_inode, inode); - unsigned long long remote_i_size; + uoff_t remote_i_size; #if BITS_PER_LONG==32 && defined(CONFIG_SMP) unsigned int seq; @@ -528,7 +528,7 @@ static inline unsigned long long netfs_read_remote_i_size(const struct inode *in * spinning forever. */ static inline void netfs_write_remote_i_size(struct inode *inode, - unsigned long long remote_i_size) + uoff_t remote_i_size) { struct netfs_inode *ictx = netfs_inode(inode); @@ -565,10 +565,10 @@ static inline void netfs_write_remote_i_size(struct inode *inode, * cmpxchg8b without the need of the lock prefix). For SMP compiles and 64bit * archs it makes no difference if preempt is enabled or not. */ -static inline unsigned long long netfs_read_zero_point(const struct inode *inode) +static inline uoff_t netfs_read_zero_point(const struct inode *inode) { struct netfs_inode *ictx = container_of(inode, struct netfs_inode, inode); - unsigned long long zero_point; + uoff_t zero_point; #if BITS_PER_LONG==32 && defined(CONFIG_SMP) unsigned int seq; @@ -603,7 +603,7 @@ static inline unsigned long long netfs_read_zero_point(const struct inode *inode * forever. */ static inline void netfs_write_zero_point(struct inode *inode, - unsigned long long zero_point) + uoff_t zero_point) { struct netfs_inode *ictx = netfs_inode(inode); @@ -644,9 +644,9 @@ static inline void netfs_write_zero_point(struct inode *inode, * archs it makes no difference if preempt is enabled or not. */ static inline void netfs_read_sizes(const struct inode *inode, - unsigned long long *i_size, - unsigned long long *remote_i_size, - unsigned long long *zero_point) + uoff_t *i_size, + uoff_t *remote_i_size, + uoff_t *zero_point) { const struct netfs_inode *ictx = container_of(inode, struct netfs_inode, inode); #if BITS_PER_LONG==32 && defined(CONFIG_SMP) @@ -692,9 +692,9 @@ static inline void netfs_read_sizes(const struct inode *inode, * forever. */ static inline void netfs_write_sizes(struct inode *inode, - unsigned long long i_size, - unsigned long long remote_i_size, - unsigned long long zero_point) + uoff_t i_size, + uoff_t remote_i_size, + uoff_t zero_point) { struct netfs_inode *ictx = netfs_inode(inode); @@ -762,7 +762,7 @@ static inline void netfs_inode_init(struct netfs_inode *ctx, * Inform the netfs lib that a file got resized so that it can adjust its state. */ static inline void netfs_resize_file(struct netfs_inode *ictx, - unsigned long long new_i_size, + uoff_t new_i_size, bool changed_on_server) { #if BITS_PER_LONG==32 && defined(CONFIG_SMP) diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index e3101410e8b2dc..1938d51a945940 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -449,7 +449,7 @@ TRACE_EVENT(cachefiles_vol_coherency, TRACE_EVENT(cachefiles_prep_read, TP_PROTO(struct cachefiles_object *obj, - loff_t start, + uoff_t start, size_t len, unsigned short flags, enum netfs_io_source source, @@ -464,7 +464,7 @@ TRACE_EVENT(cachefiles_prep_read, __field(enum netfs_io_source, source) __field(enum cachefiles_prepare_read_trace, why) __field(size_t, len) - __field(loff_t, start) + __field(uoff_t, start) __field(unsigned int, netfs_inode) __field(unsigned int, cache_inode) ), @@ -492,16 +492,16 @@ TRACE_EVENT(cachefiles_prep_read, TRACE_EVENT(cachefiles_read, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, + uoff_t start, size_t len), TP_ARGS(obj, backer, start, len), TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(size_t, len) - __field(loff_t, start) + __field(unsigned int, obj) + __field(unsigned int, backer) + __field(size_t, len) + __field(uoff_t, start) ), TP_fast_assign( @@ -521,16 +521,16 @@ TRACE_EVENT(cachefiles_read, TRACE_EVENT(cachefiles_write, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, + uoff_t start, size_t len), TP_ARGS(obj, backer, start, len), TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(size_t, len) - __field(loff_t, start) + __field(unsigned int, obj) + __field(unsigned int, backer) + __field(size_t, len) + __field(uoff_t, start) ), TP_fast_assign( @@ -549,7 +549,7 @@ TRACE_EVENT(cachefiles_write, TRACE_EVENT(cachefiles_trunc, TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t from, loff_t to, enum cachefiles_trunc_trace why), + uoff_t from, uoff_t to, enum cachefiles_trunc_trace why), TP_ARGS(obj, backer, from, to, why), @@ -557,8 +557,8 @@ TRACE_EVENT(cachefiles_trunc, __field(unsigned int, obj) __field(unsigned int, backer) __field(enum cachefiles_trunc_trace, why) - __field(loff_t, from) - __field(loff_t, to) + __field(uoff_t, from) + __field(uoff_t, to) ), TP_fast_assign( diff --git a/include/trace/events/fscache.h b/include/trace/events/fscache.h index f1a73aa83fbbfb..8735d428ebd9ba 100644 --- a/include/trace/events/fscache.h +++ b/include/trace/events/fscache.h @@ -460,13 +460,13 @@ TRACE_EVENT(fscache_relinquish, ); TRACE_EVENT(fscache_invalidate, - TP_PROTO(struct fscache_cookie *cookie, loff_t new_size), + TP_PROTO(struct fscache_cookie *cookie, uoff_t new_size), TP_ARGS(cookie, new_size), TP_STRUCT__entry( __field(unsigned int, cookie ) - __field(loff_t, new_size ) + __field(uoff_t, new_size ) ), TP_fast_assign( @@ -479,14 +479,14 @@ TRACE_EVENT(fscache_invalidate, ); TRACE_EVENT(fscache_resize, - TP_PROTO(struct fscache_cookie *cookie, loff_t new_size), + TP_PROTO(struct fscache_cookie *cookie, uoff_t new_size), TP_ARGS(cookie, new_size), TP_STRUCT__entry( __field(unsigned int, cookie ) - __field(loff_t, old_size ) - __field(loff_t, new_size ) + __field(uoff_t, old_size ) + __field(uoff_t, new_size ) ), TP_fast_assign( diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 3fec3e8f91c859..2010c878b0dacc 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -301,7 +301,7 @@ netfs_folioq_traces; TRACE_EVENT(netfs_read, TP_PROTO(struct netfs_io_request *rreq, - loff_t start, size_t len, + uoff_t start, size_t len, enum netfs_read_trace what), TP_ARGS(rreq, start, len, what), @@ -309,8 +309,8 @@ TRACE_EVENT(netfs_read, TP_STRUCT__entry( __field(unsigned int, rreq) __field(unsigned int, cookie) - __field(loff_t, i_size) - __field(loff_t, start) + __field(uoff_t, i_size) + __field(uoff_t, start) __field(size_t, len) __field(enum netfs_read_trace, what) __field(u64, netfs_inode) @@ -377,7 +377,7 @@ TRACE_EVENT(netfs_sreq, __field(u8, slot) __field(size_t, len) __field(size_t, transferred) - __field(loff_t, start) + __field(uoff_t, start) ), TP_fast_assign( @@ -418,7 +418,7 @@ TRACE_EVENT(netfs_failure, __field(enum netfs_failure, what) __field(size_t, len) __field(size_t, transferred) - __field(loff_t, start) + __field(uoff_t, start) ), TP_fast_assign( @@ -524,10 +524,10 @@ TRACE_EVENT(netfs_write_iter, TP_ARGS(iocb, from), TP_STRUCT__entry( - __field(unsigned long long, start) - __field(size_t, len) - __field(unsigned int, flags) - __field(unsigned int, ino) + __field(uoff_t, start) + __field(size_t, len) + __field(unsigned int, flags) + __field(unsigned int, ino) ), TP_fast_assign( @@ -552,8 +552,8 @@ TRACE_EVENT(netfs_write, __field(unsigned int, cookie) __field(unsigned int, ino) __field(enum netfs_write_trace, what) - __field(unsigned long long, start) - __field(unsigned long long, len) + __field(uoff_t, start) + __field(uoff_t, len) ), TP_fast_assign( @@ -582,10 +582,10 @@ TRACE_EVENT(netfs_copy2cache, TP_ARGS(rreq, creq), TP_STRUCT__entry( - __field(unsigned int, rreq) - __field(unsigned int, creq) - __field(unsigned int, cookie) - __field(unsigned int, ino) + __field(unsigned int, rreq) + __field(unsigned int, creq) + __field(unsigned int, cookie) + __field(unsigned int, ino) ), TP_fast_assign( @@ -610,10 +610,10 @@ TRACE_EVENT(netfs_collect, TP_ARGS(wreq), TP_STRUCT__entry( - __field(unsigned int, wreq) - __field(unsigned int, len) - __field(unsigned long long, transferred) - __field(unsigned long long, start) + __field(unsigned int, wreq) + __field(unsigned int, len) + __field(uoff_t, transferred) + __field(uoff_t, start) ), TP_fast_assign( @@ -636,12 +636,12 @@ TRACE_EVENT(netfs_collect_sreq, TP_ARGS(wreq, subreq), TP_STRUCT__entry( - __field(unsigned int, wreq) - __field(unsigned int, subreq) - __field(unsigned int, stream) - __field(unsigned int, len) - __field(unsigned int, transferred) - __field(unsigned long long, start) + __field(unsigned int, wreq) + __field(unsigned int, subreq) + __field(unsigned int, stream) + __field(unsigned int, len) + __field(unsigned int, transferred) + __field(uoff_t, start) ), TP_fast_assign( @@ -661,17 +661,16 @@ TRACE_EVENT(netfs_collect_sreq, TRACE_EVENT(netfs_collect_folio, TP_PROTO(const struct netfs_io_request *wreq, const struct folio *folio, - unsigned long long fend, - unsigned long long collected_to), + uoff_t fend, uoff_t collected_to), TP_ARGS(wreq, folio, fend, collected_to), TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned long, index) - __field(unsigned long long, fend) - __field(unsigned long long, cleaned_to) - __field(unsigned long long, collected_to) + __field(uoff_t, fend) + __field(uoff_t, cleaned_to) + __field(uoff_t, collected_to) ), TP_fast_assign( @@ -684,13 +683,13 @@ TRACE_EVENT(netfs_collect_folio, TP_printk("R=%08x ix=%05lx r=%llx-%llx t=%llx/%llx", __entry->wreq, __entry->index, - (unsigned long long)__entry->index * PAGE_SIZE, __entry->fend, + (uoff_t)__entry->index * PAGE_SIZE, __entry->fend, __entry->cleaned_to, __entry->collected_to) ); TRACE_EVENT(netfs_collect_state, TP_PROTO(const struct netfs_io_request *wreq, - unsigned long long collected_to, + uoff_t collected_to, unsigned int notes), TP_ARGS(wreq, collected_to, notes), @@ -698,8 +697,8 @@ TRACE_EVENT(netfs_collect_state, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned int, notes) - __field(unsigned long long, collected_to) - __field(unsigned long long, cleaned_to) + __field(uoff_t, collected_to) + __field(uoff_t, cleaned_to) ), TP_fast_assign( @@ -718,7 +717,7 @@ TRACE_EVENT(netfs_collect_state, TRACE_EVENT(netfs_collect_gap, TP_PROTO(const struct netfs_io_request *wreq, const struct netfs_io_stream *stream, - unsigned long long jump_to, char type), + uoff_t jump_to, char type), TP_ARGS(wreq, stream, jump_to, type), @@ -726,8 +725,8 @@ TRACE_EVENT(netfs_collect_gap, __field(unsigned int, wreq) __field(unsigned char, stream) __field(unsigned char, type) - __field(unsigned long long, from) - __field(unsigned long long, to) + __field(uoff_t, from) + __field(uoff_t, to) ), TP_fast_assign( @@ -752,8 +751,8 @@ TRACE_EVENT(netfs_collect_stream, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned char, stream) - __field(unsigned long long, collected_to) - __field(unsigned long long, issued_to) + __field(uoff_t, collected_to) + __field(uoff_t, issued_to) ), TP_fast_assign( From 9f40ea2aac9e8ba0770d4661a9214601b50b36dd Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:20:56 +0100 Subject: [PATCH 1454/2286] netfs: Remove the writethrough code Remove the netfs writethrough code as it's very tricky to get the locking right and it will probably deadlock if used in conjunction with Ceph snapshots because it excludes writeback for the duration, but to flush out old snapshots, it does a synchronous flush that invokes writeback. Instead, O_SYNC writes do a flush after performing the write - which is already there as the callers of netfs_perform_write() all call generic_write_sync(). Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-3-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/9p/vfs_addr.c | 1 - fs/afs/file.c | 1 - fs/netfs/buffered_write.c | 56 ++----------------- fs/netfs/internal.h | 7 --- fs/netfs/main.c | 1 - fs/netfs/stats.c | 4 +- fs/netfs/write_collect.c | 2 - fs/netfs/write_issue.c | 104 +---------------------------------- include/linux/netfs.h | 1 - include/trace/events/netfs.h | 8 +-- 10 files changed, 9 insertions(+), 176 deletions(-) diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 13cf87a5f90cb7..170a2b91c5f07a 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -150,7 +150,6 @@ static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file) struct p9_fid *fid; struct dentry *dentry; bool writing = (rreq->origin == NETFS_READ_FOR_WRITE || - rreq->origin == NETFS_WRITETHROUGH || rreq->origin == NETFS_UNBUFFERED_WRITE || rreq->origin == NETFS_DIO_WRITE); diff --git a/fs/afs/file.c b/fs/afs/file.c index bdb4ca0ef8da73..b7f461b9d5f506 100644 --- a/fs/afs/file.c +++ b/fs/afs/file.c @@ -400,7 +400,6 @@ static int afs_init_request(struct netfs_io_request *rreq, struct file *file) } break; case NETFS_WRITEBACK: - case NETFS_WRITETHROUGH: case NETFS_UNBUFFERED_WRITE: case NETFS_DIO_WRITE: if (S_ISREG(rreq->inode->i_mode)) diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c index 500ebaead08c11..0afdd634ca864a 100644 --- a/fs/netfs/buffered_write.c +++ b/fs/netfs/buffered_write.c @@ -91,44 +91,14 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter, struct inode *inode = file_inode(file); struct address_space *mapping = inode->i_mapping; struct netfs_inode *ctx = netfs_inode(inode); - struct writeback_control wbc = { - .sync_mode = WB_SYNC_NONE, - .for_sync = true, - .nr_to_write = LONG_MAX, - .range_start = iocb->ki_pos, - .range_end = iocb->ki_pos + iter->count, - }; - struct netfs_io_request *wreq = NULL; - struct folio *folio = NULL, *writethrough = NULL; + struct folio *folio = NULL; unsigned int bdp_flags = (iocb->ki_flags & IOCB_NOWAIT) ? BDP_ASYNC : 0; - ssize_t written = 0, ret, ret2; + ssize_t written = 0, ret; uoff_t pos = iocb->ki_pos; size_t max_chunk = mapping_max_folio_size(mapping); bool maybe_trouble = false; - if (unlikely(iocb->ki_flags & (IOCB_DSYNC | IOCB_SYNC)) - ) { - wbc_attach_fdatawrite_inode(&wbc, mapping->host); - - ret = filemap_write_and_wait_range(mapping, pos, pos + iter->count); - if (ret < 0) { - wbc_detach_inode(&wbc); - goto out; - } - - wreq = netfs_begin_writethrough(iocb, iter->count); - if (IS_ERR(wreq)) { - wbc_detach_inode(&wbc); - ret = PTR_ERR(wreq); - wreq = NULL; - goto out; - } - if (!is_sync_kiocb(iocb)) - wreq->iocb = iocb; - netfs_stat(&netfs_n_wh_writethrough); - } else { - netfs_stat(&netfs_n_wh_buffered_write); - } + netfs_stat(&netfs_n_wh_buffered_write); do { enum netfs_folio_trace trace; @@ -390,15 +360,8 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter, pos += copied; written += copied; - if (likely(!wreq)) { - folio_mark_dirty(folio); - folio_unlock(folio); - } else { - netfs_advance_writethrough(wreq, &wbc, folio, copied, - offset + copied == flen, - &writethrough); - /* Folio unlocked */ - } + folio_mark_dirty(folio); + folio_unlock(folio); retry: folio_put(folio); folio = NULL; @@ -420,15 +383,6 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter, ctx->ops->post_modify(inode); } - if (unlikely(wreq)) { - ret2 = netfs_end_writethrough(wreq, &wbc, writethrough); - wbc_detach_inode(&wbc); - if (ret2 == -EIOCBQUEUED) - return ret2; - if (ret == 0 && ret2 < 0) - ret = ret2; - } - iocb->ki_pos += written; _leave(" = %zd [%zd]", written, ret); return written ? written : ret; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 1bc5e5828d0868..0f3c7250ae99fc 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -159,7 +159,6 @@ extern atomic_t netfs_n_rh_write_zskip; extern atomic_t netfs_n_rh_retry_read_req; extern atomic_t netfs_n_rh_retry_read_subreq; extern atomic_t netfs_n_wh_buffered_write; -extern atomic_t netfs_n_wh_writethrough; extern atomic_t netfs_n_wh_dio_write; extern atomic_t netfs_n_wh_writepages; extern atomic_t netfs_n_wh_copy_to_cache; @@ -218,12 +217,6 @@ void netfs_issue_write(struct netfs_io_request *wreq, size_t netfs_advance_write(struct netfs_io_request *wreq, struct netfs_io_stream *stream, uoff_t start, size_t len, bool to_eof); -struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len); -int netfs_advance_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc, - struct folio *folio, size_t copied, bool to_page_end, - struct folio **writethrough_cache); -ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc, - struct folio *writethrough_cache); /* * write_retry.c diff --git a/fs/netfs/main.c b/fs/netfs/main.c index 927badf3989db4..609e22e8f76a06 100644 --- a/fs/netfs/main.c +++ b/fs/netfs/main.c @@ -44,7 +44,6 @@ static const char *netfs_origins[nr__netfs_io_origin] = { [NETFS_DIO_READ] = "DR", [NETFS_WRITEBACK] = "WB", [NETFS_WRITEBACK_SINGLE] = "W1", - [NETFS_WRITETHROUGH] = "WT", [NETFS_UNBUFFERED_WRITE] = "UW", [NETFS_DIO_WRITE] = "DW", [NETFS_PGPRIV2_COPY_TO_CACHE] = "2C", diff --git a/fs/netfs/stats.c b/fs/netfs/stats.c index ab6b916addc443..9a607c4e62dda5 100644 --- a/fs/netfs/stats.c +++ b/fs/netfs/stats.c @@ -32,7 +32,6 @@ atomic_t netfs_n_rh_write_zskip; atomic_t netfs_n_rh_retry_read_req; atomic_t netfs_n_rh_retry_read_subreq; atomic_t netfs_n_wh_buffered_write; -atomic_t netfs_n_wh_writethrough; atomic_t netfs_n_wh_dio_write; atomic_t netfs_n_wh_writepages; atomic_t netfs_n_wh_copy_to_cache; @@ -58,9 +57,8 @@ int netfs_stats_show(struct seq_file *m, void *v) atomic_read(&netfs_n_rh_read_single), atomic_read(&netfs_n_rh_write_begin), atomic_read(&netfs_n_rh_write_zskip)); - seq_printf(m, "Writes : BW=%u WT=%u DW=%u WP=%u 2C=%u\n", + seq_printf(m, "Writes : BW=%u DW=%u WP=%u 2C=%u\n", atomic_read(&netfs_n_wh_buffered_write), - atomic_read(&netfs_n_wh_writethrough), atomic_read(&netfs_n_wh_dio_write), atomic_read(&netfs_n_wh_writepages), atomic_read(&netfs_n_wh_copy_to_cache)); diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 100a5038c61e39..244a68e04624bc 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -214,7 +214,6 @@ static void netfs_collect_write_results(struct netfs_io_request *wreq) smp_rmb(); collected_to = ULLONG_MAX; if (wreq->origin == NETFS_WRITEBACK || - wreq->origin == NETFS_WRITETHROUGH || wreq->origin == NETFS_PGPRIV2_COPY_TO_CACHE) notes = NEED_UNLOCK; else @@ -411,7 +410,6 @@ bool netfs_write_collection(struct netfs_io_request *wreq) switch (wreq->origin) { case NETFS_WRITEBACK: case NETFS_WRITEBACK_SINGLE: - case NETFS_WRITETHROUGH: netfs_wb_end(ictx); break; default: diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 3b33ad5b69c39c..5d130df2ff0d06 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -96,7 +96,6 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping, struct netfs_inode *ictx; bool is_cacheable = (origin == NETFS_WRITEBACK || origin == NETFS_WRITEBACK_SINGLE || - origin == NETFS_WRITETHROUGH || origin == NETFS_PGPRIV2_COPY_TO_CACHE); wreq = netfs_alloc_request(mapping, file, start, 0, origin); @@ -367,11 +366,7 @@ static int netfs_write_folio(struct netfs_io_request *wreq, streamw = true; } - if (wreq->origin == NETFS_WRITETHROUGH) { - to_eof = false; - if (flen > i_size - fpos) - flen = i_size - fpos; - } else if (flen > i_size - fpos) { + if (flen > i_size - fpos) { flen = i_size - fpos; if (!streamw) folio_zero_segment(folio, flen, fsize); @@ -613,103 +608,6 @@ int netfs_writepages(struct address_space *mapping, } EXPORT_SYMBOL(netfs_writepages); -/* - * Begin a write operation for writing through the pagecache. - */ -struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len) -{ - struct netfs_io_request *wreq = NULL; - struct netfs_inode *ictx = netfs_inode(file_inode(iocb->ki_filp)); - - netfs_wb_begin(ictx, false); - - wreq = netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp, - iocb->ki_pos, NETFS_WRITETHROUGH); - if (IS_ERR(wreq)) { - netfs_wb_end(ictx); - return wreq; - } - - wreq->io_streams[0].avail = true; - __set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags); - trace_netfs_write(wreq, netfs_write_trace_writethrough); - return wreq; -} - -/* - * Advance the state of the write operation used when writing through the - * pagecache. Data has been copied into the pagecache that we need to append - * to the request. If we've added more than wsize then we need to create a new - * subrequest. - */ -int netfs_advance_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc, - struct folio *folio, size_t copied, bool to_page_end, - struct folio **writethrough_cache) -{ - int ret; - - _enter("R=%x ic=%zu ws=%u cp=%zu tp=%u", - wreq->debug_id, wreq->buffer.iter.count, wreq->wsize, copied, to_page_end); - - /* The folio is locked. */ - - if (*writethrough_cache != folio) { - if (*writethrough_cache) { - /* Did the folio get moved? */ - folio_put(*writethrough_cache); - *writethrough_cache = NULL; - } - /* We can make multiple writes to the folio... */ - if (wreq->len == 0) - trace_netfs_folio(folio, netfs_folio_trace_wthru); - else - trace_netfs_folio(folio, netfs_folio_trace_wthru_plus); - *writethrough_cache = folio; - folio_get(folio); - } - - wreq->len += copied; - - if (!to_page_end) { - folio_mark_dirty(folio); - folio_unlock(folio); - return 0; - } - - ret = netfs_write_folio(wreq, wbc, folio); - folio_put(*writethrough_cache); - *writethrough_cache = NULL; - wreq->submitted = wreq->len; - return ret; -} - -/* - * End a write operation used when writing through the pagecache. - */ -ssize_t netfs_end_writethrough(struct netfs_io_request *wreq, struct writeback_control *wbc, - struct folio *writethrough_cache) -{ - ssize_t ret; - - _enter("R=%x", wreq->debug_id); - - if (writethrough_cache) { - folio_lock(writethrough_cache); - netfs_write_folio(wreq, wbc, writethrough_cache); - folio_put(writethrough_cache); - wreq->submitted = wreq->len; - } - - netfs_end_issue_write(wreq); - - if (wreq->iocb) - ret = -EIOCBQUEUED; - else - ret = netfs_wait_for_write(wreq); - netfs_put_request(wreq, netfs_rreq_trace_put_return); - return ret; -} - /* * Write some of a pending folio data back to the server and/or the cache. */ diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 12dd286e65c88a..0fb4884daf6d59 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -208,7 +208,6 @@ enum netfs_io_origin { NETFS_DIO_READ, /* This is a direct I/O read */ NETFS_WRITEBACK, /* This write was triggered by writepages */ NETFS_WRITEBACK_SINGLE, /* This monolithic write was triggered by writepages */ - NETFS_WRITETHROUGH, /* This write was made by netfs_perform_write() */ NETFS_UNBUFFERED_WRITE, /* This is an unbuffered write */ NETFS_DIO_WRITE, /* This is a direct I/O write */ NETFS_PGPRIV2_COPY_TO_CACHE, /* [DEPRECATED] This is writing read data to the cache */ diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 2010c878b0dacc..ef1185993ba45e 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -30,8 +30,7 @@ EM(netfs_write_trace_dio_write, "DIO-WRITE") \ EM(netfs_write_trace_unbuffered_write, "UNB-WRITE") \ EM(netfs_write_trace_writeback, "WRITEBACK") \ - EM(netfs_write_trace_writeback_single, "WB-SINGLE") \ - E_(netfs_write_trace_writethrough, "WRITETHRU") + E_(netfs_write_trace_writeback_single, "WB-SINGLE") #define netfs_rreq_origins \ EM(NETFS_READAHEAD, "RA") \ @@ -43,7 +42,6 @@ EM(NETFS_DIO_READ, "DR") \ EM(NETFS_WRITEBACK, "WB") \ EM(NETFS_WRITEBACK_SINGLE, "W1") \ - EM(NETFS_WRITETHROUGH, "WT") \ EM(NETFS_UNBUFFERED_WRITE, "UW") \ EM(NETFS_DIO_WRITE, "DW") \ E_(NETFS_PGPRIV2_COPY_TO_CACHE, "2C") @@ -223,9 +221,7 @@ EM(netfs_folio_trace_sched_copy, "sched-copy") \ EM(netfs_folio_trace_store, "store") \ EM(netfs_folio_trace_store_copy, "store-copy") \ - EM(netfs_folio_trace_store_plus, "store+") \ - EM(netfs_folio_trace_wthru, "wthru") \ - E_(netfs_folio_trace_wthru_plus, "wthru+") + E_(netfs_folio_trace_store_plus, "store+") #define netfs_collect_contig_traces \ EM(netfs_contig_trace_collect, "Collect") \ From f860732bbe129b0786797040156fee30161013d0 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:20:57 +0100 Subject: [PATCH 1455/2286] netfs: trace: Change the "clear" folio traces to "endwb" Change the "clear" folio traces to "endwb" as it's more obvious what it means. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-4-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/write_collect.c | 8 ++++---- include/trace/events/netfs.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 244a68e04624bc..6114bdf27ce088 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -56,7 +56,7 @@ static void netfs_dump_request(const struct netfs_io_request *rreq) */ int netfs_folio_written_back(struct folio *folio) { - enum netfs_folio_trace why = netfs_folio_trace_clear; + enum netfs_folio_trace why = netfs_folio_trace_endwb; struct inode *inode = folio_inode(folio); struct netfs_inode *ictx = netfs_inode(inode); struct netfs_folio *finfo; @@ -79,13 +79,13 @@ int netfs_folio_written_back(struct folio *folio) group = finfo->netfs_group; gcount++; kfree(finfo); - why = netfs_folio_trace_clear_s; + why = netfs_folio_trace_endwb_s; goto end_wb; } if ((group = netfs_folio_group(folio))) { if (group == NETFS_FOLIO_COPY_TO_CACHE) { - why = netfs_folio_trace_clear_cc; + why = netfs_folio_trace_endwb_cc; folio_detach_private(folio); goto end_wb; } @@ -98,7 +98,7 @@ int netfs_folio_written_back(struct folio *folio) if (!folio_test_dirty(folio)) { folio_detach_private(folio); gcount++; - why = netfs_folio_trace_clear_g; + why = netfs_folio_trace_endwb_g; } } diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index ef1185993ba45e..2491a0bc417904 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -192,11 +192,11 @@ EM(netfs_folio_trace_alloc_buffer, "alloc-buf") \ EM(netfs_folio_trace_cancel_copy, "cancel-copy") \ EM(netfs_folio_trace_cancel_store, "cancel-store") \ - EM(netfs_folio_trace_clear, "clear") \ - EM(netfs_folio_trace_clear_cc, "clear-cc") \ - EM(netfs_folio_trace_clear_g, "clear-g") \ - EM(netfs_folio_trace_clear_s, "clear-s") \ EM(netfs_folio_trace_end_copy, "end-copy") \ + EM(netfs_folio_trace_endwb, "endwb") \ + EM(netfs_folio_trace_endwb_cc, "endwb-cc") \ + EM(netfs_folio_trace_endwb_g, "endwb-g") \ + EM(netfs_folio_trace_endwb_s, "endwb-s") \ EM(netfs_folio_trace_filled_gaps, "filled-gaps") \ EM(netfs_folio_trace_invalidate_all, "inval-all") \ EM(netfs_folio_trace_invalidate_front, "inval-front") \ From f26fefffe8b54028c1d4a2975fee5374f6419095 Mon Sep 17 00:00:00 2001 From: Zenghui Yu Date: Sat, 20 Jun 2026 21:14:25 +0800 Subject: [PATCH 1456/2286] cachefiles: Fix path of the "debug" module parameter in help paragraph The correct path of the "debug" module parameter should be /sys/module/cachefiles/parameters/debug. Fix it. Signed-off-by: Zenghui Yu Link: https://patch.msgid.link/20260620131425.8210-1-zenghui.yu@linux.dev Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/cachefiles/Kconfig b/fs/cachefiles/Kconfig index afb25b6af5aa17..c9c168c7e07268 100644 --- a/fs/cachefiles/Kconfig +++ b/fs/cachefiles/Kconfig @@ -17,7 +17,7 @@ config CACHEFILES_DEBUG help This permits debugging to be dynamically enabled in the filesystem caching on files module. If this is set, the debugging output may be - enabled by setting bits in /sys/modules/cachefiles/parameter/debug or + enabled by setting bits in /sys/module/cachefiles/parameters/debug or by including a debugging specifier in /etc/cachefilesd.conf. config CACHEFILES_ERROR_INJECTION From 03b200b256855cb95c3eab75e7355eea91793643 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:20:58 +0100 Subject: [PATCH 1457/2286] netfs: trace: Rejig a couple of the tracepoints Rejig the following tracepoints: (1) Change netfs_folio to show the pfn. (2) Change netfs_collect_folio to show a folio index range rather than file position range and don't show the cleaned_to or collected_to points. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-5-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/read_collect.c | 2 +- fs/netfs/read_pgpriv2.c | 2 +- fs/netfs/write_collect.c | 2 +- include/trace/events/netfs.h | 23 ++++++++++------------- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 3f0cac1267111f..647aacb06db4fa 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -208,7 +208,7 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq, fpos = folio_pos(folio); fend = fpos + fsize; - trace_netfs_collect_folio(rreq, folio, fend, collected_to); + trace_netfs_collect_folio(rreq, folio); /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index 4d50585f69a608..bf4d9d6877d7b2 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -201,7 +201,7 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq) fend = min_t(uoff_t, fpos + flen, creq->i_size); - trace_netfs_collect_folio(creq, folio, fend, collected_to); + trace_netfs_collect_folio(creq, folio); /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 6114bdf27ce088..7194182b975cf2 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -156,7 +156,7 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq, fend = min_t(uoff_t, fpos + flen, wreq->i_size); - trace_netfs_collect_folio(wreq, folio, fend, collected_to); + trace_netfs_collect_folio(wreq, folio); /* Unlock any folio we've transferred all of. */ if (collected_to < fend) diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 2491a0bc417904..21a661cd1ca472 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -497,6 +497,7 @@ TRACE_EVENT(netfs_folio, TP_STRUCT__entry( __field(u64, ino) __field(pgoff_t, index) + __field(unsigned long, pfn) __field(unsigned int, nr) __field(enum netfs_folio_trace, why) ), @@ -507,9 +508,11 @@ TRACE_EVENT(netfs_folio, __entry->why = why; __entry->index = folio->index; __entry->nr = folio_nr_pages(folio); + __entry->pfn = folio_pfn(folio); ), - TP_printk("i=%05llx ix=%05lx-%05lx %s", + TP_printk("p=%lx i=%05llx ix=%05lx-%05lx %s", + __entry->pfn, __entry->ino, __entry->index, __entry->index + __entry->nr - 1, __print_symbolic(__entry->why, netfs_folio_traces)) ); @@ -656,31 +659,25 @@ TRACE_EVENT(netfs_collect_sreq, TRACE_EVENT(netfs_collect_folio, TP_PROTO(const struct netfs_io_request *wreq, - const struct folio *folio, - uoff_t fend, uoff_t collected_to), + const struct folio *folio), - TP_ARGS(wreq, folio, fend, collected_to), + TP_ARGS(wreq, folio), TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned long, index) - __field(uoff_t, fend) - __field(uoff_t, cleaned_to) - __field(uoff_t, collected_to) + __field(unsigned int, nr) ), TP_fast_assign( __entry->wreq = wreq->debug_id; __entry->index = folio->index; - __entry->fend = fend; - __entry->cleaned_to = wreq->cleaned_to; - __entry->collected_to = collected_to; + __entry->nr = folio_nr_pages(folio); ), - TP_printk("R=%08x ix=%05lx r=%llx-%llx t=%llx/%llx", + TP_printk("R=%08x ix=%05lx-%05lx", __entry->wreq, __entry->index, - (uoff_t)__entry->index * PAGE_SIZE, __entry->fend, - __entry->cleaned_to, __entry->collected_to) + __entry->index + __entry->nr - 1) ); TRACE_EVENT(netfs_collect_state, From 634696be11359b74cb2a6ca285e5662f319e8346 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:20:59 +0100 Subject: [PATCH 1458/2286] netfs: Add the cache object ID to netfs_read/write tracepoints Add the cache object debug ID to netfs_read/write tracepoints to make debugging easier as there's now a direct cross-reference with the cachefiles tracepoints that only log that debug ID. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-6-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/io.c | 1 + fs/netfs/fscache_io.c | 2 +- include/linux/netfs.h | 3 ++- include/trace/events/netfs.h | 27 +++++++++++++++------------ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 7de8069d15b6b5..e61e885784d695 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -721,6 +721,7 @@ bool cachefiles_begin_operation(struct netfs_cache_resources *cres, if (!cachefiles_cres_file(cres)) { cres->ops = &cachefiles_netfs_cache_ops; + cres->object_id = object->debug_id; if (object->file) { spin_lock(&object->lock); if (!cres->cache_priv2 && object->file) diff --git a/fs/netfs/fscache_io.c b/fs/netfs/fscache_io.c index 8bca63721eeb03..056a2bae5d99a3 100644 --- a/fs/netfs/fscache_io.c +++ b/fs/netfs/fscache_io.c @@ -79,7 +79,7 @@ static int fscache_begin_operation(struct netfs_cache_resources *cres, cres->ops = NULL; cres->cache_priv = cookie; cres->cache_priv2 = NULL; - cres->debug_id = cookie->debug_id; + cres->cookie_id = cookie->debug_id; cres->inval_counter = cookie->inval_counter; if (!fscache_begin_cookie_access(cookie, why)) { diff --git a/include/linux/netfs.h b/include/linux/netfs.h index 0fb4884daf6d59..c574aaa7de9452 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -161,7 +161,8 @@ struct netfs_cache_resources { const struct netfs_cache_ops *ops; void *cache_priv; void *cache_priv2; - unsigned int debug_id; /* Cookie debug ID */ + unsigned int cookie_id; /* Cache cookie debug ID */ + unsigned int object_id; /* Cache object debug ID */ unsigned int inval_counter; /* object->inval_counter at begin_op */ }; diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 21a661cd1ca472..c9ec4da97f0bdd 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -305,6 +305,7 @@ TRACE_EVENT(netfs_read, TP_STRUCT__entry( __field(unsigned int, rreq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(uoff_t, i_size) __field(uoff_t, start) __field(size_t, len) @@ -314,7 +315,8 @@ TRACE_EVENT(netfs_read, TP_fast_assign( __entry->rreq = rreq->debug_id; - __entry->cookie = rreq->cache_resources.debug_id; + __entry->cookie = rreq->cache_resources.cookie_id; + __entry->object = rreq->cache_resources.object_id; __entry->i_size = rreq->i_size; __entry->start = start; __entry->len = len; @@ -322,10 +324,10 @@ TRACE_EVENT(netfs_read, __entry->netfs_inode = rreq->inode->i_ino; ), - TP_printk("R=%08x %s c=%08x ni=%llx s=%llx l=%zx sz=%llx", + TP_printk("R=%08x %s c=%08x o=%08x ni=%llx s=%llx l=%zx sz=%llx", __entry->rreq, __print_symbolic(__entry->what, netfs_read_traces), - __entry->cookie, + __entry->cookie, __entry->object, __entry->netfs_inode, __entry->start, __entry->len, __entry->i_size) ); @@ -549,6 +551,7 @@ TRACE_EVENT(netfs_write, TP_STRUCT__entry( __field(unsigned int, wreq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(unsigned int, ino) __field(enum netfs_write_trace, what) __field(uoff_t, start) @@ -556,20 +559,19 @@ TRACE_EVENT(netfs_write, ), TP_fast_assign( - struct netfs_inode *__ctx = netfs_inode(wreq->inode); - struct fscache_cookie *__cookie = netfs_i_cookie(__ctx); __entry->wreq = wreq->debug_id; - __entry->cookie = __cookie ? __cookie->debug_id : 0; + __entry->cookie = wreq->cache_resources.cookie_id; + __entry->object = wreq->cache_resources.object_id; __entry->ino = wreq->inode->i_ino; __entry->what = what; __entry->start = wreq->start; __entry->len = wreq->len; ), - TP_printk("R=%08x %s c=%08x i=%x by=%llx-%llx", + TP_printk("R=%08x %s c=%08x o=%08x i=%x by=%llx-%llx", __entry->wreq, __print_symbolic(__entry->what, netfs_write_traces), - __entry->cookie, + __entry->cookie, __entry->object, __entry->ino, __entry->start, __entry->start + __entry->len - 1) ); @@ -584,22 +586,23 @@ TRACE_EVENT(netfs_copy2cache, __field(unsigned int, rreq) __field(unsigned int, creq) __field(unsigned int, cookie) + __field(unsigned int, object) __field(unsigned int, ino) ), TP_fast_assign( - struct netfs_inode *__ctx = netfs_inode(rreq->inode); - struct fscache_cookie *__cookie = netfs_i_cookie(__ctx); __entry->rreq = rreq->debug_id; __entry->creq = creq->debug_id; - __entry->cookie = __cookie ? __cookie->debug_id : 0; + __entry->cookie = rreq->cache_resources.cookie_id; + __entry->object = rreq->cache_resources.object_id; __entry->ino = rreq->inode->i_ino; ), - TP_printk("R=%08x CR=%08x c=%08x i=%x ", + TP_printk("R=%08x CR=%08x c=%08x o=%08x i=%x ", __entry->rreq, __entry->creq, __entry->cookie, + __entry->object, __entry->ino) ); From 83e8bcde6da6861c20d3173e2be03f5049822371 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:21:00 +0100 Subject: [PATCH 1459/2286] netfs: Make deprecated PG_private_2 support opt-in Make the deprecated PG_private_2 support opt-in, requiring it to be selected by the filesystems that might want to use it. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-7-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: Trond Myklebust cc: Anna Schumaker cc: Ilya Dryomov cc: Alex Markuze cc: Viacheslav Dubeyko cc: netfs@lists.linux.dev cc: linux-nfs@vger.kernel.org cc: ceph-devel@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/ceph/Kconfig | 1 + fs/netfs/Kconfig | 3 +++ fs/netfs/Makefile | 2 +- fs/netfs/buffered_read.c | 2 +- fs/netfs/internal.h | 28 ++++++++++++++++++++++++++++ fs/netfs/read_collect.c | 4 ++-- fs/nfs/Kconfig | 1 + include/linux/netfs.h | 2 ++ 8 files changed, 39 insertions(+), 4 deletions(-) diff --git a/fs/ceph/Kconfig b/fs/ceph/Kconfig index 3d64a316ca31d3..aa6ccd7794d233 100644 --- a/fs/ceph/Kconfig +++ b/fs/ceph/Kconfig @@ -4,6 +4,7 @@ config CEPH_FS depends on INET select CEPH_LIB select NETFS_SUPPORT + select NETFS_PGPRIV2 select FS_ENCRYPTION_ALGS if FS_ENCRYPTION default n help diff --git a/fs/netfs/Kconfig b/fs/netfs/Kconfig index 7701c037c3283f..d0e7b0971fa3f0 100644 --- a/fs/netfs/Kconfig +++ b/fs/netfs/Kconfig @@ -22,6 +22,9 @@ config NETFS_STATS between CPUs. On the other hand, the stats are very useful for debugging purposes. Saying 'Y' here is recommended. +config NETFS_PGPRIV2 + bool + config NETFS_DEBUG bool "Enable dynamic debugging netfslib and FS-Cache" depends on NETFS_SUPPORT diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile index b43188d64bd87e..54834cde7e5686 100644 --- a/fs/netfs/Makefile +++ b/fs/netfs/Makefile @@ -11,7 +11,6 @@ netfs-y := \ misc.o \ objects.o \ read_collect.o \ - read_pgpriv2.o \ read_retry.o \ read_single.o \ rolling_buffer.o \ @@ -19,6 +18,7 @@ netfs-y := \ write_issue.o \ write_retry.o +netfs-$(CONFIG_NETFS_PGPRIV2) += read_pgpriv2.o netfs-$(CONFIG_NETFS_STATS) += stats.o netfs-$(CONFIG_FSCACHE) += \ diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 628e31f56cf1cd..63f9bb70022c76 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -242,7 +242,7 @@ static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq, if (overlap > 0 && copy) { folio = folioq_folio(*fq, *slot); - if (unlikely(test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags))) { + if (netfs_using_pgpriv2(rreq)) { if (!folio_test_private_2(folio)) folio_start_private_2(folio); } else { diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 0f3c7250ae99fc..5b8e75d8980a4e 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -122,9 +122,37 @@ void netfs_cache_read_terminated(void *priv, ssize_t transferred_or_error); /* * read_pgpriv2.c */ +#ifdef CONFIG_NETFS_PGPRIV2 +int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs); void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio); void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq); bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *wreq); +static inline bool netfs_using_pgpriv2(const struct netfs_io_request *rreq) +{ + return unlikely(test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)); +} +#else +static inline int netfs_prepare_pgpriv2_write_buffer(struct netfs_io_subrequest *subreq, + unsigned int max_segs) +{ + return -EIO; +} +static inline void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) +{ +} +static inline void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq) +{ +} +static inline bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *wreq) +{ + return true; +} +static inline bool netfs_using_pgpriv2(const struct netfs_io_request *rreq) +{ + return false; +} +#endif /* * read_retry.c diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 647aacb06db4fa..7ba020135236ec 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -54,7 +54,7 @@ static void netfs_clear_unread_dio(struct netfs_io_subrequest *subreq) */ void netfs_cancel_copy_to_cache(struct netfs_io_request *rreq, struct folio *folio) { - if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (!netfs_using_pgpriv2(rreq)) { if (folio_get_private(folio) == NETFS_FOLIO_COPY_TO_CACHE) { folio_detach_private(folio); trace_netfs_folio(folio, netfs_folio_trace_cancel_copy); @@ -97,7 +97,7 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq, if (unlikely(test_bit(NETFS_RREQ_CANCEL_CACHING, &rreq->flags))) netfs_cancel_copy_to_cache(rreq, folio); - if (!test_bit(NETFS_RREQ_USE_PGPRIV2, &rreq->flags)) { + if (!netfs_using_pgpriv2(rreq)) { if (netfs_folio_group(folio) == NETFS_FOLIO_COPY_TO_CACHE) { trace_netfs_folio(folio, netfs_folio_trace_sched_copy); folio_mark_dirty(folio); diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index 6bb30543eff00f..e7862f35b72c8c 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -174,6 +174,7 @@ config NFS_FSCACHE bool "Provide NFS client caching support" depends on NFS_FS select NETFS_SUPPORT + select NETFS_PGPRIV2 select FSCACHE help Say Y here if you want NFS data to be cached locally on disc through diff --git a/include/linux/netfs.h b/include/linux/netfs.h index c574aaa7de9452..262a6348f88848 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -279,8 +279,10 @@ struct netfs_io_request { #define NETFS_RREQ_UPLOAD_TO_SERVER 11 /* Need to write to the server */ #define NETFS_RREQ_USE_IO_ITER 12 /* Use ->io_iter rather than ->i_pages */ #define NETFS_RREQ_NEED_PUT_RA_REFS 17 /* Need to put the folio refs RA gave us */ +#ifdef CONFIG_NETFS_PGPRIV2 #define NETFS_RREQ_USE_PGPRIV2 31 /* [DEPRECATED] Use PG_private_2 to mark * write to cache on read */ +#endif const struct netfs_request_ops *netfs_ops; }; From 90ce99b47cebbf345865fceef138489afeaac732 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:21:01 +0100 Subject: [PATCH 1460/2286] netfs: Add some functions to wrap the all-queued handling Add some helper functions to wrap the handling of the NETFS_RREQ_ALL_QUEUED flag and to insert the appropriate barriers. Also add an smb_mb__after_atomic() after the set_bit() to make sure stuff after the set_bit() in the same thread doesn't get ordered before. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-8-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 9 +++------ fs/netfs/direct_read.c | 9 +++------ fs/netfs/internal.h | 20 ++++++++++++++++++++ fs/netfs/misc.c | 2 +- fs/netfs/read_collect.c | 4 +--- fs/netfs/read_pgpriv2.c | 3 +-- fs/netfs/read_single.c | 9 +++------ fs/netfs/write_collect.c | 3 +-- fs/netfs/write_issue.c | 6 ++---- include/trace/events/netfs.h | 1 + 10 files changed, 36 insertions(+), 30 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 63f9bb70022c76..08852913a736b7 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -355,10 +355,8 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) } start += slice; size -= slice; - if (size <= 0) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); - } + if (size <= 0) + netfs_all_subreqs_queued(rreq); if (fq) { /* See if the cache indicated this should be cached. */ @@ -378,8 +376,7 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) } while (size > 0); if (unlikely(size > 0)) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index aa10af5171a860..5405e108b7a33e 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -84,10 +84,8 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) size -= slice; start += slice; rreq->submitted += slice; - if (size <= 0) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); - } + if (size <= 0) + netfs_all_subreqs_queued(rreq); rreq->netfs_ops->issue_read(subreq); @@ -99,8 +97,7 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) } while (size > 0); if (unlikely(size > 0)) { - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); } } diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index 5b8e75d8980a4e..c50cf2997e41c4 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -343,6 +343,26 @@ static inline bool netfs_check_subreq_in_progress(const struct netfs_io_subreque return test_bit_acquire(NETFS_SREQ_IN_PROGRESS, &subreq->flags); } +/* + * Indicate that we've generated and queued all the subrequests we're going to. + */ +static inline void netfs_all_subreqs_queued(struct netfs_io_request *rreq) +{ + smp_wmb(); /* Write lists before ALL_QUEUED. */ + set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + smp_mb__after_atomic(); + trace_netfs_rreq(rreq, netfs_rreq_trace_all_queued); +} + +/* + * Query if all subrequests are queued. + */ +static inline bool netfs_are_all_subreqs_queued(const struct netfs_io_request *rreq) +{ + /* Read lists after ALL_QUEUED. */ + return test_bit_acquire(NETFS_RREQ_ALL_QUEUED, &rreq->flags); +} + /* * fscache-cache.c */ diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c index 073257e15e4b81..5465db834b9499 100644 --- a/fs/netfs/misc.c +++ b/fs/netfs/misc.c @@ -425,7 +425,7 @@ static int netfs_collect_in_app(struct netfs_io_request *rreq, need_collect = true; break; } - if (subreq || !test_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags)) + if (subreq || !netfs_are_all_subreqs_queued(rreq)) done = false; } diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c index 7ba020135236ec..be0f9d2f7e10f5 100644 --- a/fs/netfs/read_collect.c +++ b/fs/netfs/read_collect.c @@ -491,10 +491,8 @@ bool netfs_read_collection(struct netfs_io_request *rreq) /* We're done when the app thread has finished posting subreqs and the * queue is empty. */ - if (!test_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags)) + if (!netfs_are_all_subreqs_queued(rreq)) return false; - smp_rmb(); /* Read ALL_QUEUED before subreq lists. */ - if (!list_empty(&stream->subrequests)) return false; diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c index bf4d9d6877d7b2..5280b606fda410 100644 --- a/fs/netfs/read_pgpriv2.c +++ b/fs/netfs/read_pgpriv2.c @@ -158,8 +158,7 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq) return; netfs_issue_write(creq, &creq->io_streams[1]); - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &creq->flags); + netfs_all_subreqs_queued(creq); trace_netfs_rreq(rreq, netfs_rreq_trace_end_copy_to_cache); if (list_empty_careful(&creq->io_streams[1].subrequests)) netfs_wake_collector(creq); diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index de67ac41548d1a..ccb5fc809d993f 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -113,14 +113,12 @@ static int netfs_single_dispatch_read(struct netfs_io_request *rreq) goto cancel; } - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); rreq->netfs_ops->issue_read(subreq); rreq->submitted += subreq->len; break; case NETFS_READ_FROM_CACHE: - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); trace_netfs_sreq(subreq, netfs_sreq_trace_submit); netfs_single_read_cache(rreq, subreq); rreq->submitted += subreq->len; @@ -136,8 +134,7 @@ static int netfs_single_dispatch_read(struct netfs_io_request *rreq) return ret; cancel: netfs_cancel_read(subreq, ret); - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &rreq->flags); + netfs_all_subreqs_queued(rreq); netfs_wake_collector(rreq); return ret; } diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c index 7194182b975cf2..6d99d4a6f7808c 100644 --- a/fs/netfs/write_collect.c +++ b/fs/netfs/write_collect.c @@ -371,9 +371,8 @@ bool netfs_write_collection(struct netfs_io_request *wreq) /* We're done when the app thread has finished posting subreqs and all * the queues in all the streams are empty. */ - if (!test_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags)) + if (!netfs_are_all_subreqs_queued(wreq)) return false; - smp_rmb(); /* Read ALL_QUEUED before lists. */ transferred = LONG_MAX; for (s = 0; s < NR_IO_STREAMS; s++) { diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 5d130df2ff0d06..9a528da4bf9fe5 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -520,8 +520,7 @@ static void netfs_end_issue_write(struct netfs_io_request *wreq) { bool needs_poke = true; - smp_wmb(); /* Write subreq lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags); + netfs_all_subreqs_queued(wreq); for (int s = 0; s < NR_IO_STREAMS; s++) { struct netfs_io_stream *stream = &wreq->io_streams[s]; @@ -789,8 +788,7 @@ int netfs_writeback_single(struct address_space *mapping, stop: for (int s = 0; s < NR_IO_STREAMS; s++) netfs_issue_write(wreq, &wreq->io_streams[s]); - smp_wmb(); /* Write lists before ALL_QUEUED. */ - set_bit(NETFS_RREQ_ALL_QUEUED, &wreq->flags); + netfs_all_subreqs_queued(wreq); netfs_wake_collector(wreq); diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index c9ec4da97f0bdd..303309be253f2a 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -47,6 +47,7 @@ E_(NETFS_PGPRIV2_COPY_TO_CACHE, "2C") #define netfs_rreq_traces \ + EM(netfs_rreq_trace_all_queued, "ALL-Q ") \ EM(netfs_rreq_trace_assess, "ASSESS ") \ EM(netfs_rreq_trace_collect, "COLLECT") \ EM(netfs_rreq_trace_complete, "COMPLET") \ From a05dd3341d7bbf62c6a61a194f2ed739ae5dc528 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 9 Sep 2026 08:21:02 +0100 Subject: [PATCH 1461/2286] netfs: Set subrequest->source at alloc before trace emission Set subrequest->source in netfs_alloc_subrequest() before we emit the trace line indicating we allocated the subrequest. Note that this requires the allocation of the subreq in netfs_read_to_pagecache() to be pushed to after the decision about what sort of subreq it should be. Signed-off-by: David Howells Link: https://patch.msgid.link/20260909072105.1663687-9-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org Signed-off-by: Christian Brauner (Amutable) --- fs/netfs/buffered_read.c | 4 ++-- fs/netfs/direct_read.c | 3 +-- fs/netfs/internal.h | 3 ++- fs/netfs/objects.c | 4 +++- fs/netfs/read_retry.c | 3 +-- fs/netfs/read_single.c | 3 +-- fs/netfs/write_issue.c | 3 +-- fs/netfs/write_retry.c | 3 +-- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c index 08852913a736b7..3640cb9a62f33a 100644 --- a/fs/netfs/buffered_read.c +++ b/fs/netfs/buffered_read.c @@ -276,10 +276,10 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq) do { struct netfs_io_subrequest *subreq; - enum netfs_io_source source = NETFS_SOURCE_UNKNOWN; + enum netfs_io_source source; ssize_t slice; - subreq = netfs_alloc_subrequest(rreq); + subreq = netfs_alloc_subrequest(rreq, NETFS_SOURCE_UNKNOWN); if (!subreq) { ret = -ENOMEM; break; diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c index 5405e108b7a33e..8c15f307972383 100644 --- a/fs/netfs/direct_read.c +++ b/fs/netfs/direct_read.c @@ -55,7 +55,7 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) struct netfs_io_subrequest *subreq; ssize_t slice; - subreq = netfs_alloc_subrequest(rreq); + subreq = netfs_alloc_subrequest(rreq, NETFS_DOWNLOAD_FROM_SERVER); if (!subreq) { /* Stash the error in the request if there's not * already an error set. @@ -64,7 +64,6 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq) break; } - subreq->source = NETFS_DOWNLOAD_FROM_SERVER; subreq->start = start; subreq->len = size; diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h index c50cf2997e41c4..fa2b214f90cf2c 100644 --- a/fs/netfs/internal.h +++ b/fs/netfs/internal.h @@ -94,7 +94,8 @@ void netfs_get_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace void netfs_clear_subrequests(struct netfs_io_request *rreq); void netfs_put_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what); void netfs_put_failed_request(struct netfs_io_request *rreq); -struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq); +struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq, + enum netfs_io_source source); static inline void netfs_see_request(struct netfs_io_request *rreq, enum netfs_rreq_ref_trace what) diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c index fcedd00fa86a2d..b0d06d31ce8d06 100644 --- a/fs/netfs/objects.c +++ b/fs/netfs/objects.c @@ -207,7 +207,8 @@ void netfs_put_failed_request(struct netfs_io_request *rreq) /* * Allocate and partially initialise an I/O request structure. */ -struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq) +struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq, + enum netfs_io_source source) { struct netfs_io_subrequest *subreq; mempool_t *mempool = rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool; @@ -224,6 +225,7 @@ struct netfs_io_subrequest *netfs_alloc_subrequest(struct netfs_io_request *rreq INIT_WORK(&subreq->work, NULL); INIT_LIST_HEAD(&subreq->rreq_link); refcount_set(&subreq->ref, 2); + subreq->source = source; subreq->rreq = rreq; subreq->debug_index = atomic_inc_return(&rreq->subreq_counter); netfs_get_request(rreq, netfs_rreq_trace_get_subreq); diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c index 46810d29f0c0e3..396a05432a7e92 100644 --- a/fs/netfs/read_retry.c +++ b/fs/netfs/read_retry.c @@ -195,12 +195,11 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq) * and insert them after. */ do { - subreq = netfs_alloc_subrequest(rreq); + subreq = netfs_alloc_subrequest(rreq, NETFS_DOWNLOAD_FROM_SERVER); if (!subreq) { subreq = to; goto abandon_after; } - subreq->source = NETFS_DOWNLOAD_FROM_SERVER; subreq->start = start; subreq->len = len; subreq->stream_nr = stream->stream_nr; diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c index ccb5fc809d993f..81295c055cedbc 100644 --- a/fs/netfs/read_single.c +++ b/fs/netfs/read_single.c @@ -92,11 +92,10 @@ static int netfs_single_dispatch_read(struct netfs_io_request *rreq) struct netfs_io_subrequest *subreq; int ret = 0; - subreq = netfs_alloc_subrequest(rreq); + subreq = netfs_alloc_subrequest(rreq, NETFS_SOURCE_UNKNOWN); if (!subreq) return -ENOMEM; - subreq->source = NETFS_SOURCE_UNKNOWN; subreq->start = 0; subreq->len = rreq->len; subreq->io_iter = rreq->buffer.iter; diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c index 9a528da4bf9fe5..e7cb496f63246a 100644 --- a/fs/netfs/write_issue.c +++ b/fs/netfs/write_issue.c @@ -168,10 +168,9 @@ void netfs_prepare_write(struct netfs_io_request *wreq, wreq_iter->folioq_slot >= folioq_nr_slots(wreq_iter->folioq)) rolling_buffer_make_space(&wreq->buffer, wreq->gfp); - subreq = netfs_alloc_subrequest(wreq); + subreq = netfs_alloc_subrequest(wreq, stream->source); if (!subreq) return; - subreq->source = stream->source; subreq->start = start; subreq->stream_nr = stream->stream_nr; subreq->io_iter = *wreq_iter; diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c index 6cd584242af282..d7d5348496fc8d 100644 --- a/fs/netfs/write_retry.c +++ b/fs/netfs/write_retry.c @@ -149,8 +149,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq, * and insert them after. */ do { - subreq = netfs_alloc_subrequest(wreq); - subreq->source = to->source; + subreq = netfs_alloc_subrequest(wreq, stream->source); subreq->start = start; subreq->stream_nr = to->stream_nr; subreq->retry_count = 1; From c8774176db7458bd562a9c2ecdb7e079cf33d7fb Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 10 Sep 2026 23:02:36 +0100 Subject: [PATCH 1462/2286] cachefiles: Clean up cachefiles_do_prepare_read() Clean up cachefiles_do_prepare_read() by merging it into cachefiles_prepare_read() now that the ondemand mode stuff has been removed. Signed-off-by: David Howells Link: https://patch.msgid.link/20260910220242.2165023-2-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: Marc Dionne cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/io.c | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index e61e885784d695..2c1cd703f4bf91 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -375,19 +375,23 @@ static int cachefiles_write(struct netfs_cache_resources *cres, term_func, term_func_priv); } -static inline enum netfs_io_source -cachefiles_do_prepare_read(struct netfs_cache_resources *cres, - uoff_t start, size_t *_len, loff_t i_size, - unsigned long *_flags, ino_t netfs_ino) +/* + * Prepare a read operation, shortening it to a cached/uncached boundary as + * appropriate. + */ +static enum netfs_io_source +cachefiles_prepare_read(struct netfs_io_subrequest *subreq, uoff_t i_size) { enum cachefiles_prepare_read_trace why; + struct netfs_cache_resources *cres = &subreq->rreq->cache_resources; struct cachefiles_object *object = NULL; struct cachefiles_cache *cache; struct fscache_cookie *cookie = fscache_cres_cookie(cres); const struct cred *saved_cred; struct file *file = cachefiles_cres_file(cres); enum netfs_io_source ret = NETFS_DOWNLOAD_FROM_SERVER; - size_t len = *_len; + uoff_t start = subreq->start; + size_t len = subreq->len; loff_t off, to; ino_t ino = file ? file_inode(file)->i_ino : 0; @@ -400,7 +404,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, } if (test_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags)) { - __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); + __set_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); why = cachefiles_trace_read_no_data; goto out_no_object; } @@ -441,7 +445,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, if (off > start) { off = round_up(off, cache->bsize); len = off - start; - *_len = len; + subreq->len = len; why = cachefiles_trace_read_found_part; goto download_and_store; } @@ -462,7 +466,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, else to = round_down(to, cache->bsize); len = to - start; - *_len = len; + subreq->len = len; } why = cachefiles_trace_read_have_data; @@ -470,26 +474,15 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, goto out; download_and_store: - __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); + __set_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags); out: cachefiles_end_secure(cache, saved_cred); out_no_object: - trace_cachefiles_prep_read(object, start, len, *_flags, ret, why, ino, netfs_ino); + trace_cachefiles_prep_read(object, start, len, subreq->flags, ret, why, + ino, subreq->rreq->inode->i_ino); return ret; } -/* - * Prepare a read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ -static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subrequest *subreq, - uoff_t i_size) -{ - return cachefiles_do_prepare_read(&subreq->rreq->cache_resources, - subreq->start, &subreq->len, i_size, - &subreq->flags, subreq->rreq->inode->i_ino); -} - /* * Prepare for a write to occur. */ From a22e13e346f0c389c22279dd5346778ab6b9845f Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 10 Sep 2026 23:02:37 +0100 Subject: [PATCH 1463/2286] netfs, cachefiles: Add a couple of traces for write failure Add a couple of subrequest traces for write failure in the cache. Signed-off-by: David Howells Link: https://patch.msgid.link/20260910220242.2165023-3-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: Marc Dionne cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/io.c | 8 ++++++-- include/trace/events/netfs.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index 2c1cd703f4bf91..dac48fdf85d13c 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -605,10 +605,14 @@ static void cachefiles_prepare_write_subreq(struct netfs_io_subrequest *subreq) stream->sreq_max_segs = BIO_MAX_VECS; if (!cachefiles_cres_file(cres)) { - if (!fscache_wait_for_operation(cres, FSCACHE_WANT_WRITE)) + if (!fscache_wait_for_operation(cres, FSCACHE_WANT_WRITE)) { + trace_netfs_sreq(subreq, netfs_sreq_trace_cache_waitfail); return netfs_prepare_write_failed(subreq); - if (!cachefiles_cres_file(cres)) + } + if (!cachefiles_cres_file(cres)) { + trace_netfs_sreq(subreq, netfs_sreq_trace_cache_nofile); return netfs_prepare_write_failed(subreq); + } } } diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h index 303309be253f2a..35826aea1c2ddf 100644 --- a/include/trace/events/netfs.h +++ b/include/trace/events/netfs.h @@ -93,8 +93,10 @@ EM(netfs_sreq_trace_abandoned, "ABNDN") \ EM(netfs_sreq_trace_add_donations, "+DON ") \ EM(netfs_sreq_trace_added, "ADD ") \ + EM(netfs_sreq_trace_cache_nofile, "CA-!F") \ EM(netfs_sreq_trace_cache_nowrite, "CA-NW") \ EM(netfs_sreq_trace_cache_prepare, "CA-PR") \ + EM(netfs_sreq_trace_cache_waitfail, "CA-!W") \ EM(netfs_sreq_trace_cache_write, "CA-WR") \ EM(netfs_sreq_trace_cancel, "CANCL") \ EM(netfs_sreq_trace_clear, "CLEAR") \ From 98e0cd700d23cd6b0a09b71f4a39830dbb575a87 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 10 Sep 2026 23:02:38 +0100 Subject: [PATCH 1464/2286] cachefiles: Add a tracepoint to log insufficient space errors Add a tracepoint to log insufficient space errors. Signed-off-by: David Howells Link: https://patch.msgid.link/20260910220242.2165023-4-dhowells@redhat.com Reviewed-by: Paulo Alcantara cc: Marc Dionne cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/cachefiles/io.c | 15 +++++++++++---- fs/cachefiles/namei.c | 10 ++++++++-- include/trace/events/cachefiles.h | 30 +++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index dac48fdf85d13c..4b3ceda17426cf 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -533,10 +533,14 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, * space, we need to see if it's fully allocated. If it's not, we may * want to cull it. */ - if (cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, - cachefiles_has_space_check) == 0) + ret = cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, + cachefiles_has_space_check); + if (ret == 0) return 0; /* Enough space to simply overwrite the whole block */ + if (ret == -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_write_nospace_2); + pos = cachefiles_inject_read_error(); if (pos == 0) pos = vfs_llseek(file, start, SEEK_HOLE); @@ -565,8 +569,11 @@ int __cachefiles_prepare_write(struct cachefiles_object *object, return ret; check_space: - return cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, - cachefiles_has_space_for_write); + ret = cachefiles_has_space(cache, 0, *_len / PAGE_SIZE, + cachefiles_has_space_for_write); + if (ret == -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_write_nospace); + return ret; } static int cachefiles_prepare_write(struct netfs_cache_resources *cres, diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 88955249a1a64b..4780ce6dc8309b 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -117,8 +117,11 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache, if (d_is_negative(subdir)) { ret = cachefiles_has_space(cache, 1, 0, cachefiles_has_space_for_create); - if (ret < 0) + if (ret < 0) { + if (ret == -ENOBUFS) + trace_cachefiles_no_space(NULL, cachefiles_trace_mkdir_nospace); goto mkdir_error; + } _debug("attempt mkdir"); @@ -487,8 +490,11 @@ static bool cachefiles_create_file(struct cachefiles_object *object) ret = cachefiles_has_space(object->volume->cache, 1, 0, cachefiles_has_space_for_create); - if (ret < 0) + if (ret < 0) { + if (ret == -ENOBUFS) + trace_cachefiles_no_space(object, cachefiles_trace_create_nospace); return false; + } file = cachefiles_create_tmpfile(object); if (IS_ERR(file)) diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index 1938d51a945940..cd865c265c2451 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -80,11 +80,13 @@ enum cachefiles_prepare_read_trace { }; enum cachefiles_error_trace { + cachefiles_trace_create_nospace, cachefiles_trace_fallocate_error, cachefiles_trace_getxattr_error, cachefiles_trace_link_error, cachefiles_trace_lookup_error, cachefiles_trace_mkdir_error, + cachefiles_trace_mkdir_nospace, cachefiles_trace_notify_change_error, cachefiles_trace_open_error, cachefiles_trace_read_error, @@ -97,6 +99,8 @@ enum cachefiles_error_trace { cachefiles_trace_trunc_error, cachefiles_trace_unlink_error, cachefiles_trace_write_error, + cachefiles_trace_write_nospace, + cachefiles_trace_write_nospace_2, }; #endif @@ -161,11 +165,13 @@ enum cachefiles_error_trace { E_(cachefiles_trace_read_seek_nxio, "seek-enxio") #define cachefiles_error_traces \ + EM(cachefiles_trace_create_nospace, "create-nospace") \ EM(cachefiles_trace_fallocate_error, "fallocate") \ EM(cachefiles_trace_getxattr_error, "getxattr") \ EM(cachefiles_trace_link_error, "link") \ EM(cachefiles_trace_lookup_error, "lookup") \ EM(cachefiles_trace_mkdir_error, "mkdir") \ + EM(cachefiles_trace_mkdir_nospace, "mkdir-nospace") \ EM(cachefiles_trace_notify_change_error, "notify_change") \ EM(cachefiles_trace_open_error, "open") \ EM(cachefiles_trace_read_error, "read") \ @@ -177,7 +183,9 @@ enum cachefiles_error_trace { EM(cachefiles_trace_tmpfile_error, "tmpfile") \ EM(cachefiles_trace_trunc_error, "trunc") \ EM(cachefiles_trace_unlink_error, "unlink") \ - E_(cachefiles_trace_write_error, "write") + EM(cachefiles_trace_write_error, "write") \ + EM(cachefiles_trace_write_nospace, "write-nospace") \ + E_(cachefiles_trace_write_nospace_2, "write-nospace-2") /* @@ -694,6 +702,26 @@ TRACE_EVENT(cachefiles_io_error, __entry->error) ); +TRACE_EVENT(cachefiles_no_space, + TP_PROTO(struct cachefiles_object *obj, enum cachefiles_error_trace trace), + + TP_ARGS(obj, trace), + + TP_STRUCT__entry( + __field(unsigned int, obj) + __field(enum cachefiles_error_trace, trace) + ), + + TP_fast_assign( + __entry->obj = obj ? obj->debug_id : 0; + __entry->trace = trace; + ), + + TP_printk("o=%08x %s", + __entry->obj, + __print_symbolic(__entry->trace, cachefiles_error_traces)) + ); + #endif /* _TRACE_CACHEFILES_H */ /* This part must be outside protection */ From 117b8bbd2b80f0d11667ceae051c5175026378cc Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 09:34:44 +0200 Subject: [PATCH 1465/2286] netfs: Document the remote size member and its accessors netfs_inode.remote_i_size was renamed to _remote_i_size and accessors were added to prevent torn accesses. The netfs library documentation still shows the old member name. Update the struct excerpt and member description. Direct readers to netfs_read_remote_i_size() and netfs_write_remote_i_size(), and state that writes require inode->i_lock. Fixes: 2c8f4742bb76 ("netfs: Fix potential for tearing in ->remote_i_size and ->zero_point") Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260912073444.51152-1-kmehltretter@gmail.com Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/netfs_library.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/filesystems/netfs_library.rst b/Documentation/filesystems/netfs_library.rst index a9de281db8ee34..215a6621f3d346 100644 --- a/Documentation/filesystems/netfs_library.rst +++ b/Documentation/filesystems/netfs_library.rst @@ -195,7 +195,7 @@ structure is defined:: struct inode inode; const struct netfs_request_ops *ops; struct fscache_cookie * cache; - loff_t remote_i_size; + loff_t _remote_i_size; unsigned long flags; ... }; @@ -229,11 +229,14 @@ filesystem: Local caching cookie, or NULL if no caching is enabled. This field does not exist if fscache is disabled. - * ``remote_i_size`` + * ``_remote_i_size`` The size of the file on the server. This differs from inode->i_size if local modifications have been made but not yet written back. + Use netfs_read_remote_i_size() and netfs_write_remote_i_size() to access + this field. Hold inode->i_lock when writing it. + * ``flags`` A set of flags, some of which the filesystem might be interested in: From a3fcb14f5673f8127fbb40ce430bb9d1ff61db35 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 1 Sep 2026 18:27:06 +0100 Subject: [PATCH 1466/2286] kselftest/arm64: Extend gcs-locking to cover cases where enable is unlocked The gcs-locking test case currently only covers locking combinations of the various flags that can be set for GCS (enable, write and push) where enable is included in the locked bits. Since it is valid to only lock a combination of write and push while leaving enable user controllable we should also cover those cases, add coverage for them. The new cases pass as expected. Signed-off-by: Mark Brown Signed-off-by: Catalin Marinas --- tools/testing/selftests/arm64/gcs/gcs-locking.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/testing/selftests/arm64/gcs/gcs-locking.c b/tools/testing/selftests/arm64/gcs/gcs-locking.c index 1e6abb136ffd07..4dd7ab4191f1ce 100644 --- a/tools/testing/selftests/arm64/gcs/gcs-locking.c +++ b/tools/testing/selftests/arm64/gcs/gcs-locking.c @@ -77,6 +77,21 @@ FIXTURE_VARIANT_ADD(valid_modes, enable_write_push) PR_SHADOW_STACK_PUSH, }; +FIXTURE_VARIANT_ADD(valid_modes, write) +{ + .mode = PR_SHADOW_STACK_WRITE, +}; + +FIXTURE_VARIANT_ADD(valid_modes, push) +{ + .mode = PR_SHADOW_STACK_PUSH, +}; + +FIXTURE_VARIANT_ADD(valid_modes, write_push) +{ + .mode = PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH, +}; + FIXTURE_SETUP(valid_modes) { } From 197a7a41e6e006916fe68c952e96d768933104fe Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Sat, 3 Oct 2026 14:12:07 +0100 Subject: [PATCH 1467/2286] arm_mpam: On saving mbwu state read the monitors before clearing CFG_MBWU_CTL In mpam_save_mbwu_state() the monitor values are read using __ris_msmon_read_locked() which checks if the configuration provided matches that in the hardware and if not then resets the monitor. To avoid resetting the monitor before reading the value that needs saving only clear the configuration after the monitor is read. Fixes: d056d29aefbe ("arm_mpam: Use __ris_msmon_read() for saving MBWU state") Signed-off-by: Ben Horgan Signed-off-by: Catalin Marinas --- drivers/resctrl/mpam_devices.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/resctrl/mpam_devices.c b/drivers/resctrl/mpam_devices.c index c32175426433e8..48adc49b833cac 100644 --- a/drivers/resctrl/mpam_devices.c +++ b/drivers/resctrl/mpam_devices.c @@ -2052,9 +2052,6 @@ static int mpam_save_mbwu_state(void *arg) if (ret) return ret; ret = mpam_read_monsel_reg(msc, CFG_MBWU_CTL, &cur_ctl); - if (ret) - return ret; - ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); if (ret) return ret; @@ -2088,6 +2085,10 @@ static int mpam_save_mbwu_state(void *arg) mbwu_state->correction = val; } + ret = mpam_write_monsel_reg(msc, CFG_MBWU_CTL, 0); + if (ret) + return ret; + mpam_mon_sel_unlock(msc); } From 1cd024f3f6ae5a349f06c51161e80101c8329610 Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Tue, 22 Sep 2026 11:59:58 +0100 Subject: [PATCH 1468/2286] kselftest/arm64/mte: Introduce MTE safe memory accessors The MTE selftests assume that they can skip (expected) MTE faults by advancing the PC by 4 in mte_default_handler(), but this is not generally safe, as the faults are triggered from arbitrary library functions (e.g. memset() and memcpy()). For instance, memset() could be implemented as simple as: mov x3, x0 // copy pointer add x4, x0, x2 // calculate end loop: strb w1, [x3], #1 // faulting access cmp x3, x4 b.ne loop ret which leads to an infinite loop since X3 could not be updated. Or, it could be having advanced implementation (FEAT_MOPS): mov x3, x0 // copy pointer setp [x3]!, x2!, x1 // prologue setm [x3]!, x2!, x1 // main sete [x3]!, x2!, x1 // epilogue ret with faulting access at prologue advancing PC to main could lead to infinite loop due to main could be triggering unaligned access fault which kernel fixing up by advancing PC back to prologue. Instead of relying on arbitrary implementations of library functions introduce helpers to perform the faulting accesses, where those faults can safely be handled. Signed-off-by: Vladimir Murzin Tested-by: Muhammad Usama Anjum Reviewed-by: Muhammad Usama Anjum Signed-off-by: Catalin Marinas --- .../selftests/arm64/mte/mte_common_util.h | 4 ++ .../testing/selftests/arm64/mte/mte_helper.S | 44 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tools/testing/selftests/arm64/mte/mte_common_util.h b/tools/testing/selftests/arm64/mte/mte_common_util.h index 250d671329a511..547ec5659f78ab 100644 --- a/tools/testing/selftests/arm64/mte/mte_common_util.h +++ b/tools/testing/selftests/arm64/mte/mte_common_util.h @@ -64,6 +64,10 @@ void mte_restore_setup(void); int mte_switch_mode(int mte_option, unsigned long incl_mask, bool stonly); void mte_initialize_current_context(int mode, uintptr_t ptr, ssize_t range); +/* Safe memory access functions */ +void *memset_safe(void *s, int c, size_t n); +void *memcpy_safe(void *dest, const void *src, size_t n); + /* Common utility functions */ int create_temp_file(void); diff --git a/tools/testing/selftests/arm64/mte/mte_helper.S b/tools/testing/selftests/arm64/mte/mte_helper.S index a55dbbc56ed190..eb62abfa1eb4e7 100644 --- a/tools/testing/selftests/arm64/mte/mte_helper.S +++ b/tools/testing/selftests/arm64/mte/mte_helper.S @@ -128,3 +128,47 @@ ENTRY(mte_get_pstate_tco) ubfx x0, x0, #MT_PSTATE_TCO_SHIFT, #1 ret ENDPROC(mte_get_pstate_tco) + +/* + * memset_safe: Fill memory with a constant byte + * Input: + * x0 - destination pointer + * x1 - constant byte + * x2 - number of bytes to fill + * Return: + * x0 - original destination pointer + */ +ENTRY(memset_safe) + cbz x2, 2f + add x4, x2, x0 + mov x3, x0 +1: + strb w1, [x3] + add x3, x3, #0x1 + cmp x3, x4 + b.ne 1b +2: + ret +ENDPROC(memset_safe) + +/* + * memcpy_safe: Copy memory area + * Input: + * x0 - destination pointer + * x1 - source pointer + * x2 - number of bytes to copy + * Return: + * x0 - destination pointer + */ +ENTRY(memcpy_safe) + cbz x2, 2f + mov x3, #0x0 +1: + ldrb w4, [x1, x3] + strb w4, [x0, x3] + add x3, x3, #0x1 + cmp x3, x2 + b.ne 1b +2: + ret +ENDPROC(memcpy_safe) From 9f7af3cf5cc78f880e8d2349d66b46f1341e104c Mon Sep 17 00:00:00 2001 From: Vladimir Murzin Date: Tue, 22 Sep 2026 11:59:59 +0100 Subject: [PATCH 1469/2286] kselftest/arm64/mte: Use MTE safe memory accessors Use MTE safe memory accessors instead of library functions with arbitrary implementations. Also, for consistency, use accessors even for byte size accesses. Signed-off-by: Vladimir Murzin Tested-by: Muhammad Usama Anjum Reviewed-by: Muhammad Usama Anjum Signed-off-by: Catalin Marinas --- tools/testing/selftests/arm64/mte/check_buffer_fill.c | 10 +++++----- tools/testing/selftests/arm64/mte/check_child_memory.c | 6 +++--- .../selftests/arm64/mte/check_hugetlb_options.c | 4 ++-- tools/testing/selftests/arm64/mte/check_mmap_options.c | 10 +++++----- .../testing/selftests/arm64/mte/check_tags_inclusion.c | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/arm64/mte/check_buffer_fill.c b/tools/testing/selftests/arm64/mte/check_buffer_fill.c index 039b1d7d856637..54e1dcaf52ac2f 100644 --- a/tools/testing/selftests/arm64/mte/check_buffer_fill.c +++ b/tools/testing/selftests/arm64/mte/check_buffer_fill.c @@ -41,7 +41,7 @@ static int check_buffer_by_byte(int mem_type, int mode) mte_initialize_current_context(mode, (uintptr_t)ptr, sizes[i]); /* Set some value in tagged memory */ for (j = 0; j < sizes[i]; j++) - ptr[j] = '1'; + memset_safe(&ptr[j], '1', 1); mte_wait_after_trig(); err = cur_mte_cxt.fault_valid; /* Check the buffer whether it is filled. */ @@ -82,7 +82,7 @@ static int check_buffer_underflow_by_byte(int mem_type, int mode, /* Set some value in tagged memory and make the buffer underflow */ for (j = sizes[i] - 1; (j >= -underflow_range) && (!cur_mte_cxt.fault_valid); j--) { - ptr[j] = '1'; + memset_safe(&ptr[j], '1', 1); last_index = j; } mte_wait_after_trig(); @@ -180,7 +180,7 @@ static int check_buffer_overflow_by_byte(int mem_type, int mode, /* Set some value in tagged memory and make the buffer underflow */ for (j = 0, last_index = 0 ; (j < (sizes[i] + overflow_range)) && (cur_mte_cxt.fault_valid == false); j++) { - ptr[j] = '1'; + memset_safe(&ptr[j], '1', 1); last_index = j; } mte_wait_after_trig(); @@ -308,8 +308,8 @@ static int check_buffer_by_block_iterate(int mem_type, int mode, size_t size) result = KSFT_PASS; mte_initialize_current_context(mode, (uintptr_t)dst, size); /* Set some value in memory and copy*/ - memset((void *)src, (int)'1', size); - memcpy((void *)dst, (void *)src, size); + memset_safe((void *)src, (int)'1', size); + memcpy_safe((void *)dst, (void *)src, size); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid) { result = KSFT_FAIL; diff --git a/tools/testing/selftests/arm64/mte/check_child_memory.c b/tools/testing/selftests/arm64/mte/check_child_memory.c index e6a8acca2a9411..5c62359e568256 100644 --- a/tools/testing/selftests/arm64/mte/check_child_memory.c +++ b/tools/testing/selftests/arm64/mte/check_child_memory.c @@ -41,7 +41,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode) } else if (child == 0) { mte_initialize_current_context(mode, (uintptr_t)ptr, size); /* Do copy on write */ - memset(ptr, '1', size); + memset_safe(ptr, '1', size); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) { fault = 1; @@ -56,14 +56,14 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode) } } mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW); - memset(ptr - UNDERFLOW, '2', UNDERFLOW); + memset_safe(ptr - UNDERFLOW, '2', UNDERFLOW); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == false) { fault = 1; goto check_child_tag_inheritance_err; } mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW); - memset(ptr + size, '3', OVERFLOW); + memset_safe(ptr + size, '3', OVERFLOW); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == false) { fault = 1; diff --git a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c index 23e4a7a9950c1f..85f1574b88be83 100644 --- a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c +++ b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c @@ -106,7 +106,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode) } else if (child == 0) { mte_initialize_current_context(mode, (uintptr_t)ptr, size); /* Do copy on write */ - memset(ptr, '1', size); + memset_safe(ptr, '1', size); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) { fault = 1; @@ -135,7 +135,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode) static int check_mte_memory(char *ptr, int size, int mode, int tag_check) { mte_initialize_current_context(mode, (uintptr_t)ptr, size); - memset(ptr, '1', size); + memset_safe(ptr, '1', size); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) return KSFT_FAIL; diff --git a/tools/testing/selftests/arm64/mte/check_mmap_options.c b/tools/testing/selftests/arm64/mte/check_mmap_options.c index 492f2cd41f43b7..0f4d43fe80f2c2 100644 --- a/tools/testing/selftests/arm64/mte/check_mmap_options.c +++ b/tools/testing/selftests/arm64/mte/check_mmap_options.c @@ -72,13 +72,13 @@ static int check_mte_memory(char *ptr, int size, int mode, ptr = mte_insert_atag(ptr); mte_initialize_current_context(mode, (uintptr_t)ptr, size); - memset(ptr, '1', size); + memset_safe(ptr, '1', size); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) return KSFT_FAIL; mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW); - memset(ptr - UNDERFLOW, '2', UNDERFLOW); + memset_safe(ptr - UNDERFLOW, '2', UNDERFLOW); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == false && tag_check == TAG_CHECK_ON) return KSFT_FAIL; @@ -86,7 +86,7 @@ static int check_mte_memory(char *ptr, int size, int mode, return KSFT_FAIL; mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW); - memset(ptr + size, '3', OVERFLOW); + memset_safe(ptr + size, '3', OVERFLOW); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == false && tag_check == TAG_CHECK_ON) return KSFT_FAIL; @@ -95,13 +95,13 @@ static int check_mte_memory(char *ptr, int size, int mode, if (tag_op == TAG_OP_STONLY) { mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW); - memcpy(buf, ptr - UNDERFLOW, MT_GRANULE_SIZE); + memcpy_safe(buf, ptr - UNDERFLOW, MT_GRANULE_SIZE); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) return KSFT_FAIL; mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW); - memcpy(buf, ptr + size, MT_GRANULE_SIZE); + memcpy_safe(buf, ptr + size, MT_GRANULE_SIZE); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid == true) return KSFT_FAIL; diff --git a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c index 6b4fa6705d7c1b..bc39be231ae28e 100644 --- a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c +++ b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c @@ -23,7 +23,7 @@ static int verify_mte_pointer_validity(char *ptr, int mode) { mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE); /* Check the validity of the tagged pointer */ - memset(ptr, '1', BUFFER_SIZE); + memset_safe(ptr, '1', BUFFER_SIZE); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid) { ksft_print_msg("Unexpected fault recorded for %p-%p in mode %x\n", @@ -159,7 +159,7 @@ static int check_none_included_tags(int mem_type, int mode) } mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE); /* Check the write validity of the untagged pointer */ - memset(ptr, '1', BUFFER_SIZE); + memset_safe(ptr, '1', BUFFER_SIZE); mte_wait_after_trig(); if (cur_mte_cxt.fault_valid) break; From 842c5beca0b369bb143b86d6ae63dbcd7c5f0988 Mon Sep 17 00:00:00 2001 From: Georgios Androutsopoulos Date: Tue, 8 Sep 2026 16:31:57 -0400 Subject: [PATCH 1470/2286] rust: id_pool: document panics in `find_unused_id` and `release_id` `find_unused_id()` calls `Bitmap::next_zero_bit()` and `release_id()` calls `Bitmap::clear_bit()`, both of which panic when `CONFIG_RUST_BITMAP_HARDENED` is enabled and the index is out of bounds. Neither `IdPool` method documents this. Add the missing `# Panics` sections and state the bounds requirement on `offset` and `id`. Fixes: 2cdae413cd3e ("rust: add dynamic ID pool abstraction for bitmap") Reviewed-by: Alexandre Courbot Signed-off-by: Georgios Androutsopoulos Signed-off-by: Yury Norov --- rust/kernel/id_pool.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rust/kernel/id_pool.rs b/rust/kernel/id_pool.rs index 384753fe0e44a3..c8b3465548027a 100644 --- a/rust/kernel/id_pool.rs +++ b/rust/kernel/id_pool.rs @@ -224,7 +224,13 @@ impl IdPool { /// Finds an unused ID in the bitmap. /// /// Upon success, returns its index. Otherwise, returns [`None`] - /// to indicate that a [`Self::grow_request`] is needed. + /// to indicate that a [`Self::grow_request`] is needed. `offset` must be + /// within bounds. + /// + /// # Panics + /// + /// Panics if `CONFIG_RUST_BITMAP_HARDENED` is enabled and `offset` is out + /// of bounds. #[inline] #[must_use] pub fn find_unused_id(&mut self, offset: usize) -> Option> { @@ -236,6 +242,13 @@ impl IdPool { } /// Releases an ID. + /// + /// The `id` must be within bounds. + /// + /// # Panics + /// + /// Panics if `CONFIG_RUST_BITMAP_HARDENED` is enabled and `id` is out of + /// bounds. #[inline] pub fn release_id(&mut self, id: usize) { self.map.clear_bit(id); From a73b4d1fa7d334a5f7de5bb0a14726530f66196e Mon Sep 17 00:00:00 2001 From: Georgios Androutsopoulos Date: Tue, 8 Sep 2026 16:13:02 -0400 Subject: [PATCH 1471/2286] rust: bitmap: document panics in `next_bit` and `next_zero_bit` `next_bit()` and `next_zero_bit()` use `bitmap_assert!()` to check that `start` is less than `self.len()`, which panics when `CONFIG_RUST_BITMAP_HARDENED` is enabled. However, neither function has a `# Panics` section, and both document that `None` is returned for exactly the input that triggers the panic. Update the documentation of `next_bit()` and `next_zero_bit()` to add the missing `# Panics` sections, add the missing blank doc comment line before `Returns` in `next_zero_bit()`, and simplify the wording. Fixes: 11eca92a2cae ("rust: add bitmap API.") Closes: https://github.com/Rust-for-Linux/linux/issues/1252 Reviewed-by: Alexandre Courbot Signed-off-by: Georgios Androutsopoulos Signed-off-by: Yury Norov --- rust/kernel/bitmap.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs index b27e0ec80d643d..b51c8a96f1a8dc 100644 --- a/rust/kernel/bitmap.rs +++ b/rust/kernel/bitmap.rs @@ -459,7 +459,13 @@ impl Bitmap { /// Finds next set bit, starting from `start`. /// - /// Returns `None` if `start` is greater or equal to `self.nbits`. + /// Returns `None` if no bits are set on or after the given index. The + /// index `start` must be in bounds. + /// + /// # Panics + /// + /// Panics if `CONFIG_RUST_BITMAP_HARDENED` is enabled and `start` is + /// out of bounds. #[inline] pub fn next_bit(&self, start: usize) -> Option { bitmap_assert!( @@ -479,7 +485,14 @@ impl Bitmap { } /// Finds next zero bit, starting from `start`. - /// Returns `None` if `start` is greater than or equal to `self.len()`. + /// + /// Returns `None` if no bits are zero on or after the given index. The + /// index `start` must be in bounds. + /// + /// # Panics + /// + /// Panics if `CONFIG_RUST_BITMAP_HARDENED` is enabled and `start` is + /// out of bounds. #[inline] pub fn next_zero_bit(&self, start: usize) -> Option { bitmap_assert!( From 814f5a53b4724798795b15690ff3d1c1d9ac1eb6 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Tue, 6 Oct 2026 17:48:25 +0900 Subject: [PATCH 1472/2286] nilfs2: fix link count underflow when removing corrupted directory When removing an empty directory with a corrupted link count (e.g., i_nlink is 1 due to filesystem corruption), nilfs_rmdir() calls drop_nlink() via nilfs_do_unlink() and then calls drop_nlink() again on the directory inode. This second call causes an underflow of i_nlink, triggering a kernel WARNING in drop_nlink(). Fix this issue by using clear_nlink() instead of drop_nlink() to finalize the link count reset for the target directory in nilfs_rmdir(). Additionally, log a warning message when i_nlink is not 2 before removing an empty directory to notify about the link count inconsistency. Reported-by: syzbot+c388db0347b7fc88fa7a@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c388db0347b7fc88fa7a Fixes: d25006523d0b ("nilfs2: pathname operations") Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/namei.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index e037e0c6e31a31..d326bb1acb1181 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -341,10 +341,16 @@ static int nilfs_rmdir(struct inode *dir, struct dentry *dentry) err = -ENOTEMPTY; if (nilfs_empty_dir(inode)) { + if (unlikely(inode->i_nlink != 2)) + nilfs_warn(dir->i_sb, + "inconsistent empty directory link count %u (ino=%llu)", + inode->i_nlink, + (unsigned long long)inode->i_ino); + err = nilfs_do_unlink(dir, dentry); if (!err) { inode->i_size = 0; - drop_nlink(inode); + clear_nlink(inode); nilfs_mark_inode_dirty(inode); drop_nlink(dir); nilfs_mark_inode_dirty(dir); From 109f733202e32c85b41b92276cae6fd789af1890 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 22 Sep 2026 17:33:50 +0000 Subject: [PATCH 1473/2286] lib: Add two-byte cmpxchg emulation function cmpxchg_emu_u8() emulates one-byte cmpxchg() in terms of four-byte cmpxchg() for the architectures lacking native one-byte atomics. The same architectures also lack native two-byte cmpxchg(), where such an operation is not supported and either fails to compile via BUILD_BUG() or fails to link, because the bad pointer sentinels these architectures declare are never defined. Add cmpxchg_emu_u16(), the two-byte sibling. It reads the enclosing word with READ_ONCE(), splices the two target bytes through a union and loops on cmpxchg() of the full word until the compare succeeds. Like cmpxchg_emu_u8() it is fully ordered. Unlike cmpxchg_emu_u8() it casts the old and new values to u16 internally and returns unsigned long, taking the old and new values as unsigned long, per the suggestion from David Laight. The switch statements in the architecture macros instantiate every size case, so a cmpxchg() on a pointer type checks the two-byte case as well, and a u16 parameter or return would make the macro casts and return conversions warn there. With unsigned long parameters and return the call sites need no narrowing casts, pointer exchanges compile warning free, and the function still compares and returns exactly the 16 bits the caller asked for, which matches the hardware cmpxchg r16 behaviour where a 16-bit compare only looks at the low 16 bits of the register. cmpxchg_emu_u8() keeps returning the old value unmasked, so a caller that passes a wider old than 8 bits still gets it back as passed, matching the behaviour the one-byte emulator always had. The Kconfig symbol gating this file is renamed from ARCH_NEED_CMPXCHG_1_EMU to ARCH_NEED_CMPXCHG_1_2_EMU, as it now selects both the one-byte and the two-byte emulation. [ paulmck: Apply kernel test robot feedback. ] Suggested-by: Paul E. McKenney Suggested-by: David Laight Signed-off-by: Bradley Morgan Signed-off-by: Paul E. McKenney Reviewed-by: David Laight Cc: Andrew Morton Cc: Arnd Bergmann --- include/linux/cmpxchg-emu.h | 4 +++- lib/cmpxchg-emu.c | 38 +++++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/include/linux/cmpxchg-emu.h b/include/linux/cmpxchg-emu.h index 998deec67740aa..2db70f1e39253c 100644 --- a/include/linux/cmpxchg-emu.h +++ b/include/linux/cmpxchg-emu.h @@ -4,12 +4,14 @@ * lacking direct support for these sizes. These are implemented in terms * of 4-byte cmpxchg operations. * - * Copyright (C) 2024 Paul E. McKenney. + * Copyright (C) 2024 Paul E. McKenney + * Copyright (C) 2026 Bradley Morgan */ #ifndef __LINUX_CMPXCHG_EMU_H #define __LINUX_CMPXCHG_EMU_H uintptr_t cmpxchg_emu_u8(volatile u8 *p, uintptr_t old, uintptr_t new); +unsigned long cmpxchg_emu_u16(volatile u16 *p, unsigned long old, unsigned long new); #endif /* __LINUX_CMPXCHG_EMU_H */ diff --git a/lib/cmpxchg-emu.c b/lib/cmpxchg-emu.c index 27f6f97cb60dd5..25c69224a75979 100644 --- a/lib/cmpxchg-emu.c +++ b/lib/cmpxchg-emu.c @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Emulated 1-byte cmpxchg operation for architectures lacking direct - * support for this size. This is implemented in terms of 4-byte cmpxchg - * operations. + * Emulated 1-byte and 2-byte cmpxchg operations for architectures lacking + * direct support for these sizes. These are implemented in terms of + * 4-byte cmpxchg operations. * - * Copyright (C) 2024 Paul E. McKenney. + * Copyright (C) 2024 Paul E. McKenney + * Copyright (C) 2026 Bradley Morgan */ #include @@ -43,3 +44,32 @@ uintptr_t cmpxchg_emu_u8(volatile u8 *p, uintptr_t old, uintptr_t new) return old; } EXPORT_SYMBOL_GPL(cmpxchg_emu_u8); + +union u16_32 { + u16 h[2]; + u32 w; +}; + +/* Emulate two-byte cmpxchg() in terms of 4-byte cmpxchg. */ +unsigned long cmpxchg_emu_u16(volatile u16 *p, unsigned long old, unsigned long new) +{ + u32 *p32 = (u32 *)(((uintptr_t)p) & ~0x3); + int i = (((uintptr_t)p) & 0x2) / 2; + union u16_32 old32; + union u16_32 new32; + u32 ret; + + WARN_ON_ONCE(((uintptr_t)p) & 0x1); + ret = READ_ONCE(*p32); + do { + old32.w = ret; + if (old32.h[i] != (u16)old) + return (unsigned long)old32.h[i]; + new32.w = old32.w; + new32.h[i] = (u16)new; + instrument_atomic_read_write(p, 2); + ret = data_race(cmpxchg(p32, old32.w, new32.w)); // Overridden above. + } while (ret != old32.w); + return (u16)old; +} +EXPORT_SYMBOL_GPL(cmpxchg_emu_u16); From 887685c52e44c3a80dfc55f8545f5598badb98c9 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Tue, 22 Sep 2026 17:33:52 +0000 Subject: [PATCH 1474/2286] sh: Emulate two-byte cmpxchg SH has no byte or halfword atomic memory operations, so the __cmpxchg() switch routes case 1 through cmpxchg_emu_u8() and lets case 2 fall through to __cmpxchg_called_with_bad_pointer(), which is declared but never defined, so a two-byte cmpxchg() fails at link time. Route case 2 through the new cmpxchg_emu_u16(), which takes the old and new values as unsigned long, so the (unsigned long) casts move off the call and into _old_ and _new_ declarations that type check the old and new arguments against *ptr through (unsigned long)(0 ? *ptr : _o_), the idiom David Laight suggested, so cmpxchg(&p, 4, 5) no longer compiles silently. [ paulmck: Apply kernel test robot feedback. ] Acked-by: John Paul Adrian Glaubitz Signed-off-by: Bradley Morgan Signed-off-by: Paul E. McKenney Cc: Yoshinori Sato Cc: Rich Felker Cc: John Paul Adrian Glaubitz Cc: --- arch/sh/include/asm/cmpxchg.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/sh/include/asm/cmpxchg.h b/arch/sh/include/asm/cmpxchg.h index 1e5dc5ccf7bf50..477d3025a441cc 100644 --- a/arch/sh/include/asm/cmpxchg.h +++ b/arch/sh/include/asm/cmpxchg.h @@ -59,6 +59,8 @@ static inline unsigned long __cmpxchg(volatile void * ptr, unsigned long old, switch (size) { case 1: return cmpxchg_emu_u8(ptr, old, new); + case 2: + return cmpxchg_emu_u16(ptr, old, new); case 4: return __cmpxchg_u32(ptr, old, new); } From 49be2ba9f0fa25cf2137ce111c7d84e869398a8f Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Thu, 3 Sep 2026 15:26:24 -0700 Subject: [PATCH 1475/2286] rcu: Add running and boosted indications to RCU task stall dump Currently, rcu_print_task_stall() will dump out the PID, RCU reader nesting level, the rcu_special structure's flags, and whether or not that reader is on the ->blkd_tasks list. When debugging RCU priority boosting, it is also good to know whether the stalled RCU reader is currently running and whether it is currently being RCU priority boosted. This commit therefore adds this information to the output. [ paulmck: Apply kernel test robot feedback. ] [ paulmck: Apply feedback from Arnd Bergmann and kernel test robot. ] Link: https://lore.kernel.org/all/20260925133922.1356404-1-arnd@kernel.org/ Link: https://lore.kernel.org/all/202609261005.GrnaXBXT-lkp@intel.com/ Signed-off-by: Paul E. McKenney Reviewed-by: Bradley Morgan Reviewed-by: Kunwu Chan Tested-by: Kunwu Chan --- kernel/rcu/tree_plugin.h | 2 -- kernel/rcu/tree_stall.h | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/kernel/rcu/tree_plugin.h b/kernel/rcu/tree_plugin.h index 743c16247fc057..ae5d21bae7bc0f 100644 --- a/kernel/rcu/tree_plugin.h +++ b/kernel/rcu/tree_plugin.h @@ -11,8 +11,6 @@ * Paul E. McKenney */ -#include "../locking/rtmutex_common.h" - static bool rcu_rdp_is_offloaded(struct rcu_data *rdp) { /* diff --git a/kernel/rcu/tree_stall.h b/kernel/rcu/tree_stall.h index 93ba31a619b670..803a56af3259f6 100644 --- a/kernel/rcu/tree_stall.h +++ b/kernel/rcu/tree_stall.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include "../locking/rtmutex_common.h" ////////////////////////////////////////////////////////////////////////////// // @@ -305,6 +307,8 @@ struct rcu_stall_chk_rdr { int nesting; union rcu_special rs; bool on_blkd_list; + bool rcu_rdr_running; + int rcu_rdr_boosted; }; /* @@ -313,6 +317,7 @@ struct rcu_stall_chk_rdr { */ static int check_slow_task(struct task_struct *t, void *arg) { + struct rcu_node *rnp; struct rcu_stall_chk_rdr *rscrp = arg; if (task_curr(t)) @@ -320,6 +325,19 @@ static int check_slow_task(struct task_struct *t, void *arg) rscrp->nesting = t->rcu_read_lock_nesting; rscrp->rs = t->rcu_read_unlock_special; rscrp->on_blkd_list = !list_empty(&t->rcu_node_entry); + rscrp->rcu_rdr_running = task_curr(t); + rscrp->rcu_rdr_boosted = 0; + if (rscrp->on_blkd_list) { + rnp = READ_ONCE(t->rcu_blocked_node); + raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */ + if (rnp == READ_ONCE(t->rcu_blocked_node)) { + if (rt_mutex_owner(&rnp->boost_mtx.rtmutex) == t) + rscrp->rcu_rdr_boosted = 1; + } else { + rscrp->rcu_rdr_boosted = 2; + } + raw_spin_unlock_rcu_node(rnp); /* irqs remain disabled. */ + } return 0; } @@ -357,12 +375,14 @@ static int rcu_print_task_stall(struct rcu_node *rnp, unsigned long flags) if (task_call_func(t, check_slow_task, &rscr)) pr_cont(" P%d", t->pid); else - pr_cont(" P%d/%d:%c%c%c%c", + pr_cont(" P%d/%d:%c%c%c%c%c%c", t->pid, rscr.nesting, ".b"[rscr.rs.b.blocked], ".q"[rscr.rs.b.need_qs], ".e"[rscr.rs.b.exp_hint], - ".l"[rscr.on_blkd_list]); + ".l"[rscr.on_blkd_list], + ".R"[rscr.rcu_rdr_running], + ".B?"[rscr.rcu_rdr_boosted]); lockdep_assert_irqs_disabled(); put_task_struct(t); ndetected++; From 2eaee974b5da25b55ae72cb6c649524410316ae8 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:06:22 +0530 Subject: [PATCH 1476/2286] rcu: Fix typo "upto" in comment Correct "upto" to "up to", reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index ed204b3f4b8440..01f19bb17dd6d6 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -624,7 +624,7 @@ module_param(srcu_retry_check_delay, ulong, 0444); #define SRCU_UL_CLAMP_LO(val, low) ((val) > (low) ? (val) : (low)) #define SRCU_UL_CLAMP_HI(val, high) ((val) < (high) ? (val) : (high)) #define SRCU_UL_CLAMP(val, low, high) SRCU_UL_CLAMP_HI(SRCU_UL_CLAMP_LO((val), (low)), (high)) -// per-GP-phase no-delay instances adjusted to allow non-sleeping poll upto +// per-GP-phase no-delay instances adjusted to allow non-sleeping poll up to // one jiffies time duration. Mult by 2 is done to factor in the srcu_get_delay() // called from process_srcu(). #define SRCU_DEFAULT_MAX_NODELAY_PHASE_ADJUSTED \ From 88f77859564b81543b7efbe0d866b2a0b20963d9 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Thu, 10 Sep 2026 17:25:37 +0000 Subject: [PATCH 1477/2286] rcu: Drop the private tick-internal.h include from tree.c Nothing in the tree.c translation unit uses anything from the private timekeeping header ../time/tick-internal.h, included since commit 48d07c04b4cc ("rcu: Enable elimination of Tree-RCU softirq processing"). The tick symbols RCU uses, tick_dep_set(), tick_dep_clear(), their _cpu and _task variants, tick_nohz_full_cpu() and TICK_DEP_BIT_RCU, are all declared in the public linux/tick.h, which tree.c already includes. Drop the include. Signed-off-by: Bradley Morgan Signed-off-by: Paul E. McKenney --- kernel/rcu/tree.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 96848fc1f02b8f..338737b9781cba 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -64,7 +64,6 @@ #include #include #include -#include "../time/tick-internal.h" #include "tree.h" #include "rcu.h" From c0fa92ccfdc9af4c6db695cbbae3f25262d56ca4 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 11 Sep 2026 11:40:59 +0800 Subject: [PATCH 1478/2286] rcu: Make userspace barrier hook drain kvfree_rcu work The bcachefs ktest allocation-leak check writes rcutree.do_rcu_barrier before reading /proc/allocinfo. While testing bcachefs performance changes, small objects released with kfree_rcu() remained visible after repeated writes to the hook and 20 seconds of waiting, causing otherwise clean tests to fail their leak check. The test assumes a stronger contract than the hook currently documents: rcu_barrier() waits for ordinary callbacks, but does not flush objects still held in kfree_rcu() batching or per-CPU SLUB sheaves. The retained population eventually fell as a sheaf filled; there is no evidence here of unbounded growth or OOM. Changing the hook to drain kvfree_rcu() work let the same unmodified bcachefs workload pass its allocation check. All eight checkpoints in one VM, after 50 through 400 option changes, reported zero retained reconcile_scan objects. This motivated the separate private-cache test used to isolate the incomplete drain from bcachefs. Calling kvfree_rcu_barrier() from rcu_barrier_throttled() was proposed when kvfree_rcu_barrier() was added in 2024, to restore a clean baseline between userspace benchmark runs. The discussion concluded that keeping the existing hook name, adding the second operation and documenting both was the safest compatibility choice, but the follow-up was not added. Add that drain and document the stronger test interface. Always retain the existing start-rate limit and perform the kvfree_rcu() drain: an unrelated ordinary barrier does not establish that this work completed. Retain the entry ordinary-barrier sequence snapshot. After draining, skip the final ordinary barrier only if that snapshot is complete, preserving the memory barrier on the completion path. Otherwise, invoke rcu_barrier() explicitly. This keeps the ordinary-callback guarantee independent of whether kvfree_rcu_barrier() embeds an ordinary barrier. Clarify that the documented completion guarantee covers work queued before the request, without preventing new work from being queued. Earlier validation of the unconditional-drain version used four fresh VM pairs with a private-cache fixture: controls retained the queued object (60 to 60 active objects), and treatments drained it (60 to 59). An ordinary-callback test passed on both kernels. Those runs predated the guarded skip and do not validate that change. No elapsed-time improvement is claimed. Link: https://lore.kernel.org/all/20240820155935.1167988-1-urezki@gmail.com/ Signed-off-by: Matthias Goergens Signed-off-by: Paul E. McKenney --- .../admin-guide/kernel-parameters.txt | 9 ++++-- kernel/rcu/tree.c | 30 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 68647ff4bdd24b..914b65ae941346 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -5699,9 +5699,12 @@ Kernel parameters there is an ongoing too-long CSD-lock wait. rcutree.do_rcu_barrier= [KNL] - Request a call to rcu_barrier(). This is - throttled so that userspace tests can safely - hammer on the sysfs variable if they so choose. + Wait for deferred kfree_rcu() frees and ordinary + call_rcu() callbacks queued before this request to + complete. This does not prevent new work from being + queued concurrently. Requests are throttled so that + userspace tests can safely hammer on the sysfs + variable if they so choose. If triggered before the RCU grace-period machinery is fully active, this will error out with EAGAIN. diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c index 338737b9781cba..f60252390d5dea 100644 --- a/kernel/rcu/tree.c +++ b/kernel/rcu/tree.c @@ -3988,12 +3988,12 @@ EXPORT_SYMBOL_GPL(rcu_barrier); static unsigned long rcu_barrier_last_throttle; /** - * rcu_barrier_throttled - Do rcu_barrier(), but limit to one per second + * rcu_barrier_throttled - Drain deferred RCU frees, but rate-limit starts * - * This can be thought of as guard rails around rcu_barrier() that - * permits unrestricted userspace use, at least assuming the hardware's - * try_cmpxchg() is robust. There will be at most one call per second to - * rcu_barrier() system-wide from use of this function, which means that + * This can be thought of as guard rails around the deferred-free barriers + * that permit unrestricted userspace use, at least assuming the hardware's + * try_cmpxchg() is robust. There will be at most one drain operation started + * per sixteenth of a second from use of this function, which means that * callers might needlessly wait a second or three. * * This is intended for use by test suites to avoid OOM by flushing RCU @@ -4015,14 +4015,24 @@ static void rcu_barrier_throttled(void) while (time_in_range(j, old, old + HZ / 16) || !try_cmpxchg(&rcu_barrier_last_throttle, &old, j)) { schedule_timeout_idle(HZ / 16); - if (rcu_seq_done(&rcu_state.barrier_sequence, s)) { - smp_mb(); /* caller's subsequent code after above check. */ - return; - } j = jiffies; old = READ_ONCE(rcu_barrier_last_throttle); } - rcu_barrier(); + /* + * kfree_rcu() can retain objects outside the ordinary callback lists in + * per-CPU SLUB sheaves and kvfree_rcu batches. Always drain those queues: + * an ordinary barrier does not establish that this work was drained. + */ + kvfree_rcu_barrier(); + /* + * A completed barrier can still cover ordinary callbacks queued before + * our entry snapshot. Otherwise, retain an explicit ordinary barrier + * without depending on the implementation of kvfree_rcu_barrier(). + */ + if (rcu_seq_done(&rcu_state.barrier_sequence, s)) + smp_mb(); /* caller's subsequent code after above check. */ + else + rcu_barrier(); } /* From 852fbf7fc857fd375d8caccbce576aefc37fb065 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Fri, 11 Sep 2026 23:36:43 +0200 Subject: [PATCH 1479/2286] rcuref: Fix the rcuread_is_dead reference in rcuref_read() kernel-doc The kernel-doc comment of rcuref_read() refers to rcuread_is_dead, which does not exist. The name is rcuref_is_dead. Say rcuref_is_dead. Fixes: 3efa66ce6ee1 ("rcuref: Provide rcuref_is_dead()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Reviewed-by: Sebastian Andrzej Siewior Signed-off-by: Paul E. McKenney --- include/linux/rcuref.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/rcuref.h b/include/linux/rcuref.h index 2fb2af6d982497..01fee161b67c3e 100644 --- a/include/linux/rcuref.h +++ b/include/linux/rcuref.h @@ -34,7 +34,7 @@ static inline void rcuref_init(rcuref_t *ref, unsigned int cnt) * indicate that it is safe to schedule the object, protected by this reference * counter, for deconstruction. * If you want to know if the reference counter has been marked DEAD (as - * signaled by rcuref_put()) please use rcuread_is_dead(). + * signaled by rcuref_put()) please use rcuref_is_dead(). */ static inline unsigned int rcuref_read(rcuref_t *ref) { From 5de907d24919f42ec2e221ed33a50749016368fe Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 11 Aug 2026 11:44:00 -0700 Subject: [PATCH 1480/2286] rcu-tasks: Disable callback contend/collapse messages by default New workloads can do large bursts of call_rcu_tasks() invocations in a short time period, followed by a quiet time period long enough to drain all of the callbacks, followed by another burst of call_rcu_tasks() invocations. This can cause RCU Tasks to switch back and forth between queuing callbacks only on CPU 0 (during quiet periods) and on all CPUs (during bursts). Which is fine. Except for the fact that each cycle from CPU-0-only to all-CPUs queuing and back generates three console messages, one announcing the shift to all-CPUs queuing, another announcing the start of the shift back to CPU-0-only queuing, and the third announcing completion of this shift after an RCU grace period. And these console messages can overrun console-log communications channels and obscure other console-message-based debugging information. And the only known use for these console messages is debugging RCU Tasks itself. This commit therefore adds a rcupdate.rcu_task_collapse_debug module parameter that defaults to false (suppressing these console messages). Those debugging or otherwise playing with RCU Tasks callback queuing auto-adjustment can set this parameter to the value true. [ paulmck: Apply Breno Leitao feedback. ] Reported-by: Breno Leitao Reported-by: David Dai Signed-off-by: Paul E. McKenney Reviewed-by: Breno Leitao --- Documentation/admin-guide/kernel-parameters.txt | 7 +++++++ kernel/rcu/tasks.h | 15 ++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 914b65ae941346..6cc6d45b59d68a 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -6405,6 +6405,13 @@ Kernel parameters period to instead use normal non-expedited grace-period processing. + rcupdate.rcu_task_collapse_debug= [KNL] + Enable debugging prints that record when RCU Tasks + and RCU Tasks Trace expand to per-CPU callback + queuing and collapse back to CPU-0 queuing. + This is default-disabled due to the fact that + some workloads can make it quite noisy. + rcupdate.rcu_task_collapse_lim= [KNL] Set the maximum number of callbacks present at the beginning of a grace period that allows diff --git a/kernel/rcu/tasks.h b/kernel/rcu/tasks.h index 627295396cd91d..fcac7361ec51ec 100644 --- a/kernel/rcu/tasks.h +++ b/kernel/rcu/tasks.h @@ -178,6 +178,8 @@ static int rcu_task_contend_lim __read_mostly = 100; module_param(rcu_task_contend_lim, int, 0444); static int rcu_task_collapse_lim __read_mostly = 10; module_param(rcu_task_collapse_lim, int, 0444); +static bool rcu_task_collapse_debug __read_mostly; +module_param(rcu_task_collapse_debug, bool, 0644); static int rcu_task_lazy_lim __read_mostly = 32; module_param(rcu_task_lazy_lim, int, 0444); @@ -390,7 +392,8 @@ static void call_rcu_tasks_generic(struct rcu_head *rhp, rcu_callback_t func, WRITE_ONCE(rtp->percpu_enqueue_shift, 0); WRITE_ONCE(rtp->percpu_dequeue_lim, rcu_task_cpu_ids); smp_store_release(&rtp->percpu_enqueue_lim, rcu_task_cpu_ids); - pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name); + if (data_race(rcu_task_collapse_debug)) + pr_info("Switching %s to per-CPU callback queuing.\n", rtp->name); } raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags); } @@ -511,7 +514,9 @@ static int rcu_tasks_need_gpcb(struct rcu_tasks *rtp) smp_store_release(&rtp->percpu_enqueue_lim, 1); rtp->percpu_dequeue_gpseq = get_state_synchronize_rcu(); gpdone = false; - pr_info("Starting switch %s to CPU-0 callback queuing.\n", rtp->name); + if (data_race(rcu_task_collapse_debug)) + pr_info("Starting switch %s to CPU-0 callback queuing.\n", + rtp->name); } raw_spin_unlock_irqrestore(&rtp->cbs_gbl_lock, flags); } @@ -519,7 +524,9 @@ static int rcu_tasks_need_gpcb(struct rcu_tasks *rtp) raw_spin_lock_irqsave(&rtp->cbs_gbl_lock, flags); if (rtp->percpu_enqueue_lim < rtp->percpu_dequeue_lim) { WRITE_ONCE(rtp->percpu_dequeue_lim, 1); - pr_info("Completing switch %s to CPU-0 callback queuing.\n", rtp->name); + if (data_race(rcu_task_collapse_debug)) + pr_info("Completing switch %s to CPU-0 callback queuing.\n", + rtp->name); } if (rtp->percpu_dequeue_lim == 1) { for (cpu = rtp->percpu_dequeue_lim; cpu < rcu_task_cpu_ids; cpu++) { @@ -704,6 +711,8 @@ static void __init rcu_tasks_bootup_oddness(void) pr_info("\tTasks-RCU CPU stall info multiplier clamped to %d (rcu_task_stall_info_mult).\n", rtsimc); rcu_task_stall_info_mult = rtsimc; } + if (rcu_task_collapse_debug) + pr_info("\tTasks-RCU callback contend/collapse debug enabled.\n"); #endif /* #ifdef CONFIG_TASKS_RCU */ #ifdef CONFIG_TASKS_RCU pr_info("\tTrampoline variant of Tasks RCU enabled.\n"); From 0381ade2ea82b48584fa57ef23d63f97b2d87d7b Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 14 Sep 2026 13:53:31 +0000 Subject: [PATCH 1481/2286] rcu: Drop the address space qualifier from the dereference macros The RCU dereference macros end with a cast that is meant to hand back a plain kernel pointer from a __rcu pointer. For that it uses: ((typeof(*p) __force __kernel *)(local)) The problem is that typeof preserves every qualifier, including the address space qualifiers (__rcu). Recent versions of smatch[1] care about this and throw tens of warnings like this one: ./include/trace/events/vb2.h:46:1: warning: incorrect type in assignment (different address spaces) ./include/trace/events/vb2.h:46:1: expected struct tracepoint_func *it_func_ptr ./include/trace/events/vb2.h:46:1: got struct tracepoint_func __rcu * Use a new macro TYPEOF_NO_ADDRESS_SPACE() for the result type. This new macro strips all the qualifiers when running with sparse (so const and volatile are gone). But keeps all the qualifiers when running with the compiler, caring about const/volatile mismatch. [1] https://github.com/error27/smatch/commit/e53027a4e816a772403baafa83c09e4a94c1cb8f Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20260825-unqual-v1-1-7024fb81b4f9@chromium.org Signed-off-by: Ricardo Ribalda Reviewed-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- include/linux/compiler.h | 17 +++++++++++++++++ include/linux/rcupdate.h | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/include/linux/compiler.h b/include/linux/compiler.h index cb2f6050bdf7dc..70cb31d6053846 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -239,6 +239,23 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val, # define TYPEOF_UNQUAL(exp) __typeof__(exp) #endif +/* + * TYPEOF_NO_ADDRESS_SPACE() - typeof() without the address space qualifiers + * + * No operator strips only the address space qualifiers: typeof() keeps every + * qualifier and TYPEOF_UNQUAL() drops every qualifier, const and volatile + * included. + * + * Approximate one by dropping the qualifiers for sparse only, as it is the + * only one that knows about address spaces. The compiler keeps seeing the fully + * qualified type, so a missing const or volatile will still throw a warning. + */ +#ifdef __CHECKER__ +# define TYPEOF_NO_ADDRESS_SPACE(exp) TYPEOF_UNQUAL(exp) +#else +# define TYPEOF_NO_ADDRESS_SPACE(exp) __typeof__(exp) +#endif + #endif /* __KERNEL__ */ #if defined(CONFIG_CFI) && !defined(__DISABLE_EXPORTS) && !defined(BUILD_VDSO) diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h index 44c07a66edfff2..3f74ae6d6e1f20 100644 --- a/include/linux/rcupdate.h +++ b/include/linux/rcupdate.h @@ -488,7 +488,7 @@ static __always_inline bool lockdep_assert_rcu_helper(bool c, const struct __ctx context_unsafe( \ typeof(*p) *local = (typeof(*p) *__force)(p); \ rcu_check_sparse(p, __rcu); \ - ((typeof(*p) __force __kernel *)(local)) \ + ((TYPEOF_NO_ADDRESS_SPACE(*p) __force __kernel *)(local)) \ ) /** * unrcu_pointer - mark a pointer as not being RCU protected @@ -503,7 +503,7 @@ context_unsafe( \ ({ \ typeof(*p) *local = (typeof(*p) *__force)READ_ONCE(p); \ rcu_check_sparse(p, space); \ - ((typeof(*p) __force __kernel *)(local)); \ + ((TYPEOF_NO_ADDRESS_SPACE(*p) __force __kernel *)(local)); \ }) ) #define __rcu_dereference_check(p, local, c, space) \ ({ \ @@ -511,19 +511,19 @@ context_unsafe( \ typeof(*p) *local = (typeof(*p) *__force)READ_ONCE(p); \ RCU_LOCKDEP_WARN(!(c), "suspicious rcu_dereference_check() usage"); \ rcu_check_sparse(p, space); \ - ((typeof(*p) __force __kernel *)(local)); \ + ((TYPEOF_NO_ADDRESS_SPACE(*p) __force __kernel *)(local)); \ }) #define __rcu_dereference_protected(p, local, c, space) \ ({ \ RCU_LOCKDEP_WARN(!(c), "suspicious rcu_dereference_protected() usage"); \ rcu_check_sparse(p, space); \ - ((typeof(*p) __force __kernel *)(p)); \ + ((TYPEOF_NO_ADDRESS_SPACE(*p) __force __kernel *)(p)); \ }) #define __rcu_dereference_raw(p, local) \ ({ \ /* Dependency order vs. p above. */ \ typeof(p) local = READ_ONCE(p); \ - ((typeof(*p) __force __kernel *)(local)); \ + ((TYPEOF_NO_ADDRESS_SPACE(*p) __force __kernel *)(local)); \ }) #define rcu_dereference_raw(p) __rcu_dereference_raw(p, __UNIQUE_ID(rcu)) From 95cc3927ce26cadc325833a16b13c2a3c08353de Mon Sep 17 00:00:00 2001 From: Kathiravan Thirumoorthy Date: Wed, 7 Oct 2026 12:31:56 +0530 Subject: [PATCH 1482/2286] cpufreq: qcom-nvmem: Add IPQ9650 support Add IPQ9650 support to the Qualcomm NVMEM cpufreq driver so the supported OPP can be selected at runtime using the eFuse value and the opp-supported-hw OPP property. Also block the generic cpufreq-dt platform device for IPQ9650 so the NVMEM-based driver is used. Signed-off-by: Kathiravan Thirumoorthy Reviewed-by: Dmitry Baryshkov Signed-off-by: Viresh Kumar --- drivers/cpufreq/cpufreq-dt-platdev.c | 1 + drivers/cpufreq/qcom-cpufreq-nvmem.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/drivers/cpufreq/cpufreq-dt-platdev.c b/drivers/cpufreq/cpufreq-dt-platdev.c index 574c5bec483122..bce4c87573a18b 100644 --- a/drivers/cpufreq/cpufreq-dt-platdev.c +++ b/drivers/cpufreq/cpufreq-dt-platdev.c @@ -210,6 +210,7 @@ static const struct of_device_id blocklist[] __initconst = { { .compatible = "qcom,ipq8064", }, { .compatible = "qcom,ipq8074", }, { .compatible = "qcom,ipq9574", }, + { .compatible = "qcom,ipq9650", }, { .compatible = "qcom,apq8064", }, { .compatible = "qcom,msm8974", }, { .compatible = "qcom,msm8960", }, diff --git a/drivers/cpufreq/qcom-cpufreq-nvmem.c b/drivers/cpufreq/qcom-cpufreq-nvmem.c index efa766e98d8654..375f12d5d11a76 100644 --- a/drivers/cpufreq/qcom-cpufreq-nvmem.c +++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c @@ -211,6 +211,14 @@ static int qcom_cpufreq_kryo_name_version(struct device *cpu_dev, case QCOM_ID_IPQ5404: drv->versions = (*speedbin == 0x3b) ? BIT(1) : BIT(0); break; + case QCOM_ID_IPQ9610: + case QCOM_ID_IPQ9620: + case QCOM_ID_IPQ9630: + case QCOM_ID_IPQ9640: + case QCOM_ID_IPQ9650: + case QCOM_ID_IPQ9670: + drv->versions = (*speedbin == 0x4b) ? BIT(3) : BIT(2); + break; case QCOM_ID_MSM8996SG: case QCOM_ID_APQ8096SG: drv->versions = 1 << ((unsigned int)(*speedbin) + 4); @@ -633,6 +641,7 @@ static const struct of_device_id qcom_cpufreq_match_list[] __initconst __maybe_u { .compatible = "qcom,ipq8074", .data = &match_data_ipq8074 }, { .compatible = "qcom,apq8064", .data = &match_data_krait }, { .compatible = "qcom,ipq9574", .data = &match_data_kryo }, + { .compatible = "qcom,ipq9650", .data = &match_data_kryo }, { .compatible = "qcom,msm8974", .data = &match_data_krait }, { .compatible = "qcom,msm8960", .data = &match_data_krait }, {}, From c8dd1aa0274d8ad3df82ec1b064065f410ce99ed Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Mon, 5 Oct 2026 03:57:29 -0500 Subject: [PATCH 1483/2286] gen_init_cpio: stop parsing on lines with a missing argument list When a line in the cpio list consists of a file type followed only by whitespace and no newline (e.g. "slink " as the last line of the file), strtok(NULL, "\n") returns NULL. main() prints an error and sets ec, but then carries on and passes the NULL args pointer to the type handler, which hands it to sscanf() and crashes: AddressSanitizer: SEGV on unknown address 0x000000000000 #4 __isoc99_sscanf #5 cpio_mkslink_line usr/gen_init_cpio.c:169 Break out of the loop as is already done for the other format error, so that the error is reported and gen_init_cpio exits with a failure status. Found by fuzzing gen_init_cpio built with ASan/UBSan. Reproducer: printf 'slink ' > list && usr/gen_init_cpio list Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: Claude:claude-opus-5-5 ASan UBSan Signed-off-by: Leizhen Zhang Link: https://patch.msgid.link/20261005085729.872402-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/gen_init_cpio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gen_init_cpio.c b/scripts/gen_init_cpio.c index b7296edc6626fb..0f0c61ec21ca3d 100644 --- a/scripts/gen_init_cpio.c +++ b/scripts/gen_init_cpio.c @@ -756,6 +756,7 @@ int main (int argc, char *argv[]) "ERROR: incorrect format, newline required line %d: '%s'\n", line_nr, line); ec = -1; + break; } for (type_idx = 0; file_handler_table[type_idx].type; type_idx++) { From 70ff1ccded06372e80acb5a67b710c3e979e3f4b Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:31 +0200 Subject: [PATCH 1484/2286] rust: pin-init: internal: init: forget guards in reverse order Currently, the forget order is the same as initialization order, this is the wrong way around compared to their drop order when going out-of-scope. The wrong order will cause borrow checker errors if there are lifetime dependencies between the guards. This does not matter for now because there cannot be any lifetime dependency; it would matter for self-referential borrows. Reverse the order to be consistent with their out-of-scope drop order. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-1-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 6605e04d49df27..f0b23d0311ac18 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -361,8 +361,7 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi /// Generate the code that initializes the fields of the struct using the initializers in `field`. fn init_fields(fields: &Punctuated, pinned: bool) -> TokenStream { - let mut guards = vec![]; - let mut guard_attrs = vec![]; + let mut forget_guards = vec![]; let mut res = TokenStream::new(); for InitializerField { attrs, kind } in fields { let cfgs = { @@ -474,17 +473,19 @@ fn init_fields(fields: &Punctuated, pinned: bool) - #binding }); - guards.push(guard); - guard_attrs.push(cfgs); + forget_guards.push(quote_spanned! { span => + #(#cfgs)* + ::core::mem::forget(#guard); + }); } + + forget_guards.reverse(); + quote! { #res // If execution reaches this point, all fields have been initialized. Therefore we can now // dismiss the guards by forgetting them. - #( - #(#guard_attrs)* - ::core::mem::forget(#guards); - )* + #(#forget_guards)* } } From da2b9009d445efb826a9eb535c4d62129fee6b35 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:32 +0200 Subject: [PATCH 1485/2286] rust: pin-init: internal: init: set span location of guards Set the span location of the guards to point to the identifier. The generated diagnostic is still not ideal, as ideally it points the place where the identifier is used rather than the identifier name in initializer, but is still an improvement to status quo. Before the change: error[E0505]: cannot move out of value because it is borrowed --> tests/ui/compile-fail/init/accessor_lifetime.rs:8:13 | 8 | let _ = init!(Foo { | _____________^ 9 | | x: 0, 10 | | _: { 11 | | let _: &'static usize = x; | | -------------- type annotation requires that borrow lasts for `'static` 12 | | }, 13 | | }); | | ^ | | | | |______move out of value occurs here | borrow of value occurs here After the change: error[E0505]: cannot move out of value because it is borrowed --> tests/ui/compile-fail/init/accessor_lifetime.rs:9:9 | 9 | x: 0, | ^ | | | move out of value occurs here | borrow of value occurs here 10 | _: { 11 | let _: &'static usize = x; | -------------- type annotation requires that borrow lasts for `'static` Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-2-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index f0b23d0311ac18..80e4dc068d2e32 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -413,7 +413,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - }; // `mixed_site` ensures that the guard is not accessible to the user-controlled code. - let guard = format_ident!("__{ident}_guard", span = Span::mixed_site()); + let guard = format_ident!("__{ident}_guard", span = span); let full_span = kind.span().resolved_at(Span::mixed_site()); let init = match kind { From 25e82b177c8bad26509e1e989795f63ef08e9dc5 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:33 +0200 Subject: [PATCH 1486/2286] rust: pin-init: internal: add utility to handle generics merging There're many cases where we want to merge multiple generics together. Currently this is done by cloning generics and modifying it, making the code less readable. Add a utility code that allow printing multiple generics together, ensuring correct ordering between lifetime and other parameters. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-3-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 10 +- rust/pin-init/internal/src/util.rs | 164 ++++++++++++++++++++++++- 2 files changed, 168 insertions(+), 6 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 982a38ceea9335..4ffa35bbbccdfa 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -321,10 +321,12 @@ fn generate_projections( is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { - let (impl_generics, ty_generics, _) = generics.split_for_impl(); - let mut generics_with_pin_lt = generics.clone(); - generics_with_pin_lt.params.insert(0, parse_quote!('__pin)); - let (_, ty_generics_with_pin_lt, whr) = generics_with_pin_lt.split_for_impl(); + let pin_lt_generics: Generics = parse_quote!(<'__pin>); + let generics_with_pin_lt = CombinedGenerics(vec![&pin_lt_generics, generics]); + + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl(); + let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 0b5c28b2109152..2a57a25b70ae99 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use proc_macro2::{Ident, TokenStream}; -use quote::format_ident; -use syn::{Attribute, Index, Member}; +use quote::{format_ident, ToTokens}; +use syn::{Attribute, GenericParam, Generics, Index, Member, Token}; pub(crate) trait AttrListExt { fn extract_cfg_attrs(&mut self) -> Vec; @@ -52,3 +52,163 @@ impl MemberExt for Member { } } } + +pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>); +pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>); +pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>); + +impl CombinedGenerics<'_> { + pub(crate) fn split_for_impl( + &self, + ) -> ( + CombinedImplGenerics<'_>, + CombinedTypeGenerics<'_>, + // A stub type so `split_for_impl` signature matches that of `syn`'s. + impl Sized, + ) { + (CombinedImplGenerics(self), CombinedTypeGenerics(self), ()) + } +} + +impl ToTokens for CombinedGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + // Most of the time we are only adding lifetimes, so we prefer to place + // the user provided generics last. + self.0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + lt.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(_) = param.value() { + continue; + }; + param.value().to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +} + +impl ToTokens for CombinedImplGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + lt.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + // Leave out defaults. + match param.value() { + GenericParam::Lifetime(_) => continue, + GenericParam::Type(param) => { + param.ident.to_tokens(tokens); + if !param.bounds.is_empty() { + param + .colon_token + .unwrap_or_else(Default::default) + .to_tokens(tokens); + param.bounds.to_tokens(tokens); + } + } + GenericParam::Const(param) => { + param.const_token.to_tokens(tokens); + param.ident.to_tokens(tokens); + param.colon_token.to_tokens(tokens); + param.ty.to_tokens(tokens); + } + } + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +} + +impl ToTokens for CombinedTypeGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .last() + .and_then(|x| x.lt_token) + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + // Output lifetimes first. + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + if let GenericParam::Lifetime(lt) = param.value() { + // Leave out bounds + lt.lifetime.to_tokens(tokens); + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + } + + for generics in self.0 .0.iter() { + for param in generics.params.pairs() { + // Leave out bounds and defaults. + match param.value() { + GenericParam::Lifetime(_) => continue, + GenericParam::Type(param) => { + param.ident.to_tokens(tokens); + } + GenericParam::Const(param) => { + param.ident.to_tokens(tokens); + } + } + param.punct().unwrap_or(&&comma).to_tokens(tokens); + } + } + + self.0 + .0 + .last() + .and_then(|x| x.gt_token) + .unwrap_or_default() + .to_tokens(tokens); + } +} From 003a8953d2f1c310550ac477a508b7be910b9827 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:34 +0200 Subject: [PATCH 1487/2286] rust: pin-init: internal: add utility for extract a single attribute More attributes are to be added which has the requirement that they should only be specified once. Add a utility code to extract attributes for a specific path and report error when appeared multiple times. Convert `#[pin]` parsing to use the added utility. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-4-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 9 ++------- rust/pin-init/internal/src/util.rs | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 4ffa35bbbccdfa..ac83f0f73e61e0 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -137,12 +137,7 @@ pub(crate) fn pin_data( .iter_mut() .enumerate() .map(|(index, field)| { - let len = field.attrs.len(); - field.attrs.retain(|a| !a.path().is_ident("pin")); - let pinned_count = len - field.attrs.len(); - if pinned_count > 1 { - dcx.error(&field, "#[pin] attribute specified more than once"); - } + let pinned = field.attrs.extract_single_attr(dcx, "pin").is_some(); assert!( !field.attrs.iter().any(|a| a.path().is_ident("cfg")), @@ -159,7 +154,7 @@ pub(crate) fn pin_data( FieldInfo { field: &*field, member, - pinned: pinned_count != 0, + pinned, } }) .collect(); diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 2a57a25b70ae99..3ce498cea754b8 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -4,8 +4,15 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use syn::{Attribute, GenericParam, Generics, Index, Member, Token}; +use crate::DiagCtxt; + pub(crate) trait AttrListExt { fn extract_cfg_attrs(&mut self) -> Vec; + + /// Extract attribute with identifier `path`. + /// + /// Report error if the attribute appears multiple times. + fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option; } impl AttrListExt for Vec { @@ -25,6 +32,24 @@ impl AttrListExt for Vec { cfg } + + fn extract_single_attr(&mut self, dcx: &DiagCtxt, path: &str) -> Option { + // FIXME: Replace with `extract_if` when MSRV >= 1.85. + let attr_pos = self.iter().position(|attr| attr.path().is_ident(path))?; + let attr = self.remove(attr_pos); + self.retain(|attr| { + if !attr.path().is_ident(path) { + return true; + } + + dcx.error( + attr, + format!("`#[{path}]` attribute specified more than once"), + ); + false + }); + Some(attr) + } } pub(crate) trait MemberExt { From 6fbdebfeb02f36c5ec187aea34e47d219b866253 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:35 +0200 Subject: [PATCH 1488/2286] rust: pin-init: internal: pin_data: move projection struct into `const _: ()` block Most users would not need to mention the generated projection struct. With the upcoming self-referential support, there needs to be additional projection structs. Instead of give them publicly visible names, simply make the name not publicly accessible. A heavy user of pin projection outside kernel is for async programming. I've checked tokio codebase there is a single use case and that's for enums. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-5-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index ac83f0f73e61e0..3acced1fe80650 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -186,10 +186,10 @@ pub(crate) fn pin_data( Ok(quote! { #struct_ - #projections // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { + #projections #the_pin_data #unpin_impl #drop_impl @@ -322,7 +322,6 @@ fn generate_projections( let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl(); - let projection = format_ident!("{ident}Projection"); let this = format_ident!("this"); let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields @@ -371,13 +370,13 @@ fn generate_projections( let (projection_def, projection_init) = if is_tuple_struct { ( quote! { - #vis struct #projection #generics_with_pin_lt ( + #vis struct __Projection #generics_with_pin_lt ( #(#fields_decl)* ::core::marker::PhantomData<&'__pin mut ()>, ) #whr; }, quote! { - #projection( + __Projection( #(#fields_proj)* ::core::marker::PhantomData, ) @@ -386,7 +385,7 @@ fn generate_projections( } else { ( quote! { - #vis struct #projection #generics_with_pin_lt + #vis struct __Projection #generics_with_pin_lt #whr { #(#fields_decl)* @@ -394,7 +393,7 @@ fn generate_projections( } }, quote! { - #projection { + __Projection { #(#fields_proj)* ___pin_phantom_data: ::core::marker::PhantomData, } @@ -422,7 +421,7 @@ fn generate_projections( #[inline] #vis fn project<'__pin>( self: ::core::pin::Pin<&'__pin mut Self>, - ) -> #projection #ty_generics_with_pin_lt { + ) -> __Projection #ty_generics_with_pin_lt { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; #projection_init From 93bfdb53067cca9b89632022bd3f4560e03ac015 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:36 +0200 Subject: [PATCH 1489/2286] rust: pin-init: internal: pin_data: extract `#[cfg]` processing to its own function The `pin_data` function is getting big. Split the `#[cfg]` handling which does not interact with other part of expansion to its own `expand_with_cfg` function. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-6-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/lib.rs | 3 ++- rust/pin-init/internal/src/pin_data.rs | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/pin-init/internal/src/lib.rs b/rust/pin-init/internal/src/lib.rs index 07b3d33d228248..01e19fcd31fb8f 100644 --- a/rust/pin-init/internal/src/lib.rs +++ b/rust/pin-init/internal/src/lib.rs @@ -22,7 +22,8 @@ mod zeroable; #[proc_macro_attribute] pub fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream { - DiagCtxt::for_item(|dcx| pin_data::pin_data(syn::parse(args)?, syn::parse(input)?, dcx)).into() + DiagCtxt::for_item(|dcx| pin_data::expand_with_cfg(syn::parse(args)?, syn::parse(input)?, dcx)) + .into() } #[proc_macro_attribute] diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 3acced1fe80650..a4d6965c15728a 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -8,7 +8,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Index, Item, Member, PathSegment, Type, TypePath, Visibility, + Field, Fields, Generics, Ident, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath, + Visibility, }; use crate::{ @@ -54,7 +55,7 @@ struct FieldInfo<'a> { pinned: bool, } -pub(crate) fn pin_data( +pub(crate) fn expand_with_cfg( args: Args, input: Item, dcx: &mut DiagCtxt, @@ -120,6 +121,14 @@ pub(crate) fn pin_data( )); } + expand(args, struct_, dcx) +} + +fn expand( + args: Args, + mut struct_: ItemStruct, + dcx: &mut DiagCtxt, +) -> Result { // The generics might contain the `Self` type. Since this macro will define a new type with the // same generics and bounds, this poses a problem: `Self` will refer to the new type as opposed // to this struct definition. Therefore we have to replace `Self` with the concrete name. From ec87b5938354046ca79a3ed7fcd89a2cb9469f11 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:37 +0200 Subject: [PATCH 1490/2286] rust: pin-init: internal: pin_data: rename `'__pin` lifetime to `'__this` "pin" is a very overloaded word in pin-init. For the generated projection structs, the lifetime refer to that of the whole struct, so rename the lifetime used in expansion from `'__pin` to `'__this` instead. The `__pin_phantom_data` field is renamed to `__this` for the same reason and for clarity purpose, capture a `PhantomData` of the mutable reference to the struct. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-7-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index a4d6965c15728a..6e18bc9e890aea 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -325,11 +325,11 @@ fn generate_projections( is_tuple_struct: bool, fields: &[FieldInfo<'_>], ) -> TokenStream { - let pin_lt_generics: Generics = parse_quote!(<'__pin>); - let generics_with_pin_lt = CombinedGenerics(vec![&pin_lt_generics, generics]); + let this_lt_generics: Generics = parse_quote!(<'__this>); + let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); - let (_, ty_generics_with_pin_lt, _) = generics_with_pin_lt.split_for_impl(); + let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); let this = format_ident!("this"); @@ -348,7 +348,7 @@ fn generate_projections( if field.pinned { ( quote!( - #vis #name ::core::pin::Pin<&'__pin mut #ty>, + #vis #name ::core::pin::Pin<&'__this mut #ty>, ), quote!( // SAFETY: this field is structurally pinned. @@ -358,7 +358,7 @@ fn generate_projections( } else { ( quote!( - #vis #name &'__pin mut #ty, + #vis #name &'__this mut #ty, ), quote!( #name &mut #this.#member, @@ -379,9 +379,9 @@ fn generate_projections( let (projection_def, projection_init) = if is_tuple_struct { ( quote! { - #vis struct __Projection #generics_with_pin_lt ( + #vis struct __Projection #generics_with_this_lt ( #(#fields_decl)* - ::core::marker::PhantomData<&'__pin mut ()>, + ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, ) #whr; }, quote! { @@ -394,17 +394,17 @@ fn generate_projections( } else { ( quote! { - #vis struct __Projection #generics_with_pin_lt + #vis struct __Projection #generics_with_this_lt #whr { #(#fields_decl)* - ___pin_phantom_data: ::core::marker::PhantomData<&'__pin mut ()>, + __this: ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, } }, quote! { __Projection { #(#fields_proj)* - ___pin_phantom_data: ::core::marker::PhantomData, + __this: ::core::marker::PhantomData, } }, ) @@ -428,9 +428,9 @@ fn generate_projections( /// These fields are **not** structurally pinned: #(#[doc = #not_structurally_pinned_fields_docs])* #[inline] - #vis fn project<'__pin>( - self: ::core::pin::Pin<&'__pin mut Self>, - ) -> __Projection #ty_generics_with_pin_lt { + #vis fn project<'__this>( + self: ::core::pin::Pin<&'__this mut Self>, + ) -> __Projection #ty_generics_with_this_lt { // SAFETY: we only give access to `&mut` for fields not structurally pinned. let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; #projection_init From 0d9aa4b994379c6e0099737221ed421deb7e1a31 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 6 Oct 2026 17:29:38 +0200 Subject: [PATCH 1491/2286] rust: pin-init: internal: pin_data: parse into `StructInfo` before generating Organize parsing and generation into two separate phases, with a single `StructInfo` in the middle. This allows analysis and processing to happen with a single struct and avoid having to pass different information into the token stream generation functions. A drawback of this approach is that as we consume the `Fields`, we cannot expand the original struct using `quote!(#struct_)` anymore, and the field needs to be re-constructed in `generate_struct_def` function. However, this is only a temporary drawback, as the upcoming pin-init self-referential feature will require modification of the generated struct anyway. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261006-dev-selfref-dep-v1-8-da716f740d6d@garyguo.net Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 162 +++++++++++++++++-------- 1 file changed, 112 insertions(+), 50 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 6e18bc9e890aea..03e893cf547553 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -8,8 +8,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath, - Visibility, + Field, Fields, Generics, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath, }; use crate::{ @@ -49,12 +48,19 @@ impl ToTokens for Args { } } -struct FieldInfo<'a> { - field: &'a Field, +struct FieldInfo { + field: Field, member: Member, pinned: bool, } +struct StructInfo { + args: Args, + struct_: ItemStruct, + fields: Vec, + is_tuple_struct: bool, +} + pub(crate) fn expand_with_cfg( args: Args, input: Item, @@ -141,11 +147,11 @@ fn expand( replacer.visit_fields_mut(&mut struct_.fields); let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); - let fields: Vec> = struct_ + let fields: Vec = struct_ .fields - .iter_mut() + .into_iter() .enumerate() - .map(|(index, field)| { + .map(|(index, mut field)| { let pinned = field.attrs.extract_single_attr(dcx, "pin").is_some(); assert!( @@ -161,17 +167,25 @@ fn expand( }; FieldInfo { - field: &*field, + field, member, pinned, } }) .collect(); - for field in &fields { + struct_.fields = Fields::Unit; + let info = StructInfo { + args, + struct_, + fields, + is_tuple_struct, + }; + + for field in &info.fields { if !field.pinned && is_phantom_pinned(&field.field.ty) { dcx.warn( - field.field, + &field.field, format!( "The field {} of type `PhantomPinned` only has an effect \ if it has the `#[pin]` attribute", @@ -181,20 +195,14 @@ fn expand( } } - let unpin_impl = generate_unpin_impl(&struct_.ident, &struct_.generics, &fields); - let drop_impl = generate_drop_impl(&struct_.ident, &struct_.generics, args); - let projections = generate_projections( - &struct_.vis, - &struct_.ident, - &struct_.generics, - is_tuple_struct, - &fields, - ); - let the_pin_data = - generate_the_pin_data(&struct_.vis, &struct_.ident, &struct_.generics, &fields); + let struct_def = generate_struct_def(&info); + let unpin_impl = generate_unpin_impl(&info); + let drop_impl = generate_drop_impl(&info); + let projections = generate_projections(&info); + let the_pin_data = generate_the_pin_data(&info); Ok(quote! { - #struct_ + #struct_def // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { @@ -230,17 +238,63 @@ fn is_phantom_pinned(ty: &Type) -> bool { } } -fn generate_unpin_impl( - ident: &Ident, - generics: &Generics, - fields: &[FieldInfo<'_>], -) -> TokenStream { +fn generate_struct_def(info: &StructInfo) -> TokenStream { + let ItemStruct { + attrs, + vis, + struct_token, + ident, + generics, + fields: _, + semi_token, + } = &info.struct_; + + let generated_fields = info.fields.iter().map(|field| { + let Field { + attrs, + vis, + mutability: _, + ident, + colon_token, + ty, + } = &field.field; + + quote! { + #(#attrs)* #vis #ident #colon_token #ty + } + }); + + let whr = &generics.where_clause; + + if info.is_tuple_struct { + quote!( + #(#attrs)* + #vis + #struct_token #ident #generics (#(#generated_fields,)*) #whr + #semi_token + ) + } else { + quote!( + #(#attrs)* + #vis + #struct_token #ident #generics #whr { + #(#generated_fields,)* + } + #semi_token + ) + } +} + +fn generate_unpin_impl(info: &StructInfo) -> TokenStream { + let ItemStruct { + generics, ident, .. + } = &info.struct_; let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let predicates = whr .map(|x| &x.predicates) .unwrap_or(const { &Punctuated::new() }); - let pinned_fields = fields.iter().filter(|f| f.pinned).map(|f| { + let pinned_fields = info.fields.iter().filter(|f| f.pinned).map(|f| { let ident = f.member.as_ident(); let ty = &f.field.ty; quote!( @@ -271,9 +325,12 @@ fn generate_unpin_impl( } } -fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenStream { +fn generate_drop_impl(info: &StructInfo) -> TokenStream { + let ItemStruct { + generics, ident, .. + } = &info.struct_; let (impl_generics, ty_generics, whr) = generics.split_for_impl(); - let has_pinned_drop = matches!(args, Args::PinnedDrop(_)); + let has_pinned_drop = matches!(info.args, Args::PinnedDrop(_)); // We need to disallow normal `Drop` implementation, the exact behavior depends on whether // `PinnedDrop` was specified in `args`. if has_pinned_drop { @@ -318,13 +375,13 @@ fn generate_drop_impl(ident: &Ident, generics: &Generics, args: Args) -> TokenSt } } -fn generate_projections( - vis: &Visibility, - ident: &Ident, - generics: &Generics, - is_tuple_struct: bool, - fields: &[FieldInfo<'_>], -) -> TokenStream { +fn generate_projections(info: &StructInfo) -> TokenStream { + let ItemStruct { + vis, + ident, + generics, + .. + } = &info.struct_; let this_lt_generics: Generics = parse_quote!(<'__this>); let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); @@ -333,14 +390,15 @@ fn generate_projections( let this = format_ident!("this"); - let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = fields + let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info + .fields .iter() .map(|field| { let Field { vis, ty, .. } = &field.field; let member = &field.member; // The projection of a tuple struct is a tuple struct itself, so its fields are // positional and must not be named. - let name = (!is_tuple_struct).then(|| { + let name = (!info.is_tuple_struct).then(|| { let ident = field.member.as_ident(); quote!(#ident:) }); @@ -367,16 +425,18 @@ fn generate_projections( } }) .collect(); - let structurally_pinned_fields_docs = fields + let structurally_pinned_fields_docs = info + .fields .iter() .filter(|f| f.pinned) .map(|f| format!(" - {}", f.member.display_name())); - let not_structurally_pinned_fields_docs = fields + let not_structurally_pinned_fields_docs = info + .fields .iter() .filter(|f| !f.pinned) .map(|f| format!(" - {}", f.member.display_name())); let docs = format!(" Pin-projections of [`{ident}`]"); - let (projection_def, projection_init) = if is_tuple_struct { + let (projection_def, projection_init) = if info.is_tuple_struct { ( quote! { #vis struct __Projection #generics_with_this_lt ( @@ -439,22 +499,24 @@ fn generate_projections( } } -fn generate_the_pin_data( - vis: &Visibility, - struct_name: &Ident, - generics: &Generics, - fields: &[FieldInfo<'_>], -) -> TokenStream { +fn generate_the_pin_data(info: &StructInfo) -> TokenStream { + let ItemStruct { + vis, + ident: struct_name, + generics, + .. + } = &info.struct_; let (impl_generics, ty_generics, whr) = generics.split_for_impl(); // For every field, we create an initializing projection function according to its projection // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with // `Unpinned` which allows initialization via `Init`. - let field_accessors = fields + let field_accessors = info + .fields .iter() .map(|f| { - let Field { vis, ty, .. } = f.field; + let Field { vis, ty, .. } = &f.field; let field_name = f.member.as_ident(); let member = &f.member; let pin_marker = if f.pinned { From 72a61a7fef954152abd9df9def4f94816eb344ca Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Wed, 7 Oct 2026 03:44:13 -0500 Subject: [PATCH 1492/2286] checkkconfigsymbols.py: Parse Git status output line by line tree_is_dirty() iterates over the string returned by execute() one character at a time. Its status check therefore examines filenames as well as the two status columns. An otherwise clean tree containing an untracked README is reported as dirty because its name contains letters matched by [URMADC]. This prevents --commit and --diff from running, whereas an untracked notes.txt does not trigger the check. Split the output into lines before examining the two status columns so that filenames do not affect the dirty-tree check. Fixes: b1a3f243485f ("checkkconfigsymbols.py: make it Git aware") Assisted-by: LLM Signed-off-by: Ting-Han Hou Link: https://patch.msgid.link/CA+NtrhQqXgjT+4eg6W4P94Hc=DqdV3gQXz3EKKCxWvdWzfND6A@mail.gmail.com Signed-off-by: Nathan Chancellor --- scripts/checkkconfigsymbols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/checkkconfigsymbols.py b/scripts/checkkconfigsymbols.py index 36c920e7131373..9f7aed98d91c0c 100755 --- a/scripts/checkkconfigsymbols.py +++ b/scripts/checkkconfigsymbols.py @@ -245,7 +245,7 @@ def tree_is_dirty(): """Return true if the current working tree is dirty (i.e., if any file has been added, deleted, modified, renamed or copied but not committed).""" stdout = execute(["git", "status", "--porcelain"]) - for line in stdout: + for line in stdout.splitlines(): if re.findall(r"[URMADC]{1}", line[:2]): return True return False From 277e5657237a92e279ee28da2f5c07902cc6aa0b Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 04:54:26 +0000 Subject: [PATCH 1493/2286] clocksource/drivers/fttmr010: Fix clk reference leak in error paths fttmr010_common_init() obtains the PCLK reference with of_clk_get_by_name() but never calls clk_put(). If clk_prepare_enable() fails the reference is leaked directly, and every error path that jumps to out_disable_clock only calls clk_disable_unprepare(), which releases the prepare/enable state but not the reference obtained from of_clk_get_by_name(). Add clk_put() to the clk_prepare_enable() failure path and to the out_disable_clock label so the clock reference is no longer leaked. The success path keeps PCLK enabled for the lifetime of the timer and is left untouched. Fixes: dd98442e17a6 ("clocksource/drivers/fttmr010: Drop Gemini specifics") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Daniel Lezcano Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260915045426.1726805-1-vulab@iscas.ac.cn --- drivers/clocksource/timer-fttmr010.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clocksource/timer-fttmr010.c b/drivers/clocksource/timer-fttmr010.c index 308f94bfbaed23..c2553851e8d6f8 100644 --- a/drivers/clocksource/timer-fttmr010.c +++ b/drivers/clocksource/timer-fttmr010.c @@ -292,6 +292,7 @@ static int __init fttmr010_common_init(struct device_node *np, ret = clk_prepare_enable(clk); if (ret) { pr_err("failed to enable PCLK\n"); + clk_put(clk); return ret; } @@ -432,6 +433,7 @@ static int __init fttmr010_common_init(struct device_node *np, kfree(fttmr010); out_disable_clock: clk_disable_unprepare(clk); + clk_put(clk); return ret; } From 9cc2e18b32f12d26ae6729c62a03640423956b17 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 04:52:42 +0000 Subject: [PATCH 1494/2286] clocksource/drivers/digicolor: Fix clk reference leak on request_irq failure digicolor_timer_init() obtains the timer clock with of_clk_get() and enables it, but the request_irq() failure path returns without releasing the clock reference, leaking both the enable/prepare state and the reference itself. Disable and put the clock before returning on the request_irq() failure path. The success path keeps the clock enabled for the lifetime of the timer and is left untouched. Fixes: 9b8bb7736b78 ("clocksource: Driver for Conexant Digicolor SoC timer") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Signed-off-by: Daniel Lezcano Acked-by: Baruch Siach Link: https://patch.msgid.link/20260915045242.1726702-1-vulab@iscas.ac.cn --- drivers/clocksource/timer-digicolor.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/clocksource/timer-digicolor.c b/drivers/clocksource/timer-digicolor.c index 559aa96089c344..46e0c329957a26 100644 --- a/drivers/clocksource/timer-digicolor.c +++ b/drivers/clocksource/timer-digicolor.c @@ -190,6 +190,8 @@ static int __init digicolor_timer_init(struct device_node *node) &dc_timer_dev.ce); if (ret) { pr_warn("request of timer irq %d failed (%d)\n", irq, ret); + clk_disable_unprepare(clk); + clk_put(clk); return ret; } From 36790ef57803400339d8ea4081d85483264dbd6c Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Tue, 15 Sep 2026 05:45:52 +0000 Subject: [PATCH 1495/2286] clocksource/drivers/timer-econet-en751221: Fix clock reference leak timer_init() obtains the timer clock with of_clk_get() and only reads its frequency with clk_get_rate(); the clock is never prepared nor enabled. All four return paths after the acquisition (of_iomap() failure, clocksource_mmio_init() failure, cevt_init() failure and success) return without clk_put(), leaking the consumer reference. Drop the reference with clk_put() right after the frequency has been read. The clock enable state is untouched. Fixes: 3b4c33ac87d0 ("clocksource/drivers: Add EcoNet Timer HPT driver") Signed-off-by: Wentao Liang Signed-off-by: Daniel Lezcano Tested-by: Caleb James DeLisle Link: https://patch.msgid.link/20260915054552.1729612-1-vulab@iscas.ac.cn --- drivers/clocksource/timer-econet-en751221.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/clocksource/timer-econet-en751221.c b/drivers/clocksource/timer-econet-en751221.c index 4008076b1a2109..1859335345b593 100644 --- a/drivers/clocksource/timer-econet-en751221.c +++ b/drivers/clocksource/timer-econet-en751221.c @@ -181,6 +181,7 @@ static int __init timer_init(struct device_node *np) } econet_timer.freq_hz = clk_get_rate(clk); + clk_put(clk); for (int i = 0; i < num_blocks; i++) { econet_timer.membase[i] = of_iomap(np, i); From 2d3ee0bdb321ad84d9cf18d4bfd2b839293a695d Mon Sep 17 00:00:00 2001 From: Marek Maslanka Date: Wed, 7 Oct 2026 05:38:37 +0200 Subject: [PATCH 1496/2286] platform/chrome: cros_ec_typec: Poll for role swap completion Depending on the EC USB-C stack (TCPMv2 vs PDC), EC_CMD_USB_PD_CONTROL with a role swap request may either block until the swap completes or queue the request asynchronously for the EC PD task and return the current (pre-swap) role in the immediate response. On platforms where the EC handles the swap asynchronously, checking resp.role right after issuing the swap command sees the old role and fails with -EIO before the USB PD handshake completes. If the immediate response does not yet reflect the target role, poll EC_CMD_USB_PD_CONTROL with USB_PD_CTRL_SWAP_NONE until the target role is reached while connected, or until ROLE_SWAP_TIMEOUT_MS expires. The timeout mirrors PD_ROLE_SWAP_TIMEOUT used by the TCPM port manager, since a PR_Swap may legitimately take seconds when the partner answers with Wait and the initiator retries. While at it, report the failure modes the same way tcpm.c does, so that userspace can tell a rejected swap from a disconnect: -ETIMEDOUT when the partner never completes the swap, and -ENOTCONN when the port goes away while waiting. Decoding of the swapped role is factored out into cros_typec_resp_to_role() instead of being open coded for each swap type, which also keeps an unhandled swap type from being misread as a power role swap. Fixes: ab229c2b72c3 ("platform/chrome: cros_ec_typec: Add role swap ops") Signed-off-by: Marek Maslanka Link: https://lore.kernel.org/r/20261007033837.708119-1-mmaslanka@google.com Signed-off-by: Tzung-Bi Shih --- drivers/platform/chrome/cros_ec_typec.c | 94 +++++++++++++++++-------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index 4427897eb3f59f..5871fe031f14d3 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -24,6 +26,14 @@ #define DRV_NAME "cros-ec-typec" +/* + * Mirrors PD_ROLE_SWAP_TIMEOUT used by the TCPM port manager: a PR_Swap may + * legitimately take seconds when the partner answers with Wait and the + * initiator retries. + */ +#define ROLE_SWAP_TIMEOUT_MS 10000 +#define ROLE_SWAP_DELAY_MS 20 + #define DP_PORT_VDO (DP_CAP_DFP_D | DP_CAP_RECEPTACLE | \ DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | \ BIT(DP_PIN_ASSIGN_D) | \ @@ -60,12 +70,33 @@ static int cros_typec_enter_usb_mode(struct typec_port *tc_port, enum usb_mode m &req, sizeof(req), NULL, 0); } +/* + * Decode the role relevant to @swap_type out of an EC_CMD_USB_PD_CONTROL + * response. + * + * Returns a value comparable against the typec class target role + * (TYPEC_DEVICE/TYPEC_HOST or TYPEC_SINK/TYPEC_SOURCE), or -EOPNOTSUPP if + * @swap_type is not a role swap this driver knows how to track. + */ +static int cros_typec_resp_to_role(struct ec_response_usb_pd_control_v2 *resp, u8 swap_type) +{ + switch (swap_type) { + case USB_PD_CTRL_SWAP_DATA: + return (resp->role & PD_CTRL_RESP_ROLE_DATA) ? TYPEC_HOST : TYPEC_DEVICE; + case USB_PD_CTRL_SWAP_POWER: + return (resp->role & PD_CTRL_RESP_ROLE_POWER) ? TYPEC_SOURCE : TYPEC_SINK; + default: + return -EOPNOTSUPP; + } +} + static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_role, u8 swap_type) { struct cros_typec_port *port = typec_get_drvdata(tc_port); struct cros_typec_data *data = port->typec_data; struct ec_response_usb_pd_control_v2 resp; struct ec_params_usb_pd_control req; + unsigned long end; int role, ret; /* Must be at least v1 to support role swap. */ @@ -83,18 +114,10 @@ static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_r if (ret < 0) return ret; - switch (swap_type) { - case USB_PD_CTRL_SWAP_DATA: - role = (resp.role & PD_CTRL_RESP_ROLE_DATA) ? TYPEC_HOST : - TYPEC_DEVICE; - break; - case USB_PD_CTRL_SWAP_POWER: - role = (resp.role & PD_CTRL_RESP_ROLE_POWER) ? TYPEC_SOURCE : - TYPEC_SINK; - break; - default: + role = cros_typec_resp_to_role(&resp, swap_type); + if (role < 0) { dev_warn(data->dev, "Unsupported role swap type %d\n", swap_type); - return -EOPNOTSUPP; + return role; } if (role == target_role) @@ -106,28 +129,41 @@ static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_r if (ret < 0) return ret; - switch (swap_type) { - case USB_PD_CTRL_SWAP_DATA: - role = resp.role & PD_CTRL_RESP_ROLE_DATA ? TYPEC_HOST : TYPEC_DEVICE; - if (role != target_role) { - dev_err(data->dev, "Data role swap failed despite EC returning success\n"); - return -EIO; + /* + * Depending on the EC USB-C stack (TCPMv2 vs PDC), EC_CMD_USB_PD_CONTROL + * may either block until the swap completes or queue the request + * asynchronously and return the pre-swap role. Poll until the target + * role is reached while connected, or until the timeout expires. + */ + req.swap = USB_PD_CTRL_SWAP_NONE; + end = jiffies + msecs_to_jiffies(ROLE_SWAP_TIMEOUT_MS); + for (;;) { + if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) { + dev_warn(data->dev, "Port disconnected during role swap\n"); + return -ENOTCONN; } - typec_set_data_role(tc_port, target_role); - break; - case USB_PD_CTRL_SWAP_POWER: - role = resp.role & PD_CTRL_RESP_ROLE_POWER ? TYPEC_SOURCE : TYPEC_SINK; - if (role != target_role) { - dev_err(data->dev, "Power role swap failed despite EC returning success\n"); - return -EIO; + + if (cros_typec_resp_to_role(&resp, swap_type) == target_role) + break; + + if (time_after_eq(jiffies, end)) { + dev_err(data->dev, "Timed out waiting for role swap to complete\n"); + return -ETIMEDOUT; } - typec_set_pwr_role(tc_port, target_role); - break; - default: - /* Should never execute */ - break; + + msleep(ROLE_SWAP_DELAY_MS); + ret = cros_ec_cmd(data->ec, data->pd_ctrl_ver, + EC_CMD_USB_PD_CONTROL, &req, sizeof(req), + &resp, sizeof(resp)); + if (ret < 0) + return ret; } + if (swap_type == USB_PD_CTRL_SWAP_DATA) + typec_set_data_role(tc_port, target_role); + else + typec_set_pwr_role(tc_port, target_role); + return 0; } From 2b47be015a5e54f4a3d62afa636ba593de9b67f6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:55:33 -0700 Subject: [PATCH 1497/2286] xfs: flag excessive delalloc rt extents as corruption LOLLM observed that we should not allow repairs to proceed if the count of delayed-allocation realtime extents exceeds the number of free rt extents because we can't install negative numbers into the superblock. It's unclear how we could end up in that state in real life, but let's at least complain loudly about it if we ever encounter it. Cc: stable@vger.kernel.org # v6.2 Fixes: e74331d6fa2c21 ("xfs: online checking of the free rt extent count") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/fscounters.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index b3fb1b4227659d..eb1a095a0fa9b9 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -609,6 +609,13 @@ xchk_fscounters( try_again = true; } + if (!xfs_has_zoned(mp) && fsc->frextents < fsc->frextents_delayed) { + if (fsc->frozen) + xchk_set_incomplete(sc); + else + try_again = true; + } + if (!xfs_has_zoned(mp) && !xchk_fscount_within_range(sc, frextents, &mp->m_free[XC_FREE_RTEXTENTS].count, From f5b4d8c8ce16675f77c25a4ff06caaa9a2739f31 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:55:48 -0700 Subject: [PATCH 1498/2286] xfs: zero out di_metatype when removing the metadir file iflag LOLLM pointed out that we should zero di_metatype (fka di_onlink) when we're clearing the METADIR inode flag because the file is no longer a metadata file. Cc: stable@vger.kernel.org # v6.13 Fixes: 7297fd0bebbd70 ("xfs: enforce metadata inode flag") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 47557a30a5f269..e1a7a2729b479d 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -579,8 +579,10 @@ xrep_dinode_flags( fa = xfs_dinode_verify_metadir(sc->mp, dip, mode, flags, flags2); - if (fa) + if (fa) { flags2 &= ~XFS_DIFLAG2_METADATA; + dip->di_metatype = cpu_to_be16(XFS_METAFILE_UNKNOWN); + } } dip->di_flags = cpu_to_be16(flags); From 780212c8d8105eadb5f56b4c7aafb94937755582 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:56:04 -0700 Subject: [PATCH 1499/2286] xfs: forcibly reset quota timers when upgrading to bigtime during repair If we decide to upgrade a dquot to BIGTIME as part of repairing it, we should zero out the timer fields so that they are re-evaluated and reset in bigtime format instead of letting through whatever was in there before. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 6a2b3271264cc7..fd14d81ee4e9e8 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -343,6 +343,8 @@ xrep_quota_block( bp->b_ops = &xfs_dquot_buf_ops; for (i = 0; i < qi->qi_dqperchunk; i++, dqblk++) { struct xfs_disk_dquot *ddq = &dqblk->dd_diskdq; + bool was_bigtime = + !!(ddq->d_type & XFS_DQTYPE_BIGTIME); trace_xrep_disk_dquot(sc->mp, dqtype, id + i); @@ -351,8 +353,21 @@ xrep_quota_block( ddq->d_type = dqtype; ddq->d_id = cpu_to_be32(id + i); - if (xfs_has_bigtime(sc->mp) && ddq->d_id) + if (xfs_has_bigtime(sc->mp) && ddq->d_id) { + /* + * If something was broken with this dquot and it was a + * non-bigtime dquot, we'll grant everyone a fresh + * grace period by zeroing the timer field rather than + * try to interpret what might be garbage. + */ + if (!was_bigtime) { + ddq->d_btimer = 0; + ddq->d_itimer = 0; + ddq->d_rtbtimer = 0; + } + ddq->d_type |= XFS_DQTYPE_BIGTIME; + } xrep_quota_fix_timer(sc->mp, ddq, ddq->d_blk_softlimit, ddq->d_bcount, &ddq->d_btimer, From 7f5e8bf335e5e257fb82cf5b3521980ded60e863 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:56:20 -0700 Subject: [PATCH 1500/2286] xfs: don't pass iget failures up when marking ondisk inodes LOLLM observes a couple of problems with this callsite -- an ENOMEM return is completely ignored and a subsequent run through the loop body obliterates the return value completely. Also, an error value indicating that the list is corrupt can get passed up (thus terminating the repair) even though we can handle corrupt lists. Fix the callsite not to have these problems. Cc: stable@vger.kernel.org # v6.10 Fixes: ab97f4b1c03075 ("xfs: repair AGI unlinked inode bucket lists") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 2b0cd7d1ad0389..497701cf96940e 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1348,8 +1348,12 @@ xrep_iunlink_mark_ondisk_rec( */ error = xchk_iget(ragi->sc, xfs_agino_to_ino(sc->sa.pag, agino), &ip); - if (error) + if (error == -ENOMEM) + return error; + if (error) { + error = 0; continue; + } trace_xrep_iunlink_reload_ondisk(ip); From 71cb13685e72a3c5d118732559c727a0c9090b7f Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:56:35 -0700 Subject: [PATCH 1501/2286] xfs: always reset incore attr fork buffer when resetting fork LOLLM noticed that the local format attr fork verifier depends on if_bytes being set correctly. However, the fork reset function doesn't reset that correctly, which can cause post-repair verifier failures. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: e5d7ce0364d8ee ("xfs: replay unlocked parent pointer updates that accrue during xattr repair") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 387ad909e20bb9..98a74066b24000 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -840,11 +840,11 @@ xrep_xattr_full_reset( ASSERT(ifp->if_bytes == 0); ifp->if_format = XFS_DINODE_FMT_LOCAL; - xfs_idata_realloc(sc->tempip, sizeof(*hdr), XFS_ATTR_FORK); } /* Reinitialize the attr fork to an empty shortform structure. */ - hdr = ifp->if_data; + hdr = xfs_idata_realloc(sc->tempip, + (int64_t)sizeof(*hdr) - ifp->if_bytes, XFS_ATTR_FORK); memset(hdr, 0, sizeof(*hdr)); hdr->totsize = cpu_to_be16(sizeof(*hdr)); xfs_trans_log_inode(sc->tp, sc->tempip, XFS_ILOG_CORE | XFS_ILOG_ADATA); From e5bde75a816b3549942f4b09a8bf09006b7ea9de Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:56:51 -0700 Subject: [PATCH 1502/2286] xfs: propagate errors while checking null siblings LOLLM observes that if xfs_da3_path_shift returns an error here, scrub doesn't handle it the way that it handles most runtime errors. Instead, it drops the error and keeps going, even though that's not how that's supposed to work. Fix that by calling xchk_da_process_error to set corruption flags and then jump out of the flow of execution. Cc: stable@vger.kernel.org # v4.15 Fixes: 7c4a07a424c18d ("xfs: scrub directory/attribute btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dabtree.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/dabtree.c b/fs/xfs/scrub/dabtree.c index 639228601cc6bc..bbf6ece58828ca 100644 --- a/fs/xfs/scrub/dabtree.c +++ b/fs/xfs/scrub/dabtree.c @@ -253,9 +253,10 @@ xchk_da_btree_block_check_sibling( if (sibling == 0) { error = xfs_da3_path_shift(ds->state, altpath, direction, false, &retval); - if (error == 0 && retval == 0) + if (!xchk_da_process_error(ds, level, &error)) + goto out; + if (retval == 0) xchk_da_set_corrupt(ds, level); - error = 0; goto out; } From 1e7337a3ecebab1bb2ba02187d88b94e5146e4f0 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:57:07 -0700 Subject: [PATCH 1503/2286] xfs: don't try to scrub self-referential dirent in metapath checker LOLLM observes that the _ilock_both helper will loop forever trying to double-lock the same inode if mpath->dp == sc->ip. Callers are not supposed to call us with parent == child because that makes no sense. Trigger an assertion, mark the scan incomplete, and return IO error so that this fails noisily. Cc: stable@vger.kernel.org # v6.13 Fixes: b3c03efa5972f0 ("xfs: check metadata directory file path connectivity") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index e0ee7d9b903ff0..4cc0d37b529f97 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -319,6 +319,13 @@ xchk_metapath( return 0; } + /* Callers should not set the parent to the child. */ + if (mpath->dp == sc->ip) { + ASSERT(mpath->dp != sc->ip); + xchk_set_incomplete(sc); + return -EIO; + } + xchk_trans_alloc_empty(sc); error = xchk_metapath_ilock_both(mpath); From 20c5a56bc4f2329872d21561e4a2d4b8576004c9 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:57:22 -0700 Subject: [PATCH 1504/2286] xfs: don't allow sm_agno for non-group metadir path checking LOLLM noticed that the metadata directory tree path checker accepts non-zero values in the sm_agno field for non-group metadata. Make that case return -EINVAL. Cc: stable@vger.kernel.org # v6.13 Fixes: a74923333d9c3b ("xfs: scrub metadir paths for rtgroup metadata") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index 4cc0d37b529f97..1488c8bcb52490 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -129,6 +129,8 @@ static int xchk_setup_metapath_rtdir( struct xfs_scrub *sc) { + if (sc->sm->sm_agno) + return -EINVAL; if (!sc->mp->m_rtdirip) return -ENOENT; @@ -176,6 +178,8 @@ xchk_setup_metapath_quotadir( { struct xfs_quotainfo *qi = sc->mp->m_quotainfo; + if (sc->sm->sm_agno) + return -EINVAL; if (!qi || !qi->qi_dirip) return -ENOENT; @@ -192,6 +196,8 @@ xchk_setup_metapath_dqinode( struct xfs_quotainfo *qi = sc->mp->m_quotainfo; struct xfs_inode *ip = NULL; + if (sc->sm->sm_agno) + return -EINVAL; if (!qi) return -ENOENT; From e98643f794d0d7a7fde8b29276d74e09fa0d5d19 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:57:38 -0700 Subject: [PATCH 1505/2286] xfs: init inode number for tracepoint LOLLM complains that if the xchk_dir_lookup call fails, it won't set ino to anything. ino is then passed into the tracepoint function, which means that it reports random stack garbage. Set it to NULLFSINO instead so that the circumstance is more obvious. Cc: stable@vger.kernel.org # v6.13 Fixes: 0d2c636e489c11 ("xfs: repair metadata directory file path connectivity") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/metapath.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/metapath.c b/fs/xfs/scrub/metapath.c index 1488c8bcb52490..eb987f8c994d25 100644 --- a/fs/xfs/scrub/metapath.c +++ b/fs/xfs/scrub/metapath.c @@ -522,7 +522,7 @@ xrep_metapath_try_link( xfs_ino_t *alleged_child) { struct xfs_scrub *sc = mpath->sc; - xfs_ino_t ino; + xfs_ino_t ino = NULLFSINO; int error; /* Allocate transaction, lock inodes, join to transaction. */ @@ -621,7 +621,7 @@ xrep_metapath_try_unlink( { struct xfs_scrub *sc = mpath->sc; struct xfs_inode *ip = NULL; - xfs_ino_t ino; + xfs_ino_t ino = NULLFSINO; int error; ASSERT(*alleged_child != I_INO(sc->ip)); From 9a263723d7c9ab0615bab3f5cc145d3d24454a5c Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:57:53 -0700 Subject: [PATCH 1506/2286] xfs: release ip after unlinked list store failure If, while processing an unlinked list we see an inode that has nonzero link count, we fail to store the incore prev/next pointers, we should release the incore inode instead of leaking it. This was in the llm fixes branch but fell out for -rc7. Weird. Cc: stable@vger.kernel.org # v7.2-rc7 Fixes: 0052633527158b ("xfs: don't ignore runtime errors in xrep_iunlink_reload_next") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader_repair.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c index 497701cf96940e..32dd09ac3a55de 100644 --- a/fs/xfs/scrub/agheader_repair.c +++ b/fs/xfs/scrub/agheader_repair.c @@ -1104,12 +1104,9 @@ xrep_iunlink_reload_next( if (VFS_I(ip)->i_nlink != 0) { error = xrep_iunlink_store_next(ragi, agino, NULLAGINO); if (error) - return error; + goto rele; error = xrep_iunlink_store_prev(ragi, agino, LINKED_AGINO); - if (error) - return error; - goto rele; } @@ -1123,7 +1120,7 @@ xrep_iunlink_reload_next( */ rele: xchk_irele(sc, ip); - return 0; + return error; } /* From 52aa50cea408e27454d206cccecbe4f694a6c1e1 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:58:09 -0700 Subject: [PATCH 1507/2286] xfs: always zero the whole shortform header when zeroing attr fork LOLLM noticed that xrep_xattr_fork_remove can decide to reset the attr fork contents to an empty shortform header, but then fails to zero the padding byte in the header. Fix that. Cc: stable@vger.kernel.org # v6.10 Fixes: e47dcf113ae348 ("xfs: repair extended attributes") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 98a74066b24000..4a6124b592b525 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -970,7 +970,7 @@ xrep_xattr_fork_remove( ifp->if_format = XFS_DINODE_FMT_LOCAL; hdr = xfs_idata_realloc(ip, (int)sizeof(*hdr) - ifp->if_bytes, XFS_ATTR_FORK); - hdr->count = 0; + memset(hdr, 0, sizeof(*hdr)); hdr->totsize = cpu_to_be16(sizeof(*hdr)); xfs_trans_log_inode(sc->tp, ip, XFS_ILOG_CORE | XFS_ILOG_ADATA); From 41ee987f53785b9443388a756912b1a0fe3c9592 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Sun, 4 Oct 2026 22:58:25 -0700 Subject: [PATCH 1508/2286] xfs: always forget cached ACLs if the attr fork swap succeeds LOLLM complains that xrep_xattr_rebuild_tree omits the xfs_forget_acl call if the attr fork swap succeeds (and hence the acls might have changed) but reaping the old attr fork fails. Fix it so that we always purge the cached acls if the swap succeeds, even if subsequent steps do not. This avoids some cache coherency problems with access controls. Cc: stable@vger.kernel.org # v6.10 Fixes: e47dcf113ae348 ("xfs: repair extended attributes") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index 4a6124b592b525..c7695d4cc8e7cc 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1491,7 +1491,7 @@ xrep_xattr_rebuild_tree( error = xrep_xattr_reset_tempfile_fork(sc); if (error) - return error; + goto forget_acls; /* * Roll to get a transaction without any inodes joined to it. Then we @@ -1500,7 +1500,7 @@ xrep_xattr_rebuild_tree( */ error = xfs_trans_roll(&sc->tp); if (error) - return error; + goto forget_acls; xrep_tempfile_iunlock(sc); xrep_tempfile_iounlock(sc); @@ -1509,7 +1509,7 @@ xrep_xattr_rebuild_tree( /* Invalidate cached ACLs now that we've reloaded all the xattrs. */ xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_FILE); xfs_forget_acl(VFS_I(sc->ip), SGI_ACL_DEFAULT); - return 0; + return error; } /* Tear down all the incore scan stuff we created. */ From e3c682dd11303a95ff82ea63b9172dce788578d4 Mon Sep 17 00:00:00 2001 From: Daejun Park Date: Tue, 6 Oct 2026 09:34:25 +0900 Subject: [PATCH 1509/2286] xfs: map pNFS layouts to the end of the extent again Since commit 36ca6f11424a ("xfs: fix overlapping extents returned for pNFS LAYOUTGET"), xfs_fs_map_blocks() maps only the range that nfsd asks for. For O_DIRECT, the Linux block layout client asks for the range of the I/O at hand, so it now needs a LAYOUTGET for every O_DIRECT read or write to a part of a file it has no layout for yet, where the first LAYOUTGET used to return the whole extent. Each LAYOUTGET is a round trip, and xfs_fs_map_blocks() takes the iolock exclusively and writes back and invalidates the page cache of the file for it. On three QEMU VMs (an NVMe/TCP target, the server with nfsd and XFS, and a client) running the same kernel without KASAN or lock debugging, a client doing O_DIRECT I/O over the block layout of NFSv4.2 for 30 seconds to a 1 GiB file of one written extent, in order unless noted and with an fsync every 16 MiB written, gets this many I/Os done (and LAYOUTGETs counted at the server), one run each: unpatched ENTIRE, no trim this patch 4 KiB reads 41207 (41207) 334931 (1) 343749 (1) 4 KiB random 36670 (34148) 259527 (1) 263829 (15) 4 KiB overwrite 38556 (38556) 325533 (1) 307105 (1) 64 KiB reads 70241 (16384) 80829 (1) 91196 (1) 1 MiB reads 6857 (1024) 6759 (1) 6706 (1) "ENTIRE, no trim" is a test kernel that maps with XFS_BMAPI_ENTIRE and does not trim. The random reads need a LAYOUTGET each time they go before the lowest offset read so far, as the part of the extent before the offset asked for is not mapped. 1 MiB overwrites and writes to an unwritten extent also go from 1024 LAYOUTGETs to 1, with no clear change in I/Os done. Appending to a new file needs a LAYOUTGET per 1 MiB appended either way, as XFS allocates only the range asked for on a filesystem without a stripe unit or an extent size hint. The overlap fixed by that commit comes from XFS_BMAPI_ENTIRE reaching back. nfsd4_block_proc_layoutget() calls ->map_blocks once per extent of a LAYOUTGET, each time for the range left after the previous extent. An allocation in one call can merge the extent that the next call starts in with the extents before it, and the whole extent then starts before the offset of that call and overlaps extents already in the layout. The Linux client rejects such a layout (verify_extent() returns -EIO), and the I/O that needed it fails. In the thread of that commit, Christoph Hellwig asked for XFS_BMAPI_ENTIRE to be dropped to stop the overlap, Darrick J. Wong agreed, and it was said that the flag makes no difference on the first call, which is for the whole range the client asked for. The difference is past that range: the Linux client asks for the range of each O_DIRECT I/O and uses the rest of a longer layout for the I/Os that follow, which RFC 8881 allows (Table 22 sets only a minimum length). A client could ask for more for a read layout, but for a write layout xfs_fs_map_blocks() allocates any hole in the range asked for. Dave Chinner suggested keeping the flag for the first call and trimming the mappings of the calls that follow. ->map_blocks cannot tell the first call from the others, and Christoph preferred to keep such a choice out of that interface, so trim every mapping to start at the offset asked for instead. No mapping can then overlap the one before it, since each call starts where the previous extent ended. Unlike Dave's suggestion, the first mapping loses the part of the extent before the offset, and the last mapping keeps the part past the end of the range, which the loop in nfsd4_block_proc_layoutget() already handles. Trimming in that loop instead would keep his suggestion exactly, but would change nfsd as well, while trimming in XFS keeps every mapping it returns free of overlap whatever the caller does. Unlike before that commit, don't let the mapping reach past EOF beyond the range asked for. On an inode without XFS_DIFLAG_PREALLOC or XFS_DIFLAG_APPEND, xfs_free_eofblocks() can free blocks past EOF, such as speculative preallocation, without breaking the layout, so a client that did not ask for them should not get them. What a client gets for a range past EOF that it asks for does not change. The aio group, the fsx tests and generic/013 (fsstress) of fstests over the block layout, and the fsx tests and generic/013 over the SCSI layout, give the same results with and without this patch. generic/091 and 263 fail with the ENTIRE, no trim kernel and pass with this patch, and so does the pynfs test BLOCK5, which checks a write layout over a hole between two allocated blocks against three rules of RFC 5663 section 2.3.1. Fixes: 36ca6f11424a ("xfs: fix overlapping extents returned for pNFS LAYOUTGET") Cc: stable@vger.kernel.org Suggested-by: Dave Chinner Link: https://lore.kernel.org/r/ageSguSyf2kBY33a@dread Link: https://lore.kernel.org/r/agwDhixPAAA0-cTa@infradead.org Link: https://lore.kernel.org/r/agqfBPRWXQDR2ImG@infradead.org Signed-off-by: Daejun Park Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_pnfs.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_pnfs.c b/fs/xfs/xfs_pnfs.c index f8535ecde21aaf..ab385617003a20 100644 --- a/fs/xfs/xfs_pnfs.c +++ b/fs/xfs/xfs_pnfs.c @@ -183,13 +183,28 @@ xfs_fs_map_blocks( offset_fsb = XFS_B_TO_FSBT(mp, offset); lock_flags = xfs_ilock_data_map_shared(ip); - /* request mappings for the specified range only */ + /* + * Map to the end of the extent that covers the start of the range, + * so that a client doing I/O in pieces gets a layout it can use for + * the pieces that follow. Never map anything before the start of + * the range: nfsd calls in here once per extent of a LAYOUTGET, for + * the range that is left after the previous extent, and the mapping + * can change in between, so a mapping that reaches back can overlap + * one already in the layout. Don't extend the mapping past EOF + * beyond the range either: xfs_free_eofblocks() can free blocks past + * EOF without breaking the layout. + */ error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb, - &imap, &nimaps, 0); + &imap, &nimaps, XFS_BMAPI_ENTIRE); if (error) { xfs_iunlock(ip, lock_flags); goto out_unlock; } + if (nimaps) + xfs_trim_extent(&imap, offset_fsb, + max_t(xfs_fileoff_t, end_fsb, + XFS_B_TO_FSB(mp, XFS_ISIZE(ip))) - + offset_fsb); seq = xfs_iomap_inode_sequence(ip, 0); ASSERT(!nimaps || imap.br_startblock != DELAYSTARTBLOCK); From 6fe0d151eaada4f3ea4947b19ac048b8894b9e88 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:18:26 +0200 Subject: [PATCH 1510/2286] xfs: remove various unused includes in libxfs/ Remove a few unused includes in libxfs/ that cause spurious differences between the kernel and userspace versions of these files. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_defer.c | 1 - fs/xfs/libxfs/xfs_inode_buf.c | 1 - fs/xfs/libxfs/xfs_refcount.c | 1 - fs/xfs/libxfs/xfs_rtbitmap.c | 1 - fs/xfs/libxfs/xfs_rtgroup.c | 1 - fs/xfs/libxfs/xfs_sb.c | 1 - 6 files changed, 6 deletions(-) diff --git a/fs/xfs/libxfs/xfs_defer.c b/fs/xfs/libxfs/xfs_defer.c index 3152acdc335d2f..c5fa4450281dc2 100644 --- a/fs/xfs/libxfs/xfs_defer.c +++ b/fs/xfs/libxfs/xfs_defer.c @@ -24,7 +24,6 @@ #include "xfs_refcount.h" #include "xfs_bmap.h" #include "xfs_alloc.h" -#include "xfs_buf.h" #include "xfs_da_format.h" #include "xfs_da_btree.h" #include "xfs_attr.h" diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index 0340e2189921db..b82f067ca5b18f 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -14,7 +14,6 @@ #include "xfs_inode.h" #include "xfs_errortag.h" #include "xfs_error.h" -#include "xfs_icache.h" #include "xfs_trans.h" #include "xfs_ialloc.h" #include "xfs_dir2.h" diff --git a/fs/xfs/libxfs/xfs_refcount.c b/fs/xfs/libxfs/xfs_refcount.c index 0ec6ccd8b4dcf5..6b67308fa594aa 100644 --- a/fs/xfs/libxfs/xfs_refcount.c +++ b/fs/xfs/libxfs/xfs_refcount.c @@ -26,7 +26,6 @@ #include "xfs_health.h" #include "xfs_refcount_item.h" #include "xfs_rtgroup.h" -#include "xfs_rtalloc.h" #include "xfs_rtrefcount_btree.h" struct kmem_cache *xfs_refcount_intent_cache; diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c index 01536f4fb3868c..7fbfca2a5edd57 100644 --- a/fs/xfs/libxfs/xfs_rtbitmap.c +++ b/fs/xfs/libxfs/xfs_rtbitmap.c @@ -16,7 +16,6 @@ #include "xfs_bmap_btree.h" #include "xfs_trans_space.h" #include "xfs_trans.h" -#include "xfs_rtalloc.h" #include "xfs_error.h" #include "xfs_rtbitmap.h" #include "xfs_health.h" diff --git a/fs/xfs/libxfs/xfs_rtgroup.c b/fs/xfs/libxfs/xfs_rtgroup.c index fe7222bbe449d9..c6981f566860fd 100644 --- a/fs/xfs/libxfs/xfs_rtgroup.c +++ b/fs/xfs/libxfs/xfs_rtgroup.c @@ -27,7 +27,6 @@ #include "xfs_trans.h" #include "xfs_trace.h" #include "xfs_inode.h" -#include "xfs_icache.h" #include "xfs_buf_item.h" #include "xfs_rtgroup.h" #include "xfs_rtbitmap.h" diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c index d2ef7b20a49ffa..ce44123c98cef6 100644 --- a/fs/xfs/libxfs/xfs_sb.c +++ b/fs/xfs/libxfs/xfs_sb.c @@ -26,7 +26,6 @@ #include "xfs_health.h" #include "xfs_ag.h" #include "xfs_rtbitmap.h" -#include "xfs_exchrange.h" #include "xfs_rtgroup.h" #include "xfs_rtrmap_btree.h" #include "xfs_rtrefcount_btree.h" From 5e68008e9c0d5ec602582dd659d4d1c6f910ef4a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:18:27 +0200 Subject: [PATCH 1511/2286] xfs: include in xfs_platform.h This removes the only non-xfs inludes from libxfs/ and thus reduces the differences between the kernel and userspace versions of these files. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_inode_buf.c | 2 -- fs/xfs/libxfs/xfs_inode_util.c | 1 - fs/xfs/libxfs/xfs_trans_inode.c | 2 -- fs/xfs/xfs_platform.h | 1 + 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index b82f067ca5b18f..282e78e5e3e82a 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -20,8 +20,6 @@ #include "xfs_health.h" #include "xfs_metafile.h" -#include - /* * If we are doing readahead on an inode buffer, we might be in log recovery * reading an inode allocation buffer that hasn't yet been replayed, and hence diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c index 258ac3d0d486e6..16c4c2ccdbb83b 100644 --- a/fs/xfs/libxfs/xfs_inode_util.c +++ b/fs/xfs/libxfs/xfs_inode_util.c @@ -3,7 +3,6 @@ * Copyright (c) 2000-2006 Silicon Graphics, Inc. * All Rights Reserved. */ -#include #include "xfs_platform.h" #include "xfs_fs.h" #include "xfs_shared.h" diff --git a/fs/xfs/libxfs/xfs_trans_inode.c b/fs/xfs/libxfs/xfs_trans_inode.c index 1a0fdcbf39fa79..e3dca0652d2664 100644 --- a/fs/xfs/libxfs/xfs_trans_inode.c +++ b/fs/xfs/libxfs/xfs_trans_inode.c @@ -15,8 +15,6 @@ #include "xfs_trans_priv.h" #include "xfs_inode_item.h" -#include - /* * Add a locked inode to the transaction. * diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h index 745d715b4c646b..7b63ea26b186ce 100644 --- a/fs/xfs/xfs_platform.h +++ b/fs/xfs/xfs_platform.h @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include From 834a6a1e9633722df3bfde5f4fa830432faa360a Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:18:28 +0200 Subject: [PATCH 1512/2286] xfs: move xfs_*_item.h to libxfs All these files can be used as-is in xfsprogs to replace various shades of copy and paste. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/{ => libxfs}/xfs_attr_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_bmap_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_buf_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_dquot_item.h | 0 fs/xfs/{ => libxfs}/xfs_exchmaps_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_extfree_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_icreate_item.h | 0 fs/xfs/{ => libxfs}/xfs_inode_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_iunlink_item.h | 0 fs/xfs/{ => libxfs}/xfs_refcount_item.h | 2 -- fs/xfs/{ => libxfs}/xfs_rmap_item.h | 2 -- 11 files changed, 16 deletions(-) rename fs/xfs/{ => libxfs}/xfs_attr_item.h (97%) rename fs/xfs/{ => libxfs}/xfs_bmap_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_buf_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_dquot_item.h (100%) rename fs/xfs/{ => libxfs}/xfs_exchmaps_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_extfree_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_icreate_item.h (100%) rename fs/xfs/{ => libxfs}/xfs_inode_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_iunlink_item.h (100%) rename fs/xfs/{ => libxfs}/xfs_refcount_item.h (98%) rename fs/xfs/{ => libxfs}/xfs_rmap_item.h (98%) diff --git a/fs/xfs/xfs_attr_item.h b/fs/xfs/libxfs/xfs_attr_item.h similarity index 97% rename from fs/xfs/xfs_attr_item.h rename to fs/xfs/libxfs/xfs_attr_item.h index d108a11b55ae7c..3bb3d7108a9078 100644 --- a/fs/xfs/xfs_attr_item.h +++ b/fs/xfs/libxfs/xfs_attr_item.h @@ -6,8 +6,6 @@ #ifndef __XFS_ATTR_ITEM_H__ #define __XFS_ATTR_ITEM_H__ -/* kernel only ATTRI/ATTRD definitions */ - struct xfs_mount; struct kmem_zone; diff --git a/fs/xfs/xfs_bmap_item.h b/fs/xfs/libxfs/xfs_bmap_item.h similarity index 98% rename from fs/xfs/xfs_bmap_item.h rename to fs/xfs/libxfs/xfs_bmap_item.h index b42fee06899df6..381b8a62b47c6c 100644 --- a/fs/xfs/xfs_bmap_item.h +++ b/fs/xfs/libxfs/xfs_bmap_item.h @@ -22,8 +22,6 @@ * bmbt metadata updates in the non-first transaction. */ -/* kernel only BUI/BUD definitions */ - struct xfs_mount; struct kmem_cache; diff --git a/fs/xfs/xfs_buf_item.h b/fs/xfs/libxfs/xfs_buf_item.h similarity index 98% rename from fs/xfs/xfs_buf_item.h rename to fs/xfs/libxfs/xfs_buf_item.h index 28c79989d725c6..d0a1247d22c6fd 100644 --- a/fs/xfs/xfs_buf_item.h +++ b/fs/xfs/libxfs/xfs_buf_item.h @@ -6,8 +6,6 @@ #ifndef __XFS_BUF_ITEM_H__ #define __XFS_BUF_ITEM_H__ -/* kernel only definitions */ - struct xfs_buf; struct xfs_mount; diff --git a/fs/xfs/xfs_dquot_item.h b/fs/xfs/libxfs/xfs_dquot_item.h similarity index 100% rename from fs/xfs/xfs_dquot_item.h rename to fs/xfs/libxfs/xfs_dquot_item.h diff --git a/fs/xfs/xfs_exchmaps_item.h b/fs/xfs/libxfs/xfs_exchmaps_item.h similarity index 98% rename from fs/xfs/xfs_exchmaps_item.h rename to fs/xfs/libxfs/xfs_exchmaps_item.h index efa368d25d09c7..4a42f70237fb69 100644 --- a/fs/xfs/xfs_exchmaps_item.h +++ b/fs/xfs/libxfs/xfs_exchmaps_item.h @@ -22,8 +22,6 @@ * rest of the mapping exchanges. */ -/* kernel only XMI/XMD definitions */ - struct xfs_mount; struct kmem_cache; diff --git a/fs/xfs/xfs_extfree_item.h b/fs/xfs/libxfs/xfs_extfree_item.h similarity index 98% rename from fs/xfs/xfs_extfree_item.h rename to fs/xfs/libxfs/xfs_extfree_item.h index af1b0331f7afa4..4d8ae7438b84eb 100644 --- a/fs/xfs/xfs_extfree_item.h +++ b/fs/xfs/libxfs/xfs_extfree_item.h @@ -6,8 +6,6 @@ #ifndef __XFS_EXTFREE_ITEM_H__ #define __XFS_EXTFREE_ITEM_H__ -/* kernel only EFI/EFD definitions */ - struct xfs_mount; struct kmem_cache; diff --git a/fs/xfs/xfs_icreate_item.h b/fs/xfs/libxfs/xfs_icreate_item.h similarity index 100% rename from fs/xfs/xfs_icreate_item.h rename to fs/xfs/libxfs/xfs_icreate_item.h diff --git a/fs/xfs/xfs_inode_item.h b/fs/xfs/libxfs/xfs_inode_item.h similarity index 98% rename from fs/xfs/xfs_inode_item.h rename to fs/xfs/libxfs/xfs_inode_item.h index 2ddcca41714f7a..226c9ec0143408 100644 --- a/fs/xfs/xfs_inode_item.h +++ b/fs/xfs/libxfs/xfs_inode_item.h @@ -6,8 +6,6 @@ #ifndef __XFS_INODE_ITEM_H__ #define __XFS_INODE_ITEM_H__ -/* kernel only definitions */ - struct xfs_buf; struct xfs_bmbt_rec; struct xfs_inode; diff --git a/fs/xfs/xfs_iunlink_item.h b/fs/xfs/libxfs/xfs_iunlink_item.h similarity index 100% rename from fs/xfs/xfs_iunlink_item.h rename to fs/xfs/libxfs/xfs_iunlink_item.h diff --git a/fs/xfs/xfs_refcount_item.h b/fs/xfs/libxfs/xfs_refcount_item.h similarity index 98% rename from fs/xfs/xfs_refcount_item.h rename to fs/xfs/libxfs/xfs_refcount_item.h index 0fc3f493342bbd..c94b119624b390 100644 --- a/fs/xfs/xfs_refcount_item.h +++ b/fs/xfs/libxfs/xfs_refcount_item.h @@ -22,8 +22,6 @@ * to replay the refcountbt metadata updates. */ -/* kernel only CUI/CUD definitions */ - struct xfs_mount; struct kmem_cache; diff --git a/fs/xfs/xfs_rmap_item.h b/fs/xfs/libxfs/xfs_rmap_item.h similarity index 98% rename from fs/xfs/xfs_rmap_item.h rename to fs/xfs/libxfs/xfs_rmap_item.h index 3a99f0117f2d8a..8e9a23e89fd155 100644 --- a/fs/xfs/xfs_rmap_item.h +++ b/fs/xfs/libxfs/xfs_rmap_item.h @@ -25,8 +25,6 @@ * (rmapbt/bnobt/cntbt) metadata updates in the non-first transaction. */ -/* kernel only RUI/RUD definitions */ - struct xfs_mount; struct kmem_cache; From ce47e8aab95d6bd4f70b9cbc7308ca13b9d57660 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 25 Sep 2026 17:18:29 +0200 Subject: [PATCH 1513/2286] xfs: move xfs_trans.h to libxfs This file can be reused in xfsprogs instead of carrying a slightly modified version. Signed-off-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/{ => libxfs}/xfs_trans.h | 2 -- 1 file changed, 2 deletions(-) rename fs/xfs/{ => libxfs}/xfs_trans.h (99%) diff --git a/fs/xfs/xfs_trans.h b/fs/xfs/libxfs/xfs_trans.h similarity index 99% rename from fs/xfs/xfs_trans.h rename to fs/xfs/libxfs/xfs_trans.h index edb0cfff4d3cec..05b3cc19a311bd 100644 --- a/fs/xfs/xfs_trans.h +++ b/fs/xfs/libxfs/xfs_trans.h @@ -6,8 +6,6 @@ #ifndef __XFS_TRANS_H__ #define __XFS_TRANS_H__ -/* kernel only transaction subsystem defines */ - struct xlog; struct xlog_format_buf; struct xfs_buf; From e029aeece84b0fe4f0aa6b1dd64c0c1c49281cde Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 2 Oct 2026 16:08:09 -0500 Subject: [PATCH 1514/2286] xfs: factor out xfs_dir2_sf_copy_entries helper xfs_dir2_sf_toino8 and xfs_dir2_sf_toino64 share a dozen or so lines of copied code used to move short form directory entries; factor that out to eliminate cut and paste. Signed-off-by: Eric Sandeen Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_dir2_sf.c | 68 ++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/fs/xfs/libxfs/xfs_dir2_sf.c b/fs/xfs/libxfs/xfs_dir2_sf.c index 0567cf8b9c1bb2..a8675a0d574e4a 100644 --- a/fs/xfs/libxfs/xfs_dir2_sf.c +++ b/fs/xfs/libxfs/xfs_dir2_sf.c @@ -1124,6 +1124,34 @@ xfs_dir2_sf_replace( return 0; } +static inline void +xfs_dir2_sf_copy_entries( + struct xfs_mount *mp, + struct xfs_dir2_sf_hdr *sfp, + struct xfs_dir2_sf_hdr *oldsfp) +{ + int i; + struct xfs_dir2_sf_entry *sfep; /* new sf entry */ + struct xfs_dir2_sf_entry *oldsfep; /* old sf entry */ + + /* + * Copy the entries field by field. + */ + for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp), + oldsfep = xfs_dir2_sf_firstentry(oldsfp); + i < sfp->count; + i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep), + oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep)) { + sfep->namelen = oldsfep->namelen; + memcpy(sfep->offset, oldsfep->offset, sizeof(sfep->offset)); + memcpy(sfep->name, oldsfep->name, sfep->namelen); + xfs_dir2_sf_put_ino(mp, sfp, sfep, + xfs_dir2_sf_get_ino(mp, oldsfp, oldsfep)); + xfs_dir2_sf_put_ftype(mp, sfep, + xfs_dir2_sf_get_ftype(mp, oldsfep)); + } +} + /* * Convert from 8-byte inode numbers to 4-byte inode numbers. * The last 8-byte inode number is gone, but the count is still 1. @@ -1136,11 +1164,8 @@ xfs_dir2_sf_toino4( struct xfs_mount *mp = dp->i_mount; struct xfs_dir2_sf_hdr *oldsfp = dp->i_df.if_data; char *buf; /* old dir's buffer */ - int i; /* entry index */ int newsize; /* new inode size */ - xfs_dir2_sf_entry_t *oldsfep; /* old sf entry */ int oldsize; /* old inode size */ - xfs_dir2_sf_entry_t *sfep; /* new sf entry */ xfs_dir2_sf_hdr_t *sfp; /* new sf directory */ trace_xfs_dir2_sf_toino4(args); @@ -1171,22 +1196,7 @@ xfs_dir2_sf_toino4( sfp->count = oldsfp->count; sfp->i8count = 0; xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp)); - /* - * Copy the entries field by field. - */ - for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp), - oldsfep = xfs_dir2_sf_firstentry(oldsfp); - i < sfp->count; - i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep), - oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep)) { - sfep->namelen = oldsfep->namelen; - memcpy(sfep->offset, oldsfep->offset, sizeof(sfep->offset)); - memcpy(sfep->name, oldsfep->name, sfep->namelen); - xfs_dir2_sf_put_ino(mp, sfp, sfep, - xfs_dir2_sf_get_ino(mp, oldsfp, oldsfep)); - xfs_dir2_sf_put_ftype(mp, sfep, - xfs_dir2_sf_get_ftype(mp, oldsfep)); - } + xfs_dir2_sf_copy_entries(mp, sfp, oldsfp); /* * Clean up the inode. */ @@ -1208,11 +1218,8 @@ xfs_dir2_sf_toino8( struct xfs_mount *mp = dp->i_mount; struct xfs_dir2_sf_hdr *oldsfp = dp->i_df.if_data; char *buf; /* old dir's buffer */ - int i; /* entry index */ int newsize; /* new inode size */ - xfs_dir2_sf_entry_t *oldsfep; /* old sf entry */ int oldsize; /* old inode size */ - xfs_dir2_sf_entry_t *sfep; /* new sf entry */ xfs_dir2_sf_hdr_t *sfp; /* new sf directory */ trace_xfs_dir2_sf_toino8(args); @@ -1243,22 +1250,7 @@ xfs_dir2_sf_toino8( sfp->count = oldsfp->count; sfp->i8count = 1; xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp)); - /* - * Copy the entries field by field. - */ - for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp), - oldsfep = xfs_dir2_sf_firstentry(oldsfp); - i < sfp->count; - i++, sfep = xfs_dir2_sf_nextentry(mp, sfp, sfep), - oldsfep = xfs_dir2_sf_nextentry(mp, oldsfp, oldsfep)) { - sfep->namelen = oldsfep->namelen; - memcpy(sfep->offset, oldsfep->offset, sizeof(sfep->offset)); - memcpy(sfep->name, oldsfep->name, sfep->namelen); - xfs_dir2_sf_put_ino(mp, sfp, sfep, - xfs_dir2_sf_get_ino(mp, oldsfp, oldsfep)); - xfs_dir2_sf_put_ftype(mp, sfep, - xfs_dir2_sf_get_ftype(mp, oldsfep)); - } + xfs_dir2_sf_copy_entries(mp, sfp, oldsfp); /* * Clean up the inode. */ From 1e3f1adf6d533b503bc2356129bd7e27c199ad5b Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Wed, 7 Oct 2026 10:25:21 -0500 Subject: [PATCH 1515/2286] xfs: factor out xrep_reset_fork_to_extents helper for scrub/repair The open-coded 8-line series of steps to reset a fork to empty extents format is repeated 3 times; factor this out into a helper to eliminate cut and paste. Signed-off-by: Eric Sandeen Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr_repair.c | 17 ++--------------- fs/xfs/scrub/dir_repair.c | 16 ++-------------- fs/xfs/scrub/repair.c | 31 +++++++++++++++++++++++++++++++ fs/xfs/scrub/repair.h | 1 + fs/xfs/scrub/symlink_repair.c | 16 ++-------------- 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index c7695d4cc8e7cc..f2868fb4d27ab0 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -1319,21 +1319,8 @@ xrep_xattr_swap_prep( * that to an empty extent list in preparation for the atomic mapping * exchange. */ - if (ip_local) { - struct xfs_ifork *ifp; - - ifp = xfs_ifork_ptr(sc->ip, XFS_ATTR_FORK); - - xfs_idestroy_fork(ifp); - ifp->if_format = XFS_DINODE_FMT_EXTENTS; - ifp->if_nextents = 0; - ifp->if_bytes = 0; - ifp->if_data = NULL; - ifp->if_height = 0; - - xfs_trans_log_inode(sc->tp, sc->ip, - XFS_ILOG_CORE | XFS_ILOG_ADATA); - } + if (ip_local) + xrep_reset_fork_to_extents(sc, XFS_ATTR_FORK); return 0; } diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 2cfcf1c35679ea..5db1ec32440908 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1510,20 +1510,8 @@ xrep_dir_swap_prep( * to an empty extent list in preparation for the atomic mapping * exchange. */ - if (ip_local) { - struct xfs_ifork *ifp; - - ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK); - xfs_idestroy_fork(ifp); - ifp->if_format = XFS_DINODE_FMT_EXTENTS; - ifp->if_nextents = 0; - ifp->if_bytes = 0; - ifp->if_data = NULL; - ifp->if_height = 0; - - xfs_trans_log_inode(sc->tp, sc->ip, - XFS_ILOG_CORE | XFS_ILOG_DDATA); - } + if (ip_local) + xrep_reset_fork_to_extents(sc, XFS_DATA_FORK); return 0; } diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 956aa75218aa24..dd00d4ed8c17ba 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -882,6 +882,37 @@ xrep_ino_ensure_extent_count( return 0; } +/* Discard the contents of this fork and initialize as empty extent-format. */ +void +xrep_reset_fork_to_extents( + struct xfs_scrub *sc, + int whichfork) +{ + struct xfs_ifork *ifp = xfs_ifork_ptr(sc->ip, whichfork); + uint ilog_flags = XFS_ILOG_CORE; + + switch (whichfork) { + case XFS_DATA_FORK: + ilog_flags |= XFS_ILOG_DDATA; + break; + case XFS_ATTR_FORK: + ilog_flags |= XFS_ILOG_ADATA; + break; + default: + ASSERT(0); + return; + } + + xfs_idestroy_fork(ifp); + ifp->if_format = XFS_DINODE_FMT_EXTENTS; + ifp->if_nextents = 0; + ifp->if_bytes = 0; + ifp->if_data = NULL; + ifp->if_height = 0; + + xfs_trans_log_inode(sc->tp, sc->ip, ilog_flags); +} + /* * Initialize all the btree cursors for an AG repair except for the btree that * we're rebuilding. diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index 2bb125c4f9bf2b..c1ba462e4426f6 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -81,6 +81,7 @@ int xrep_setup_xfbtree(struct xfs_scrub *sc, const char *descr); int xrep_ino_ensure_extent_count(struct xfs_scrub *sc, int whichfork, xfs_extnum_t nextents); int xrep_reset_perag_resv(struct xfs_scrub *sc); +void xrep_reset_fork_to_extents(struct xfs_scrub *sc, int whichfork); int xrep_bmap(struct xfs_scrub *sc, int whichfork, bool allow_unwritten); int xrep_metadata_inode_forks(struct xfs_scrub *sc); int xrep_setup_ag_rmapbt(struct xfs_scrub *sc); diff --git a/fs/xfs/scrub/symlink_repair.c b/fs/xfs/scrub/symlink_repair.c index 18196136423336..9f6901905bfd88 100644 --- a/fs/xfs/scrub/symlink_repair.c +++ b/fs/xfs/scrub/symlink_repair.c @@ -303,20 +303,8 @@ xrep_symlink_swap_prep( * to an empty extent list in preparation for the atomic mapping * exchange. */ - if (ip_local) { - struct xfs_ifork *ifp; - - ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK); - xfs_idestroy_fork(ifp); - ifp->if_format = XFS_DINODE_FMT_EXTENTS; - ifp->if_nextents = 0; - ifp->if_bytes = 0; - ifp->if_data = NULL; - ifp->if_height = 0; - - xfs_trans_log_inode(sc->tp, sc->ip, - XFS_ILOG_CORE | XFS_ILOG_DDATA); - } + if (ip_local) + xrep_reset_fork_to_extents(sc, XFS_DATA_FORK); return 0; } From 1d55a10278054fbebb9f571c99c32dac42327361 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Fri, 2 Oct 2026 16:08:11 -0500 Subject: [PATCH 1516/2286] xfs: re-use refcount scrub/repair code for rtrefcount Some code in scrub/rtrefcount.c is copied from scrub/refcount.c; it differs only in function & structure names and a couple types and macros, which share the same underlying types and values. Similarly for code in fs/xfs/scrub/[rt]refcount_repair.c Make the refcount versions non-static and use them for rtrefcount scrubbing, eliminate the copies, and add build-time asserts so that the type/value matches mentioned above do not drift. Signed-off-by: Eric Sandeen Reviewed-by: Carlos Maiolino Reviewed-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 27 +--- fs/xfs/scrub/refcount.h | 56 +++++++ fs/xfs/scrub/refcount_repair.c | 3 +- fs/xfs/scrub/rtrefcount.c | 263 +------------------------------ fs/xfs/scrub/rtrefcount_repair.c | 39 +---- 5 files changed, 72 insertions(+), 316 deletions(-) create mode 100644 fs/xfs/scrub/refcount.h diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index f8c51d8fbb3da5..c18bb89325a5ec 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -20,6 +20,7 @@ #include "scrub/btree.h" #include "scrub/trace.h" #include "scrub/repair.h" +#include "scrub/refcount.h" /* * Set us up to scrub reference count btrees. @@ -80,24 +81,6 @@ xchk_setup_ag_refcountbt( * If the refcount is correct, all the check conditions in the algorithm * should always hold true. If not, the refcount is incorrect. */ -struct xchk_refcnt_frag { - struct list_head list; - struct xfs_rmap_irec rm; -}; - -struct xchk_refcnt_check { - struct xfs_scrub *sc; - struct list_head fragments; - - /* refcount extent we're examining */ - xfs_agblock_t bno; - xfs_extlen_t len; - xfs_nlink_t refcount; - - /* number of owners seen */ - xfs_nlink_t seen; -}; - /* * Decide if the given rmap is large enough that we can redeem it * towards refcount verification now, or if it's a fragment, in @@ -105,7 +88,7 @@ struct xchk_refcnt_check { * discover that we've collected exactly the correct number of * fragments as the refcountbt says we should have. */ -STATIC int +int xchk_refcountbt_rmap_check( struct xfs_btree_cur *cur, const struct xfs_rmap_irec *rec, @@ -159,7 +142,7 @@ xchk_refcountbt_rmap_check( * number of extents that totally covered the refcountbt extent), * we have a refcountbt error. */ -STATIC void +void xchk_refcountbt_process_rmap_fragments( struct xchk_refcnt_check *refchk) { @@ -290,7 +273,7 @@ xchk_refcountbt_xref_rmap( .bno = irec->rc_startblock, .len = irec->rc_blockcount, .refcount = irec->rc_refcount, - .seen = 0, + .seen = 0, }; struct xfs_rmap_irec low; struct xfs_rmap_irec high; @@ -355,7 +338,7 @@ struct xchk_refcbt_records { enum xfs_refc_domain prev_domain; }; -STATIC int +int xchk_refcountbt_rmap_check_gap( struct xfs_btree_cur *cur, const struct xfs_rmap_irec *rec, diff --git a/fs/xfs/scrub/refcount.h b/fs/xfs/scrub/refcount.h new file mode 100644 index 00000000000000..0706afd9f80b59 --- /dev/null +++ b/fs/xfs/scrub/refcount.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017-2023 Oracle. All Rights Reserved. + * Author: Darrick J. Wong + */ +#ifndef __XFS_SCRUB_REFCOUNT_H__ +#define __XFS_SCRUB_REFCOUNT_H__ + +/* + * Ensure that types and macros match across rt and non-rt variants, + * as this code is shared for both. + */ +static_assert(__same_type(xfs_agblock_t, xfs_rgblock_t)); +static_assert(NULLAGBLOCK == NULLRGBLOCK); + +/* + * Confirming reference counts via reverse mappings. + * + * These helpers are shared by the AG (refcount.c) and realtime + * (rtrefcount.c) refcount scrubbers. Both the rmap and refcount incore + * records use xfs_agblock_t for their start block regardless of whether the + * data lives in an allocation group or a realtime group, so the fragment + * bookkeeping here uses xfs_agblock_t for group-relative block numbers. + */ +struct xchk_refcnt_frag { + struct list_head list; + struct xfs_rmap_irec rm; +}; + +struct xchk_refcnt_check { + struct xfs_scrub *sc; + struct list_head fragments; + + /* refcount extent we're examining */ + xfs_agblock_t bno; + xfs_extlen_t len; + xfs_nlink_t refcount; + + /* number of owners seen */ + xfs_nlink_t seen; +}; + +int xchk_refcountbt_rmap_check(struct xfs_btree_cur *cur, + const struct xfs_rmap_irec *rec, void *priv); +void xchk_refcountbt_process_rmap_fragments(struct xchk_refcnt_check *refchk); +int xchk_refcountbt_rmap_check_gap(struct xfs_btree_cur *cur, + const struct xfs_rmap_irec *rec, void *priv); + +/* + * Compare two refcount records so that both the AG (refcount_repair.c) and + * realtime (rtrefcount_repair.c) rebuilders sort staged records into the same + * order as the ondisk records. + */ +int xrep_refc_extent_cmp(const void *a, const void *b); + +#endif /* __XFS_SCRUB_REFCOUNT_H__ */ diff --git a/fs/xfs/scrub/refcount_repair.c b/fs/xfs/scrub/refcount_repair.c index ca9c382005ff1d..72de263117a75a 100644 --- a/fs/xfs/scrub/refcount_repair.c +++ b/fs/xfs/scrub/refcount_repair.c @@ -39,6 +39,7 @@ #include "scrub/newbt.h" #include "scrub/reap.h" #include "scrub/rcbag.h" +#include "scrub/refcount.h" /* * Rebuilding the Reference Count Btree @@ -295,7 +296,7 @@ xrep_refc_encode_startblock( } /* Sort in the same order as the ondisk records. */ -static int +int xrep_refc_extent_cmp( const void *a, const void *b) diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 460e3f094adf13..b78784051308f3 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -25,6 +25,7 @@ #include "scrub/common.h" #include "scrub/btree.h" #include "scrub/repair.h" +#include "scrub/refcount.h" /* Set us up with the realtime refcount metadata locked. */ int @@ -59,248 +60,13 @@ xchk_setup_rtrefcountbt( /* Realtime Reference count btree scrubber. */ -/* - * Confirming Reference Counts via Reverse Mappings - * - * We want to count the reverse mappings overlapping a refcount record - * (bno, len, refcount), allowing for the possibility that some of the - * overlap may come from smaller adjoining reverse mappings, while some - * comes from single extents which overlap the range entirely. The - * outer loop is as follows: - * - * 1. For all reverse mappings overlapping the refcount extent, - * a. If a given rmap completely overlaps, mark it as seen. - * b. Otherwise, record the fragment (in agbno order) for later - * processing. - * - * Once we've seen all the rmaps, we know that for all blocks in the - * refcount record we want to find $refcount owners and we've already - * visited $seen extents that overlap all the blocks. Therefore, we - * need to find ($refcount - $seen) owners for every block in the - * extent; call that quantity $target_nr. Proceed as follows: - * - * 2. Pull the first $target_nr fragments from the list; all of them - * should start at or before the start of the extent. - * Call this subset of fragments the working set. - * 3. Until there are no more unprocessed fragments, - * a. Find the shortest fragments in the set and remove them. - * b. Note the block number of the end of these fragments. - * c. Pull the same number of fragments from the list. All of these - * fragments should start at the block number recorded in the - * previous step. - * d. Put those fragments in the set. - * 4. Check that there are $target_nr fragments remaining in the list, - * and that they all end at or beyond the end of the refcount extent. - * - * If the refcount is correct, all the check conditions in the algorithm - * should always hold true. If not, the refcount is incorrect. - */ -struct xchk_rtrefcnt_frag { - struct list_head list; - struct xfs_rmap_irec rm; -}; - -struct xchk_rtrefcnt_check { - struct xfs_scrub *sc; - struct list_head fragments; - - /* refcount extent we're examining */ - xfs_rgblock_t bno; - xfs_extlen_t len; - xfs_nlink_t refcount; - - /* number of owners seen */ - xfs_nlink_t seen; -}; - -/* - * Decide if the given rmap is large enough that we can redeem it - * towards refcount verification now, or if it's a fragment, in - * which case we'll hang onto it in the hopes that we'll later - * discover that we've collected exactly the correct number of - * fragments as the rtrefcountbt says we should have. - */ -STATIC int -xchk_rtrefcountbt_rmap_check( - struct xfs_btree_cur *cur, - const struct xfs_rmap_irec *rec, - void *priv) -{ - struct xchk_rtrefcnt_check *refchk = priv; - struct xchk_rtrefcnt_frag *frag; - xfs_rgblock_t rm_last; - xfs_rgblock_t rc_last; - int error = 0; - - if (xchk_should_terminate(refchk->sc, &error)) - return error; - - rm_last = rec->rm_startblock + rec->rm_blockcount - 1; - rc_last = refchk->bno + refchk->len - 1; - - /* Confirm that a single-owner refc extent is a CoW stage. */ - if (refchk->refcount == 1 && rec->rm_owner != XFS_RMAP_OWN_COW) { - xchk_btree_xref_set_corrupt(refchk->sc, cur, 0); - return 0; - } - - if (rec->rm_startblock <= refchk->bno && rm_last >= rc_last) { - /* - * The rmap overlaps the refcount record, so we can confirm - * one refcount owner seen. - */ - refchk->seen++; - } else { - /* - * This rmap covers only part of the refcount record, so - * save the fragment for later processing. If the rmapbt - * is healthy each rmap_irec we see will be in agbno order - * so we don't need insertion sort here. - */ - frag = kmalloc_obj(struct xchk_rtrefcnt_frag, XCHK_GFP_FLAGS); - if (!frag) - return -ENOMEM; - memcpy(&frag->rm, rec, sizeof(frag->rm)); - list_add_tail(&frag->list, &refchk->fragments); - } - - return 0; -} - -/* - * Given a bunch of rmap fragments, iterate through them, keeping - * a running tally of the refcount. If this ever deviates from - * what we expect (which is the rtrefcountbt's refcount minus the - * number of extents that totally covered the rtrefcountbt extent), - * we have a rtrefcountbt error. - */ -STATIC void -xchk_rtrefcountbt_process_rmap_fragments( - struct xchk_rtrefcnt_check *refchk) -{ - struct list_head worklist; - struct xchk_rtrefcnt_frag *frag; - struct xchk_rtrefcnt_frag *n; - xfs_rgblock_t bno; - xfs_rgblock_t rbno; - xfs_rgblock_t next_rbno; - xfs_nlink_t nr; - xfs_nlink_t target_nr; - - target_nr = refchk->refcount - refchk->seen; - if (target_nr == 0) - return; - - /* - * There are (refchk->rc.rc_refcount - refchk->nr refcount) - * references we haven't found yet. Pull that many off the - * fragment list and figure out where the smallest rmap ends - * (and therefore the next rmap should start). All the rmaps - * we pull off should start at or before the beginning of the - * refcount record's range. - */ - INIT_LIST_HEAD(&worklist); - rbno = NULLRGBLOCK; - - /* Make sure the fragments actually /are/ in bno order. */ - bno = 0; - list_for_each_entry(frag, &refchk->fragments, list) { - if (frag->rm.rm_startblock < bno) - goto done; - bno = frag->rm.rm_startblock; - } - - /* - * Find all the rmaps that start at or before the refc extent, - * and put them on the worklist. - */ - nr = 0; - list_for_each_entry_safe(frag, n, &refchk->fragments, list) { - if (frag->rm.rm_startblock > refchk->bno || nr > target_nr) - break; - bno = frag->rm.rm_startblock + frag->rm.rm_blockcount; - if (bno < rbno) - rbno = bno; - list_move_tail(&frag->list, &worklist); - nr++; - } - - /* - * We should have found exactly $target_nr rmap fragments starting - * at or before the refcount extent. - */ - if (nr != target_nr) - goto done; - - while (!list_empty(&refchk->fragments)) { - /* Discard any fragments ending at rbno from the worklist. */ - nr = 0; - next_rbno = NULLRGBLOCK; - list_for_each_entry_safe(frag, n, &worklist, list) { - bno = frag->rm.rm_startblock + frag->rm.rm_blockcount; - if (bno != rbno) { - if (bno < next_rbno) - next_rbno = bno; - continue; - } - list_del(&frag->list); - kfree(frag); - nr++; - } - - /* Try to add nr rmaps starting at rbno to the worklist. */ - list_for_each_entry_safe(frag, n, &refchk->fragments, list) { - bno = frag->rm.rm_startblock + frag->rm.rm_blockcount; - if (frag->rm.rm_startblock != rbno) - goto done; - list_move_tail(&frag->list, &worklist); - if (next_rbno > bno) - next_rbno = bno; - nr--; - if (nr == 0) - break; - } - - /* - * If we get here and nr > 0, this means that we added fewer - * items to the worklist than we discarded because the fragment - * list ran out of items. Therefore, we cannot maintain the - * required refcount. Something is wrong, so we're done. - */ - if (nr) - goto done; - - rbno = next_rbno; - } - - /* - * Make sure the last extent we processed ends at or beyond - * the end of the refcount extent. - */ - if (rbno < refchk->bno + refchk->len) - goto done; - - /* Actually record us having seen the remaining refcount. */ - refchk->seen = refchk->refcount; -done: - /* Delete fragments and work list. */ - list_for_each_entry_safe(frag, n, &worklist, list) { - list_del(&frag->list); - kfree(frag); - } - list_for_each_entry_safe(frag, n, &refchk->fragments, list) { - list_del(&frag->list); - kfree(frag); - } -} - /* Use the rmap entries covering this extent to verify the refcount. */ STATIC void xchk_rtrefcountbt_xref_rmap( struct xfs_scrub *sc, const struct xfs_refcount_irec *irec) { - struct xchk_rtrefcnt_check refchk = { + struct xchk_refcnt_check refchk = { .sc = sc, .bno = irec->rc_startblock, .len = irec->rc_blockcount, @@ -309,8 +75,8 @@ xchk_rtrefcountbt_xref_rmap( }; struct xfs_rmap_irec low; struct xfs_rmap_irec high; - struct xchk_rtrefcnt_frag *frag; - struct xchk_rtrefcnt_frag *n; + struct xchk_refcnt_frag *frag; + struct xchk_refcnt_frag *n; int error; if (!sc->sr.rmap_cur || xchk_skip_xref(sc->sm)) @@ -324,11 +90,11 @@ xchk_rtrefcountbt_xref_rmap( INIT_LIST_HEAD(&refchk.fragments); error = xfs_rmap_query_range(sc->sr.rmap_cur, &low, &high, - xchk_rtrefcountbt_rmap_check, &refchk); + xchk_refcountbt_rmap_check, &refchk); if (!xchk_should_check_xref(sc, &error, &sc->sr.rmap_cur)) goto out_free; - xchk_rtrefcountbt_process_rmap_fragments(&refchk); + xchk_refcountbt_process_rmap_fragments(&refchk); if (irec->rc_refcount != refchk.seen) xchk_btree_xref_set_corrupt(sc, sc->sr.rmap_cur, 0); @@ -408,21 +174,6 @@ xchk_rtrefcountbt_check_mergeable( memcpy(&rrc->prev_rec, irec, sizeof(struct xfs_refcount_irec)); } -STATIC int -xchk_rtrefcountbt_rmap_check_gap( - struct xfs_btree_cur *cur, - const struct xfs_rmap_irec *rec, - void *priv) -{ - xfs_rgblock_t *next_bno = priv; - - if (*next_bno != NULLRGBLOCK && rec->rm_startblock < *next_bno) - return -ECANCELED; - - *next_bno = rec->rm_startblock + rec->rm_blockcount; - return 0; -} - /* * Make sure that a gap in the reference count records does not correspond to * overlapping records (i.e. shared extents) in the reverse mappings. @@ -448,7 +199,7 @@ xchk_rtrefcountbt_xref_gaps( high.rm_startblock = bno - 1; error = xfs_rmap_query_range(sc->sr.rmap_cur, &low, &high, - xchk_rtrefcountbt_rmap_check_gap, &next_bno); + xchk_refcountbt_rmap_check_gap, &next_bno); if (error == -ECANCELED) xchk_btree_xref_set_corrupt(sc, sc->sr.rmap_cur, 0); else diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index c78a6d2990c586..5ca9959dfacc40 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -44,6 +44,7 @@ #include "scrub/newbt.h" #include "scrub/reap.h" #include "scrub/rcbag.h" +#include "scrub/refcount.h" /* * Rebuilding the Reference Count Btree @@ -266,42 +267,6 @@ xrep_rtrefc_walk_rmaps( return 0; } -static inline uint32_t -xrep_rtrefc_encode_startblock( - const struct xfs_refcount_irec *irec) -{ - uint32_t start; - - start = irec->rc_startblock & ~XFS_REFC_COWFLAG; - if (irec->rc_domain == XFS_REFC_DOMAIN_COW) - start |= XFS_REFC_COWFLAG; - - return start; -} - -/* - * Compare two refcount records. We want to sort in order of increasing block - * number. - */ -static int -xrep_rtrefc_extent_cmp( - const void *a, - const void *b) -{ - const struct xfs_refcount_irec *ap = a; - const struct xfs_refcount_irec *bp = b; - uint32_t sa, sb; - - sa = xrep_rtrefc_encode_startblock(ap); - sb = xrep_rtrefc_encode_startblock(bp); - - if (sa > sb) - return 1; - if (sa < sb) - return -1; - return 0; -} - /* * Sort the refcount extents by startblock or else the btree records will be in * the wrong order. Make sure the records do not overlap in physical space. @@ -316,7 +281,7 @@ xrep_rtrefc_sort_records( xfs_rgblock_t next_rgbno = 0; int error; - error = xfarray_sort(rr->refcount_records, xrep_rtrefc_extent_cmp, + error = xfarray_sort(rr->refcount_records, xrep_refc_extent_cmp, XFARRAY_SORT_KILLABLE); if (error) return error; From ff05910cb1b77c2b229c5e6e260c6173a17b579b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 26 Sep 2026 10:08:51 +0800 Subject: [PATCH 1517/2286] fanotify: report names longer than NAME_MAX fanotify keeps the length of a reported name in a u8, so it drops any name longer than NAME_MAX and warns: WARNING: fs/notify/fanotify/fanotify.h:217 at fanotify_handle_event+0x2e82/0x39f0 Such names are legitimate on some filesystems. vfat mounted with utf8 takes up to 255 UTF-16 characters, which are up to 765 bytes of UTF-8, and FUSE accepts names up to PATH_MAX - 1. readdir() and inotify report them in full. fanotify sends the event without the name, and for FAN_RENAME it trips a second warning in copy_fid_info_to_user(), after which read() fails with EFAULT and the event is lost. When syzbot hit this, Jan suggested accommodating names up to PATH_MAX [1], the limit readdir() already applies to a name (verify_dirent_name()). Store the name lengths as u16, which keeps struct fanotify_info at 8 bytes, and drop without a warning only names of PATH_MAX bytes or more, which no path can refer to. An event with such a name can exceed a page, so allocate name events with kvmalloc(), as Jan also suggested. Names up to NAME_MAX are reported exactly as before, and the record format is unchanged: an event with a long name is just longer. Link: https://lore.kernel.org/all/20241112115059.ecvomkalee5m4i4i@quack3/ [1] Fixes: 2d9374f09513 ("fanotify: use macros to get the offset to fanotify_info buffer") Cc: stable@vger.kernel.org Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/20260926020851.2938961-1-matthias.goergens@gmail.com Signed-off-by: Jan Kara --- fs/notify/fanotify/fanotify.c | 7 +++++-- fs/notify/fanotify/fanotify.h | 21 ++++++++++++--------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/fs/notify/fanotify/fanotify.c b/fs/notify/fanotify/fanotify.c index a208a7ec1692a2..71b3a2361929c4 100644 --- a/fs/notify/fanotify/fanotify.c +++ b/fs/notify/fanotify/fanotify.c @@ -648,6 +648,9 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir, unsigned long name2_len = name2 ? name2->len : 0; unsigned int len, size; + if (WARN_ON_ONCE(name_len >= PATH_MAX || name2_len >= PATH_MAX)) + return NULL; + /* Reserve terminating null byte even for empty name */ size = sizeof(*fne) + name_len + name2_len + 2; if (dir_fh_len) @@ -656,7 +659,7 @@ static struct fanotify_event *fanotify_alloc_name_event(struct inode *dir, size += FANOTIFY_FH_HDR_LEN + dir2_fh_len; if (child_fh_len) size += FANOTIFY_FH_HDR_LEN + child_fh_len; - fne = kmalloc(size, gfp); + fne = kvmalloc(size, gfp); if (!fne) return NULL; @@ -1050,7 +1053,7 @@ static void fanotify_free_fid_event(struct fanotify_event *event) static void fanotify_free_name_event(struct fanotify_event *event) { - kfree(FANOTIFY_NE(event)); + kvfree(FANOTIFY_NE(event)); } static void fanotify_free_error_event(struct fsnotify_group *group, diff --git a/fs/notify/fanotify/fanotify.h b/fs/notify/fanotify/fanotify.h index 3710543dbf8278..87ba788574bff3 100644 --- a/fs/notify/fanotify/fanotify.h +++ b/fs/notify/fanotify/fanotify.h @@ -43,9 +43,16 @@ struct fanotify_info { u8 dir_fh_totlen; u8 dir2_fh_totlen; u8 file_fh_totlen; - u8 name_len; - u8 name2_len; - u8 pad[3]; + u8 pad; + /* + * Deliberately not limited to NAME_MAX: some filesystems return longer + * names (vfat up to 255 UTF-16 characters, as Windows allows, which is + * up to 765 bytes of UTF-8; FUSE up to PATH_MAX - 1), and readdir() and + * inotify report them in full. Only names of PATH_MAX bytes or more, + * which no path can refer to, are dropped. + */ + u16 name_len; + u16 name2_len; unsigned char buf[]; /* * (struct fanotify_fh) dir_fh starts at buf[0] @@ -168,7 +175,7 @@ static inline char *fanotify_info_name2(struct fanotify_info *info) static inline void fanotify_info_init(struct fanotify_info *info) { BUILD_BUG_ON(FANOTIFY_FH_HDR_LEN + MAX_HANDLE_SZ > U8_MAX); - BUILD_BUG_ON(NAME_MAX > U8_MAX); + BUILD_BUG_ON(PATH_MAX > U16_MAX); info->dir_fh_totlen = 0; info->dir2_fh_totlen = 0; @@ -214,8 +221,7 @@ static inline void fanotify_info_set_file_fh(struct fanotify_info *info, static inline void fanotify_info_copy_name(struct fanotify_info *info, const struct qstr *name) { - if (WARN_ON_ONCE(name->len > NAME_MAX) || - WARN_ON_ONCE(info->name2_len > 0)) + if (WARN_ON_ONCE(info->name2_len > 0)) return; info->name_len = name->len; @@ -225,9 +231,6 @@ static inline void fanotify_info_copy_name(struct fanotify_info *info, static inline void fanotify_info_copy_name2(struct fanotify_info *info, const struct qstr *name) { - if (WARN_ON_ONCE(name->len > NAME_MAX)) - return; - info->name2_len = name->len; strscpy(fanotify_info_name2(info), name->name, name->len + 1); } From ef2ccb1554df254eb4066171ce93e6d5fb1af2e3 Mon Sep 17 00:00:00 2001 From: Jake Labelle Date: Thu, 8 Oct 2026 20:52:47 +0900 Subject: [PATCH 1518/2286] nilfs2: reject non-regular inodes in dsync recovery During roll-forward recovery, nilfs_recover_dsync_blocks() obtains the inode designated by fi_ino of a dsync log without checking its type. If a corrupted image designates a special inode (e.g. a device node) there, the inode's embedded bmap was never initialized: __nilfs_read_inode() calls nilfs_bmap_read() only for regular files, directories and symlinks. The subsequent nilfs_get_block() then dereferences the uninitialized bmap->b_ops and jumps through it, crashing the kernel or worse. Regular files, directories, and symlinks are the only inode types with data blocks to recover; reject anything else before touching its block mapping. [ryusuke: conformed AI tag to guidelines] Fixes: 0f3e1c7f23f8 ("nilfs2: recovery functions") Assisted-by: Claude:claude-mythos-5 Signed-off-by: Jake Labelle Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/recovery.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c index abe4101f7550c0..c384325a57d02d 100644 --- a/fs/nilfs2/recovery.c +++ b/fs/nilfs2/recovery.c @@ -545,6 +545,23 @@ static int nilfs_recover_dsync_blocks(struct the_nilfs *nilfs, goto failed_inode; } + /* + * Regular files, directories, and symlinks are the only + * inode types with data blocks and an initialized bmap; + * a crafted image can reference some other inode type + * here, whose i_bmap_data was never set up by + * __nilfs_read_inode(). + */ + if (!likely(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || + S_ISLNK(inode->i_mode))) { + nilfs_warn(sb, + "%s: invalid inode type (ino=%lu, mode=0%o)", + __func__, (unsigned long)rb->ino, + inode->i_mode); + err = -EINVAL; + goto failed_inode; + } + pos = rb->blkoff << inode->i_blkbits; err = block_write_begin(inode->i_mapping, pos, blocksize, &folio, nilfs_get_block); From de7f49c12566cc5c7437a2f26c6c754b9d4d5648 Mon Sep 17 00:00:00 2001 From: Jake Labelle Date: Thu, 8 Oct 2026 20:52:48 +0900 Subject: [PATCH 1519/2286] nilfs2: validate directory position after llseek in readdir nilfs_readdir() uses ctx->pos, which userspace can set to an arbitrary value via llseek, as an offset into the directory folio and parses whatever bytes sit there as a directory entry. A position pointing into the middle of an entry -- or at unused bytes of a corrupted image that the page validator never inspected -- makes dir_emit() copy up to 255 (name_len) bytes from a bogus entry, possibly reading past the end of the folio and returning unrelated memory contents to userspace. Snap unaligned positions onto a valid entry boundary by walking the rec_len chain from the chunk start before parsing, as ext2 does in ext2_validate_entry(). [ryusuke: fixed offset wrap-around in nilfs_validate_entry() and conformed AI tag to guidelines] Fixes: 2ba466d74ed7 ("nilfs2: directory entry operations") Assisted-by: Claude:claude-mythos-5 Signed-off-by: Jake Labelle Cc: stable@vger.kernel.org Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- fs/nilfs2/dir.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c index 8b53802b6ebd78..7686ca9228fc86 100644 --- a/fs/nilfs2/dir.c +++ b/fs/nilfs2/dir.c @@ -231,11 +231,27 @@ static struct nilfs_dir_entry *nilfs_next_entry(struct nilfs_dir_entry *p) nilfs_rec_len_from_disk(p->rec_len)); } +static inline unsigned int +nilfs_validate_entry(char *base, unsigned int offset, unsigned int mask) +{ + struct nilfs_dir_entry *de = (struct nilfs_dir_entry *)(base + offset); + struct nilfs_dir_entry *p = + (struct nilfs_dir_entry *)(base + (offset & mask)); + + while ((char *)p < (char *)de) { + if (p->rec_len == 0) + break; + p = nilfs_next_entry(p); + } + return (char *)p - base; +} + static int nilfs_readdir(struct file *file, struct dir_context *ctx) { loff_t pos = ctx->pos; struct inode *inode = file_inode(file); struct super_block *sb = inode->i_sb; + unsigned int chunk_size = nilfs_chunk_size(inode); unsigned int offset = pos & ~PAGE_MASK; unsigned long n = pos >> PAGE_SHIFT; unsigned long npages = dir_pages(inode); @@ -254,6 +270,24 @@ static int nilfs_readdir(struct file *file, struct dir_context *ctx) ctx->pos += PAGE_SIZE - offset; return -EIO; } + /* + * ctx->pos comes from userspace via llseek and may point + * into the middle of an entry -- or at unvalidated bytes + * of a crafted image. offset is only nonzero here on the + * very first iteration (subsequent pages always start at + * offset 0, which -- like any chunk boundary -- is always + * a legitimate entry start, since no rec_len chain crosses + * a chunk boundary). Snap a non-chunk-aligned offset onto + * a real entry boundary by walking the chain from the + * enclosing chunk's start, as ext2 does; otherwise + * dir_emit() copies name_len bytes from a fake entry, + * reading past the end of the folio. + */ + if (offset & (chunk_size - 1)) { + offset = nilfs_validate_entry(kaddr, offset, + ~(chunk_size - 1)); + ctx->pos = ((loff_t)n << PAGE_SHIFT) + offset; + } de = (struct nilfs_dir_entry *)(kaddr + offset); limit = kaddr + nilfs_last_byte(inode, n) - NILFS_DIR_REC_LEN(1); From 9232301894a06e664d8de2da97175cf98419bd16 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 23 Sep 2026 08:57:44 -0300 Subject: [PATCH 1520/2286] xfs: bound inode fork length against the fork size during log recovery xlog_recover_inode_commit_pass2() copies an inode log item's data/attr fork region into the inode buffer using the on-log region length without bounding it against the fork capacity, e.g.: len = item->ri_buf[2].iov_len; memcpy(XFS_DFORK_DPTR(dip), src, len); The only guard is an ASSERT, which is a no-op on production kernels (CONFIG_XFS_DEBUG off), and xfs_dinode_verify() runs only after the copy. A crafted image with a dirty log can therefore drive a heap out-of-bounds write at mount time. The XFS_ILOG_DBROOT sibling already passes XFS_DFORK_DSIZE as a bound; the DDATA/DEXT and ADATA/AEXT memcpy paths did not. Bound each logged fork region against the destination fork size before copying it, and reject the log item with -EFSCORRUPTED when it does not fit. Because the recovered inode is only verified after the fork data has been copied in, the checks are done up front, before any memcpy into the on-disk inode. Fixes: 658fa68b6f34 ("xfs: refactor log recovery inode item dispatch for pass2 commit functions") Cc: stable@vger.kernel.org # v5.8 Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_inode_item_recover.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recover.c index 1d2319ad15c518..8da95131ab55f2 100644 --- a/fs/xfs/xfs_inode_item_recover.c +++ b/fs/xfs/xfs_inode_item_recover.c @@ -507,6 +507,25 @@ xlog_recover_inode_commit_pass2( ASSERT(!(fields & XFS_ILOG_DFORK) || (len == xlog_calc_iovec_len(in_f->ilf_dsize))); + /* + * The recovered inode is verified only after the fork data has been + * copied into it, so bound each logged fork region against the size of + * its fork now, before the memcpy below can overrun the on-disk inode. + * The DBROOT/ABROOT cases already bound their copies against the fork + * size. + */ + if ((fields & (XFS_ILOG_DDATA | XFS_ILOG_DEXT)) && + item->ri_buf[2].iov_len > XFS_DFORK_DSIZE(dip, mp)) { + error = -EFSCORRUPTED; + goto out_release; + } + if ((fields & (XFS_ILOG_ADATA | XFS_ILOG_AEXT)) && + item->ri_buf[(fields & XFS_ILOG_DFORK) ? 3 : 2].iov_len > + XFS_DFORK_ASIZE(dip, mp)) { + error = -EFSCORRUPTED; + goto out_release; + } + switch (fields & XFS_ILOG_DFORK) { case XFS_ILOG_DDATA: case XFS_ILOG_DEXT: @@ -546,7 +565,6 @@ xlog_recover_inode_commit_pass2( case XFS_ILOG_ADATA: case XFS_ILOG_AEXT: dest = XFS_DFORK_APTR(dip); - ASSERT(len <= XFS_DFORK_ASIZE(dip, mp)); memcpy(dest, src, len); break; From 3df5e5aaa3bc0913236c48ab29b7df3a785aa29f Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 23 Sep 2026 08:57:54 -0300 Subject: [PATCH 1521/2286] xfs: bound da-node entry count against the correct geometry xfs_da3_node_verify() bounds the node entry count against the larger of the directory and attribute geometries because, as a buffer verifier, it cannot tell whether the block belongs to the directory or the attribute tree. When the directory block size exceeds the fs block size (e.g. mkfs.xfs -n size=64k -b size=4k), an attribute node buffer is a single fs block that holds only m_attr_geo->node_ents entries, yet a crafted attr node may claim a count up to m_dir_geo->node_ents and still pass the verifier. xfs_da3_node_lookup_int() then indexes btree[] up to that count during its binary search -- an out-of-bounds read via getxattr/listxattr on a mounted crafted image. The buffer verifier is the wrong place to tighten this: it has no fork context, and the transaction-less read path used by getxattr does not run xfs_da3_node_set_type() either. xfs_da3_node_lookup_int(), on the other hand, always runs on that path and holds args->geo, the geometry of the fork actually being searched. Bound the entry count against args->geo->node_ents there, before walking the entries. Fixes: 7ab610f9e0f1 ("xfs: move node entry counts to xfs_da_geometry") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_da_btree.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fs/xfs/libxfs/xfs_da_btree.c b/fs/xfs/libxfs/xfs_da_btree.c index 3d02a0d7ba44a0..faf88488265c54 100644 --- a/fs/xfs/libxfs/xfs_da_btree.c +++ b/fs/xfs/libxfs/xfs_da_btree.c @@ -1787,6 +1787,20 @@ xfs_da3_node_lookup_int( } else expected_level--; + /* + * The node verifier cannot tell whether this block belongs to + * the directory or the attribute tree, so it only bounds the + * entry count against the larger of the two geometries. Here + * args->geo is the geometry of the fork we are actually + * searching, so reject a count that would walk btree[] off the + * end of this node buffer. + */ + if (nodehdr.count > args->geo->node_ents) { + xfs_buf_mark_corrupt(blk->bp); + xfs_da_mark_sick(args); + return -EFSCORRUPTED; + } + max = nodehdr.count; blk->hashval = be32_to_cpu(btree[max - 1].hashval); From 0643549ffecaeb716c0b75590a9b76dfa5eb35b3 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 23 Sep 2026 08:58:22 -0300 Subject: [PATCH 1522/2286] xfs: reject out-of-range attribute value lengths in xfs_attr_copy_value xfs_attr_copy_value() takes the value length as a signed int and, for a remote xattr, is handed args->rmtvaluelen. That field is filled from the on-disk __be32 xfs_attr_leaf_name_remote.valuelen in xfs_attr3_leaf_getvalue(), so a crafted length such as 0x80000000 is stored into the signed rmtvaluelen as a negative number. The "buffer too small" guard in xfs_attr_copy_value() is a signed comparison: if (args->valuelen < valuelen) return -ERANGE; A negative valuelen therefore compares as smaller than the caller's buffer size, skips the -ERANGE path, and is then used as a copy length, leading to an out-of-bounds copy of a full remote block into a small getxattr(2) buffer on a mounted crafted image. Reject a value length that is negative or larger than the maximum xattr size before it is used, so a bogus on-disk length can no longer slip through the value copier. Fixes: 9df243a1a9e6 ("xfs: consolidate attribute value copying") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr_leaf.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 2c80f4fd0b78de..776f51180fa939 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -642,6 +642,17 @@ xfs_attr_copy_value( unsigned char *value, int valuelen) { + /* + * A value length that is negative or larger than the maximum xattr + * size is on-disk corruption. The remote value length is an on-disk + * __be32 stored into the signed args->rmtvaluelen, so a crafted value + * such as 0x80000000 becomes negative and would slip past the + * "args->valuelen < valuelen" check below and be used as a copy + * length. Reject it before that can happen. + */ + if (valuelen < 0 || valuelen > XFS_XATTR_SIZE_MAX) + return -EFSCORRUPTED; + /* * Parent pointer lookups require the caller to specify the name and * value, so don't copy anything. From 15ccd92a782a0253f4a17895f3d3f71e5c1d1973 Mon Sep 17 00:00:00 2001 From: Aldo Ariel Panzardo Date: Wed, 23 Sep 2026 08:58:23 -0300 Subject: [PATCH 1523/2286] xfs: reject remote xattr entries with an out-of-range value length xfs_attr3_leaf_verify_entry() validates a remote attribute entry's name but never bounds its on-disk value length (xfs_attr_leaf_name_remote. valuelen, a __be32). A crafted leaf with valuelen = 0x80000000 passes the verifier and the CRC. That length is later assigned into the signed int args->rmtvaluelen (xfs_attr3_leaf_getvalue), becoming negative, which slips past the signed -ERANGE check in xfs_attr_copy_value(); a getxattr() with a small buffer then memcpy()s a full remote block into the small kvalue buffer (xfs_attr_rmtval_copyout) -- a heap out-of-bounds write with attacker-controlled content, from an unprivileged getxattr(2) on a mounted crafted image. Reject remote entries whose value length exceeds XFS_XATTR_SIZE_MAX in the leaf verifier, so the malicious block is rejected at read time. Fixes: c84760659dcf ("xfs: check attribute leaf block structure") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr_leaf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 776f51180fa939..02ca50442b4665 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -353,6 +353,8 @@ xfs_attr3_leaf_verify_entry( if (!(ent->flags & XFS_ATTR_INCOMPLETE) && rentry->valueblk == 0) return __this_address; + if (be32_to_cpu(rentry->valuelen) > XFS_XATTR_SIZE_MAX) + return __this_address; } if (name_end > buf_end) From 6fa50b1988b8dfd2e92a9cecba073fe4347a5e23 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 9 Oct 2026 01:24:42 +0800 Subject: [PATCH 1524/2286] cpufreq: qcom-cpufreq-hw: fix device node refcount leak in phandle parsing of_parse_phandle_with_args() transfers a reference to the provider node in out_args->np, which the caller is responsible for releasing with of_node_put(). Neither qcom_get_related_cpus() nor qcom_cpufreq_hw_cpu_init() ever puts args.np: they only put cpu_np, which is a different node obtained from of_cpu_device_node_get(). The reference is therefore leaked on every successful call, and qcom_get_related_cpus() leaks one per present CPU. Release args.np after the parsed index has been read in both call sites. Fixes: 2849dd8bc72b ("cpufreq: qcom-hw: Add support for QCOM cpufreq HW driver") Assisted-by: DeepSeek-V4.1-Flash Signed-off-by: Haotian Zhang Signed-off-by: Viresh Kumar --- drivers/cpufreq/qcom-cpufreq-hw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 874ff3fb9d978b..060a460716f79d 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -322,6 +322,8 @@ static void qcom_get_related_cpus(int index, struct cpumask *m) if (index == args.args[0]) cpumask_set_cpu(cpu, m); + + of_node_put(args.np); } } @@ -554,6 +556,7 @@ static int qcom_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) return ret; index = args.args[0]; + of_node_put(args.np); data = &qcom_cpufreq.data[index]; /* HW should be in enabled state to proceed */ From 5d12c51bd98cdb5a32cbfb606963b05740ec42f2 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Thu, 20 Aug 2026 21:56:03 +0800 Subject: [PATCH 1525/2286] xen/pcifront: Fix PCI device reference leak in AER handling pci_get_domain_bus_and_slot() increments the reference count of the returned PCI device. pcifront_common_process() drops that reference only when the device or its driver is missing. All paths for a bound device either return directly after invoking an error recovery callback or fall through without calling pci_dev_put(). Consequently, each AER request for a bound device leaks a reference and can keep the device allocated after removal. Declare the looked-up device with __free(pci_dev_put), so every return path releases the reference after callback dispatch. This keeps the device alive while its callback runs and balances the lookup without restructuring the callback returns. This issue was found by a static analysis checker and confirmed by manual source review. Fixes: 956a9202cd12 ("xen-pcifront: Xen PCI frontend driver.") Suggested-by: Lukas Wunner Signed-off-by: Ruoyu Wang Reviewed-by: Lukas Wunner Link: https://lore.kernel.org/r/20260813153138.3953222-1-ruoyuw560@gmail.com/ Signed-off-by: Juergen Gross Message-ID: <20260820135603.3901798-1-ruoyuw560@gmail.com> --- drivers/pci/xen-pcifront.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c index cffc32d6603277..0dea8a69fa36c5 100644 --- a/drivers/pci/xen-pcifront.c +++ b/drivers/pci/xen-pcifront.c @@ -579,16 +579,15 @@ static pci_ers_result_t pcifront_common_process(int cmd, int bus = pdev->sh_info->aer_op.bus; int devfn = pdev->sh_info->aer_op.devfn; int domain = pdev->sh_info->aer_op.domain; - struct pci_dev *pcidev; + struct pci_dev *pcidev __free(pci_dev_put) = + pci_get_domain_bus_and_slot(domain, bus, devfn); dev_dbg(&pdev->xdev->dev, "pcifront AER process: cmd %x (bus:%x, devfn%x)", cmd, bus, devfn); - pcidev = pci_get_domain_bus_and_slot(domain, bus, devfn); if (!pcidev || !pcidev->dev.driver) { dev_err(&pdev->xdev->dev, "device or AER driver is NULL\n"); - pci_dev_put(pcidev); return PCI_ERS_RESULT_NONE; } pdrv = to_pci_driver(pcidev->dev.driver); From 9b4df0baa2aa03d22310e59846ca628f7afd4115 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Thu, 30 Jul 2026 16:40:00 +0800 Subject: [PATCH 1526/2286] xen/blkback: Prevent missed completion when draining I/O MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xen_blk_drain_io() sets drain before checking inflight. xen_blkbk_unmap_and_respond_callback() decrements inflight with atomic_dec_and_test() before checking drain. The pre-wait condition check was added to avoid missing a completion, but atomic_set() is unordered. With one I/O in flight, the drain path can set drain to 1 and read inflight as 1 before the completion decrement. The completion path then decrements inflight to 0 but can still read drain as 0. It skips complete(), so the drain path waits until the timeout despite no I/O remaining. Add a full barrier between setting drain and reading inflight. atomic_dec_and_test() already provides full ordering on the completion side. Fixes: 6927d92091df ("xen/blkback: Fix two races in the handling of barrier requests.") Signed-off-by: Gui-Dong Han Acked-by: Roger Pau Monné Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20260730084000.3305702-1-hanguidong02@gmail.com> --- drivers/block/xen-blkback/blkback.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/block/xen-blkback/blkback.c b/drivers/block/xen-blkback/blkback.c index 7871099bc0d442..88526e349ce1be 100644 --- a/drivers/block/xen-blkback/blkback.c +++ b/drivers/block/xen-blkback/blkback.c @@ -1021,6 +1021,13 @@ static void xen_blk_drain_io(struct xen_blkif_ring *ring) struct xen_blkif *blkif = ring->blkif; atomic_set(&blkif->drain, 1); + /* + * Publish drain before checking inflight. Otherwise, + * xen_blkbk_unmap_and_respond_callback() can decrement inflight with + * atomic_dec_and_test() and still see drain == 0 after this path saw + * inflight > 0, missing the completion. + */ + smp_mb(); do { if (atomic_read(&ring->inflight) == 0) break; From f3e27b7955727a4d7c0e9264a2a209fbba788bdc Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Tue, 22 Sep 2026 15:21:55 +0800 Subject: [PATCH 1527/2286] xen-blkfront: unbind irq before tearing down ring and shadow requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In blkif_free_ring(), the driver tears down the ring's persistent grants, shadow request arrays, and shared ring structure (xenbus_teardown_ring), and only calls unbind_from_irqhandler() at the very end. While blkif_free_ring() is freeing persistent grants and clearing the shadow array, the event channel interrupt (blkif_interrupt) is still registered and active. If an interrupt arrives from the backend during this teardown window, blkif_interrupt() reads rinfo->ring.sring and, via blkif_completion(), accesses rinfo->shadow[id].grants_used and rinfo->shadow[id].sg. blkif_free_ring() tears these structures down without holding rinfo->ring_lock, and the handler only checks info->connected at entry, so this is a real race resulting in a use-after-free or NULL pointer dereference. Fix this by moving unbind_from_irqhandler() to the beginning of blkif_free_ring(). Calling unbind_from_irqhandler() first frees the IRQ and synchronizes with any in-flight interrupt handlers on other CPUs before ring memory and shadow request structures are deallocated, matching the teardown order in drivers/net/xen-netfront.c. Fixes: 11659569f720 ("xen/blkfront: split per device io_lock") Cc: stable@vger.kernel.org Signed-off-by: Yuchao Zhang Acked-by: Roger Pau Monné Signed-off-by: Juergen Gross Message-ID: <20260922072155.34625-1-ndaugoing@gmail.com> --- drivers/block/xen-blkfront.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c index 8dad7bf5f6641e..86f5dd3aced133 100644 --- a/drivers/block/xen-blkfront.c +++ b/drivers/block/xen-blkfront.c @@ -1210,6 +1210,14 @@ static void blkif_free_ring(struct blkfront_ring_info *rinfo) struct blkfront_info *info = rinfo->dev_info; int i, j, segs; + /* + * Interrupt teardown must be done ahead of freeing queue-related + * data, otherwise the interrupt handler can race with the cleanup. + */ + if (rinfo->irq) + unbind_from_irqhandler(rinfo->irq, rinfo); + rinfo->evtchn = rinfo->irq = 0; + /* * Remove indirect pages, this only happens when using indirect * descriptors but not persistent grants @@ -1292,10 +1300,6 @@ static void blkif_free_ring(struct blkfront_ring_info *rinfo) /* Free resources associated with old device channel. */ xenbus_teardown_ring((void **)&rinfo->ring.sring, info->nr_ring_pages, rinfo->ring_ref); - - if (rinfo->irq) - unbind_from_irqhandler(rinfo->irq, rinfo); - rinfo->evtchn = rinfo->irq = 0; } static void blkif_free(struct blkfront_info *info, int suspend) From fe2eea9afbc4727ec972ec8dd4ff1480a5333bab Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sat, 19 Sep 2026 00:46:43 -0700 Subject: [PATCH 1528/2286] xen: remove unused hvm_vcpu.h header The header appears to have been unused ever since it was added to the kernel in commit cee2cfb7d18d ("xen/pvh: Import PVH-related Xen public interfaces"). Remove it. Signed-off-by: Ethan Nelson-Moore Reviewed-by: Stefano Stabellini Signed-off-by: Juergen Gross Message-ID: <20260919074650.453007-1-enelsonmoore@gmail.com> --- include/xen/interface/hvm/hvm_vcpu.h | 116 --------------------------- 1 file changed, 116 deletions(-) delete mode 100644 include/xen/interface/hvm/hvm_vcpu.h diff --git a/include/xen/interface/hvm/hvm_vcpu.h b/include/xen/interface/hvm/hvm_vcpu.h deleted file mode 100644 index cbf93493275c5e..00000000000000 --- a/include/xen/interface/hvm/hvm_vcpu.h +++ /dev/null @@ -1,116 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -/* - * Copyright (c) 2015, Roger Pau Monne - */ - -#ifndef __XEN_PUBLIC_HVM_HVM_VCPU_H__ -#define __XEN_PUBLIC_HVM_HVM_VCPU_H__ - -#include "../xen.h" - -struct vcpu_hvm_x86_32 { - uint32_t eax; - uint32_t ecx; - uint32_t edx; - uint32_t ebx; - uint32_t esp; - uint32_t ebp; - uint32_t esi; - uint32_t edi; - uint32_t eip; - uint32_t eflags; - - uint32_t cr0; - uint32_t cr3; - uint32_t cr4; - - uint32_t pad1; - - /* - * EFER should only be used to set the NXE bit (if required) - * when starting a vCPU in 32bit mode with paging enabled or - * to set the LME/LMA bits in order to start the vCPU in - * compatibility mode. - */ - uint64_t efer; - - uint32_t cs_base; - uint32_t ds_base; - uint32_t ss_base; - uint32_t es_base; - uint32_t tr_base; - uint32_t cs_limit; - uint32_t ds_limit; - uint32_t ss_limit; - uint32_t es_limit; - uint32_t tr_limit; - uint16_t cs_ar; - uint16_t ds_ar; - uint16_t ss_ar; - uint16_t es_ar; - uint16_t tr_ar; - - uint16_t pad2[3]; -}; - -/* - * The layout of the _ar fields of the segment registers is the - * following: - * - * Bits [0,3]: type (bits 40-43). - * Bit 4: s (descriptor type, bit 44). - * Bit [5,6]: dpl (descriptor privilege level, bits 45-46). - * Bit 7: p (segment-present, bit 47). - * Bit 8: avl (available for system software, bit 52). - * Bit 9: l (64-bit code segment, bit 53). - * Bit 10: db (meaning depends on the segment, bit 54). - * Bit 11: g (granularity, bit 55) - * Bits [12,15]: unused, must be blank. - * - * A more complete description of the meaning of this fields can be - * obtained from the Intel SDM, Volume 3, section 3.4.5. - */ - -struct vcpu_hvm_x86_64 { - uint64_t rax; - uint64_t rcx; - uint64_t rdx; - uint64_t rbx; - uint64_t rsp; - uint64_t rbp; - uint64_t rsi; - uint64_t rdi; - uint64_t rip; - uint64_t rflags; - - uint64_t cr0; - uint64_t cr3; - uint64_t cr4; - uint64_t efer; - - /* - * Using VCPU_HVM_MODE_64B implies that the vCPU is launched - * directly in long mode, so the cached parts of the segment - * registers get set to match that environment. - * - * If the user wants to launch the vCPU in compatibility mode - * the 32-bit structure should be used instead. - */ -}; - -struct vcpu_hvm_context { -#define VCPU_HVM_MODE_32B 0 /* 32bit fields of the structure will be used. */ -#define VCPU_HVM_MODE_64B 1 /* 64bit fields of the structure will be used. */ - uint32_t mode; - - uint32_t pad; - - /* CPU registers. */ - union { - struct vcpu_hvm_x86_32 x86_32; - struct vcpu_hvm_x86_64 x86_64; - } cpu_regs; -}; -typedef struct vcpu_hvm_context vcpu_hvm_context_t; - -#endif /* __XEN_PUBLIC_HVM_HVM_VCPU_H__ */ From 2176dea8d8c19cb43a9b3651bfa1cb21eab1b31a Mon Sep 17 00:00:00 2001 From: Abdifatah Suruur Date: Wed, 19 Aug 2026 11:42:17 +0300 Subject: [PATCH 1529/2286] xen/gntdev: prevent private mappings from becoming writable gntdev_mmap() rejects writable private (non-shared) mappings of foreign grant pages, because a private writable mapping takes the COW path on pages that have no proper struct-page backing. However, VM_MAYWRITE is left set, so userspace can map the grant read-only and then upgrade the mapping to writable with mprotect(), hitting the same COW path the check was written to prevent. Clear VM_MAYWRITE for private mappings, as i915 does for its read-only objects and as fixed in drm/vc4 (CVE-2026-68445) and drm/panthor (CVE-2024-53071) and ptp: vmclock (commit a5edadbae57e2298a56cf7a4e774a027905a331f). Fixes: ab31523c2fcac ("xen/gntdev: allow usermode to map granted pages") Cc: stable@vger.kernel.org Signed-off-by: Abdifatah Suruur Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20260819084217.1577-1-suruurism@gmail.com> --- drivers/xen/gntdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/xen/gntdev.c b/drivers/xen/gntdev.c index 1dcc4675580ed1..b71f5bae25b633 100644 --- a/drivers/xen/gntdev.c +++ b/drivers/xen/gntdev.c @@ -1066,6 +1066,13 @@ static int gntdev_mmap(struct file *flip, struct vm_area_struct *vma) if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) return -EINVAL; + /* + * Private mappings of foreign grant pages must never become + * writable: they would take the COW path on granted pages. + */ + if (!(vma->vm_flags & VM_SHARED)) + vm_flags_clear(vma, VM_MAYWRITE); + pr_debug("map %d+%d at %lx (pgoff %lx)\n", index, count, vma->vm_start, vma->vm_pgoff); From 71d59fc2a5e357ac03bb2ee1522a7c1f8bd0c34f Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:05:31 +0530 Subject: [PATCH 1530/2286] xen: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: Frediano Ziglio Signed-off-by: Juergen Gross Message-ID: <20260907063531.23981-1-hemanth.selam@gmail.com> --- drivers/scsi/xen-scsifront.c | 2 +- drivers/xen/mcelog.c | 2 +- include/xen/interface/platform.h | 4 ++-- include/xen/interface/xen.h | 2 +- include/xen/xenbus.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/scsi/xen-scsifront.c b/drivers/scsi/xen-scsifront.c index 989bcaee42caf6..f86a47056ea8ce 100644 --- a/drivers/scsi/xen-scsifront.c +++ b/drivers/scsi/xen-scsifront.c @@ -1076,7 +1076,7 @@ static void scsifront_do_lun_hotplug(struct vscsifrnt_info *info, int op) /* * Front device state path, used in sdev_configure called - * on successfull scsi_add_device, and in sdev_destroy called + * on successful scsi_add_device, and in sdev_destroy called * on remove of a device. */ snprintf(info->dev_state_path, sizeof(info->dev_state_path), diff --git a/drivers/xen/mcelog.c b/drivers/xen/mcelog.c index 32ab419bb5034e..f19cc7a67f367b 100644 --- a/drivers/xen/mcelog.c +++ b/drivers/xen/mcelog.c @@ -1,6 +1,6 @@ /****************************************************************************** * mcelog.c - * Driver for receiving and transferring machine check error infomation + * Driver for receiving and transferring machine check error information * * Copyright (c) 2012 Intel Corporation * Author: Liu, Jinsong diff --git a/include/xen/interface/platform.h b/include/xen/interface/platform.h index 79a443c65ea93c..462e36392a9f05 100644 --- a/include/xen/interface/platform.h +++ b/include/xen/interface/platform.h @@ -421,10 +421,10 @@ struct xenpf_pcpuinfo { /* IN */ uint32_t xen_cpuid; /* OUT */ - /* The maxium cpu_id that is present */ + /* The maximum cpu_id that is present */ uint32_t max_present; #define XEN_PCPU_FLAGS_ONLINE 1 - /* Correponding xen_cpuid is not present*/ + /* Corresponding xen_cpuid is not present*/ #define XEN_PCPU_FLAGS_INVALID 2 uint32_t flags; uint32_t apic_id; diff --git a/include/xen/interface/xen.h b/include/xen/interface/xen.h index 40c9793e98805a..f9acb21875cf69 100644 --- a/include/xen/interface/xen.h +++ b/include/xen/interface/xen.h @@ -94,7 +94,7 @@ #define VIRQ_XENOPROF 7 /* V. XenOprofile interrupt: new sample available */ #define VIRQ_CON_RING 8 /* G. (DOM0) Bytes received on console */ #define VIRQ_PCPU_STATE 9 /* G. (DOM0) PCPU state changed */ -#define VIRQ_MEM_EVENT 10 /* G. (DOM0) A memory event has occured */ +#define VIRQ_MEM_EVENT 10 /* G. (DOM0) A memory event has occurred */ #define VIRQ_XC_RESERVED 11 /* G. Reserved for XenClient */ #define VIRQ_ENOMEM 12 /* G. (DOM0) Low on heap memory */ #define VIRQ_XENPMU 13 /* PMC interrupt */ diff --git a/include/xen/xenbus.h b/include/xen/xenbus.h index 8ca15743af7f32..6424c5cf8ef500 100644 --- a/include/xen/xenbus.h +++ b/include/xen/xenbus.h @@ -63,7 +63,7 @@ struct xenbus_watch unsigned int nr_pending; /* - * Called just before enqueing new event while a spinlock is held. + * Called just before enqueuing new event while a spinlock is held. * The event will be discarded if this callback returns false. */ bool (*will_handle)(struct xenbus_watch *, From 992df081bc8878d11e1eb5ae4a609910fd35c62b Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Mon, 5 Oct 2026 21:35:54 +0900 Subject: [PATCH 1531/2286] xen/scsifront: check for a NULL shadow entry on a backend response scsifront_do_response() validates the rqid in a backend response against VSCSIIF_MAX_REQS before using it to index info->shadow[], but then dereferences the entry to test ->inflight without checking that the slot is populated. Shadow slots are NULL before a command is submitted (the host private area is zeroed at allocation) and are reset to NULL in _scsifront_put_rqid() once a request completes, so an in-range rqid that does not correspond to an outstanding request makes the frontend dereference a NULL pointer. A malicious or buggy backend can thus crash the guest by returning a response whose rqid is in range but not in flight -- for example a spurious response before any command has been issued, or a duplicate of one already completed. The ring has only VSCSIIF_MAX_REQS (16) slots, so before the first command every in-range rqid selects a NULL slot. Reject a response whose shadow slot is not populated. Fixes: 6d1c2f48f3fc ("xen/scsifront: harden driver against malicious backend") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20261005123555.253995-1-yhlee@isslab.korea.ac.kr> --- drivers/scsi/xen-scsifront.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/scsi/xen-scsifront.c b/drivers/scsi/xen-scsifront.c index f86a47056ea8ce..9d7e12e400896c 100644 --- a/drivers/scsi/xen-scsifront.c +++ b/drivers/scsi/xen-scsifront.c @@ -367,6 +367,7 @@ static void scsifront_do_response(struct vscsifrnt_info *info, struct vscsifrnt_shadow *shadow; if (ring_rsp->rqid >= VSCSIIF_MAX_REQS || + !info->shadow[ring_rsp->rqid] || !info->shadow[ring_rsp->rqid]->inflight) { scsifront_set_error(info, "illegal rqid returned by backend!"); return; From 908019e5999161ca89da310c05ddbf7fc89c129e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Fri, 21 Aug 2026 17:41:12 +0900 Subject: [PATCH 1532/2286] xen/privcmd: Only claim ioreqs matching an ioeventfd The ioeventfd handler shares its event channel with the userspace device model. It must only claim requests that match a registered ioeventfd. Other requests are left for userspace. ioeventfd_interrupt() currently marks every MMIO write INPROCESS before looking for a match. For an unmatched request it drops the lock and restores READY. Userspace may see INPROCESS and skip it. Restoring READY does not send another notification. If userspace handles the request at the same time, the READY store may overwrite its state. Find a matching ioeventfd before changing the request state. The lock now protects only the ioeventfd list. Use explicit virt_*mb() barriers for the ioreq shared with Xen. Fixes: f0d7db7b3324 ("xen: privcmd: Add support for ioeventfd") Cc: stable@vger.kernel.org Signed-off-by: Koichiro Den Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20260821084112.2374949-1-den@valinux.co.jp> --- drivers/xen/privcmd.c | 47 +++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/drivers/xen/privcmd.c b/drivers/xen/privcmd.c index 7cfc28f1bb86fc..c67ec6d282cf5c 100644 --- a/drivers/xen/privcmd.c +++ b/drivers/xen/privcmd.c @@ -1169,51 +1169,40 @@ static irqreturn_t ioeventfd_interrupt(int irq, void *dev_id) struct privcmd_kernel_ioreq *kioreq = port->kioreq; struct ioreq *ioreq = &kioreq->ioreq[port->vcpu]; struct privcmd_kernel_ioeventfd *kioeventfd; - unsigned int state = STATE_IOREQ_READY; + bool matched = false; - if (ioreq->state != STATE_IOREQ_READY || - ioreq->type != IOREQ_TYPE_COPY || ioreq->dir != IOREQ_WRITE) + if (ioreq->state != STATE_IOREQ_READY) return IRQ_NONE; - /* - * We need a barrier, smp_mb(), here to ensure reads are finished before - * `state` is updated. Since the lock implementation ensures that - * appropriate barrier will be added anyway, we can avoid adding - * explicit barrier here. - * - * Ideally we don't need to update `state` within the locks, but we do - * that here to avoid adding explicit barrier. - */ + /* Xen publishes the request before changing its state to READY. */ + virt_rmb(); - spin_lock(&kioreq->lock); - ioreq->state = STATE_IOREQ_INPROCESS; + if (ioreq->type != IOREQ_TYPE_COPY || ioreq->dir != IOREQ_WRITE) + return IRQ_NONE; + spin_lock(&kioreq->lock); list_for_each_entry(kioeventfd, &kioreq->ioeventfds, list) { if (ioreq->addr == kioeventfd->addr + VIRTIO_MMIO_QUEUE_NOTIFY && ioreq->size == kioeventfd->addr_len && (ioreq->data & QUEUE_NOTIFY_VQ_MASK) == kioeventfd->vq) { + /* Finish reading the request before claiming it. */ + virt_mb(); + ioreq->state = STATE_IOREQ_INPROCESS; eventfd_signal(kioeventfd->eventfd); - state = STATE_IORESP_READY; + matched = true; break; } } spin_unlock(&kioreq->lock); - /* - * We need a barrier, smp_mb(), here to ensure writes are finished - * before `state` is updated. Since the lock implementation ensures that - * appropriate barrier will be added anyway, we can avoid adding - * explicit barrier here. - */ - - ioreq->state = state; - - if (state == STATE_IORESP_READY) { - notify_remote_via_evtchn(port->port); - return IRQ_HANDLED; - } + if (!matched) + return IRQ_NONE; - return IRQ_NONE; + /* Publish the response only after signaling the eventfd. */ + virt_wmb(); + ioreq->state = STATE_IORESP_READY; + notify_remote_via_evtchn(port->port); + return IRQ_HANDLED; } static void ioreq_free(struct privcmd_kernel_ioreq *kioreq) From fcc43d1d6bf5fbd41e1e308b17187f9b759df56a Mon Sep 17 00:00:00 2001 From: Jan Beulich Date: Thu, 8 Oct 2026 11:48:35 +0200 Subject: [PATCH 1533/2286] x86/PVH: don't truncate initrd address/size Consuming merely the lower halves of the provided values isn't very useful. (While generally loaders try to place it below 4G, especially when it's huge it may not fit there.) Fixes: 7243b93345f7 ("xen/pvh: Bootstrap PVH guest") Signed-off-by: Jan Beulich Reviewed-by: Jason Andryuk Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <9698d810-6664-4a9d-a847-18ad124261e0@suse.com> --- arch/x86/platform/pvh/enlighten.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/x86/platform/pvh/enlighten.c b/arch/x86/platform/pvh/enlighten.c index f2053cbe9b0ce3..9be83990cf1596 100644 --- a/arch/x86/platform/pvh/enlighten.c +++ b/arch/x86/platform/pvh/enlighten.c @@ -78,7 +78,9 @@ static void __init init_pvh_bootparams(bool xen_guest) struct hvm_modlist_entry *modaddr = __va(pvh_start_info.modlist_paddr); pvh_bootparams.hdr.ramdisk_image = modaddr->paddr; + pvh_bootparams.ext_ramdisk_image = modaddr->paddr >> 32; pvh_bootparams.hdr.ramdisk_size = modaddr->size; + pvh_bootparams.ext_ramdisk_size = modaddr->size >> 32; } /* From 6f165abe2b40ede71c98956cd74cd17c18505e75 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:09:56 +0000 Subject: [PATCH 1534/2286] xen/gntdev: Fix gntdev_dmabuf ref leak in dmabuf_exp_wait_released() dmabuf_exp_wait_released() takes a reference on the exported gntdev_dmabuf with dmabuf_exp_wait_obj_get_dmabuf() and passes it to dmabuf_exp_wait_obj_new(), which drops that reference after adding the wait object to the wait list. When the wait object allocation fails, dmabuf_exp_wait_obj_new() returns ERR_PTR(-ENOMEM) early and the reference is never dropped, leaking a reference to the exported gntdev_dmabuf. Drop the reference on the allocation failure path so the reference is consumed whether the wait object is created or not. Fixes: 932d6562179e ("xen/gntdev: Add initial support for dma-buf UAPI") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Juergen Gross Signed-off-by: Juergen Gross Message-ID: <20260916170956.2086505-1-vulab@iscas.ac.cn> --- drivers/xen/gntdev-dmabuf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/xen/gntdev-dmabuf.c b/drivers/xen/gntdev-dmabuf.c index 83b0df46089412..df1883aca850e2 100644 --- a/drivers/xen/gntdev-dmabuf.c +++ b/drivers/xen/gntdev-dmabuf.c @@ -96,8 +96,10 @@ dmabuf_exp_wait_obj_new(struct gntdev_dmabuf_priv *priv, struct gntdev_dmabuf_wait_obj *obj; obj = kzalloc_obj(*obj); - if (!obj) + if (!obj) { + kref_put(&gntdev_dmabuf->u.exp.refcount, dmabuf_exp_release); return ERR_PTR(-ENOMEM); + } init_completion(&obj->completion); obj->gntdev_dmabuf = gntdev_dmabuf; From 3b4854b69ee70fffb2fa7acac09e715a7efcbae1 Mon Sep 17 00:00:00 2001 From: Yehyeong Lee Date: Tue, 6 Oct 2026 11:34:43 +0900 Subject: [PATCH 1535/2286] xen/pcifront: check that an AER callback exists before calling it pcifront_common_process() handles an AER request from the backend by dispatching on aer_op.cmd to the bound driver's PCI error handler. It only checks that err_handler and err_handler->error_detected are present, then for the mmio_enabled, slot_reset and resume commands it calls the corresponding callback unconditionally. Those three callbacks are optional -- the PCI core NULL-checks each of them individually before use -- and many drivers (for example igb, igc, ice and ixgbevf) install error_detected without all of them. aer_op.cmd comes from the shared ring, so a malicious or buggy backend can send XEN_PCI_OP_aer_mmio (or _slotreset/_resume) for a device whose driver leaves that callback NULL and make the frontend call through a NULL pointer, crashing the guest. Check each callback before calling it. When mmio_enabled or slot_reset is absent, return PCI_ERS_RESULT_RECOVERED so recovery proceeds, as the PCI core does for a missing callback; returning PCI_ERS_RESULT_NONE would instead make xen-pciback tear the guest domain down. Fixes: 956a9202cd12 ("xen-pcifront: Xen PCI frontend driver.") Cc: stable@vger.kernel.org Signed-off-by: Yehyeong Lee Reviewed-by: Juergen Gross Acked-by: Bjorn Helgaas Signed-off-by: Juergen Gross Message-ID: <20261006023443.338194-1-yhlee@isslab.korea.ac.kr> --- drivers/pci/xen-pcifront.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c index 0dea8a69fa36c5..b8f2921cc253f9 100644 --- a/drivers/pci/xen-pcifront.c +++ b/drivers/pci/xen-pcifront.c @@ -598,11 +598,16 @@ static pci_ers_result_t pcifront_common_process(int cmd, case XEN_PCI_OP_aer_detected: return pdrv->err_handler->error_detected(pcidev, state); case XEN_PCI_OP_aer_mmio: - return pdrv->err_handler->mmio_enabled(pcidev); + if (pdrv->err_handler->mmio_enabled) + return pdrv->err_handler->mmio_enabled(pcidev); + return PCI_ERS_RESULT_RECOVERED; case XEN_PCI_OP_aer_slotreset: - return pdrv->err_handler->slot_reset(pcidev); + if (pdrv->err_handler->slot_reset) + return pdrv->err_handler->slot_reset(pcidev); + return PCI_ERS_RESULT_RECOVERED; case XEN_PCI_OP_aer_resume: - pdrv->err_handler->resume(pcidev); + if (pdrv->err_handler->resume) + pdrv->err_handler->resume(pcidev); return PCI_ERS_RESULT_NONE; default: dev_err(&pdev->xdev->dev, From cfaf31becde901f7a2376d36db628f8be0bc7283 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:11:41 +0000 Subject: [PATCH 1536/2286] xen-pciback: Fix pcistub_device ref leak in pcistub_get_gsi_from_sbdf() pcistub_get_gsi_from_sbdf() obtains the stub device with pcistub_device_find(), which returns it with its kref incremented, and returns psdev->gsi without dropping that reference again. The caller cannot release it either, so every lookup leaks one reference to the stub device. Release the reference after reading gsi. Fixes: 2fae6bb7be32 ("xen/privcmd: Add new syscall to get gsi from dev") Cc: stable@vger.kernel.org Signed-off-by: Wentao Liang Reviewed-by: Juergen Gross Reviewed-by: Jiqian Chen Signed-off-by: Juergen Gross Message-ID: <20260916171141.2086627-1-vulab@iscas.ac.cn> --- drivers/xen/xen-pciback/pci_stub.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/xen/xen-pciback/pci_stub.c b/drivers/xen/xen-pciback/pci_stub.c index 79a2b5dfd6941f..d1a335233aab0c 100644 --- a/drivers/xen/xen-pciback/pci_stub.c +++ b/drivers/xen/xen-pciback/pci_stub.c @@ -234,13 +234,16 @@ static int pcistub_get_gsi_from_sbdf(unsigned int sbdf) int bus = PCI_BUS_NUM(sbdf); int slot = PCI_SLOT(sbdf); int func = PCI_FUNC(sbdf); + int gsi; psdev = pcistub_device_find(domain, bus, slot, func); - if (!psdev) return -ENODEV; - return psdev->gsi; + gsi = psdev->gsi; + pcistub_device_put(psdev); + + return gsi; } #endif From 833ee20e712bd0c71435feddaa7b578799fe50c6 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 5 Oct 2026 11:22:56 +0200 Subject: [PATCH 1537/2286] s390/dis: Mark non-NUL-terminated character arrays with __nonstring With [1] the generated string initializers were changed to use an array instead of a non-NUL-terminated string. The reason was that GCC 15 warns about string initializers that truncate the NUL terminator with -Wunterminated-string-initialization. The better solution is to use the __nonstring annotation, which exists to deal exactly with this problem. Therefore use it and basically revert [1] in order to generate more readable initializers: Before: [LONG_INSN_ALGHSIK] = { 'a', 'l', 'g', 'h', 's', 'i', 'k', }, After: [LONG_INSN_ALGHSIK] = "alghsik", [1] commit 7cbae7ea3de5 ("s390/tools: Use array instead of string initializer") Reviewed-by: Jens Remus Signed-off-by: Heiko Carstens --- arch/s390/kernel/dis.c | 4 ++-- arch/s390/tools/gen_opcode_table.c | 23 ++++------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/arch/s390/kernel/dis.c b/arch/s390/kernel/dis.c index 1cec93895b3a2f..9c349eb884bfa1 100644 --- a/arch/s390/kernel/dis.c +++ b/arch/s390/kernel/dis.c @@ -51,7 +51,7 @@ struct s390_operand { struct s390_insn { union { - const char name[5]; + const char name[5] __nonstring; struct { unsigned char zero; unsigned int offset; @@ -340,7 +340,7 @@ static const unsigned char formats[][6] = { [INSTR_VSI_URDV] = { V_32, D_20, B_16, U8_8, 0, 0 }, }; -static char long_insn_name[][7] = LONG_INSN_INITIALIZER; +static char long_insn_name[][7] __nonstring_array = LONG_INSN_INITIALIZER; static struct s390_insn opcode[] = OPCODE_TABLE_INITIALIZER; static struct s390_opcode_offset opcode_offset[] = OPCODE_OFFSET_INITIALIZER; diff --git a/arch/s390/tools/gen_opcode_table.c b/arch/s390/tools/gen_opcode_table.c index 7d76c417f83f52..820905eca78aad 100644 --- a/arch/s390/tools/gen_opcode_table.c +++ b/arch/s390/tools/gen_opcode_table.c @@ -201,17 +201,6 @@ static int cmp_long_insn(const void *a, const void *b) return strcmp(((struct insn *)a)->name, ((struct insn *)b)->name); } -static void print_insn_name(const char *name) -{ - size_t i, len; - - len = strlen(name); - printf("{"); - for (i = 0; i < len; i++) - printf(" \'%c\',", name[i]); - printf(" }"); -} - static void print_long_insn(struct gen_opcode *desc) { struct insn *insn; @@ -234,9 +223,7 @@ static void print_long_insn(struct gen_opcode *desc) insn = &desc->insn[i]; if (insn->name_len < 6) continue; - printf("\t[LONG_INSN_%s] = ", insn->upper); - print_insn_name(insn->name); - printf(", \\\n"); + printf("\t[LONG_INSN_%s] = \"%s\", \\\n", insn->upper, insn->name); } printf("}\n\n"); } @@ -249,12 +236,10 @@ static void print_opcode(struct insn *insn, int nr) if (insn->type->byte != 0) opcode += 2; printf("\t[%4d] = { .opfrag = 0x%s, .format = INSTR_%s, ", nr, opcode, insn->format); - if (insn->name_len < 6) { - printf(".name = "); - print_insn_name(insn->name); - } else { + if (insn->name_len < 6) + printf(".name = \"%s\"", insn->name); + else printf(".offset = LONG_INSN_%s", insn->upper); - } printf(" }, \\\n"); } From 99a95985dfc85eecad97f0991a2401d2fcef294c Mon Sep 17 00:00:00 2001 From: Sumanth Korikkar Date: Tue, 6 Oct 2026 13:13:03 +0200 Subject: [PATCH 1538/2286] s390/cpum_cf: Check session mask before cpu offline cfset_offline_cpu() stops counter sets for every active session without checking whether the outgoing cpu belongs to the session mask. For example, run an hwctr session on cpu0 and a cpu bound perf event on cpu1: lshwc -l 9999 -i 5 0:b perf stat -C 1 -e cpum_cf/CPU_CYCLES/ sleep 120 chcpu -d 1 chcpu -e 1 Although the hwctr session does not include cpu1, cfset_offline_cpu() decrements its counter set reference count. The subsequent perf teardown decrements it again, causing it to underflow. The perf event keeps the percpu state alive across hotplug. When cpu1 comes online, the hwctr session is started there and the reference count moves from -1 to 0. Stopping the hwctr session then decrements it to -1 again, so the zero triggered counter set disable path is skipped. Check the session mask before stopping counter sets on the outgoing cpu. This prevents the unmatched decrement and the resulting reference count underflow. Fixes: a029a4eab39e ("s390/cpumf: Allow concurrent access for CPU Measurement Counter Facility") Reported-by: Christian Borntraeger Cc: stable@vger.kernel.org # v5.14+ Signed-off-by: Sumanth Korikkar Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/perf_cpum_cf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index 69fa303cd47915..1d63dc3b0ab99a 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -1797,6 +1797,9 @@ static int cfset_offline_cpu(unsigned int cpu) if (!list_empty(&cfset_session.head)) { list_for_each_entry(rp, &cfset_session.head, node) { + if (!cpumask_test_cpu(cpu, &rp->mask)) + continue; + p.sets = rp->ctrset; cfset_ioctl_off(&p); cpumask_clear_cpu(cpu, &rp->mask); From c8b2bb9385d08fbb734146b7902838ca79097127 Mon Sep 17 00:00:00 2001 From: Sumanth Korikkar Date: Tue, 6 Oct 2026 13:13:04 +0200 Subject: [PATCH 1539/2286] s390/cpum_cf: Honor hwctr session cpu mask during hotplug cfset_online_cpu() and cfset_offline_cpu() cpu hotplug callbacks start and stop counter sets for every active hwctr session without checking the session cpu mask, and dynamically modify that mask on each hotplug event. For example, assume cpu0 and cpu1 are online and cpu2 is offline. Start an hwctr session restricted to cpu0 and cpu1, and then bring cpu2 online: lshwc -l 9999 -i 5 0-1:b chcpu -e 2 Although cpu2 is not part of the requested mask, cfset_online_cpu() starts the counter sets on cpu2 and adds it to the session mask. cpu2 can then appear in lshwc output (s390-tools) and is handled as part of the session until it is stopped. Keep the userspace provided cpu mask unchanged and update counter sets only when the hotplugged cpu belongs to that mask. For all cpu requests, lshwc should supply the possible cpu mask so that cpus coming online later remain part of the session. Note: A corresponding lshwc (s390-tools) change is needed to supply the possible cpu mask for allcpu sessions, ensuring that cpus coming online later remain part of the session. Without it, newly online cpus would be silently excluded from an active session. Since this userspace change is not yet available, the patch is not suitable for stable kernels and intentionally omits a Fixes: tag. The prerequisite bug fix is in "s390/cpum_cf: Check session mask before cpu offline". Reported-by: Christian Borntraeger Signed-off-by: Sumanth Korikkar Reviewed-by: Heiko Carstens Signed-off-by: Heiko Carstens --- arch/s390/kernel/perf_cpum_cf.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index 1d63dc3b0ab99a..a2334af5154474 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -1768,8 +1768,9 @@ static struct miscdevice cfset_dev = { .mode = 0666, }; -/* Hotplug add of a CPU. Scan through all active processes and add - * that CPU to the list of CPUs supplied with ioctl(..., START, ...). +/* + * Start counter sets on a newly online CPU for each active hwctr session that + * includes this CPU in its requested mask. */ static int cfset_online_cpu(unsigned int cpu) { @@ -1778,17 +1779,19 @@ static int cfset_online_cpu(unsigned int cpu) if (!list_empty(&cfset_session.head)) { list_for_each_entry(rp, &cfset_session.head, node) { + if (!cpumask_test_cpu(cpu, &rp->mask)) + continue; + p.sets = rp->ctrset; cfset_ioctl_on(&p); - cpumask_set_cpu(cpu, &rp->mask); } } return 0; } -/* Hotplug remove of a CPU. Scan through all active processes and clear - * that CPU from the list of CPUs supplied with ioctl(..., START, ...). - * Adjust reference counts. +/* + * Stop counter sets on an outgoing CPU for each active hwctr session that + * includes this CPU in its requested mask. */ static int cfset_offline_cpu(unsigned int cpu) { @@ -1802,7 +1805,6 @@ static int cfset_offline_cpu(unsigned int cpu) p.sets = rp->ctrset; cfset_ioctl_off(&p); - cpumask_clear_cpu(cpu, &rp->mask); } } return 0; From 0c920885e593ee0be7579c22adf7f6a1bbef71be Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 6 Oct 2026 16:01:12 +0200 Subject: [PATCH 1540/2286] s390/appldata: Emulate virtual timer with delayed work Emulate the virtual timer in appldata using delayed work: the work is scheduled for the minimum possible wall-clock time until the configured CPU-time interval elapses (remaining CPU time / number of online CPUs), with a minimum delay of 100ms. The work then reads per-CPU statistics of all online CPUs to check whether enough CPU time has elapsed, and if so runs the registered callbacks. This is slightly more expensive, but allows to subsequently remove the entire vtimer infrastructure. Reviewed-by: Gerald Schaefer Tested-by: Gerald Schaefer Signed-off-by: Heiko Carstens --- arch/s390/appldata/appldata_base.c | 173 ++++++++++++++++++----------- 1 file changed, 108 insertions(+), 65 deletions(-) diff --git a/arch/s390/appldata/appldata_base.c b/arch/s390/appldata/appldata_base.c index 9cba4633c3f3b1..836c51da18f4b2 100644 --- a/arch/s390/appldata/appldata_base.c +++ b/arch/s390/appldata/appldata_base.c @@ -28,19 +28,14 @@ #include #include #include +#include #include -#include #include #include "appldata.h" - -#define APPLDATA_CPU_INTERVAL 10000 /* default (CPU) time for - sampling interval in - milliseconds */ - -#define TOD_MICRO 0x01000 /* nr. of TOD clock units - for 1 microsecond */ +/* Default CPU time for sampling interval */ +#define APPLDATA_CPU_INTERVAL (10 * NSEC_PER_SEC) /* * /proc entries (sysctl) @@ -64,22 +59,14 @@ static const struct ctl_table appldata_table[] = { }, }; -/* - * Timer - */ -static struct vtimer_list appldata_timer; +static void appldata_work_fn(struct work_struct *work); +static DECLARE_DELAYED_WORK(appldata_work, appldata_work_fn); -static DEFINE_SPINLOCK(appldata_timer_lock); -static int appldata_interval = APPLDATA_CPU_INTERVAL; +static DEFINE_MUTEX(appldata_timer_lock); +static u64 appldata_interval = APPLDATA_CPU_INTERVAL; static int appldata_timer_active; -/* - * Work queue - */ -static struct workqueue_struct *appldata_wq; -static void appldata_work_fn(struct work_struct *work); -static DECLARE_WORK(appldata_work, appldata_work_fn); - +static u64 appldata_cputime_start; /* * Ops list @@ -89,14 +76,34 @@ static LIST_HEAD(appldata_ops_list); /*************************** timer, work, DIAG *******************************/ -/* - * appldata_timer_function() - * - * schedule work and reschedule timer - */ -static void appldata_timer_function(unsigned long data) +static u64 appldata_total_cpu_time_ns(void) { - queue_work(appldata_wq, (struct work_struct *) data); + u64 total = 0; + int cpu; + + for_each_possible_cpu(cpu) { + total += kcpustat_cpu(cpu).cpustat[CPUTIME_USER]; + total += kcpustat_cpu(cpu).cpustat[CPUTIME_NICE]; + total += kcpustat_cpu(cpu).cpustat[CPUTIME_SYSTEM]; + total += kcpustat_cpu(cpu).cpustat[CPUTIME_IRQ]; + total += kcpustat_cpu(cpu).cpustat[CPUTIME_SOFTIRQ]; + } + return total; +} + +static void appldata_schedule_work(u64 remaining) +{ + unsigned int ncpus = num_online_cpus(); + unsigned long delay = HZ / 10; + + /* + * At most ncpus CPUs consume CPU time simultaneously, so the + * minimum wall-clock time until the remaining CPU time elapses + * is remaining / ncpus. + * Make sure the work is not scheduled more than once per 100ms. + */ + delay = max(delay, nsecs_to_jiffies(remaining / ncpus)); + mod_delayed_work(system_percpu_wq, &appldata_work, delay); } /* @@ -106,9 +113,27 @@ static void appldata_timer_function(unsigned long data) */ static void appldata_work_fn(struct work_struct *work) { - struct list_head *lh; + u64 now, elapsed, interval; struct appldata_ops *ops; - + struct list_head *lh; + bool expired = false; + + now = appldata_total_cpu_time_ns(); + mutex_lock(&appldata_timer_lock); + if (appldata_timer_active) { + interval = appldata_interval; + elapsed = now - appldata_cputime_start; + if (elapsed < interval) { + appldata_schedule_work(interval - elapsed); + } else { + appldata_cputime_start = now; + appldata_schedule_work(interval); + expired = true; + } + } + mutex_unlock(&appldata_timer_lock); + if (!expired) + return; mutex_lock(&appldata_ops_mutex); list_for_each(lh, &appldata_ops_list) { ops = list_entry(lh, struct appldata_ops, list); @@ -162,33 +187,52 @@ int appldata_diag(char record_nr, u16 function, unsigned long buffer, #define APPLDATA_MOD_TIMER 2 /* - * __appldata_vtimer_setup() + * __appldata_timer_setup() * - * Add, delete or modify virtual timers on all online cpus. - * The caller needs to get the appldata_timer_lock spinlock. + * Add, delete or modify the appldata delayed work. */ -static void __appldata_vtimer_setup(int cmd) +static void __appldata_timer_setup(int cmd) { - u64 timer_interval = (u64) appldata_interval * 1000 * TOD_MICRO; + static DEFINE_MUTEX(appldata_cancel_lock); + u64 now, elapsed, remaining; switch (cmd) { case APPLDATA_ADD_TIMER: - if (appldata_timer_active) - break; - appldata_timer.expires = timer_interval; - add_virt_timer_periodic(&appldata_timer); - appldata_timer_active = 1; + mutex_lock(&appldata_cancel_lock); + mutex_lock(&appldata_timer_lock); + if (!appldata_timer_active) { + appldata_cputime_start = appldata_total_cpu_time_ns(); + appldata_schedule_work(appldata_interval); + appldata_timer_active = 1; + } + mutex_unlock(&appldata_timer_lock); + mutex_unlock(&appldata_cancel_lock); break; case APPLDATA_DEL_TIMER: - del_virt_timer(&appldata_timer); - if (!appldata_timer_active) + mutex_lock(&appldata_cancel_lock); + mutex_lock(&appldata_timer_lock); + if (!appldata_timer_active) { + mutex_unlock(&appldata_timer_lock); + mutex_unlock(&appldata_cancel_lock); break; + } appldata_timer_active = 0; + mutex_unlock(&appldata_timer_lock); + cancel_delayed_work_sync(&appldata_work); + mutex_unlock(&appldata_cancel_lock); break; case APPLDATA_MOD_TIMER: - if (!appldata_timer_active) - break; - mod_virt_timer_periodic(&appldata_timer, timer_interval); + mutex_lock(&appldata_timer_lock); + if (appldata_timer_active) { + now = appldata_total_cpu_time_ns(); + elapsed = now - appldata_cputime_start; + remaining = appldata_interval - elapsed; + if (elapsed >= appldata_interval) + remaining = 1; + appldata_schedule_work(remaining); + } + mutex_unlock(&appldata_timer_lock); + break; } } @@ -215,12 +259,10 @@ appldata_timer_handler(const struct ctl_table *ctl, int write, if (rc < 0 || !write) return rc; - spin_lock(&appldata_timer_lock); if (timer_active) - __appldata_vtimer_setup(APPLDATA_ADD_TIMER); + __appldata_timer_setup(APPLDATA_ADD_TIMER); else - __appldata_vtimer_setup(APPLDATA_DEL_TIMER); - spin_unlock(&appldata_timer_lock); + __appldata_timer_setup(APPLDATA_DEL_TIMER); return 0; } @@ -234,7 +276,7 @@ static int appldata_interval_handler(const struct ctl_table *ctl, int write, void *buffer, size_t *lenp, loff_t *ppos) { - int interval = appldata_interval; + int interval = appldata_interval / NSEC_PER_MSEC; int rc; struct ctl_table ctl_entry = { .procname = ctl->procname, @@ -247,10 +289,10 @@ appldata_interval_handler(const struct ctl_table *ctl, int write, if (rc < 0 || !write) return rc; - spin_lock(&appldata_timer_lock); - appldata_interval = interval; - __appldata_vtimer_setup(APPLDATA_MOD_TIMER); - spin_unlock(&appldata_timer_lock); + mutex_lock(&appldata_timer_lock); + appldata_interval = interval * NSEC_PER_MSEC; + mutex_unlock(&appldata_timer_lock); + __appldata_timer_setup(APPLDATA_MOD_TIMER); return 0; } @@ -392,19 +434,20 @@ void appldata_unregister_ops(struct appldata_ops *ops) /******************************* init / exit *********************************/ -/* - * appldata_init() - * - * init timer, register /proc entries - */ +static int appldata_cpu_online(unsigned int cpu) +{ + __appldata_timer_setup(APPLDATA_MOD_TIMER); + return 0; +} + static int __init appldata_init(void) { - init_virt_timer(&appldata_timer); - appldata_timer.function = appldata_timer_function; - appldata_timer.data = (unsigned long) &appldata_work; - appldata_wq = alloc_ordered_workqueue("appldata", 0); - if (!appldata_wq) - return -ENOMEM; + int rc; + + rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "s390/appldata:online", + appldata_cpu_online, NULL); + if (rc < 0) + return rc; register_sysctl(appldata_proc_name, appldata_table); return 0; } From 13efc382cc7cc0dfba557b55dc65477c2d24ddf3 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 6 Oct 2026 16:01:13 +0200 Subject: [PATCH 1541/2286] s390/vtime: Remove vtimer infrastructure The only user is gone - remove the vtimer infrastructure. Signed-off-by: Heiko Carstens --- arch/s390/include/asm/vtime.h | 2 + arch/s390/include/asm/vtimer.h | 30 ----- arch/s390/kernel/process.c | 1 - arch/s390/kernel/smp.c | 2 +- arch/s390/kernel/time.c | 2 +- arch/s390/kernel/vtime.c | 212 ++------------------------------- 6 files changed, 12 insertions(+), 237 deletions(-) delete mode 100644 arch/s390/include/asm/vtimer.h diff --git a/arch/s390/include/asm/vtime.h b/arch/s390/include/asm/vtime.h index da116a93d3b6cd..22e494b2219270 100644 --- a/arch/s390/include/asm/vtime.h +++ b/arch/s390/include/asm/vtime.h @@ -8,6 +8,8 @@ DECLARE_PER_CPU(u64, mt_cycles[8]); +void vtime_init(void); + static inline void update_timer_sys(void) { struct lowcore *lc = get_lowcore(); diff --git a/arch/s390/include/asm/vtimer.h b/arch/s390/include/asm/vtimer.h deleted file mode 100644 index e601adaa6320d5..00000000000000 --- a/arch/s390/include/asm/vtimer.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright IBM Corp. 2003, 2012 - * Virtual CPU timer - * - * Author(s): Jan Glauber - */ - -#ifndef _ASM_S390_TIMER_H -#define _ASM_S390_TIMER_H - -#define VTIMER_MAX_SLICE (0x7fffffffffffffffULL) - -struct vtimer_list { - struct list_head entry; - u64 expires; - u64 interval; - void (*function)(unsigned long); - unsigned long data; -}; - -extern void init_virt_timer(struct vtimer_list *timer); -extern void add_virt_timer(struct vtimer_list *timer); -extern void add_virt_timer_periodic(struct vtimer_list *timer); -extern int mod_virt_timer(struct vtimer_list *timer, u64 expires); -extern int mod_virt_timer_periodic(struct vtimer_list *timer, u64 expires); -extern int del_virt_timer(struct vtimer_list *timer); -extern void vtime_init(void); - -#endif /* _ASM_S390_TIMER_H */ diff --git a/arch/s390/kernel/process.c b/arch/s390/kernel/process.c index 416650ae4871c6..4c73d2528fcb22 100644 --- a/arch/s390/kernel/process.c +++ b/arch/s390/kernel/process.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index 32499cad86f00c..f7c262a17c034c 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/arch/s390/kernel/time.c b/arch/s390/kernel/time.c index 2b989bebd220ad..dd8a88cc9c4b2b 100644 --- a/arch/s390/kernel/time.c +++ b/arch/s390/kernel/time.c @@ -48,9 +48,9 @@ #include #include #include -#include #include #include +#include #include "entry.h" union tod_clock __bootdata_preserved(tod_clock_base); diff --git a/arch/s390/kernel/vtime.c b/arch/s390/kernel/vtime.c index efcbf406f03e73..d9fd39c5732ee5 100644 --- a/arch/s390/kernel/vtime.c +++ b/arch/s390/kernel/vtime.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Virtual cpu timer based timer functions. + * Virtual CPU time accounting * * Copyright IBM Corp. 2004, 2012 * Author(s): Jan Glauber @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -22,19 +21,14 @@ #include "entry.h" -static void virt_timer_expire(void); - -static LIST_HEAD(virt_timer_list); -static DEFINE_SPINLOCK(virt_timer_lock); -static atomic64_t virt_timer_current; -static atomic64_t virt_timer_elapsed; +#define CPU_TIMER_MAX 0x7fffffffffffffffUL DEFINE_PER_CPU(u64, mt_cycles[8]); static DEFINE_PER_CPU(u64, mt_scaling_mult) = { 1 }; static DEFINE_PER_CPU(u64, mt_scaling_div) = { 1 }; static DEFINE_PER_CPU(unsigned long, mt_scaling_jiffies); -static inline void set_vtimer(u64 expires) +static inline void cpu_timer_init(u64 value) { struct lowcore *lc = get_lowcore(); u64 timer; @@ -42,18 +36,9 @@ static inline void set_vtimer(u64 expires) asm volatile( " stpt %0\n" /* Store current cpu timer value */ " spt %1" /* Set new value imm. afterwards */ - : "=Q" (timer) : "Q" (expires)); + : "=Q" (timer) : "Q" (value)); lc->system_timer += lc->last_update_timer - timer; - lc->last_update_timer = expires; -} - -static inline int virt_timer_forward(u64 elapsed) -{ - lockdep_assert_irqs_disabled(); - if (list_empty(&virt_timer_list)) - return 0; - elapsed = atomic64_add_return(elapsed, &virt_timer_elapsed); - return elapsed >= atomic64_read(&virt_timer_current); + lc->last_update_timer = value; } static void update_mt_scaling(void) @@ -125,7 +110,7 @@ static inline void vtime_reset_last_update(struct lowcore *lc) * Update process times based on virtual cpu times stored by entry.S * to the lowcore fields user_timer, system_timer & steal_clock. */ -static int do_account_vtime(struct task_struct *tsk) +static void do_account_vtime(struct task_struct *tsk) { u64 timer, clock, user, guest, system, hardirq, softirq; struct lowcore *lc = get_lowcore(); @@ -172,8 +157,6 @@ static int do_account_vtime(struct task_struct *tsk) account_system_index_scaled(tsk, hardirq, CPUTIME_IRQ); if (softirq) account_system_index_scaled(tsk, softirq, CPUTIME_SOFTIRQ); - - return virt_timer_forward(user + guest + system + hardirq + softirq); } void vtime_task_switch(struct task_struct *prev) @@ -203,8 +186,7 @@ void vtime_flush(struct task_struct *tsk) struct lowcore *lc = get_lowcore(); u64 steal, avg_steal; - if (do_account_vtime(tsk)) - virt_timer_expire(); + do_account_vtime(tsk); steal = lc->steal_timer; avg_steal = lc->avg_steal_timer; @@ -247,187 +229,9 @@ void vtime_account_hardirq(struct task_struct *tsk) get_lowcore()->hardirq_timer += vtime_delta(); } -/* - * Sorted add to a list. List is linear searched until first bigger - * element is found. - */ -static void list_add_sorted(struct vtimer_list *timer, struct list_head *head) -{ - struct vtimer_list *tmp; - - list_for_each_entry(tmp, head, entry) { - if (tmp->expires > timer->expires) { - list_add_tail(&timer->entry, &tmp->entry); - return; - } - } - list_add_tail(&timer->entry, head); -} - -/* - * Handler for expired virtual CPU timer. - */ -static void virt_timer_expire(void) -{ - struct vtimer_list *timer, *tmp; - unsigned long elapsed; - LIST_HEAD(cb_list); - - /* walk timer list, fire all expired timers */ - spin_lock(&virt_timer_lock); - elapsed = atomic64_read(&virt_timer_elapsed); - list_for_each_entry_safe(timer, tmp, &virt_timer_list, entry) { - if (timer->expires < elapsed) - /* move expired timer to the callback queue */ - list_move_tail(&timer->entry, &cb_list); - else - timer->expires -= elapsed; - } - if (!list_empty(&virt_timer_list)) { - timer = list_first_entry(&virt_timer_list, - struct vtimer_list, entry); - atomic64_set(&virt_timer_current, timer->expires); - } - atomic64_sub(elapsed, &virt_timer_elapsed); - spin_unlock(&virt_timer_lock); - - /* Do callbacks and recharge periodic timers */ - list_for_each_entry_safe(timer, tmp, &cb_list, entry) { - list_del_init(&timer->entry); - timer->function(timer->data); - if (timer->interval) { - /* Recharge interval timer */ - timer->expires = timer->interval + - atomic64_read(&virt_timer_elapsed); - spin_lock(&virt_timer_lock); - list_add_sorted(timer, &virt_timer_list); - spin_unlock(&virt_timer_lock); - } - } -} - -void init_virt_timer(struct vtimer_list *timer) -{ - timer->function = NULL; - INIT_LIST_HEAD(&timer->entry); -} -EXPORT_SYMBOL(init_virt_timer); - -static inline int vtimer_pending(struct vtimer_list *timer) -{ - return !list_empty(&timer->entry); -} - -static void internal_add_vtimer(struct vtimer_list *timer) -{ - if (list_empty(&virt_timer_list)) { - /* First timer, just program it. */ - atomic64_set(&virt_timer_current, timer->expires); - atomic64_set(&virt_timer_elapsed, 0); - list_add(&timer->entry, &virt_timer_list); - } else { - /* Update timer against current base. */ - timer->expires += atomic64_read(&virt_timer_elapsed); - if (likely((s64) timer->expires < - (s64) atomic64_read(&virt_timer_current))) - /* The new timer expires before the current timer. */ - atomic64_set(&virt_timer_current, timer->expires); - /* Insert new timer into the list. */ - list_add_sorted(timer, &virt_timer_list); - } -} - -static void __add_vtimer(struct vtimer_list *timer, int periodic) -{ - unsigned long flags; - - timer->interval = periodic ? timer->expires : 0; - spin_lock_irqsave(&virt_timer_lock, flags); - internal_add_vtimer(timer); - spin_unlock_irqrestore(&virt_timer_lock, flags); -} - -/* - * add_virt_timer - add a oneshot virtual CPU timer - */ -void add_virt_timer(struct vtimer_list *timer) -{ - __add_vtimer(timer, 0); -} -EXPORT_SYMBOL(add_virt_timer); - -/* - * add_virt_timer_int - add an interval virtual CPU timer - */ -void add_virt_timer_periodic(struct vtimer_list *timer) -{ - __add_vtimer(timer, 1); -} -EXPORT_SYMBOL(add_virt_timer_periodic); - -static int __mod_vtimer(struct vtimer_list *timer, u64 expires, int periodic) -{ - unsigned long flags; - int rc; - - BUG_ON(!timer->function); - - if (timer->expires == expires && vtimer_pending(timer)) - return 1; - spin_lock_irqsave(&virt_timer_lock, flags); - rc = vtimer_pending(timer); - if (rc) - list_del_init(&timer->entry); - timer->interval = periodic ? expires : 0; - timer->expires = expires; - internal_add_vtimer(timer); - spin_unlock_irqrestore(&virt_timer_lock, flags); - return rc; -} - -/* - * returns whether it has modified a pending timer (1) or not (0) - */ -int mod_virt_timer(struct vtimer_list *timer, u64 expires) -{ - return __mod_vtimer(timer, expires, 0); -} -EXPORT_SYMBOL(mod_virt_timer); - -/* - * returns whether it has modified a pending timer (1) or not (0) - */ -int mod_virt_timer_periodic(struct vtimer_list *timer, u64 expires) -{ - return __mod_vtimer(timer, expires, 1); -} -EXPORT_SYMBOL(mod_virt_timer_periodic); - -/* - * Delete a virtual timer. - * - * returns whether the deleted timer was pending (1) or not (0) - */ -int del_virt_timer(struct vtimer_list *timer) -{ - unsigned long flags; - - if (!vtimer_pending(timer)) - return 0; - spin_lock_irqsave(&virt_timer_lock, flags); - list_del_init(&timer->entry); - spin_unlock_irqrestore(&virt_timer_lock, flags); - return 1; -} -EXPORT_SYMBOL(del_virt_timer); - -/* - * Start the virtual CPU timer on the current CPU. - */ void vtime_init(void) { - /* set initial cpu timer */ - set_vtimer(VTIMER_MAX_SLICE); + cpu_timer_init(CPU_TIMER_MAX); /* Setup initial MT scaling values */ if (smp_cpu_mtid) { __this_cpu_write(mt_scaling_jiffies, jiffies); From 7b88e8b6d714cd1b2b6185e2963c8ec4d98542d0 Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Tue, 6 Oct 2026 17:46:02 +0200 Subject: [PATCH 1542/2286] s390/zcrypt: Guard domain index uses against speculative bypass The previous array_index_nospec() placement only covered the admin permission check, leaving ap_test_config_usage_domain() and ap_test_config_ctrl_domain() in the CCA path exposed to speculative execution. Move the sanitization to before all permission and config checks in both the CCA and EP11 paths. Hold the sanitized domain value in another variable so that the later use can choose to use either the original value (subject to speculative execution) or the sanitized value (which may in case of AUTOSEL_DOM reflect the wrong value). Before that, check the domain value to be either AUTOSEL_DOM or in range 0...AP_DOMAINS-1 and return with -EINVAL in case this check does not pass. Fixes: e935cd525af4 ("s390/zcrypt: Close speculative mem read possibility") Signed-off-by: Harald Freudenberger Cc: stable@vger.kernel.org Reviewed-by: Holger Dengler Signed-off-by: Heiko Carstens --- drivers/s390/crypto/zcrypt_api.c | 35 +++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_api.c b/drivers/s390/crypto/zcrypt_api.c index ec6a4c2f9f04a7..80435fd8e6ad61 100644 --- a/drivers/s390/crypto/zcrypt_api.c +++ b/drivers/s390/crypto/zcrypt_api.c @@ -854,7 +854,7 @@ static long _zcrypt_send_cprb(u32 xflags, struct ap_perms *perms, struct ica_xcRB *xcrb) { bool userspace = xflags & ZCRYPT_XFLAG_USERSPACE; - unsigned int card, domain, func_code = 0; + unsigned int card, domain, _dom, func_code = 0; unsigned int wgt = 0, pref_wgt = 0; struct zcrypt_queue *zq, *pref_zq; struct zcrypt_card *zc, *pref_zc; @@ -877,10 +877,17 @@ static long _zcrypt_send_cprb(u32 xflags, struct ap_perms *perms, print_hex_dump_debug("ccareq: ", DUMP_PREFIX_ADDRESS, 16, 1, ap_msg.msg, ap_msg.len, false); + /* Check domain for either AUTOSEL_DOM or in range 0...AP_DOMAINS-1 */ + if (domain != AUTOSEL_DOM && domain >= AP_DOMAINS) { + rc = -EINVAL; + goto out; + } + /* Spectre-v1: sanitize domain unconditionally for later use */ + _dom = array_index_nospec(domain, AP_DOMAINS); + if (perms != &ap_perms && domain < AP_DOMAINS) { if (ap_msg.flags & AP_MSG_FLAG_ADMIN) { - domain = array_index_nospec(domain, AP_DOMAINS); - if (!test_bit_inv(domain, perms->adm)) { + if (!test_bit_inv(_dom, perms->adm)) { rc = -ENODEV; goto out; } @@ -893,10 +900,11 @@ static long _zcrypt_send_cprb(u32 xflags, struct ap_perms *perms, * If a valid target domain is set and this domain is NOT a usage * domain but a control only domain, autoselect target domain. */ - if (domain < AP_DOMAINS && - !ap_test_config_usage_domain(domain) && - ap_test_config_ctrl_domain(domain)) - domain = AUTOSEL_DOM; + if (domain < AP_DOMAINS) { + if (!ap_test_config_usage_domain(_dom) && + ap_test_config_ctrl_domain(_dom)) + domain = AUTOSEL_DOM; + } pref_zc = NULL; pref_zq = NULL; @@ -1041,7 +1049,7 @@ static long _zcrypt_send_ep11_cprb(u32 xflags, struct ap_perms *perms, struct ep11_target_dev *targets = NULL; unsigned short target_num; unsigned int wgt = 0, pref_wgt = 0; - unsigned int func_code = 0, domain; + unsigned int func_code = 0, domain, _dom; struct ap_message ap_msg; int cpen, qpen, qid = 0, rc; struct module *mod; @@ -1078,10 +1086,17 @@ static long _zcrypt_send_ep11_cprb(u32 xflags, struct ap_perms *perms, print_hex_dump_debug("ep11req: ", DUMP_PREFIX_ADDRESS, 16, 1, ap_msg.msg, ap_msg.len, false); + /* Check domain for either AUTOSEL_DOM or in range 0...AP_DOMAINS-1 */ + if (domain != AUTOSEL_DOM && domain >= AP_DOMAINS) { + rc = -EINVAL; + goto out; + } + /* Spectre-v1: sanitize domain unconditionally for later use */ + _dom = array_index_nospec(domain, AP_DOMAINS); + if (perms != &ap_perms && domain < AP_DOMAINS) { if (ap_msg.flags & AP_MSG_FLAG_ADMIN) { - domain = array_index_nospec(domain, AP_DOMAINS); - if (!test_bit_inv(domain, perms->adm)) { + if (!test_bit_inv(_dom, perms->adm)) { rc = -ENODEV; goto out; } From 9fd7fca66735307fd7afa3450882a55ef80f2add Mon Sep 17 00:00:00 2001 From: Harald Freudenberger Date: Tue, 6 Oct 2026 17:46:03 +0200 Subject: [PATCH 1543/2286] s390/zcrypt: Fix out-of-bounds ptr advance in cca_query_crypto_facility() The FQ reply parser code blindly advanced the walk pointer by a length value read directly from the hardware reply payload without checking that the advance stayed within the allocated reply buffer. A corrupt or malicious device response could push ptr beyond the cprbmem region, causing an out-of-bounds dereference or kernel memory exposure via the subsequent memcpy(). Fix this by tracking the remaining reply buffer space in a variable and validating each device-supplied length field against it before advancing or dereferencing the pointer. Fixes: 2004b57cde6b ("s390/zcrypt: code cleanup") Signed-off-by: Harald Freudenberger Cc: stable@vger.kernel.org Reviewed-by: Holger Dengler Signed-off-by: Heiko Carstens --- drivers/s390/crypto/zcrypt_ccamisc.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/s390/crypto/zcrypt_ccamisc.c b/drivers/s390/crypto/zcrypt_ccamisc.c index 19909bf43dc91f..862947431164f8 100644 --- a/drivers/s390/crypto/zcrypt_ccamisc.c +++ b/drivers/s390/crypto/zcrypt_ccamisc.c @@ -1627,6 +1627,7 @@ int cca_query_crypto_facility(u16 cardnr, u16 domain, u8 subfunc_code[2]; u8 lvdata[]; } __packed * prepparm; + size_t datalen; /* get already prepared memory for 2 cprbs with param block each */ rc = alloc_and_prep_cprbmem(parmbsize, &mem, @@ -1673,27 +1674,44 @@ int cca_query_crypto_facility(u16 cardnr, u16 domain, prepcblk->rpl_parmb = (u8 __user *)ptr; prepparm = (struct fqrepparm *)ptr; ptr = prepparm->lvdata; + datalen = parmbsize - 2 * sizeof(u8); /* check and possibly copy reply rule array */ len = *((u16 *)ptr); + if (len > datalen) { + ZCRYPT_DBF_ERR("%s reply rule array len %u exceeds datalen %zu\n", + __func__, len, datalen); + rc = -EIO; + goto out; + } + datalen -= sizeof(u16); + ptr += sizeof(u16); if (len > sizeof(u16)) { - ptr += sizeof(u16); len -= sizeof(u16); if (rarray && rarraylen && *rarraylen > 0) { *rarraylen = (len > *rarraylen ? *rarraylen : len); memcpy(rarray, ptr, *rarraylen); } + datalen -= len; ptr += len; } /* check and possible copy reply var array */ len = *((u16 *)ptr); + if (len > datalen) { + ZCRYPT_DBF_ERR("%s reply var array len %u exceeds datalen %zu\n", + __func__, len, datalen); + rc = -EIO; + goto out; + } + datalen -= sizeof(u16); + ptr += sizeof(u16); if (len > sizeof(u16)) { - ptr += sizeof(u16); len -= sizeof(u16); if (varray && varraylen && *varraylen > 0) { *varraylen = (len > *varraylen ? *varraylen : len); memcpy(varray, ptr, *varraylen); } + datalen -= len; ptr += len; } From 61b6d1df1d6234c38a6ea7e4f58e7da035ef6ab4 Mon Sep 17 00:00:00 2001 From: Ross Lagerwall Date: Tue, 6 Oct 2026 17:34:07 +0100 Subject: [PATCH 1544/2286] xen-blkfront: Fix IO race during unplug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit During unplug, blkfront stops the hw queues and marks the disk as dead, then later during removal calls del_gendisk(). However, IO issued after the hw queues are stopped but before the call to del_gendisk() will be queued but never handled. This causes del_gendisk() to hang forever waiting for the queue refcount to drop to zero. This can be reproduced by issuing IO during an artificial delay after stopping the hw queues. Fix this by simply not stopping the hw queues directly. Marking the disk as dead already freezes the queue which prevents new requests being added and it synchronously runs the hw queues to clear anything pending. If the hw queues are stopped when calling blk_mark_disk_dead(), running the hw queues is a no-op and can leave queued requests unprocessed. Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Ross Lagerwall Acked-by: Roger Pau Monné Signed-off-by: Juergen Gross Message-ID: <20261006163411.2583538-1-ross.lagerwall@citrix.com> --- drivers/block/xen-blkfront.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/block/xen-blkfront.c b/drivers/block/xen-blkfront.c index 86f5dd3aced133..ad0cb42c101972 100644 --- a/drivers/block/xen-blkfront.c +++ b/drivers/block/xen-blkfront.c @@ -2142,10 +2142,8 @@ static void blkfront_closing(struct blkfront_info *info) return; /* No more blkif_request(). */ - if (info->rq && info->gd) { - blk_mq_stop_hw_queues(info->rq); + if (info->gd) blk_mark_disk_dead(info->gd); - } for_each_rinfo(info, rinfo, i) { /* No more gnttab callback work. */ From c4c24fd63a8d56cfefd78678758f9be72e17c90a Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 7 Oct 2026 17:22:21 +0200 Subject: [PATCH 1545/2286] scripts/Kconfig.toolchain: Drop compiler symbols from dependencies Commit 454e00cf8ee5 ("kbuild: avoid re-running compiler and linker probes") moved many cc-option calls from the main Makefile into Kconfig. In the process, code such as ifdef CONFIG_CC_IS_GCC KBUILD_CFLAGS += $(call cc-option,-foo) endif was converted into Kconfig as config CC_OPT_FOO def_string "$(cc-option-str,-foo)" if CC_IS_GCC The 'if CC_IS_GCC' is generally unnecessary because it does not prevent the cc-option-str call from running (which is the primary reason the ifdef guards exists in the Makefile), as cc-option-str and the like are run before Kconfig even parses the dependency. It only impacts the symbol's visibility, which does not really matter in the general case, so remove these dependencies. Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Link: https://patch.msgid.link/20261007-scripts-kconfig-toolchain-improvements-v1-1-1e524178da11@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.toolchain | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index d708a175fe4866..c9a1692e614986 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -161,10 +161,10 @@ config CC_OPT_STRICT_FLEX_ARRAYS # gcc-10 renamed --param=allow-store-data-races=0 to -fno-allow-store-data-races config CC_OPT_ALLOW_STORE_DATA_RACES_PARAM - def_string "$(cc-option-str,--param=allow-store-data-races=0)" if CC_IS_GCC + def_string "$(cc-option-str,--param=allow-store-data-races=0)" config CC_OPT_NO_ALLOW_STORE_DATA_RACES - def_string "$(cc-option-str,-fno-allow-store-data-races)" if CC_IS_GCC + def_string "$(cc-option-str,-fno-allow-store-data-races)" config CC_HAS_MNOP_MCOUNT def_bool $(cc-option,-mnop-mcount) @@ -183,13 +183,13 @@ config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF - def_string "-Wno-format-overflow-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-overflow-non-kprintf) + def_string "-Wno-format-overflow-non-kprintf" if $(cc-option,-Wformat-overflow-non-kprintf) config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF - def_string "-Wno-format-truncation-non-kprintf" if CC_IS_CLANG && $(cc-option,-Wformat-truncation-non-kprintf) + def_string "-Wno-format-truncation-non-kprintf" if $(cc-option,-Wformat-truncation-non-kprintf) config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE - def_string "-Wno-default-const-init-unsafe" if CC_IS_CLANG && $(cc-option,-Wdefault-const-init-unsafe) + def_string "-Wno-default-const-init-unsafe" if $(cc-option,-Wdefault-const-init-unsafe) config CC_OPT_WNO_DANGLING_POINTER def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) From 7202c08793a243fb4c7f9a1b170e052b2f038b33 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Wed, 7 Oct 2026 17:22:22 +0200 Subject: [PATCH 1546/2286] kconfig: Improve ergonomics around disabling warnings with cc-option This is essentially a port of commit 550ccb178de2 ("Make 'cc-option' work correctly for the -Wno-xyzzy pattern") to scripts/Kconfig.include, which simplifies the definition of many of the newly added CC_OPT_WNO_ symbols in scripts/Kconfig.toolchain and matches cc-option's behavior in Kbuild. Reviewed-by: Nicolas Schier Reviewed-by: Kees Cook Link: https://patch.msgid.link/20261007-scripts-kconfig-toolchain-improvements-v1-2-1e524178da11@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Kconfig.include | 2 +- scripts/Kconfig.toolchain | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/Kconfig.include b/scripts/Kconfig.include index 622d21e6953f5a..a7d9201acfcce7 100644 --- a/scripts/Kconfig.include +++ b/scripts/Kconfig.include @@ -26,7 +26,7 @@ failure = $(if-success,$(1),n,y) # $(cc-probe,) # The command $(cc-option) and $(cc-option-str) test with. Not meant # to be called directly; use one of those two instead. -cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(1) -c -x c /dev/null -o .tmp_$$/tmp.o +cc-probe = trap "rm -rf .tmp_$$" EXIT; mkdir .tmp_$$; $(CC) -Werror $(CLANG_FLAGS) $(shell,echo $(1) | sed "s;^-Wno-;-W;") -c -x c /dev/null -o .tmp_$$/tmp.o # $(cc-option,) # Return y if the compiler supports , n otherwise diff --git a/scripts/Kconfig.toolchain b/scripts/Kconfig.toolchain index c9a1692e614986..254393d4f98ad0 100644 --- a/scripts/Kconfig.toolchain +++ b/scripts/Kconfig.toolchain @@ -180,19 +180,19 @@ config LD_OPT_NO_WARN_RWX_SEGMENTS def_string "$(ld-option-str,--no-warn-rwx-segments)" config CC_OPT_WNO_ADDRESS_OF_PACKED_MEMBER - def_string "-Wno-address-of-packed-member" if $(cc-option,-Waddress-of-packed-member) + def_string "$(cc-option-str,-Wno-address-of-packed-member)" config CC_OPT_WNO_FORMAT_OVERFLOW_NON_KPRINTF - def_string "-Wno-format-overflow-non-kprintf" if $(cc-option,-Wformat-overflow-non-kprintf) + def_string "$(cc-option-str,-Wno-format-overflow-non-kprintf)" config CC_OPT_WNO_FORMAT_TRUNCATION_NON_KPRINTF - def_string "-Wno-format-truncation-non-kprintf" if $(cc-option,-Wformat-truncation-non-kprintf) + def_string "$(cc-option-str,-Wno-format-truncation-non-kprintf)" config CC_OPT_WNO_DEFAULT_CONST_INIT_UNSAFE - def_string "-Wno-default-const-init-unsafe" if $(cc-option,-Wdefault-const-init-unsafe) + def_string "$(cc-option-str,-Wno-default-const-init-unsafe)" config CC_OPT_WNO_DANGLING_POINTER - def_string "-Wno-dangling-pointer" if $(cc-option,-Wdangling-pointer) + def_string "$(cc-option-str,-Wno-dangling-pointer)" config CC_OPT_WVLA_LARGER_THAN def_string "$(cc-option-str,-Wvla-larger-than=1)" @@ -201,7 +201,7 @@ config CC_HAS_WSTRINGOP_OVERFLOW def_bool $(cc-option,-Wstringop-overflow) config CC_OPT_WNO_UNTERMINATED_STRING_INITIALIZATION - def_string "-Wno-unterminated-string-initialization" if $(cc-option,-Wunterminated-string-initialization) + def_string "$(cc-option-str,-Wno-unterminated-string-initialization)" config CC_OPT_WERROR_DESIGNATED_INIT def_string "$(cc-option-str,-Werror=designated-init)" @@ -210,13 +210,13 @@ config CC_OPT_WENUM_CONVERSION def_string "$(cc-option-str,-Wenum-conversion)" config CC_OPT_WNO_PACKED_NOT_ALIGNED - def_string "-Wno-packed-not-aligned" if $(cc-option,-Wpacked-not-aligned) + def_string "$(cc-option-str,-Wno-packed-not-aligned)" config CC_OPT_WNO_FORMAT_OVERFLOW - def_string "-Wno-format-overflow" if $(cc-option,-Wformat-overflow) + def_string "$(cc-option-str,-Wno-format-overflow)" config CC_OPT_WNO_STRINGOP_TRUNCATION - def_string "-Wno-stringop-truncation" if $(cc-option,-Wstringop-truncation) + def_string "$(cc-option-str,-Wno-stringop-truncation)" config CC_HAS_COUNTED_BY bool From 2160297cabd4bd121ef2e7327c6a6f376c0fbf3c Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Fri, 9 Oct 2026 10:46:17 +0100 Subject: [PATCH 1547/2286] kconfig: cleanup stale comment about 'keys' array 'keys' was removed from 'dialog_textbox' in: commit e14f1242a8be ("kconfig: menuconfig: simplify global jump key assignment") but the comment referencing it was never removed. Let's cleanup this stale comment. Signed-off-by: Julian Braha Reviewed-by: Nicolas Schier Link: https://patch.msgid.link/20261009094617.17322-1-julianbraha@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/lxdialog/textbox.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c index 0abaf635978f98..3fe7682fc8a8a8 100644 --- a/scripts/kconfig/lxdialog/textbox.c +++ b/scripts/kconfig/lxdialog/textbox.c @@ -145,8 +145,6 @@ static void refresh_text_box(WINDOW *dialog, WINDOW *box, int boxh, int boxw, /* * Display text from a file in a dialog box. - * - * keys is a null-terminated array */ int dialog_textbox(const char *title, const char *tbuf, int initial_height, int initial_width, int *_vscroll, int *_hscroll, From 52f26261e27dbdb7c6dea75d8f1a92ea805b2c58 Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Thu, 8 Oct 2026 11:45:21 -0500 Subject: [PATCH 1548/2286] kconfig: fix infinite loop in conf_choice() when no choice value is visible If a choice is visible but none of its choice values is, conf_choice() counts cnt == 0 visible values and finds no default. When the choice has to be asked, e.g. because a new symbol was added to the same menu (oldconfig, syncconfig) or because stdin is at EOF (oldaskconfig), the loop that maps the answer to a menu entry never finds one and the while (1) loop repeats forever, printing "choice[1-0?]: 0" each time. This happens in the tree for s390 when the compiler supports none of the -march=z* options (e.g. a non-s390 host compiler), so that all values of the "Processor type" choice are invisible: $ make ARCH=s390 defconfig $ sed -i '/^CONFIG_NR_CPUS=/d' .config $ make ARCH=s390 oldconfig # hangs Before the removal of tristate choice support, conf_choice() returned early in this case. Do the same when there is no visible choice value. Found by fuzzing Kconfig input with ASan/UBSan. Fixes: fde192511bdb ("kconfig: remove tristate choice support") Assisted-by: Claude:claude-opus-5-5 libFuzzer Signed-off-by: Leizhen Zhang Reviewed-by: Julian Braha Tested-by: Julian Braha Link: https://patch.msgid.link/20261008164521.3684735-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/conf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c index ca6d9d735922fb..31c6fd25e6ecd8 100644 --- a/scripts/kconfig/conf.c +++ b/scripts/kconfig/conf.c @@ -483,6 +483,9 @@ static void conf_choice(struct menu *menu) } printf("\n"); } + /* Nothing to ask if none of the choice values is visible. */ + if (!cnt) + return; printf("%*schoice", indent - 1, ""); if (cnt == 1) { printf("[1]: 1\n"); From a8f72a75b38a153d32a59b2ebca1d7079db1dd1a Mon Sep 17 00:00:00 2001 From: Leizhen Zhang Date: Thu, 8 Oct 2026 11:45:40 -0500 Subject: [PATCH 1549/2286] kconfig: use the value argument in sym_get_near_range_bound() sym_get_near_range_bound() is meant to check whether the given value is outside of the active range of an int/hex symbol, and is called with the symbol's current value from sym_calc_value() and with the default value from sym_get_string_default(). Since commit 9b8719973ee2 ("kconfig: use unsigned integers for hex range checks") it ignores its value argument and uses the current value of the symbol (via sym_get_range_val()) instead. As a result, an out-of-range default is no longer clamped in sym_get_string_default(), and savedefconfig writes out values that are equal to the effective default again, for example CONFIG_A=4 for: config A int "a" range 1 4 default 8 The existing savedefconfig_range test did not catch this because it only covers symbols without a prompt. Parse the value argument again, and add a test case for a symbol with a prompt. Found while fuzzing Kconfig input with ASan/UBSan. Fixes: 9b8719973ee2 ("kconfig: use unsigned integers for hex range checks") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Leizhen Zhang Tested-by: Julian Braha Reviewed-by: Julian Braha Link: https://patch.msgid.link/20261008164540.3686383-1-lzsx618@gmail.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/symbol.c | 5 ++++- scripts/kconfig/tests/savedefconfig_range/Kconfig | 7 +++++++ scripts/kconfig/tests/savedefconfig_range/config | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c index dcb4b45e674b80..4ced4dd6485ba4 100644 --- a/scripts/kconfig/symbol.c +++ b/scripts/kconfig/symbol.c @@ -195,7 +195,10 @@ static struct symbol *sym_get_near_range_bound(struct symbol *sym, prop = sym_get_range_prop(sym); if (!prop) return NULL; - val = sym_get_range_val(sym, base); + if (base == 10) + val.s = strtoll(value, NULL, base); + else + val.u = strtoull(value, NULL, base); range_sym = prop->expr->left.sym; val2 = sym_get_range_val(range_sym, base); diff --git a/scripts/kconfig/tests/savedefconfig_range/Kconfig b/scripts/kconfig/tests/savedefconfig_range/Kconfig index fd59d9082ed5ba..d879870a526750 100644 --- a/scripts/kconfig/tests/savedefconfig_range/Kconfig +++ b/scripts/kconfig/tests/savedefconfig_range/Kconfig @@ -58,3 +58,10 @@ config INT_CONDITIONAL_DEFAULT int range 1 2 default 3 if USE_DEFAULT + +# Default outside of the range of a symbol with a prompt + +config INT_PROMPT_DEFAULT_ABOVE_RANGE + int "int with prompt" + range 1 4 + default 8 diff --git a/scripts/kconfig/tests/savedefconfig_range/config b/scripts/kconfig/tests/savedefconfig_range/config index d939cfe5fd3494..079a3107a02b62 100644 --- a/scripts/kconfig/tests/savedefconfig_range/config +++ b/scripts/kconfig/tests/savedefconfig_range/config @@ -5,3 +5,4 @@ CONFIG_HEX_DEFAULT_ABOVE_RANGE=0x20 CONFIG_INT_IMPLICIT_DEFAULT_ZERO=1 CONFIG_INT_CONDITIONAL_RANGE=2 CONFIG_INT_CONDITIONAL_DEFAULT=2 +CONFIG_INT_PROMPT_DEFAULT_ABOVE_RANGE=4 From d26ab84593e63bc5395ed9b518502830fda3367c Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Sat, 10 Oct 2026 01:58:32 +0800 Subject: [PATCH 1550/2286] clang-tools: Import os for broken pipe handling The BrokenPipeError handler in run-clang-tools.py calls os.open() and os.dup2() without importing os. If this handler is reached, it raises NameError instead of redirecting stdout and exiting with status 1. Import os so the handler can work as intended. Fixes: 87c7ee67deb7 ("scripts: handle BrokenPipeError for python scripts") Assisted-by: LLM Signed-off-by: Ting-Han Hou Link: https://patch.msgid.link/20261009175836.2277-1-ue081723@gmail.com Signed-off-by: Nathan Chancellor --- scripts/clang-tools/run-clang-tools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/clang-tools/run-clang-tools.py b/scripts/clang-tools/run-clang-tools.py index e78be82aa6931b..79d58b34e7acd9 100755 --- a/scripts/clang-tools/run-clang-tools.py +++ b/scripts/clang-tools/run-clang-tools.py @@ -12,6 +12,7 @@ import argparse import json import multiprocessing +import os import subprocess import sys From 3bf2237f59af842e546fa4b2c146198db95d1519 Mon Sep 17 00:00:00 2001 From: Ting-Han Hou Date: Tue, 6 Oct 2026 08:19:54 -0700 Subject: [PATCH 1551/2286] scripts/make_fit: Ignore schema checks when decomposing DTBs With CHECK_DTBS enabled, Kbuild appends a schema validation command to the fdtoverlay command saved in a composite DTB's .cmd file. When DTB decomposition is requested, process_dtb() treats every word after -i as an input file, including the semicolon and the validator arguments. FIT generation then fails with FileNotFoundError while opening or checking the size of those words as files. Limit the input list to the fdtoverlay command before extracting its arguments. This preserves the base DTB and overlay order and ignores the subsequent schema validation command. Fixes: 17c31aded9a1 ("scripts/make_fit: Support decomposing DTBs") Assisted-by: LLM Signed-off-by: Ting-Han Hou Reviewed-by: Simon Glass Link: https://patch.msgid.link/CA+NtrhQBK8uW32qO3dXxVxO1GSN-0EJfyJ16tnyx0o52T17WtQ@mail.gmail.com Signed-off-by: Nathan Chancellor --- scripts/make_fit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/make_fit.py b/scripts/make_fit.py index 15ba26974fd729..9bd35aa721191f 100755 --- a/scripts/make_fit.py +++ b/scripts/make_fit.py @@ -292,7 +292,8 @@ def process_dtb(fname, args): if 'scripts/dtc/fdtoverlay' in cmd: # This depends on the structure of the composite DTB command - files = cmd.split() + # Ignore any subsequent commands, such as schema validation + files = cmd.split(';', 1)[0].split() files = files[files.index('-i') + 1:] else: files = [fname] From 2c10b3fa5bdf61e344bc522e6c2e00d870ce7ab8 Mon Sep 17 00:00:00 2001 From: Tony Luck Date: Fri, 9 Oct 2026 12:39:23 -0700 Subject: [PATCH 1552/2286] fs/resctrl: Ensure default group reports tasks on monitor-only systems resctrl can be mounted on a system that supports monitoring but not allocation. On such a system reading the "tasks" file in the default group reports no tasks, even when there are tasks assigned to the default group. This problem occurs because the check for whether a task belongs to a CTRL_MON group is gated by a check for whether the system supports allocation. The resctrl_arch_alloc_capable() check in is_closid_match() breaks task reporting for the default group on monitor-only systems so remove this check. On systems that support allocation the check is redundant. Fixes: e6b2fac36fcc ("x86/resctrl: Use is_closid_match() in more places") Closes: https://sashiko.dev/#/patchset/20260831174421.13921-1-tony.luck%40intel.com?part=9 Reported-by: Sashiko Signed-off-by: Tony Luck Signed-off-by: Borislav Petkov (AMD) Reviewed-by: Reinette Chatre Link: https://patch.msgid.link/20261009193923.15498-1-tony.luck@intel.com --- fs/resctrl/rdtgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c index 68be9b903ac666..57ab090072c218 100644 --- a/fs/resctrl/rdtgroup.c +++ b/fs/resctrl/rdtgroup.c @@ -685,7 +685,7 @@ static int __rdtgroup_move_task(struct task_struct *tsk, static bool is_closid_match(struct task_struct *t, struct rdtgroup *r) { - return (resctrl_arch_alloc_capable() && (r->type == RDTCTRL_GROUP) && + return (r->type == RDTCTRL_GROUP && resctrl_arch_match_closid(t, r->closid)); } From 6b722cefbf83558af07586ca94cdb4b370d5a82d Mon Sep 17 00:00:00 2001 From: Binglei Wang Date: Thu, 1 Oct 2026 14:01:31 +0800 Subject: [PATCH 1553/2286] erofs: fix xattrs handling when metadata compression is used erofs_bread() reuses a cached folio on page index match without checking folio->mapping. xattr.c calls erofs_init_metabuf() twice on the same buffer without erofs_put_metabuf(); the two in_metabox sources differ, so buf->mapping can switch while buf->page still belongs to the old address_space. The next erofs_bread() then reads the wrong mapping, dropping shared xattrs with METABOX. We require folio->mapping == buf->mapping in the reuse check; otherwise drop the cached folio and re-read via the slow path. Fixes: 414091322c63 ("erofs: implement metadata compression") Cc: Bo Liu (OpenAnolis) Signed-off-by: Binglei Wang Reviewed-by: Gao Xiang Reviewed-by: Chao Yu Signed-off-by: Gao Xiang --- fs/erofs/data.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/erofs/data.c b/fs/erofs/data.c index be63b89f086229..a726be5e27ca67 100644 --- a/fs/erofs/data.c +++ b/fs/erofs/data.c @@ -33,7 +33,10 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap) if (buf->page) { folio = page_folio(buf->page); - if (folio_file_page(folio, index) != buf->page) + if (folio->mapping != buf->mapping) + /* the cached folio belongs to another address_space */ + folio = NULL; + else if (folio_file_page(folio, index) != buf->page) erofs_unmap_metabuf(buf); } if (!folio || !folio_contains(folio, index)) { From 0c4e5b4161ac55911c77154b67e043a277985fa0 Mon Sep 17 00:00:00 2001 From: Chao Yu Date: Sat, 10 Oct 2026 15:06:23 +0800 Subject: [PATCH 1554/2286] MAINTAINERS: erofs: add dev branch to git tree entry The EROFS entry lists the git tree without a branch, so tools that derive a baseline from MAINTAINERS, e.g. sashiko, fall back to the remote HEAD, which points to master. master only moves at -rc1, so series developed against the dev branch fail to apply there. Name the dev branch explicitly, where erofs patches are queued. Signed-off-by: Chao Yu Acked-by: Gao Xiang Signed-off-by: Gao Xiang --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 65e8a4b5c90b1b..50d3875a8c2d9b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9690,7 +9690,7 @@ R: Chunhai Guo L: linux-erofs@lists.ozlabs.org S: Maintained W: https://erofs.docs.kernel.org -T: git git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git dev F: Documentation/ABI/testing/sysfs-fs-erofs F: Documentation/filesystems/erofs.rst F: fs/erofs/ From 46d4ccf3b621368af5e98187e9e21dccd8beb5cc Mon Sep 17 00:00:00 2001 From: Aaron Tomlin Date: Fri, 25 Sep 2026 14:12:30 -0400 Subject: [PATCH 1555/2286] localmodconfig: Explicitly report boolean configs during integrity check When running make localmodconfig, streamline_config.pl reads loaded modules and maps each module object to its corresponding Kconfig symbol(s) using repository Makefiles. In several instances, a loaded module corresponds to an object controlled by a boolean symbol (e.g. config FOO declared as bool), or is a composite module that depends on a boolean configuration. Because boolean options only accept "=y" or disabled, they cannot be configured as loadable modules ("=m"). Previously, if such a symbol was not already built-in ("=y") in the baseline configuration, the final integrity check emitted an ambiguous diagnostic. For example foo did not have configs CONFIG_FOO Further investigation is required by developers to determine whether the module was out-of-tree, its dependencies were unmet, or it failed due to a type constraint. Address this by: 1. Tracking symbol types in a %types hash during Kconfig parsing. 2. Enhancing the final integrity check when running localmodconfig (!$localyesconfig) to explicitly annotate boolean symbols. For example: module cec did not have configs CONFIG_CEC_CORE (tristate) CONFIG_RAS_CEC (boolean, not supported with =m) Signed-off-by: Aaron Tomlin Acked-by: Steven Rostedt Link: https://patch.msgid.link/20260925181230.130525-1-atomlin@atomlin.com Signed-off-by: Nathan Chancellor --- scripts/kconfig/streamline_config.pl | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/scripts/kconfig/streamline_config.pl b/scripts/kconfig/streamline_config.pl index 8677d1ca06a726..6a52a98eb40e41 100755 --- a/scripts/kconfig/streamline_config.pl +++ b/scripts/kconfig/streamline_config.pl @@ -145,6 +145,7 @@ sub read_config { my %objects; my %config2kfile; my %defaults; +my %types; my $var; my $iflevel = 0; my @ifdeps; @@ -224,6 +225,7 @@ sub read_kconfig { } elsif ($state ne "NONE" && /^\s*def(_(bool|tristate)|ault)\s+(\S.*)$/) { my $dep = $3; $defaults{$config} = 1; + $types{$config} = $2 if (defined($2)); if ($dep !~ /^\s*(y|m|n)\s*$/) { $dep =~ s/.*\sif\s+//; $depends{$config} .= " " . $dep; @@ -240,7 +242,13 @@ sub read_kconfig { } # configs without prompts must be selected - } elsif ($state ne "NONE" && /^\s*(tristate\s+\S|prompt\b)/) { + } elsif ($state ne "NONE" && /^\s*(bool|boolean)(\s+\S.*)?$/) { + $types{$config} = "bool"; + $prompts{$config} = 1 if (defined($2)); + } elsif ($state ne "NONE" && /^\s*tristate(\s+\S.*)?$/) { + $types{$config} = "tristate"; + $prompts{$config} = 1 if (defined($1)); + } elsif ($state ne "NONE" && /^\s*prompt\b/) { # note if the config has a prompt $prompts{$config} = 1; @@ -707,7 +715,15 @@ sub in_preserved_kconfigs { } print STDERR "module $module did not have configs"; foreach my $conf (@arr) { - print STDERR " " , $conf; + my $c = $conf; + $c =~ s/^CONFIG_//; + if (!$localyesconfig && defined($types{$c}) && $types{$c} eq "bool") { + print STDERR " $conf (boolean, not supported with =m)"; + } elsif (defined($types{$c})) { + print STDERR " $conf ($types{$c})"; + } else { + print STDERR " " , $conf; + } } print STDERR "\n"; } From 0d4d70da348b066b02f5aa4b893b13c4e32dc838 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 10 Oct 2026 01:19:10 -0700 Subject: [PATCH 1556/2286] kbuild: Mark the gzip recipes as recursive Since commit 48ebe1cbbeb51 ("kbuild: compress the kernel with pigz if available"), cmd_gzip runs pigz through scripts/jobserver-exec, which takes the job slots make has free. GNU Make passes the jobserver pipe only to recipes it treats as recursive, but leaves --jobserver-auth in MAKEFLAGS for all of them. The make 4.3 in CentOS Stream 9 also hides the pipe in sub-makes (a backport of the fix for GNU Make bug 58232), so jobserver-exec cannot open it, runs pigz on one thread, and prints: WARNING: Unable to reopen jobserver read-side pipe: FileNotFoundError(2, 'No such file or directory') Mark every recipe that can run cmd_gzip as recursive with "+", as commit ecab4115c44cc ("kbuild: mark `rustc` (and others) invocations as recursive") did for rustc. Besides the $(call if_changed,gzip) rules, this covers those that choose gzip through a variable: the built-in initramfs in usr/Makefile (which rcutorture's kvm.sh builds), the EFI zboot image, and the arm, csky, and mips compressed images. As with rustc, "make -n" now runs these recipes, including those that choose through a variable when they select another compressor. Also update the gzip entry in Documentation/kbuild/makefiles.rst, as suggested in review. Tested with GNU Make 4.3 plus CentOS Stream 9's make-4.3-cloexec.patch, GCC 16.2.0, and -j64, on ARCH=x86_64, arm64, and arm defconfig, and ARCH=x86_64 defconfig with a built-in initramfs: none of them prints the warning, and pigz gets the free job slots (-p 54, 64, and 50 for the kernel image). Without this change, the x86_64 builds print it for the kernel image and for the initramfs. Reported-by: Paul E. McKenney Closes: https://lore.kernel.org/all/0ddb5e5e-f801-444c-aa98-0cfe814a3026@paulmck-laptop/ Fixes: 48ebe1cbbeb51 ("kbuild: compress the kernel with pigz if available") Assisted-by: LLM Reviewed-by: Nicolas Schier Acked-by: Lorenzo Stoakes (ARM) Signed-off-by: Kees Cook Tested-by: Paul E. McKenney Link: https://patch.msgid.link/20261010081914.540611-1-kees@kernel.org Signed-off-by: Nathan Chancellor --- Documentation/kbuild/makefiles.rst | 7 +++++-- arch/alpha/boot/Makefile | 4 ++-- arch/arc/boot/Makefile | 2 +- arch/arm/boot/compressed/Makefile | 2 +- arch/arm64/boot/Makefile | 2 +- arch/csky/boot/Makefile | 2 +- arch/microblaze/boot/Makefile | 2 +- arch/mips/boot/Makefile | 2 +- arch/mips/boot/compressed/Makefile | 2 +- arch/nios2/boot/Makefile | 2 +- arch/parisc/boot/compressed/Makefile | 2 +- arch/riscv/boot/Makefile | 2 +- arch/s390/boot/Makefile | 2 +- arch/sh/boot/Makefile | 2 +- arch/sh/boot/compressed/Makefile | 2 +- arch/sparc/boot/Makefile | 4 ++-- arch/x86/boot/compressed/Makefile | 2 +- arch/xtensa/boot/Makefile | 2 +- drivers/firmware/efi/libstub/Makefile.zboot | 2 +- usr/Makefile | 2 +- 20 files changed, 26 insertions(+), 23 deletions(-) diff --git a/Documentation/kbuild/makefiles.rst b/Documentation/kbuild/makefiles.rst index 7521cae7d56f11..535d30eef59a32 100644 --- a/Documentation/kbuild/makefiles.rst +++ b/Documentation/kbuild/makefiles.rst @@ -1452,13 +1452,16 @@ objcopy OBJCOPYFLAGS_$@ may be used to set additional options. gzip - Compress target. Use maximum compression to compress target. + Compress target. Use maximum compression to compress target. Prefix the + recipe line with '+' so that GNU Make passes it the jobserver, which + scripts/jobserver-exec needs to run pigz (if available) on the free job + slots. Example:: #arch/x86/boot/compressed/Makefile $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) dtc Create flattened device tree blob object suitable for linking diff --git a/arch/alpha/boot/Makefile b/arch/alpha/boot/Makefile index d8dba85e606c9a..b050753e901571 100644 --- a/arch/alpha/boot/Makefile +++ b/arch/alpha/boot/Makefile @@ -40,7 +40,7 @@ endif # Compressed kernel image $(obj)/vmlinux.gz: $(obj)/vmlinux FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) @echo ' Kernel $@ is ready' $(obj)/main.o: $(obj)/ksize.h @@ -85,7 +85,7 @@ $(obj)/vmlinux.nh: vmlinux $(OBJSTRIP) FORCE $(call if_changed,objstrip) $(obj)/vmlinux.nh.gz: $(obj)/vmlinux.nh FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/tools/lxboot: $(obj)/bootloader $(OBJSTRIP) FORCE $(call if_changed,objstrip) diff --git a/arch/arc/boot/Makefile b/arch/arc/boot/Makefile index 5a8550124b73ec..8a44c3e7ff3eab 100644 --- a/arch/arc/boot/Makefile +++ b/arch/arc/boot/Makefile @@ -23,7 +23,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE $(call if_changed,lzma) diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile index e3f550d6285786..5f0a1a2e915aab 100644 --- a/arch/arm/boot/compressed/Makefile +++ b/arch/arm/boot/compressed/Makefile @@ -153,7 +153,7 @@ $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \ @$(check_for_bad_syms) $(obj)/piggy_data: $(obj)/../Image FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) $(obj)/piggy.o: $(obj)/piggy_data diff --git a/arch/arm64/boot/Makefile b/arch/arm64/boot/Makefile index b5a08333bc57b5..d42d0766782268 100644 --- a/arch/arm64/boot/Makefile +++ b/arch/arm64/boot/Makefile @@ -26,7 +26,7 @@ $(obj)/Image.bz2: $(obj)/Image FORCE $(call if_changed,bzip2) $(obj)/Image.gz: $(obj)/Image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/Image.lz4: $(obj)/Image FORCE $(call if_changed,lz4) diff --git a/arch/csky/boot/Makefile b/arch/csky/boot/Makefile index c3cfde28f8e603..5d88dc31877b62 100644 --- a/arch/csky/boot/Makefile +++ b/arch/csky/boot/Makefile @@ -12,7 +12,7 @@ compress-$(CONFIG_KERNEL_XZ) = xzkern compress-$(CONFIG_KERNEL_LZ4) = lz4 $(obj)/zImage: $(obj)/Image FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) @echo ' Kernel: $@ is ready' UIMAGE_ARCH = sandbox diff --git a/arch/microblaze/boot/Makefile b/arch/microblaze/boot/Makefile index 2b42c370d57427..e3df3a64808ab2 100644 --- a/arch/microblaze/boot/Makefile +++ b/arch/microblaze/boot/Makefile @@ -14,7 +14,7 @@ $(obj)/linux.bin.ub: $(obj)/linux.bin FORCE $(call if_changed,uimage) $(obj)/linux.bin.gz: $(obj)/linux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) quiet_cmd_strip = STRIP $< $@$2 cmd_strip = $(STRIP) -K microblaze_start -K _end -K __log_buf \ diff --git a/arch/mips/boot/Makefile b/arch/mips/boot/Makefile index 8473c46717029d..996bf41b78dea4 100644 --- a/arch/mips/boot/Makefile +++ b/arch/mips/boot/Makefile @@ -63,7 +63,7 @@ $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE $(call if_changed,lzma) diff --git a/arch/mips/boot/compressed/Makefile b/arch/mips/boot/compressed/Makefile index e0b8ec9a951628..2684540758a8dc 100644 --- a/arch/mips/boot/compressed/Makefile +++ b/arch/mips/boot/compressed/Makefile @@ -69,7 +69,7 @@ tool_$(CONFIG_KERNEL_ZSTD) = zstd22_with_size targets += vmlinux.bin.z $(obj)/vmlinux.bin.z: $(obj)/vmlinux.bin FORCE - $(call if_changed,$(tool_y)) + +$(call if_changed,$(tool_y)) targets += piggy.o dummy.o diff --git a/arch/nios2/boot/Makefile b/arch/nios2/boot/Makefile index 29c11a06b750ad..1381e6e428a88c 100644 --- a/arch/nios2/boot/Makefile +++ b/arch/nios2/boot/Makefile @@ -18,7 +18,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmImage: $(obj)/vmlinux.gz FORCE $(call if_changed,uimage) diff --git a/arch/parisc/boot/compressed/Makefile b/arch/parisc/boot/compressed/Makefile index 14eefb5ed5d1d7..a7fc7d424c00e2 100644 --- a/arch/parisc/boot/compressed/Makefile +++ b/arch/parisc/boot/compressed/Makefile @@ -55,7 +55,7 @@ suffix-$(CONFIG_KERNEL_LZO) := lzo suffix-$(CONFIG_KERNEL_XZ) := xz $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lz4: $(obj)/vmlinux.bin FORCE diff --git a/arch/riscv/boot/Makefile b/arch/riscv/boot/Makefile index 4a2d75b7e42df3..b80a077a8e8fe8 100644 --- a/arch/riscv/boot/Makefile +++ b/arch/riscv/boot/Makefile @@ -23,7 +23,7 @@ $(obj)/Image: vmlinux FORCE $(call if_changed,objcopy) $(obj)/Image.gz: $(obj)/Image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/loader.o: $(src)/loader.S $(obj)/Image diff --git a/arch/s390/boot/Makefile b/arch/s390/boot/Makefile index 10b75e053a6f6b..e8e43ae80dd52a 100644 --- a/arch/s390/boot/Makefile +++ b/arch/s390/boot/Makefile @@ -117,7 +117,7 @@ suffix-$(CONFIG_KERNEL_XZ) := .xz suffix-$(CONFIG_KERNEL_ZSTD) := .zst $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lz4: $(obj)/vmlinux.bin FORCE diff --git a/arch/sh/boot/Makefile b/arch/sh/boot/Makefile index 1f5d2df3c7e060..cf63e319b7866a 100644 --- a/arch/sh/boot/Makefile +++ b/arch/sh/boot/Makefile @@ -66,7 +66,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2) diff --git a/arch/sh/boot/compressed/Makefile b/arch/sh/boot/compressed/Makefile index 58df491778b29a..b85aa5f0d61655 100644 --- a/arch/sh/boot/compressed/Makefile +++ b/arch/sh/boot/compressed/Makefile @@ -39,7 +39,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(obj)/vmlinux.bin FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lzma: $(obj)/vmlinux.bin FORCE diff --git a/arch/sparc/boot/Makefile b/arch/sparc/boot/Makefile index 339c42d35089ac..b53cf596611ea2 100644 --- a/arch/sparc/boot/Makefile +++ b/arch/sparc/boot/Makefile @@ -23,7 +23,7 @@ ifeq ($(CONFIG_SPARC64),y) # Actual linking $(obj)/zImage: $(obj)/image FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) @$(kecho) 'Kernel: $@ is ready' '(#'$(or $(KBUILD_BUILD_VERSION),`cat .version`)')' $(obj)/vmlinux.aout: vmlinux FORCE @@ -45,7 +45,7 @@ $(obj)/image.bin: $(obj)/image FORCE $(call if_changed,objcopy) $(obj)/image.gz: $(obj)/image.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) UIMAGE_LOADADDR = $(CONFIG_UBOOT_LOAD_ADDR) UIMAGE_ENTRYADDR = $(CONFIG_UBOOT_ENTRY_ADDR) diff --git a/arch/x86/boot/compressed/Makefile b/arch/x86/boot/compressed/Makefile index 6ec5e031db1d27..680ad803cba7df 100644 --- a/arch/x86/boot/compressed/Makefile +++ b/arch/x86/boot/compressed/Makefile @@ -133,7 +133,7 @@ vmlinux.bin.all-y := $(obj)/vmlinux.bin vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) $(obj)/vmlinux.bin.bz2: $(vmlinux.bin.all-y) FORCE $(call if_changed,bzip2_with_size) $(obj)/vmlinux.bin.lzma: $(vmlinux.bin.all-y) FORCE diff --git a/arch/xtensa/boot/Makefile b/arch/xtensa/boot/Makefile index d8b0fadf429a97..934f9015b15fa1 100644 --- a/arch/xtensa/boot/Makefile +++ b/arch/xtensa/boot/Makefile @@ -36,7 +36,7 @@ $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) $(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE - $(call if_changed,gzip) + +$(call if_changed,gzip) boot-elf: $(obj)/vmlinux.bin boot-redboot: $(obj)/vmlinux.bin.gz diff --git a/drivers/firmware/efi/libstub/Makefile.zboot b/drivers/firmware/efi/libstub/Makefile.zboot index 832deee36e48e9..52baacf3e9d94e 100644 --- a/drivers/firmware/efi/libstub/Makefile.zboot +++ b/drivers/firmware/efi/libstub/Makefile.zboot @@ -24,7 +24,7 @@ zboot-method-$(CONFIG_KERNEL_ZSTD) := zstd22_with_size zboot-size-len-$(CONFIG_KERNEL_ZSTD) := 4 $(obj)/vmlinuz: $(obj)/vmlinux.bin FORCE - $(call if_changed,$(zboot-method-y)) + +$(call if_changed,$(zboot-method-y)) # avoid eager evaluation to prevent references to non-existent build artifacts OBJCOPYFLAGS_vmlinuz.o = -I binary -O $(EFI_ZBOOT_BFD_TARGET) $(EFI_ZBOOT_OBJCOPY_FLAGS) \ diff --git a/usr/Makefile b/usr/Makefile index e8f42478a0b765..a452369262282c 100644 --- a/usr/Makefile +++ b/usr/Makefile @@ -78,7 +78,7 @@ $(obj)/initramfs_data.cpio: $(src)/gen_initramfs.sh $(obj)/gen_init_cpio $(deps_ endif $(obj)/initramfs_inc_data: $(cpio-data) FORCE - $(call if_changed,$(compress-y)) + +$(call if_changed,$(compress-y)) targets += initramfs_data.cpio initramfs_inc_data From ae5f6044c4cdffe2b3361b15c1d6fc67b339f2a4 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Sat, 10 Oct 2026 01:19:11 -0700 Subject: [PATCH 1557/2286] kbuild: Mark the initcall ordering recipe as recursive generate_initcall_order.pl, which orders initcalls for Clang LTO, forks a child per input file, up to the job count scripts/jobserver-exec passes it in PARALLELISM. It used to run from link-vmlinux.sh, which the top-level Makefile invokes as a recursive recipe, but commit 5d45950dfbb15 ("kbuild: move vmlinux.o link to scripts/Makefile.vmlinux_o") moved it into a recipe without "+". GNU Make passes the jobserver pipe only to recipes it treats as recursive, and the make 4.3 in CentOS Stream 9 also hides it in sub-makes (a backport of the fix for GNU Make bug 58232), so jobserver-exec cannot open it and prints: WARNING: Unable to reopen jobserver read-side pipe: FileNotFoundError(2, 'No such file or directory') PARALLELISM is then left unset, and the script caps its children at the number of online CPUs instead of the free job slots. Mark the recipe as recursive again (this makes "make -n" runs the script again too, as it did from link-vmlinux.sh). Masahiro posted the same change and withdrew it because the script was given only vmlinux.a, and so never ran more than one child [1]. But on arm64, LoongArch, and RISC-V with CONFIG_EFI_STUB, KBUILD_VMLINUX_LIBS also gives it the EFI stub's lib.a. Either way, the warning is silenced. Tested ARCH=x86_64 defconfig with CONFIG_LTO_CLANG_THIN=y, LLVM=1 (Clang 24.0.0git), and GNU Make 4.3 plus CentOS Stream 9's make-4.3-cloexec.patch. Fixes: 5d45950dfbb15 ("kbuild: move vmlinux.o link to scripts/Makefile.vmlinux_o") Link: https://lore.kernel.org/all/CAK7LNARCM=rUm8mA8GRQ7ufeyfneGf4OEvHmESKt=zuxs2KrHw@mail.gmail.com/ [1] Assisted-by: LLM Reviewed-by: Nicolas Schier Signed-off-by: Kees Cook Tested-by: Paul E. McKenney Link: https://patch.msgid.link/20261010081914.540611-2-kees@kernel.org Signed-off-by: Nathan Chancellor --- scripts/Makefile.vmlinux_o | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Makefile.vmlinux_o b/scripts/Makefile.vmlinux_o index 24a3a4fd271c23..0b37bd5dc3a491 100644 --- a/scripts/Makefile.vmlinux_o +++ b/scripts/Makefile.vmlinux_o @@ -19,7 +19,7 @@ quiet_cmd_gen_initcalls_lds = GEN $@ .tmp_initcalls.lds: $(srctree)/scripts/generate_initcall_order.pl \ vmlinux.a $(KBUILD_VMLINUX_LIBS) FORCE - $(call if_changed,gen_initcalls_lds) + +$(call if_changed,gen_initcalls_lds) targets := .tmp_initcalls.lds From d45ef897ecf8d34ca25a3772552a8c75260fe5e1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 8 Oct 2026 14:15:29 +0200 Subject: [PATCH 1558/2286] kbuild: rust: allow `clippy::comparison_chain` globally The lint would suggest comparison chains to be converted to match on `Ordering` instead. The suggestions do not always produce more readable code, and also it can produce false-positive when not all ordering has a branch. The issue is known upstream and thus it has been downgraded from style lints to pedantic lints, which makes it allow-by-default since Rust 1.87.0. The current kernel MSRV is 1.85.0, and thus the lint still triggers; allow it globally to match the updated upstream behaviour. Link: https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/demote.20some.20lints.20to.20.60pedantic.60 Link: https://rust-lang.github.io/rust-clippy/master/#comparison_chain Acked-by: Benno Lossin Acked-by: Miguel Ojeda Link: https://patch.msgid.link/20261008-dev-selfref-v2-1-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 0f1b80100b4798..2839c6c5045b99 100644 --- a/Makefile +++ b/Makefile @@ -476,6 +476,7 @@ KBUILD_USERLDFLAGS := $(USERLDFLAGS) # These flags apply to all Rust code in the tree, including the kernel and # host programs. # +# `-Aclippy::comparison_chain`: the lint has been demoted since 1.87. # `-Aclippy::unwrap_or_default`: the lint is buggy [1] and ignores our # MSRV. It can trigger depending on the optimization level. # [1] https://github.com/rust-lang/rust-clippy/issues/17379 @@ -494,6 +495,7 @@ export rust_common_flags := --edition=2021 \ -Wclippy::cast_lossless \ -Aclippy::collapsible_if \ -Aclippy::collapsible_match \ + -Aclippy::comparison_chain \ -Wclippy::ignored_unit_patterns \ -Aclippy::incompatible_msrv \ -Wclippy::mut_mut \ From 414b5c482e910335d1b2698711fb9da03a89ac93 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 30 Apr 2026 18:49:47 +0100 Subject: [PATCH 1559/2286] rust: pin-init: internal: pin_data: infer self-referential struct As a first step towards adding self-referential data structures in pin-init, add parsing support. Scan all field types for unbounded lifetimes, and if the names that of fields, it is inferred as a self-referential field lifetime. No explicit annotations are supported yet. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-2-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 162 ++++++++++++++++++++++++- rust/pin-init/internal/src/util.rs | 94 +++++++++++++- 2 files changed, 253 insertions(+), 3 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 03e893cf547553..cf6142cd656d98 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT +use std::collections::{BTreeMap, BTreeSet}; + use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{ @@ -7,8 +9,10 @@ use syn::{ parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, + visit::Visit, visit_mut::VisitMut, - Field, Fields, Generics, Index, Item, ItemStruct, Member, PathSegment, Type, TypePath, + Field, Fields, Generics, Ident, Index, Item, ItemStruct, Lifetime, Member, PathSegment, Type, + TypePath, }; use crate::{ @@ -48,10 +52,69 @@ impl ToTokens for Args { } } +/// Description of how a field is borrowed. +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum BorrowedKind { + /// Implicitly inferreed. + #[default] + Shared, +} + +/// Information about a borrowed field. +#[expect(unused)] +struct BorrowedInfo { + kind: BorrowedKind, + /// Field lifetime for this field. + lifetime: Lifetime, +} + +#[derive(Clone, Copy, Default, PartialEq, Eq)] +enum Variance { + /// Implicitly inferred variance. + #[default] + Covariant, +} + +/// Information about field lifetimes captured in a type. +#[expect(unused)] +struct Capture { + variance: Variance, + /// Lifetime to be captured. + lifetime: Lifetime, +} + +impl std::borrow::Borrow for Capture { + fn borrow(&self) -> &Lifetime { + &self.lifetime + } +} + +impl PartialEq for Capture { + fn eq(&self, other: &Self) -> bool { + self.lifetime == other.lifetime + } +} + +impl Eq for Capture {} + +impl PartialOrd for Capture { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Capture { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.lifetime.cmp(&other.lifetime) + } +} + struct FieldInfo { field: Field, member: Member, pinned: bool, + borrowed: Option, + captures: BTreeSet, } struct StructInfo { @@ -59,6 +122,7 @@ struct StructInfo { struct_: ItemStruct, fields: Vec, is_tuple_struct: bool, + self_referential: bool, } pub(crate) fn expand_with_cfg( @@ -147,7 +211,22 @@ fn expand( replacer.visit_fields_mut(&mut struct_.fields); let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); - let fields: Vec = struct_ + + // Collect all bound lifetimes from generics. + let bound_lifetimes: BTreeSet<&Lifetime> = + struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); + // Collect all fields. + let field_idx_map: BTreeMap = struct_ + .fields + .iter() + .enumerate() + .filter_map(|(index, field)| Some((field.ident.clone()?, index))) + .collect(); + + // Keep track on fields being implicitly borrowed by being mentioned. + let mut implicitly_borrowed = BTreeSet::new(); + + let mut fields: Vec = struct_ .fields .into_iter() .enumerate() @@ -166,16 +245,88 @@ fn expand( }), }; + let mut captures = BTreeSet::new(); + let wildcard_variance = Variance::default(); + + // Infer lifetime based on the field referenced. + // Bound lifetimes from struct generics take priority. + // + // For example, + // ``` + // struct Foo<'a> { + // bar: &'a (), + // a: u32, + // } + // ``` + // would not be inferred as self-referential because `'a` is already bound by the + // struct generics. + Lifetime::visitor(|lt| { + if bound_lifetimes.contains(lt) || captures.contains(lt) { + return; + } + + if !field_idx_map.contains_key(<.ident) { + dcx.error( + lt, + format!("`{lt}` is neither a lifetime in generics nor a field name"), + ); + return; + } + + captures.insert(Capture { + variance: wildcard_variance, + lifetime: lt.clone(), + }); + }) + .visit_type(&field.ty); + + for capture in captures.iter() { + implicitly_borrowed.insert(capture.lifetime.ident.clone()); + } + FieldInfo { field, member, pinned, + borrowed: None, + captures, } }) .collect(); + for field_name in implicitly_borrowed.into_iter() { + let field = &mut fields[field_idx_map[&field_name]]; + + // If field is not explicit marked as borrowed, infer a shared borrow. + if field.borrowed.is_none() { + field.borrowed = Some(BorrowedInfo { + kind: BorrowedKind::Shared, + // Obtaining from `field` instead of `field_name` for the correct span. + lifetime: Lifetime::from_ident(&field.member.as_ident()), + }); + } + } + + // Check that field lifetimes do not appear in the bounds. + Lifetime::visitor(|lt| { + if bound_lifetimes.contains(<) { + return; + } + + if field_idx_map.contains_key(<.ident) { + // Forbid the use of field lifetimes within bounds. + dcx.error(lt, "field lifetimes cannot be used in bounds"); + } + + // Otherwise this is completely unbound. Let Rust compiler produce that error instead. + }) + .visit_generics(&struct_.generics); + struct_.fields = Fields::Unit; let info = StructInfo { + self_referential: fields + .iter() + .any(|f| !f.captures.is_empty() || f.borrowed.is_some()), args, struct_, fields, @@ -195,6 +346,13 @@ fn expand( } } + if info.self_referential { + dcx.error( + &info.struct_.ident, + "self-referential support is not fully implemented", + ); + } + let struct_def = generate_struct_def(&info); let unpin_impl = generate_unpin_impl(&info); let drop_impl = generate_drop_impl(&info); diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 3ce498cea754b8..67ebb333710f76 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -1,8 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT +use std::collections::BTreeSet; + use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; -use syn::{Attribute, GenericParam, Generics, Index, Member, Token}; +use syn::{ + visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, Token, +}; use crate::DiagCtxt; @@ -237,3 +241,91 @@ impl ToTokens for CombinedTypeGenerics<'_> { .to_tokens(tokens); } } + +pub(crate) trait LifetimeExt { + /// Get a visitor that call the provided function for all unbound lifetimes. + fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>; + + /// Obtain a lifetime from a identifier. + /// + /// The created lifetime has the same span. + fn from_ident(ident: &Ident) -> Self; +} + +impl LifetimeExt for Lifetime { + fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a> { + LifetimeVisitor { + bound: BTreeSet::new(), + visit: f, + } + } + + fn from_ident(ident: &Ident) -> Self { + Lifetime { + apostrophe: ident.span(), + ident: ident.clone(), + } + } +} + +struct LifetimeVisitor<'a, F> { + bound: BTreeSet<&'a Lifetime>, + visit: F, +} + +impl<'a, F> LifetimeVisitor<'a, F> { + fn with_bound_lifetimes( + &mut self, + bound: Option<&'a BoundLifetimes>, + f: impl FnOnce(&mut Self), + ) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // the lifetimes in the binder are bound and should not be visited. + + let mut to_remove = Vec::new(); + if let Some(bound) = bound { + for lt in &bound.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if !self.bound.contains(&<.lifetime) { + self.bound.insert(<.lifetime); + to_remove.push(<.lifetime); + } + } + } + + f(self); + + for lt in to_remove { + self.bound.remove(lt); + } + } +} + +impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> { + fn visit_lifetime(&mut self, lt: &'a Lifetime) { + if lt.ident == "static" { + return; + } + + if !self.bound.contains(lt) { + (self.visit)(lt); + } + } + + fn visit_trait_bound(&mut self, bound: &'a syn::TraitBound) { + self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| { + this.visit_path(&bound.path) + }); + } + + fn visit_type_bare_fn(&mut self, bare_fn: &'a syn::TypeBareFn) { + self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| { + for input in bare_fn.inputs.iter() { + this.visit_bare_fn_arg(input); + } + this.visit_return_type(&bare_fn.output); + }); + } +} From 9ad9425015e267f6fccc065980f88625d0fa6fcb Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 18:32:29 +0100 Subject: [PATCH 1560/2286] rust: pin-init: internal: pin_data: rewrite fields that borrow others Fields that borrow other fields have lifetimes that are within the struct and these are not part of the struct generics. Therefore, these fields need to have their lifetime erased. A naive implementation would be to replace their lifetimes with `'static`. However, doing so is unsound for multiple reasons: * Users may directly access such field with field access syntax, and get exposed with wrong lifetime; * Auto trait implementations will cause the struct to be implementing auto traits when the type only implements the auto trait for specific lifetime. This is similar to how specialization can be unsound if specialized on lifetime. Create a `Erase` type, which has `for<'a> fn(&'a ()) -> Foo<'a>` as generic parameter. Internally, it uses a helper trait to resolve that to `Foo<'static>`. The first issue is solved by not exposing any public accessor on that type. The second issue is solved by add custom `Send` and `Sync` implementations that requires `Send` to be implemented for all lifetimes, thus closing the lifetime specialization hole. The actual implementation is a bit more convoluted because it supports erasing multiple lifetimes. This is more or less a stable polyfill of the unstable `unsafe_binder` feature, without `unsafe_binders`'s no drop glue requirement. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-3-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 16 ++++++ rust/pin-init/src/__internal.rs | 77 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index cf6142cd656d98..36678843251ccc 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -417,6 +417,22 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { ty, } = &field.field; + let mut ty = ty.to_token_stream(); + + // Replace lifetime for self-referential fields. + if !field.captures.is_empty() { + // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt` + // implementation and thus may be used inside `Erase`. + ty = quote!((#ty,)); + + for borrow in field.captures.iter().rev() { + let lt = &borrow.lifetime; + ty = quote!(for<#lt> fn(&#lt()) -> #ty); + } + + ty = quote!(::pin_init::__internal::Erase<#ty>); + }; + quote! { #(#attrs)* #vis #ident #colon_token #ty } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index cba53b8c3c9495..0a8247473cbcaf 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -385,3 +385,80 @@ unsafe impl PinInit for AlwaysFail { Err(()) } } +/// Polyfill of `FnOnce` trait to be able to reference output via associated type. +pub trait FnOutput { + type Output; +} + +macro_rules! impl_fn_output { + () => {}; + ($ret:ident, $($arg:ident,)*) => { + impl FnOutput<($($arg,)*)> for This + where + This: FnOnce($($arg,)*) -> $ret, + { + type Output = $ret; + } + impl_fn_output!($($arg,)*); + }; +} + +impl_fn_output!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U,); + +/// Lifetime erasure facility. +/// +/// Say we have `exists<'a, 'b> Foo<'a, 'b>` and we want to store it. There's no concrete +/// lifetimes we can use, so we want to erase the lifetime. +/// +/// Such erasure can be encoded as +/// `Erased fn(&'a ()) -> for<'b> fn(&'b()) -> (Foo<'a, 'b>,)`. +/// +/// This can be considered the stable version of Rust's `unsafe_binder` feature, without the +/// no-drop-glue requirement. +#[repr(transparent)] +#[allow(private_bounds)] +pub struct Erase(F::Erased); + +/// Helper trait to resolve the erased lifetime. +trait EraseLt { + type Erased; +} + +impl EraseLt for (T,) { + type Erased = T; +} + +impl EraseLt for T +where + T: for<'a> FnOutput<(&'a (),), Output: EraseLt>, +{ + type Erased = <>::Output as EraseLt>::Erased; +} + +// The default `Send` and `Sync` are not sufficient, because one can use lifetime specialization to +// implement `Send` or `Sync` for a concrete instance of lifetime. Use HRTB to ensure that the type +// will only implement `Send` or `Sync` if it's implemented for *all* erased lifetimes. + +// SAFETY: Trivial, no lifetime to erase. +unsafe impl Send for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Send` for across *all* that +// lifetimes. +unsafe impl Send for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Send, +{ +} + +// SAFETY: Trivial, no lifetime to erase. +unsafe impl Sync for Erase<(T,)> {} + +// SAFETY: If we erased a lifetime, then the type needs to be `Sync` for across *all* that +// lifetimes. +unsafe impl Sync for Erase +where + F: for<'a> FnOutput<(&'a (),), Output: EraseLt>, + for<'a> Erase<>::Output>: Sync, +{ +} From 73511e489bb0cb41d65a99b6f9df4f73a2f4827a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 22 Sep 2026 21:42:34 +0100 Subject: [PATCH 1561/2286] rust: pin-init: internal: pin_data: pin borrowed fields with wrapper For fields that are borrowed, a mutable reference to the struct no longer mean that it has the permission to access these fields. Therefore, the memory that they refer to must be pinned. Wrap these fields inside a `Borrowed` struct which pins it. They may be accessed directly (if they're not themselves referencing other struct fields), so implement a `Deref`. As such fields are always pinned, there is no need to generate a conditional `Unpin` implementations that implements `Unpin` when all fields are. Simply generate a never satisfiable `Unpin` implementation to prevent user from adding their own. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-4-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 19 +++++++++++++++++++ rust/pin-init/src/__internal.rs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 36678843251ccc..a3e492c455da1d 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -433,6 +433,10 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { ty = quote!(::pin_init::__internal::Erase<#ty>); }; + if field.borrowed.is_some() { + ty = quote!(::pin_init::__internal::Borrowed<#ty>); + } + quote! { #(#attrs)* #vis #ident #colon_token #ty } @@ -468,6 +472,20 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream { .map(|x| &x.predicates) .unwrap_or(const { &Punctuated::new() }); + if info.self_referential { + // Self-referential structs must always be pinned. + return quote! { + #[doc(hidden)] + impl #impl_generics ::core::marker::Unpin for #ident #ty_generics + where + // the `for<'__dummy>` HRTB makes this not error without the `trivial_bounds` + // feature . + for<'__dummy> ::core::marker::PhantomPinned: ::core::marker::Unpin, + #predicates + {} + }; + } + let pinned_fields = info.fields.iter().filter(|f| f.pinned).map(|f| { let ident = f.member.as_ident(); let ty = &f.field.ty; @@ -475,6 +493,7 @@ fn generate_unpin_impl(info: &StructInfo) -> TokenStream { #ident: #ty ) }); + quote! { // This struct will be used for the unpin analysis. It is needed, because only structurally // pinned fields are relevant whether the struct should implement `Unpin`. diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 0a8247473cbcaf..74dc23506d9774 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -5,6 +5,9 @@ //! These items must not be used outside of this crate and the pin-init-internal crate located at //! `../internal`. +use core::marker::PhantomPinned; +use core::ops::Deref; + use super::*; /// Zero-sized type used to mark a type as invariant. @@ -462,3 +465,19 @@ where for<'a> Erase<>::Output>: Sync, { } + +/// Wrapper for borrowed fields. +/// +/// This should be switched to `UnsafePinned` when it is stable. +/// NOTE: This type needs to be covariant; Rust's 1.89+'s `UnsafePinned` is invariant. +#[repr(transparent)] +pub struct Borrowed(PhantomPinned, T); + +impl Deref for Borrowed { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + &self.1 + } +} From 29213c00ccaa55e8ba01cfc79a93e000b0fcacc4 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 22 Sep 2026 22:58:11 +0100 Subject: [PATCH 1562/2286] rust: pin-init: internal: pin_data: teach drop check about generics that cannot dangle Lifetimes not needed by drop glue are considered by Rust's drop check to be considered `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of borrowed fields in their drop glue, so compiler's automatic check is insufficient. Code like this: #[pin_data] struct SelfRef<'a> { borrow: PrintOnDrop<&'owner str>, owner: &'a str, } may access `owner` during the drop, however Rust will determine that since `'a` only is used in `owner`, the `'a` lifetime may dangle during drop. This is undesirable for pin-init self references, because `&'a str` could be coerced to `&'owner str` and this could further coerce if there're implied outlives, e.g. `&'earlier_field &'owner ()` would allow `&'owner str` to further coerce to `&'earlier_field`. Thus, if any self-referential field require field lifetime access in `Drop` impl, we would need to ensure that the all generic parameters visible by self-referential fields would strictly outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict outlive relation is needed. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-5-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/src/__internal.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 74dc23506d9774..32bd6d8c39a1dc 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -473,6 +473,35 @@ where #[repr(transparent)] pub struct Borrowed(PhantomPinned, T); +// Lifetimes not needed by drop glue are considered by Rust's drop check to be considered +// `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of +// borrowed fields in their drop glue, so compiler's automatic check is insufficient. +// +// Code like this: +// ``` +// #[pin_data] +// struct SelfRef<'a> { +// borrow: PrintOnDrop<&'owner str>, +// owner: &'a str, +// } +// ``` +// may access `owner` during the drop, however Rust will determine that since `'a` only is used in +// `owner`, the `'a` lifetime may dangle during drop. +// +// This is undesirable for pin-init self references, because `&'a str` could be coerecd to +// `&'owner str` and this could further coerce if there're implied outlives, e.g. +// `&'earlier_field &'owner ()` would allow `&'owner str` to further coerce to `&'earlier_field`. +// +// Thus, if any self-referential field require field lifetime access in `Drop` impl, we would need +// to ensure that the all generic parameters visible by self-referential fields would strictly +// outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a +// dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict +// outlive relation is needed. +impl Drop for Borrowed { + #[inline(always)] + fn drop(&mut self) {} +} + impl Deref for Borrowed { type Target = T; From 71293ee9f3a8b222dc72bfa41275bd05c29eedbd Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 1 May 2026 20:50:50 +0100 Subject: [PATCH 1563/2286] rust: pin-init: internal: pin_data: self-referential drop order checks Check drop order to ensure that usage of lifetime inside self-referential struct is consistent with the order that the fields will dropped in drop glue. First, fields are checked according to their index to ensure that if `a` borrows from `b`, `b` must outlive `a`. This is simple and produces a very good diagnostic when misused. Lifetime bounds can also be indirectly crafted with implied bounds that make fields well-formed. For example, in this struct struct Foo { x: &'b &'a (), a: String, y: PrintOnDrop<&'b str>, b: String, } `&'b &'a ()` will imply that `a` outlive `b`, which is inconsistent with the actual drop order. For this case, create a `__drop_order_check` function with field lifetimes and outlive relationship of them as generic parameter, and ask Rust to prove that the types are well-formed inside the generated function, to ensure that the bad implied bounds cannot happen. The `__drop_order_check` also need to correlate lifetimes or types captured by generics and the field lifetimes. Do this by inserting outlive bounds when a field mentions a specific type or lifetime parameter. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-6-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 207 ++++++++++++++++++++++++- rust/pin-init/internal/src/util.rs | 65 +++++++- 2 files changed, 263 insertions(+), 9 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index a3e492c455da1d..ae0e169a00c639 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -2,8 +2,8 @@ use std::collections::{BTreeMap, BTreeSet}; -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ parse::{End, Nothing, Parse}, parse_quote, parse_quote_spanned, @@ -11,8 +11,8 @@ use syn::{ spanned::Spanned, visit::Visit, visit_mut::VisitMut, - Field, Fields, Generics, Ident, Index, Item, ItemStruct, Lifetime, Member, PathSegment, Type, - TypePath, + Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam, + Member, PathSegment, Type, TypePath, }; use crate::{ @@ -115,14 +115,19 @@ struct FieldInfo { pinned: bool, borrowed: Option, captures: BTreeSet, + generic_lt_captures: BTreeSet, + generic_ty_captures: BTreeSet, } struct StructInfo { args: Args, struct_: ItemStruct, fields: Vec, + field_idx_map: BTreeMap, is_tuple_struct: bool, self_referential: bool, + /// Field lifetime generics. + field_lts: Generics, } pub(crate) fn expand_with_cfg( @@ -215,6 +220,8 @@ fn expand( // Collect all bound lifetimes from generics. let bound_lifetimes: BTreeSet<&Lifetime> = struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); + // Collect all type parameters from generics. + let type_params: BTreeSet<&Ident> = struct_.generics.type_params().map(|x| &x.ident).collect(); // Collect all fields. let field_idx_map: BTreeMap = struct_ .fields @@ -248,6 +255,9 @@ fn expand( let mut captures = BTreeSet::new(); let wildcard_variance = Variance::default(); + let mut generic_lt_captures = BTreeSet::new(); + let mut generic_ty_captures = BTreeSet::new(); + // Infer lifetime based on the field referenced. // Bound lifetimes from struct generics take priority. // @@ -261,7 +271,12 @@ fn expand( // would not be inferred as self-referential because `'a` is already bound by the // struct generics. Lifetime::visitor(|lt| { - if bound_lifetimes.contains(lt) || captures.contains(lt) { + if bound_lifetimes.contains(lt) { + generic_lt_captures.insert(lt.clone()); + return; + } + + if captures.contains(lt) { return; } @@ -284,12 +299,21 @@ fn expand( implicitly_borrowed.insert(capture.lifetime.ident.clone()); } + GenericParam::maybe_type_params_visitor(|ident| { + if type_params.contains(ident) { + generic_ty_captures.insert(ident.clone()); + } + }) + .visit_type(&field.ty); + FieldInfo { field, member, pinned, borrowed: None, captures, + generic_lt_captures, + generic_ty_captures, } }) .collect(); @@ -322,6 +346,58 @@ fn expand( }) .visit_generics(&struct_.generics); + // Create a lifetime parameter for each field. + let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect(); + let mut field_lts = Generics { + lt_token: None, + params: borrowed_fields + .iter() + .map(|borrowed| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: Default::default(), + }) + }) + .collect(), + gt_token: None, + where_clause: None, + }; + + // Insert necessary bounds to make types well-formed. + for field in fields.iter() { + let Some(borrowed) = &field.borrowed else { + continue; + }; + let field_lt = &borrowed.lifetime; + + // For each borrowed field that borrows from other fields, we need to insert outlive bounds. + for capture in &field.captures { + let lt = &capture.lifetime; + field_lts + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + + // For each borrowed field that references a generic, we also need to insert their outlive + // bounds so they can refer to generics. + for lt in field.generic_lt_captures.iter() { + field_lts + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + + for ty in field.generic_ty_captures.iter() { + field_lts + .make_where_clause() + .predicates + .push(parse_quote!(#ty: #field_lt)); + } + } + struct_.fields = Fields::Unit; let info = StructInfo { self_referential: fields @@ -330,7 +406,9 @@ fn expand( args, struct_, fields, + field_idx_map, is_tuple_struct, + field_lts, }; for field in &info.fields { @@ -356,6 +434,7 @@ fn expand( let struct_def = generate_struct_def(&info); let unpin_impl = generate_unpin_impl(&info); let drop_impl = generate_drop_impl(&info); + let drop_order_check = generate_drop_order_check(dcx, &info); let projections = generate_projections(&info); let the_pin_data = generate_the_pin_data(&info); @@ -364,6 +443,7 @@ fn expand( // We put the rest into this const item, because it then will not be accessible to anything // outside. const _: () = { + #drop_order_check #projections #the_pin_data #unpin_impl @@ -568,6 +648,123 @@ fn generate_drop_impl(info: &StructInfo) -> TokenStream { } } +fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStream { + let ItemStruct { + ident: struct_name, + generics, + .. + } = &info.struct_; + + // If the struct is not self-referential then we can just skip. + if !info.self_referential { + return quote!(); + } + + // Make sure fields are dropped earlier than the fields that they borrow. + for (i, field) in info.fields.iter().enumerate() { + let ident = field.member.as_ident(); + for capture in &field.captures { + let borrowed_field = &capture.lifetime.ident; + + if let Some(&borrowed_idx) = info.field_idx_map.get(borrowed_field) { + if i == borrowed_idx { + // We need a strict outlive relationship, in case the lifetime is needed by the + // field's drop glue. + dcx.error( + borrowed_field, + format!("field `{ident}` cannot borrow from itself"), + ); + } else if i > borrowed_idx { + dcx.error( + borrowed_field, + format!("field `{ident}` borrows `{borrowed_field}`, but drops later"), + ); + } + } + } + } + + // The check above is necessary, but not sufficient. + // + // Consider this case: + // ``` + // struct Foo { + // x: &'b &'a (), + // a: String, + // y: PrintOnDrop<&'b str>, + // b: String, + // } + // ``` + // we need to ensure that `b` will strictly outlive `a`. + // + // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is + // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this + // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust + // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str` + // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a` + // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a + // use-after-free! + // + // Therefore, we must ensure the types contained within the struct has their implied bound being + // consistent with the actual lifetime relationship. We create a `__drop_order_check` function, + // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types + // are wellformed, given the bounds that we understand. + + let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]); + + let (_, ty_generics, _) = generics.split_for_impl(); + let (impl_generics_with_field_lt, _, whr_with_field_lt) = + generics_with_field_lt.split_for_impl(); + + // Prove the wellformedness of struct fields with regarding to the bounds of + // `__drop_order_check`. + // + // Consider this case: + // ``` + // struct Foo { + // x: &'b &'a (), + // a: String, + // y: PrintOnDrop<&'b str>, + // b: String, + // } + // ``` + // we need to ensure that `b` will strictly outlive `a`. + // + // Rust needs to ensure that types are well-formed; in the above example, `&'b &'a ()` is + // well-formed only if `a` outlive `b`. To avoid requiring everyone from having to express this + // bound explicitly when declaring a struct, the `'b: 'a` bound is inferred by the Rust + // compiler. However this causes an issue, where now `&'a str` can be coerced to `&'b str` + // because compiler thinks that it shorten the lifetime. We'll be able to put a reference to `a` + // into `y`; but `a` drops first, so when `y` drops, it accesses `a` and causes a + // use-after-free! + // + // Rust needs to *prove* the wellformedness of the type below, taking into account only the + // explicitly defined bounds plus the bounds implied by the lifetime-erased struct (but not + // the full implied bound between the field lifetimes). + let wf_proofs = info.fields.iter().rev().map(|f| { + let ty = &f.field.ty; + let span = ty.span().resolved_at(Span::mixed_site()); + let ident = f.member.as_ident(); + let lt = f.borrowed.as_ref().map(|b| &b.lifetime); + quote_spanned!(span => + let #ident: &#lt mut #ty = loop {}; + ) + }); + + let struct_span = struct_name.span().resolved_at(Span::mixed_site()); + quote_spanned! {struct_span => + #[allow(non_snake_case, unused)] + fn __drop_order_check #impl_generics_with_field_lt ( + // This must be present so the function can *assume* the implied bounds on the erased + // struct. For example, if the struct has `&'a T`, Rust will infer `T: 'a`; we still + // want to assume these bounds as they are not relevant to the field lifetimes. + _: &#struct_name #ty_generics, + ) #whr_with_field_lt { + #(#wf_proofs)* + } + } +} + fn generate_projections(info: &StructInfo) -> TokenStream { let ItemStruct { vis, diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 67ebb333710f76..f19712a46a3083 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -5,7 +5,8 @@ use std::collections::BTreeSet; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use syn::{ - visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, Token, + visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, + Token, TypePath, }; use crate::DiagCtxt; @@ -85,6 +86,7 @@ impl MemberExt for Member { pub(crate) struct CombinedGenerics<'a>(pub(crate) Vec<&'a Generics>); pub(crate) struct CombinedImplGenerics<'a>(&'a CombinedGenerics<'a>); pub(crate) struct CombinedTypeGenerics<'a>(&'a CombinedGenerics<'a>); +pub(crate) struct CombinedWhereClauses<'a>(&'a CombinedGenerics<'a>); impl CombinedGenerics<'_> { pub(crate) fn split_for_impl( @@ -92,10 +94,13 @@ impl CombinedGenerics<'_> { ) -> ( CombinedImplGenerics<'_>, CombinedTypeGenerics<'_>, - // A stub type so `split_for_impl` signature matches that of `syn`'s. - impl Sized, + CombinedWhereClauses<'_>, ) { - (CombinedImplGenerics(self), CombinedTypeGenerics(self), ()) + ( + CombinedImplGenerics(self), + CombinedTypeGenerics(self), + CombinedWhereClauses(self), + ) } } @@ -242,6 +247,31 @@ impl ToTokens for CombinedTypeGenerics<'_> { } } +impl ToTokens for CombinedWhereClauses<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0 + .0 + .iter() + .filter_map(|x| Some(x.where_clause.as_ref()?.where_token)) + .next_back() + .unwrap_or_default() + .to_tokens(tokens); + + let comma: Token![,] = Default::default(); + + for generics in self.0 .0.iter() { + let Some(where_clause) = &generics.where_clause else { + continue; + }; + + where_clause.predicates.to_tokens(tokens); + if !where_clause.predicates.empty_or_trailing() { + comma.to_tokens(tokens); + } + } + } +} + pub(crate) trait LifetimeExt { /// Get a visitor that call the provided function for all unbound lifetimes. fn visitor<'a>(f: impl FnMut(&'a Lifetime)) -> impl Visit<'a>; @@ -329,3 +359,30 @@ impl<'a, F: FnMut(&'a Lifetime)> Visit<'a> for LifetimeVisitor<'a, F> { }); } } + +pub(crate) trait GenericParamExt { + fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a>; +} + +impl GenericParamExt for GenericParam { + fn maybe_type_params_visitor<'a>(f: impl FnMut(&'a Ident)) -> impl Visit<'a> { + struct TypeParamVisitor(F); + + impl<'a, F> Visit<'a> for TypeParamVisitor + where + F: FnMut(&'a Ident), + { + fn visit_type_path(&mut self, ty: &'a TypePath) { + if ty.qself.is_none() + && ty.path.leading_colon.is_none() + && ty.path.segments[0].arguments.is_none() + { + (self.0)(&ty.path.segments[0].ident); + } + syn::visit::visit_type_path(self, ty); + } + } + + TypeParamVisitor(f) + } +} From 5729b5908312576f0f626683aac0b5965df0f434 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:08:21 +0100 Subject: [PATCH 1564/2286] rust: pin-init: internal: pin_data: check covariance of self-referential fields We implicitly infer covariance for fields that self-references. This needs to be checked to ensure that the fields are really covariant, so the rest of expansion code can rely on this fact. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-7-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 75 +++++++++++++++++++++++++- rust/pin-init/internal/src/util.rs | 24 ++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index ae0e169a00c639..84a1fbacac9a16 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -76,7 +76,6 @@ enum Variance { } /// Information about field lifetimes captured in a type. -#[expect(unused)] struct Capture { variance: Variance, /// Lifetime to be captured. @@ -435,6 +434,7 @@ fn expand( let unpin_impl = generate_unpin_impl(&info); let drop_impl = generate_drop_impl(&info); let drop_order_check = generate_drop_order_check(dcx, &info); + let variance_check = generate_variance_check(&info); let projections = generate_projections(&info); let the_pin_data = generate_the_pin_data(&info); @@ -444,6 +444,7 @@ fn expand( // outside. const _: () = { #drop_order_check + #variance_check #projections #the_pin_data #unpin_impl @@ -765,6 +766,78 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre } } +/// Produce variance checks, so we can ensure that the variance of lifetimes captured by field types +/// actually match our expectation. +fn generate_variance_check(info: &StructInfo) -> TokenStream { + if !info.self_referential { + return quote!(); + } + + let mut checks = Vec::new(); + + for f in info.fields.iter() { + let covariant_captures: Vec<_> = f + .captures + .iter() + .filter(|b| b.variance == Variance::Covariant) + .map(|b| &b.lifetime) + .collect(); + if covariant_captures.is_empty() { + continue; + } + + let ident = f.member.as_ident(); + // Use the span of type for better error message. + let span = f.field.ty.span().resolved_at(Span::mixed_site()); + + let other_field_lifetimes = Generics { + lt_token: None, + params: f + .captures + .iter() + .filter(|b| b.variance != Variance::Covariant) + .map(|b| GenericParam::Lifetime(LifetimeParam::new(b.lifetime.clone()))) + .collect(), + gt_token: None, + where_clause: None, + }; + + let long = Lifetime::new("'__long", span); + let long_ty = f + .field + .ty + .replace_lifetimes(&covariant_captures, &vec![&long; covariant_captures.len()]); + + let short = Lifetime::new("'__short", span); + let short_ty = f + .field + .ty + .replace_lifetimes(&covariant_captures, &vec![&short; covariant_captures.len()]); + + let check_name = format_ident!("__{ident}_covariance", span = span); + + // Add `<'__long: '__short, 'short>` as additional generics. + let covariance_check_generics = parse_quote!(<#long: #short, #short>); + let combined_generics = CombinedGenerics(vec![ + &covariance_check_generics, + &other_field_lifetimes, + &info.struct_.generics, + ]); + let (combined_impl_generics, _, whr) = combined_generics.split_for_impl(); + + checks.push(quote_spanned!(span => + // Emit a check to ensure the type is *really* covariant for soundness. + fn #check_name #combined_impl_generics (long: #long_ty) -> #short_ty #whr { + long + } + )); + } + + quote!( + #(#checks)* + ) +} + fn generate_projections(info: &StructInfo) -> TokenStream { let ItemStruct { vis, diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index f19712a46a3083..59054f5934fb9f 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -5,8 +5,8 @@ use std::collections::BTreeSet; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use syn::{ - visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, Member, - Token, TypePath, + parse_quote, visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, + Member, Token, Type, TypePath, }; use crate::DiagCtxt; @@ -386,3 +386,23 @@ impl GenericParamExt for GenericParam { TypeParamVisitor(f) } } + +pub(crate) trait TypeExt { + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type; +} + +impl TypeExt for Type { + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type { + if needle.is_empty() { + return self.clone(); + } + + parse_quote!( + < + for<#(#needle,)*> fn(#(&#needle (),)*) -> #self + as + ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)> + >::Output + ) + } +} From c7ec00c230b5014757a76f3f285c7fa880321163 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 25 Apr 2026 18:27:37 +0100 Subject: [PATCH 1565/2286] rust: pin-init: internal: pin_data: implement initialization of borrowed structs We now have the checks to ensure that lifetime relations are what is expected, we can generate the slot projections in `generate_pin_data` so self-referential struct can be implemented. New slot and guard types are defined (`SelfRefSlot` and `SelfRefDropGuard`) which gives the generated let bindings longer lifetime than the guard themselves. Have `__make_closure` take `data` back as an argument. This gives `#[pin_data]` an opportunity to change the type to add lifetimes. Higher-ranked trait bound on `__make_closure` is used to ensure that the initialization closure cannot make arbitrary assumptions of those lifetimes. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-8-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/init.rs | 42 +++++- rust/pin-init/internal/src/pin_data.rs | 114 +++++++++++++++-- rust/pin-init/src/__internal.rs | 169 ++++++++++++++++++++++++- rust/pin-init/src/lib.rs | 1 + 4 files changed, 305 insertions(+), 21 deletions(-) diff --git a/rust/pin-init/internal/src/init.rs b/rust/pin-init/internal/src/init.rs index 80e4dc068d2e32..ded2b528337618 100644 --- a/rust/pin-init/internal/src/init.rs +++ b/rust/pin-init/internal/src/init.rs @@ -288,8 +288,10 @@ fn expand( }, }; // `mixed_site` ensures that the data is not accessible to the user-controlled code. - let init_fields = init_fields(&fields, pinned); + let init_fields = make_field_init(&fields, pinned, false); + let drop_check = make_field_init(&fields, pinned, true); let field_check = make_field_check(&fields, init_kind, &path); + Ok(quote_spanned! { Span::mixed_site() => { // Get the data about fields from the supplied type. let data = { @@ -303,17 +305,29 @@ fn expand( // Ensure that `data` really is of type `data` and help with type inference: let init = data.__make_closure::<_, #error>( - move |slot| { + move |slot, data_lt| { #zeroable_check #this - #init_fields + // Generate init twice, which is mostly identical except for lifetimes. + if true { + // In this path, we use the field lifetime from HRTB to prevent environment + // lifetime from entering the fields, and to ensure that the dependency of + // fields is consistent with the field drop of the struct. + #init_fields + } else { + // In this path, we use local lifetime, to make sure that if initialization + // fails, the destructor execution will not cause lifetime issues. This is + // separate as implied bounds between field lifetimes can be inconsistent with + // that of the drop. + #drop_check + } #field_check // SAFETY: we are the `init!` macro that is allowed to call this. Ok(unsafe { ::pin_init::__internal::InitOk::new() }) } ); let init = move |slot| -> ::core::result::Result<(), #error> { - init(slot).map(|__InitOk| ()) + init(slot, data.__with_lt()).map(|__InitOk| ()) }; // SAFETY: TODO unsafe { ::pin_init::#init_from_closure::<_, #error>(init) } @@ -360,7 +374,11 @@ fn get_init_kind(rest: Option<(Token![..], Expr)>, dcx: &mut DiagCtxt) -> InitKi } /// Generate the code that initializes the fields of the struct using the initializers in `field`. -fn init_fields(fields: &Punctuated, pinned: bool) -> TokenStream { +fn make_field_init( + fields: &Punctuated, + pinned: bool, + dropck: bool, +) -> TokenStream { let mut forget_guards = vec![]; let mut res = TokenStream::new(); for InitializerField { attrs, kind } in fields { @@ -388,13 +406,18 @@ fn init_fields(fields: &Punctuated, pinned: bool) - let span = Span::mixed_site().located_at(ident.span()); let slot = if pinned { + let data = if !dropck { + quote_spanned!(span => data_lt) + } else { + quote_spanned!(span => data.__with_lt()) + }; quote_spanned! { span => // SAFETY: // - `slot` is valid and properly aligned. // - `make_field_check` checks that `&raw mut (*slot).#member` is properly aligned. // - `make_field_check` prevents `#member` from being used twice, therefore // `(*slot).#member` is exclusively accessed and has not been initialized. - (unsafe { data.#ident(slot) }) + (unsafe { #data.#ident(slot) }) } } else { quote_spanned! { span => @@ -455,6 +478,11 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // A tuple field has no name that could be bound here (the `_0` identifiers are considered // implementation detail and not user-facing). + let let_binding_method = if !dropck { + format_ident!("let_binding", span = span) + } else { + format_ident!("let_binding_in_dropck", span = span) + }; let binding = match member { Member::Named(ident) => quote_spanned! { span => #(#cfgs)* @@ -462,7 +490,7 @@ fn init_fields(fields: &Punctuated, pinned: bool) - // struct field. #[allow(unused_variables, non_snake_case)] // Include `mut` so that `Pin<&mut T>` bindings can be reborrowed via `.as_mut()`. - let mut #ident = #guard.let_binding(); + let mut #ident = #guard.#let_binding_method(); }, Member::Unnamed(_) => quote!(), }; diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 84a1fbacac9a16..a46c4dcd7da545 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -61,7 +61,6 @@ enum BorrowedKind { } /// Information about a borrowed field. -#[expect(unused)] struct BorrowedInfo { kind: BorrowedKind, /// Field lifetime for this field. @@ -969,12 +968,36 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { generics, .. } = &info.struct_; + + // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be + // used with `for`. + let field_lts = CombinedGenerics(vec![&info.field_lts]); + let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]); + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); + let (impl_generics_with_lt, ty_generics_with_field_lt, whr_with_field_lt) = + generics_with_field_lt.split_for_impl(); + + // Wrap each field in a `PhantomInvariant`. For borrowed fields, additionally + // use `&#lt mut #ty` so the `lt` becomes associated with `#ty` which deduces + // implied bounds. + let phantom_fields = info.fields.iter().map(|f| { + let ty = &f.field.ty; + let ident = f.member.as_ident(); + + if let Some(borrowed) = &f.borrowed { + let lt = &borrowed.lifetime; + quote!( + #ident: ::pin_init::__internal::PhantomInvariant<&#lt mut #ty>, + ) + } else { + quote!( + #ident: ::pin_init::__internal::PhantomInvariant<#ty>, + ) + } + }); - // For every field, we create an initializing projection function according to its projection - // type. If a field is structurally pinned, we create a `Slot` with `Pinned` which must be - // initialized via `PinInit`; if it is not structurally pinned, then we create a `Slot` with - // `Unpinned` which allows initialization via `Init`. let field_accessors = info .fields .iter() @@ -987,6 +1010,30 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { } else { quote!(Unpinned) }; + + let (slot_ty, slot_arg) = match &f.borrowed { + None => (quote!(Slot), quote!()), + Some(BorrowedInfo { + kind: BorrowedKind::Shared, + lifetime, + }) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lifetime,), + ), + }; + quote! { /// # Safety /// @@ -1001,19 +1048,54 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #vis unsafe fn #field_name( self, slot: *mut #struct_name #ty_generics, - ) -> ::pin_init::__internal::Slot<::pin_init::__internal::#pin_marker, #ty> { + ) -> ::pin_init::__internal::#slot_ty< + #slot_arg ::pin_init::__internal::#pin_marker, #ty + > { + // CAST: `as _` is needed to convert types wrapped inside `SelfRef`. // SAFETY: // - If `#pin_marker` is `Pinned`, the corresponding field is structurally // pinned. // - Other safety requirements follows the safety requirement. - unsafe { ::pin_init::__internal::Slot::new(&raw mut (*slot).#member) } + // - If `#slot_ty` is `SelfRefSlot`, the lifetime `#lt` represents that of the + // field. + unsafe { ::pin_init::__internal::#slot_ty::new(&raw mut (*slot).#member as _) } } } }) .collect::(); + quote! { - // We declare this struct which will host all of the projection function for our type. It - // will be invariant over all generic parameters which are inherited from the struct. + // We declare this struct which will host all of the projection function for our type. + #[doc(hidden)] + #[allow(non_snake_case)] + #vis struct __PinDataLt #generics_with_field_lt + #whr_with_field_lt + { + #(#phantom_fields)* + __pin_phantom: ::core::marker::PhantomData<#struct_name #ty_generics>, + } + + impl #impl_generics_with_lt ::core::clone::Clone for __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + { + fn clone(&self) -> Self { *self } + } + + impl #impl_generics_with_lt ::core::marker::Copy for __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + {} + + #[allow(dead_code)] // Some functions might never be used and private. + #[expect(clippy::missing_safety_doc)] + impl #impl_generics_with_lt __PinDataLt #ty_generics_with_field_lt + #whr_with_field_lt + { + #field_accessors + } + + // Declare a type that serves as the entry point of interaction with the `pin_init!` macro. + // We use this type instead of defining methods directly on user's type to avoid possibility + // of name conflicts. #[doc(hidden)] #vis struct __ThePinData #generics #whr @@ -1032,7 +1114,6 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #whr {} - #[allow(dead_code)] // Some functions might never be used and private. impl #impl_generics __ThePinData #ty_generics #whr { @@ -1040,13 +1121,20 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { #[inline(always)] #vis fn __make_closure<__F, __E>(self, f: __F) -> __F where - __F: FnOnce(*mut #struct_name #ty_generics) -> - ::core::result::Result<::pin_init::__internal::InitOk, __E>, + __F: for #field_lt_ty_generics ::core::ops::FnOnce( + *mut #struct_name #ty_generics, + __PinDataLt #ty_generics_with_field_lt + ) -> ::core::result::Result<::pin_init::__internal::InitOk, __E>, { f } - #field_accessors + #[inline(always)] + #vis fn __with_lt #field_lts(self) -> __PinDataLt #ty_generics_with_field_lt { + // Generate a zeroed to avoid naming all fields. + // SAFETY: `__PinDataLt` only contains phantom fields. + unsafe { ::core::mem::zeroed() } + } } // SAFETY: We have added the correct projection functions above to `__ThePinData` and diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 32bd6d8c39a1dc..b639c9204a7d6d 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -109,10 +109,15 @@ impl InitData { #[inline(always)] pub fn __make_closure(self, f: F) -> F where - F: FnOnce(*mut T) -> Result, + F: FnOnce(*mut T, Self) -> Result, { f } + + #[inline(always)] + pub fn __with_lt(self) -> Self { + self + } } /// Stack initializer helper type. Use [`stack_pin_init`] instead of this primitive. @@ -322,6 +327,12 @@ impl DropGuard { // SAFETY: Per type invariant. unsafe { &mut *self.ptr } } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &mut T { + self.let_binding() + } } impl DropGuard { @@ -332,6 +343,12 @@ impl DropGuard { // pinned per type invariant. unsafe { Pin::new_unchecked(&mut *self.ptr) } } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> { + self.let_binding() + } } impl Drop for DropGuard { @@ -342,6 +359,156 @@ impl Drop for DropGuard { } } +/// Represent an uninitialized field in a pinned struct that will be referenced by other fields. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory +/// and will live longer than `'a`. +/// - If `P` is `Pinned`, then `ptr` is structurally pinned. +pub struct SelfRefSlot<'a, P, T: ?Sized> { + pub ptr: *mut T, + pub _phantom: PhantomData<(P, &'a mut T)>, +} + +impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { + /// # Safety + /// + /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed + /// memory and will live longer than `'a`. + /// - If `P` is `Pinned`, then `ptr` is structurally pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: Per safety requirement. + Self { + ptr, + _phantom: PhantomData, + } + } + + /// Initialize the field by value. + #[inline] + pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T> + where + T: Sized, + { + // SAFETY: `self.ptr` is a valid and aligned pointer for write. + unsafe { self.ptr.write(value) } + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + // - If `P` is `Pinned`, `self.ptr` is pinned. + unsafe { SelfRefDropGuard::new(self.ptr) } + } +} + +impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { + /// Initialize the field. + #[inline] + pub fn init(self, init: impl Init) -> Result, E> { + // SAFETY: + // - `self.ptr` is valid and properly aligned. + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // also `self` is consumed so it cannot be touched further. + unsafe { init.__init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} + +impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { + /// Initialize the field. + #[inline] + pub fn init(self, init: impl PinInit) -> Result, E> { + // SAFETY: + // - `ptr` is valid + // - when `Err` is returned, we also propagate the error without touching `ptr`; + // also `self` is consumed so it cannot be touched further. + // - the drop guard will not hand out `&mut` (only `Pin<&mut T>`). + unsafe { init.__init(self.ptr)? }; + + // SAFETY: + // - `self.ptr` is valid, properly aligned and live longer than `'a` per type invariant. + // - `*self.ptr` is initialized above and the ownership is transferred to the guard. + Ok(unsafe { SelfRefDropGuard::new(self.ptr) }) + } +} +/// When a value of this type is dropped, it drops a `T`. +/// +/// Can be forgotten to prevent the drop. +/// +/// # Invariants +/// +/// - `ptr` is valid, properly aligned and live longer than `'a`. +/// - `*ptr` is initialized and owned by this guard. +/// - if `P` is `Pinned`, `ptr` is pinned. +pub struct SelfRefDropGuard<'a, P, T: ?Sized> { + ptr: *mut T, + phantom: PhantomData<(P, &'a mut T)>, +} + +impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { + /// Creates a drop guard and transfer the ownership of the pointer content. + /// + /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`]. + /// + /// # Safety + /// + /// - `ptr` is valid, properly aligned and live longer than `'a`. + /// - `*ptr` is initialized, and the ownership is transferred to this guard. + /// - if `P` is `Pinned`, `ptr` is pinned. + #[inline] + pub unsafe fn new(ptr: *mut T) -> Self { + // INVARIANT: By safety requirement. + Self { + ptr, + phantom: PhantomData, + } + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a T { + // SAFETY: Per type invariant. + unsafe { &*self.ptr } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &T { + self.let_binding() + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&*self.ptr) } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&T> { + self.let_binding() + } +} + +impl Drop for SelfRefDropGuard<'_, P, T> { + #[inline] + fn drop(&mut self) { + // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard. + unsafe { ptr::drop_in_place(self.ptr) } + } +} + /// Token used by `PinnedDrop` to prevent calling the function without creating this unsafely /// created struct. This is needed, because the `drop` function is safe, but should not be called /// manually. diff --git a/rust/pin-init/src/lib.rs b/rust/pin-init/src/lib.rs index d1ed0561bb27f6..9ffa764343105a 100644 --- a/rust/pin-init/src/lib.rs +++ b/rust/pin-init/src/lib.rs @@ -923,6 +923,7 @@ macro_rules! assert_pinned { let data = <$ty as $crate::__internal::HasInitData>::__init_data(); let data = $crate::__internal::HasPinData::__pin_data(data); _ = data + .__with_lt() .$field(ptr) .init($crate::__internal::AlwaysFail::<$field_ty>::new()); }; From 71b0637185270a657333bb378d36e6bc79d64518 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 26 Sep 2026 00:25:21 +0100 Subject: [PATCH 1566/2286] rust: pin-init: internal: pin_data: project self-referential fields This adds the projection for fields that are shared borrowed or that borrows other fields but is covariant. Both cases allow a shared reference to be accessed. No mutable references can be created for these cases for different reasons: * For fields that are shared borrowed, aliasing restriction prevents creation of mutable reference * For fields that borrow other fields, their proper type contains field lifetimes. These lifetimes cannot be made available in the returned `project` struct (because there is no way to represent existential lifetime in return position). For covariant types, it is possible to shorten these lifetimes to that of `&self`; but doing so requires the reference to also be covariant over the pointee type, so we cannot give out `&mut` as it is invariant over the pointee. Due to field-referencing fields being wrapped inside `Erase`, the normal accessor syntax stop working; create accessor methods for these fields instead. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-9-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 109 +++++++++++++++++++++---- rust/pin-init/src/__internal.rs | 12 +++ 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index a46c4dcd7da545..002489a9b36c29 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -12,7 +12,7 @@ use syn::{ visit::Visit, visit_mut::VisitMut, Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam, - Member, PathSegment, Type, TypePath, + Member, PathSegment, Token, Type, TypePath, }; use crate::{ @@ -844,7 +844,8 @@ fn generate_projections(info: &StructInfo) -> TokenStream { generics, .. } = &info.struct_; - let this_lt_generics: Generics = parse_quote!(<'__this>); + let this_lt = Lifetime::new("'__this", Span::mixed_site()); + let this_lt_generics: Generics = parse_quote!(<#this_lt>); let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); @@ -855,33 +856,68 @@ fn generate_projections(info: &StructInfo) -> TokenStream { let (fields_decl, fields_proj): (Vec<_>, Vec<_>) = info .fields .iter() - .map(|field| { - let Field { vis, ty, .. } = &field.field; - let member = &field.member; + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; // The projection of a tuple struct is a tuple struct itself, so its fields are // positional and must not be named. - let name = (!info.is_tuple_struct).then(|| { - let ident = field.member.as_ident(); - quote!(#ident:) - }); + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + // if `f.ty` contains field lifetimes, which we need to replace them with shorter + // `'__this` lifetime as field lifetimes are not available in this context. + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = f + .field + .ty + .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]); + + // Fields sharedly borrowed by other fields can only be shared accessed. Fields that + // references other field and are covariant can also only be given shared reference + // as mutable reference is invariant. + let mut_token: Option = if f.borrowed.is_none() && f.captures.is_empty() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance. + unsafe { ::core::mem::transmute::<_, &#mut_token #ty>(#accessor) } + ) + } - if field.pinned { + if !f.captures.iter().all(|b| b.variance == Variance::Covariant) { + // If the type is not covariant, it must omitted, as projection shortens the + // lifetime to `'__this`. ( quote!( - #vis #name ::core::pin::Pin<&'__this mut #ty>, + #vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { + ( + quote!( + #vis #name ::core::pin::Pin<&'__this #mut_token #ty>, ), quote!( // SAFETY: this field is structurally pinned. - #name unsafe { ::core::pin::Pin::new_unchecked(&mut #this.#member) }, + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, ), ) } else { ( quote!( - #vis #name &'__this mut #ty, + #vis #name &'__this #mut_token #ty, ), quote!( - #name &mut #this.#member, + #name #accessor, ), ) } @@ -931,6 +967,49 @@ fn generate_projections(info: &StructInfo) -> TokenStream { }, ) }; + + // For fields that references other fields, field access syntax stops working as they're wrapped + // behind `Erase` because their actual lifetime is not on the struct. + // + // Generate an accessor method for them. + let mut accessors = Vec::new(); + for f in info.fields.iter() { + let ident = f.member.as_ident(); + let member = &f.member; + + if f.captures.is_empty() { + // They can be accessed normally, no accessor to be generated. + continue; + } + + if f.captures.iter().all(|b| b.variance == Variance::Covariant) { + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + + // Use the span of type for better error message. + let span = f.field.ty.span().resolved_at(Span::mixed_site()); + + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = f + .field + .ty + .replace_lifetimes(&all_lifetimes, &vec![&this_lt; all_lifetimes.len()]); + + accessors.push(quote_spanned!(span => + #[doc = #f_doc] + #[inline] + #[allow(clippy::mut_from_ref)] // false positive when `&&mut` is returned. + #vis fn #ident<#this_lt>(&#this_lt self) -> &#this_lt #ty { + // SAFETY: we have `Erased<..>` which we know is layout compatible with `f.ty`. + // Field lifetimes in `f.ty` can be shortened to `#ty` due to covariance. + unsafe { ::core::mem::transmute(&self.#member) } + } + )) + } else { + continue; + } + } + quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on @@ -957,6 +1036,8 @@ fn generate_projections(info: &StructInfo) -> TokenStream { let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; #projection_init } + + #(#accessors)* } } } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index b639c9204a7d6d..d44a5b1f336235 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -5,6 +5,8 @@ //! These items must not be used outside of this crate and the pin-init-internal crate located at //! `../internal`. +#![expect(clippy::new_without_default, reason = "private API")] + use core::marker::PhantomPinned; use core::ops::Deref; @@ -677,3 +679,13 @@ impl Deref for Borrowed { &self.1 } } + +/// An alias of `PhantomData` but with a name to aid user in case of misuse. +pub struct NotVisible(PhantomData); + +impl NotVisible { + #[inline(always)] + pub fn new() -> Self { + Self(PhantomData) + } +} From b8d10c864a76cfbe9d3d94b35482683219aca712 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:29:08 +0100 Subject: [PATCH 1567/2286] rust: pin-init: internal: pin_data: add `with_project` method The `project` method needs to perform covariant coercion on covariant fields, causing them to no longer being mutable. Implement a `with_project` that does not require covariant coercion by using higher-ranked trait bounds, thus allow the fields to be assignable inside the callback. This mechanism can also be used to access non-covariant fields. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-10-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 141 ++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 5 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 002489a9b36c29..78044cbc5a23d7 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -846,10 +846,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream { } = &info.struct_; let this_lt = Lifetime::new("'__this", Span::mixed_site()); let this_lt_generics: Generics = parse_quote!(<#this_lt>); - let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); + // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be + // used with `for`. + let field_lts = CombinedGenerics(vec![&info.field_lts]); + let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); + let generics_with_this_field_lt = + CombinedGenerics(vec![&this_lt_generics, &info.field_lts, generics]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); + let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); + let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl(); let this = format_ident!("this"); @@ -923,16 +930,78 @@ fn generate_projections(info: &StructInfo) -> TokenStream { } }) .collect(); - let structurally_pinned_fields_docs = info + + let (fields_decl_lt, fields_proj_lt): (Vec<_>, Vec<_>) = info + .fields + .iter() + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + let ty = &f.field.ty; + + // Fields shared-referenced by other fields can only be shared accessed. + let mut_token: Option = if f.borrowed.is_none() { + Some(Default::default()) + } else { + None + }; + + let mut accessor = quote!(&#mut_token #this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // We cannot include explicit type name here as the field lifetimes are nameable + // in this context, so `for<'field_name> ..` would fail. + unsafe { ::core::mem::transmute(#accessor) } + ) + } + + // In `with_project`, borrowed fields have their field lifetime available, so use it + // instead of `'__this`. + let lt = if f.borrowed.is_some() { + Lifetime::from_ident(&ident) + } else { + this_lt.clone() + }; + + if f.pinned { + ( + quote!( + #vis #name ::core::pin::Pin<&#lt #mut_token #ty>, + ), + quote!( + // SAFETY: this field is structurally pinned. + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #vis #name &#lt #mut_token #ty, + ), + quote!( + #name #accessor, + ), + ) + } + }) + .collect(); + + let structurally_pinned_fields_docs: Vec<_> = info .fields .iter() .filter(|f| f.pinned) - .map(|f| format!(" - {}", f.member.display_name())); - let not_structurally_pinned_fields_docs = info + .map(|f| format!(" - {}", f.member.display_name())) + .collect(); + let not_structurally_pinned_fields_docs: Vec<_> = info .fields .iter() .filter(|f| !f.pinned) - .map(|f| format!(" - {}", f.member.display_name())); + .map(|f| format!(" - {}", f.member.display_name())) + .collect(); let docs = format!(" Pin-projections of [`{ident}`]"); let (projection_def, projection_init) = if info.is_tuple_struct { ( @@ -968,6 +1037,42 @@ fn generate_projections(info: &StructInfo) -> TokenStream { ) }; + let projection_lt = format_ident!("__ProjectionLt"); + let (projection_lt_def, projection_lt_init) = if info.is_tuple_struct { + ( + quote! { + #vis struct #projection_lt #generics_with_this_field_lt ( + #(#fields_decl_lt)* + ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, + ) #whr; + }, + quote! { + #projection_lt( + #(#fields_proj_lt)* + ::core::marker::PhantomData, + ) + }, + ) + } else { + ( + quote! { + #vis struct #projection_lt #generics_with_this_field_lt + #whr + { + #(#fields_decl_lt)* + ___pin_phantom_data: + ::core::marker::PhantomData<&'__this mut #ident #ty_generics>, + } + }, + quote! { + #projection_lt { + #(#fields_proj_lt)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; + // For fields that references other fields, field access syntax stops working as they're wrapped // behind `Erase` because their actual lifetime is not on the struct. // @@ -1018,6 +1123,13 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #[doc(hidden)] #projection_def + #[doc = #docs] + // Allow `non_snake_case` since the same warning will be emitted on + // the struct definition. + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + #projection_lt_def + impl #impl_generics #ident #ty_generics #whr { @@ -1037,6 +1149,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #projection_init } + /// Pin-projects all fields of `Self` with proper lifetime. + /// + /// These fields are structurally pinned: + #(#[doc = #structurally_pinned_fields_docs])* + /// + /// These fields are **not** structurally pinned: + #(#[doc = #not_structurally_pinned_fields_docs])* + #[inline] + #vis fn with_project<'__this, R>( + self: ::core::pin::Pin<&'__this mut Self>, + f: impl for #field_lt_ty_generics ::core::ops::FnOnce( + #projection_lt #ty_generics_with_this_field_lt + ) -> R, + ) -> R { + // SAFETY: we only give access to `&mut` for fields not structurally pinned. + let #this = unsafe { ::core::pin::Pin::get_unchecked_mut(self) }; + f(#projection_lt_init) + } + #(#accessors)* } } From 8f6607d1e51d082f3eabf82adf62e0c732e1a5d6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 29 Sep 2026 13:58:15 +0100 Subject: [PATCH 1568/2286] rust: pin-init: internal: pin_data: enable self-referential support Enable self-referential support, and add example and test cases. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-11-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/examples/selfref.rs | 35 ++++++++++++++++++++++++++ rust/pin-init/internal/src/pin_data.rs | 7 ------ 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 rust/pin-init/examples/selfref.rs diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs new file mode 100644 index 00000000000000..b5cdf96b6d1cb0 --- /dev/null +++ b/rust/pin-init/examples/selfref.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![allow(clippy::disallowed_names)] + +use pin_init::*; + +#[pin_data] +struct SelfRef { + part: &'str str, + str: String, +} + +fn use_self_ref() { + stack_pin_init!(let foo = pin_init!(SelfRef { + str: "hello world".to_owned(), + part: &str[..5], + })); + + // Access via projection. + println!("{}", foo.as_mut().project().part); + + // Access via accessor. + println!("{}", foo.part()); + + // Access via `with_project`, gives mutable reference. + foo.as_mut().with_project(|proj| { + *proj.part = &proj.str[5..]; + }); + + println!("{}", foo.part()); +} + +fn main() { + use_self_ref(); +} diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 78044cbc5a23d7..ad98bb9f553f05 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -422,13 +422,6 @@ fn expand( } } - if info.self_referential { - dcx.error( - &info.struct_.ident, - "self-referential support is not fully implemented", - ); - } - let struct_def = generate_struct_def(&info); let unpin_impl = generate_unpin_impl(&info); let drop_impl = generate_drop_impl(&info); From f53736dd800db574f508ba6bdab6804c795f79a6 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 1 Oct 2026 14:37:21 +0100 Subject: [PATCH 1569/2286] rust: pin-init: internal: pin_data: allow lifetime to be shortened per field drop order Add the outlive relations per field drop order. This allows a single lifetime to be used when a field potentially borrow from two different fields, by allowing the longer-living field lifetime to be shortened to a shorter-living field lifetime. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-12-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 82 +++++++++++++------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index ad98bb9f553f05..f27dff95a11ab2 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -107,6 +107,7 @@ impl Ord for Capture { } } +#[expect(unused)] struct FieldInfo { field: Field, member: Member, @@ -124,8 +125,8 @@ struct StructInfo { field_idx_map: BTreeMap, is_tuple_struct: bool, self_referential: bool, - /// Field lifetime generics. - field_lts: Generics, + /// Field lifetime generics with outlive chain. + field_lts_outlive_chain: Generics, } pub(crate) fn expand_with_cfg( @@ -346,16 +347,20 @@ fn expand( // Create a lifetime parameter for each field. let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect(); - let mut field_lts = Generics { + let mut field_lts_outlive_chain = Generics { lt_token: None, params: borrowed_fields .iter() - .map(|borrowed| { + .zip(std::iter::once(None).chain(borrowed_fields.iter().map(Some))) + .map(|(borrowed, prev)| { GenericParam::Lifetime(LifetimeParam { attrs: Vec::new(), lifetime: borrowed.lifetime.clone(), colon_token: None, - bounds: Default::default(), + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), }) }) .collect(), @@ -363,36 +368,26 @@ fn expand( where_clause: None, }; - // Insert necessary bounds to make types well-formed. - for field in fields.iter() { - let Some(borrowed) = &field.borrowed else { - continue; - }; - let field_lt = &borrowed.lifetime; - - // For each borrowed field that borrows from other fields, we need to insert outlive bounds. - for capture in &field.captures { - let lt = &capture.lifetime; - field_lts - .make_where_clause() - .predicates - .push(parse_quote!(#lt: #field_lt)); - } - - // For each borrowed field that references a generic, we also need to insert their outlive - // bounds so they can refer to generics. - for lt in field.generic_lt_captures.iter() { - field_lts - .make_where_clause() - .predicates - .push(parse_quote!(#lt: #field_lt)); - } - - for ty in field.generic_ty_captures.iter() { - field_lts - .make_where_clause() - .predicates - .push(parse_quote!(#ty: #field_lt)); + if let Some(last_borrowed_field) = borrowed_fields.last() { + let field_lt = &last_borrowed_field.lifetime; + for param in struct_.generics.params.iter() { + match param { + GenericParam::Lifetime(param) => { + let lt = ¶m.lifetime; + field_lts_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + GenericParam::Type(param) => { + let ty = ¶m.ident; + field_lts_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#ty: #field_lt)); + } + GenericParam::Const(_) => (), + } } } @@ -406,7 +401,7 @@ fn expand( fields, field_idx_map, is_tuple_struct, - field_lts, + field_lts_outlive_chain, }; for field in &info.fields { @@ -703,7 +698,7 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types // are wellformed, given the bounds that we understand. - let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]); + let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]); let (_, ty_generics, _) = generics.split_for_impl(); let (impl_generics_with_field_lt, _, whr_with_field_lt) = @@ -842,10 +837,13 @@ fn generate_projections(info: &StructInfo) -> TokenStream { // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be // used with `for`. - let field_lts = CombinedGenerics(vec![&info.field_lts]); + let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]); let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); - let generics_with_this_field_lt = - CombinedGenerics(vec![&this_lt_generics, &info.field_lts, generics]); + let generics_with_this_field_lt = CombinedGenerics(vec![ + &this_lt_generics, + &info.field_lts_outlive_chain, + generics, + ]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); @@ -1176,8 +1174,8 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be // used with `for`. - let field_lts = CombinedGenerics(vec![&info.field_lts]); - let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts, generics]); + let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]); + let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); From c660201f8245df541041420b3a2087207c8e2ed9 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Sep 2026 13:30:18 +0100 Subject: [PATCH 1570/2286] rust: pin-init: internal: pin_data: parse explicit `#[borrowed]` annotation `#[borrowed]` attribute explicitly marks a field as potentially being borrowed by other fields. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-13-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index f27dff95a11ab2..9b584e4c6d5df5 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -11,8 +11,8 @@ use syn::{ spanned::Spanned, visit::Visit, visit_mut::VisitMut, - Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, LifetimeParam, - Member, PathSegment, Token, Type, TypePath, + Attribute, Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, + LifetimeParam, Member, Meta, PathSegment, Token, Type, TypePath, }; use crate::{ @@ -55,11 +55,25 @@ impl ToTokens for Args { /// Description of how a field is borrowed. #[derive(Clone, Copy, Default, PartialEq, Eq)] enum BorrowedKind { - /// Implicitly inferreed. + /// `#[borrowed]`, or implicitly inferreed. #[default] Shared, } +impl BorrowedKind { + fn parse(dcx: &mut DiagCtxt, attrs: &mut Vec) -> Option { + let attr = attrs.extract_single_attr(dcx, "borrowed")?; + + Some(if let Meta::Path(_) = attr.meta { + BorrowedKind::Shared + } else { + // Swallow the error and recover by inferring shared. + dcx.error(attr.path(), "unexpected `#[borrowed]` attribute"); + BorrowedKind::Shared + }) + } +} + /// Information about a borrowed field. struct BorrowedInfo { kind: BorrowedKind, @@ -305,11 +319,25 @@ fn expand( }) .visit_type(&field.ty); + let borrowed = BorrowedKind::parse(dcx, &mut field.attrs).and_then(|kind| { + let lifetime = Lifetime::from_ident(&member.as_ident()); + + if bound_lifetimes.contains(&lifetime) { + dcx.error( + &lifetime, + format!("`{lifetime}` appear in generics and would conflict with field lifetime"), + ); + return None; + } + + Some(BorrowedInfo { kind, lifetime }) + }); + FieldInfo { field, member, pinned, - borrowed: None, + borrowed, captures, generic_lt_captures, generic_ty_captures, From 0b326dd96e773ef5e980f62f5e1b4fb05c4f983a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 6 May 2026 18:12:59 +0100 Subject: [PATCH 1571/2286] rust: pin-init: internal: pin_data: support mutable borrows Allow fields to be mutably referenced by other fields in addition to shared references. In order for this to be sound, the fields that can be mutably borrowed are blocked from being accessed via field access syntax or projection to maintain the aliasing requirements. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-14-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/examples/selfref.rs | 13 ++++ rust/pin-init/internal/src/pin_data.rs | 94 +++++++++++++++++++++++--- rust/pin-init/src/__internal.rs | 66 ++++++++++++++---- 3 files changed, 149 insertions(+), 24 deletions(-) diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs index b5cdf96b6d1cb0..5e9494dcc17e7f 100644 --- a/rust/pin-init/examples/selfref.rs +++ b/rust/pin-init/examples/selfref.rs @@ -8,12 +8,18 @@ use pin_init::*; struct SelfRef { part: &'str str, str: String, + + mut_part: &'mut_str mut str, + #[borrowed(mut)] + mut_str: String, } fn use_self_ref() { stack_pin_init!(let foo = pin_init!(SelfRef { str: "hello world".to_owned(), part: &str[..5], + mut_str: "hello world".to_owned(), + mut_part: &mut mut_str[..5], })); // Access via projection. @@ -28,6 +34,13 @@ fn use_self_ref() { }); println!("{}", foo.part()); + + // Access fields that mutable borrow others are similar to those of shared borrow. + println!("{}", foo.as_mut().project().mut_part); + println!("{}", foo.mut_part()); + foo.as_mut().with_project(|proj| { + proj.mut_part.make_ascii_uppercase(); + }); } fn main() { diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 9b584e4c6d5df5..9910e14b9926c9 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -5,7 +5,7 @@ use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ - parse::{End, Nothing, Parse}, + parse::{End, Nothing, Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, @@ -58,6 +58,8 @@ enum BorrowedKind { /// `#[borrowed]`, or implicitly inferreed. #[default] Shared, + // `#[borrowed(mut)]`. + Mutable, } impl BorrowedKind { @@ -67,9 +69,17 @@ impl BorrowedKind { Some(if let Meta::Path(_) = attr.meta { BorrowedKind::Shared } else { - // Swallow the error and recover by inferring shared. - dcx.error(attr.path(), "unexpected `#[borrowed]` attribute"); - BorrowedKind::Shared + match attr.parse_args_with(|input: ParseStream<'_>| { + let _: Token![mut] = input.parse()?; + Ok(BorrowedKind::Mutable) + }) { + Ok(v) => v, + Err(err) => { + // Swallow the error and recover by inferring shared. + dcx.error(attr.path(), err); + BorrowedKind::Shared + } + } }) } } @@ -515,8 +525,17 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { let mut ty = ty.to_token_stream(); - // Replace lifetime for self-referential fields. - if !field.captures.is_empty() { + // Replace lifetime for self-referential fields. For mutable fields, this uses `Erase` to + // block direct access. + if !field.captures.is_empty() + || matches!( + field.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) + { // Build a chain `for<'a> fn(&'a ()) -> ... -> (Ty,)`. Such type will have a `EraseLt` // implementation and thus may be used inside `Erase`. ty = quote!((#ty,)); @@ -916,9 +935,18 @@ fn generate_projections(info: &StructInfo) -> TokenStream { ) } - if !f.captures.iter().all(|b| b.variance == Variance::Covariant) { + if !f.captures.iter().all(|b| b.variance == Variance::Covariant) + || matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) + { // If the type is not covariant, it must omitted, as projection shortens the // lifetime to `'__this`. + // Mutable borrow must be omitted for aliasing reason. ( quote!( #vis #name ::pin_init::__internal::NotVisible<&'__this #mut_token #ty>, @@ -986,7 +1014,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream { this_lt.clone() }; - if f.pinned { + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // If the type is not covariant, it must omitted, as projection shortens the + // lifetime to `'__this`. + // Mutable borrow must be omitted for aliasing reason. + ( + quote!( + #vis #name ::pin_init::__internal::NotVisible<&#lt #mut_token #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { ( quote!( #vis #name ::core::pin::Pin<&#lt #mut_token #ty>, @@ -1106,6 +1152,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream { continue; } + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // Mutably borrowed fields cannot be accessed directly under any circumstance. + continue; + } + if f.captures.iter().all(|b| b.variance == Variance::Covariant) { let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); let vis = &f.field.vis; @@ -1261,7 +1318,26 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { // assumptions on the lifetime except for those implied by the struct's bounds, // and we have validated them in `generate_drop_check`. quote!(SelfRefSlot), - quote!(#lifetime,), + quote!(#lifetime, ::pin_init::__internal::Shared, ), + ), + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + lifetime, + }) => ( + // For borrowed fields, create a `SelfRefSlot`, which after initialization + // turns into a `SelfRefDropGuard` instead of `DropGuard`. + // + // They're mostly the same, except that `SelfRefDropGuard` returns `&'field T` + // instead of `&'guard T` for let bindings; this allows it to be used to be + // used to initialize other fields. + // + // The soundness of doing so relies on fact that `__make_init` requires a + // higher-ranked trait bound on the closure. Within the closure (which is the + // caller of the generated slot projection functions here), it can make no + // assumptions on the lifetime except for those implied by the struct's bounds, + // and we have validated them in `generate_drop_check`. + quote!(SelfRefSlot), + quote!(#lifetime, ::pin_init::__internal::Mutable, ), ), }; diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index d44a5b1f336235..c013d94ab5aa01 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -361,6 +361,9 @@ impl Drop for DropGuard { } } +pub struct Shared; +pub struct Mutable; + /// Represent an uninitialized field in a pinned struct that will be referenced by other fields. /// /// # Invariants @@ -368,12 +371,12 @@ impl Drop for DropGuard { /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed memory /// and will live longer than `'a`. /// - If `P` is `Pinned`, then `ptr` is structurally pinned. -pub struct SelfRefSlot<'a, P, T: ?Sized> { - pub ptr: *mut T, - pub _phantom: PhantomData<(P, &'a mut T)>, +pub struct SelfRefSlot<'a, M, P, T: ?Sized> { + ptr: *mut T, + _phantom: PhantomData<(M, P, &'a mut T)>, } -impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { +impl<'a, M, P, T: ?Sized> SelfRefSlot<'a, M, P, T> { /// # Safety /// /// - `ptr` is valid, properly aligned and points to uninitialized and exclusively accessed @@ -390,7 +393,7 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { /// Initialize the field by value. #[inline] - pub fn write(self, value: T) -> SelfRefDropGuard<'a, P, T> + pub fn write(self, value: T) -> SelfRefDropGuard<'a, M, P, T> where T: Sized, { @@ -404,10 +407,10 @@ impl<'a, P, T: ?Sized> SelfRefSlot<'a, P, T> { } } -impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Unpinned, T> { /// Initialize the field. #[inline] - pub fn init(self, init: impl Init) -> Result, E> { + pub fn init(self, init: impl Init) -> Result, E> { // SAFETY: // - `self.ptr` is valid and properly aligned. // - when `Err` is returned, we also propagate the error without touching `ptr`; @@ -421,10 +424,13 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Unpinned, T> { } } -impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { +impl<'a, M, T: ?Sized> SelfRefSlot<'a, M, Pinned, T> { /// Initialize the field. #[inline] - pub fn init(self, init: impl PinInit) -> Result, E> { + pub fn init( + self, + init: impl PinInit, + ) -> Result, E> { // SAFETY: // - `ptr` is valid // - when `Err` is returned, we also propagate the error without touching `ptr`; @@ -447,12 +453,12 @@ impl<'a, T: ?Sized> SelfRefSlot<'a, Pinned, T> { /// - `ptr` is valid, properly aligned and live longer than `'a`. /// - `*ptr` is initialized and owned by this guard. /// - if `P` is `Pinned`, `ptr` is pinned. -pub struct SelfRefDropGuard<'a, P, T: ?Sized> { +pub struct SelfRefDropGuard<'a, M, P, T: ?Sized> { ptr: *mut T, - phantom: PhantomData<(P, &'a mut T)>, + phantom: PhantomData<(M, P, &'a mut T)>, } -impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { +impl<'a, M, P, T: ?Sized> SelfRefDropGuard<'a, M, P, T> { /// Creates a drop guard and transfer the ownership of the pointer content. /// /// The ownership is only relinquished if the guard is forgotten via [`core::mem::forget`]. @@ -472,7 +478,7 @@ impl<'a, P, T: ?Sized> SelfRefDropGuard<'a, P, T> { } } -impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Unpinned, T> { /// Create a let binding for accessor use. #[inline] pub fn let_binding(&mut self) -> &'a T { @@ -487,7 +493,7 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Unpinned, T> { } } -impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Shared, Pinned, T> { /// Create a let binding for accessor use. #[inline] pub fn let_binding(&mut self) -> Pin<&'a T> { @@ -503,7 +509,37 @@ impl<'a, T: ?Sized> SelfRefDropGuard<'a, Pinned, T> { } } -impl Drop for SelfRefDropGuard<'_, P, T> { +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Unpinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> &'a mut T { + // SAFETY: Per type invariant. + unsafe { &mut *self.ptr } + } + + /// Create a let binding for accessor use in dropck. + #[inline] + pub fn let_binding_in_dropck(&mut self) -> &mut T { + self.let_binding() + } +} + +impl<'a, T: ?Sized> SelfRefDropGuard<'a, Mutable, Pinned, T> { + /// Create a let binding for accessor use. + #[inline] + pub fn let_binding(&mut self) -> Pin<&'a mut T> { + // SAFETY: `self.ptr` is valid, properly aligned, live longer than `'a`, initialized, + // exclusively accessible and pinned per type invariant. + unsafe { Pin::new_unchecked(&mut *self.ptr) } + } + + #[inline] + pub fn let_binding_in_dropck(&mut self) -> Pin<&mut T> { + self.let_binding() + } +} + +impl Drop for SelfRefDropGuard<'_, M, P, T> { #[inline] fn drop(&mut self) { // SAFETY: `self.ptr` is valid, properly aligned and `*self.ptr` is owned by this guard. From 59a9b11bccdb308f242ba6abc4638d64816781b1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Sep 2026 13:30:18 +0100 Subject: [PATCH 1572/2286] rust: pin-init: internal: pin_data: parse explicit `#[uses]` annotation Add support for explicit self-referential annotations. `#[uses]` attribute is used to mark what other field lifetimes are captured by this field, and also the variance of the type in respect to the field lifetimes. For example, #[uses('a: covariant, 'b: invariant)] indicates that the field captures the field lifetime `'a` covariantly, and field lifetime `'b` invariantly. Many types are covariant, so this is the default variance if the variance is omitted (e.g. `#[uses('a)]`), consistent with the automatically inferred borrow. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-15-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 99 ++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 4 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 9910e14b9926c9..ad43f0bbc4c1a2 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -21,6 +21,9 @@ use crate::{ }; pub(crate) mod kw { + syn::custom_keyword!(covariant); + syn::custom_keyword!(invariant); + syn::custom_keyword!(contravariant); syn::custom_keyword!(PinnedDrop); } @@ -93,9 +96,37 @@ struct BorrowedInfo { #[derive(Clone, Copy, Default, PartialEq, Eq)] enum Variance { - /// Implicitly inferred variance. + /// `covariant` annotation, or implicitly inferred. #[default] Covariant, + // `invariant` annotation. + Invariant, +} + +impl Parse for Variance { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lh = input.lookahead1(); + Ok(if lh.peek(kw::covariant) { + let _: kw::covariant = input.parse()?; + Variance::Covariant + } else if lh.peek(kw::invariant) { + let _: kw::invariant = input.parse()?; + Variance::Invariant + } else if lh.peek(kw::contravariant) { + // Field lifetimes are inherently covariant, so combining with contravariance, + // we would constrain it to be invariant. + let token: kw::contravariant = input.parse()?; + DiagCtxt::current(|dcx| { + dcx.error( + token, + "field lifetimes cannot be `contravariant`; use `invariant` instead", + ) + }); + Variance::Invariant + } else { + Err(lh.error())? + }) + } } /// Information about field lifetimes captured in a type. @@ -131,7 +162,65 @@ impl Ord for Capture { } } -#[expect(unused)] +impl Parse for Capture { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lifetime = input.parse()?; + let variance = if input.peek(Token![:]) { + let _: Token![:] = input.parse()?; + input.parse()? + } else { + // If variance is not explicitly specified, infer covariance by default. + Variance::Covariant + }; + Ok(Capture { variance, lifetime }) + } +} + +impl Capture { + fn parse_list( + dcx: &mut DiagCtxt, + attrs: &mut Vec, + bound_lifetimes: &BTreeSet<&Lifetime>, + field_idx_map: &BTreeMap, + ) -> Option<(BTreeSet, Variance)> { + let attr = attrs.extract_single_attr(dcx, "uses")?; + let punctuated: Punctuated = attr + .parse_args_with(Punctuated::parse_terminated) + .map_err(ErrorGuaranteed::from) + .ok()?; + + // Check for misuses inside attribute. + let mut set = BTreeSet::new(); + for borrow in punctuated { + let lt = &borrow.lifetime; + if set.contains(&borrow) { + dcx.error(lt, format!("lifetime `{lt}` is mentioned more than once")); + continue; + } + + if bound_lifetimes.contains(lt) { + dcx.error( + lt, + format!("`{lt}` is a struct generics and cannot be used in `#[uses]`"), + ); + continue; + } + + if lt.ident != "_" && !field_idx_map.contains_key(<.ident) { + dcx.error(lt, format!("`{lt}` is not a field name")); + continue; + } + + set.insert(borrow); + } + + let wildcard = Lifetime::new("'_", Span::mixed_site()); + let wildcard_variance = set.take(&wildcard).map(|b| b.variance).unwrap_or_default(); + Some((set, wildcard_variance)) + } +} + +#[allow(unused)] struct FieldInfo { field: Field, member: Member, @@ -275,8 +364,10 @@ fn expand( }), }; - let mut captures = BTreeSet::new(); - let wildcard_variance = Variance::default(); + // Parse `#[uses]` attribute. + let (mut captures, wildcard_variance) = + Capture::parse_list(dcx, &mut field.attrs, &bound_lifetimes, &field_idx_map) + .unwrap_or_default(); let mut generic_lt_captures = BTreeSet::new(); let mut generic_ty_captures = BTreeSet::new(); From 91a255fea7306c5b07f6b26d740be31fdc8bfa41 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 9 Sep 2026 19:37:00 +0100 Subject: [PATCH 1573/2286] rust: pin-init: internal: pin_data: make field lifetime invariance imply type invariance If invariant field captures a field lifetime, then we also need to make sure that field is also invariant. Imagine this struct: #[pin_data] struct SelfRef<'a> { #[uses('outer: invariant)] part: Mutex<&'outer str>, outer: &'a String, } fn new<'a>(str: &'a String) -> impl PinInit, Infallible> { pin_init!(SelfRef { outer: str, part: Mutex::new(*outer), }) } If we make this struct covariant over `'a`, then we can have the following case: let mut long = "hello world".to_owned(); let s = Box::pin_init(new(&long)).unwrap(); { let mut short = "hello world".to_owned(); // If `s` is covariant, this would be okay, because we shorten from // `SelfRef<'long>` to `SelfRef<'short>`. s.with_project_ref(|p| { *p.part.lock().unwrap() = &short; }); } Conceptually, a field's type must outlive the field's lifetime, so if we have a covariant `'a`, we can have a shortened `SelfRef<'short_a>` where `'outer` outlives `'short_a`. But the wellformedness requirement of the field will imply `'short_a: 'outer`, which enables the `&'short_a` to `'outer` coercion, effectively making the field lifetime `'f` behave covariantly, too, breaking the requirement that it is invariant. Therefore, compute an invariant closure and use an additional generics on `Borrowed` to allow capturing things invariantly. Note that we do capture all parameters explicitly rather than capture the field type invariantly, because if type aliases are involved, we might be syntactically determining that the field uses a type parameter but actually not, causing the invariance enforcement to be missed. Outlive bounds between field lifetimes can also cause the same issue (an invariant field lifetime cannot be a lower bound of a covariant field lifetime). Since we cannot deduce whether any implied bounds from type wellformness would create such outlive relationships, prevent such bounds from happening by using a split outlive chain. Note that this is not a soundness hole in itself in absence of `with_project_ref`, because with single field accessors only, the field lifetimes of the fields are not connected; in methods like `with_project` lifetimes are invariant so shortening cannot happen. However, such method is likely desirable, so include the variance rule before it has been heavily relied upon. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-16-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 175 ++++++++++++++++++++++++- rust/pin-init/src/__internal.rs | 8 +- 2 files changed, 174 insertions(+), 9 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index ad43f0bbc4c1a2..77c8ce2604033b 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -92,6 +92,8 @@ struct BorrowedInfo { kind: BorrowedKind, /// Field lifetime for this field. lifetime: Lifetime, + // Variance of the field lifetime, as captured by other fields. + lt_variance: Variance, } #[derive(Clone, Copy, Default, PartialEq, Eq)] @@ -238,8 +240,11 @@ struct StructInfo { field_idx_map: BTreeMap, is_tuple_struct: bool, self_referential: bool, - /// Field lifetime generics with outlive chain. + /// Field lifetime genercis with outlive chain. field_lts_outlive_chain: Generics, + /// Field lifetime genercis with outlive chain, with one chain for covariant fields and one + /// chain for invariant fields. + field_lts_split_variance_outlive_chain: Generics, } pub(crate) fn expand_with_cfg( @@ -431,7 +436,7 @@ fn expand( return None; } - Some(BorrowedInfo { kind, lifetime }) + Some(BorrowedInfo { kind, lifetime, lt_variance: Variance::default() }) }); FieldInfo { @@ -455,6 +460,7 @@ fn expand( kind: BorrowedKind::Shared, // Obtaining from `field` instead of `field_name` for the correct span. lifetime: Lifetime::from_ident(&field.member.as_ident()), + lt_variance: Variance::Covariant, }); } } @@ -474,8 +480,63 @@ fn expand( }) .visit_generics(&struct_.generics); + // Obtain an closure of invariant fields. + // + // If a field lifetime is used invariantly, we also need to make sure that + // for each generic parameter `T: 'field` bound, `T` is also captured + // invariantly (same is true for lifetime parameters). For example, say we + // have a struct `SelfRef<'a>` and a field `f: &'a ()`. For wellformedness, + // we would have `'a: 'f`. If we have a covariant `'a`, we can observe a + // shortened `SelfRef<'short_a>` where `'f` outlives `'short_a`, conflicting + // with the wellformedness bound `'short_a: 'f`. This will enable the + // `&'short_a ()` to `&'f ()` coercion, which effectively shortens `'f` too, + // so we shortened the field lifetime despite it being invariant. + let mut invariant_field_idx = BTreeSet::new(); + let mut worklist = Vec::new(); + for field in fields.iter() { + for borrow in field.captures.iter() { + if let Variance::Invariant = borrow.variance { + let Some(borrow_idx) = fields + .iter() + .position(|f| f.member.as_ident() == borrow.lifetime.ident) + else { + continue; + }; + if invariant_field_idx.insert(borrow_idx) { + worklist.push(&fields[borrow_idx]); + } + } + } + } + while let Some(field) = worklist.pop() { + for borrow in field.captures.iter() { + let Some(borrow_idx) = fields + .iter() + .position(|f| f.member.as_ident() == borrow.lifetime.ident) + else { + continue; + }; + let borrow = &fields[borrow_idx]; + if invariant_field_idx.insert(borrow_idx) { + worklist.push(borrow); + } + } + } + for idx in invariant_field_idx { + fields[idx].borrowed.as_mut().unwrap().lt_variance = Variance::Invariant; + } + // Create a lifetime parameter for each field. let borrowed_fields: Vec<_> = fields.iter().filter_map(|f| f.borrowed.as_ref()).collect(); + let borrowed_covariant_fields: Vec<_> = borrowed_fields + .iter() + .filter(|f| f.lt_variance == Variance::Covariant) + .collect(); + let borrowed_invariant_fields: Vec<_> = borrowed_fields + .iter() + .filter(|f| f.lt_variance == Variance::Invariant) + .collect(); + let mut field_lts_outlive_chain = Generics { lt_token: None, params: borrowed_fields @@ -520,6 +581,96 @@ fn expand( } } + let mut field_lts_split_variance_outlive_chain = Generics { + lt_token: Some(Default::default()), + params: borrowed_covariant_fields + .iter() + .zip(std::iter::once(None).chain(borrowed_covariant_fields.iter().map(Some))) + .map(|(borrowed, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), + }) + }) + .chain( + borrowed_invariant_fields + .iter() + .zip(std::iter::once(None).chain(borrowed_invariant_fields.iter().map(Some))) + .map(|(borrowed, prev)| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: borrowed.lifetime.clone(), + colon_token: None, + bounds: prev + .iter() + .map(|borrowed| borrowed.lifetime.clone()) + .collect(), + }) + }), + ) + .collect(), + gt_token: Some(Default::default()), + where_clause: None, + }; + + // For `field_lts_split_variance_outlive_chain`, we may need to insert some additional bounds + // for types to be WF. + for field in fields.iter() { + let Some(borrowed) = &field.borrowed else { + continue; + }; + let field_lt = &borrowed.lifetime; + + // For each borrowed field that references a generic, we also need to insert their outlive + // bounds so they can refer to generics. + for lt in field.generic_lt_captures.iter() { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + + for ty in field.generic_ty_captures.iter() { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#ty: #field_lt)); + } + + // If a field is invariant, then the invariance closure rule will make all borrowed fields + // to be invariant, so they're already captured in `field_lts_split_variance_outlive_chain`. + if borrowed.lt_variance != Variance::Covariant { + continue; + } + + for capture in field.captures.iter() { + let Some(&idx) = field_idx_map.get(&capture.lifetime.ident) else { + continue; + }; + + let prev_borrowed = fields[idx].borrowed.as_ref().unwrap(); + + // If borrowed field is covariant, it's already captured in + // `field_lts_split_variance_outlive_chain`. + if prev_borrowed.lt_variance == Variance::Invariant { + // Covariant field borrowing an invariant field. This is not captured in the chain + // so we need to add additional bound. This bound is okay, as the invariant lifetime + // is the longer living one, so arbitrary shortening of the covariant one does not + // violate their relation. + let param = field_lts_split_variance_outlive_chain + .lifetimes_mut() + .find(|l| l.lifetime == prev_borrowed.lifetime) + .unwrap(); + param.bounds.push(borrowed.lifetime.clone()); + } + } + } + struct_.fields = Fields::Unit; let info = StructInfo { self_referential: fields @@ -531,6 +682,7 @@ fn expand( field_idx_map, is_tuple_struct, field_lts_outlive_chain, + field_lts_split_variance_outlive_chain, }; for field in &info.fields { @@ -639,8 +791,18 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { ty = quote!(::pin_init::__internal::Erase<#ty>); }; - if field.borrowed.is_some() { - ty = quote!(::pin_init::__internal::Borrowed<#ty>); + if let Some(borrowed) = &field.borrowed { + let mut invariance_capture = Vec::new(); + if borrowed.lt_variance == Variance::Invariant { + for lt in &field.generic_lt_captures { + invariance_capture.push(quote!(&#lt ())); + } + + for ty in &field.generic_ty_captures { + invariance_capture.push(quote!(::core::marker::PhantomData<#ty>)); + } + } + ty = quote!(::pin_init::__internal::Borrowed<#ty, (#(#invariance_capture,)*)>); } quote! { @@ -836,7 +998,8 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types // are wellformed, given the bounds that we understand. - let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]); + let generics_with_field_lt = + CombinedGenerics(vec![&info.field_lts_split_variance_outlive_chain, generics]); let (_, ty_generics, _) = generics.split_for_impl(); let (impl_generics_with_field_lt, _, whr_with_field_lt) = @@ -1395,6 +1558,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { Some(BorrowedInfo { kind: BorrowedKind::Shared, lifetime, + .. }) => ( // For borrowed fields, create a `SelfRefSlot`, which after initialization // turns into a `SelfRefDropGuard` instead of `DropGuard`. @@ -1414,6 +1578,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { Some(BorrowedInfo { kind: BorrowedKind::Mutable, lifetime, + .. }) => ( // For borrowed fields, create a `SelfRefSlot`, which after initialization // turns into a `SelfRefDropGuard` instead of `DropGuard`. diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index c013d94ab5aa01..9f0f5a34e630c2 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -676,7 +676,7 @@ where /// This should be switched to `UnsafePinned` when it is stable. /// NOTE: This type needs to be covariant; Rust's 1.89+'s `UnsafePinned` is invariant. #[repr(transparent)] -pub struct Borrowed(PhantomPinned, T); +pub struct Borrowed(PhantomInvariant

, PhantomPinned, T); // Lifetimes not needed by drop glue are considered by Rust's drop check to be considered // `#[may_dangle]`. In case for a self-referential struct, we may have fields which need lifetime of @@ -702,17 +702,17 @@ pub struct Borrowed(PhantomPinned, T); // outlive the struct. And this can be done by a simple `Drop` impl that does nothing. Without a // dropck eye patch, presence of `Drop` impl, albeit empty, tells the drop check that the strict // outlive relation is needed. -impl Drop for Borrowed { +impl Drop for Borrowed { #[inline(always)] fn drop(&mut self) {} } -impl Deref for Borrowed { +impl Deref for Borrowed { type Target = T; #[inline(always)] fn deref(&self) -> &T { - &self.1 + &self.2 } } From e55acb73379fc2a5164c741bb1257becd346bc39 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:40:15 +0100 Subject: [PATCH 1574/2286] rust: pin-init: internal: pin_data: complete invariant borrow support Non-covariant types can already be accessed inside projections. As projections are only generated for `Pin<&mut T>`, they're not accessible otherwise. Add `with_{field_name}` methods so fields can be accessed using closures with just `&T`. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-17-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/examples/selfref.rs | 12 ++++++++++++ rust/pin-init/internal/src/pin_data.rs | 19 ++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/rust/pin-init/examples/selfref.rs b/rust/pin-init/examples/selfref.rs index 5e9494dcc17e7f..5212b9255b7508 100644 --- a/rust/pin-init/examples/selfref.rs +++ b/rust/pin-init/examples/selfref.rs @@ -6,6 +6,9 @@ use pin_init::*; #[pin_data] struct SelfRef { + #[uses('_: invariant)] + not_cov: Box bool + 'str>, + part: &'str str, str: String, @@ -20,6 +23,7 @@ fn use_self_ref() { part: &str[..5], mut_str: "hello world".to_owned(), mut_part: &mut mut_str[..5], + not_cov: Box::new(move |s| s == str), })); // Access via projection. @@ -41,6 +45,14 @@ fn use_self_ref() { foo.as_mut().with_project(|proj| { proj.mut_part.make_ascii_uppercase(); }); + + // Access non-covariant type using `with_` accessor. + foo.with_not_cov(|not_cov| { + not_cov(""); + }); + + // Access non-covariant type using `with_project`. + foo.as_mut().with_project(|proj| (proj.not_cov)(proj.str)); } fn main() { diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 77c8ce2604033b..4b361c510a4086 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1441,7 +1441,24 @@ fn generate_projections(info: &StructInfo) -> TokenStream { } )) } else { - continue; + let f_doc = format!("Access the `{ident}` field on a shared reference of `Self`."); + let vis = &f.field.vis; + let with_ident = format_ident!("with_{ident}"); + + let all_lifetimes: Vec<_> = f.captures.iter().map(|b| &b.lifetime).collect(); + let ty = &f.field.ty; + + accessors.push(quote!( + #[doc = #f_doc] + #[inline] + #vis fn #with_ident<'__this, R>( + &'__this self, + f: impl for<#(#all_lifetimes,)*> ::core::ops::FnOnce(&'__this #ty) -> R, + ) -> R { + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + f(unsafe { ::core::mem::transmute(&self.#member) }) + } + )) } } From e2b2e01365120d388c25ddaefa9f1e6a25aa1f28 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 2 May 2026 22:58:33 +0100 Subject: [PATCH 1575/2286] rust: pin-init: internal: pin_data: perform AST lifetime replacement if possible Currently lifetimes are replaced via function type and `FnOutput` trait. This is very general approach as it uses generic associated type to replace lifetime, so it can even work when macros are involved. This does cause more generated code, and does not render in documentation nicely. Thus, just replace the lifetime in the AST if no macros are involved. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-18-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/util.rs | 112 ++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 10 deletions(-) diff --git a/rust/pin-init/internal/src/util.rs b/rust/pin-init/internal/src/util.rs index 59054f5934fb9f..4d3331b852b4f8 100644 --- a/rust/pin-init/internal/src/util.rs +++ b/rust/pin-init/internal/src/util.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use syn::{ - parse_quote, visit::Visit, Attribute, BoundLifetimes, GenericParam, Generics, Index, Lifetime, - Member, Token, Type, TypePath, + parse_quote, visit::Visit, visit_mut::VisitMut, Attribute, BoundLifetimes, GenericParam, + Generics, Index, Lifetime, Member, Token, Type, TypePath, }; use crate::DiagCtxt; @@ -388,21 +388,113 @@ impl GenericParamExt for GenericParam { } pub(crate) trait TypeExt { + /// Check if the type includes macro invocations. + /// + /// Proc-macros cannot expand macros and peek into them, so if macro is involved sometimes + /// special handling is required. + fn has_macro(&self) -> bool; + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type; } impl TypeExt for Type { + fn has_macro(&self) -> bool { + struct HasMacro(bool); + + impl<'ast> Visit<'ast> for HasMacro { + fn visit_macro(&mut self, _: &'ast syn::Macro) { + self.0 = true; + } + } + + let mut visitor = HasMacro(false); + visitor.visit_type(self); + visitor.0 + } + fn replace_lifetimes(&self, needle: &[&Lifetime], replacement: &[&Lifetime]) -> Type { if needle.is_empty() { return self.clone(); } - parse_quote!( - < - for<#(#needle,)*> fn(#(&#needle (),)*) -> #self - as - ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)> - >::Output - ) + // If the type has macro, we cannot peek into it. Use some different approach to replace + // the type using GAT. + if self.has_macro() { + return parse_quote!( + < + for<#(#needle,)*> fn(#(&#needle (),)*) -> #self + as + ::pin_init::__internal::FnOutput<(#(&#replacement (),)*)> + >::Output + ); + } + + struct LifetimeReplacer<'a> { + to_replace: BTreeMap<&'a Lifetime, &'a Lifetime>, + } + + impl<'a> LifetimeReplacer<'a> { + fn with_bound_lifetimes( + &mut self, + bound: Option<&BoundLifetimes>, + f: impl FnOnce(&mut Self), + ) { + // In case the type includes a lifetime binder, e.g. `dyn for<'a> Foo`, + // temporarily remove them from to_replace if they're. + + let mut removed = Vec::new(); + if let Some(bound) = bound { + for lt in &bound.lifetimes { + let GenericParam::Lifetime(lt) = lt else { + continue; + }; + if let Some(entry) = self.to_replace.remove_entry(<.lifetime) { + removed.push(entry); + } + } + } + + f(self); + + for (key, val) in removed { + self.to_replace.insert(key, val); + } + } + } + + impl VisitMut for LifetimeReplacer<'_> { + fn visit_lifetime_mut(&mut self, lt: &mut syn::Lifetime) { + if let Some(&replacement) = self.to_replace.get(lt) { + *lt = replacement.clone(); + } + } + + fn visit_trait_bound_mut(&mut self, bound: &mut syn::TraitBound) { + self.with_bound_lifetimes(bound.lifetimes.as_ref(), |this| { + this.visit_path_mut(&mut bound.path) + }); + } + + fn visit_type_bare_fn_mut(&mut self, bare_fn: &mut syn::TypeBareFn) { + self.with_bound_lifetimes(bare_fn.lifetimes.as_ref(), |this| { + for input in bare_fn.inputs.iter_mut() { + this.visit_bare_fn_arg_mut(input); + } + + this.visit_return_type_mut(&mut bare_fn.output); + }); + } + } + + let mut ret = self.clone(); + LifetimeReplacer { + to_replace: needle + .iter() + .copied() + .zip(replacement.iter().copied()) + .collect(), + } + .visit_type_mut(&mut ret); + ret } } From e2ab80587d02ef262df78a48660eef06adeb133b Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Tue, 8 Sep 2026 23:54:51 +0100 Subject: [PATCH 1576/2286] rust: pin-init: internal: pin_data: support shared projection For fields that self-references, it is desirable that projection can happen on shared references too, so lifetime between different fields can be correlated. Add support for that with `with_project_ref`. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-19-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 134 +++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 4b361c510a4086..8ab71544f67442 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1145,10 +1145,17 @@ fn generate_projections(info: &StructInfo) -> TokenStream { &info.field_lts_outlive_chain, generics, ]); + let generics_with_this_field_ref_lt = CombinedGenerics(vec![ + &this_lt_generics, + &info.field_lts_split_variance_outlive_chain, + generics, + ]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl(); + let (_, ty_generics_with_this_field_ref_lt, _) = + generics_with_this_field_ref_lt.split_for_impl(); let this = format_ident!("this"); @@ -1462,6 +1469,109 @@ fn generate_projections(info: &StructInfo) -> TokenStream { } } + let (fields_ref_decl_lt, fields_ref_proj_lt): (Vec<_>, Vec<_>) = info + .fields + .iter() + .map(|f| { + let vis = &f.field.vis; + let ident = f.member.as_ident(); + let member = &f.member; + let name = (!info.is_tuple_struct).then(|| quote!(#ident:)); + + let ty = &f.field.ty; + + let mut accessor = quote!(&#this.#member); + if !f.captures.is_empty() || f.borrowed.is_some() { + accessor = quote!( + // SAFETY: we have `Erase<..>` which we know is layout compatible with `f.ty`. + // We cannot include explicit type name here as the field lifetimes are nameable + // in this context. + unsafe { ::core::mem::transmute(#accessor) } + ) + } + + // In `with_project`, borrowed fields have their field lifetime available, so use it + // instead of `'__this`. + let lt = if f.borrowed.is_some() { + Lifetime::from_ident(&ident) + } else { + this_lt.clone() + }; + + if matches!( + f.borrowed, + Some(BorrowedInfo { + kind: BorrowedKind::Mutable, + .. + }) + ) { + // Mutable borrow must be omitted for aliasing reason. + ( + quote!( + #vis #name ::pin_init::__internal::NotVisible<&#lt #ty>, + ), + quote!( + #name ::pin_init::__internal::NotVisible::new(), + ), + ) + } else if f.pinned { + ( + quote!( + #vis #name ::core::pin::Pin<&#lt #ty>, + ), + quote!( + // SAFETY: this field is structurally pinned. + #name unsafe { ::core::pin::Pin::new_unchecked(#accessor) }, + ), + ) + } else { + ( + quote!( + #vis #name &#lt #ty, + ), + quote!( + #name #accessor, + ), + ) + } + }) + .collect(); + + let projection_ref_lt = format_ident!("__ProjectionRef"); + let (projection_ref_lt_def, projection_ref_lt_init) = if info.is_tuple_struct { + ( + quote!( + #vis struct #projection_ref_lt #generics_with_this_field_ref_lt( + #(#fields_ref_decl_lt)* + ::core::marker::PhantomData<&'__this #ident #ty_generics>, + ) #whr; + ), + quote!( + #projection_ref_lt( + #(#fields_ref_proj_lt)* + ::core::marker::PhantomData, + ) + ), + ) + } else { + ( + quote! { + #vis struct #projection_ref_lt #generics_with_this_field_ref_lt + #whr + { + #(#fields_ref_decl_lt)* + ___pin_phantom_data: ::core::marker::PhantomData<&'__this #ident #ty_generics>, + } + }, + quote! { + #projection_ref_lt { + #(#fields_ref_proj_lt)* + ___pin_phantom_data: ::core::marker::PhantomData, + } + }, + ) + }; + quote! { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on @@ -1477,6 +1587,12 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #[doc(hidden)] #projection_lt_def + // Allow `non_snake_case` since the same warning will be emitted on + // the struct definition. + #[allow(dead_code, non_snake_case)] + #[doc(hidden)] + #projection_ref_lt_def + impl #impl_generics #ident #ty_generics #whr { @@ -1515,6 +1631,24 @@ fn generate_projections(info: &StructInfo) -> TokenStream { f(#projection_lt_init) } + /// Pin-projects all fields of `Self` from a shared reference with proper lifetime. + /// + /// These fields are structurally pinned: + #(#[doc = #structurally_pinned_fields_docs])* + /// + /// These fields are **not** structurally pinned: + #(#[doc = #not_structurally_pinned_fields_docs])* + #[inline] + #vis fn with_project_ref<'__this, R>( + self: ::core::pin::Pin<&'__this Self>, + f: impl for #field_lt_ty_generics ::core::ops::FnOnce( + #projection_ref_lt #ty_generics_with_this_field_ref_lt + ) -> R, + ) -> R { + let #this = ::core::pin::Pin::get_ref(self); + f(#projection_ref_lt_init) + } + #(#accessors)* } } From 73cf61724648ad57dd5664a0e2915e20fd304650 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 9 Sep 2026 23:23:20 +0100 Subject: [PATCH 1577/2286] rust: pin-init: internal: pin_data: support existential lifetimes There are many cases where structs that have interior mutability, but they do not need the invariance over captured lifetimes as the lifetime is captured upon construction, and new data of that particular lifetime does not flow back into the struct. For these use cases, the same mechanism as pin-init self-reference may be used. Add support for existential lifetimes, introduced by having where clauses such as exists<'a>: 'b The lifetime `'a` above is minted similar to field lifetimes. Because `'a` is an erased lifetime living longer than `'b`, the only variance requirement that we have is that `'b` cannot be contravariant; that defense is fulfilled by adding `PhantomData<&'b ()>` so the struct is either covariant or invariant over `'b`. Acked-by: Benno Lossin Link: https://patch.msgid.link/20261008-dev-selfref-v2-20-e280b3c8fba5@garyguo.net/ Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 253 +++++++++++++++++++++++-- rust/pin-init/src/__internal.rs | 15 ++ 2 files changed, 252 insertions(+), 16 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index 8ab71544f67442..dbf9084bfd22ee 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -11,8 +11,9 @@ use syn::{ spanned::Spanned, visit::Visit, visit_mut::VisitMut, - Attribute, Field, Fields, GenericParam, Generics, Ident, Index, Item, ItemStruct, Lifetime, - LifetimeParam, Member, Meta, PathSegment, Token, Type, TypePath, + Attribute, Field, Fields, GenericArgument, GenericParam, Generics, Ident, Index, Item, + ItemStruct, Lifetime, LifetimeParam, Member, Meta, PathArguments, PathSegment, Token, Type, + TypeParamBound, TypePath, WhereClause, WherePredicate, }; use crate::{ @@ -183,6 +184,7 @@ impl Capture { dcx: &mut DiagCtxt, attrs: &mut Vec, bound_lifetimes: &BTreeSet<&Lifetime>, + exist_lifetimes: &BTreeSet, field_idx_map: &BTreeMap, ) -> Option<(BTreeSet, Variance)> { let attr = attrs.extract_single_attr(dcx, "uses")?; @@ -208,7 +210,10 @@ impl Capture { continue; } - if lt.ident != "_" && !field_idx_map.contains_key(<.ident) { + if lt.ident != "_" + && !exist_lifetimes.contains(lt) + && !field_idx_map.contains_key(<.ident) + { dcx.error(lt, format!("`{lt}` is not a field name")); continue; } @@ -240,11 +245,15 @@ struct StructInfo { field_idx_map: BTreeMap, is_tuple_struct: bool, self_referential: bool, + /// Existential lifetimes defined. + exist_lts: BTreeSet, /// Field lifetime genercis with outlive chain. field_lts_outlive_chain: Generics, /// Field lifetime genercis with outlive chain, with one chain for covariant fields and one /// chain for invariant fields. field_lts_split_variance_outlive_chain: Generics, + /// Existential lifetime with their outlives bounds. + exist_lts_generics: Generics, } pub(crate) fn expand_with_cfg( @@ -334,6 +343,12 @@ fn expand( let is_tuple_struct = matches!(struct_.fields, Fields::Unnamed(_)); + let exist_lts = if let Some(whr) = &mut struct_.generics.where_clause { + ExistLt::parse(dcx, whr) + } else { + BTreeSet::new() + }; + // Collect all bound lifetimes from generics. let bound_lifetimes: BTreeSet<&Lifetime> = struct_.generics.lifetimes().map(|x| &x.lifetime).collect(); @@ -347,6 +362,16 @@ fn expand( .filter_map(|(index, field)| Some((field.ident.clone()?, index))) .collect(); + for l in &exist_lts { + let lt = &l.lifetime; + if bound_lifetimes.contains(<) { + dcx.error( + lt, + format!("existential `{lt}` conflicts with struct generics"), + ); + } + } + // Keep track on fields being implicitly borrowed by being mentioned. let mut implicitly_borrowed = BTreeSet::new(); @@ -370,9 +395,14 @@ fn expand( }; // Parse `#[uses]` attribute. - let (mut captures, wildcard_variance) = - Capture::parse_list(dcx, &mut field.attrs, &bound_lifetimes, &field_idx_map) - .unwrap_or_default(); + let (mut captures, wildcard_variance) = Capture::parse_list( + dcx, + &mut field.attrs, + &bound_lifetimes, + &exist_lts, + &field_idx_map, + ) + .unwrap_or_default(); let mut generic_lt_captures = BTreeSet::new(); let mut generic_ty_captures = BTreeSet::new(); @@ -399,7 +429,7 @@ fn expand( return; } - if !field_idx_map.contains_key(<.ident) { + if !exist_lts.contains(lt) && !field_idx_map.contains_key(<.ident) { dcx.error( lt, format!("`{lt}` is neither a lifetime in generics nor a field name"), @@ -414,8 +444,10 @@ fn expand( }) .visit_type(&field.ty); - for capture in captures.iter() { - implicitly_borrowed.insert(capture.lifetime.ident.clone()); + for capture in captures.iter(){ + if !exist_lts.contains(&capture.lifetime){ + implicitly_borrowed.insert(capture.lifetime.ident.clone()); + } } GenericParam::maybe_type_params_visitor(|ident| { @@ -495,6 +527,9 @@ fn expand( let mut worklist = Vec::new(); for field in fields.iter() { for borrow in field.captures.iter() { + if exist_lts.contains(&borrow.lifetime) { + continue; + } if let Variance::Invariant = borrow.variance { let Some(borrow_idx) = fields .iter() @@ -510,6 +545,9 @@ fn expand( } while let Some(field) = worklist.pop() { for borrow in field.captures.iter() { + if exist_lts.contains(&borrow.lifetime) { + continue; + } let Some(borrow_idx) = fields .iter() .position(|f| f.member.as_ident() == borrow.lifetime.ident) @@ -581,6 +619,23 @@ fn expand( } } + let exist_lt_generics = Generics { + lt_token: Some(Default::default()), + params: exist_lts + .iter() + .map(|l| { + GenericParam::Lifetime(LifetimeParam { + attrs: Vec::new(), + lifetime: l.lifetime.clone(), + colon_token: Default::default(), + bounds: l.bounds.iter().cloned().collect(), + }) + }) + .collect(), + gt_token: Some(Default::default()), + where_clause: None, + }; + let mut field_lts_split_variance_outlive_chain = Generics { lt_token: Some(Default::default()), params: borrowed_covariant_fields @@ -642,6 +697,16 @@ fn expand( .push(parse_quote!(#ty: #field_lt)); } + for borrow in field.captures.iter().rev() { + let lt = &borrow.lifetime; + if exist_lts.contains(lt) { + field_lts_split_variance_outlive_chain + .make_where_clause() + .predicates + .push(parse_quote!(#lt: #field_lt)); + } + } + // If a field is invariant, then the invariance closure rule will make all borrowed fields // to be invariant, so they're already captured in `field_lts_split_variance_outlive_chain`. if borrowed.lt_variance != Variance::Covariant { @@ -681,8 +746,10 @@ fn expand( fields, field_idx_map, is_tuple_struct, + exist_lts, field_lts_outlive_chain, field_lts_split_variance_outlive_chain, + exist_lts_generics: exist_lt_generics, }; for field in &info.fields { @@ -745,6 +812,129 @@ fn is_phantom_pinned(ty: &Type) -> bool { } } +struct ExistLt { + lifetime: Lifetime, + bounds: Vec, +} + +impl std::borrow::Borrow for ExistLt { + fn borrow(&self) -> &Lifetime { + &self.lifetime + } +} + +impl PartialEq for ExistLt { + fn eq(&self, other: &Self) -> bool { + self.lifetime == other.lifetime + } +} + +impl Eq for ExistLt {} + +impl PartialOrd for ExistLt { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ExistLt { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.lifetime.cmp(&other.lifetime) + } +} + +impl ExistLt { + fn parse(dcx: &mut DiagCtxt, whr: &mut WhereClause) -> BTreeSet { + fn parse_one( + dcx: &mut DiagCtxt, + pred: &WherePredicate, + result: &mut BTreeSet, + ) -> bool { + let WherePredicate::Type(pred) = &pred else { + return false; + }; + let Type::Path(path) = &pred.bounded_ty else { + return false; + }; + + if path.qself.is_some() + || path.path.leading_colon.is_some() + || path.path.segments.len() != 1 + { + return false; + } + let path_seg = &path.path.segments[0]; + + if path_seg.ident != "exists" { + return false; + }; + + let PathArguments::AngleBracketed(p) = &path_seg.arguments else { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + }; + + if p.args.len() != 1 { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + } + + let GenericArgument::Lifetime(lt) = &p.args[0] else { + dcx.error( + path_seg, + "existential clauses require a single lifetime, e.g. `exists<'a>`", + ); + return true; + }; + + if result.contains(lt) { + dcx.error( + lt, + format!("an existential clause for `{lt}` already exists"), + ); + return true; + } + + let mut bounds = Vec::new(); + for bound in pred.bounds.iter() { + let TypeParamBound::Lifetime(lt) = bound else { + dcx.error(bound, "only lifetime can appear in existential clauses"); + return true; + }; + bounds.push(lt.clone()); + } + if bounds.is_empty() { + dcx.error( + pred.colon_token, + "existential clauses require at least one outlive bounds", + ); + return true; + } + + result.insert(ExistLt { + lifetime: lt.clone(), + bounds: bounds.clone(), + }); + + true + } + + let mut result = BTreeSet::new(); + whr.predicates = std::mem::take(&mut whr.predicates) + .into_pairs() + .filter(|p| !parse_one(dcx, p.value(), &mut result)) + .collect(); + + result + } +} + fn generate_struct_def(info: &StructInfo) -> TokenStream { let ItemStruct { attrs, @@ -783,12 +973,23 @@ fn generate_struct_def(info: &StructInfo) -> TokenStream { // implementation and thus may be used inside `Erase`. ty = quote!((#ty,)); + let mut exist_lt_phantoms = Vec::new(); for borrow in field.captures.iter().rev() { let lt = &borrow.lifetime; ty = quote!(for<#lt> fn(&#lt()) -> #ty); + + if let Some(exist_lt) = info.exist_lts.get(lt) { + for bound in &exist_lt.bounds { + exist_lt_phantoms.push(quote!(::pin_init::__internal::ExistLt<#bound>)); + } + } } ty = quote!(::pin_init::__internal::Erase<#ty>); + + if !exist_lt_phantoms.is_empty() { + ty = quote!(::pin_init::__internal::WithPhantom<#ty, (#(#exist_lt_phantoms,)*)>); + } }; if let Some(borrowed) = &field.borrowed { @@ -998,8 +1199,11 @@ fn generate_drop_order_check(dcx: &mut DiagCtxt, info: &StructInfo) -> TokenStre // with known lifetime bounds as bounds on the function, and asks Rust to *prove* that the types // are wellformed, given the bounds that we understand. - let generics_with_field_lt = - CombinedGenerics(vec![&info.field_lts_split_variance_outlive_chain, generics]); + let generics_with_field_lt = CombinedGenerics(vec![ + &info.exist_lts_generics, + &info.field_lts_split_variance_outlive_chain, + generics, + ]); let (_, ty_generics, _) = generics.split_for_impl(); let (impl_generics_with_field_lt, _, whr_with_field_lt) = @@ -1138,20 +1342,25 @@ fn generate_projections(info: &StructInfo) -> TokenStream { // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be // used with `for`. - let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]); + let field_lts = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + ]); let generics_with_this_lt = CombinedGenerics(vec![&this_lt_generics, generics]); let generics_with_this_field_lt = CombinedGenerics(vec![ &this_lt_generics, + &info.exist_lts_generics, &info.field_lts_outlive_chain, generics, ]); let generics_with_this_field_ref_lt = CombinedGenerics(vec![ &this_lt_generics, + &info.exist_lts_generics, &info.field_lts_split_variance_outlive_chain, generics, ]); - let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); + let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, ty_generics_with_this_lt, _) = generics_with_this_lt.split_for_impl(); let (_, ty_generics_with_this_field_lt, _) = generics_with_this_field_lt.split_for_impl(); let (_, ty_generics_with_this_field_ref_lt, _) = @@ -1665,10 +1874,19 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { // Wrap in `CombinedGenerics` because it's ty generics will always output `<>`, so it can be // used with `for`. let field_lts = CombinedGenerics(vec![&info.field_lts_outlive_chain]); - let generics_with_field_lt = CombinedGenerics(vec![&info.field_lts_outlive_chain, generics]); + let field_exist_lts = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + ]); + let generics_with_field_lt = CombinedGenerics(vec![ + &info.field_lts_outlive_chain, + &info.exist_lts_generics, + generics, + ]); let (impl_generics, ty_generics, whr) = generics.split_for_impl(); let (_, field_lt_ty_generics, _) = field_lts.split_for_impl(); + let (_, field_exist_lt_ty_generics, _) = field_exist_lts.split_for_impl(); let (impl_generics_with_lt, ty_generics_with_field_lt, whr_with_field_lt) = generics_with_field_lt.split_for_impl(); @@ -1778,6 +1996,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { }) .collect::(); + let exist_lt_generics_params = info.exist_lts_generics.params.iter(); quote! { // We declare this struct which will host all of the projection function for our type. #[doc(hidden)] @@ -1833,7 +2052,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { { /// Type inference helper function. #[inline(always)] - #vis fn __make_closure<__F, __E>(self, f: __F) -> __F + #vis fn __make_closure<#(#exist_lt_generics_params,)* __F, __E>(self, f: __F) -> __F where __F: for #field_lt_ty_generics ::core::ops::FnOnce( *mut #struct_name #ty_generics, @@ -1844,7 +2063,9 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { } #[inline(always)] - #vis fn __with_lt #field_lts(self) -> __PinDataLt #ty_generics_with_field_lt { + #vis fn __with_lt #field_exist_lt_ty_generics(self) + -> __PinDataLt #ty_generics_with_field_lt + { // Generate a zeroed to avoid naming all fields. // SAFETY: `__PinDataLt` only contains phantom fields. unsafe { ::core::mem::zeroed() } diff --git a/rust/pin-init/src/__internal.rs b/rust/pin-init/src/__internal.rs index 9f0f5a34e630c2..04abb7590aa592 100644 --- a/rust/pin-init/src/__internal.rs +++ b/rust/pin-init/src/__internal.rs @@ -725,3 +725,18 @@ impl NotVisible { Self(PhantomData) } } + +/// Marker type to capture outlive bounds coming from existential lifetimes. +pub struct ExistLt<'a>(PhantomData<&'a ()>); + +// Dummy `Drop`, same reason as `Borrowed`. +impl Drop for ExistLt<'_> { + #[inline(always)] + fn drop(&mut self) {} +} + +/// Type that allows additional `PhantomData` to be attached. +/// +/// This allows changing variance of types without introducing additional fields. +#[repr(transparent)] +pub struct WithPhantom(PhantomData

, T); From 4608d8d060bff286b62fdf64f708e20d5b1f7576 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Sun, 4 Oct 2026 21:42:11 +0900 Subject: [PATCH 1578/2286] MAINTAINERS: add entry for the Rust `bug` module Add a MAINTAINERS entry for the Rust `bug` module, which currently provides the `warn_on!` macro. Suggested-by: Miguel Ojeda Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20261004124211.381401-1-tomo@flapping.org Signed-off-by: Miguel Ojeda --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index db263cefb46bbf..ffdf8aa430be5c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23990,6 +23990,15 @@ L: rust-for-linux@vger.kernel.org S: Maintained F: rust/kernel/bitfield.rs +RUST [BUG] +M: FUJITA Tomonori +L: rust-for-linux@vger.kernel.org +S: Maintained +F: rust/helpers/bug.c +F: rust/kernel/bug.rs +F: rust/kernel/generated_arch_reachable_asm.rs.S +F: rust/kernel/generated_arch_warn_asm.rs.S + RUST [INTEROP] M: Joel Fernandes M: Alexandre Courbot From b7218f1996f8491dbe28632b70f444cb73f554df Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sun, 11 Oct 2026 14:32:18 +0100 Subject: [PATCH 1579/2286] rust: pin-init: internal: suppress `explicit_outlives_requirements` `#[pin_data]` inserts outlive bounds between lifetimes to allow closure callbacks to rely on their relationship. In some cases, these relations are implied by fields, and Rust generates `explicit_outlives_requirements` allow-by-default lint, which is turned out by kernel as part of rust-2018-idioms lint group. As `#[pin_data]` is a proc macro and it cannot know if the bounds are already implied, suppress such lints for generated code. Link: https://patch.msgid.link/20261011133904.600785-1-gary@kernel.org Signed-off-by: Gary Guo --- rust/pin-init/internal/src/pin_data.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/pin-init/internal/src/pin_data.rs b/rust/pin-init/internal/src/pin_data.rs index dbf9084bfd22ee..08e11128d353db 100644 --- a/rust/pin-init/internal/src/pin_data.rs +++ b/rust/pin-init/internal/src/pin_data.rs @@ -1792,13 +1792,13 @@ fn generate_projections(info: &StructInfo) -> TokenStream { #[doc = #docs] // Allow `non_snake_case` since the same warning will be emitted on // the struct definition. - #[allow(dead_code, non_snake_case)] + #[allow(dead_code, non_snake_case, explicit_outlives_requirements)] #[doc(hidden)] #projection_lt_def // Allow `non_snake_case` since the same warning will be emitted on // the struct definition. - #[allow(dead_code, non_snake_case)] + #[allow(dead_code, non_snake_case, explicit_outlives_requirements)] #[doc(hidden)] #projection_ref_lt_def @@ -2000,7 +2000,7 @@ fn generate_the_pin_data(info: &StructInfo) -> TokenStream { quote! { // We declare this struct which will host all of the projection function for our type. #[doc(hidden)] - #[allow(non_snake_case)] + #[allow(non_snake_case, explicit_outlives_requirements)] #vis struct __PinDataLt #generics_with_field_lt #whr_with_field_lt { From a4b90518dd01f85e1e4bf7e9361396b2eded1f7e Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Fri, 9 Oct 2026 14:44:43 -0500 Subject: [PATCH 1580/2286] cpufreq: mediatek: Enter system sleep at the suspend OPP cpufreq_suspend() stops the governors at the start of system suspend, and this driver has no .suspend callback, so each cluster sleeps at whatever OPP it was last left at. On an MT8173 tablet (Amazon Fire HD 10, 2017), entering suspend-to-RAM with both clusters at their highest OPP measured 63.5 mA. With this change and the lowest OPP marked, the same entry measured 52.0 mA. Use the generic suspend-frequency support, as cpufreq-dt does. Tables without an opp-suspend entry leave suspend_freq at 0, which makes cpufreq_generic_suspend() a no-op, so they are unaffected. Assisted-by: LLM Signed-off-by: Ryan Brue Signed-off-by: Viresh Kumar --- drivers/cpufreq/mediatek-cpufreq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/cpufreq/mediatek-cpufreq.c b/drivers/cpufreq/mediatek-cpufreq.c index 052ca7cd2f4fd8..f9b1eca176eb80 100644 --- a/drivers/cpufreq/mediatek-cpufreq.c +++ b/drivers/cpufreq/mediatek-cpufreq.c @@ -603,6 +603,7 @@ static int mtk_cpufreq_init(struct cpufreq_policy *policy) policy->freq_table = freq_table; policy->driver_data = info; policy->clk = info->cpu_clk; + policy->suspend_freq = dev_pm_opp_get_suspend_opp_freq(info->cpu_dev) / 1000; return 0; } @@ -624,6 +625,7 @@ static struct cpufreq_driver mtk_cpufreq_driver = { .init = mtk_cpufreq_init, .exit = mtk_cpufreq_exit, .register_em = cpufreq_register_em_with_opp, + .suspend = cpufreq_generic_suspend, .name = "mtk-cpufreq", }; From 71245acb8c937382fc31806572314d36667b17f6 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Fri, 9 Oct 2026 14:44:44 -0500 Subject: [PATCH 1581/2286] arm64: dts: mediatek: mt8173: Mark the lowest CPU OPPs for suspend Both clusters otherwise enter system sleep at whatever OPP the governor last chose. On the Amazon Fire HD 10 (2017), entering suspend-to-RAM with both clusters at their highest OPP measured 63.5 mA, against 52.0 mA when they suspend at 507 MHz. Mark the 507 MHz OPP of each cluster, the lowest in both tables, as the one to suspend at. mediatek-cpufreq uses it for system sleep, and a kernel without that support ignores it. Assisted-by: LLM Signed-off-by: Ryan Brue Signed-off-by: Viresh Kumar --- arch/arm64/boot/dts/mediatek/mt8173.dtsi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/boot/dts/mediatek/mt8173.dtsi b/arch/arm64/boot/dts/mediatek/mt8173.dtsi index 78c2ccd5be132d..7f1af15cb9344b 100644 --- a/arch/arm64/boot/dts/mediatek/mt8173.dtsi +++ b/arch/arm64/boot/dts/mediatek/mt8173.dtsi @@ -56,6 +56,7 @@ opp-507000000 { opp-hz = /bits/ 64 <507000000>; opp-microvolt = <859000>; + opp-suspend; }; opp-702000000 { opp-hz = /bits/ 64 <702000000>; @@ -93,6 +94,7 @@ opp-507000000 { opp-hz = /bits/ 64 <507000000>; opp-microvolt = <828000>; + opp-suspend; }; opp-702000000 { opp-hz = /bits/ 64 <702000000>; From 3a4e0ee57c5563aa997ea6d6b887a915e035e8e0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 7 Oct 2026 14:30:01 +0000 Subject: [PATCH 1582/2286] rust: sync: move lockdep types to rust/kernel/sync/lockdep.rs The lockdep types are currently stored directly in rust/kernel/sync.rs, but there are starting to be too many of them to keep them in that file. Thus, move them to a submodule. The new module is listed under LOCKING PRIMITIVES in the MAINTAINERS file. For commonly used lockdep logic it's useful to keep re-exports in kernel::sync, and this also avoids the need to update any users. Reviewed-by: Gary Guo Reviewed-by: Boqun Feng Reviewed-by: Carlos Llamas Acked-by: Miguel Ojeda Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20261007-pr-ratelimited-v6-1-f14accd4ea3b@google.com [ Used the "kernel vertical" imports style. - Miguel ] Signed-off-by: Miguel Ojeda --- MAINTAINERS | 1 + rust/kernel/sync.rs | 138 ++--------------------------------- rust/kernel/sync/lockdep.rs | 139 ++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 133 deletions(-) create mode 100644 rust/kernel/sync/lockdep.rs diff --git a/MAINTAINERS b/MAINTAINERS index ffdf8aa430be5c..1e7f1329c34884 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15284,6 +15284,7 @@ F: rust/helpers/mutex.c F: rust/helpers/spinlock.c F: rust/kernel/sync/lock.rs F: rust/kernel/sync/lock/ +F: rust/kernel/sync/lockdep.rs F: rust/kernel/sync/locked_by.rs X: kernel/locking/locktorture.c diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index 3e55de3b163659..ee372c048a8a09 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -5,10 +5,6 @@ //! This module contains the kernel APIs related to synchronisation that have been ported or //! wrapped for usage by Rust code in the kernel. -use crate::prelude::*; -use crate::types::Opaque; -use pin_init; - mod arc; pub mod aref; pub mod atomic; @@ -16,6 +12,7 @@ pub mod barrier; pub mod completion; mod condvar; pub mod lock; +pub mod lockdep; mod locked_by; pub mod poll; pub mod rcu; @@ -36,136 +33,11 @@ pub use lock::spinlock::{ SpinLockIrq, SpinLockIrqGuard, // }; +pub use lockdep::{ + static_lock_class, + LockClassKey, // +}; pub use locked_by::LockedBy; pub use refcount::Refcount; pub use set_once::SetOnce; pub use srcu::Srcu; - -/// Represents a lockdep class. -/// -/// Wraps the kernel's `struct lock_class_key`. -#[repr(transparent)] -#[pin_data(PinnedDrop)] -pub struct LockClassKey { - #[pin] - inner: Opaque, -} - -// SAFETY: Unregistering a lock class key from a different thread than where it was registered is -// allowed. -unsafe impl Send for LockClassKey {} - -// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and -// provides its own synchronization. -unsafe impl Sync for LockClassKey {} - -impl LockClassKey { - /// Initializes a statically allocated lock class key. - /// - /// This is usually used indirectly through the [`static_lock_class!`] macro. See its - /// documentation for more information. - /// - /// # Safety - /// - /// * Before using the returned value, it must be pinned in a static memory location. - /// * The destructor must never run on the returned `LockClassKey`. - pub const unsafe fn new_static() -> Self { - LockClassKey { - inner: Opaque::uninit(), - } - } - - /// Initializes a dynamically allocated lock class key. - /// - /// In the common case of using a statically allocated lock class key, the - /// [`static_lock_class!`] macro should be used instead. - /// - /// # Examples - /// - /// ``` - /// use kernel::alloc::KBox; - /// use kernel::types::ForeignOwnable; - /// use kernel::sync::{LockClassKey, SpinLock}; - /// use pin_init::stack_pin_init; - /// - /// let key = KBox::pin_init(LockClassKey::new_dynamic(), GFP_KERNEL)?; - /// let key_ptr = key.into_foreign(); - /// - /// { - /// stack_pin_init!(let num: SpinLock = SpinLock::new( - /// 0, - /// c"my_spinlock", - /// // SAFETY: `key_ptr` is returned by the above `into_foreign()`, whose - /// // `from_foreign()` has not yet been called. - /// unsafe { > as ForeignOwnable>::borrow(key_ptr) } - /// )); - /// } - /// - /// // SAFETY: We dropped `num`, the only use of the key, so the result of the previous - /// // `borrow` has also been dropped. Thus, it's safe to use from_foreign. - /// unsafe { drop(> as ForeignOwnable>::from_foreign(key_ptr)) }; - /// # Ok::<(), Error>(()) - /// ``` - pub fn new_dynamic() -> impl PinInit { - pin_init!(Self { - // SAFETY: lockdep_register_key expects an uninitialized block of memory - inner <- Opaque::ffi_init(|slot| unsafe { bindings::lockdep_register_key(slot) }) - }) - } - - /// Returns a raw pointer to the inner C struct. - /// - /// It is up to the caller to use the raw pointer correctly. - pub fn as_ptr(&self) -> *mut bindings::lock_class_key { - self.inner.get() - } -} - -#[pinned_drop] -impl PinnedDrop for LockClassKey { - fn drop(self: Pin<&mut Self>) { - // SAFETY: `self.as_ptr()` was registered with lockdep and `self` is pinned, so the address - // hasn't changed. Thus, it's safe to pass it to unregister. - unsafe { bindings::lockdep_unregister_key(self.as_ptr()) } - } -} - -/// Defines a new static lock class and returns a pointer to it. -/// -/// # Examples -/// -/// ``` -/// use kernel::sync::{static_lock_class, Arc, SpinLock}; -/// -/// fn new_locked_int() -> Result>> { -/// Arc::pin_init(SpinLock::new( -/// 42, -/// c"new_locked_int", -/// static_lock_class!(), -/// ), GFP_KERNEL) -/// } -/// ``` -#[macro_export] -macro_rules! static_lock_class { - () => {{ - static CLASS: $crate::sync::LockClassKey = - // SAFETY: The returned `LockClassKey` is stored in static memory and we pin it. Drop - // never runs on a static global. - unsafe { $crate::sync::LockClassKey::new_static() }; - $crate::prelude::Pin::static_ref(&CLASS) - }}; -} -pub use static_lock_class; - -/// Returns the given string, if one is provided, otherwise generates one based on the source code -/// location. -#[doc(hidden)] -#[macro_export] -macro_rules! optional_name { - () => { - $crate::c_str!(::core::concat!(::core::file!(), ":", ::core::line!())) - }; - ($name:literal) => { - $crate::c_str!($name) - }; -} diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs new file mode 100644 index 00000000000000..784821cc2a39df --- /dev/null +++ b/rust/kernel/sync/lockdep.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Utilities related to lockdep. +//! +//! C headers: [`include/linux/lockdep.h`](srctree/include/linux/lockdep.h) + +use crate::{ + prelude::*, + types::Opaque, // +}; + +/// Represents a lockdep class. +/// +/// Wraps the kernel's `struct lock_class_key`. +#[repr(transparent)] +#[pin_data(PinnedDrop)] +pub struct LockClassKey { + #[pin] + inner: Opaque, +} + +// SAFETY: Unregistering a lock class key from a different thread than where it was registered is +// allowed. +unsafe impl Send for LockClassKey {} + +// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and +// provides its own synchronization. +unsafe impl Sync for LockClassKey {} + +impl LockClassKey { + /// Initializes a statically allocated lock class key. + /// + /// This is usually used indirectly through the [`static_lock_class!`] macro. See its + /// documentation for more information. + /// + /// # Safety + /// + /// * Before using the returned value, it must be pinned in a static memory location. + /// * The destructor must never run on the returned `LockClassKey`. + pub const unsafe fn new_static() -> Self { + LockClassKey { + inner: Opaque::uninit(), + } + } + + /// Initializes a dynamically allocated lock class key. + /// + /// In the common case of using a statically allocated lock class key, the + /// [`static_lock_class!`] macro should be used instead. + /// + /// # Examples + /// + /// ``` + /// use kernel::alloc::KBox; + /// use kernel::types::ForeignOwnable; + /// use kernel::sync::{LockClassKey, SpinLock}; + /// use pin_init::stack_pin_init; + /// + /// let key = KBox::pin_init(LockClassKey::new_dynamic(), GFP_KERNEL)?; + /// let key_ptr = key.into_foreign(); + /// + /// { + /// stack_pin_init!(let num: SpinLock = SpinLock::new( + /// 0, + /// c"my_spinlock", + /// // SAFETY: `key_ptr` is returned by the above `into_foreign()`, whose + /// // `from_foreign()` has not yet been called. + /// unsafe { > as ForeignOwnable>::borrow(key_ptr) } + /// )); + /// } + /// + /// // SAFETY: We dropped `num`, the only use of the key, so the result of the previous + /// // `borrow` has also been dropped. Thus, it's safe to use from_foreign. + /// unsafe { drop(> as ForeignOwnable>::from_foreign(key_ptr)) }; + /// # Ok::<(), Error>(()) + /// ``` + pub fn new_dynamic() -> impl PinInit { + pin_init!(Self { + // SAFETY: lockdep_register_key expects an uninitialized block of memory + inner <- Opaque::ffi_init(|slot| unsafe { bindings::lockdep_register_key(slot) }) + }) + } + + /// Returns a raw pointer to the inner C struct. + /// + /// It is up to the caller to use the raw pointer correctly. + pub fn as_ptr(&self) -> *mut bindings::lock_class_key { + self.inner.get() + } +} + +#[pinned_drop] +impl PinnedDrop for LockClassKey { + fn drop(self: Pin<&mut Self>) { + // SAFETY: `self.as_ptr()` was registered with lockdep and `self` is pinned, so the address + // hasn't changed. Thus, it's safe to pass it to unregister. + unsafe { bindings::lockdep_unregister_key(self.as_ptr()) } + } +} + +/// Defines a new static lock class and returns a pointer to it. +/// +/// # Examples +/// +/// ``` +/// use kernel::sync::{static_lock_class, Arc, SpinLock}; +/// +/// fn new_locked_int() -> Result>> { +/// Arc::pin_init(SpinLock::new( +/// 42, +/// c"new_locked_int", +/// static_lock_class!(), +/// ), GFP_KERNEL) +/// } +/// ``` +#[macro_export] +macro_rules! static_lock_class { + () => {{ + static CLASS: $crate::sync::LockClassKey = + // SAFETY: The returned `LockClassKey` is stored in static memory and we pin it. Drop + // never runs on a static global. + unsafe { $crate::sync::LockClassKey::new_static() }; + $crate::prelude::Pin::static_ref(&CLASS) + }}; +} +pub use static_lock_class; + +/// Returns the given string, if one is provided, otherwise generates one based on the source code +/// location. +#[doc(hidden)] +#[macro_export] +macro_rules! optional_name { + () => { + $crate::c_str!(::core::concat!(::core::file!(), ":", ::core::line!())) + }; + ($name:literal) => { + $crate::c_str!($name) + }; +} From 107a25d62c40ef47b2284ee37fdefc1d3f5d013b Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 7 Oct 2026 14:30:02 +0000 Subject: [PATCH 1583/2286] rust: sync: add const constructor for raw_spinlock_t The abstractions for pr_*_ratelimited! need to construct a global `struct ratelimit_state`, which contains a `raw_spinlock_t` field. Thus, add a const constructor for the `raw_spinlock_t` type. The SPINLOCK_OWNER_INIT constant isn't mirrored via a const helper because bindgen generates a 'static mut' instead of a constant from the pointer constant., The __ARCH_SPIN_LOCK_UNLOCKED constant cannot be translated by bindgen because it's a define for a struct without type annotations, so it's explicitly declared in Rust. Reviewed-by: Boqun Feng Reviewed-by: Carlos Llamas Acked-by: Miguel Ojeda Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20261007-pr-ratelimited-v6-2-f14accd4ea3b@google.com Signed-off-by: Miguel Ojeda --- include/linux/spinlock_types_raw.h | 4 ++++ rust/bindings/lib.rs | 25 +++++++++++++++++++++++++ rust/kernel/sync/lock/spinlock.rs | 29 +++++++++++++++++++++++++++++ rust/kernel/sync/lockdep.rs | 22 ++++++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/include/linux/spinlock_types_raw.h b/include/linux/spinlock_types_raw.h index e5644ab2161f8c..942c229c90bba6 100644 --- a/include/linux/spinlock_types_raw.h +++ b/include/linux/spinlock_types_raw.h @@ -11,6 +11,10 @@ #include +/* + * Keep in sync with rust/kernel/sync/lock/spinlock.rs + */ + context_lock_struct(raw_spinlock) { arch_spinlock_t raw_lock; #ifdef CONFIG_DEBUG_SPINLOCK diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index 98313fc310242b..f95295f1b4ab96 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -88,3 +88,28 @@ pub const compat_ptr_ioctl: Option< // The generated `HZ` is therefore `__USER_HZ` from // `include/uapi/asm-generic/param.h`, not `CONFIG_HZ`. pub const HZ: u32 = CONFIG_HZ; + +// Explicitly list architectures where this logic is checked correct. +#[cfg(any( + CONFIG_ARM, + CONFIG_ARM64, + CONFIG_LOONGARCH, + CONFIG_PPC, + CONFIG_RISCV, + CONFIG_S390, + CONFIG_UML_X86, + CONFIG_X86, +))] +pub const __ARCH_SPIN_LOCK_UNLOCKED: arch_spinlock_t = { + // SAFETY: The `arch_spinlock_t` type can be zeroed. + #[allow(unused_mut)] + let mut lock: arch_spinlock_t = unsafe { core::mem::zeroed() }; + + #[cfg(not(CONFIG_SMP))] + #[cfg(CONFIG_DEBUG_SPINLOCK)] + { + lock.slock = 1; + } + + lock +}; diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs index aafc80125f5911..037fa63ebcac86 100644 --- a/rust/kernel/sync/lock/spinlock.rs +++ b/rust/kernel/sync/lock/spinlock.rs @@ -463,3 +463,32 @@ mod tests { Ok(()) } } + +/// Helper for creating a raw unlocked `bindings::raw_spinlock_t`. +/// +/// For use in statics containing raw spinlocks. +#[doc(alias("__SPIN_LOCK_UNLOCKED", "DEFINE_SPINLOCK"))] +#[expect(dead_code)] +pub(crate) const fn raw_spin_lock_unlocked(name: &'static CStr) -> bindings::raw_spinlock_t { + // Silence unused variable warnings. + #[cfg(not(CONFIG_DEBUG_LOCK_ALLOC))] + let _ = name; + + bindings::raw_spinlock_t { + raw_lock: bindings::__ARCH_SPIN_LOCK_UNLOCKED, + + #[cfg(CONFIG_DEBUG_SPINLOCK)] + magic: bindings::SPINLOCK_MAGIC, + #[cfg(CONFIG_DEBUG_SPINLOCK)] + owner_cpu: u32::MAX, + #[cfg(CONFIG_DEBUG_SPINLOCK)] + owner: usize::MAX as *mut c_void, + + #[cfg(CONFIG_DEBUG_LOCK_ALLOC)] + dep_map: kernel::sync::lockdep::raw_lockdep_map( + name, + kernel::sync::lockdep::LD_WAIT_SPIN, + kernel::sync::lockdep::LD_WAIT_INV, + ), + } +} diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs index 784821cc2a39df..d9222be0fb29e3 100644 --- a/rust/kernel/sync/lockdep.rs +++ b/rust/kernel/sync/lockdep.rs @@ -137,3 +137,25 @@ macro_rules! optional_name { $crate::c_str!($name) }; } + +/// Not checked, catch all. +pub const LD_WAIT_INV: u8 = bindings::lockdep_wait_type_LD_WAIT_INV as u8; +/// Spin loops, `raw_spinlock_t` etc +pub const LD_WAIT_SPIN: u8 = bindings::lockdep_wait_type_LD_WAIT_SPIN as u8; + +/// Helper for declaring a raw `struct lockdep_map` for locks in statics. +/// +/// It's up to the caller to use the returned `struct lockdep_map` correctly. +#[cfg(CONFIG_DEBUG_LOCK_ALLOC)] +pub(crate) const fn raw_lockdep_map( + name: &'static CStr, + wait_type_inner: u8, + wait_type_outer: u8, +) -> bindings::lockdep_map { + // SAFETY: All zeros is valid for this type. + let mut map: bindings::lockdep_map = unsafe { core::mem::zeroed() }; + map.name = kernel::str::as_char_ptr_in_const_context(name); + map.wait_type_inner = wait_type_inner; + map.wait_type_outer = wait_type_outer; + map +} From ecdfcc7f2974c368fb932b16dd3c2deb4a8cbdb0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 7 Oct 2026 14:30:03 +0000 Subject: [PATCH 1584/2286] rust: add pr_*_ratelimited! macros for printing Printing can be very expensive if it occurs often, so printing that can be triggered by userspace should be rate limited. For this purpose, add a Rust wrapper around `struct ratelimit_state` and use it in the new macros. The new files will fall under the catch-all RUST maintainers entry. Tested-by: Alvin Sun Reviewed-by: Carlos Llamas Link: https://github.com/Rust-for-Linux/linux/issues/122 Acked-by: Miguel Ojeda Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20261007-pr-ratelimited-v6-3-f14accd4ea3b@google.com [ Removed unused link reference definitions. On the `static`, allowed the `non_upper_case_globals` lint so that `kernel` users (and users providing their own name) do not warn. Reworded title for typo. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/helpers/helpers.c | 1 + rust/helpers/ratelimit.c | 14 +++ rust/kernel/lib.rs | 1 + rust/kernel/prelude.rs | 8 ++ rust/kernel/ratelimit.rs | 201 ++++++++++++++++++++++++++++++ rust/kernel/sync/lock/spinlock.rs | 1 - 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 rust/helpers/ratelimit.c create mode 100644 rust/kernel/ratelimit.rs diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 08374b163774b9..9bb850294a2f3e 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -86,6 +86,7 @@ #include "processor.c" #include "property.c" #include "pwm.c" +#include "ratelimit.c" #include "rbtree.c" #include "rcu.c" #include "refcount.c" diff --git a/rust/helpers/ratelimit.c b/rust/helpers/ratelimit.c new file mode 100644 index 00000000000000..e5052f568b810d --- /dev/null +++ b/rust/helpers/ratelimit.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +__rust_helper void rust_helper_ratelimit_state_init(struct ratelimit_state *rs, + int interval, int burst) +{ + ratelimit_state_init(rs, interval, burst); +} + +__rust_helper void rust_helper_ratelimit_state_exit(struct ratelimit_state *rs) +{ + ratelimit_state_exit(rs); +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 7225abc6408447..a4747f75b16326 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -120,6 +120,7 @@ pub mod processor; pub mod ptr; #[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)] pub mod pwm; +pub mod ratelimit; pub mod rbtree; pub mod regulator; pub mod revocable; diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index ca396f1f78a6ae..bcaa232205becd 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -107,13 +107,21 @@ pub use super::{ }, init::InPlaceInit, pr_alert, + pr_alert_ratelimited, pr_crit, + pr_crit_ratelimited, pr_debug, + pr_debug_ratelimited, pr_emerg, + pr_emerg_ratelimited, pr_err, + pr_err_ratelimited, pr_info, + pr_info_ratelimited, pr_notice, + pr_notice_ratelimited, pr_warn, + pr_warn_ratelimited, str::CStrExt as _, try_init, try_pin_init, diff --git a/rust/kernel/ratelimit.rs b/rust/kernel/ratelimit.rs new file mode 100644 index 00000000000000..2c54099cf11e43 --- /dev/null +++ b/rust/kernel/ratelimit.rs @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Rate limiting support. +//! +//! C header: [`include/linux/ratelimit.h`](srctree/include/linux/ratelimit.h) + +use crate::{ + bindings, + prelude::*, + types::Opaque, // +}; + +/// Defines a `static` containing a [`Ratelimit`]. +#[macro_export] +macro_rules! ratelimit_state_init { + ($name:ident, $interval:expr, $burst:expr $(,)?) => { + #[allow(non_upper_case_globals)] + static $name: $crate::ratelimit::Ratelimit = { + let name = $crate::c_str!(::core::stringify!($name)); + let interval = $interval; + let burst = $burst; + // SAFETY: This will be stored in static memory. + unsafe { $crate::ratelimit::Ratelimit::new_static(name, interval, burst) } + }; + }; +} +pub use ratelimit_state_init; + +/// Rate limiter state. +/// +/// # Invariants +/// +/// The `inner` field contains an initialized `struct ratelimit_state`. +#[pin_data(PinnedDrop)] +#[repr(transparent)] +pub struct Ratelimit { + #[pin] + inner: Opaque, +} + +// SAFETY: `Ratelimit` is safe to be sent to any task. +unsafe impl Send for Ratelimit {} + +// SAFETY: `Ratelimit` is safe to be accessed concurrently as it is protected by an internal +// spinlock. +unsafe impl Sync for Ratelimit {} + +impl Ratelimit { + /// Constructs a [`Ratelimit`] with the specified configuration. + /// + /// If `interval` is zero, then no rate limit is applied. + #[inline] + pub fn new(interval: i32, burst: i32) -> impl PinInit { + // INVARIANT: This creates a `Ratelimit` containing an initialized `struct ratelimit_state` + pin_init!(Self { + inner <- Opaque::ffi_init(|slot: *mut bindings::ratelimit_state| { + // SAFETY: `slot` is a valid pointer to an uninitialized `struct ratelimit_state`. + // The memory is pinned so it remains valid until `ratelimit_state_exit` is called. + unsafe { bindings::ratelimit_state_init(slot, interval, burst) }; + }), + }) + } + + /// Constructs a [`Ratelimit`] with the default configuration. + #[inline] + pub fn new_default() -> impl PinInit { + Ratelimit::new(Ratelimit::DEFAULT_INTERVAL, Ratelimit::DEFAULT_BURST) + } + + /// Constructs a [`Ratelimit`] with the specified configuration. + /// + /// The name will be used for the lockdep name of the internal spinlock. See [`Self::new`] for + /// the meaning of `interval` and `burst`. + /// + /// # Safety + /// + /// The resulting value must be stored in static memory. + #[inline] + pub const unsafe fn new_static(name: &'static CStr, interval: i32, burst: i32) -> Self { + // INVARIANT: This creates a `Ratelimit` containing an initialized `struct ratelimit_state`. + Self { + inner: Opaque::new(bindings::ratelimit_state { + lock: kernel::sync::lock::spinlock::raw_spin_lock_unlocked(name), + interval, + burst, + ..pin_init::zeroed() + }), + } + } + + /// The default interval used for rate limiting. + pub const DEFAULT_INTERVAL: i32 = bindings::DEFAULT_RATELIMIT_INTERVAL as i32; + + /// The default burst size. + pub const DEFAULT_BURST: i32 = bindings::DEFAULT_RATELIMIT_BURST as i32; + + /// Check if an action should be rate-limited. + /// + /// Returns [`true`] if the action is allowed, and [`false`] if it should be suppressed. + #[inline] + pub fn ratelimit(&self, func: &CStr) -> bool { + // SAFETY: `self.inner.get()` is a valid pointer to a `struct ratelimit_state`. + // The lifetime of `func` ensures the pointer remains valid for the duration of the call. + // The C function `___ratelimit` handles its own internal locking, so it is safe to call + // concurrently. + unsafe { bindings::___ratelimit(self.inner.get(), func.as_char_ptr()) != 0 } + } +} + +#[pinned_drop] +impl PinnedDrop for Ratelimit { + #[inline] + fn drop(self: Pin<&mut Self>) { + // SAFETY: By the type invariants, this struct contains an initialized `struct + // ratelimit_state`. + unsafe { bindings::ratelimit_state_exit(self.inner.get()) }; + } +} + +/// Helper macro to implement ratelimited printing. +#[macro_export] +#[doc(hidden)] +macro_rules! print_ratelimited { + ($print_macro:ident, $($arg:tt)*) => {{ + $crate::ratelimit::ratelimit_state_init!( + _rs, + $crate::ratelimit::Ratelimit::DEFAULT_INTERVAL, + $crate::ratelimit::Ratelimit::DEFAULT_BURST, + ); + let loc = $crate::c_str!(::core::concat!(::core::file!(), ":", ::core::line!())); + if $crate::ratelimit::Ratelimit::ratelimit(&_rs, loc) { + $crate::$print_macro!($($arg)*); + } + }}; +} + +/// Prints an emergency-level message (level 0) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_emerg_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_emerg, $($arg)*) + ) +); + +/// Prints an alert-level message (level 1) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_alert_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_alert, $($arg)*) + ) +); + +/// Prints a critical-level message (level 2) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_crit_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_crit, $($arg)*) + ) +); + +/// Prints an error-level message (level 3) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_err_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_err, $($arg)*) + ) +); + +/// Prints a warning-level message (level 4) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_warn_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_warn, $($arg)*) + ) +); + +/// Prints a notice-level message (level 5) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_notice_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_notice, $($arg)*) + ) +); + +/// Prints an info-level message (level 6) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_info_ratelimited ( + ($($arg:tt)*) => ( + $crate::print_ratelimited!(pr_info, $($arg)*) + ) +); + +/// Prints a debug-level message (level 7) if allowed by a rate limiter. +#[macro_export] +macro_rules! pr_debug_ratelimited ( + ($($arg:tt)*) => ( + if cfg!(debug_assertions) { + $crate::print_ratelimited!(pr_debug, $($arg)*) + } + ) +); diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs index 037fa63ebcac86..6a71bb3a5c45c4 100644 --- a/rust/kernel/sync/lock/spinlock.rs +++ b/rust/kernel/sync/lock/spinlock.rs @@ -468,7 +468,6 @@ mod tests { /// /// For use in statics containing raw spinlocks. #[doc(alias("__SPIN_LOCK_UNLOCKED", "DEFINE_SPINLOCK"))] -#[expect(dead_code)] pub(crate) const fn raw_spin_lock_unlocked(name: &'static CStr) -> bindings::raw_spinlock_t { // Silence unused variable warnings. #[cfg(not(CONFIG_DEBUG_LOCK_ALLOC))] From a12f22b2c9398541d75d86d18be0a9ec92909fe0 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Wed, 7 Oct 2026 14:30:04 +0000 Subject: [PATCH 1585/2286] rust_binder: use pr_*_ratelimited! for printing To avoid DoS from printing too much, make printing in Binder rate limited. A big portion of these print statements have been updated to use binder_debug!, but some still remain to be converted. For now, just update them to use pr_*_ratelimited! until we get around to converting them to use binder_debug! too. While we're at it, fix the missing newlines at the end of some of those println statements. Four transaction failure pr_warn! calls in thread.rs and transaction.rs are not converted because they're being deleted by another patch series. Acked-by: Carlos Llamas Signed-off-by: Alice Ryhl Link: https://patch.msgid.link/20261007-pr-ratelimited-v6-4-f14accd4ea3b@google.com Signed-off-by: Miguel Ojeda --- drivers/android/binder/allocation.rs | 4 +- drivers/android/binder/context.rs | 6 +-- drivers/android/binder/debug.rs | 4 +- drivers/android/binder/freeze.rs | 2 +- drivers/android/binder/node.rs | 4 +- drivers/android/binder/page_range.rs | 12 +++--- drivers/android/binder/process.rs | 22 +++++----- drivers/android/binder/range_alloc/tree.rs | 26 ++++++------ drivers/android/binder/thread.rs | 48 ++++++++++++---------- drivers/android/binder/transaction.rs | 6 +-- 10 files changed, 71 insertions(+), 63 deletions(-) diff --git a/drivers/android/binder/allocation.rs b/drivers/android/binder/allocation.rs index 165cb797eb1e9d..8151ba3ea7f4e5 100644 --- a/drivers/android/binder/allocation.rs +++ b/drivers/android/binder/allocation.rs @@ -261,7 +261,7 @@ impl Drop for Allocation { let view = AllocationView::new(self, offsets.start); for i in offsets.step_by(size_of::()) { if view.cleanup_object(i).is_err() { - pr_warn!("Error cleaning up object at offset {}\n", i) + pr_warn_ratelimited!("Error cleaning up object at offset {}\n", i) } } } @@ -286,7 +286,7 @@ impl Drop for Allocation { if info.clear_on_free { if let Err(e) = self.fill_zero() { - pr_warn!("Failed to clear data on free: {:?}", e); + pr_warn_ratelimited!("Failed to clear data on free: {:?}\n", e); } } } diff --git a/drivers/android/binder/context.rs b/drivers/android/binder/context.rs index ddddb66b355710..431d6007a9b1fd 100644 --- a/drivers/android/binder/context.rs +++ b/drivers/android/binder/context.rs @@ -80,7 +80,7 @@ impl Context { pub(crate) fn register_process(self: &Arc, proc: Arc) -> Result { if !Arc::ptr_eq(self, &proc.ctx) { - pr_err!("Context::register_process called on the wrong context."); + pr_err_ratelimited!("Context::register_process called on the wrong context.\n"); return Err(EINVAL); } self.manager.lock().all_procs.push(proc, GFP_KERNEL)?; @@ -89,7 +89,7 @@ impl Context { pub(crate) fn deregister_process(self: &Arc, proc: &Arc) { if !Arc::ptr_eq(self, &proc.ctx) { - pr_err!("Context::deregister_process called on the wrong context."); + pr_err_ratelimited!("Context::deregister_process called on the wrong context.\n"); return; } let mut manager = self.manager.lock(); @@ -110,7 +110,7 @@ impl Context { pub(crate) fn set_manager_node(&self, node_ref: NodeRef) -> Result { let mut manager = self.manager.lock(); if manager.node.is_some() { - pr_warn!("BINDER_SET_CONTEXT_MGR already set"); + pr_warn_ratelimited!("BINDER_SET_CONTEXT_MGR already set\n"); return Err(EBUSY); } security::binder_set_context_mgr(&node_ref.node.owner.cred)?; diff --git a/drivers/android/binder/debug.rs b/drivers/android/binder/debug.rs index 824b10c004c333..6d8dcddf461913 100644 --- a/drivers/android/binder/debug.rs +++ b/drivers/android/binder/debug.rs @@ -53,7 +53,7 @@ macro_rules! binder_debug { // Rule to explicitly specify a PID (used in kworkers). (pid=$pid:expr, $mask:ident, $($arg:tt)*) => { if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) { - kernel::pr_info!( + kernel::pr_info_ratelimited!( "{}: {}\n", $pid, kernel::prelude::fmt!($($arg)*) @@ -65,7 +65,7 @@ macro_rules! binder_debug { ($mask:ident, $($arg:tt)*) => { if $crate::debug::debug_mask_enabled($crate::debug::DebugMask::$mask) { let thread = kernel::current!(); - kernel::pr_info!( + kernel::pr_info_ratelimited!( "{}:{} {}\n", thread.tgid(), thread.pid(), diff --git a/drivers/android/binder/freeze.rs b/drivers/android/binder/freeze.rs index 66912b4cb527b8..7fbde3345ced4b 100644 --- a/drivers/android/binder/freeze.rs +++ b/drivers/android/binder/freeze.rs @@ -412,7 +412,7 @@ impl Process { recipients .push_within_capacity(node_proc_pair) .map_err(|_| { - pr_err!( + pr_err_ratelimited!( "push_within_capacity failed even though we checked the capacity\n" ); AllocError diff --git a/drivers/android/binder/node.rs b/drivers/android/binder/node.rs index 0a82af14cda3ca..69b36951dbbed6 100644 --- a/drivers/android/binder/node.rs +++ b/drivers/android/binder/node.rs @@ -405,7 +405,7 @@ impl Node { !is_dead && !state.has_count } else { if state.count < count { - pr_err!("Failure: refcount underflow!"); + pr_err_ratelimited!("Failure: refcount underflow!\n"); return None; } state.count -= count; @@ -693,7 +693,7 @@ impl Node { .freeze_list .retain(|proc| !core::ptr::eq::(&**proc, p)); if len == inner.freeze_list.len() { - pr_warn!( + pr_warn_ratelimited!( "Could not remove freeze listener for {}\n", p.pid_in_current_ns() ); diff --git a/drivers/android/binder/page_range.rs b/drivers/android/binder/page_range.rs index 52ffbf3504e7f7..c1f4f635c2c063 100644 --- a/drivers/android/binder/page_range.rs +++ b/drivers/android/binder/page_range.rs @@ -212,7 +212,7 @@ impl PageInfo { // SAFETY: The pointer is valid for writing, so also valid for reading. if unsafe { (*ptr).is_some() } { - pr_err!("set_page called when there is already a page"); + pr_err_ratelimited!("set_page called when there is already a page\n"); // SAFETY: We will initialize the page again below. unsafe { ptr::drop_in_place(ptr) }; } @@ -300,11 +300,11 @@ impl ShrinkablePageRange { let num_pages = num_bytes >> PAGE_SHIFT; if !ptr::eq::(&*self.mm, &**vma.mm()) { - pr_debug!("Failed to register with vma: invalid vma->vm_mm"); + pr_debug_ratelimited!("Failed to register with vma: invalid vma->vm_mm\n"); return Err(EINVAL); } if num_pages == 0 { - pr_debug!("Failed to register with vma: size zero"); + pr_debug_ratelimited!("Failed to register with vma: size zero\n"); return Err(EINVAL); } @@ -325,7 +325,7 @@ impl ShrinkablePageRange { let mut inner = self.lock.lock(); if inner.size > 0 { - pr_debug!("Failed to register with vma: already registered"); + pr_debug_ratelimited!("Failed to register with vma: already registered\n"); drop(inner); return Err(EBUSY); } @@ -380,7 +380,7 @@ impl ShrinkablePageRange { match unsafe { self.use_page_slow(i) } { Ok(()) => {} Err(err) => { - pr_warn!("Error in use_page_slow: {:?}", err); + pr_warn_ratelimited!("Error in use_page_slow: {:?}\n", err); return Err(err); } } @@ -529,7 +529,7 @@ impl ShrinkablePageRange { // duration of this call to `iterate`, so nobody will change the page. let page = unsafe { PageInfo::get_page(page_info) }; if page.is_none() { - pr_warn!("Page is null!"); + pr_warn_ratelimited!("Page is null!\n"); } let page = page.ok_or(EFAULT)?; cb(page, offset, available)?; diff --git a/drivers/android/binder/process.rs b/drivers/android/binder/process.rs index 5372bfbd93b35d..65f719e46c3e3e 100644 --- a/drivers/android/binder/process.rs +++ b/drivers/android/binder/process.rs @@ -324,7 +324,7 @@ impl ProcessInner { if let Some(death) = ListArc::try_from_arc_or_drop(death) { self.delivered_deaths.push_back(death); } else { - pr_warn!("Notification added to `delivered_deaths` twice."); + pr_warn_ratelimited!("Notification added to `delivered_deaths` twice.\n"); } } @@ -701,7 +701,7 @@ impl Process { let id = { let current = kernel::current!(); if self.task != current.group_leader() { - pr_err!("get_current_thread was called from the wrong process."); + pr_err_ratelimited!("get_current_thread was called from the wrong process.\n"); return Err(EINVAL); } current.pid() @@ -725,7 +725,7 @@ impl Process { Ok(ta) } rbtree::Entry::Occupied(_entry) => { - pr_err!("Cannot create two threads with the same id."); + pr_err_ratelimited!("Cannot create two threads with the same id.\n"); Err(EINVAL) } } @@ -861,7 +861,9 @@ impl Process { match refs.by_handle.entry(res.as_u32()) { rbtree::Entry::Vacant(entry) => break (res, entry), rbtree::Entry::Occupied(_) => { - pr_err!("Detected mismatch between handle_is_present and by_handle"); + pr_err_ratelimited!( + "Detected mismatch between handle_is_present and by_handle\n" + ); res.acquire(); kernel::warn_on!(true); return Err(EINVAL); @@ -1101,7 +1103,7 @@ impl Process { ) { Ok(()) => {} Err(err) => { - pr_warn!("use_range failure {:?}", err); + pr_warn_ratelimited!("use_range failure {:?}\n", err); return Err(err.into()); } } @@ -1132,7 +1134,7 @@ impl Process { let freed_range = match mapping.alloc.reservation_abort(offset) { Ok(freed_range) => freed_range, Err(_) => { - pr_warn!( + pr_warn_ratelimited!( "Pointer {:x} failed to free, base = {:x}\n", ptr, mapping.address @@ -1154,7 +1156,7 @@ impl Process { let mut inner = self.inner.lock(); if let Some(ref mut mapping) = &mut inner.mapping { if mapping.alloc.reservation_commit(offset, &mut data).is_err() { - pr_warn!("Offset {} failed to be marked freeable\n", offset); + pr_warn_ratelimited!("Offset {} failed to be marked freeable\n", offset); } } } @@ -1516,7 +1518,7 @@ impl Process { let wake = { let mut inner = self.inner.lock(); if inner.outstanding_txns == 0 { - pr_err!("outstanding_txns underflow"); + pr_err_ratelimited!("outstanding_txns underflow\n"); return; } inner.outstanding_txns -= 1; @@ -1836,7 +1838,7 @@ impl<'a> Registration<'a> { // It is an error to hit this branch, and it should not be reachable. We try to do // something reasonable when the failure path happens. Most likely, the thread in // question will sleep forever. - pr_err!("Same thread registered with `ready_threads` twice."); + pr_err_ratelimited!("Same thread registered with `ready_threads` twice.\n"); } Self { thread } } @@ -1861,7 +1863,7 @@ impl Drop for WithNodes<'_> { fn drop(&mut self) { core::mem::swap(&mut self.nodes, &mut self.inner.nodes); if self.nodes.iter().next().is_some() { - pr_err!("nodes array was modified while using lock_with_nodes\n"); + pr_err_ratelimited!("nodes array was modified while using lock_with_nodes\n"); } } } diff --git a/drivers/android/binder/range_alloc/tree.rs b/drivers/android/binder/range_alloc/tree.rs index 48796fcdb36249..3e3b452909ed89 100644 --- a/drivers/android/binder/range_alloc/tree.rs +++ b/drivers/android/binder/range_alloc/tree.rs @@ -166,7 +166,7 @@ impl TreeRangeAllocator { let (found_size, found_off, tree_node, free_tree_node) = match self.find_best_match(size) { None => { - pr_warn!("ENOSPC from range_alloc.reserve_new - size: {}", size); + pr_warn_ratelimited!("ENOSPC from range_alloc.reserve_new - size: {}\n", size); return Err(ENOSPC); } Some(desc) => { @@ -208,8 +208,8 @@ impl TreeRangeAllocator { pub(crate) fn reservation_abort(&mut self, offset: usize) -> Result { let mut cursor = self.tree.cursor_lower_bound_mut(&offset).ok_or_else(|| { - pr_warn!( - "EINVAL from range_alloc.reservation_abort - offset: {}", + pr_warn_ratelimited!( + "EINVAL from range_alloc.reservation_abort - offset: {}\n", offset ); EINVAL @@ -218,8 +218,8 @@ impl TreeRangeAllocator { let (_, desc) = cursor.current_mut(); if desc.offset != offset { - pr_warn!( - "EINVAL from range_alloc.reservation_abort - offset: {}", + pr_warn_ratelimited!( + "EINVAL from range_alloc.reservation_abort - offset: {}\n", offset ); return Err(EINVAL); @@ -230,15 +230,15 @@ impl TreeRangeAllocator { (None, Ok((reservation, free_node_res))) } None => { - pr_warn!( - "EINVAL from range_alloc.reservation_abort - offset: {}", + pr_warn_ratelimited!( + "EINVAL from range_alloc.reservation_abort - offset: {}\n", offset ); (None, Err(EINVAL)) } allocated => { - pr_warn!( - "EPERM from range_alloc.reservation_abort - offset: {}", + pr_warn_ratelimited!( + "EPERM from range_alloc.reservation_abort - offset: {}\n", offset ); (allocated, Err(EPERM)) @@ -327,8 +327,8 @@ impl TreeRangeAllocator { /// Returns the size of the existing entry and the data associated with it. pub(crate) fn reserve_existing(&mut self, offset: usize) -> Result<(usize, usize, Option)> { let desc = self.tree.get_mut(&offset).ok_or_else(|| { - pr_warn!( - "ENOENT from range_alloc.reserve_existing - offset: {}", + pr_warn_ratelimited!( + "ENOENT from range_alloc.reserve_existing - offset: {}\n", offset ); ENOENT @@ -344,8 +344,8 @@ impl TreeRangeAllocator { ) } other => { - pr_warn!( - "ENOENT from range_alloc.reserve_existing - offset: {}", + pr_warn_ratelimited!( + "ENOENT from range_alloc.reserve_existing - offset: {}\n", offset ); (other, Err(ENOENT)) diff --git a/drivers/android/binder/thread.rs b/drivers/android/binder/thread.rs index 18a14aa8a835ae..897c86188c9638 100644 --- a/drivers/android/binder/thread.rs +++ b/drivers/android/binder/thread.rs @@ -160,8 +160,8 @@ impl ScatterGatherState { let sg_entry = match self.sg_entries.get(sg_idx) { Some(sg_entry) => sg_entry, None => { - pr_err!( - "self.ancestors[{}] is {}, but self.sg_entries.len() is {}", + pr_err_ratelimited!( + "self.ancestors[{}] is {}, but self.sg_entries.len() is {}\n", ancestors_i, sg_idx, self.sg_entries.len() @@ -170,8 +170,8 @@ impl ScatterGatherState { } }; if sg_entry.fixup_min_offset > parent_offset { - pr_warn!( - "validate_parent_fixup: fixup_min_offset={}, parent_offset={}", + pr_warn_ratelimited!( + "validate_parent_fixup: fixup_min_offset={}, parent_offset={}\n", sg_entry.fixup_min_offset, parent_offset ); @@ -179,8 +179,8 @@ impl ScatterGatherState { } let new_min_offset = parent_offset.checked_add(length).ok_or(EINVAL)?; if new_min_offset > sg_entry.length { - pr_warn!( - "validate_parent_fixup: new_min_offset={}, sg_entry.length={}", + pr_warn_ratelimited!( + "validate_parent_fixup: new_min_offset={}, sg_entry.length={}\n", new_min_offset, sg_entry.length ); @@ -339,7 +339,7 @@ impl InnerThread { work.set_error_code(code); self.push_work(work) } else { - pr_warn!("Thread reply work is already in use."); + pr_warn_ratelimited!("Thread reply work is already in use.\n"); PushWorkRes::Ok } } @@ -350,7 +350,7 @@ impl InnerThread { // Not notifying: Reply to current thread. let _ = self.push_work(work); } else { - pr_warn!("Thread return work is already in use."); + pr_warn_ratelimited!("Thread return work is already in use.\n"); } } @@ -803,8 +803,8 @@ impl Thread { let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { Some(parent_entry) => parent_entry, None => { - pr_err!( - "validate_parent_fixup returned index out of bounds for sg.entries" + pr_err_ratelimited!( + "validate_parent_fixup returned index out of bounds for sg.entries\n" ); return Err(EINVAL.into()); } @@ -850,8 +850,8 @@ impl Thread { let parent_entry = match sg_state.sg_entries.get_mut(info.parent_sg_index) { Some(parent_entry) => parent_entry, None => { - pr_err!( - "validate_parent_fixup returned index out of bounds for sg.entries" + pr_err_ratelimited!( + "validate_parent_fixup returned index out of bounds for sg.entries\n" ); return Err(EINVAL.into()); } @@ -884,7 +884,9 @@ impl Thread { .read_all(&mut fda_bytes, GFP_KERNEL)?; if fds_len != fda_bytes.len() { - pr_err!("UserSlice::read_all returned wrong length in BINDER_TYPE_FDA"); + pr_err_ratelimited!( + "UserSlice::read_all returned wrong length in BINDER_TYPE_FDA\n" + ); return Err(EINVAL.into()); } @@ -999,7 +1001,11 @@ impl Thread { let ctx = match security::SecurityCtx::from_secid(secid) { Ok(ctx) => ctx, Err(err) => { - pr_warn!("Failed to get security ctx for id {}: {:?}", secid, err); + pr_warn_ratelimited!( + "Failed to get security ctx for id {}: {:?}\n", + secid, + err + ); return Err(err.into()); } }; @@ -1233,7 +1239,7 @@ impl Thread { let inner = self.inner.lock(); if let Some(cur) = &inner.current_transaction { if core::ptr::eq(self, cur.from.as_ref()) { - pr_warn!("got new transaction with bad transaction stack"); + pr_warn_ratelimited!("got new transaction with bad transaction stack\n"); return Err(EINVAL); } Ok(Some(cur.clone())) @@ -1562,7 +1568,7 @@ impl Thread { let mut has_noop_placeholder = false; if req.read_consumed == 0 { if let Err(err) = writer.write_code(BR_NOOP) { - pr_warn!("Failure when writing BR_NOOP at beginning of buffer."); + pr_warn_ratelimited!("Failure when writing BR_NOOP at beginning of buffer.\n"); return Err(err); } has_noop_placeholder = true; @@ -1585,7 +1591,7 @@ impl Thread { Err(err) => { // Propagate the error if we haven't written anything else. if err != EINTR && err != EAGAIN { - pr_warn!("Failure in work getter: {:?}", err); + pr_warn_ratelimited!("Failure in work getter: {:?}\n", err); } if initial_len == writer.len() { return Err(err); @@ -1620,8 +1626,8 @@ impl Thread { ret = self.write(&mut req); crate::trace::trace_write_done(ret); if let Err(err) = ret { - pr_warn!( - "Write failure {:?} in pid:{}", + pr_warn_ratelimited!( + "Write failure {:?} in pid:{}\n", err, self.process.pid_in_current_ns() ); @@ -1637,8 +1643,8 @@ impl Thread { ret = self.read(&mut req, wait); crate::trace::trace_read_done(ret); if ret.is_err() && ret != Err(EINTR) { - pr_warn!( - "Read failure {:?} in pid:{}", + pr_warn_ratelimited!( + "Read failure {:?} in pid:{}\n", ret, self.process.pid_in_current_ns() ); diff --git a/drivers/android/binder/transaction.rs b/drivers/android/binder/transaction.rs index 245f1556b5db37..66045547c23ada 100644 --- a/drivers/android/binder/transaction.rs +++ b/drivers/android/binder/transaction.rs @@ -83,7 +83,7 @@ impl TransactionInfo { pub(crate) fn report_netlink(&self, reply: u32, ctx: &crate::Context) { if let Err(err) = self.report_netlink_inner(reply, ctx) { - pr_warn!( + pr_warn_ratelimited!( "{}:{} netlink report failed: {err:?}\n", self.from_pid, self.from_tid @@ -183,7 +183,7 @@ impl Transaction { }; if info.is_oneway() { if from_parent.is_some() { - pr_warn!("Oneway transaction should not be in a transaction stack."); + pr_warn_ratelimited!("Oneway transaction should not be in a transaction stack.\n"); return Err(EINVAL.into()); } alloc.set_info_oneway_node(node_ref.node.clone()); @@ -384,7 +384,7 @@ impl Transaction { return Ok(()); } } else { - pr_err!("Failed to submit oneway transaction to node."); + pr_err_ratelimited!("Failed to submit oneway transaction to node.\n"); } } From cad3f4daef8a8793a953c632c2fcb0a6560da46a Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 2 Oct 2026 14:04:25 +0900 Subject: [PATCH 1586/2286] rust: time: make Delta::ZERO generic over the time unit Delta::ZERO is defined only for Delta, so code that needs a zero Delta has to write Delta::from_jiffies(0). Define ZERO for every Delta instead. An integer literal cannot be used for a generic U::Repr, so add ZERO to TimeUnit. Suggested-by: Markus Probst Suggested-by: Gary Guo Signed-off-by: FUJITA Tomonori Acked-by: Andreas Hindborg Reviewed-by: Gary Guo Link: https://patch.msgid.link/20261002050428.2746277-2-tomo@flapping.org Signed-off-by: Miguel Ojeda --- rust/kernel/time.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs index 5ae377e70dd880..9f231bf25ead1e 100644 --- a/rust/kernel/time.rs +++ b/rust/kernel/time.rs @@ -306,6 +306,9 @@ mod private { pub trait TimeUnit: private::Sealed { /// The underlying representation of the time unit. type Repr: Copy + Clone + PartialEq + PartialOrd + Eq + Ord + core::fmt::Debug; + + /// The zero value of [`Self::Repr`]. + const ZERO: Self::Repr; } /// A time unit of nanoseconds. @@ -317,6 +320,7 @@ pub enum Nsec {} impl TimeUnit for Nsec { type Repr = i64; + const ZERO: Self::Repr = 0; } /// A time unit of jiffies. @@ -328,6 +332,7 @@ pub enum Jiffy {} impl TimeUnit for Jiffy { type Repr = isize; + const ZERO: Self::Repr = 0; } /// A span of time. @@ -341,6 +346,11 @@ pub struct Delta { value: U::Repr, } +impl Delta { + /// A span of time equal to zero. + pub const ZERO: Self = Self { value: U::ZERO }; +} + impl Delta { /// Create a new [`Delta`] from a number of jiffies. #[inline] @@ -428,9 +438,6 @@ impl ops::Div for Delta { } impl Delta { - /// A span of time equal to zero. - pub const ZERO: Self = Self { value: 0 }; - /// Create a new [`Delta`] from a number of nanoseconds. #[inline] pub const fn from_nanos(nanos: i64) -> Self { From daff0977b2e36c777a00b4ca9fabc019834e60aa Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 2 Oct 2026 14:04:26 +0900 Subject: [PATCH 1587/2286] rust: time: add Delta::as_jiffies_unsigned() Delta is signed, but some C functions, such as queue_delayed_work_on(), take a span in jiffies as unsigned long. So each caller has to clamp a negative value to zero and cast it before it passes the value to C. Add a helper that does both. Suggested-by: Gary Guo Signed-off-by: FUJITA Tomonori Acked-by: Andreas Hindborg Reviewed-by: Gary Guo Link: https://patch.msgid.link/20261002050428.2746277-3-tomo@flapping.org Signed-off-by: Miguel Ojeda --- rust/kernel/time.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs index 9f231bf25ead1e..b6a2aab12a500a 100644 --- a/rust/kernel/time.rs +++ b/rust/kernel/time.rs @@ -363,6 +363,27 @@ impl Delta { pub const fn as_jiffies(self) -> isize { self.value } + + /// Return the number of jiffies in the [`Delta`] as an unsigned value. + /// + /// If the number of jiffies is negative, zero is returned. + /// + /// # Examples + /// + /// ``` + /// use kernel::time::Delta; + /// + /// // A negative span returns zero. + /// assert_eq!(Delta::from_jiffies(-1).as_jiffies_unsigned(), 0); + /// + /// // A positive span returns its value. + /// assert_eq!(Delta::from_jiffies(1).as_jiffies_unsigned(), 1); + /// ``` + #[inline] + pub fn as_jiffies_unsigned(self) -> usize { + // CAST: `isize::max()` makes the value non-negative, so the cast keeps it. + isize::max(self.value, 0) as usize + } } impl ops::Add for Delta { From 41e24dc80739b07d592566ceeaf0bb5e3620cc5c Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sun, 11 Oct 2026 16:16:51 +0200 Subject: [PATCH 1588/2286] pid: document that do_each_pid_task() needs tasklist_lock do_each_pid_task() looks "obviously rcu-safe". This confuses people and we already have 2 buggy users: send_sigio() and send_sigurg(). The reasons why it is not are not obvious at all, add the comment. Signed-off-by: Oleg Nesterov Link: https://patch.msgid.link/asuaU8H_5MbO3-cW@redhat.com Signed-off-by: Christian Brauner (Amutable) --- include/linux/pid.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/linux/pid.h b/include/linux/pid.h index ddaef0bbc8ba3a..f5476d7140a352 100644 --- a/include/linux/pid.h +++ b/include/linux/pid.h @@ -187,6 +187,15 @@ static inline pid_t pid_nr(struct pid *pid) pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns); pid_t pid_vnr(struct pid *pid); +/* + * Needs tasklist_lock if type > PIDTYPE_TGID. A lockless reader can: + * + * - race with a change_pid() that moves the task to another hlist, + * and continue to scan the wrong list. + * + * - race with a copy_process() that adds a new task to the start of hlist, + * and miss the new child. + */ #define do_each_pid_task(pid, type, task) \ do { \ if ((pid) != NULL) \ From fd0349aadbb99252931e237df15f72e43e649254 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:31 -0500 Subject: [PATCH 1589/2286] pid: add helpers to operate on a struct pid array We're about to extend af_unix sockets and coredump code with the ability to hand out thread-specific pidfds. Add a few simple helpers that allow to operate on multiple struct pids up to PIDTYPE_MAX with automatic bounds checking. Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Kuniyuki Iwashima Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-1-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- fs/pidfs.c | 16 ++++++++ include/linux/pid.h | 53 +++++++++++++++++++++++++++ include/linux/pid_types.h | 8 ++++ include/linux/pidfs.h | 5 +++ include/linux/sched/signal.h | 18 +++++++++ tools/lib/python/kdoc/xforms_lists.py | 1 + 6 files changed, 101 insertions(+) diff --git a/fs/pidfs.c b/fs/pidfs.c index a6a643f15d08a9..586af2e5811cc2 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -1070,6 +1070,22 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp) return 0; } +/* Register the pids up to pid type @last of @pids in pidfs. */ +int __pidfs_register_pids(struct pid *const *pids, enum pid_type last) +{ + if (WARN_ON_ONCE(last >= PIDTYPE_MAX)) + return -EINVAL; + + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) { + int ret = pidfs_register_pid(pids[type]); + + if (unlikely(ret)) + return ret; + } + + return 0; +} + static struct dentry *pidfs_stash_dentry(struct dentry **stashed, struct dentry *dentry) { diff --git a/include/linux/pid.h b/include/linux/pid.h index ddaef0bbc8ba3a..87635d0cc1f712 100644 --- a/include/linux/pid.h +++ b/include/linux/pid.h @@ -2,6 +2,9 @@ #ifndef _LINUX_PID_H #define _LINUX_PID_H +#include +#include +#include #include #include #include @@ -92,6 +95,56 @@ static inline struct pid *get_pid(struct pid *pid) } extern void put_pid(struct pid *pid); + +/* + * Helpers for arrays of struct pid indexed by pid type declared with + * DECLARE_PIDS(). The array covers PIDTYPE_PID up to the pid type it + * was declared with and the helpers take that bound from the array. + */ +static inline void __get_pids(struct pid **dst, struct pid *const *src, + enum pid_type last) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) + dst[type] = get_pid(src[type]); +} + +static inline void __put_pids(struct pid **pids, enum pid_type last) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) { + put_pid(pids[type]); + pids[type] = NULL; + } +} + +static inline void __swap_pids(struct pid **a, struct pid **b, + enum pid_type last) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) + swap(a[type], b[type]); +} + +static inline bool __pids_equal(struct pid *const *a, struct pid *const *b, + enum pid_type last) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) + if (a[type] != b[type]) + return false; + return true; +} + +/* The last pid type an array declared with DECLARE_PIDS() covers. */ +#define pids_last(pids) \ + ((enum pid_type)(ARRAY_SIZE(pids) - 1 + \ + BUILD_BUG_ON_ZERO(ARRAY_SIZE(pids) > PIDTYPE_MAX))) + +#define __pids_last2(a, b) \ + (pids_last(a) + BUILD_BUG_ON_ZERO(ARRAY_SIZE(a) != ARRAY_SIZE(b))) + +#define get_pids(dst, src) __get_pids(dst, src, __pids_last2(dst, src)) +#define put_pids(pids) __put_pids(pids, pids_last(pids)) +#define swap_pids(a, b) __swap_pids(a, b, __pids_last2(a, b)) +#define pids_equal(a, b) __pids_equal(a, b, __pids_last2(a, b)) + extern struct task_struct *pid_task(struct pid *pid, enum pid_type); static inline bool pid_has_task(struct pid *pid, enum pid_type type) { diff --git a/include/linux/pid_types.h b/include/linux/pid_types.h index c2aee1d91dcfdc..3302690a2a2812 100644 --- a/include/linux/pid_types.h +++ b/include/linux/pid_types.h @@ -2,6 +2,8 @@ #ifndef _LINUX_PID_TYPES_H #define _LINUX_PID_TYPES_H +#include + enum pid_type { PIDTYPE_PID, PIDTYPE_TGID, @@ -10,6 +12,12 @@ enum pid_type { PIDTYPE_MAX, }; +struct pid; + +/* An array of struct pid indexed by pid type, PIDTYPE_PID up to @last. */ +#define DECLARE_PIDS(name, last) \ + struct pid *name[(last) + 1 + BUILD_BUG_ON_ZERO((last) >= PIDTYPE_MAX)] + struct pid_namespace; extern struct pid_namespace init_pid_ns; diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h index 0abf7da9ab236e..3c1e82f1a36913 100644 --- a/include/linux/pidfs.h +++ b/include/linux/pidfs.h @@ -3,6 +3,7 @@ #define _LINUX_PID_FS_H #include +#include struct coredump_params; @@ -32,5 +33,9 @@ static inline int pidfs_register_pid(struct pid *pid) } void pidfs_free_pid(struct pid *pid); +int __pidfs_register_pids(struct pid *const *pids, enum pid_type last); + +/* Register the pids of an array declared with DECLARE_PIDS(). */ +#define pidfs_register_pids(pids) __pidfs_register_pids(pids, pids_last(pids)) #endif /* _LINUX_PID_FS_H */ diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h index d45a5476b97dec..3149cdb976c607 100644 --- a/include/linux/sched/signal.h +++ b/include/linux/sched/signal.h @@ -674,6 +674,24 @@ struct pid *task_pid_type(struct task_struct *task, enum pid_type type) return pid; } +/* Fill @pids with the pid types of @task up to @last, without references. */ +static inline void __task_pids(struct pid **pids, enum pid_type last, + struct task_struct *task) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) + pids[type] = task_pid_type(task, type); +} + +static inline void __get_task_pids(struct pid **pids, enum pid_type last, + struct task_struct *task) +{ + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) + pids[type] = get_pid(task_pid_type(task, type)); +} + +#define task_pids(pids, task) __task_pids(pids, pids_last(pids), task) +#define get_task_pids(pids, task) __get_task_pids(pids, pids_last(pids), task) + static inline struct pid *task_tgid(struct task_struct *task) { return task->signal->pids[PIDTYPE_TGID]; diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py index e3dda2fe8a5371..e792785a15ec6c 100644 --- a/tools/lib/python/kdoc/xforms_lists.py +++ b/tools/lib/python/kdoc/xforms_lists.py @@ -45,6 +45,7 @@ class CTransforms: (CMatch("DECLARE_HASHTABLE"), r"unsigned long \1[1 << ((\2) - 1)]"), (CMatch("DECLARE_KFIFO"), r"\2 *\1"), (CMatch("DECLARE_KFIFO_PTR"), r"\2 *\1"), + (CMatch("DECLARE_PIDS"), r"struct pid *\1[(\2) + 1]"), (CMatch("(?:__)?DECLARE_FLEX_ARRAY"), r"\1 \2[]"), (CMatch("DEFINE_DMA_UNMAP_ADDR"), r"dma_addr_t \1"), (CMatch("DEFINE_DMA_UNMAP_LEN"), r"__u32 \1"), From c48ae5bcfc356a8472132ad7fbdd5df83a34e8ac Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:32 -0500 Subject: [PATCH 1590/2286] af_unix: record the pid of the sending thread Currently only the struct pid of the thread-group leader is recorded. The identity of the actual thread that sent the message or is connected to a given socket cannot be retrieved. Add the plumbing to make it possible to retrieve a pidfd for the sender. Nothing uses the thread-specific struct pid yet. No functional changes. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-2-350183b6e02a@amutable.com Reviewed-by: Kuniyuki Iwashima Signed-off-by: Christian Brauner (Amutable) --- include/net/scm.h | 10 +++++----- net/core/scm.c | 21 +++++++++++++++++---- net/unix/af_unix.c | 21 +++++++++++---------- net/unix/af_unix.h | 3 ++- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/include/net/scm.h b/include/net/scm.h index 86ae6bc109ec6d..b68a143f42de5c 100644 --- a/include/net/scm.h +++ b/include/net/scm.h @@ -42,7 +42,7 @@ struct scm_fp_list { }; struct scm_cookie { - struct pid *pid; /* Skb credentials */ + DECLARE_PIDS(pid, PIDTYPE_TGID); /* Skb credentials by pid type */ struct scm_fp_list *fp; /* Passed files */ struct scm_creds creds; /* Skb credentials */ #ifdef CONFIG_SECURITY_NETWORK @@ -69,7 +69,6 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co static __inline__ void scm_set_cred(struct scm_cookie *scm, struct pid *pid, kuid_t uid, kgid_t gid) { - scm->pid = get_pid(pid); scm->creds.pid = pid_vnr(pid); scm->creds.uid = uid; scm->creds.gid = gid; @@ -77,8 +76,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm, static __inline__ void scm_destroy_cred(struct scm_cookie *scm) { - put_pid(scm->pid); - scm->pid = NULL; + put_pids(scm->pid); } static __inline__ void scm_destroy(struct scm_cookie *scm) @@ -94,8 +92,10 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg, memset(scm, 0, sizeof(*scm)); scm->creds.uid = INVALID_UID; scm->creds.gid = INVALID_GID; - if (forcecreds) + if (forcecreds) { + scm->pid[PIDTYPE_TGID] = get_pid(task_tgid(current)); scm_set_cred(scm, task_tgid(current), current_uid(), current_gid()); + } unix_get_peersec_dgram(sock, scm); if (msg->msg_controllen <= 0) return 0; diff --git a/net/core/scm.c b/net/core/scm.c index f0d44ecdb11fca..9b9e119c353a8d 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy); static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid) { + struct pid *thread_pid; int err; /* drop all previous references */ @@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid) if (unlikely(err)) return err; - scm->pid = pid; + /* A sender naming its own thread-group sends from the current thread. */ + if (pid == task_tgid(current)) + thread_pid = task_pid(current); + else + thread_pid = pid; + + err = pidfs_register_pid(thread_pid); + if (unlikely(err)) + return err; + + scm->pid[PIDTYPE_TGID] = pid; + scm->pid[PIDTYPE_PID] = get_pid(thread_pid); scm->creds.pid = pid_vnr(pid); return 0; } @@ -207,7 +219,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) if (err) goto error; - if (!p->pid || pid_vnr(p->pid) != creds.pid) { + if (!p->pid[PIDTYPE_TGID] || + pid_vnr(p->pid[PIDTYPE_TGID]) != creds.pid) { struct pid *pid; err = -ESRCH; pid = find_get_pid(creds.pid); @@ -504,10 +517,10 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm) return; } - if (!scm->pid) + if (!scm->pid[PIDTYPE_TGID]) return; - pidfd = pidfd_prepare(scm->pid, PIDFD_STALE, &pidfd_file); + pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file); if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) { if (pidfd_file) { diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 42cffeafc8c144..64e720bd289b5b 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1973,7 +1973,7 @@ static void unix_destruct_scm(struct sk_buff *skb) { struct scm_cookie scm = {}; - swap(scm.pid, UNIXCB(skb).pid); + swap_pids(scm.pid, UNIXCB(skb).pid); if (UNIXCB(skb).fp) unix_detach_fds(&scm, skb); @@ -1991,7 +1991,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen { int err = 0; - UNIXCB(skb).pid = get_pid(scm->pid); + get_pids(UNIXCB(skb).pid, scm->pid); UNIXCB(skb).uid = scm->creds.uid; UNIXCB(skb).gid = scm->creds.gid; UNIXCB(skb).fp = NULL; @@ -2005,7 +2005,8 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm) { - scm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid); + get_pids(scm->pid, UNIXCB(skb).pid); + scm_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid); unix_set_secdata(scm, skb); } @@ -2025,20 +2026,20 @@ static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm) static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk, const struct sock *other) { - if (UNIXCB(skb).pid) + if (UNIXCB(skb).pid[PIDTYPE_TGID]) return 0; if (unix_may_passcred(sk) || unix_may_passcred(other) || !other->sk_socket) { - struct pid *pid; int err; - pid = task_tgid(current); - err = pidfs_register_pid(pid); - if (unlikely(err)) + get_task_pids(UNIXCB(skb).pid, current); + err = pidfs_register_pids(UNIXCB(skb).pid); + if (unlikely(err)) { + put_pids(UNIXCB(skb).pid); return err; + } - UNIXCB(skb).pid = get_pid(pid); current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid); } @@ -2048,7 +2049,7 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk, static bool unix_skb_scm_eq(struct sk_buff *skb, struct scm_cookie *scm) { - return UNIXCB(skb).pid == scm->pid && + return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] && uid_eq(UNIXCB(skb).uid, scm->creds.uid) && gid_eq(UNIXCB(skb).gid, scm->creds.gid) && unix_secdata_eq(scm, skb); diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h index 8119dbeef3a3c6..402742895acca8 100644 --- a/net/unix/af_unix.h +++ b/net/unix/af_unix.h @@ -2,6 +2,7 @@ #ifndef __AF_UNIX_H #define __AF_UNIX_H +#include #include #define UNIX_HASH_MOD (256 - 1) @@ -11,7 +12,7 @@ struct sock *unix_peer_get(struct sock *sk); struct unix_skb_parms { - struct pid *pid; /* skb credentials */ + DECLARE_PIDS(pid, PIDTYPE_TGID); /* skb credentials by pid type */ kuid_t uid; kgid_t gid; struct scm_fp_list *fp; /* Passed files */ From be3a5e06b962ed056afb2be933817242a6ce32a5 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:33 -0500 Subject: [PATCH 1591/2286] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Currently, SCM_PIDFD carries a pidfd for the thread-group leader. A broker or the coredump server cannot learn the identity of the specific thread that sent a given message. Now that both struct pids are recorded a receiver can ask for the specific identity it needs. So add SO_PASSPIDFD_THREAD as a sibling of SO_PASSPIDFD. SO_PASSPIDFD_THREAD delivers an SCM_PIDFD_THREAD, i.e. scoped to the specific thread sending. This is completely independent of SO_PASSPIDFD, meaning you can use both options and get both cmsgs. On SOCK_STREAM sockets recvmsg() only stops merging data at a thread boundary when the receiver asked for a thread pidfd. For SO_PASSCRED and SO_PASSPIDFD receivers all threads of one process remain a single writer. Co-developed-by: Andrew Halaney Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-3-350183b6e02a@amutable.com Reviewed-by: Kuniyuki Iwashima Signed-off-by: Christian Brauner (Amutable) --- arch/alpha/include/uapi/asm/socket.h | 2 ++ arch/mips/include/uapi/asm/socket.h | 2 ++ arch/parisc/include/uapi/asm/socket.h | 2 ++ arch/sparc/include/uapi/asm/socket.h | 2 ++ include/linux/socket.h | 9 +++++---- include/net/sock.h | 4 +++- include/uapi/asm-generic/socket.h | 2 ++ net/core/scm.c | 23 +++++++++++++++++------ net/core/sock.c | 14 ++++++++++++++ net/unix/af_unix.c | 12 +++++++++--- 10 files changed, 58 insertions(+), 14 deletions(-) diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h index 946a5fad269128..bb3d534826bb0c 100644 --- a/arch/alpha/include/uapi/asm/socket.h +++ b/arch/alpha/include/uapi/asm/socket.h @@ -157,6 +157,8 @@ #define SO_RIGHTS_NOTRUNC 85 +#define SO_PASSPIDFD_THREAD 86 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h index f1641dde135fc8..269badcaa08685 100644 --- a/arch/mips/include/uapi/asm/socket.h +++ b/arch/mips/include/uapi/asm/socket.h @@ -168,6 +168,8 @@ #define SO_RIGHTS_NOTRUNC 85 +#define SO_PASSPIDFD_THREAD 86 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h index f3a3815c7dc293..313aee10a52cc7 100644 --- a/arch/parisc/include/uapi/asm/socket.h +++ b/arch/parisc/include/uapi/asm/socket.h @@ -149,6 +149,8 @@ #define SO_RIGHTS_NOTRUNC 0x4053 +#define SO_PASSPIDFD_THREAD 0x4054 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h index 7907f3b1f0ee0f..bd3e69bcce7a87 100644 --- a/arch/sparc/include/uapi/asm/socket.h +++ b/arch/sparc/include/uapi/asm/socket.h @@ -150,6 +150,8 @@ #define SO_RIGHTS_NOTRUNC 0x005e +#define SO_PASSPIDFD_THREAD 0x005f + #if !defined(__KERNEL__) diff --git a/include/linux/socket.h b/include/linux/socket.h index 5a5eb125010326..b17fdb7d38a773 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg) /* "Socket"-level control message types: */ -#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ -#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ -#define SCM_SECURITY 0x03 /* rw: security label */ -#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_RIGHTS 0x01 /* rw: access rights (array of int) */ +#define SCM_CREDENTIALS 0x02 /* rw: struct ucred */ +#define SCM_SECURITY 0x03 /* rw: security label */ +#define SCM_PIDFD 0x04 /* ro: pidfd (int) */ +#define SCM_PIDFD_THREAD 0x05 /* ro: thread pidfd (int) */ struct ucred { __u32 pid; diff --git a/include/net/sock.h b/include/net/sock.h index 60ea55dc18854a..2c4f754b498bc3 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -356,6 +356,7 @@ struct sk_filter; * @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY * @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD * @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS + * @sk_scm_pidfd_thread: flagged by SO_PASSPIDFD_THREAD to recv a thread SCM_PIDFD * @sk_scm_unused: unused flags for scm_recv() * @ns_tracker: tracker for netns reference * @sk_user_frags: xarray of pages the user is holding a reference on. @@ -562,7 +563,8 @@ struct sock { sk_scm_security : 1, sk_scm_pidfd : 1, sk_scm_rights : 1, - sk_scm_unused : 4; + sk_scm_pidfd_thread : 1, + sk_scm_unused : 3; }; }; u8 sk_clockid; diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h index 84ea7b92936e22..d1e5c6de146da7 100644 --- a/include/uapi/asm-generic/socket.h +++ b/include/uapi/asm-generic/socket.h @@ -152,6 +152,8 @@ #define SO_RIGHTS_NOTRUNC 85 +#define SO_PASSPIDFD_THREAD 86 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__)) diff --git a/net/core/scm.c b/net/core/scm.c index 9b9e119c353a8d..00025579d53e0b 100644 --- a/net/core/scm.c +++ b/net/core/scm.c @@ -499,11 +499,16 @@ static bool scm_has_secdata(struct sock *sk) } #endif -static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm) +static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm, + enum pid_type type, int cmsg_type) { + unsigned int flags = PIDFD_STALE; struct file *pidfd_file = NULL; + struct pid *pid; int len, pidfd; + pid = scm->pid[type]; + /* put_cmsg() doesn't return an error if CMSG is truncated, * that's why we need to opencode these checks here. */ @@ -517,12 +522,15 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm) return; } - if (!scm->pid[PIDTYPE_TGID]) + if (!pid) return; - pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file); + if (type == PIDTYPE_PID) + flags |= PIDFD_THREAD; + + pidfd = pidfd_prepare(pid, flags, &pidfd_file); - if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) { + if (put_cmsg(msg, SOL_SOCKET, cmsg_type, sizeof(int), &pidfd)) { if (pidfd_file) { put_unused_fd(pidfd); fput(pidfd_file); @@ -540,7 +548,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg, { if (!msg->msg_control) { if (sk->sk_scm_credentials || sk->sk_scm_pidfd || - scm->fp || scm_has_secdata(sk)) + sk->sk_scm_pidfd_thread || scm->fp || scm_has_secdata(sk)) msg->msg_flags |= MSG_CTRUNC; scm_destroy(scm); @@ -587,7 +595,10 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg, } if (sock->sk->sk_scm_pidfd) - scm_pidfd_recv(msg, scm); + scm_pidfd_recv(msg, scm, PIDTYPE_TGID, SCM_PIDFD); + + if (sock->sk->sk_scm_pidfd_thread) + scm_pidfd_recv(msg, scm, PIDTYPE_PID, SCM_PIDFD_THREAD); scm_destroy_cred(scm); } diff --git a/net/core/sock.c b/net/core/sock.c index e8551df8330ff4..c6f33fcbea4372 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -1578,6 +1578,13 @@ int sk_setsockopt(struct sock *sk, int level, int optname, ret = -EOPNOTSUPP; break; + case SO_PASSPIDFD_THREAD: + if (sk_is_unix(sk)) + sk->sk_scm_pidfd_thread = valbool; + else + ret = -EOPNOTSUPP; + break; + case SO_PASSRIGHTS: if (sk_is_unix(sk)) sk->sk_scm_rights = valbool; @@ -1893,6 +1900,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname, v.val = sk->sk_scm_pidfd; break; + case SO_PASSPIDFD_THREAD: + if (!sk_is_unix(sk)) + return -EOPNOTSUPP; + + v.val = sk->sk_scm_pidfd_thread; + break; + case SO_PASSRIGHTS: if (!sk_is_unix(sk)) return -EOPNOTSUPP; diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 64e720bd289b5b..e44afb059abfa4 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -803,7 +803,8 @@ static void copy_peercred(struct sock *sk, struct sock *peersk) static bool unix_may_passcred(const struct sock *sk) { - return sk->sk_scm_credentials || sk->sk_scm_pidfd; + return sk->sk_scm_credentials || sk->sk_scm_pidfd || + sk->sk_scm_pidfd_thread; } static int unix_listen(struct socket *sock, int backlog) @@ -2046,9 +2047,14 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk, return 0; } -static bool unix_skb_scm_eq(struct sk_buff *skb, +static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb, struct scm_cookie *scm) { + /* Only a thread pidfd receiver can tell threads of one process apart. */ + if (sk->sk_scm_pidfd_thread && + UNIXCB(skb).pid[PIDTYPE_PID] != scm->pid[PIDTYPE_PID]) + return false; + return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] && uid_eq(UNIXCB(skb).uid, scm->creds.uid) && gid_eq(UNIXCB(skb).gid, scm->creds.gid) && @@ -3031,7 +3037,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state, if (check_creds) { /* Never glue messages from different writers */ - if (!unix_skb_scm_eq(skb, &scm)) + if (!unix_skb_scm_eq(sk, skb, &scm)) break; } else if (unix_may_passcred(sk)) { /* Copy credentials */ From 4e2446c83f384fb535379ef4d0f52151e8079d67 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:34 -0500 Subject: [PATCH 1592/2286] selftests/net: SO_PASSPIDFD_THREAD Extend the af_unix scm_pidfd selftest with SO_PASSPIDFD_THREAD tests. Co-developed-by: Andrew Halaney Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-4-350183b6e02a@amutable.com Reviewed-by: Kuniyuki Iwashima Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/net/af_unix/Makefile | 2 + .../testing/selftests/net/af_unix/scm_pidfd.c | 505 ++++++++++++++++++ 2 files changed, 507 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile index a66f10fb0c231d..45b841758f1bfb 100644 --- a/tools/testing/selftests/net/af_unix/Makefile +++ b/tools/testing/selftests/net/af_unix/Makefile @@ -23,6 +23,8 @@ TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o include ../../lib.mk include ../bpf.mk +$(OUTPUT)/scm_pidfd: CFLAGS += -pthread + $(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ) $(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include $(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c index 2c18b92a260353..d73ae9d27a4803 100644 --- a/tools/testing/selftests/net/af_unix/scm_pidfd.c +++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,14 @@ #define SCM_PIDFD 0x04 #endif +#ifndef SCM_PIDFD_THREAD +#define SCM_PIDFD_THREAD 0x05 +#endif + +#ifndef SO_PASSPIDFD_THREAD +#define SO_PASSPIDFD_THREAD 86 +#endif + #define CHILD_EXIT_CODE_OK 123 static void child_die() @@ -553,4 +562,500 @@ TEST_F(scm_pidfd, test) close(pfd); } +struct thread_ids { + pid_t pid; + pid_t tid; +}; + +#define MAX_WRITERS 2 + +/* Used by writers to signal they've written, so we can sequence multiple writers */ +static int seq_pipe[2]; + +static void *send_ids_thread(void *arg) +{ + int fd = *(int *)arg; + struct thread_ids ids = { + .pid = getpid(), + .tid = gettid(), + }; + char sync; + + if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids)) + return (void *)1; + + /* let the client start the next writer, so the queue order is known */ + if (write(seq_pipe[1], "1", 1) != 1) + return (void *)1; + + /* stay alive until the receiver has looked at our pidfd */ + if (read(fd, &sync, 1) != 1) + return (void *)1; + + return NULL; +} + +static void *send_ids_creds_thread(void *arg) +{ + int fd = *(int *)arg; + struct thread_ids ids = { + .pid = getpid(), + .tid = gettid(), + }; + struct ucred ucred = { + .pid = getpid(), + .uid = getuid(), + .gid = getgid(), + }; + char control[CMSG_SPACE(sizeof(ucred))] = { 0 }; + struct iovec iov; + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + char sync; + + iov.iov_base = &ids; + iov.iov_len = sizeof(ids); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(ucred)); + memcpy(CMSG_DATA(cmsg), &ucred, sizeof(ucred)); + + if (sendmsg(fd, &msg, 0) != sizeof(ids)) + return (void *)1; + + /* signal we've written our info */ + if (write(seq_pipe[1], "1", 1) != 1) + return (void *)1; + + /* stay alive until the receiver has looked at our pidfd */ + if (read(fd, &sync, 1) != 1) + return (void *)1; + + return NULL; +} + +/* + * Runs @nwriters copies of @sender, each in its own non-leader thread, one + * at a time so the queue order is known, then tells the parent on @ackfd + * that everything is queued. + */ +static void thread_client(int fd, int syncfd, int ackfd, + void *(*sender)(void *), int nwriters) +{ + pthread_t writers[MAX_WRITERS]; + void *ret; + char sync; + int i; + + /* wait until the receiver enabled the options it wants */ + if (read(syncfd, &sync, 1) != 1) + child_die(); + + for (i = 0; i < nwriters; i++) { + if (pthread_create(&writers[i], NULL, sender, &fd)) + child_die(); + + if (read(seq_pipe[0], &sync, 1) != 1) + child_die(); + } + + if (write(ackfd, "1", 1) != 1) + child_die(); + + for (i = 0; i < nwriters; i++) + if (pthread_join(writers[i], &ret) || ret) + child_die(); + + exit(0); +} + +struct pidfd_msg { + ssize_t len; + struct thread_ids ids; + struct pidfd_info tgid_info; + struct pidfd_info thread_info; + int tgid_flags; + int thread_flags; + bool have_tgid; + bool have_thread; +}; + +static int get_pidfd_info(int pidfd, struct pidfd_info *info) +{ + info->mask = PIDFD_INFO_PID; + if (ioctl(pidfd, PIDFD_GET_INFO, info)) { + log_err("ioctl(PIDFD_GET_INFO)"); + return -1; + } + + return 0; +} + +/* One recvmsg(), reporting how many bytes came back and what the pidfd + * cmsgs that came with them say. + */ +static int read_pidfd_msg(int fd, struct pidfd_msg *out) +{ + char control[CMSG_SPACE(sizeof(int)) * 2] = { 0 }; + int tgid_pidfd = -1, thread_pidfd = -1; + struct thread_ids buf[2] = { 0 }; + struct msghdr msg = { 0 }; + struct cmsghdr *cmsg; + struct iovec iov; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + out->len = recvmsg(fd, &msg, 0); + if (out->len < 0) { + log_err("recvmsg"); + return -1; + } + + if (msg.msg_flags & MSG_CTRUNC) { + log_err("recvmsg: control truncated"); + return -1; + } + + out->ids = buf[0]; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + + if (cmsg->cmsg_type == SCM_PIDFD) + memcpy(&tgid_pidfd, CMSG_DATA(cmsg), sizeof(tgid_pidfd)); + else if (cmsg->cmsg_type == SCM_PIDFD_THREAD) + memcpy(&thread_pidfd, CMSG_DATA(cmsg), sizeof(thread_pidfd)); + } + + out->have_tgid = tgid_pidfd >= 0; + out->have_thread = thread_pidfd >= 0; + + if (out->have_tgid) { + if (get_pidfd_info(tgid_pidfd, &out->tgid_info)) + return -1; + out->tgid_flags = fcntl(tgid_pidfd, F_GETFL); + close(tgid_pidfd); + } + + if (out->have_thread) { + if (get_pidfd_info(thread_pidfd, &out->thread_info)) + return -1; + out->thread_flags = fcntl(thread_pidfd, F_GETFL); + close(thread_pidfd); + } + + return 0; +} + +/* + * Runs @nwriters threads of one child process against a SOCK_STREAM pair, + * all of them queued before the parent reads. SO_PASSPIDFD is set when + * @want_tgid is given, SO_PASSPIDFD_THREAD when @want_thread is. @nread + * reads are returned in @out. + */ +static int pidfd_flow(void *(*sender)(void *), int nwriters, bool want_tgid, + bool want_thread, struct pidfd_msg *out, int nread) +{ + int child_status = 0; + int syncpipe[2]; + int ackpipe[2]; + int sk[2]; + int on = 1; + char sync; + pid_t child; + int i; + + if (nwriters > MAX_WRITERS) + return -1; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk)) + return -1; + if (pipe(syncpipe) || pipe(ackpipe) || pipe(seq_pipe)) + return -1; + + child = fork(); + if (child < 0) + return -1; + + if (child == 0) { + close(sk[0]); + close(syncpipe[1]); + close(ackpipe[0]); + thread_client(sk[1], syncpipe[0], ackpipe[1], sender, nwriters); + } + close(sk[1]); + close(syncpipe[0]); + close(ackpipe[1]); + close(seq_pipe[0]); + close(seq_pipe[1]); + + if (want_tgid && + setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) { + log_err("Failed to set SO_PASSPIDFD"); + return -1; + } + + if (want_thread && + setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) { + log_err("Failed to set SO_PASSPIDFD_THREAD"); + return -1; + } + + /* let the child know the options are set, it can write now */ + if (write(syncpipe[1], "1", 1) != 1) + return -1; + close(syncpipe[1]); + + /* wait until every writer has queued its message */ + if (read(ackpipe[0], &sync, 1) != 1) + return -1; + close(ackpipe[0]); + + for (i = 0; i < nread; i++) + if (read_pidfd_msg(sk[0], &out[i])) + return -1; + + /* release the writers */ + for (i = 0; i < nwriters; i++) + if (write(sk[0], "x", 1) != 1) + return -1; + close(sk[0]); + + waitpid(child, &child_status, 0); + if (!WIFEXITED(child_status) || WEXITSTATUS(child_status)) + return -1; + + return 0; +} + +static int sockopt_set(int fd, int optname, int val) +{ + return setsockopt(fd, SOL_SOCKET, optname, &val, sizeof(val)); +} + +static int sockopt_get(int fd, int optname) +{ + socklen_t len = sizeof(int); + int val = -1; + + if (getsockopt(fd, SOL_SOCKET, optname, &val, &len)) + return -1; + + return val; +} + +TEST(scm_pidfd_setsockopt_values) +{ + int sk[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sk)); + + /* Verify that the options are truly set independently */ + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1)); + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD)); + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD)); + + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1)); + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD)); + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD)); + + /* Verify that the options are truly cleared independently */ + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0)); + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD)); + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD)); + + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 0)); + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD)); + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD)); + + close(sk[0]); + close(sk[1]); +} + +/* A receiver that only asked about the process cannot tell the two + * threads apart, so their writes are glued into one read. + */ +TEST(scm_pidfd_stream_glues_threads) +{ + struct pidfd_msg msg[1] = { 0 }; + + ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, false, msg, 1)); + EXPECT_EQ(sizeof(struct thread_ids) * 2, msg[0].len); + + EXPECT_TRUE(msg[0].have_tgid); + EXPECT_FALSE(msg[0].have_thread); + EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.pid); + EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.tgid); +} + +/* A receiver asking for both thread and process pidfd should get a unique + * msg for each thread writing + */ +TEST(scm_pidfd_thread_stream_splits_on_threads) +{ + struct pidfd_msg msg[2] = { 0 }; + int i; + + ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, true, msg, 2)); + + for (i = 0; i < 2; i++) { + EXPECT_EQ(sizeof(struct thread_ids), msg[i].len); + EXPECT_TRUE(msg[i].have_tgid); + EXPECT_TRUE(msg[i].have_thread); + + /* Make sure the info makes sense for the pidfd type */ + EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.pid); + EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.tgid); + EXPECT_EQ(msg[i].ids.tid, msg[i].thread_info.pid); + EXPECT_EQ(msg[i].ids.pid, msg[i].thread_info.tgid); + EXPECT_NE(msg[i].ids.pid, msg[i].ids.tid); + } + + /* Make sure we really got unique threads per message */ + EXPECT_NE(msg[0].ids.tid, msg[1].ids.tid); + EXPECT_EQ(msg[0].ids.pid, msg[1].ids.pid); + EXPECT_EQ(msg[0].tgid_info.pid, msg[1].tgid_info.pid); + EXPECT_NE(msg[0].thread_info.pid, msg[1].thread_info.pid); +} + +/* Sends from the thread-group leader */ +static int leader_flow(bool want_thread, struct pidfd_msg *out) +{ + struct thread_ids ids = { + .pid = getpid(), + .tid = gettid(), + }; + int sk[2]; + int on = 1; + int ret; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk)) + return -1; + + if (setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) { + log_err("Failed to set SO_PASSPIDFD"); + return -1; + } + + if (want_thread && + setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) { + log_err("Failed to set SO_PASSPIDFD_THREAD"); + return -1; + } + + if (send(sk[1], &ids, sizeof(ids), 0) != sizeof(ids)) { + log_err("send"); + return -1; + } + + ret = read_pidfd_msg(sk[0], out); + + close(sk[0]); + close(sk[1]); + + return ret; +} + +TEST(scm_pidfd_leader_sender) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(getpid(), gettid()); + + ASSERT_EQ(0, leader_flow(false, &msg)); + ASSERT_TRUE(msg.have_tgid); + EXPECT_FALSE(msg.have_thread); + + EXPECT_EQ(getpid(), msg.tgid_info.pid); + EXPECT_EQ(getpid(), msg.tgid_info.tgid); + EXPECT_FALSE(msg.tgid_flags & O_EXCL); +} + +TEST(scm_pidfd_thread_leader_sender) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(getpid(), gettid()); + + ASSERT_EQ(0, leader_flow(true, &msg)); + ASSERT_TRUE(msg.have_tgid); + ASSERT_TRUE(msg.have_thread); + + EXPECT_EQ(getpid(), msg.tgid_info.pid); + EXPECT_EQ(getpid(), msg.tgid_info.tgid); + EXPECT_EQ(getpid(), msg.thread_info.pid); + EXPECT_EQ(getpid(), msg.thread_info.tgid); + + EXPECT_FALSE(msg.tgid_flags & O_EXCL); + EXPECT_TRUE(msg.thread_flags & O_EXCL); +} + +TEST(scm_pidfd_thread_and_group) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, true, &msg, 1)); + ASSERT_NE(msg.ids.pid, msg.ids.tid); + ASSERT_TRUE(msg.have_tgid); + ASSERT_TRUE(msg.have_thread); + + EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid); + EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid); + + EXPECT_EQ(msg.ids.tid, msg.thread_info.pid); + EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid); +} + +TEST(scm_pidfd_thread) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, false, true, &msg, 1)); + ASSERT_NE(msg.ids.pid, msg.ids.tid); + ASSERT_TRUE(msg.have_thread); + EXPECT_FALSE(msg.have_tgid); + EXPECT_EQ(msg.ids.tid, msg.thread_info.pid); + EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid); +} + +TEST(scm_pidfd_thread_group) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, false, &msg, 1)); + ASSERT_NE(msg.ids.pid, msg.ids.tid); + ASSERT_TRUE(msg.have_tgid); + EXPECT_FALSE(msg.have_thread); + EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid); + EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid); +} + +TEST(scm_pidfd_thread_creds) +{ + struct pidfd_msg msg = { 0 }; + + ASSERT_EQ(0, pidfd_flow(send_ids_creds_thread, 1, true, true, &msg, 1)); + ASSERT_NE(msg.ids.pid, msg.ids.tid); + ASSERT_TRUE(msg.have_tgid); + ASSERT_TRUE(msg.have_thread); + EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid); + EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid); + EXPECT_EQ(msg.ids.tid, msg.thread_info.pid); + EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid); +} + TEST_HARNESS_MAIN From 95d9b7369665a7de15b42bf6924d193ccb952151 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:35 -0500 Subject: [PATCH 1593/2286] net: turn sk_peer_pid into an array indexed by pid type Currently only the struct pid of the thread-group leader is recorded for a socket's peer. To make room for the struct pid of the thread that called connect(), listen() or socketpair() turn sk_peer_pid into an array indexed by pid type. All users, including bluetooth and the coredump socket, keep using the PIDTYPE_TGID slot. Nothing fills the PIDTYPE_PID slot yet. No functional changes. Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Kuniyuki Iwashima Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-5-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 2 +- include/net/sock.h | 4 ++-- include/trace/events/landlock.h | 2 +- net/bluetooth/af_bluetooth.c | 6 +++--- net/bluetooth/hci_sock.c | 8 ++++---- net/bluetooth/l2cap_sock.c | 2 +- net/core/sock.c | 9 +++++---- net/unix/af_unix.c | 14 +++++++------- 8 files changed, 24 insertions(+), 23 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 6114839f5178b0..9b267d3c0ed759 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -722,7 +722,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params * } /* ... and validate that @sk_peer_pid matches @cprm.pid. */ - if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid != cprm->pid)) + if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid[PIDTYPE_TGID] != cprm->pid)) return false; cprm->limit = RLIM_INFINITY; diff --git a/include/net/sock.h b/include/net/sock.h index 2c4f754b498bc3..77dfb170d3c890 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -301,7 +301,7 @@ struct sk_filter; * @sk_type: socket type (%SOCK_STREAM, etc) * @sk_protocol: which protocol this socket belongs in this network family * @sk_peer_lock: lock protecting @sk_peer_pid and @sk_peer_cred - * @sk_peer_pid: &struct pid for this socket's peer + * @sk_peer_pid: &struct pid for this socket's peer, by pid type * @sk_peer_cred: %SO_PEERCRED setting * @sk_rcvlowat: %SO_RCVLOWAT setting * @sk_rcvtimeo: %SO_RCVTIMEO setting @@ -546,7 +546,7 @@ struct sock { u64 sk_ino; spinlock_t sk_peer_lock; int sk_bind_phc; - struct pid *sk_peer_pid; + DECLARE_PIDS(sk_peer_pid, PIDTYPE_TGID); const struct cred *sk_peer_cred; ktime_t sk_stamp; diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h index 3a43638c9bc290..9e172ea22d95c3 100644 --- a/include/trace/events/landlock.h +++ b/include/trace/events/landlock.h @@ -1037,7 +1037,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket, * updates. The peer socket keeps a reference to sk_peer_pid * through pid_nr(); sun_path is the reliable identifier. */ - peer_pid = READ_ONCE(peer->sk_peer_pid); + peer_pid = READ_ONCE(peer->sk_peer_pid[PIDTYPE_TGID]); __entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0; __assign_str(sun_path); ), diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index 411d66f24393dd..7758e9ea3848c3 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -161,7 +161,7 @@ struct sock *bt_sock_alloc(struct net *net, struct socket *sock, /* Init peer information so it can be properly monitored */ if (!kern) { spin_lock(&sk->sk_peer_lock); - sk->sk_peer_pid = get_pid(task_tgid(current)); + sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(task_tgid(current)); sk->sk_peer_cred = get_current_cred(); spin_unlock(&sk->sk_peer_lock); } @@ -235,9 +235,9 @@ void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh) * socket is allocated by the kernel. */ spin_lock(&sk->sk_peer_lock); - old_pid = sk->sk_peer_pid; + old_pid = sk->sk_peer_pid[PIDTYPE_TGID]; old_cred = sk->sk_peer_cred; - sk->sk_peer_pid = get_pid(parent->sk_peer_pid); + sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(parent->sk_peer_pid[PIDTYPE_TGID]); sk->sk_peer_cred = get_cred(parent->sk_peer_cred); spin_unlock(&sk->sk_peer_lock); diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 81068b585764e8..ec45b531e38733 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -284,21 +284,21 @@ static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb) creds = &bt_cb(skb)->creds; /* Check if peer credentials is set */ - if (!sk->sk_peer_pid) { + if (!sk->sk_peer_pid[PIDTYPE_TGID]) { /* Check if parent peer credentials is set */ - if (bt_sk(sk)->parent && bt_sk(sk)->parent->sk_peer_pid) + if (bt_sk(sk)->parent && bt_sk(sk)->parent->sk_peer_pid[PIDTYPE_TGID]) sk = bt_sk(sk)->parent; else return; } /* Check if scm_creds already set */ - if (creds->pid == pid_vnr(sk->sk_peer_pid)) + if (creds->pid == pid_vnr(sk->sk_peer_pid[PIDTYPE_TGID])) return; memset(creds, 0, sizeof(*creds)); - creds->pid = pid_vnr(sk->sk_peer_pid); + creds->pid = pid_vnr(sk->sk_peer_pid[PIDTYPE_TGID]); if (sk->sk_peer_cred) { creds->uid = sk->sk_peer_cred->uid; creds->gid = sk->sk_peer_cred->gid; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 1194c37e466f18..872d8fb31b6fb6 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1890,7 +1890,7 @@ static struct pid *l2cap_sock_get_peer_pid_cb(struct l2cap_chan *chan) { struct sock *sk = chan->data; - return sk->sk_peer_pid; + return sk->sk_peer_pid[PIDTYPE_TGID]; } static void l2cap_sock_suspend_cb(struct l2cap_chan *chan) diff --git a/net/core/sock.c b/net/core/sock.c index c6f33fcbea4372..f8436e49413877 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -1921,7 +1921,8 @@ int sk_getsockopt(struct sock *sk, int level, int optname, len = sizeof(peercred); spin_lock(&sk->sk_peer_lock); - cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred); + cred_to_ucred(sk->sk_peer_pid[PIDTYPE_TGID], sk->sk_peer_cred, + &peercred); spin_unlock(&sk->sk_peer_lock); if (copy_to_sockptr(optval, &peercred, len)) @@ -1940,7 +1941,7 @@ int sk_getsockopt(struct sock *sk, int level, int optname, len = sizeof(pidfd); spin_lock(&sk->sk_peer_lock); - peer_pid = get_pid(sk->sk_peer_pid); + peer_pid = get_pid(sk->sk_peer_pid[PIDTYPE_TGID]); spin_unlock(&sk->sk_peer_lock); if (!peer_pid) @@ -2394,7 +2395,7 @@ static void __sk_destruct(struct rcu_head *head) /* We do not need to acquire sk->sk_peer_lock, we are the last user. */ put_cred(sk->sk_peer_cred); - put_pid(sk->sk_peer_pid); + put_pids(sk->sk_peer_pid); if (likely(sk->sk_net_refcnt)) { put_net_track(net, &sk->ns_tracker); @@ -3799,7 +3800,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid) sk->sk_frag.offset = 0; sk->sk_peek_off = -1; - sk->sk_peer_pid = NULL; + memset(sk->sk_peer_pid, 0, sizeof(sk->sk_peer_pid)); sk->sk_peer_cred = NULL; spin_lock_init(&sk->sk_peer_lock); diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index e44afb059abfa4..4e571c5a0e1603 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -737,7 +737,7 @@ static void unix_release_sock(struct sock *sk, int embrion) } struct unix_peercred { - struct pid *peer_pid; + DECLARE_PIDS(peer_pid, PIDTYPE_TGID); const struct cred *peer_cred; }; @@ -749,7 +749,7 @@ static inline int prepare_peercred(struct unix_peercred *peercred) pid = task_tgid(current); err = pidfs_register_pid(pid); if (likely(!err)) { - peercred->peer_pid = get_pid(pid); + peercred->peer_pid[PIDTYPE_TGID] = get_pid(pid); peercred->peer_cred = get_current_cred(); } return err; @@ -762,7 +762,7 @@ static void drop_peercred(struct unix_peercred *peercred) might_sleep(); - swap(peercred->peer_pid, pid); + swap(peercred->peer_pid[PIDTYPE_TGID], pid); swap(peercred->peer_cred, cred); put_pid(pid); @@ -772,7 +772,7 @@ static void drop_peercred(struct unix_peercred *peercred) static inline void init_peercred(struct sock *sk, const struct unix_peercred *peercred) { - sk->sk_peer_pid = peercred->peer_pid; + sk->sk_peer_pid[PIDTYPE_TGID] = peercred->peer_pid[PIDTYPE_TGID]; sk->sk_peer_cred = peercred->peer_cred; } @@ -782,12 +782,12 @@ static void update_peercred(struct sock *sk, struct unix_peercred *peercred) struct pid *old_pid; spin_lock(&sk->sk_peer_lock); - old_pid = sk->sk_peer_pid; + old_pid = sk->sk_peer_pid[PIDTYPE_TGID]; old_cred = sk->sk_peer_cred; init_peercred(sk, peercred); spin_unlock(&sk->sk_peer_lock); - peercred->peer_pid = old_pid; + peercred->peer_pid[PIDTYPE_TGID] = old_pid; peercred->peer_cred = old_cred; } @@ -796,7 +796,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk) lockdep_assert_held(&unix_sk(peersk)->lock); spin_lock(&sk->sk_peer_lock); - sk->sk_peer_pid = get_pid(peersk->sk_peer_pid); + sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(peersk->sk_peer_pid[PIDTYPE_TGID]); sk->sk_peer_cred = get_cred(peersk->sk_peer_cred); spin_unlock(&sk->sk_peer_lock); } From 1592d89361e9bff94bb34106d523d0e64853183d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:36 -0500 Subject: [PATCH 1594/2286] af_unix: record the pid of the connecting thread Currently only the struct pid of the thread-group leader is recorded. The identity of the actual thread that connected to a given socket cannot be retrieved. Add the plumbing to make it possible to retrieve a pidfd for the connecting thread. Nothing uses the thread-specific struct pid yet. No functional changes. Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Kuniyuki Iwashima Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-6-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- net/unix/af_unix.c | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 4e571c5a0e1603..b7d62e61d9c478 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -743,51 +743,47 @@ struct unix_peercred { static inline int prepare_peercred(struct unix_peercred *peercred) { - struct pid *pid; int err; - pid = task_tgid(current); - err = pidfs_register_pid(pid); - if (likely(!err)) { - peercred->peer_pid[PIDTYPE_TGID] = get_pid(pid); - peercred->peer_cred = get_current_cred(); + get_task_pids(peercred->peer_pid, current); + err = pidfs_register_pids(peercred->peer_pid); + if (unlikely(err)) { + put_pids(peercred->peer_pid); + return err; } - return err; + + peercred->peer_cred = get_current_cred(); + return 0; } static void drop_peercred(struct unix_peercred *peercred) { const struct cred *cred = NULL; - struct pid *pid = NULL; might_sleep(); - swap(peercred->peer_pid[PIDTYPE_TGID], pid); + put_pids(peercred->peer_pid); swap(peercred->peer_cred, cred); - - put_pid(pid); put_cred(cred); } static inline void init_peercred(struct sock *sk, const struct unix_peercred *peercred) { - sk->sk_peer_pid[PIDTYPE_TGID] = peercred->peer_pid[PIDTYPE_TGID]; + memcpy(sk->sk_peer_pid, peercred->peer_pid, sizeof(sk->sk_peer_pid)); sk->sk_peer_cred = peercred->peer_cred; } static void update_peercred(struct sock *sk, struct unix_peercred *peercred) { const struct cred *old_cred; - struct pid *old_pid; spin_lock(&sk->sk_peer_lock); - old_pid = sk->sk_peer_pid[PIDTYPE_TGID]; + swap_pids(sk->sk_peer_pid, peercred->peer_pid); old_cred = sk->sk_peer_cred; - init_peercred(sk, peercred); + sk->sk_peer_cred = peercred->peer_cred; spin_unlock(&sk->sk_peer_lock); - peercred->peer_pid[PIDTYPE_TGID] = old_pid; peercred->peer_cred = old_cred; } @@ -796,7 +792,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk) lockdep_assert_held(&unix_sk(peersk)->lock); spin_lock(&sk->sk_peer_lock); - sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(peersk->sk_peer_pid[PIDTYPE_TGID]); + get_pids(sk->sk_peer_pid, peersk->sk_peer_pid); sk->sk_peer_cred = get_cred(peersk->sk_peer_cred); spin_unlock(&sk->sk_peer_lock); } From e10899b2a098f496af6316a38a107c859b47c2a4 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:37 -0500 Subject: [PATCH 1595/2286] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd SO_PEERPIDFD hands out a pidfd for the thread-group that called connect() or socketpair(). Enable workloads such as the coredump server or a broker to get a pidfd of the specific thread that connected to the socket. Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Kuniyuki Iwashima Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-7-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- arch/alpha/include/uapi/asm/socket.h | 2 + arch/mips/include/uapi/asm/socket.h | 2 + arch/parisc/include/uapi/asm/socket.h | 2 + arch/sparc/include/uapi/asm/socket.h | 2 + include/uapi/asm-generic/socket.h | 2 + net/core/sock.c | 87 +++++++++++++++------------ net/unix/af_unix.c | 1 + 7 files changed, 61 insertions(+), 37 deletions(-) diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h index bb3d534826bb0c..5d3524c26b2bc6 100644 --- a/arch/alpha/include/uapi/asm/socket.h +++ b/arch/alpha/include/uapi/asm/socket.h @@ -159,6 +159,8 @@ #define SO_PASSPIDFD_THREAD 86 +#define SO_PEERPIDFD_THREAD 87 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h index 269badcaa08685..245a43f52fb1a1 100644 --- a/arch/mips/include/uapi/asm/socket.h +++ b/arch/mips/include/uapi/asm/socket.h @@ -170,6 +170,8 @@ #define SO_PASSPIDFD_THREAD 86 +#define SO_PEERPIDFD_THREAD 87 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h index 313aee10a52cc7..f23710e1c6714e 100644 --- a/arch/parisc/include/uapi/asm/socket.h +++ b/arch/parisc/include/uapi/asm/socket.h @@ -151,6 +151,8 @@ #define SO_PASSPIDFD_THREAD 0x4054 +#define SO_PEERPIDFD_THREAD 0x4055 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h index bd3e69bcce7a87..b35b25bdefc2b4 100644 --- a/arch/sparc/include/uapi/asm/socket.h +++ b/arch/sparc/include/uapi/asm/socket.h @@ -152,6 +152,8 @@ #define SO_PASSPIDFD_THREAD 0x005f +#define SO_PEERPIDFD_THREAD 0x0060 + #if !defined(__KERNEL__) diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h index d1e5c6de146da7..56fed7ab27ab80 100644 --- a/include/uapi/asm-generic/socket.h +++ b/include/uapi/asm-generic/socket.h @@ -154,6 +154,8 @@ #define SO_PASSPIDFD_THREAD 86 +#define SO_PEERPIDFD_THREAD 87 + #if !defined(__KERNEL__) #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__)) diff --git a/net/core/sock.c b/net/core/sock.c index f8436e49413877..3c25d66ba31afa 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -1736,6 +1736,50 @@ static int groups_to_user(sockptr_t dst, const struct group_info *src) return 0; } +/* Hand out a pidfd for @type of the socket's peer via SO_PEERPIDFD*. */ +static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type, + sockptr_t optval, sockptr_t optlen, int len) +{ + struct file *pidfd_file = NULL; + unsigned int flags = 0; + struct pid *peer_pid; + int pidfd; + + if (len > sizeof(pidfd)) + len = sizeof(pidfd); + + spin_lock(&sk->sk_peer_lock); + peer_pid = get_pid(sk->sk_peer_pid[type]); + spin_unlock(&sk->sk_peer_lock); + + if (!peer_pid) + return -ENODATA; + + /* The use of PIDFD_STALE requires stashing of struct pid on pidfs + * with pidfs_register_pid() and only AF_UNIX is prepared for this. + */ + if (sk_is_unix(sk)) + flags |= PIDFD_STALE; + if (type == PIDTYPE_PID) + flags |= PIDFD_THREAD; + + pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file); + put_pid(peer_pid); + if (pidfd < 0) + return pidfd; + + if (copy_to_sockptr(optval, &pidfd, len) || + copy_to_sockptr(optlen, &len, sizeof(int))) { + put_unused_fd(pidfd); + fput(pidfd_file); + + return -EFAULT; + } + + fd_install(pidfd, pidfd_file); + return 0; +} + int sk_getsockopt(struct sock *sk, int level, int optname, sockptr_t optval, sockptr_t optlen) { @@ -1931,45 +1975,14 @@ int sk_getsockopt(struct sock *sk, int level, int optname, } case SO_PEERPIDFD: - { - struct pid *peer_pid; - struct file *pidfd_file = NULL; - unsigned int flags = 0; - int pidfd; - - if (len > sizeof(pidfd)) - len = sizeof(pidfd); - - spin_lock(&sk->sk_peer_lock); - peer_pid = get_pid(sk->sk_peer_pid[PIDTYPE_TGID]); - spin_unlock(&sk->sk_peer_lock); - - if (!peer_pid) - return -ENODATA; - - /* The use of PIDFD_STALE requires stashing of struct pid - * on pidfs with pidfs_register_pid() and only AF_UNIX - * were prepared for this. - */ - if (sk->sk_family == AF_UNIX) - flags = PIDFD_STALE; + return sk_getsockopt_peerpidfd(sk, PIDTYPE_TGID, optval, optlen, len); - pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file); - put_pid(peer_pid); - if (pidfd < 0) - return pidfd; - - if (copy_to_sockptr(optval, &pidfd, len) || - copy_to_sockptr(optlen, &len, sizeof(int))) { - put_unused_fd(pidfd); - fput(pidfd_file); - - return -EFAULT; - } + case SO_PEERPIDFD_THREAD: + /* Only AF_UNIX records the peer's connecting thread. */ + if (!sk_is_unix(sk)) + return -EOPNOTSUPP; - fd_install(pidfd, pidfd_file); - return 0; - } + return sk_getsockopt_peerpidfd(sk, PIDTYPE_PID, optval, optlen, len); case SO_PEERGROUPS: { diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index b7d62e61d9c478..a78e8ef3e0b0e2 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -1057,6 +1057,7 @@ static bool unix_bpf_bypass_getsockopt(int level, int optname) if (level == SOL_SOCKET) { switch (optname) { case SO_PEERPIDFD: + case SO_PEERPIDFD_THREAD: return true; default: return false; From 02e658dffe501640079ad78c8fdd7eb6e1a5e4dc Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:38 -0500 Subject: [PATCH 1596/2286] selftests/net: SO_PEERPIDFD_THREAD Add tests for SO_PEERPIDFD_THREAD. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-8-350183b6e02a@amutable.com Reviewed-by: Kuniyuki Iwashima Signed-off-by: Christian Brauner (Amutable) --- .../testing/selftests/net/af_unix/scm_pidfd.c | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c index d73ae9d27a4803..cbe53fab44fc5a 100644 --- a/tools/testing/selftests/net/af_unix/scm_pidfd.c +++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c @@ -36,6 +36,10 @@ #define SO_PASSPIDFD_THREAD 86 #endif +#ifndef SO_PEERPIDFD_THREAD +#define SO_PEERPIDFD_THREAD 87 +#endif + #define CHILD_EXIT_CODE_OK 123 static void child_die() @@ -1058,4 +1062,97 @@ TEST(scm_pidfd_thread_creds) EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid); } +static void *peer_connect_thread(void *arg) +{ + struct sock_addr *sa = arg; + struct thread_ids ids = { + .pid = getpid(), + .tid = gettid(), + }; + int fd; + char sync; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return (void *)1; + + if (connect(fd, (struct sockaddr *)&sa->listen_addr, sa->addrlen)) + return (void *)1; + + if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids)) + return (void *)1; + + /* stay alive until the server has looked at our pidfd */ + if (read(fd, &sync, 1) != 1) + return (void *)1; + + close(fd); + return NULL; +} + +static int peer_pidfd_info(int fd, int optname, struct pidfd_info *info) +{ + int pidfd; + socklen_t len = sizeof(pidfd); + + if (getsockopt(fd, SOL_SOCKET, optname, &pidfd, &len)) { + log_err("getsockopt(SO_PEERPIDFD*)"); + return -1; + } + + info->mask = PIDFD_INFO_PID; + if (ioctl(pidfd, PIDFD_GET_INFO, info)) { + log_err("ioctl(PIDFD_GET_INFO)"); + close(pidfd); + return -1; + } + + close(pidfd); + return 0; +} + +/* SO_PEERPIDFD_THREAD returns a pidfd for the peer's connecting thread. */ +TEST(so_peerpidfd_thread) +{ + struct sock_addr sa; + struct thread_ids ids; + struct pidfd_info info; + pthread_t thread; + void *tret; + int server, cfd; + + server = socket(AF_UNIX, SOCK_STREAM, 0); + ASSERT_LE(0, server); + + fill_sockaddr(&sa, true); + ASSERT_EQ(0, bind(server, (struct sockaddr *)&sa.listen_addr, sa.addrlen)); + ASSERT_EQ(0, listen(server, 1)); + + ASSERT_EQ(0, pthread_create(&thread, NULL, peer_connect_thread, &sa)); + + cfd = accept(server, NULL, NULL); + ASSERT_LE(0, cfd); + + ASSERT_EQ(sizeof(ids), recv(cfd, &ids, sizeof(ids), MSG_WAITALL)); + ASSERT_NE(ids.pid, ids.tid); + + /* SO_PEERPIDFD refers to the peer's thread-group. */ + ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD, &info)); + EXPECT_EQ(ids.pid, info.pid); + EXPECT_EQ(ids.pid, info.tgid); + + /* SO_PEERPIDFD_THREAD refers to the connecting thread. */ + ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD_THREAD, &info)); + EXPECT_EQ(ids.tid, info.pid); + EXPECT_EQ(ids.pid, info.tgid); + + /* release the connecting thread */ + ASSERT_EQ(1, write(cfd, "x", 1)); + ASSERT_EQ(0, pthread_join(thread, &tret)); + ASSERT_EQ(NULL, tret); + + close(cfd); + close(server); +} + TEST_HARNESS_MAIN From 3209535cd39e492074d5519ecf5721605bea3a26 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:39 -0500 Subject: [PATCH 1597/2286] pidfs: record the coredump on the dumping thread's pid too If a thread-group coredumps only the thread-group leader pidfd will return coredump information. A pidfd for the thread that took the fatal signal cannot be used to retrieve it. Record both the thread-group leader and the specific thread that took the signal and register both in pidfs. Mark both the thread-group leader and the specific thread with the coredump information so retrieval works for both pidfds. Now that both SO_PEERPIDFD and SO_PEERPIDFD_THREAD are available it's easy to get the coredump information for the specific thread. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-9-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- fs/coredump.c | 22 +++++++++++++--------- fs/pidfs.c | 11 +++++++++-- include/linux/coredump.h | 4 +++- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 9b267d3c0ed759..870ad720b2152d 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -454,7 +454,7 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm, * leader we know that the thread-group leader * cannot be reaped until @current has exited. */ - cprm->pid = task_tgid(current); + task_pids(cprm->pid, current); err = cn_printf(cn, "%d", COREDUMP_PIDFD_NUMBER); break; } @@ -626,13 +626,17 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new) struct coredump_params *cp = (struct coredump_params *)info->data; int err; - if (cp->pid) { + if (cp->pid[PIDTYPE_TGID]) { struct file *pidfs_file __free(fput) = NULL; - pidfs_file = pidfs_alloc_file(cp->pid, 0); + pidfs_file = pidfs_alloc_file(cp->pid[PIDTYPE_TGID], 0); if (IS_ERR(pidfs_file)) return PTR_ERR(pidfs_file); + err = pidfs_register_pids(cp->pid); + if (err) + return err; + pidfs_coredump(cp); /* @@ -695,12 +699,12 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params * return false; /* - * Set the thread-group leader pid which is used for the peer - * credentials during connect() below. Then immediately register - * it in pidfs... + * Set the pids of the dumping thread and its thread-group leader + * which are used for the peer credentials during connect() below. + * Then immediately register them in pidfs... */ - cprm->pid = task_tgid(current); - retval = pidfs_register_pid(cprm->pid); + task_pids(cprm->pid, current); + retval = pidfs_register_pids(cprm->pid); if (retval) return false; @@ -722,7 +726,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params * } /* ... and validate that @sk_peer_pid matches @cprm.pid. */ - if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid[PIDTYPE_TGID] != cprm->pid)) + if (WARN_ON_ONCE(!pids_equal(unix_peer(socket->sk)->sk_peer_pid, cprm->pid))) return false; cprm->limit = RLIM_INFINITY; diff --git a/fs/pidfs.c b/fs/pidfs.c index 586af2e5811cc2..29299b2c7ca71b 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -793,9 +793,9 @@ void pidfs_exit(struct task_struct *tsk) } #ifdef CONFIG_COREDUMP -void pidfs_coredump(const struct coredump_params *cprm) +static void pidfs_coredump_pid(struct pid *pid, + const struct coredump_params *cprm) { - struct pid *pid = cprm->pid; struct pidfs_attr *attr; attr = READ_ONCE(pid->attr); @@ -814,6 +814,13 @@ void pidfs_coredump(const struct coredump_params *cprm) smp_wmb(); set_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask); } + +void pidfs_coredump(const struct coredump_params *cprm) +{ + /* The dumping thread's pidfd reports the coredump as well. */ + for (enum pid_type type = PIDTYPE_PID; type <= pids_last(cprm->pid); type++) + pidfs_coredump_pid(cprm->pid[type], cprm); +} #endif static struct vfsmount *pidfs_mnt __ro_after_init; diff --git a/include/linux/coredump.h b/include/linux/coredump.h index 7b38ee2e7913be..0bbb7de6a40212 100644 --- a/include/linux/coredump.h +++ b/include/linux/coredump.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -32,7 +33,8 @@ struct coredump_params { int vma_count; size_t vma_data_size; struct core_vma_metadata *vma_meta; - struct pid *pid; + /* Dumping thread and its thread-group leader by pid type. */ + DECLARE_PIDS(pid, PIDTYPE_TGID); }; extern unsigned int core_file_note_size_limit; From 1f47b275d5d5aca297ef66a438f160a904789699 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Oct 2026 10:09:40 -0500 Subject: [PATCH 1598/2286] selftests/coredump: check the dumping thread's pidfd Crash from a non-leader thread and verify that the pidfd from SO_PEERPIDFD_THREAD on the coredump socket refers to that thread and reports the coredump like the thread-group leader's pidfd does. Reviewed-by: Alexander Mikhalitsyn Signed-off-by: Andrew Halaney Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-10-350183b6e02a@amutable.com Signed-off-by: Christian Brauner (Amutable) --- .../selftests/coredump/coredump_socket_test.c | 175 ++++++++++++++++++ .../selftests/coredump/coredump_test.h | 2 + .../coredump/coredump_test_helpers.c | 49 +++++ 3 files changed, 226 insertions(+) diff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c index 422728f632ca6c..ec73bb690bbcb0 100644 --- a/tools/testing/selftests/coredump/coredump_socket_test.c +++ b/tools/testing/selftests/coredump/coredump_socket_test.c @@ -592,6 +592,181 @@ TEST_F(coredump, socket_coredump_signal_sigsegv) wait_and_check_coredump_server(pid_coredump_server, _metadata, self); } +static bool check_coredump_info(const struct pidfd_info *info, const char *what) +{ + if (!(info->mask & PIDFD_INFO_COREDUMP)) { + fprintf(stderr, "%s: PIDFD_INFO_COREDUMP not set in mask\n", what); + return false; + } + + if (!(info->coredump_mask & PIDFD_COREDUMPED)) { + fprintf(stderr, "%s: PIDFD_COREDUMPED not set in coredump_mask\n", what); + return false; + } + + if (!(info->mask & PIDFD_INFO_COREDUMP_SIGNAL) || info->coredump_signal != SIGSEGV) { + fprintf(stderr, "%s: coredump_signal=%d, expected SIGSEGV=%d\n", + what, info->coredump_signal, SIGSEGV); + return false; + } + + if (!(info->mask & PIDFD_INFO_COREDUMP_CODE) || info->coredump_code != SEGV_MAPERR) { + fprintf(stderr, "%s: coredump_code=%d, expected SEGV_MAPERR=%d\n", + what, info->coredump_code, SEGV_MAPERR); + return false; + } + + return true; +} + +/* + * Test: PIDFD_INFO_COREDUMP on the dumping thread's pidfd + * + * Crash from a non-leader thread and verify that the pidfd from + * SO_PEERPIDFD_THREAD refers to that thread and reports the coredump + * like the thread-group leader's pidfd from SO_PEERPIDFD does. + */ +TEST_F(coredump, socket_coredump_thread) +{ + int pidfd, ret, status; + pid_t pid, pid_coredump_server; + struct pidfd_info info = {}; + int ipc_sockets[2]; + char c; + + ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket")); + + ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets); + ASSERT_EQ(ret, 0); + + pid_coredump_server = fork(); + ASSERT_GE(pid_coredump_server, 0); + if (pid_coredump_server == 0) { + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1; + int fd_thread_pidfd = -1, fd_core_file = -1; + struct pidfd_info thread_info = {}; + int exit_code = EXIT_FAILURE; + + close(ipc_sockets[0]); + + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket"); + if (fd_server < 0) { + fprintf(stderr, "socket_coredump_thread: listen socket failed: %m\n"); + goto out; + } + + if (write_nointr(ipc_sockets[1], "1", 1) < 0) { + fprintf(stderr, "socket_coredump_thread: ipc write failed: %m\n"); + goto out; + } + + close(ipc_sockets[1]); + + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC); + if (fd_coredump < 0) { + fprintf(stderr, "socket_coredump_thread: accept4 failed: %m\n"); + goto out; + } + + fd_peer_pidfd = get_peer_pidfd(fd_coredump); + if (fd_peer_pidfd < 0) { + fprintf(stderr, "socket_coredump_thread: get_peer_pidfd failed\n"); + goto out; + } + + fd_thread_pidfd = get_peer_pidfd_thread(fd_coredump); + if (fd_thread_pidfd < 0) { + fprintf(stderr, "socket_coredump_thread: get_peer_pidfd_thread failed\n"); + goto out; + } + + if (!get_pidfd_info(fd_peer_pidfd, &info) || + !get_pidfd_info(fd_thread_pidfd, &thread_info)) { + fprintf(stderr, "socket_coredump_thread: get_pidfd_info failed\n"); + goto out; + } + + /* The peer is the thread-group leader, the dumping thread is not. */ + if (info.pid != info.tgid || thread_info.tgid != info.tgid || + thread_info.pid == thread_info.tgid) { + fprintf(stderr, "socket_coredump_thread: unexpected ids %d/%d and %d/%d\n", + info.pid, info.tgid, thread_info.pid, thread_info.tgid); + goto out; + } + + if (!check_coredump_info(&info, "SO_PEERPIDFD") || + !check_coredump_info(&thread_info, "SO_PEERPIDFD_THREAD")) + goto out; + + fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached); + if (fd_core_file < 0) { + fprintf(stderr, "socket_coredump_thread: core tmpfile failed: %m\n"); + goto out; + } + + for (;;) { + char buffer[4096]; + ssize_t bytes_read, bytes_write; + + bytes_read = read(fd_coredump, buffer, sizeof(buffer)); + if (bytes_read < 0) { + fprintf(stderr, "socket_coredump_thread: core read failed: %m\n"); + goto out; + } + + if (bytes_read == 0) + break; + + bytes_write = write(fd_core_file, buffer, bytes_read); + if (bytes_read != bytes_write) { + fprintf(stderr, "socket_coredump_thread: core write %zd/%zd: %m\n", + bytes_read, bytes_write); + goto out; + } + } + + exit_code = EXIT_SUCCESS; + fprintf(stderr, "socket_coredump_thread: completed successfully\n"); +out: + if (fd_core_file >= 0) + close(fd_core_file); + if (fd_thread_pidfd >= 0) + close(fd_thread_pidfd); + if (fd_peer_pidfd >= 0) + close(fd_peer_pidfd); + if (fd_coredump >= 0) + close(fd_coredump); + if (fd_server >= 0) + close(fd_server); + _exit(exit_code); + } + self->pid_coredump_server = pid_coredump_server; + + EXPECT_EQ(close(ipc_sockets[1]), 0); + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1); + EXPECT_EQ(close(ipc_sockets[0]), 0); + + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) + crashing_child_thread(); + + pidfd = sys_pidfd_open(pid, 0); + ASSERT_GE(pidfd, 0); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(WTERMSIG(status), SIGSEGV); + ASSERT_TRUE(WCOREDUMP(status)); + + ASSERT_TRUE(get_pidfd_info(pidfd, &info)); + ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP)); + ASSERT_TRUE(!!(info.coredump_mask & PIDFD_COREDUMPED)); + ASSERT_EQ(info.coredump_signal, SIGSEGV); + + wait_and_check_coredump_server(pid_coredump_server, _metadata, self); +} + /* * Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump with SIGABRT * diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h index ed47f01fa53c52..4212656e31f01e 100644 --- a/tools/testing/selftests/coredump/coredump_test.h +++ b/tools/testing/selftests/coredump/coredump_test.h @@ -27,10 +27,12 @@ FIXTURE(coredump) /* Shared helper function declarations */ void *do_nothing(void *arg); void crashing_child(void); +void crashing_child_thread(void); int create_detached_tmpfs(void); int create_and_listen_unix_socket(const char *path); bool set_core_pattern(const char *pattern); int get_peer_pidfd(int fd); +int get_peer_pidfd_thread(int fd); bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info); /* Inline helper that uses harness types */ diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c index 2a20faf9cb0ad3..36306069f62e54 100644 --- a/tools/testing/selftests/coredump/coredump_test_helpers.c +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,10 @@ struct _fixture_coredump_data { #define NUM_THREAD_SPAWN 128 +#ifndef SO_PEERPIDFD_THREAD +#define SO_PEERPIDFD_THREAD 87 +#endif + void *do_nothing(void *arg) { (void)arg; @@ -59,6 +64,36 @@ void crashing_child(void) i = *(volatile int *)NULL; } +static void *crashing_thread(void *arg) +{ + int *p; + + (void)arg; + + /* crash on purpose with SEGV_MAPERR */ + p = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) + return NULL; + munmap(p, PAGE_SIZE); + *p = 0; + + return NULL; +} + +void crashing_child_thread(void) +{ + pthread_t thread; + int i; + + for (i = 0; i < NUM_THREAD_SPAWN; ++i) + pthread_create(&thread, NULL, do_nothing, NULL); + + /* crash from a non-leader thread */ + pthread_create(&thread, NULL, crashing_thread, NULL); + pause(); +} + int create_detached_tmpfs(void) { int fd_context, fd_tmpfs; @@ -138,6 +173,20 @@ int get_peer_pidfd(int fd) return fd_peer_pidfd; } +int get_peer_pidfd_thread(int fd) +{ + int fd_peer_pidfd; + socklen_t fd_peer_pidfd_len = sizeof(fd_peer_pidfd); + int ret = getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD_THREAD, &fd_peer_pidfd, + &fd_peer_pidfd_len); + if (ret < 0) { + fprintf(stderr, "%s: getsockopt(SO_PEERPIDFD_THREAD) failed: %m\n", __func__); + return -1; + } + fprintf(stderr, "%s: successfully retrieved pidfd %d\n", __func__, fd_peer_pidfd); + return fd_peer_pidfd; +} + bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info) { int ret; From 09a71b58a25d8166b6aed491c87d3c1e4425c702 Mon Sep 17 00:00:00 2001 From: Natasha Klaus Date: Wed, 30 Sep 2026 14:28:53 +0300 Subject: [PATCH 1599/2286] userns: reject uid_map/gid_map values that do not fit in u32 map_write() parses each line written to uid_map, gid_map or projid_map with simple_strtoul() and stores the results in the u32 fields of struct uid_gid_extent. On 64-bit kernels a value above U32_MAX is silently truncated modulo 2^32. The coding assistant flagged this truncation site while reading map_write(), and I confirmed it by hand on a running kernel, where writing to the uid_map of a fresh user namespace installs a different mapping than the one requested: "4294967296 1000 1" -> installed as "0 1000 1" "4294967297 1000 1" -> installed as "1 1000 1" "4294967301 1000 1" -> installed as "5 1000 1" The same happens to the lower_first and count fields. Only a literal of 2^64 or more is rejected, and only because simple_strtoul() saturates to ULLONG_MAX, which truncates onto the (u32)-1 value map_write() already refuses. On 32-bit kernels unsigned long is itself 32 bits wide, so the same truncation happens inside simple_strtoul(). The write succeeds, so a runtime that computes ranges and gets one wrong ends up with a different map than it asked for and no error to tell it so. Parse each field with kstrtou32() instead. A new helper, map_parse_u32(), terminates the field at the next whitespace or NUL, parses it and puts the original character back, leaving pos where simple_strtoul() used to leave it. The checks after each field, the trailing junk check and every later check are unchanged and run in the same order. A field that is not a decimal number fitting in u32 now fails the write with -EINVAL, like any other malformed line. Besides the range check, the one visible difference is that kstrtou32() accepts a single leading '+', so "+5 1000 1" is now installed as "5 1000 1" where it used to be rejected. This makes the parser more correct; it is not a security fix: the truncated values are valid ids and every later check still applies to them. Suggested-by: Eric W. Biederman Signed-off-by: Natasha Klaus Link: https://patch.msgid.link/20260930112854.373184-2-natalie.klaus@runtimeverification.com Acked-by: "Eric W. Biederman" Signed-off-by: Christian Brauner (Amutable) --- kernel/user_namespace.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 98b0279b6cc6e7..c3f4e1f288107e 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -935,6 +935,26 @@ static bool verify_root_map(const struct file *file, return true; } +/* + * Parse the decimal u32 at *pos, up to the next whitespace or NUL, and + * leave *pos pointing at that character. + */ +static int map_parse_u32(char **pos, u32 *res) +{ + char *end = *pos; + char c; + int ret; + + while (*end && !isspace(*end)) + end++; + c = *end; + *end = '\0'; + ret = kstrtou32(*pos, 10, res); + *end = c; + *pos = end; + return ret; +} + static ssize_t map_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos, int cap_setid, @@ -1007,17 +1027,20 @@ static ssize_t map_write(struct file *file, const char __user *buf, } pos = skip_spaces(pos); - extent.first = simple_strtoul(pos, &pos, 10); + if (map_parse_u32(&pos, &extent.first)) + goto out; if (!isspace(*pos)) goto out; pos = skip_spaces(pos); - extent.lower_first = simple_strtoul(pos, &pos, 10); + if (map_parse_u32(&pos, &extent.lower_first)) + goto out; if (!isspace(*pos)) goto out; pos = skip_spaces(pos); - extent.count = simple_strtoul(pos, &pos, 10); + if (map_parse_u32(&pos, &extent.count)) + goto out; if (*pos && !isspace(*pos)) goto out; From 8b7b7739ea6a8797391189c6d07897584b611f7f Mon Sep 17 00:00:00 2001 From: Natasha Klaus Date: Wed, 30 Sep 2026 14:28:54 +0300 Subject: [PATCH 1600/2286] selftests/namespaces: add uid_map/gid_map range tests Check that a uid_map or gid_map line with a value that does not fit in u32, or with a negative value, is rejected with EINVAL and installs nothing, for each of the three fields. Check that in-range lines are still accepted and read back unchanged: a plain single-id mapping, first id 0, the largest valid first id 4294967294, and a line with extra spaces between the fields. When run as root in the initial user namespace, also check that the largest count, "0 0 4294967295", is accepted when written by the parent into the uid_map of a child namespace; an unprivileged writer can only map a single id. Signed-off-by: Natasha Klaus Link: https://patch.msgid.link/20260930112854.373184-3-natalie.klaus@runtimeverification.com Acked-by: "Eric W. Biederman" Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/namespaces/.gitignore | 1 + tools/testing/selftests/namespaces/Makefile | 3 +- .../selftests/namespaces/uid_map_range_test.c | 299 ++++++++++++++++++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/namespaces/uid_map_range_test.c diff --git a/tools/testing/selftests/namespaces/.gitignore b/tools/testing/selftests/namespaces/.gitignore index 0989e80da4571f..995b082357fa66 100644 --- a/tools/testing/selftests/namespaces/.gitignore +++ b/tools/testing/selftests/namespaces/.gitignore @@ -10,3 +10,4 @@ cred_change_test stress_test listns_pagination_bug regression_pidfd_setns_test +uid_map_range_test diff --git a/tools/testing/selftests/namespaces/Makefile b/tools/testing/selftests/namespaces/Makefile index fbb821652c1787..9b22d12708e1aa 100644 --- a/tools/testing/selftests/namespaces/Makefile +++ b/tools/testing/selftests/namespaces/Makefile @@ -13,7 +13,8 @@ TEST_GEN_PROGS := nsid_test \ cred_change_test \ stress_test \ listns_pagination_bug \ - regression_pidfd_setns_test + regression_pidfd_setns_test \ + uid_map_range_test include ../lib.mk diff --git a/tools/testing/selftests/namespaces/uid_map_range_test.c b/tools/testing/selftests/namespaces/uid_map_range_test.c new file mode 100644 index 00000000000000..1eba1e609205ef --- /dev/null +++ b/tools/testing/selftests/namespaces/uid_map_range_test.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include "../kselftest_harness.h" + +/* + * The fields of a uid_map/gid_map line are stored in u32. Values that + * do not fit, and negative values, must be rejected with EINVAL instead + * of being silently truncated, and in-range values must still be + * accepted. + */ + +static int write_file(const char *path, const char *buf) +{ + ssize_t len = strlen(buf); + ssize_t ret; + int fd; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + ret = write(fd, buf, len); + if (ret < 0) + ret = -errno; + close(fd); + if (ret < 0) + return ret; + return ret == len ? 0 : -EIO; +} + +static ssize_t read_file(const char *path, char *buf, size_t size) +{ + ssize_t ret; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + ret = read(fd, buf, size - 1); + if (ret < 0) + ret = -errno; + else + buf[ret] = '\0'; + close(fd); + return ret; +} + +/* Enter a fresh user namespace, remembering the outer ids. */ +static int enter_userns(uid_t *uid, gid_t *gid) +{ + *uid = geteuid(); + *gid = getegid(); + if (unshare(CLONE_NEWUSER) < 0) + return -errno; + /* Required before an unprivileged gid_map write. */ + return write_file("/proc/self/setgroups", "deny"); +} + +static void expect_rejected(struct __test_metadata *_metadata, + const char *path, const char *line) +{ + char buf[256]; + + TH_LOG("writing \"%s\" to %s", line, path); + EXPECT_EQ(write_file(path, line), -EINVAL); + /* Nothing may have been installed. */ + EXPECT_EQ(read_file(path, buf, sizeof(buf)), 0) { + TH_LOG("installed: %s", buf); + } +} + +static void expect_accepted(struct __test_metadata *_metadata, + const char *path, const char *line, + unsigned int first, unsigned int lower_first, + unsigned int count) +{ + unsigned int f, l, c; + char buf[256]; + + TH_LOG("writing \"%s\" to %s", line, path); + ASSERT_EQ(write_file(path, line), 0); + + ASSERT_GT(read_file(path, buf, sizeof(buf)), 0); + ASSERT_EQ(sscanf(buf, "%u %u %u", &f, &l, &c), 3); + EXPECT_EQ(f, first); + EXPECT_EQ(l, lower_first); + EXPECT_EQ(c, count); + /* Exactly one extent. */ + EXPECT_EQ(strchr(buf, '\n'), buf + strlen(buf) - 1); +} + +TEST(uid_map_first_above_u32) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "4294967301 %u 1", uid); + expect_rejected(_metadata, "/proc/self/uid_map", line); +} + +TEST(uid_map_lower_first_above_u32) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "0 %llu 1", (1ULL << 32) + uid); + expect_rejected(_metadata, "/proc/self/uid_map", line); +} + +TEST(uid_map_count_above_u32) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "0 %u 4294967297", uid); + expect_rejected(_metadata, "/proc/self/uid_map", line); +} + +TEST(gid_map_first_above_u32) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "4294967301 %u 1", gid); + expect_rejected(_metadata, "/proc/self/gid_map", line); +} + +TEST(uid_map_first_negative) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "-1 %u 1", uid); + expect_rejected(_metadata, "/proc/self/uid_map", line); +} + +TEST(uid_map_lower_first_negative) +{ + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + expect_rejected(_metadata, "/proc/self/uid_map", "0 -1 1"); +} + +TEST(uid_map_count_negative) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "0 %u -1", uid); + expect_rejected(_metadata, "/proc/self/uid_map", line); +} + +TEST(uid_map_in_range) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "5 %u 1", uid); + expect_accepted(_metadata, "/proc/self/uid_map", line, 5, uid, 1); +} + +TEST(uid_map_zero) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "0 %u 1", uid); + expect_accepted(_metadata, "/proc/self/uid_map", line, 0, uid, 1); +} + +/* 4294967295 is (u32)-1, which map_write() refuses as a starting id. */ +TEST(uid_map_largest_first) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "4294967294 %u 1", uid); + expect_accepted(_metadata, "/proc/self/uid_map", line, + 4294967294U, uid, 1); +} + +TEST(uid_map_extra_spaces) +{ + char line[64]; + uid_t uid; + gid_t gid; + int ret; + + ret = enter_userns(&uid, &gid); + if (ret) + SKIP(return, "cannot set up user namespace: %s", strerror(-ret)); + snprintf(line, sizeof(line), "0 %u 1", uid); + expect_accepted(_metadata, "/proc/self/uid_map", line, 0, uid, 1); +} + +/* + * An unprivileged writer may only map a single id, so the largest count + * needs a privileged parent writing the map of a child namespace. With + * first and lower_first 0, the no-wrap rule allows up to 4294967295. + */ +TEST(uid_map_largest_count) +{ + unsigned int first, lower_first, count; + int to_child[2], from_child[2]; + char path[64], buf[256]; + pid_t pid; + char c; + + if (geteuid() != 0) + SKIP(return, "needs root in the initial user namespace"); + ASSERT_GT(read_file("/proc/self/uid_map", buf, sizeof(buf)), 0); + ASSERT_EQ(sscanf(buf, "%u %u %u", &first, &lower_first, &count), 3); + if (first != 0 || lower_first != 0 || count != 4294967295U) + SKIP(return, "not in the initial user namespace"); + + ASSERT_EQ(pipe(to_child), 0); + ASSERT_EQ(pipe(from_child), 0); + pid = fork(); + ASSERT_GE(pid, 0); + if (pid == 0) { + close(to_child[1]); + close(from_child[0]); + c = unshare(CLONE_NEWUSER) ? 'F' : 'K'; + if (write(from_child[1], &c, 1) != 1) + _exit(1); + /* Stay alive until the parent is done with our uid_map. */ + if (read(to_child[0], &c, 1) < 0) + _exit(1); + _exit(0); + } + close(to_child[0]); + close(from_child[1]); + + ASSERT_EQ(read(from_child[0], &c, 1), 1); + ASSERT_EQ(c, 'K'); + snprintf(path, sizeof(path), "/proc/%d/uid_map", pid); + expect_accepted(_metadata, path, "0 0 4294967295", 0, 0, 4294967295U); + + close(to_child[1]); + EXPECT_EQ(waitpid(pid, NULL, 0), pid); +} + +TEST_HARNESS_MAIN From 2ec5a070946165c66b4ee1190016d5a8e44dc055 Mon Sep 17 00:00:00 2001 From: Sahaj Chaudhari Date: Wed, 7 Oct 2026 17:19:14 +0530 Subject: [PATCH 1601/2286] selftests: pid_namespace: test fork with dead ancestor Add a regression test for fork() into a PID namespace whose parent namespace has lost its init process. Since the kernel cannot assign the new process a PID in that dead ancestor, fork() must fail with ENOMEM. Create the nested PID namespaces, keep the child namespace handle open after its init process exits, then enter it and verify fork() fails. Register the test with the PID namespace selftests and ignore its generated binary. Skip when namespace operations are denied or procfs cannot provide the pid_for_children handle. Signed-off-by: Sahaj Chaudhari Link: https://patch.msgid.link/20261007114914.1668934-1-sahaj123.sc@gmail.com Signed-off-by: Christian Brauner (Amutable) --- .../selftests/pid_namespace/.gitignore | 1 + .../testing/selftests/pid_namespace/Makefile | 2 +- .../pid_namespace/pidns_dead_ancestor.c | 174 ++++++++++++++++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c diff --git a/tools/testing/selftests/pid_namespace/.gitignore b/tools/testing/selftests/pid_namespace/.gitignore index c647c6eb336728..b87295802a73cb 100644 --- a/tools/testing/selftests/pid_namespace/.gitignore +++ b/tools/testing/selftests/pid_namespace/.gitignore @@ -1,3 +1,4 @@ pid_max +pidns_dead_ancestor pidns_init_via_setns regression_enomem diff --git a/tools/testing/selftests/pid_namespace/Makefile b/tools/testing/selftests/pid_namespace/Makefile index b01a924ac04bd8..3199e6bebbdd80 100644 --- a/tools/testing/selftests/pid_namespace/Makefile +++ b/tools/testing/selftests/pid_namespace/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 CFLAGS += -g $(KHDR_INCLUDES) -TEST_GEN_PROGS = regression_enomem pid_max pidns_init_via_setns +TEST_GEN_PROGS = regression_enomem pid_max pidns_dead_ancestor pidns_init_via_setns LOCAL_HDRS += $(selfdir)/pidfd/pidfd.h diff --git a/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c b/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c new file mode 100644 index 00000000000000..7524d4975ec752 --- /dev/null +++ b/tools/testing/selftests/pid_namespace/pidns_dead_ancestor.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#define STACK_SIZE (1024 * 1024) + +struct pidns_init_args { + int ready_read; + int ready_write; + int release_read; + int release_write; +}; + +static int pidns_init(void *data) +{ + struct pidns_init_args *args = data; + char release; + int error = 0; + + close(args->ready_read); + close(args->release_write); + if (unshare(CLONE_NEWPID)) + error = errno; + if (write(args->ready_write, &error, sizeof(error)) != sizeof(error)) + return 1; + close(args->ready_write); + if (error) + return 0; + if (read(args->release_read, &release, sizeof(release)) != sizeof(release)) + return 1; + close(args->release_read); + return 0; +} + +static int create_unborn_pidns(void) +{ + struct pidns_init_args args; + char path[64], release = 0; + void *stack; + pid_t child; + int ready[2], release_pipe[2], error, status, nsfd = -1; + ssize_t n; + + if (pipe(ready)) + return -1; + if (pipe(release_pipe)) { + error = errno; + close(ready[0]); + close(ready[1]); + errno = error; + return -1; + } + + stack = mmap(NULL, STACK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) { + error = errno; + close(ready[0]); + close(ready[1]); + close(release_pipe[0]); + close(release_pipe[1]); + errno = error; + return -1; + } + + args = (struct pidns_init_args) { + .ready_read = ready[0], + .ready_write = ready[1], + .release_read = release_pipe[0], + .release_write = release_pipe[1], + }; + child = clone(pidns_init, stack + STACK_SIZE, CLONE_NEWPID | SIGCHLD, + &args); + if (child < 0) + goto err_munmap; + + close(ready[1]); + close(release_pipe[0]); + n = read(ready[0], &error, sizeof(error)); + if (n != sizeof(error)) { + error = n < 0 ? errno : EIO; + goto release_child; + } + if (error) + goto release_child; + + snprintf(path, sizeof(path), "/proc/%d/ns/pid_for_children", child); + nsfd = open(path, O_RDONLY | O_CLOEXEC); + if (nsfd < 0) { + error = errno; + goto release_child; + } + +release_child: + write(release_pipe[1], &release, sizeof(release)); + close(release_pipe[1]); + close(ready[0]); + if (waitpid(child, &status, 0) != child || !WIFEXITED(status) || + WEXITSTATUS(status)) { + if (!error) + error = ECHILD; + if (nsfd >= 0) + close(nsfd); + nsfd = -1; + } + munmap(stack, STACK_SIZE); + if (error) { + errno = error; + return -1; + } + return nsfd; + +err_munmap: + error = errno; + munmap(stack, STACK_SIZE); + close(ready[0]); + close(ready[1]); + close(release_pipe[0]); + close(release_pipe[1]); + errno = error; + return -1; +} + +TEST(pidns_dead_ancestor_rejects_fork) +{ + int original_fd, target_fd, fork_errno, ret; + pid_t child; + + target_fd = create_unborn_pidns(); + if (target_fd < 0) { + if (errno == ENOENT) + SKIP(return, "PID namespace handle unavailable through procfs\n"); + if (errno == EPERM || errno == EACCES || errno == EINVAL) + SKIP(return, "PID namespace creation requires privileges\n"); + ASSERT_GE(target_fd, 0); + } + + original_fd = open("/proc/self/ns/pid_for_children", O_RDONLY | O_CLOEXEC); + ASSERT_GE(original_fd, 0); + ret = setns(target_fd, CLONE_NEWPID); + if (ret) { + if (errno == EPERM || errno == EACCES) + SKIP(return, "setns into PID namespace requires privileges\n"); + } + ASSERT_EQ(0, ret); + + child = fork(); + fork_errno = errno; + if (child == 0) + _exit(0); + + if (child > 0) + ASSERT_EQ(child, waitpid(child, NULL, 0)); + ret = setns(original_fd, CLONE_NEWPID); + close(original_fd); + close(target_fd); + ASSERT_EQ(0, ret); + + ASSERT_EQ(-1, child); + ASSERT_EQ(ENOMEM, fork_errno); +} + +TEST_HARNESS_MAIN From 12e19bba68ba4ef222033ff9a3b63df07b7fad38 Mon Sep 17 00:00:00 2001 From: Babanpreet Singh Date: Fri, 2 Oct 2026 01:21:41 +0000 Subject: [PATCH 1602/2286] pid: use READ_ONCE() in pid_alive() KCSAN reports pid_alive() reading task->thread_pid while it gets cleared under tasklist_lock. The check only cares about NULL, so READ_ONCE() and WRITE_ONCE() are enough. Reported-by: syzbot+c382ee653fd70f5cf1bb@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c382ee653fd70f5cf1bb Signed-off-by: Babanpreet Singh Link: https://patch.msgid.link/20261002012141.7-1-bbnpreetsingh@gmail.com Acked-by: Oleg Nesterov Reviewed-by: Bradley Morgan Signed-off-by: Christian Brauner (Amutable) --- include/linux/pid.h | 2 +- kernel/pid.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/pid.h b/include/linux/pid.h index f5476d7140a352..a0458c713e4e21 100644 --- a/include/linux/pid.h +++ b/include/linux/pid.h @@ -273,7 +273,7 @@ static inline pid_t task_tgid_nr(struct task_struct *tsk) */ static inline int pid_alive(const struct task_struct *p) { - return p->thread_pid != NULL; + return READ_ONCE(p->thread_pid) != NULL; } static inline pid_t task_pgrp_nr_ns(struct task_struct *tsk, struct pid_namespace *ns) diff --git a/kernel/pid.c b/kernel/pid.c index 95b8ccfa826906..adf72160676847 100644 --- a/kernel/pid.c +++ b/kernel/pid.c @@ -411,7 +411,7 @@ static void __change_pid(struct pid **pids, struct task_struct *task, pid = *pid_ptr; hlist_del_rcu(&task->pid_links[type]); - *pid_ptr = new; + WRITE_ONCE(*pid_ptr, new); for (tmp = PIDTYPE_MAX; --tmp >= 0; ) if (pid_has_task(pid, tmp)) From 123dcfe87d5f8c9a2ff489545e85c0345de9e21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20B=2E=20Marli=C3=A8re=20=28SUSE=29?= Date: Thu, 1 Oct 2026 09:21:56 -0300 Subject: [PATCH 1603/2286] selftests/namespaces: Wait for thread exit via pidfd in active-ref tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pthread_join() returns once exit_mm() wakes the CLEARTID futex, but the exiting thread drops its namespace reference later, in exit_nsproxy_namespaces(). The joining thread can therefore still see the namespace as active, making the final open_by_handle_at() check in thread_ns_inactive_after_exit and thread_ns_fd_keeps_active fail intermittently. Have the thread open a PIDFD_THREAD pidfd on itself and poll it before pthread_join(). A pidfd becomes readable from exit_notify(), which runs after exit_nsproxy_namespaces(). Fixes: 29f083c49982 ("selftests/namespace: first threaded active reference count test") Fixes: ee861032381b ("selftests/namespace: second threaded active reference count test") Signed-off-by: Ricardo B. Marlière (SUSE) Link: https://patch.msgid.link/20261001-selftests-namespaces-nsid_race-v1-1-25f542cf0b9c@marliere.net Signed-off-by: Christian Brauner (Amutable) --- .../selftests/namespaces/ns_active_ref_test.c | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/testing/selftests/namespaces/ns_active_ref_test.c b/tools/testing/selftests/namespaces/ns_active_ref_test.c index 093268f0efaa02..ab5ffe4de5213f 100644 --- a/tools/testing/selftests/namespaces/ns_active_ref_test.c +++ b/tools/testing/selftests/namespaces/ns_active_ref_test.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,10 @@ #include "../filesystems/utils.h" #include "wrappers.h" +#ifndef PIDFD_THREAD +#define PIDFD_THREAD O_EXCL +#endif + #ifndef FD_NSFS_ROOT #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ #endif @@ -2120,6 +2125,7 @@ TEST(ns_mixed_types_same_owner) /* Thread test helpers and structures */ struct thread_ns_info { __u64 ns_id; + int pidfd; int pipefd; int syncfd_read; int syncfd_write; @@ -2152,6 +2158,13 @@ static void *thread_create_namespace(void *arg) return NULL; } + /* Open a pidfd so the main thread can later poll it to detect actual thread exit. */ + info->pidfd = syscall(__NR_pidfd_open, gettid(), PIDFD_THREAD); + if (info->pidfd < 0) { + info->exit_code = 6; + return NULL; + } + /* Send namespace ID to main thread */ if (write(info->pipefd, &info->ns_id, sizeof(info->ns_id)) != sizeof(info->ns_id)) { info->exit_code = 4; @@ -2169,6 +2182,20 @@ static void *thread_create_namespace(void *arg) return NULL; } +/* + * pthread_join() returns once exit_mm() wakes the CLEARTID futex, before + * exit_nsproxy_namespaces() drops the namespace reference. A pidfd becomes + * readable only from exit_notify(), after that point. + */ +static int wait_thread_exited(int pidfd) +{ + struct pollfd pfd = { .fd = pidfd, .events = POLLIN }; + int ret = poll(&pfd, 1, -1); + + close(pidfd); + return ret; +} + /* * Test that namespace becomes inactive after thread exits. * This verifies active reference counting works with threads, not just processes. @@ -2234,6 +2261,7 @@ TEST(thread_ns_inactive_after_exit) close(syncpipe[1]); /* Wait for thread to exit */ + ASSERT_EQ(wait_thread_exited(info.pidfd), 1); ASSERT_EQ(pthread_join(thread, NULL), 0); close(pipefd[0]); close(pipefd[1]); @@ -2317,6 +2345,7 @@ TEST(thread_ns_fd_keeps_active) close(syncpipe[1]); /* Wait for thread to exit */ + ASSERT_EQ(wait_thread_exited(info.pidfd), 1); pthread_join(thread, NULL); close(pipefd[0]); close(pipefd[1]); From 9ba6627ef6b12debd1c54443939718db63234274 Mon Sep 17 00:00:00 2001 From: Bjoern Doebel Date: Thu, 1 Oct 2026 08:24:11 +0000 Subject: [PATCH 1604/2286] selftests/filesystems: Fix idmapped_tmpfile layer dir ownership The recent idmapped_tmpfile selftest creates a new directory in tmpfs owned by user ID 0. It then idmaps the mount as caller [0,10000) to disk [10000,20000). The original uid 0 owner does not exist in the mapped range. As a result, mapped_caller_creates_and_links fails at the O_TMPFILE open because inode_permission() rejects MAY_WRITE access: idmapped_tmpfile.c:145:mapped_caller_creates_and_links: Expected fd (-1) >= 0 (0) Chown the directory into the mapped range to satisfy the UID requirement. Fixes: d943e68edc5cb ("selftests/filesystems: test O_TMPFILE creation on idmapped mounts") Cc: stable@vger.kernel.org # 7.2+ Signed-off-by: Bjoern Doebel Link: https://patch.msgid.link/20261001082411.1493072-1-doebel@amazon.de Signed-off-by: Christian Brauner (Amutable) --- tools/testing/selftests/filesystems/idmapped_tmpfile.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/testing/selftests/filesystems/idmapped_tmpfile.c b/tools/testing/selftests/filesystems/idmapped_tmpfile.c index bc411ab8281eae..77b96d09435785 100644 --- a/tools/testing/selftests/filesystems/idmapped_tmpfile.c +++ b/tools/testing/selftests/filesystems/idmapped_tmpfile.c @@ -91,6 +91,13 @@ FIXTURE_SETUP(idmapped_tmpfile) ASSERT_EQ(mkdir(self->dir, 0777), 0); /* World-writable so an unmapped caller still passes permission(). */ ASSERT_EQ(chmod(self->dir, 0777), 0); + /* + * The directory itself must be inside the range the mount maps. + * Left owned by 0 it has no mapping in the mount, and + * inode_permission() then fails MAY_WRITE via HAS_UNMAPPED_ID() + * before any of the tmpfile logic is reached, regardless of mode. + */ + ASSERT_EQ(chown(self->dir, MAP_HOST, MAP_HOST), 0); } FIXTURE_TEARDOWN(idmapped_tmpfile) From 73b40395c71c510b82fd6104416ecde1e5ac6418 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 6 Oct 2026 23:23:56 -0700 Subject: [PATCH 1605/2286] mount: add OPEN_TREE_DROP_MNTNS_MOUNTS A recursive open_tree(OPEN_TREE_CLONE) copies the nsfs mounts of any mount namespaces pinned below the source. When the clone is attached inside a mount namespace younger than a pinned one, move_mount(2) fails with ELOOP from check_for_nsfs_mounts(), per the rule from commit 8823c079ba71 ("vfs: Add setns support for the mount namespace") that prevents mount namespace reference loops. This breaks container runtimes that use OPEN_TREE_NAMESPACE, such as crun [1]. The new namespace is younger than everything else, so bind mounting e.g. the host root fails on any host that pins a mount namespace (snapd does, under /run/snapd/ns). By the time it fails, setns() has already run and userspace cannot recover: the host tree is out of reach, and the offending mounts cannot be unmounted from the detached copy. copy_mnt_ns() and create_new_namespace() already leave these mounts behind; only get_detached_copy() copies them. Add an open_tree() flag to drop them from the clone, as suggested by Aleksa [2]. Locked nsfs mounts are dropped the same way copy_mnt_ns() does it, so nothing new is exposed. Keep it opt-in: open_tree(OPEN_TREE_CLONE) plus move_mount(2) is how mount --rbind works through a file descriptor, and in the caller's own or an older namespace such mounts remain usable. The flag requires AT_RECURSIVE, since a non-recursive clone has no submounts to drop, and OPEN_TREE_CLONE or OPEN_TREE_NAMESPACE. With the latter it is a no-op, so a runtime can pass it with any recursive open_tree() call. Link: https://github.com/containers/crun/issues/2262 [1] Link: https://lore.kernel.org/all/2026-01-07-oldest-grim-captions-spills-ywC2O3@cyphar.com/ [2] Signed-off-by: Kir Kolyshkin Link: https://patch.msgid.link/20261006-nsfs-prune-rfc-v3-1-adb97ea52868@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/namespace.c | 16 ++++++++++++++-- include/uapi/linux/mount.h | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 3ed3766f3a1837..15c3c68b7f6bab 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3165,7 +3165,8 @@ static struct mnt_namespace *get_detached_copy(const struct path *path, unsigned ns->seq_origin = src_mnt_ns->ns.ns_id; } - mnt = __do_loopback(path, (flags & AT_RECURSIVE), CL_COPY_MNT_NS_FILE); + mnt = __do_loopback(path, (flags & AT_RECURSIVE), + (flags & OPEN_TREE_DROP_MNTNS_MOUNTS) ? 0 : CL_COPY_MNT_NS_FILE); if (IS_ERR(mnt)) { emptied_ns = ns; return ERR_CAST(mnt); @@ -3317,7 +3318,18 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE | - OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE)) + OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE | + OPEN_TREE_DROP_MNTNS_MOUNTS)) + return ERR_PTR(-EINVAL); + + /* + * Only meaningful when a tree is copied recursively: a non-recursive + * clone has no submounts to drop. OPEN_TREE_NAMESPACE never copies + * pinned mount namespaces, so there the flag is a no-op. + */ + if ((flags & OPEN_TREE_DROP_MNTNS_MOUNTS) && + (!(flags & AT_RECURSIVE) || + !(flags & (OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)))) return ERR_PTR(-EINVAL); if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) == diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 2204708dbf7a58..ac865edac5173e 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -63,6 +63,7 @@ */ #define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ #define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) /* Drop mntns mounts from the clone */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ /* From e6e54e7117f6eee4ae42c9c24d95a1eb775bdbeb Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 6 Oct 2026 23:23:57 -0700 Subject: [PATCH 1606/2286] selftests/filesystems: test OPEN_TREE_DROP_MNTNS_MOUNTS Pin a mount namespace below a tmpfs mount, the way snapd does under /run/snapd/ns, and attach a recursive clone of that tmpfs inside a younger mount namespace created with OPEN_TREE_NAMESPACE. Without OPEN_TREE_DROP_MNTNS_MOUNTS move_mount(2) fails with ELOOP; with it, it succeeds. Also check that the flag is rejected without OPEN_TREE_CLONE or OPEN_TREE_NAMESPACE, and accepted with the latter. Signed-off-by: Kir Kolyshkin Link: https://patch.msgid.link/20261006-nsfs-prune-rfc-v3-2-adb97ea52868@gmail.com Signed-off-by: Christian Brauner (Amutable) --- .../open_tree_ns/open_tree_ns_test.c | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c index 82f3c8c02c9ac8..438054ab6a6a4c 100644 --- a/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c +++ b/tools/testing/selftests/filesystems/open_tree_ns/open_tree_ns_test.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,10 @@ #define OPEN_TREE_NAMESPACE (1 << 1) #endif +#ifndef OPEN_TREE_DROP_MNTNS_MOUNTS +#define OPEN_TREE_DROP_MNTNS_MOUNTS (1 << 2) +#endif + static int get_mnt_ns_id(int fd, uint64_t *mnt_ns_id) { if (ioctl(fd, NS_GET_MNTNS_ID, mnt_ns_id) < 0) @@ -1004,4 +1009,192 @@ TEST_F(open_tree_ns_unbindable, recursive_skips_on_unbindable) close(fd); } +/* + * Pin a mount namespace at @where, the way snapd does under /run/snapd/ns. + * The namespace has to be younger than ours, so a child unshares and the + * parent binds the child's namespace file: binding one's own namespace is + * refused by the very check this test is about. + */ +static int pin_mount_namespace(const char *where) +{ + int pipefd[2], status, ret = -1; + char path[PATH_MAX]; + pid_t pid; + char c; + + if (pipe(pipefd)) + return -1; + + pid = fork(); + if (pid < 0) + goto out; + if (pid == 0) { + close(pipefd[0]); + if (unshare(CLONE_NEWNS)) + _exit(1); + /* Tell the parent the namespace exists, then hold it open. */ + if (write(pipefd[1], "x", 1) != 1) + _exit(1); + pause(); + _exit(0); + } + + close(pipefd[1]); + pipefd[1] = -1; + if (read(pipefd[0], &c, 1) != 1) + goto out_kill; + + snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); + if (mount(path, where, NULL, MS_BIND, NULL)) + goto out_kill; + + ret = 0; + +out_kill: + kill(pid, SIGKILL); + waitpid(pid, &status, 0); +out: + close(pipefd[0]); + if (pipefd[1] >= 0) + close(pipefd[1]); + return ret; +} + +FIXTURE(open_tree_ns_drop_mntns) +{ + char dir[64]; + char pin[PATH_MAX]; + bool mounted; +}; + +FIXTURE_SETUP(open_tree_ns_drop_mntns) +{ + int fd, ret; + + self->mounted = false; + snprintf(self->dir, sizeof(self->dir), "/tmp/open_tree_ns_drop_mntns.XXXXXX"); + + ret = sys_open_tree(-1, NULL, 0); + if (ret == -1 && errno == ENOSYS) + SKIP(return, "open_tree() syscall not supported"); + + /* + * Work in a private mount namespace, so whatever is mounted here, + * the pinned namespace included, goes away with the test process. + */ + if (unshare(CLONE_NEWNS)) + SKIP(return, "unshare(CLONE_NEWNS) failed: %s", strerror(errno)); + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL), 0); + + ASSERT_NE(mkdtemp(self->dir), NULL); + if (mount("tmpfs", self->dir, "tmpfs", 0, NULL)) + SKIP(return, "Failed to mount tmpfs"); + self->mounted = true; + + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + if (fd < 0 && errno == EINVAL) + SKIP(return, "OPEN_TREE_DROP_MNTNS_MOUNTS not supported"); + ASSERT_GE(fd, 0); + close(fd); + + snprintf(self->pin, sizeof(self->pin), "%s/ns", self->dir); + fd = open(self->pin, O_CREAT | O_RDONLY | O_CLOEXEC, 0600); + ASSERT_GE(fd, 0); + close(fd); + ASSERT_EQ(pin_mount_namespace(self->pin), 0); +} + +FIXTURE_TEARDOWN(open_tree_ns_drop_mntns) +{ + if (self->mounted) + umount2(self->dir, MNT_DETACH); + rmdir(self->dir); +} + +/* Attach @clone_fd at "/" inside the namespace @ns_fd; returns errno. */ +static int move_into_namespace(int clone_fd, int ns_fd) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + int target; + + if (setns(ns_fd, CLONE_NEWNS)) + _exit(255); + target = open("/", O_PATH | O_DIRECTORY | O_CLOEXEC); + if (target < 0) + _exit(255); + if (sys_move_mount(clone_fd, "", target, "", + MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH)) + _exit(errno); + _exit(0); + } + if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) + return -1; + return WEXITSTATUS(status); +} + +TEST_F(open_tree_ns_drop_mntns, move_into_younger_namespace) +{ + int clone_fd, ns_fd; + + ns_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC); + ASSERT_GE(ns_fd, 0); + + /* + * Without the flag the clone carries the pinned namespace along, + * and move_mount() refuses it from inside a younger namespace. + */ + clone_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | AT_RECURSIVE | OPEN_TREE_CLOEXEC); + ASSERT_GE(clone_fd, 0); + EXPECT_EQ(move_into_namespace(clone_fd, ns_fd), ELOOP); + close(clone_fd); + + /* With the flag the pinned namespace is left out. */ + clone_fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + ASSERT_GE(clone_fd, 0); + EXPECT_EQ(move_into_namespace(clone_fd, ns_fd), 0); + close(clone_fd); + + close(ns_fd); +} + +TEST_F(open_tree_ns_drop_mntns, flag_combinations) +{ + int fd; + + /* Nothing is copied, so there is nothing to drop. */ + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_DROP_MNTNS_MOUNTS | OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + + /* A non-recursive clone has no submounts, so there is nothing to drop. */ + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_CLONE | OPEN_TREE_DROP_MNTNS_MOUNTS | + OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + EXPECT_LT(sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_DROP_MNTNS_MOUNTS | + OPEN_TREE_CLOEXEC), 0); + EXPECT_EQ(errno, EINVAL); + + /* OPEN_TREE_NAMESPACE never copies them; the flag is a no-op there. */ + fd = sys_open_tree(AT_FDCWD, self->dir, + OPEN_TREE_NAMESPACE | OPEN_TREE_DROP_MNTNS_MOUNTS | + AT_RECURSIVE | OPEN_TREE_CLOEXEC); + EXPECT_GE(fd, 0); + if (fd >= 0) + close(fd); +} + TEST_HARNESS_MAIN From 64571ba6581695e936cd701e345efdbe43e05920 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 2 Oct 2026 18:31:43 +0200 Subject: [PATCH 1607/2286] fs: rename vfs_get_super() to get_tree_super() and export it Rename and export it so filesystems that need a custom superblock matching policy can reuse it directly instead of open-coding sget_fc() + fill_super(). No functional change. This is a preparatory fix for the next patch. Signed-off-by: Giuseppe Scrivano Link: https://patch.msgid.link/20261002163225.1258203-2-giuseppe@scrivano.org Reviewed-by: Christoph Hellwig Reviewed-by: Jan Kara Signed-off-by: Christian Brauner (Amutable) --- fs/super.c | 19 +++++++++++++++---- include/linux/fs_context.h | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/fs/super.c b/fs/super.c index 01db6124e409dc..d97c8731e80ec0 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1389,7 +1389,17 @@ static int test_single_super(struct super_block *s, struct fs_context *fc) return 1; } -static int vfs_get_super(struct fs_context *fc, +/** + * get_tree_super - Get a superblock, optionally sharing an existing one + * @fc: The filesystem context holding the parameters + * @test: Comparison function to find a matching existing superblock, or NULL + * @fill_super: Helper to initialise a new superblock + * + * If @test is non-NULL and matches an existing superblock, that superblock is + * reused; otherwise a new anonymous superblock is created and initialised with + * @fill_super. Passing NULL for @test always creates a new superblock. + */ +int get_tree_super(struct fs_context *fc, int (*test)(struct super_block *, struct fs_context *), int (*fill_super)(struct super_block *sb, struct fs_context *fc)) @@ -1416,12 +1426,13 @@ static int vfs_get_super(struct fs_context *fc, deactivate_locked_super(sb); return err; } +EXPORT_SYMBOL_GPL(get_tree_super); int get_tree_nodev(struct fs_context *fc, int (*fill_super)(struct super_block *sb, struct fs_context *fc)) { - return vfs_get_super(fc, NULL, fill_super); + return get_tree_super(fc, NULL, fill_super); } EXPORT_SYMBOL(get_tree_nodev); @@ -1429,7 +1440,7 @@ int get_tree_single(struct fs_context *fc, int (*fill_super)(struct super_block *sb, struct fs_context *fc)) { - return vfs_get_super(fc, test_single_super, fill_super); + return get_tree_super(fc, test_single_super, fill_super); } EXPORT_SYMBOL(get_tree_single); @@ -1439,7 +1450,7 @@ int get_tree_keyed(struct fs_context *fc, void *key) { fc->s_fs_info = key; - return vfs_get_super(fc, test_keyed_super, fill_super); + return get_tree_super(fc, test_keyed_super, fill_super); } EXPORT_SYMBOL(get_tree_keyed); diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h index 0d6c8a6d7be21f..d1727dffecc5e2 100644 --- a/include/linux/fs_context.h +++ b/include/linux/fs_context.h @@ -150,6 +150,10 @@ extern int vfs_parse_fs_param_source(struct fs_context *fc, struct fs_parameter *param); extern void fc_drop_locked(struct fs_context *fc); +int get_tree_super(struct fs_context *fc, + int (*test)(struct super_block *, struct fs_context *), + int (*fill_super)(struct super_block *sb, + struct fs_context *fc)); extern int get_tree_nodev(struct fs_context *fc, int (*fill_super)(struct super_block *sb, struct fs_context *fc)); From c6e78a4080a3b310a9f61db778d6dee7bc646cb7 Mon Sep 17 00:00:00 2001 From: "Aneesh Kumar K.V (Arm)" Date: Sat, 10 Oct 2026 15:32:49 +0530 Subject: [PATCH 1608/2286] firmware: smccc: Pass only the required device probe arguments The device probe passes zeroes for all seven argument registers to arm_smccc_1_1_invoke(). On ARM32 Thumb-2, this binds an input to r7 and GCC 8.5 reports "r7 cannot be used in asm here". The current smccc_devices entries need at most one argument after the function ID: TRNG_VERSION has none and RSI_ABI_VERSION takes a requested version. Pass zero for that argument and omit the unused arguments, avoiding the r7 constraint. Check argument requirements when adding new probe functions to smccc_devices. Cc: Lorenzo Pieralisi Cc: Mark Rutland Cc: Sudeep Holla Cc: Will Deacon Cc: Steven Price Cc: Suzuki K Poulose Cc: Andre Przywara Cc: Russell King Fixes: d8263a131bb0 ("firmware: hwrng: arm_smccc_trng: Register as an SMCCC device") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202610070641.B8hxBJcf-lkp@intel.com/ Signed-off-by: Aneesh Kumar K.V (Arm) Reviewed-by: Sudeep Holla Reviewed-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- drivers/firmware/smccc/smccc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c index afc2e4d602ba62..5dc768ac4143a7 100644 --- a/drivers/firmware/smccc/smccc.c +++ b/drivers/firmware/smccc/smccc.c @@ -116,8 +116,7 @@ static bool __init smccc_probe_smccc_device(const struct smccc_device_info *smcc if (IS_ENABLED(CONFIG_ARM) && ARM_SMCCC_IS_64(smccc_dev->func_id)) return false; - arm_smccc_1_1_invoke(smccc_dev->func_id, - 0, 0, 0, 0, 0, 0, 0, &res); + arm_smccc_1_1_invoke(smccc_dev->func_id, 0, &res); ret = res.a0; if (ret == SMCCC_RET_NOT_SUPPORTED) From 8ea457f746d915d55f78b43d33b1cd618f3d93ad Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Fri, 9 Oct 2026 12:32:35 -0400 Subject: [PATCH 1609/2286] arm_mpam: make the mon_sel guard conditional only Building arm64 allmodconfig with clang fails: drivers/resctrl/mpam_internal.h:207:7: error: ignoring return value of function declared with 'warn_unused_result' attribute [-Werror,-Wunused-result] 207 | mpam_mon_sel_lock(_T), mpam_mon_sel_unlock(_T)); mpam_mon_sel_lock() is __must_check and can fail: for an MSC accessed through the MPAM-Fb firmware interface it returns false when called from a context that cannot sleep. The mon_sel guard was defined with DEFINE_GUARD(), which calls it unconditionally and discards the result, so a guard(mon_sel) user would carry on without the lock and release it on scope exit. The unconditional guard only exists as the base for the conditional mon_sel_lock variant, which is the one actually used. Define mon_sel_lock directly as a conditional guard class instead, as posix-timers does for lock_timer: the constructor returns NULL when the lock cannot be taken, and the destructor only releases a lock that was taken. ACQUIRE(mon_sel_lock, ...) works as before, including ACQUIRE_ERR() returning -EBUSY on failure, and there is no unconditional variant left to misuse. Reported-by: kernelci.org bot Closes: https://d.kernelci.org/i/maestro:66f869ecbbb5b8592562ffd89f049c5ddf682547 Closes: https://d.kernelci.org/i/maestro:647269216758644837afcaac8e67d8dbe713a0e2 Fixes: 0db1715e4c9c ("arm_mpam: propagate MSC access errors for hw_probe functions") Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: Ben Horgan Signed-off-by: Catalin Marinas --- drivers/resctrl/mpam_internal.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/resctrl/mpam_internal.h b/drivers/resctrl/mpam_internal.h index ee7c3953a9b6c9..1f86cc158100be 100644 --- a/drivers/resctrl/mpam_internal.h +++ b/drivers/resctrl/mpam_internal.h @@ -203,9 +203,10 @@ static inline int mpam_mon_sel_lock_init(struct device *dev, return devm_mutex_init(dev, &msc->mon_sel_mutex); } -DEFINE_GUARD(mon_sel, struct mpam_msc *, - mpam_mon_sel_lock(_T), mpam_mon_sel_unlock(_T)); -DEFINE_GUARD_COND(mon_sel, _lock, mpam_mon_sel_lock(_T), _RET); +DEFINE_CLASS(mon_sel_lock, struct mpam_msc *, + if (_T) mpam_mon_sel_unlock(_T), + mpam_mon_sel_lock(msc) ? msc : NULL, struct mpam_msc *msc); +DEFINE_CLASS_IS_COND_GUARD(mon_sel_lock); /* Bits for mpam features bitmaps */ enum mpam_device_features { From cea68e7489a05cda0173eccf000260ede74410e3 Mon Sep 17 00:00:00 2001 From: Catalin Marinas Date: Sat, 10 Oct 2026 01:19:45 +0800 Subject: [PATCH 1610/2286] arm64: cpufeature: Add ARM64_CPUCAP_SOC_ERRATUM for SoC-level errata Add a new capability type for SoC-level (not CPU-level) errata. Unlike CPU errata which need to be checked on every CPU, SoC errata are probed once on the boot CPU and the result applies to all CPUs. Secondary and late CPUs skip the matches() check but still get cpu_enable() called. This avoids redundant probing on secondary and late CPUs for errata that are SoC-wide properties, not per-CPU. Convert ARM64_ERRATUM_4311569 from ARM64_CPUCAP_SYSTEM_FEATURE to ARM64_CPUCAP_SOC_ERRATUM since it is a SoC-level property already determined at boot. This changes its scope from SCOPE_SYSTEM to SCOPE_BOOT_CPU, so the workaround is now enabled before the secondary CPUs are brought up. This is safe as the erratum is enabled via an early_param, parsed before the boot CPU capabilities are detected. Co-developed-by: Peng Fan Signed-off-by: Peng Fan Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/cpufeature.h | 24 ++++++++++++++++++++++++ arch/arm64/kernel/cpu_errata.c | 2 +- arch/arm64/kernel/cpufeature.c | 5 ++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index 4f04ad82ea340a..cbbc350bf335eb 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -187,6 +187,10 @@ extern struct arm64_ftr_reg arm64_ftr_reg_ctrel0; * b) SCOPE_LOCAL_CPU, SCOPE_SYSTEM: All CPUs hotplugged on by the * user after the kernel boot are verified against the capability. * + * SCOPE_BOOT_CPU and SCOPE_SYSTEM capabilities with + * ARM64_CPUCAP_PROBE_ONCE are not matched again on these CPUs. They + * take the state already detected, so there cannot be a conflict. + * * If there is a conflict, the kernel takes an action, based on the * severity (e.g, a CPU could be prevented from booting or cause a * kernel panic). The CPU is allowed to "affect" the state of the @@ -266,6 +270,13 @@ extern struct arm64_ftr_reg arm64_ftr_reg_ctrel0; #define SCOPE_BOOT_CPU ARM64_CPUCAP_SCOPE_BOOT_CPU #define SCOPE_ALL ARM64_CPUCAP_SCOPE_MASK +/* + * matches() is called only once, when the capability is detected, and the + * result applies to all CPUs. Secondary and late CPUs are not checked for + * conflicts but cpu_enable() is still called on each of them. It has no + * effect on SCOPE_LOCAL_CPU capabilities. + */ +#define ARM64_CPUCAP_PROBE_ONCE ((u16)BIT(3)) /* * Is it permitted for a late CPU to have this capability when system * hasn't already enabled it ? @@ -293,6 +304,13 @@ extern struct arm64_ftr_reg arm64_ftr_reg_ctrel0; */ #define ARM64_CPUCAP_LOCAL_CPU_ERRATUM \ (ARM64_CPUCAP_SCOPE_LOCAL_CPU | ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU) +/* + * SoC (not CPU) errata workarounds. The erratum is probed once on the boot + * CPU, before the secondary CPUs are brought up, and no secondary or late CPU + * can conflict with it. + */ +#define ARM64_CPUCAP_SOC_ERRATUM \ + (ARM64_CPUCAP_SCOPE_BOOT_CPU | ARM64_CPUCAP_PROBE_ONCE) /* * CPU feature detected at boot time based on system-wide value of a * feature. It is safe for a late CPU to have this feature even though @@ -414,6 +432,12 @@ static inline bool cpucap_match_all_early_cpus(const struct arm64_cpu_capabiliti return cap->type & ARM64_CPUCAP_MATCH_ALL_EARLY_CPUS; } +static inline bool cpucap_probe_once(const struct arm64_cpu_capabilities *cap) +{ + return (cap->type & ARM64_CPUCAP_PROBE_ONCE) && + !(cap->type & ARM64_CPUCAP_SCOPE_LOCAL_CPU); +} + /* * Generic helper for handling capabilities with multiple (match,enable) pairs * of call backs, sharing the same capability bit. diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index b33dccfafaf8c3..eb1e8d2c6e0748 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -971,7 +971,7 @@ const struct arm64_cpu_capabilities arm64_errata[] = { #ifdef CONFIG_ARM64_ERRATUM_4311569 { .capability = ARM64_WORKAROUND_4311569, - .type = ARM64_CPUCAP_SYSTEM_FEATURE, + .type = ARM64_CPUCAP_SOC_ERRATUM, .matches = need_arm_si_l1_workaround_4311569, }, #endif diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa7b..ab5c207a98bef8 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -3694,8 +3694,11 @@ static void verify_local_cpu_caps(u16 scope_mask) if (!caps || !(caps->type & scope_mask)) continue; - cpu_has_cap = caps->matches(caps, SCOPE_LOCAL_CPU); system_has_cap = cpus_have_cap(caps->capability); + if (cpucap_probe_once(caps)) + cpu_has_cap = system_has_cap; + else + cpu_has_cap = caps->matches(caps, SCOPE_LOCAL_CPU); if (system_has_cap) { /* From 3bd2779b6cfdb67a05bc52b86a1d9cd2e31cdcc2 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 22 Sep 2026 20:10:55 +0800 Subject: [PATCH 1611/2286] ACPICA: actbl2.h: Add bit constants of the Flags field in the IOVT IOMMU structure According to the IOVT specification, define macros for each bit of the Flags field in the IOVT IOMMU structure. Link: https://github.com/open-acpica/acpica/commit/352f750e4cc0 Signed-off-by: Bibo Mao Link: https://patch.msgid.link/20260922121058.972734-2-maobibo@loongson.cn Signed-off-by: Rafael J. Wysocki --- include/acpi/actbl2.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h index baef525367b5c4..76e4a5bc79b42c 100644 --- a/include/acpi/actbl2.h +++ b/include/acpi/actbl2.h @@ -919,6 +919,13 @@ struct acpi_iovt_iommu { u32 device_entry_offset; }; +/* Masks for Flags field above */ +#define ACPI_IOVT_IOMMU_PCI_DEVICE (1<<0) +#define ACPI_IOVT_IOMMU_PXM_VALID (1<<1) +#define ACPI_IOVT_IOMMU_MAGAGE_BY_SEGMENT (1<<2) +#define ACPI_IOVT_IOMMU_HW_CAP_SUPPORT (1<<3) +#define ACPI_IOVT_IOMMU_MSI_INT_BYPASS (1<<4) + struct acpi_iovt_device_entry { u8 type; u8 length; From f341307a14895e5da056fb419a31ef7fb534ab18 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 25 Sep 2026 08:09:11 -0700 Subject: [PATCH 1612/2286] ACPI: APEI: accumulate the queued flag across error sections ghes_do_proc() walks every error section of a CPER record, and ghes_handle_arm_hw_error() walks every error-info entry of an ARM processor section. Both assign to "queued" instead of OR-ing into it, so only the last iteration counts: a later section or entry that queues no memory-failure work clears the flag an earlier one set. Instead of reassigning the variable at every time, just do an `OR`, so, if there was a single match, it will return "queued" as set. The comment above the check says "If no memory failure work is queued", which is the semantics this patch implements. I.e, only SIGBUS if there are queue was not handled at all. If we want to SIGBUS if any event was not handled/queued at all, we will need something more sophisticated. Fixes: 7f17b4a121d0 ("ACPI: APEI: Kick the memory_failure() queue for synchronous errors") Fixes: ccb5ecdc2dde ("ACPI: APEI: fix synchronous external aborts in user-mode") Cc: All applicable Signed-off-by: Breno Leitao Reviewed-by: Shuai Xue Link: https://patch.msgid.link/20260925-ghes-queued-accumulate-v1-1-df50553957d4@debian.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index ade1cc8c9f8d14..663dc2e601cf72 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -603,7 +603,7 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, * and don't filter out 'corrected' error here. */ if (is_cache && has_pa) { - queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags); + queued |= ghes_do_memory_failure(err_info->physical_fault_addr, flags); p += err_info->length; continue; } @@ -1000,11 +1000,11 @@ static void ghes_do_proc(struct ghes *ghes, atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); arch_apei_report_mem_error(sev, mem_err); - queued = ghes_handle_memory_failure(gdata, sev, sync); + queued |= ghes_handle_memory_failure(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { ghes_handle_aer(gdata); } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { - queued = ghes_handle_arm_hw_error(gdata, sev, sync); + queued |= ghes_handle_arm_hw_error(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); From 5c94956f2bfb87025c1eaded6c51d5841d38d333 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 2 Oct 2026 19:45:50 +0900 Subject: [PATCH 1613/2286] rust: bug: update the SAFETY comment for warn_slowpath_fmt() on UML The SAFETY comment says that it is always safe to call `warn_slowpath_fmt()` with a valid null-terminated string. This is not true. The taint argument must also be a valid taint number. Commit 951d09ca450c ("rust: bug: pass TAINT_WARN to warn_slowpath_fmt() on UML") fixed a bug where it was not. List each condition that the call meets: the file name, the taint number, and the null `fmt`. Suggested-by: Miguel Ojeda Signed-off-by: FUJITA Tomonori Link: https://patch.msgid.link/20261002104550.2877930-1-tomo@flapping.org Cc: stable@vger.kernel.org Fixes: dff64b072708 ("rust: Add warn_on macro") Signed-off-by: Miguel Ojeda --- rust/kernel/bug.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rust/kernel/bug.rs b/rust/kernel/bug.rs index 6abed2c055b1f5..3b40b0bd37b500 100644 --- a/rust/kernel/bug.rs +++ b/rust/kernel/bug.rs @@ -88,8 +88,10 @@ macro_rules! warn_flags { _ = $flags; } - // SAFETY: It is always safe to call `warn_slowpath_fmt()` - // with a valid null-terminated string. + // SAFETY: + // - The file name is a null-terminated string with `'static` lifetime. + // - `TAINT_WARN` is a valid taint number. + // - `fmt` can be NULL, and NULL is passed. unsafe { $crate::bindings::warn_slowpath_fmt( $crate::str::CStrExt::as_char_ptr($crate::c_str!(::core::file!())), From 691c33289b4d7a50b6fe1e03918bfbc21d4641c5 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 23:58:46 +0800 Subject: [PATCH 1614/2286] ACPI: PCI: pci_link: clear driver_data before free acpi_pci_link_remove() frees the link object without clearing the ACPI device's driver_data pointer. The ACPI device survives hot-removal and can later be retrieved by acpi_fetch_acpi_dev(), leaving callers with a dangling pointer. Clear driver_data before freeing the link. Fixes: db89b4f0dbab ("ACPI: catch calls of acpi_driver_data on pointer of wrong type") Signed-off-by: Jiale Yao [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260925155846.2381956-1-yaojiale02@163.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pci_link.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index e6ed13aee48d0c..84bfcdf242ddc9 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -787,6 +787,7 @@ static void acpi_pci_link_remove(struct acpi_device *device) list_del(&link->list); mutex_unlock(&acpi_link_lock); + device->driver_data = NULL; kfree(link); } From d44e08dab27d908510f526dc27c4529b3167be4c Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sat, 10 Oct 2026 01:19:46 +0800 Subject: [PATCH 1615/2286] arm64: errata: Add NXP i.MX8QM/8QP workaround for A53 cache coherency issue According to NXP errata document IMX8_1N94W[1], the i.MX8QuadMax and i.MX8QuadPlus SoCs suffer from a cache coherency issue (ERR050104). The upper bits, above bit 35, of the ARADDR and ACADDR buses within the Arm A53 subsystem have been incorrectly connected. This causes some TLBI and IC maintenance operations exchanged between the A53 and A72 core clusters to be corrupted. The workaround requires: - Downgrading targeted TLBI operations to broadcast-all variants. Instead of patching the low-level __TLBI_1 macro (which causes excessive over-invalidation), redirect high-level TLB flush functions (flush_tlb_mm, __do_flush_tlb_range, flush_tlb_kernel_range, __flush_tlb_kernel_pgtable) to use VMALLE1IS via alternatives. - Upgrading IC IVAU to IC IALLUIS for both kernel (via ALTERNATIVE in invalidate_icache_by_line) and EL0 userspace (via trap-and-upgrade in user_cache_maint_handler with SCTLR_EL1.UCI=0). - Disabling KVM since correct TLB maintenance cannot be guaranteed for guests. - No need to touch SMMU Broadcast TLB Maintenance (BTM) since the arm-smmu driver does not use it. SoC detection uses devicetree compatible string "fsl,imx8qm" or "fsl,imx8qp" since the boot CPU MIDR_EL1 (0x410fd034) and AIDR_EL1 (0) are not unique to these SoCs. [1] https://www.nxp.com/docs/en/errata/IMX8_1N94W.pdf [ Reworked per review feedback from Will Deacon and Mark Rutland: - Move TLBI workaround from __TLBI_1 macro to high-level flush functions to avoid excessive over-invalidation - Add kernel IC IVAU upgrade via ALTERNATIVE in assembler.h - Add cpucap_is_possible() entry for compile-time elimination] Tested-by: Franz Schnyder # Toradex Apalis iMX8QM Co-developed-by: Ivan T. Ivanov Signed-off-by: Ivan T. Ivanov Signed-off-by: Peng Fan [catalin.marinas@arm.com: Kconfig and errata text, sysreg_clear_set(), of_machine_compatible_match()] Signed-off-by: Catalin Marinas --- Documentation/arch/arm64/silicon-errata.rst | 2 + arch/arm64/Kconfig | 22 ++++++++ arch/arm64/include/asm/assembler.h | 5 ++ arch/arm64/include/asm/cpucaps.h | 2 + arch/arm64/include/asm/kvm_host.h | 2 + arch/arm64/include/asm/tlbflush.h | 62 +++++++++++++-------- arch/arm64/kernel/cpu_errata.c | 38 +++++++++++++ arch/arm64/kernel/traps.c | 2 + arch/arm64/kvm/arm.c | 5 ++ arch/arm64/tools/cpucaps | 1 + 10 files changed, 117 insertions(+), 24 deletions(-) diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst index ac3248b9f2f3bb..b7221b77b72ecf 100644 --- a/Documentation/arch/arm64/silicon-errata.rst +++ b/Documentation/arch/arm64/silicon-errata.rst @@ -327,6 +327,8 @@ stable kernels. +----------------+-----------------+-----------------+-----------------------------+ | Freescale/NXP | LS2080A/LS1043A | A-008585 | FSL_ERRATUM_A008585 | +----------------+-----------------+-----------------+-----------------------------+ +| Freescale/NXP | i.MX 8QM/8QP | ERR050104 | NXP_IMX8QM_ERRATUM_ERR050104| ++----------------+-----------------+-----------------+-----------------------------+ +----------------+-----------------+-----------------+-----------------------------+ | Hisilicon | Hip0{5,6,7} | #161010101 | HISILICON_ERRATUM_161010101 | +----------------+-----------------+-----------------+-----------------------------+ diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig index b5a51b0ef9440a..ee2c9cd36db1a3 100644 --- a/arch/arm64/Kconfig +++ b/arch/arm64/Kconfig @@ -1455,6 +1455,28 @@ config ROCKCHIP_ERRATUM_3588001 If unsure, say Y. +config NXP_IMX8QM_ERRATUM_ERR050104 + bool "NXP iMX8QM/QP ERR050104: broken cache/TLB invalidation broadcast" + default y + help + On iMX8QM (QuadMax) and iMX8QP (QuadPlus), address bits above bit 35 + in the A53 subsystem ARADDR and ACADDR buses are incorrectly + connected. This corrupts targeted TLBI and IC broadcasts exchanged + between the A53 and A72 core clusters. + + Work around this by redirecting targeted TLBI operations to + broadcast-all variants (VMALLE1IS) in the high-level TLB flush + functions, upgrading IC IVAU to IC IALLUIS for both kernel and + user-space, and disabling KVM since correct TLB maintenance + cannot be guaranteed for guests. + + The user-space IC IVAU is upgraded by trapping it to the kernel + (SCTLR_EL1.UCI cleared). This also traps the user DC CVAU, DC CVAC, + DC CVAP, DC CVADP and DC CIVAC instructions, making user cache + maintenance slower on the affected SoCs. + + If unsure, say Y. + config SOCIONEXT_SYNQUACER_PREITS bool "Socionext Synquacer: Workaround for GICv3 pre-ITS" default y diff --git a/arch/arm64/include/asm/assembler.h b/arch/arm64/include/asm/assembler.h index 0b58b550e8dce7..43c13700f17dfc 100644 --- a/arch/arm64/include/asm/assembler.h +++ b/arch/arm64/include/asm/assembler.h @@ -451,6 +451,10 @@ alternative_else_nop_endif * Corrupts: tmp1, tmp2 */ .macro invalidate_icache_by_line start, end, tmp1, tmp2, fixup +alternative_if ARM64_WORKAROUND_NXP_ERR050104 + ic ialluis + b .Licache_done\@ +alternative_else_nop_endif icache_line_size \tmp1, \tmp2 sub \tmp2, \tmp1, #1 bic \tmp2, \start, \tmp2 @@ -459,6 +463,7 @@ alternative_else_nop_endif add \tmp2, \tmp2, \tmp1 cmp \tmp2, \end b.lo .Licache_op\@ +.Licache_done\@: dsb ish isb diff --git a/arch/arm64/include/asm/cpucaps.h b/arch/arm64/include/asm/cpucaps.h index 76350b38f0d7a8..0c3bfbe99aada6 100644 --- a/arch/arm64/include/asm/cpucaps.h +++ b/arch/arm64/include/asm/cpucaps.h @@ -66,6 +66,8 @@ cpucap_is_possible(const unsigned int cap) return IS_ENABLED(CONFIG_ARM64_ERRATUM_3194386); case ARM64_WORKAROUND_4193714: return IS_ENABLED(CONFIG_ARM64_ERRATUM_4193714); + case ARM64_WORKAROUND_NXP_ERR050104: + return IS_ENABLED(CONFIG_NXP_IMX8QM_ERRATUM_ERR050104); case ARM64_MPAM: /* * KVM MPAM support doesn't rely on the host kernel supporting MPAM. diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 27fe0cd5b2d7a1..762ceb56c05837 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -73,8 +73,10 @@ enum kvm_mode { }; #ifdef CONFIG_KVM enum kvm_mode kvm_get_mode(void); +void kvm_force_disabled(void); #else static inline enum kvm_mode kvm_get_mode(void) { return KVM_MODE_NONE; }; +static inline void kvm_force_disabled(void) { }; #endif extern unsigned int __ro_after_init kvm_sve_max_vl; diff --git a/arch/arm64/include/asm/tlbflush.h b/arch/arm64/include/asm/tlbflush.h index 14a78ac0f8009a..a05520d3a0131e 100644 --- a/arch/arm64/include/asm/tlbflush.h +++ b/arch/arm64/include/asm/tlbflush.h @@ -378,9 +378,13 @@ static inline void flush_tlb_mm(struct mm_struct *mm) unsigned long asid; dsb(ishst); - asid = __TLBI_VADDR(0, ASID(mm)); - __tlbi(aside1is, asid); - __tlbi_user(aside1is, asid); + if (alternative_has_cap_unlikely(ARM64_WORKAROUND_NXP_ERR050104)) { + __tlbi(vmalle1is); + } else { + asid = __TLBI_VADDR(0, ASID(mm)); + __tlbi(aside1is, asid); + __tlbi_user(aside1is, asid); + } __tlbi_sync_s1ish(mm); mmu_notifier_arch_invalidate_secondary_tlbs(mm, 0, -1UL); } @@ -580,23 +584,28 @@ static __always_inline void __do_flush_tlb_range(struct vm_area_struct *vma, asid = ASID(mm); - switch (flags & (TLBF_NOWALKCACHE | TLBF_NOBROADCAST)) { - case TLBF_NONE: - __flush_s1_tlb_range_op(vae1is, start, pages, stride, - asid, tlb_level); - break; - case TLBF_NOWALKCACHE: - __flush_s1_tlb_range_op(vale1is, start, pages, stride, - asid, tlb_level); - break; - case TLBF_NOBROADCAST: - /* Combination unused */ - BUG(); - break; - case TLBF_NOWALKCACHE | TLBF_NOBROADCAST: - __flush_s1_tlb_range_op(vale1, start, pages, stride, - asid, tlb_level); - break; + if (alternative_has_cap_unlikely(ARM64_WORKAROUND_NXP_ERR050104) && + !(flags & TLBF_NOBROADCAST)) { + __tlbi(vmalle1is); + } else { + switch (flags & (TLBF_NOWALKCACHE | TLBF_NOBROADCAST)) { + case TLBF_NONE: + __flush_s1_tlb_range_op(vae1is, start, pages, stride, + asid, tlb_level); + break; + case TLBF_NOWALKCACHE: + __flush_s1_tlb_range_op(vale1is, start, pages, stride, + asid, tlb_level); + break; + case TLBF_NOBROADCAST: + /* Combination unused */ + BUG(); + break; + case TLBF_NOWALKCACHE | TLBF_NOBROADCAST: + __flush_s1_tlb_range_op(vale1, start, pages, stride, + asid, tlb_level); + break; + } } if (!(flags & TLBF_NONOTIFY)) @@ -657,7 +666,8 @@ static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end end = round_up(end, stride); pages = (end - start) >> PAGE_SHIFT; - if (__flush_tlb_range_limit_excess(pages, stride)) { + if (alternative_has_cap_unlikely(ARM64_WORKAROUND_NXP_ERR050104) || + __flush_tlb_range_limit_excess(pages, stride)) { flush_tlb_all(); return; } @@ -675,10 +685,14 @@ static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end */ static inline void __flush_tlb_kernel_pgtable(unsigned long kaddr) { - unsigned long addr = __TLBI_VADDR(kaddr, 0); - dsb(ishst); - __tlbi(vaae1is, addr); + if (alternative_has_cap_unlikely(ARM64_WORKAROUND_NXP_ERR050104)) { + __tlbi(vmalle1is); + } else { + unsigned long addr = __TLBI_VADDR(kaddr, 0); + + __tlbi(vaae1is, addr); + } __tlbi_sync_s1ish_kernel(); isb(); } diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c index eb1e8d2c6e0748..06034bc267388f 100644 --- a/arch/arm64/kernel/cpu_errata.c +++ b/arch/arm64/kernel/cpu_errata.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include static u64 target_impl_cpu_num; @@ -200,6 +202,33 @@ cpu_enable_cache_maint_trap(const struct arm64_cpu_capabilities *__unused) sysreg_clear_set(sctlr_el1, SCTLR_EL1_UCI, 0); } +#ifdef CONFIG_NXP_IMX8QM_ERRATUM_ERR050104 +static bool +is_imx8qm_soc(const struct arm64_cpu_capabilities *entry, int scope) +{ + static const char * const compats[] = { + "fsl,imx8qm", + "fsl,imx8qp", + NULL + }; + + return of_machine_compatible_match(compats); +} + +static void +cpu_enable_imx8qm_err050104(const struct arm64_cpu_capabilities *__unused) +{ + /* Trap the EL0 IC IVAU so that it can be upgraded to IC IALLUIS */ + sysreg_clear_set(sctlr_el1, SCTLR_EL1_UCI, 0); + + /* + * TLB maintenance cannot be guaranteed correct for guests, so + * disable KVM as if kvm-arm.mode=none was passed on the command line. + */ + kvm_force_disabled(); +} +#endif + #define CAP_MIDR_RANGE(model, v_min, r_min, v_max, r_max) \ .matches = is_affected_midr_range, \ .midr_range = MIDR_RANGE(model, v_min, r_min, v_max, r_max) @@ -1030,6 +1059,15 @@ const struct arm64_cpu_capabilities arm64_errata[] = { .type = ARM64_CPUCAP_SYSTEM_FEATURE, .matches = has_broken_gic_v3_seis, }, +#ifdef CONFIG_NXP_IMX8QM_ERRATUM_ERR050104 + { + .desc = "NXP erratum ERR050104", + .capability = ARM64_WORKAROUND_NXP_ERR050104, + .type = ARM64_CPUCAP_SOC_ERRATUM, + .matches = is_imx8qm_soc, + .cpu_enable = cpu_enable_imx8qm_err050104, + }, +#endif { } }; diff --git a/arch/arm64/kernel/traps.c b/arch/arm64/kernel/traps.c index 91428201606989..d6391ab20db38b 100644 --- a/arch/arm64/kernel/traps.c +++ b/arch/arm64/kernel/traps.c @@ -586,6 +586,8 @@ static void user_cache_maint_handler(unsigned long esr, struct pt_regs *regs) break; case ESR_ELx_SYS64_ISS_CRM_IC_IVAU: /* IC IVAU */ __user_cache_maint("ic ivau", address, ret); + if (cpus_have_final_cap(ARM64_WORKAROUND_NXP_ERR050104) && !ret) + asm volatile("ic ialluis"); break; default: force_signal_inject(SIGILL, ILL_ILLOPC, regs->pc, 0); diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 8b080804bc90b2..d010d9eb3ad706 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -3159,6 +3159,11 @@ static int __init early_kvm_mode_cfg(char *arg) } early_param("kvm-arm.mode", early_kvm_mode_cfg); +void kvm_force_disabled(void) +{ + kvm_mode = KVM_MODE_NONE; +} + static int __init early_kvm_wfx_trap_policy_cfg(char *arg, enum kvm_wfx_trap_policy *p) { if (!arg) diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps index 2775ba3359cfe3..6875ae501dcbfc 100644 --- a/arch/arm64/tools/cpucaps +++ b/arch/arm64/tools/cpucaps @@ -125,6 +125,7 @@ WORKAROUND_DEVICE_LOAD_ACQUIRE WORKAROUND_DISABLE_CNP WORKAROUND_GICv3_BROKEN_SEIS WORKAROUND_NVIDIA_OLYMPUS_1027 +WORKAROUND_NXP_ERR050104 WORKAROUND_PMUV3_IMPDEF_TRAPS WORKAROUND_QCOM_FALKOR_E1003 WORKAROUND_QCOM_ORYON_CNTVOFF From cd4108319bb3355a54252377eeaa610e8d603ba7 Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 2 Oct 2026 18:31:44 +0200 Subject: [PATCH 1616/2286] erofs: reuse superblock for file-backed mounts When the same file-backed image is mounted multiple times (via path or fd) with the superblock_share mount option, reuse the existing superblock instead of creating a new one. This allows multiple mounts of the same image to share in-kernel data structures more efficiently. The backing file is identified by its inode and fsoffset. If mount options conflict or extra devices are used, a separate superblock is created transparently as a fallback. Remount is not allowed on shared superblocks. Tested by mounting a 177M Fedora EROFS image 20 times with full traversal: #!/bin/sh IMG=${1:-/root/fedora.erofs} OPTS=${2:+-o $2} N=20 DIR=$(mktemp -d) trap "umount $DIR/m* 2>/dev/null; rm -rf $DIR" EXIT sync; echo 3 > /proc/sys/vm/drop_caches INODES_BEFORE=$(grep erofs_inode /proc/slabinfo | awk '{print $2}') MEM_BEFORE=$(grep ^Slab: /proc/meminfo | awk '{print $2}') for i in $(seq 1 $N); do mkdir $DIR/m$i mount -t erofs $OPTS "$IMG" $DIR/m$i find $DIR/m$i > /dev/null done echo "Superblocks: $(grep $DIR /proc/self/mountinfo | \ awk '{print $3}' | sort -u | wc -l)" echo "erofs_inode delta: +$(( $(grep erofs_inode /proc/slabinfo | \ awk '{print $2}') - INODES_BEFORE ))" echo "Slab delta: +$(( $(grep ^Slab: /proc/meminfo | \ awk '{print $2}') - MEM_BEFORE )) kB" without superblock_share: # time ./test.sh /root/fedora.erofs Superblocks: 20 erofs_inode delta: +45864 Slab delta: +37448 kB real 0m2.311s user 0m0.222s sys 0m1.998s with superblock_share: # time ./test.sh /root/fedora.erofs superblock_share Superblocks: 1 erofs_inode delta: +2044 Slab delta: +284 kB real 0m0.679s user 0m0.157s sys 0m0.469s The time difference shows that sharing the superblock also benefits the page cache and inode cache, as subsequent mounts of the same image avoid re-reading the backing file. This is particularly useful for container hosts running multiple containers from the same base image. Signed-off-by: Giuseppe Scrivano Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- Documentation/filesystems/erofs.rst | 7 ++++ fs/erofs/internal.h | 1 + fs/erofs/super.c | 56 +++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/erofs.rst b/Documentation/filesystems/erofs.rst index d301d9ac946a9a..02caf3b061fed7 100644 --- a/Documentation/filesystems/erofs.rst +++ b/Documentation/filesystems/erofs.rst @@ -139,6 +139,9 @@ inode_share Enable inode page sharing for this filesystem. Inodes wi page cache. source=%s (For file-backed mounts) Specify the backing image as a path or as an already-opened file descriptor. +superblock_share (For file-backed mounts) Share the superblock when the same + backing file is mounted more than once with compatible + options. Remount is not allowed on shared superblocks. =================== ========================================================= File-backed mounts @@ -156,6 +159,10 @@ Only regular files are accepted as backing files; to mount an image that resides on a block device, use the traditional block device mount path instead. +When superblock_share is specified, multiple mounts of the same backing +file at the same fsoffset with compatible mount options can share a single +superblock. Remount is not allowed on shared superblocks. + Sysfs Entries ============= diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 12e3a5b80a5a63..da58053c111692 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -150,6 +150,7 @@ struct erofs_sb_info { #define EROFS_MOUNT_DAX_NEVER 0x00000080 #define EROFS_MOUNT_DIRECT_IO 0x00000100 #define EROFS_MOUNT_INODE_SHARE 0x00000200 +#define EROFS_MOUNT_SUPERBLOCK_SHARE 0x00000400 #define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option) #define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 0967aace9f7ff3..16fa3b853f1b6e 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -386,7 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi) enum { Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum, Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share, - Opt_source, + Opt_source, Opt_superblock_share, }; static const struct constant_table erofs_param_cache_strategy[] = { @@ -415,6 +415,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = { fsparam_u64("fsoffset", Opt_fsoffset), fsparam_flag("inode_share", Opt_inode_share), fsparam_file_or_string("source", Opt_source), + fsparam_flag("superblock_share", Opt_superblock_share), {} }; @@ -560,6 +561,12 @@ static int erofs_fc_parse_param(struct fs_context *fc, else set_opt(&sbi->opt, INODE_SHARE); break; + case Opt_superblock_share: + if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE)) + errorfc(fc, "%s option not supported", erofs_fs_parameters[opt].name); + else + set_opt(&sbi->opt, SUPERBLOCK_SHARE); + break; case Opt_source: return erofs_fc_parse_source(fc, param); } @@ -667,6 +674,16 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) errorfc(fc, "FSDAX is not allowed when inode_share is on"); return -EINVAL; } + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) && + test_opt(&sbi->opt, INODE_SHARE)) { + errorfc(fc, "superblock_share is not allowed when inode_share is on"); + return -EINVAL; + } + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) && + sbi->devs->extra_devices) { + errorfc(fc, "superblock_share does not support extra devices"); + return -EINVAL; + } sbi->blkszbits = PAGE_SHIFT; if (erofs_is_fileio_mode(sbi)) { @@ -794,6 +811,35 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc) return 0; } +/* + * The function is used only with a sbi that has SUPERBLOCK_SHARE set, + * so that the memcmp later ensures new_sbi is also shareable. + */ +static int erofs_sb_share_test_super(struct super_block *sb, + struct fs_context *fc) +{ + struct erofs_sb_info *sbi = EROFS_SB(sb); + struct erofs_sb_info *new_sbi = fc->s_fs_info; + + if (sb->s_iflags & SB_I_RETIRED) + return 0; + if (!sbi->dif0.file || !new_sbi->dif0.file) + return 0; + return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) && + sbi->dif0.fsoff == new_sbi->dif0.fsoff && + !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt)); +} + +static int erofs_file_get_tree(struct fs_context *fc) +{ + struct erofs_sb_info *sbi = fc->s_fs_info; + + if (!test_opt(&sbi->opt, SUPERBLOCK_SHARE)) + return get_tree_nodev(fc, erofs_fc_fill_super); + + return get_tree_super(fc, erofs_sb_share_test_super, erofs_fc_fill_super); +} + static int erofs_fc_get_tree(struct fs_context *fc) { struct erofs_sb_info *sbi = fc->s_fs_info; @@ -809,7 +855,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) errorfc(fc, "source is unsupported"); return -EINVAL; } - return get_tree_nodev(fc, erofs_fc_fill_super); + return erofs_file_get_tree(fc); } ret = get_tree_bdev_flags(fc, erofs_fc_fill_super, @@ -827,7 +873,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) && sbi->dif0.file->f_mapping->a_ops->read_folio) - return get_tree_nodev(fc, erofs_fc_fill_super); + return erofs_file_get_tree(fc); } return ret; } @@ -840,6 +886,10 @@ static int erofs_fc_reconfigure(struct fs_context *fc) DBG_BUGON(!sb_rdonly(sb)); + /* Shared superblocks must not be reconfigured. */ + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE)) + return -EBUSY; + if (new_sbi->domain_id) erofs_info(sb, "ignoring reconfiguration for domain_id."); From 94e45b21882be65d50fe6c50d3bfe52793db28e5 Mon Sep 17 00:00:00 2001 From: Jingbo Xu Date: Mon, 21 Sep 2026 11:04:17 +0800 Subject: [PATCH 1617/2286] MAINTAINERS: erofs: update my email address This email address will not be available in a few days. Update my own email address to jefflexu@163.com. Signed-off-by: Jingbo Xu Signed-off-by: Jingbo Xu Acked-by: Gao Xiang Signed-off-by: Gao Xiang --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 50d3875a8c2d9b..5de9c27ecba828 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9683,7 +9683,7 @@ EROFS FILE SYSTEM M: Gao Xiang M: Chao Yu R: Yue Hu -R: Jeffle Xu +R: Jingbo Xu R: Sandeep Dhavale R: Hongbo Li R: Chunhai Guo From 215d7b6622733603751f546b9faf9b84b0c281d4 Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Fri, 9 Oct 2026 17:16:01 +0200 Subject: [PATCH 1618/2286] arm64/efi: Clear PTE_WRITE when applying EFI_MEMORY_RO set_permissions() sets PTE_RDONLY for regions that the Memory Attributes Table describes as read-only, but leaves PTE_WRITE set. PTE_WRITE is the DBM bit, so when hardware dirty state management is enabled (TCR_EL1.HD), such a descriptor is writable-clean rather than read-only, and a write by the firmware will be permitted by the hardware (which marks the entry dirty) instead of faulting. Clear PTE_WRITE as well, as pte_wrprotect() does. Cc: stable@vger.kernel.org Signed-off-by: Ard Biesheuvel Signed-off-by: Catalin Marinas --- arch/arm64/kernel/efi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kernel/efi.c b/arch/arm64/kernel/efi.c index 0ec90fd1754edc..6f34aef3d04eff 100644 --- a/arch/arm64/kernel/efi.c +++ b/arch/arm64/kernel/efi.c @@ -117,8 +117,14 @@ static int __init set_permissions(pte_t *ptep, unsigned long addr, void *data) const efi_memory_desc_t *md = spd->md; pte_t pte = __ptep_get(ptep); + /* + * Clear PTE_WRITE as well: it doubles as the DBM bit, and leaving it + * set would permit the hardware to make the mapping writable again + * when TCR_EL1.HD is set. + */ if (md->attribute & EFI_MEMORY_RO) - pte = set_pte_bit(pte, __pgprot(PTE_RDONLY)); + pte = set_pte_bit(clear_pte_bit(pte, __pgprot(PTE_WRITE)), + __pgprot(PTE_RDONLY)); if (md->attribute & EFI_MEMORY_XP) pte = set_pte_bit(pte, __pgprot(PTE_PXN)); else if (system_supports_bti_kernel() && spd->has_bti) From 12aeb0dc6405edf24056eae4d01202d696c78dca Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Sat, 8 Aug 2026 14:13:55 +0800 Subject: [PATCH 1619/2286] btrfs: free unlinked replace target on initialization failure btrfs_init_dev_replace_tgtdev() allocates the replacement target before looking up its dev_t and initializing its zoned device information. If either lookup_bdev() or btrfs_get_dev_zone_info() fails, the device has not been linked into fs_devices->devices yet, but the error path only drops the block device file reference. Free the allocated device on this error path to release its name, allocation state, zone info, and the device itself. The issue was found by a failure-path metadata residual analyzer and verified with targeted failure injection on v6.14. Assisted-by: Codex:gpt-5 Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/dev-replace.c | 10 +++++++--- fs/btrfs/volumes.c | 2 +- fs/btrfs/volumes.h | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/dev-replace.c b/fs/btrfs/dev-replace.c index 0284be0e4e826d..cdfe093e5c5c4f 100644 --- a/fs/btrfs/dev-replace.c +++ b/fs/btrfs/dev-replace.c @@ -235,7 +235,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, struct btrfs_device **device_out) { struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; - struct btrfs_device *device; + struct btrfs_device *device = NULL; + struct btrfs_device *tmp_device; struct file *bdev_file; struct block_device *bdev; u64 devid = BTRFS_DEV_REPLACE_DEVID; @@ -264,8 +265,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, sync_blockdev(bdev); - list_for_each_entry(device, &fs_devices->devices, dev_list) { - if (device->bdev == bdev) { + list_for_each_entry(tmp_device, &fs_devices->devices, dev_list) { + if (tmp_device->bdev == bdev) { btrfs_err(fs_info, "target device is in the filesystem!"); ret = -EEXIST; @@ -285,6 +286,7 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, device = btrfs_alloc_device(NULL, &devid, NULL, device_path); if (IS_ERR(device)) { ret = PTR_ERR(device); + device = NULL; goto error; } @@ -328,6 +330,8 @@ static int btrfs_init_dev_replace_tgtdev(struct btrfs_fs_info *fs_info, error: /* Undo the open-time freeze deny. */ + if (device) + btrfs_free_device(device); btrfs_release_device_allow_freeze(bdev_file); return ret; } diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 85ea9c5d4536c8..95cfb2664a789f 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -403,7 +403,7 @@ static struct btrfs_fs_devices *alloc_fs_devices(const u8 *fsid) return fs_devs; } -static void btrfs_free_device(struct btrfs_device *device) +void btrfs_free_device(struct btrfs_device *device) { WARN_ON(!list_empty(&device->post_commit_list)); /* diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h index 0415d74cad9ba9..337d7007d9e225 100644 --- a/fs/btrfs/volumes.h +++ b/fs/btrfs/volumes.h @@ -799,6 +799,7 @@ void btrfs_rm_dev_replace_remove_srcdev(struct btrfs_device *srcdev); void btrfs_rm_dev_replace_free_srcdev(struct btrfs_device *srcdev); void btrfs_destroy_dev_replace_tgtdev(struct btrfs_device *tgtdev, bool allow_freeze); +void btrfs_free_device(struct btrfs_device *device); unsigned long btrfs_full_stripe_len(struct btrfs_fs_info *fs_info, u64 logical); u64 btrfs_calc_stripe_length(const struct btrfs_chunk_map *map); From f5166f53b60b4d1ee9957c0baccd3b09735001d4 Mon Sep 17 00:00:00 2001 From: Guanghui Yang <3497809730@qq.com> Date: Tue, 11 Aug 2026 09:02:27 +0930 Subject: [PATCH 1620/2286] btrfs: roll back sprout setup after device add failure btrfs_init_new_device() calls btrfs_setup_sprout() before creating the first writable chunks for a seed filesystem. That moves the seed devices out of fs_info->fs_devices, clears the seeding state and installs a new fsid for the sprout filesystem. If a later step fails, the error path removes the new device but leaves fs_info->fs_devices in the partially initialized sprout state. The mounted filesystem can then be left with no open devices after the failed device add. Add the inverse of btrfs_setup_sprout() and use it from the error path so the mounted seed filesystem is restored before the temporary seed_devices copy is released. Fixes: 2b82032c34ec ("Btrfs: Seed device support") Assisted-by: Codex:gpt-5 Reviewed-by: Qu Wenruo Signed-off-by: Guanghui Yang <3497809730@qq.com> [ Fix a conflict with per-profile available space, revert sprout before updating per-profile available space estimation. ] Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 95cfb2664a789f..1fad1aa716debf 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -2814,6 +2814,41 @@ static void btrfs_setup_sprout(struct btrfs_fs_info *fs_info, btrfs_set_super_flags(disk_super, super_flags); } +static void btrfs_rollback_sprout(struct btrfs_fs_info *fs_info, + struct btrfs_fs_devices *seed_devices) +{ + struct btrfs_fs_devices *fs_devices = fs_info->fs_devices; + struct btrfs_super_block *disk_super = fs_info->super_copy; + struct btrfs_device *device; + u64 super_flags; + + lockdep_assert_held(&uuid_mutex); + lockdep_assert_held(&fs_devices->device_list_mutex); + + list_del_init(&seed_devices->seed_list); + list_splice_init_rcu(&seed_devices->devices, &fs_devices->devices, synchronize_rcu); + list_for_each_entry(device, &fs_devices->devices, dev_list) { + device->fs_devices = fs_devices; + } + + fs_devices->seeding = true; + fs_devices->num_devices = seed_devices->num_devices; + fs_devices->open_devices = seed_devices->open_devices; + fs_devices->missing_devices = seed_devices->missing_devices; + fs_devices->rotating = seed_devices->rotating; + fs_devices->latest_dev = seed_devices->latest_dev; + + memcpy(fs_devices->fsid, seed_devices->fsid, BTRFS_FSID_SIZE); + memcpy(fs_devices->metadata_uuid, seed_devices->metadata_uuid, BTRFS_FSID_SIZE); + memcpy(disk_super->fsid, seed_devices->fsid, BTRFS_FSID_SIZE); + + super_flags = (btrfs_super_flags(disk_super) | BTRFS_SUPER_FLAG_SEEDING); + btrfs_set_super_flags(disk_super, super_flags); + + seed_devices->opened = 0; + free_fs_devices(seed_devices); +} + /* * Store the expected generation for seed devices in device items. */ @@ -3165,6 +3200,8 @@ int btrfs_init_new_device(struct btrfs_fs_info *fs_info, const char *device_path orig_super_total_bytes); btrfs_set_super_num_devices(fs_info->super_copy, orig_super_num_devices); + if (seeding_dev) + btrfs_rollback_sprout(fs_info, seed_devices); btrfs_update_per_profile_avail(fs_info); mutex_unlock(&fs_info->chunk_mutex); mutex_unlock(&fs_info->fs_devices->device_list_mutex); From fdf56b9d03d475881d73b75afbdbe4058db04ffc Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 17 Aug 2026 17:00:43 +0930 Subject: [PATCH 1621/2286] btrfs: refactor read_key_bytes() to remove the dest_folio parameter The function read_key_bytes() have 3 call sites: - For BTRFS_VERITY_DESC_ITEM_KEY offset 0 inside btrfs_get_verity_descriptor() - For BTRFS_VERITY_DESC_ITEM_KEY offset 1 inside btrfs_get_verity_descriptor() Those are to read the description items, which are pretty small with fixed item size. Those call sites do not utilize the @dest_folio parameter. - For btrfs_read_merkle_tree_page() This is to read the BTRFS_VERITY_MERKLE_ITEM_KEY, which can be pretty large and split into multiple items. This is the only call site utilizing the @dest_folio parameter. Just for the only btrfs_read_merkle_tree_page() call site, we have a complex scheme for @dest and @dest_folio parameters. Since @dest can be NULL, it means if we pass @dest as NULL, then no matter if @dest_folio is provided, the merkle data will not be loaded into that @dest_folio. This can lead to a bug where a highmem folio is not mapped, then we pass folio_address(folio), which is NULL, into read_key_bytes(), causing no data to be written into @dest_folio. To address the complex scheme between @dest and @dest_folio, remove the @dest_folio parameter completely, and let the only caller to map the folio and pass the mapped kernel address into read_key_bytes() instead. This not only reduces the parameter list, but also make it much clear on the @dest parameter handling. The only downside is a longer duration of locally mapped page, but this should still be fine, as kmap_local_folio() can survive context switch. Reported-by: Hongling Zeng Link: https://lore.kernel.org/linux-btrfs/20260817022012.19658-1-zenghongling@kylinos.cn/ Fixes: 146054090b08 ("btrfs: initial fsverity support") Reviewed-by: David Sterba Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/verity.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/fs/btrfs/verity.c b/fs/btrfs/verity.c index 600337a84fbee5..83dc7dee14cfb3 100644 --- a/fs/btrfs/verity.c +++ b/fs/btrfs/verity.c @@ -286,21 +286,17 @@ static int write_key_bytes(struct btrfs_inode *inode, u8 key_type, u64 offset, * @dest: Buffer to read into. This parameter has slightly tricky * semantics. If it is NULL, the function will not do any copying * and will just return the size of all the items up to len bytes. - * If dest_page is passed, then the function will kmap_local the - * page and ignore dest, but it must still be non-NULL to avoid the - * counting-only behavior. * @len: length in bytes to read - * @dest_folio: copy into this folio instead of the dest buffer * * Helper function to read items from the btree. This returns the number of * bytes read or < 0 for errors. We can return short reads if the items don't * exist on disk or aren't big enough to fill the desired length. Supports - * reading into a provided buffer (dest) or into the page cache + * reading into a provided buffer (dest). * * Returns number of bytes read or a negative error code on failure. */ static int read_key_bytes(struct btrfs_inode *inode, u8 key_type, u64 offset, - char *dest, u64 len, struct folio *dest_folio) + char *dest, u64 len) { BTRFS_PATH_AUTO_FREE(path); struct btrfs_root *root = inode->root; @@ -320,7 +316,11 @@ static int read_key_bytes(struct btrfs_inode *inode, u8 key_type, u64 offset, if (!path) return -ENOMEM; - if (dest_folio) + /* + * Merkle items can be large and split across multiple items, so enable + * readahead for such cases. + */ + if (key_type == BTRFS_VERITY_MERKLE_ITEM_KEY) path->reada = READA_FORWARD; key.objectid = btrfs_ino(inode); @@ -364,7 +364,7 @@ static int read_key_bytes(struct btrfs_inode *inode, u8 key_type, u64 offset, break; } - /* desc = NULL to just sum all the item lengths */ + /* dest == NULL to just sum all the item lengths */ if (!dest) copy_end = item_end; else @@ -377,16 +377,10 @@ static int read_key_bytes(struct btrfs_inode *inode, u8 key_type, u64 offset, copy_offset = offset - key.offset; if (dest) { - if (dest_folio) - kaddr = kmap_local_folio(dest_folio, 0); - data = btrfs_item_ptr(leaf, path->slots[0], void); read_extent_buffer(leaf, kaddr + dest_offset, (unsigned long)data + copy_offset, copy_bytes); - - if (dest_folio) - kunmap_local(kaddr); } offset += copy_bytes; @@ -677,7 +671,7 @@ int btrfs_get_verity_descriptor(struct inode *inode, void *buf, size_t buf_size) memset(&item, 0, sizeof(item)); ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_DESC_ITEM_KEY, 0, - (char *)&item, sizeof(item), NULL); + (char *)&item, sizeof(item)); if (ret < 0) return ret; @@ -694,7 +688,7 @@ int btrfs_get_verity_descriptor(struct inode *inode, void *buf, size_t buf_size) return -ERANGE; ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_DESC_ITEM_KEY, 1, - buf, buf_size, NULL); + buf, buf_size); if (ret < 0) return ret; if (ret != true_size) @@ -720,6 +714,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, struct folio *folio; u64 off = (u64)index << PAGE_SHIFT; loff_t merkle_pos = merkle_file_pos(inode); + void *kaddr; int ret; if (merkle_pos < 0) @@ -763,6 +758,7 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, } read_folio: + kaddr = kmap_local_folio(folio, 0); /* * Merkle item keys are indexed from byte 0 in the merkle tree. * They have the form: @@ -770,7 +766,8 @@ static struct page *btrfs_read_merkle_tree_page(struct inode *inode, * [ inode objectid, BTRFS_MERKLE_ITEM_KEY, offset in bytes ] */ ret = read_key_bytes(BTRFS_I(inode), BTRFS_VERITY_MERKLE_ITEM_KEY, off, - folio_address(folio), PAGE_SIZE, folio); + kaddr, PAGE_SIZE); + kunmap_local(kaddr); if (ret < 0) { folio_unlock(folio); folio_put(folio); From 15ed4a938b30fcb72e96187f36e2c4ff0e6e2227 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 19 Aug 2026 10:36:16 +0930 Subject: [PATCH 1622/2286] btrfs: replace btrfs_repair_io_failure() to use bio for page iteration Currently btrfs_repair_io_failure() uses a @paddrs[] array to iterate pages. Such a parameter is required for bs > ps cases, as one fs block crosses several pages. However there is a much simpler and existing way to iterate pages: bio and bvec_iter. This changes btrfs_repair_io_failure() by: - Use a const @bvec_iter pointer to locate where the pages are - Extract file offset/logical from the @bbio - Require no @step parameter Above features allow us to shorten the parameter list. - Rename the function to btrfs_repair_bbio_failure() - Change the caller in btrfs_repair_eb_io_failure() to allocate a bbio Unlike the data read path, we do not have a handy bbio in that case. So we need to allocate one just for btrfs_repair_bbio_failure(). - Change the error reporting in btrfs_repair_bbio_failure() to include root id and use inode number directly Now for btree inode we will report a proper inode number (1). Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/bio.c | 61 ++++++++++++++++++++++++++-------------------- fs/btrfs/bio.h | 5 ++-- fs/btrfs/disk-io.c | 25 +++++++++++++------ 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index cc0bd03048bae6..f8d4c2d550073a 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -186,7 +186,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio, */ struct bvec_iter saved_iter = repair_bbio->saved_iter; const u32 step = min(fs_info->sectorsize, PAGE_SIZE); - const u64 logical = repair_bbio->saved_iter.bi_sector << SECTOR_SHIFT; const u32 nr_steps = repair_bbio->saved_iter.bi_size / step; int mirror = repair_bbio->mirror_num; phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; @@ -220,9 +219,8 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio, do { mirror = prev_repair_mirror(fbio, mirror); - btrfs_repair_io_failure(fs_info, btrfs_ino(inode), - repair_bbio->file_offset, fs_info->sectorsize, - logical, paddrs, step, mirror); + btrfs_repair_bbio_failure(repair_bbio, &repair_bbio->saved_iter, + fs_info->sectorsize, mirror); } while (mirror != fbio->bbio->mirror_num); done: @@ -925,21 +923,23 @@ void btrfs_submit_bbio(struct btrfs_bio *bbio, int mirror_num) * The I/O is issued synchronously to block the repair read completion from * freeing the bio. * - * @ino: Offending inode number - * @fileoff: File offset inside the inode + * @bbio: Original bbio where the repair is needed + * @orig_iter: Points to where the repair start is * @length: Length of the repair write - * @logical: Logical address of the range - * @paddrs: Physical address array of the content - * @step: Length of for each paddrs * @mirror_num: Mirror number to write to. Must not be zero */ -int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff, - u32 length, u64 logical, const phys_addr_t paddrs[], - unsigned int step, int mirror_num) +int btrfs_repair_bbio_failure(struct btrfs_bio *bbio, const struct bvec_iter *orig_iter, + u32 length, int mirror_num) { - const u32 nr_steps = DIV_ROUND_UP_POW2(length, step); + struct btrfs_inode *inode = bbio->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_io_stripe smap = { 0 }; - struct bio *bio = NULL; + struct bvec_iter iter = *orig_iter; + struct bio *repair_bio = NULL; + const u64 logical = iter.bi_sector << SECTOR_SHIFT; + const u64 fileoff = bbio->file_offset + + ((iter.bi_sector - bbio->saved_iter.bi_sector) << SECTOR_SHIFT); + u32 cur = 0; int ret = 0; BUG_ON(!mirror_num); @@ -950,8 +950,9 @@ int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff, ASSERT(IS_ALIGNED(fileoff, fs_info->sectorsize)); /* Either it's a single data or metadata block. */ ASSERT(length <= BTRFS_MAX_BLOCKSIZE); - ASSERT(step <= length); - ASSERT(is_power_of_2(step)); + + /* Our current iter should not be before the original bbio saved_iter. */ + ASSERT(iter.bi_sector >= bbio->saved_iter.bi_sector); /* * The fs either mounted RO or hit critical errors, no need @@ -979,15 +980,22 @@ int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff, goto out_counter_dec; } - bio = bio_alloc(smap.dev->bdev, nr_steps, REQ_OP_WRITE | REQ_SYNC, GFP_NOFS); - bio->bi_iter.bi_sector = smap.physical >> SECTOR_SHIFT; - for (int i = 0; i < nr_steps; i++) { - ret = bio_add_page(bio, phys_to_page(paddrs[i]), step, offset_in_page(paddrs[i])); - /* We should have allocated enough slots to contain all the different pages. */ - ASSERT(ret == step); + repair_bio = bio_alloc(smap.dev->bdev, max(1, length >> PAGE_SHIFT), + REQ_OP_WRITE | REQ_SYNC, GFP_NOFS); + repair_bio->bi_iter.bi_sector = smap.physical >> SECTOR_SHIFT; + while (cur < length) { + struct page *page = bio_iter_page(&bbio->bio, iter); + const u32 pg_off = bio_iter_offset(&bbio->bio, iter); + const u32 cur_len = min(bio_iter_len(&bbio->bio, iter), length - cur); + + ret = bio_add_page(repair_bio, page, cur_len, pg_off); + ASSERT(ret == cur_len); + bio_advance_iter_single(&bbio->bio, &iter, cur_len); + cur += cur_len; } - ret = submit_bio_wait(bio); - bio_put(bio); + + ret = submit_bio_wait(repair_bio); + bio_put(repair_bio); if (ret) { /* try to remap that extent elsewhere? */ btrfs_dev_stat_inc_and_print(smap.dev, BTRFS_DEV_STAT_WRITE_ERRS); @@ -995,8 +1003,9 @@ int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff, } btrfs_info_rl(fs_info, - "read error corrected: ino %llu off %llu (dev %s sector %llu)", - ino, fileoff, btrfs_dev_name(smap.dev), + "read error corrected: root %llu ino %llu off %llu (dev %s sector %llu)", + btrfs_root_id(inode->root), btrfs_ino(inode), fileoff, + btrfs_dev_name(smap.dev), smap.physical >> SECTOR_SHIFT); ret = 0; diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h index 303ed6c7103d92..b7bd377a016249 100644 --- a/fs/btrfs/bio.h +++ b/fs/btrfs/bio.h @@ -126,8 +126,7 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status); void btrfs_submit_bbio(struct btrfs_bio *bbio, int mirror_num); void btrfs_submit_repair_write(struct btrfs_bio *bbio, int mirror_num, bool dev_replace); -int btrfs_repair_io_failure(struct btrfs_fs_info *fs_info, u64 ino, u64 fileoff, - u32 length, u64 logical, const phys_addr_t paddrs[], - unsigned int step, int mirror_num); +int btrfs_repair_bbio_failure(struct btrfs_bio *bbio, const struct bvec_iter *orig_iter, + u32 length, int mirror_num); #endif diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index dc7ad92876c0b3..0f3e8e74b7682e 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -176,19 +176,24 @@ static int btrfs_repair_eb_io_failure(const struct extent_buffer *eb, int mirror_num) { struct btrfs_fs_info *fs_info = eb->fs_info; - const u32 step = min(fs_info->nodesize, PAGE_SIZE); - const u32 nr_steps = eb->len / step; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; + struct btrfs_bio *bbio; + int ret; if (sb_rdonly(fs_info->sb)) return -EROFS; + /* + * This bbio is only to queue all pages for btrfs_repair_bbio_failure(). + * Thus it will never get its endio called. + */ + bbio = btrfs_bio_alloc(max(1, fs_info->nodesize >> PAGE_SHIFT), REQ_OP_READ, + BTRFS_I(fs_info->btree_inode), eb->start, NULL, NULL); + bbio->bio.bi_iter.bi_sector = eb->start >> SECTOR_SHIFT; for (int i = 0; i < num_extent_pages(eb); i++) { struct folio *folio = eb->folios[i]; /* No large folio support yet. */ ASSERT(folio_order(folio) == 0); - ASSERT(i < nr_steps); /* * For nodesize < page size, there is just one paddr, with some @@ -197,11 +202,17 @@ static int btrfs_repair_eb_io_failure(const struct extent_buffer *eb, * For nodesize >= page size, it's one or more paddrs, and eb->start * must be aligned to page boundary. */ - paddrs[i] = page_to_phys(&folio->page) + offset_in_page(eb->start); + ret = bio_add_page(&bbio->bio, &folio->page, min(PAGE_SIZE, fs_info->nodesize), + offset_in_page(eb->start)); + ASSERT(ret == min(PAGE_SIZE, fs_info->nodesize)); } + /* Since the bbio is never submitted, we have to save the iter manually. */ + bbio->saved_iter = bbio->bio.bi_iter; - return btrfs_repair_io_failure(fs_info, 0, eb->start, eb->len, - eb->start, paddrs, step, mirror_num); + ret = btrfs_repair_bbio_failure(bbio, &bbio->saved_iter, fs_info->nodesize, + mirror_num); + bio_put(&bbio->bio); + return ret; } /* From 3e4a083bf0c8bb727517ae781052b80167443077 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 19 Aug 2026 10:36:17 +0930 Subject: [PATCH 1623/2286] btrfs: enhance btrfs_data_csum_ok() to use bio for page iteration Currently btrfs_data_csum_ok() requires a @paddr[] array to iterate all possible pages for bs > ps cases. However for all btrfs_data_csum_ok() call sites, we already have a btrfs_bio, and the bio infrastructure has many flexible ways to iterate multiple pages already. Change btrfs_data_csum_ok() to make full use of btrfs_bio by: - Change the parameter list to require a @bvec_iter pointer And remove @bio_offset, which can be calculated through @bvec_iter and bbio->saved_iter. Also remove paddrs[], we will iterate all the pages using bio interfaces. - Make the same parameter changes to repair_one_sector() - Use bio interfaces to iterate pages from a bio - Rename the function to btrfs_bio_data_csum_ok() - Remove on-stack paddrs[] array usage Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/bio.c | 82 +++++++++++++++--------------------------- fs/btrfs/btrfs_inode.h | 4 +-- fs/btrfs/inode.c | 49 +++++++++++++++++++------ 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index f8d4c2d550073a..19b4855969f536 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -180,29 +180,13 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio, struct btrfs_failed_bio *fbio = repair_bbio->private; struct btrfs_inode *inode = repair_bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; - /* - * We can not move forward the saved_iter, as it will be later - * utilized by repair_bbio again. - */ - struct bvec_iter saved_iter = repair_bbio->saved_iter; - const u32 step = min(fs_info->sectorsize, PAGE_SIZE); - const u32 nr_steps = repair_bbio->saved_iter.bi_size / step; int mirror = repair_bbio->mirror_num; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; - phys_addr_t paddr; - unsigned int slot = 0; - /* Repair bbio should be eaxctly one block sized. */ + /* Repair bbio should be exactly one block sized. */ ASSERT(repair_bbio->saved_iter.bi_size == fs_info->sectorsize); - btrfs_bio_for_each_block(paddr, &repair_bbio->bio, &saved_iter, step) { - ASSERT(slot < nr_steps); - paddrs[slot] = paddr; - slot++; - } - if (repair_bbio->bio.bi_status || - !btrfs_data_csum_ok(repair_bbio, dev, 0, paddrs)) { + !btrfs_bio_data_csum_ok(repair_bbio, &repair_bbio->saved_iter, dev)) { bio_reset(&repair_bbio->bio, NULL, REQ_OP_READ); repair_bbio->bio.bi_iter = repair_bbio->saved_iter; @@ -236,25 +220,21 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio, * read succeeded to restore the redundancy. */ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio, - u32 bio_offset, - phys_addr_t paddrs[], + const struct bvec_iter *orig_iter, struct btrfs_failed_bio *fbio) { struct btrfs_inode *inode = failed_bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; - const u32 sectorsize = fs_info->sectorsize; - const u32 step = min(fs_info->sectorsize, PAGE_SIZE); - const u32 nr_steps = sectorsize / step; - /* - * For bs > ps cases, the saved_iter can be partially moved forward. - * In that case we should round it down to the block boundary. - */ - const u64 logical = round_down(failed_bbio->saved_iter.bi_sector << SECTOR_SHIFT, - sectorsize); struct btrfs_bio *repair_bbio; struct bio *repair_bio; + struct bvec_iter iter = *orig_iter; + const u32 sectorsize = fs_info->sectorsize; + const u32 bio_offset = ((iter.bi_sector - failed_bbio->saved_iter.bi_sector) << + SECTOR_SHIFT); + const u64 logical = (iter.bi_sector << SECTOR_SHIFT); int num_copies; int mirror; + u32 cur = 0; btrfs_debug(fs_info, "repair read error: read error at %llu", failed_bbio->file_offset + bio_offset); @@ -275,17 +255,21 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio, atomic_inc(&fbio->repair_count); - repair_bio = bio_alloc_bioset(NULL, nr_steps, REQ_OP_READ, GFP_NOFS, - &btrfs_repair_bioset); + repair_bio = bio_alloc_bioset(NULL, max(1, sectorsize >> PAGE_SHIFT), + REQ_OP_READ, GFP_NOFS, &btrfs_repair_bioset); repair_bio->bi_iter.bi_sector = logical >> SECTOR_SHIFT; - for (int i = 0; i < nr_steps; i++) { + while (cur < sectorsize) { + struct page *page = bio_iter_page(&failed_bbio->bio, iter); + const u32 pg_off = bio_iter_offset(&failed_bbio->bio, iter); + const u32 cur_len = min(bio_iter_len(&failed_bbio->bio, iter), + sectorsize - cur); int ret; - ASSERT(offset_in_page(paddrs[i]) + step <= PAGE_SIZE); + ret = bio_add_page(repair_bio, page, cur_len, pg_off); + ASSERT(ret == cur_len); - ret = bio_add_page(repair_bio, phys_to_page(paddrs[i]), step, - offset_in_page(paddrs[i])); - ASSERT(ret == step); + bio_advance_iter_single(&failed_bbio->bio, &iter, cur_len); + cur += cur_len; } repair_bbio = btrfs_bio(repair_bio); @@ -303,18 +287,16 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; const u32 sectorsize = fs_info->sectorsize; - const u32 step = min(sectorsize, PAGE_SIZE); - const u32 nr_steps = sectorsize / step; - struct bvec_iter *iter = &bbio->saved_iter; + struct bvec_iter iter; blk_status_t status = bbio->bio.bi_status; struct btrfs_failed_bio *fbio = NULL; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; - phys_addr_t paddr; - u32 offset = 0; /* Read-repair requires the inode field to be set by the submitter. */ ASSERT(inode); + /* The original bbio should be sectorsize aligned. */ + ASSERT(IS_ALIGNED(bbio->saved_iter.bi_size, sectorsize)); + /* * Hand off repair bios to the repair code as there is no upper level * submitter for them. @@ -327,16 +309,10 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de /* Clear the I/O error. A failed repair will reset it. */ bbio->bio.bi_status = BLK_STS_OK; - btrfs_bio_for_each_block(paddr, &bbio->bio, iter, step) { - paddrs[(offset / step) % nr_steps] = paddr; - offset += step; - - if (IS_ALIGNED(offset, sectorsize)) { - if (status || - !btrfs_data_csum_ok(bbio, dev, offset - sectorsize, paddrs)) - fbio = repair_one_sector(bbio, offset - sectorsize, - paddrs, fbio); - } + for (iter = bbio->saved_iter; iter.bi_size; + bio_advance_iter(&bbio->bio, &iter, sectorsize)) { + if (status || !btrfs_bio_data_csum_ok(bbio, &iter, dev)) + fbio = repair_one_sector(bbio, &iter, fbio); } if (bbio->csum != bbio->csum_inline) kvfree(bbio->csum); @@ -924,7 +900,7 @@ void btrfs_submit_bbio(struct btrfs_bio *bbio, int mirror_num) * freeing the bio. * * @bbio: Original bbio where the repair is needed - * @orig_iter: Points to where the repair start is + * @orig_iter: Points to where the repair starts * @length: Length of the repair write * @mirror_num: Mirror number to write to. Must not be zero */ diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 1082fa92c1457a..171f96bdb8aa76 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -513,8 +513,8 @@ void btrfs_calculate_block_csum_pages(struct btrfs_fs_info *fs_info, const phys_addr_t paddrs[], u8 *dest); int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 *csum, const u8 * const csum_expected); -bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev, - u32 bio_offset, const phys_addr_t paddrs[]); +bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, const struct bvec_iter *orig_iter, + struct btrfs_device *dev); noinline int can_nocow_extent(struct btrfs_inode *inode, u64 offset, u64 *len, struct btrfs_file_extent *file_extent, bool nowait); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index faf6ed7deb18bd..2a424d4c9fca9e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3551,27 +3551,31 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 * different noncontiguous pages. * * @bbio: btrfs_io_bio which contains the csum - * @dev: device the sector is on - * @bio_offset: offset to the beginning of the bio (in bytes) - * @paddrs: physical addresses which back the fs block + * @orig_iter: bvec iter pointing to the start of the block + * @dev: device the sector is on (optional) * * Check if the checksum on a data block is valid. When a checksum mismatch is * detected, report the error and fill the corrupted range with zero. * * Return %true if the sector is ok or had no checksum to start with, else %false. */ -bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev, - u32 bio_offset, const phys_addr_t paddrs[]) +bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, + const struct bvec_iter *orig_iter, + struct btrfs_device *dev) { struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct bvec_iter iter = *orig_iter; + struct btrfs_csum_ctx cctx; const u32 blocksize = fs_info->sectorsize; - const u32 step = min(blocksize, PAGE_SIZE); - const u32 nr_steps = blocksize / step; + const u32 bio_offset = (iter.bi_sector - bbio->saved_iter.bi_sector) << SECTOR_SHIFT; u64 file_offset = bbio->file_offset + bio_offset; u64 end = file_offset + blocksize - 1; u8 *csum_expected; u8 csum[BTRFS_CSUM_SIZE]; + u32 cur = 0; + + ASSERT(iter.bi_sector >= bbio->saved_iter.bi_sector); if (!bbio->csum) return true; @@ -3587,7 +3591,22 @@ bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev, csum_expected = bbio->csum + (bio_offset >> fs_info->sectorsize_bits) * fs_info->csum_size; - btrfs_calculate_block_csum_pages(fs_info, paddrs, csum); + btrfs_csum_init(&cctx, fs_info->csum_type); + while (cur < blocksize) { + struct page *page = bio_iter_page(&bbio->bio, iter); + const u32 pg_off = bio_iter_offset(&bbio->bio, iter); + const u32 cur_len = min(bio_iter_len(&bbio->bio, iter), blocksize - cur); + void *kaddr; + + kaddr = kmap_local_page(page) + pg_off; + btrfs_csum_update(&cctx, kaddr, cur_len); + kunmap_local(kaddr); + + bio_advance_iter_single(&bbio->bio, &iter, cur_len); + cur += cur_len; + } + btrfs_csum_final(&cctx, csum); + if (unlikely(memcmp(csum, csum_expected, fs_info->csum_size) != 0)) goto zeroit; return true; @@ -3597,8 +3616,18 @@ bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev, bbio->mirror_num); if (dev) btrfs_dev_stat_inc_and_print(dev, BTRFS_DEV_STAT_CORRUPTION_ERRS); - for (int i = 0; i < nr_steps; i++) - memzero_page(phys_to_page(paddrs[i]), offset_in_page(paddrs[i]), step); + cur = 0; + iter = *orig_iter; + while (cur < blocksize) { + struct page *page = bio_iter_page(&bbio->bio, iter); + const u32 pg_off = bio_iter_offset(&bbio->bio, iter); + const u32 cur_len = min(bio_iter_len(&bbio->bio, iter), blocksize - cur); + + memzero_page(page, pg_off, cur_len); + + bio_advance_iter_single(&bbio->bio, &iter, cur_len); + cur += cur_len; + } return false; } From 64978a4eced2b8f769cdfc27d68c6710e9c92627 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 19 Aug 2026 10:36:18 +0930 Subject: [PATCH 1624/2286] btrfs: use a shared helper to calculate data checksum for a bio Since we are already calculating data checksum using bio interface, extract the generation part into btrfs_csum_one_bio_block(), and use that to replace the paddrs[] array based solution in csum_one_bio(). This will reduce 128 bytes on-stack memory usage for csum_one_bio(). Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 2 ++ fs/btrfs/file-item.c | 19 +++++------------ fs/btrfs/inode.c | 46 +++++++++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 171f96bdb8aa76..e137a99151bd07 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -515,6 +515,8 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 const u8 * const csum_expected); bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, const struct bvec_iter *orig_iter, struct btrfs_device *dev); +void btrfs_csum_one_bio_block(struct btrfs_fs_info *fs_info, struct bio *bio, + const struct bvec_iter *orig_iter, u8 *csum); noinline int can_nocow_extent(struct btrfs_inode *inode, u64 offset, u64 *len, struct btrfs_file_extent *file_extent, bool nowait); diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index cf50fd623f41a8..581ca5653be93a 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -801,25 +801,16 @@ static void csum_one_bio(struct btrfs_bio *bbio, struct bvec_iter *src) { struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; - struct bio *bio = &bbio->bio; struct btrfs_ordered_sum *sums = bbio->sums; - struct bvec_iter iter = *src; - phys_addr_t paddr; + struct bvec_iter iter; const u32 blocksize = fs_info->sectorsize; - const u32 step = min(blocksize, PAGE_SIZE); - const u32 nr_steps = blocksize / step; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; - u32 offset = 0; int index = 0; - btrfs_bio_for_each_block(paddr, bio, &iter, step) { - paddrs[(offset / step) % nr_steps] = paddr; - offset += step; + for (iter = *src; iter.bi_size; bio_advance_iter(&bbio->bio, &iter, blocksize)) { + btrfs_csum_one_bio_block(fs_info, &bbio->bio, &iter, + sums->sums + index); - if (IS_ALIGNED(offset, blocksize)) { - btrfs_calculate_block_csum_pages(fs_info, paddrs, sums->sums + index); - index += fs_info->csum_size; - } + index += fs_info->csum_size; } } diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 2a424d4c9fca9e..e993ea4eb2e4c2 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3546,6 +3546,32 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 return 0; } +/* Generate data checksum for a single fs block, pointed to by @orig_iter. */ +void btrfs_csum_one_bio_block(struct btrfs_fs_info *fs_info, struct bio *bio, + const struct bvec_iter *orig_iter, u8 *csum) +{ + struct btrfs_csum_ctx cctx; + struct bvec_iter iter = *orig_iter; + const u32 blocksize = fs_info->sectorsize; + u32 cur = 0; + + btrfs_csum_init(&cctx, fs_info->csum_type); + while (cur < blocksize) { + struct page *page = bio_iter_page(bio, iter); + const u32 pg_off = bio_iter_offset(bio, iter); + const u32 cur_len = min(bio_iter_len(bio, iter), blocksize - cur); + void *kaddr; + + kaddr = kmap_local_page(page) + pg_off; + btrfs_csum_update(&cctx, kaddr, cur_len); + kunmap_local(kaddr); + + bio_advance_iter_single(bio, &iter, cur_len); + cur += cur_len; + } + btrfs_csum_final(&cctx, csum); +} + /* * Verify the checksum of a single data sector, which can be scattered at * different noncontiguous pages. @@ -3566,7 +3592,6 @@ bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; struct bvec_iter iter = *orig_iter; - struct btrfs_csum_ctx cctx; const u32 blocksize = fs_info->sectorsize; const u32 bio_offset = (iter.bi_sector - bbio->saved_iter.bi_sector) << SECTOR_SHIFT; u64 file_offset = bbio->file_offset + bio_offset; @@ -3591,22 +3616,7 @@ bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, csum_expected = bbio->csum + (bio_offset >> fs_info->sectorsize_bits) * fs_info->csum_size; - btrfs_csum_init(&cctx, fs_info->csum_type); - while (cur < blocksize) { - struct page *page = bio_iter_page(&bbio->bio, iter); - const u32 pg_off = bio_iter_offset(&bbio->bio, iter); - const u32 cur_len = min(bio_iter_len(&bbio->bio, iter), blocksize - cur); - void *kaddr; - - kaddr = kmap_local_page(page) + pg_off; - btrfs_csum_update(&cctx, kaddr, cur_len); - kunmap_local(kaddr); - - bio_advance_iter_single(&bbio->bio, &iter, cur_len); - cur += cur_len; - } - btrfs_csum_final(&cctx, csum); - + btrfs_csum_one_bio_block(fs_info, &bbio->bio, orig_iter, csum); if (unlikely(memcmp(csum, csum_expected, fs_info->csum_size) != 0)) goto zeroit; return true; @@ -3616,8 +3626,6 @@ bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, bbio->mirror_num); if (dev) btrfs_dev_stat_inc_and_print(dev, BTRFS_DEV_STAT_CORRUPTION_ERRS); - cur = 0; - iter = *orig_iter; while (cur < blocksize) { struct page *page = bio_iter_page(&bbio->bio, iter); const u32 pg_off = bio_iter_offset(&bbio->bio, iter); From f41f4dcddea1b9eb41fc3a5491b2a8dd1f7c21ff Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 19 Aug 2026 10:36:19 +0930 Subject: [PATCH 1625/2286] btrfs: remove on-stack paddrs[] array usage Since the bs > ps support, we have to handle cases where a data block is inside several discontiguous pages. Thus we need a local paddrs[] array to assemble a data block for bs > ps cases. However to handle all possible bs/ps combinations, we have to declare such array using the max block size vs page size, no matter the current block size and page size. This adds 128 bytes on-stack memory usage for several call sites, and also introduced several duplicated helpers to calculate checksum for a data block: - btrfs_calculate_block_csum_folio() - btrfs_calculate_block_csum_pages() - btrfs_check_block_csum() The differences are mostly in how the data is passed. The first one accepts a contiguous paddr range. The second one accepts an array of paddrs[]. The last one is just a simple wrapper of the first one. However the most common interface to iterate a data block is through bio, and we have already converted most callers to use the bio based interface, e.g. btrfs_bio_data_csum_ok() and btrfs_csum_one_bio_block(). Convert the remaining two call sites to address the remaining paddrs[] usage: - btrfs_calculate_block_csum_pages() inside verify_bio_data_sectors() This can be switched to btrfs_csum_one_bio_block(). This removes the 128 bytes on-stack memory usage. - btrfs_calculate_block_csum_pages() inside verify_one_sector() This call site doesn't use on-stack memory for paddrs[], but reuses the existing btrfs_raid_bio::bio_paddrs[] or btrfs_raid_bio::stripe_paddrs[]. So implement a local version called calculate_block_csum_paddrs(). Now there is no fixed on-stack paddrs[] usage anymore. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 6 ---- fs/btrfs/inode.c | 75 ------------------------------------------ fs/btrfs/raid56.c | 46 +++++++++++++++----------- 3 files changed, 27 insertions(+), 100 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index e137a99151bd07..89e5e9c0c904f6 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -507,12 +507,6 @@ static inline void btrfs_set_inode_mapping_order(struct btrfs_inode *inode) inode->root->fs_info->block_max_order); } -void btrfs_calculate_block_csum_folio(struct btrfs_fs_info *fs_info, - const phys_addr_t paddr, u8 *dest); -void btrfs_calculate_block_csum_pages(struct btrfs_fs_info *fs_info, - const phys_addr_t paddrs[], u8 *dest); -int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 *csum, - const u8 * const csum_expected); bool btrfs_bio_data_csum_ok(struct btrfs_bio *bbio, const struct bvec_iter *orig_iter, struct btrfs_device *dev); void btrfs_csum_one_bio_block(struct btrfs_fs_info *fs_info, struct bio *bio, diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e993ea4eb2e4c2..54005045e12303 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3471,81 +3471,6 @@ int btrfs_finish_ordered_io(struct btrfs_ordered_extent *ordered) return btrfs_finish_one_ordered(ordered); } -/* - * Calculate the checksum of an fs block at physical memory address @paddr, - * and save the result to @dest. - * - * The folio containing @paddr must be large enough to contain a full fs block. - */ -void btrfs_calculate_block_csum_folio(struct btrfs_fs_info *fs_info, - const phys_addr_t paddr, u8 *dest) -{ - struct folio *folio = page_folio(phys_to_page(paddr)); - const u32 blocksize = fs_info->sectorsize; - const u32 step = min(blocksize, PAGE_SIZE); - const u32 nr_steps = blocksize / step; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; - - /* The full block must be inside the folio. */ - ASSERT(offset_in_folio(folio, paddr) + blocksize <= folio_size(folio)); - - for (int i = 0; i < nr_steps; i++) { - u32 pindex = offset_in_folio(folio, paddr + i * step) >> PAGE_SHIFT; - - /* - * For bs <= ps cases, we will only run the loop once, so the offset - * inside the page will only added to paddrs[0]. - * - * For bs > ps cases, the block must be page aligned, thus offset - * inside the page will always be 0. - */ - paddrs[i] = page_to_phys(folio_page(folio, pindex)) + offset_in_page(paddr); - } - return btrfs_calculate_block_csum_pages(fs_info, paddrs, dest); -} - -/* - * Calculate the checksum of a fs block backed by multiple noncontiguous pages - * at @paddrs[] and save the result to @dest. - * - * The folio containing @paddr must be large enough to contain a full fs block. - */ -void btrfs_calculate_block_csum_pages(struct btrfs_fs_info *fs_info, - const phys_addr_t paddrs[], u8 *dest) -{ - const u32 blocksize = fs_info->sectorsize; - const u32 step = min(blocksize, PAGE_SIZE); - const u32 nr_steps = blocksize / step; - struct btrfs_csum_ctx csum; - - btrfs_csum_init(&csum, fs_info->csum_type); - for (int i = 0; i < nr_steps; i++) { - const phys_addr_t paddr = paddrs[i]; - void *kaddr; - - ASSERT(offset_in_page(paddr) + step <= PAGE_SIZE); - kaddr = kmap_local_page(phys_to_page(paddr)) + offset_in_page(paddr); - btrfs_csum_update(&csum, kaddr, step); - kunmap_local(kaddr); - } - btrfs_csum_final(&csum, dest); -} - -/* - * Verify the checksum for a single sector without any extra action that depend - * on the type of I/O. - * - * @kaddr must be a properly kmapped address. - */ -int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 *csum, - const u8 * const csum_expected) -{ - btrfs_calculate_block_csum_folio(fs_info, paddr, csum); - if (unlikely(memcmp(csum, csum_expected, fs_info->csum_size) != 0)) - return -EIO; - return 0; -} - /* Generate data checksum for a single fs block, pointed to by @orig_iter. */ void btrfs_csum_one_bio_block(struct btrfs_fs_info *fs_info, struct bio *bio, const struct bvec_iter *orig_iter, u8 *csum) diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index 1ee52a9dcee36f..a5d0ef09d92abb 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -1652,12 +1652,7 @@ static void verify_bio_data_sectors(struct btrfs_raid_bio *rbio, struct bio *bio) { struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; - const u32 step = min(fs_info->sectorsize, PAGE_SIZE); - const u32 nr_steps = rbio->sector_nsteps; int total_sector_nr = get_bio_sector_nr(rbio, bio); - u32 offset = 0; - phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE]; - phys_addr_t paddr; /* No data csum for the whole stripe, no need to verify. */ if (!rbio->csum_bitmap || !rbio->csum_buf) @@ -1667,28 +1662,20 @@ static void verify_bio_data_sectors(struct btrfs_raid_bio *rbio, if (total_sector_nr >= rbio->nr_data * rbio->stripe_nsectors) return; - btrfs_bio_for_each_block_all(paddr, bio, step) { + for (struct bvec_iter iter = init_bvec_iter_for_bio(bio); + iter.bi_size; + bio_advance_iter(bio, &iter, fs_info->sectorsize), total_sector_nr++) { u8 csum_buf[BTRFS_CSUM_SIZE]; u8 *expected_csum; - paddrs[(offset / step) % nr_steps] = paddr; - offset += step; - - /* Not yet covering the full fs block, continue to the next step. */ - if (!IS_ALIGNED(offset, fs_info->sectorsize)) - continue; - /* No csum for this sector, skip to the next sector. */ - if (!test_bit(total_sector_nr, rbio->csum_bitmap)) { - total_sector_nr++; + if (!test_bit(total_sector_nr, rbio->csum_bitmap)) continue; - } expected_csum = rbio->csum_buf + total_sector_nr * fs_info->csum_size; - btrfs_calculate_block_csum_pages(fs_info, paddrs, csum_buf); + btrfs_csum_one_bio_block(fs_info, bio, &iter, csum_buf); if (unlikely(memcmp(csum_buf, expected_csum, fs_info->csum_size) != 0)) set_bit(total_sector_nr, rbio->error_bitmap); - total_sector_nr++; } } @@ -1879,6 +1866,27 @@ void raid56_parity_write(struct bio *bio, struct btrfs_io_context *bioc) start_async_work(rbio, rmw_rbio_work); } +static void calculate_block_csum_paddrs(struct btrfs_fs_info *fs_info, + const phys_addr_t paddrs[], u8 *dest) +{ + const u32 blocksize = fs_info->sectorsize; + const u32 step = min(blocksize, PAGE_SIZE); + const u32 nr_steps = blocksize / step; + struct btrfs_csum_ctx csum; + + btrfs_csum_init(&csum, fs_info->csum_type); + for (int i = 0; i < nr_steps; i++) { + const phys_addr_t paddr = paddrs[i]; + void *kaddr; + + ASSERT(offset_in_page(paddr) + step <= PAGE_SIZE); + kaddr = kmap_local_page(phys_to_page(paddr)) + offset_in_page(paddr); + btrfs_csum_update(&csum, kaddr, step); + kunmap_local(kaddr); + } + btrfs_csum_final(&csum, dest); +} + static int verify_one_sector(struct btrfs_raid_bio *rbio, int stripe_nr, int sector_nr) { @@ -1906,7 +1914,7 @@ static int verify_one_sector(struct btrfs_raid_bio *rbio, csum_expected = rbio->csum_buf + (stripe_nr * rbio->stripe_nsectors + sector_nr) * fs_info->csum_size; - btrfs_calculate_block_csum_pages(fs_info, paddrs, csum_buf); + calculate_block_csum_paddrs(fs_info, paddrs, csum_buf); if (unlikely(memcmp(csum_buf, csum_expected, fs_info->csum_size) != 0)) return -EIO; return 0; From 6d237d094798f422f02c88080b99b416a396a155 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 24 Aug 2026 04:54:45 -0700 Subject: [PATCH 1626/2286] btrfs: skip extent tree lock in the shrinker for inodes without extent maps find_first_inode_to_shrink() takes inode->extent_tree.lock in write mode on every inode it walks, only to find out whether that inode has any extent maps. Most inodes have none, so the lock is taken and dropped again without any work being done. Check whether the tree is empty before taking the lock. tree->root is only modified with the tree lock held for write, so the unlocked read is a harmless race: a false empty just defers the inode to a later scan, which already happens whenever the write_trylock() below fails, and a false non-empty falls through to the existing check under the lock. Across the Meta production fleet the extent map shrinker is ~0.35% of non-idle kernel CPU. Attributing callees to their caller, find_first_inode_to_shrink() is ~65% of that, and the write_trylock() it does is ~30% of the whole shrinker. Micro benchmark: a 6 GiB btrfs on a loop device, 100000 empty files kept open, plus 200 1 MiB files created last so they get the highest inode numbers and every scan has to walk all the empty ones first. Each round drops the page cache, re-reads the data files to recreate the extent maps, then triggers the shrinker with "echo 2 > /proc/sys/vm/drop_caches". 15 rounds per run on ARM64 (Neoverse V2), 8 CPUs, no lock debugging. Cost of find_first_inode_to_shrink() from the ftrace function profiler, in ns per inode walked, median of runs: base patched delta idle 46.4 40.1 -13.6% 4 concurrent readers 47.8 38.4 -19.7% A separate build with CONFIG_LOCK_STAT, same test, for the extent map tree rwlock. The shrinker is not the only user of that lock, every extent map insert and lookup takes it too, which is why the acquisition count drops by two thirds rather than to nothing: base patched delta write acquisitions 628016 228000 -63.7% hold time total (us) 47512 22717 -52.2% acq cacheline bounces 1574 1288 -18.2% Signed-off-by: Breno Leitao Reviewed-by: Filipe Manana Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/extent_map.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c index 6ad7b39ae358b2..86d9c6f5ff4bd2 100644 --- a/fs/btrfs/extent_map.c +++ b/fs/btrfs/extent_map.c @@ -1219,6 +1219,14 @@ static struct btrfs_inode *find_first_inode_to_shrink(struct btrfs_root *root, tree = &inode->extent_tree; + /* + * Most inodes have no extent maps, so check without the lock. + * The race is harmless: a false empty just defers the inode to + * a later scan, and a false non-empty is caught under the lock. + */ + if (data_race(RB_EMPTY_ROOT(&tree->root))) + goto next; + /* * We want to be fast so if the lock is busy we don't want to * spend time waiting for it (some task is about to do IO for From 13b5866be3edd278c8b0ede4ddfc0e6a454d9ca7 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 25 Aug 2026 13:42:30 +0930 Subject: [PATCH 1627/2286] btrfs: remove unused variable flags from btrfs_read_qgroup_config() Since commit e562a8bdf652 ("btrfs: introduce BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN"), that @flags variable is no longer utilized. Just remove it. Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index f68b696b4bf720..f3685cbb8f2e39 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -426,7 +426,6 @@ int btrfs_read_qgroup_config(struct btrfs_fs_info *fs_info) struct extent_buffer *l; int slot; int ret = 0; - u64 flags = 0; u64 rescan_progress = 0; if (!fs_info->quota_root) @@ -609,7 +608,6 @@ int btrfs_read_qgroup_config(struct btrfs_fs_info *fs_info) } out: btrfs_free_path(path); - fs_info->qgroup_flags |= flags; if (ret >= 0) { if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_ON) set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags); From bce672e6603d7ae2b2760069af8b093d04542dfc Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 25 Aug 2026 13:42:31 +0930 Subject: [PATCH 1628/2286] btrfs: qgroup: use atomic operations for btrfs_fs_info::qgroup_flags Currently we define btrfs_fs_info::qgroup_flags as u64, to match the on-disk qgroup status item's flag. But for now we have only 4 bits utilized for that flag, and since it's u64 we have no way to properly use the existing atomic bit operations (requires an unsigned long pointer). This results in a lot of non-atomic operations inside qgroup code. Some maybe fine as other locks are involved, but still it's not a good practice. Remove those non-atomic operations by: - Re-define btrfs_fs_info::qgroup_flags as unsigned long - Define BTRFS_QGROUP_STATUS_BIT_* and BTRFS_QGROUP_RUNTIME_BIT_* Instead of the old value define the bit number. - Use set_bit()/clear_bit()/test_bit() to replace open-coded bit operations - Add one extra check at qgroup status item read time To make sure the on-disk flag is still inside ULONG_MAX. Otherwise reject the status item and disable qgroup. - Get rid of unnecessary spinlock when checking a single bit Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/fs.h | 2 +- fs/btrfs/ioctl.c | 2 +- fs/btrfs/qgroup.c | 97 ++++++++++++++++----------------- fs/btrfs/qgroup.h | 4 +- fs/btrfs/sysfs.c | 8 +-- include/uapi/linux/btrfs_tree.h | 21 ++++--- 6 files changed, 67 insertions(+), 67 deletions(-) diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 10e15a319b93de..3eba8438593cde 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -811,7 +811,7 @@ struct btrfs_fs_info { struct btrfs_discard_ctl discard_ctl; /* Is qgroup tracking in a consistent state? */ - u64 qgroup_flags; + unsigned long qgroup_flags; /* Holds configuration and tracking. Protected by qgroup_lock. */ struct rb_root qgroup_tree; diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 576cd76ca0bf30..25d9feaa90030f 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3881,7 +3881,7 @@ static long btrfs_ioctl_quota_rescan_status(struct btrfs_fs_info *fs_info, if (!capable(CAP_SYS_ADMIN)) return -EPERM; - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN) { + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) { qsa.flags = 1; qsa.progress = fs_info->qgroup_rescan_progress.objectid; } diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index f3685cbb8f2e39..2c2ac0f16b1e5b 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -34,7 +34,7 @@ enum btrfs_qgroup_mode btrfs_qgroup_mode(const struct btrfs_fs_info *fs_info) { if (!test_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags)) return BTRFS_QGROUP_MODE_DISABLED; - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE) + if (test_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags)) return BTRFS_QGROUP_MODE_SIMPLE; return BTRFS_QGROUP_MODE_FULL; } @@ -384,14 +384,14 @@ static bool squota_check_parent_usage(struct btrfs_fs_info *fs_info, struct btrf __printf(2, 3) static void qgroup_mark_inconsistent(struct btrfs_fs_info *fs_info, const char *fmt, ...) { - const u64 old_flags = fs_info->qgroup_flags; + const unsigned long old_flags = fs_info->qgroup_flags; if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE) return; - fs_info->qgroup_flags |= (BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT | - BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN | - BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING); - if (!(old_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT)) { + set_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); + set_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags); + set_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags); + if (!test_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &old_flags)) { struct va_format vaf; va_list args; @@ -472,8 +472,12 @@ int btrfs_read_qgroup_config(struct btrfs_fs_info *fs_info) "old qgroup version, quota disabled"); goto out; } - fs_info->qgroup_flags = btrfs_qgroup_status_flags(l, ptr); - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE) + if (btrfs_qgroup_status_flags(l, ptr) > ULONG_MAX) { + btrfs_err(fs_info, "invalid qgroup status flags, quota disabled"); + goto out; + } + fs_info->qgroup_flags = (unsigned long)btrfs_qgroup_status_flags(l, ptr); + if (test_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags)) qgroup_read_enable_gen(fs_info, l, slot, ptr); else if (btrfs_qgroup_status_generation(l, ptr) != fs_info->generation) qgroup_mark_inconsistent(fs_info, "qgroup generation mismatch"); @@ -609,12 +613,12 @@ int btrfs_read_qgroup_config(struct btrfs_fs_info *fs_info) out: btrfs_free_path(path); if (ret >= 0) { - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_ON) + if (test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags)) set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags); - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN) + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) ret = qgroup_rescan_init(fs_info, rescan_progress, 0); } else { - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN; + clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags); btrfs_sysfs_del_qgroups(fs_info); } @@ -1099,9 +1103,9 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, struct btrfs_qgroup_status_item); btrfs_set_qgroup_status_generation(leaf, ptr, trans->transid); btrfs_set_qgroup_status_version(leaf, ptr, BTRFS_QGROUP_STATUS_VERSION); - fs_info->qgroup_flags = BTRFS_QGROUP_STATUS_FLAG_ON; + fs_info->qgroup_flags = (1UL << BTRFS_QGROUP_STATUS_BIT_ON); if (simple) { - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE; + set_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags); btrfs_set_fs_incompat(fs_info, SIMPLE_QUOTA); /* * Set the enable generation to the next transaction, as we cannot @@ -1111,7 +1115,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, */ btrfs_set_qgroup_status_enable_gen(leaf, ptr, trans->transid + 1); } else { - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT; + set_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); } btrfs_set_qgroup_status_flags(leaf, ptr, fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAGS_MASK); @@ -1401,8 +1405,8 @@ int btrfs_quota_disable(struct btrfs_fs_info *fs_info) spin_lock(&fs_info->qgroup_lock); quota_root = fs_info->quota_root; fs_info->quota_root = NULL; - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_ON; - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE; + clear_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); + clear_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags); fs_info->qgroup_drop_subtree_thres = BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT; spin_unlock(&fs_info->qgroup_lock); @@ -1552,7 +1556,7 @@ static int quick_update_accounting(struct btrfs_fs_info *fs_info, } out: if (ret) - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT; + set_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); return ret; } @@ -1873,7 +1877,7 @@ int btrfs_remove_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid) * very frequently. */ if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_FULL && - !(fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT)) { + !test_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags)) { if (unlikely(qgroup->rfer || qgroup->excl || qgroup->rfer_cmpr || qgroup->excl_cmpr)) { DEBUG_WARN(); @@ -2118,7 +2122,7 @@ int btrfs_qgroup_trace_extent_post(struct btrfs_trans_handle *trans, */ ASSERT(trans != NULL); - if (fs_info->qgroup_flags & BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING) + if (test_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags)) return 0; ret = btrfs_find_all_roots(&ctx, true); @@ -2959,7 +2963,7 @@ int btrfs_qgroup_account_extent(struct btrfs_trans_handle *trans, u64 bytenr, * we can't just exit here. */ if (!btrfs_qgroup_full_accounting(fs_info) || - fs_info->qgroup_flags & BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING) + test_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags)) goto out_free; if (new_roots) { @@ -2981,7 +2985,7 @@ int btrfs_qgroup_account_extent(struct btrfs_trans_handle *trans, u64 bytenr, num_bytes, nr_old_roots, nr_new_roots); mutex_lock(&fs_info->qgroup_rescan_lock); - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN) { + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) { if (fs_info->qgroup_rescan_progress.objectid <= bytenr) { mutex_unlock(&fs_info->qgroup_rescan_lock); ret = 0; @@ -3042,8 +3046,8 @@ int btrfs_qgroup_account_extents(struct btrfs_trans_handle *trans) num_dirty_extents++; trace_btrfs_qgroup_account_extents(fs_info, record, bytenr); - if (!ret && !(fs_info->qgroup_flags & - BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING)) { + if (!ret && !test_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, + &fs_info->qgroup_flags)) { struct btrfs_backref_walk_ctx ctx = { 0 }; ctx.bytenr = bytenr; @@ -3150,9 +3154,9 @@ int btrfs_run_qgroups(struct btrfs_trans_handle *trans) spin_lock(&fs_info->qgroup_lock); } if (btrfs_qgroup_enabled(fs_info)) - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_ON; + set_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); else - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_ON; + clear_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); spin_unlock(&fs_info->qgroup_lock); ret = update_qgroup_status_item(trans); @@ -3842,7 +3846,7 @@ static bool rescan_should_stop(struct btrfs_fs_info *fs_info) return true; if (!btrfs_qgroup_enabled(fs_info)) return true; - if (fs_info->qgroup_flags & BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN) + if (test_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags)) return true; return false; } @@ -3892,12 +3896,10 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) btrfs_free_path(path); mutex_lock(&fs_info->qgroup_rescan_lock); - if (ret > 0 && - fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT) { - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT; - } else if (ret < 0 || stopped) { - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT; - } + if (ret > 0) + clear_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); + else if (ret < 0 || stopped) + set_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); mutex_unlock(&fs_info->qgroup_rescan_lock); /* @@ -3921,9 +3923,9 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) } mutex_lock(&fs_info->qgroup_rescan_lock); - if (!stopped || - fs_info->qgroup_flags & BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN) - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN; + if (!stopped || test_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, + &fs_info->qgroup_flags)) + clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags); if (trans) { int ret2 = update_qgroup_status_item(trans); @@ -3933,7 +3935,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) } } fs_info->qgroup_rescan_running = false; - fs_info->qgroup_flags &= ~BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN; + clear_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags); complete_all(&fs_info->qgroup_rescan_completion); mutex_unlock(&fs_info->qgroup_rescan_lock); @@ -3944,7 +3946,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work) if (stopped) { btrfs_info(fs_info, "qgroup scan paused"); - } else if (fs_info->qgroup_flags & BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN) { + } else if (test_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags)) { btrfs_info(fs_info, "qgroup scan cancelled"); } else if (ret >= 0) { btrfs_info(fs_info, "qgroup scan completed%s", @@ -3971,13 +3973,11 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid, if (!init_flags) { /* we're resuming qgroup rescan at mount time */ - if (!(fs_info->qgroup_flags & - BTRFS_QGROUP_STATUS_FLAG_RESCAN)) { + if (!(test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags))) { btrfs_debug(fs_info, "qgroup rescan init failed, qgroup rescan is not queued"); ret = -EINVAL; - } else if (!(fs_info->qgroup_flags & - BTRFS_QGROUP_STATUS_FLAG_ON)) { + } else if (!(test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags))) { btrfs_debug(fs_info, "qgroup rescan init failed, qgroup is not enabled"); ret = -ENOTCONN; @@ -3990,10 +3990,9 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid, mutex_lock(&fs_info->qgroup_rescan_lock); if (init_flags) { - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN) { + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) { ret = -EINPROGRESS; - } else if (!(fs_info->qgroup_flags & - BTRFS_QGROUP_STATUS_FLAG_ON)) { + } else if (!test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags)) { btrfs_debug(fs_info, "qgroup rescan init failed, qgroup is not enabled"); ret = -ENOTCONN; @@ -4006,13 +4005,13 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid, mutex_unlock(&fs_info->qgroup_rescan_lock); return ret; } - fs_info->qgroup_flags |= BTRFS_QGROUP_STATUS_FLAG_RESCAN; + set_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags); } memset(&fs_info->qgroup_rescan_progress, 0, sizeof(fs_info->qgroup_rescan_progress)); - fs_info->qgroup_flags &= ~(BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN | - BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING); + clear_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags); + clear_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags); fs_info->qgroup_rescan_progress.objectid = progress_objectid; init_completion(&fs_info->qgroup_rescan_completion); mutex_unlock(&fs_info->qgroup_rescan_lock); @@ -4063,7 +4062,7 @@ btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info) ret = btrfs_commit_current_transaction(fs_info->fs_root); if (ret) { - fs_info->qgroup_flags &= ~BTRFS_QGROUP_STATUS_FLAG_RESCAN; + clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags); return ret; } @@ -4116,7 +4115,7 @@ int btrfs_qgroup_wait_for_completion(struct btrfs_fs_info *fs_info, void btrfs_qgroup_rescan_resume(struct btrfs_fs_info *fs_info) { - if (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_RESCAN) { + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) { mutex_lock(&fs_info->qgroup_rescan_lock); fs_info->qgroup_rescan_running = true; btrfs_queue_work(fs_info->qgroup_rescan_workers, diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h index 80dd2dacd56db4..b3aaad5e617d51 100644 --- a/fs/btrfs/qgroup.h +++ b/fs/btrfs/qgroup.h @@ -121,8 +121,8 @@ struct btrfs_qgroup_swapped_blocks; * To minimize the chance of collision with new persisted status flags, these * count backwards from the MSB. */ -#define BTRFS_QGROUP_RUNTIME_FLAG_CANCEL_RESCAN (1ULL << 63) -#define BTRFS_QGROUP_RUNTIME_FLAG_NO_ACCOUNTING (1ULL << 62) +#define BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN (BITS_PER_LONG - 1) +#define BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING (BITS_PER_LONG - 2) #define BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT (3) diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 39cb01ee441ab8..1df6340a71234f 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -2359,9 +2359,7 @@ static ssize_t qgroup_enabled_show(struct kobject *qgroups_kobj, struct btrfs_fs_info *fs_info = to_fs_info(qgroups_kobj->parent); bool enabled; - spin_lock(&fs_info->qgroup_lock); - enabled = fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_ON; - spin_unlock(&fs_info->qgroup_lock); + enabled = test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); return sysfs_emit(buf, "%d\n", enabled); } @@ -2401,9 +2399,7 @@ static ssize_t qgroup_inconsistent_show(struct kobject *qgroups_kobj, struct btrfs_fs_info *fs_info = to_fs_info(qgroups_kobj->parent); bool inconsistent; - spin_lock(&fs_info->qgroup_lock); - inconsistent = (fs_info->qgroup_flags & BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT); - spin_unlock(&fs_info->qgroup_lock); + inconsistent = test_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); return sysfs_emit(buf, "%d\n", inconsistent); } diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index cc3b9f7dccafa2..b6ccaf848e4b3e 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -1255,13 +1255,16 @@ static inline __u16 btrfs_qgroup_level(__u64 qgroupid) } /* - * is subvolume quota turned on? - */ -#define BTRFS_QGROUP_STATUS_FLAG_ON (1ULL << 0) -/* - * RESCAN is set during the initialization phase + * The following BTRFS_QGROUP_STATUS_BIT_* are for * btrfs_qgroup_status_item::flags. + * + * Is subvolume quota turned on? */ -#define BTRFS_QGROUP_STATUS_FLAG_RESCAN (1ULL << 1) +#define BTRFS_QGROUP_STATUS_BIT_ON (0) +#define BTRFS_QGROUP_STATUS_FLAG_ON (1UL << BTRFS_QGROUP_STATUS_BIT_ON) + +/* RESCAN is set during the initialization phase */ +#define BTRFS_QGROUP_STATUS_BIT_RESCAN (1) +#define BTRFS_QGROUP_STATUS_FLAG_RESCAN (1UL << BTRFS_QGROUP_STATUS_BIT_RESCAN) /* * Some qgroup entries are known to be out of date, * either because the configuration has changed in a way that @@ -1269,14 +1272,16 @@ static inline __u16 btrfs_qgroup_level(__u64 qgroupid) * with a non-qgroup-aware version. * Turning qouta off and on again makes it inconsistent, too. */ -#define BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT (1ULL << 2) +#define BTRFS_QGROUP_STATUS_BIT_INCONSISTENT (2) +#define BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT (1UL << BTRFS_QGROUP_STATUS_BIT_INCONSISTENT) /* * Whether or not this filesystem is using simple quotas. Not exactly the * incompat bit, because we support using simple quotas, disabling it, then * going back to full qgroup quotas. */ -#define BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE (1ULL << 3) +#define BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE (3) +#define BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE (1UL << BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE) #define BTRFS_QGROUP_STATUS_FLAGS_MASK (BTRFS_QGROUP_STATUS_FLAG_ON | \ BTRFS_QGROUP_STATUS_FLAG_RESCAN | \ From 27a895d6ec8d4d0e87cc08850226086b31107a06 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 27 Aug 2026 16:25:29 +0930 Subject: [PATCH 1629/2286] btrfs: reject new qgroup rescan during subvolume dropping Commit 011b46c30476 ("btrfs: skip subtree scan if it's too high to avoid low stall in btrfs_commit_transaction()") introduced a threshold to skip huge subtree scan during subvolume dropping. But that's not covering all cases, e.g. rescan can still be started immediately after that huge subtree skipping. This will cause rescan to do the same accounting for that subtree anyway, still causing a long stall during transaction commit. Introduce a new runtime qgroup flag, BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, so that during cleanup of a subvolume, no new qgroup rescan can be initiated. The rejection uses the same -EINPROGRESS, as if there is already a running qgroup rescan. And since we have the extra bit, we can no longer allow plain assignment in btrfs_quota_enable(), as the plain assignment will override the REJECT_RESCAN bit. To co-operate this new flag: - Make btrfs_quota_enable() to only set BTRFS_QGROUP_STATUS_BIT_ON So it won't override the existing BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN bit. - Make btrfs_quota_disable() to clear every non-rescan bit This includes: * BTRFS_QGROUP_STATUS_BIT_ON * BTRFS_QGROUP_STATUS_BIT_INCONSISTENT * BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING For rescan related bits, they are either cleared by the rescan thread, or by the caller who rejects rescan. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 2 ++ fs/btrfs/qgroup.c | 13 +++++++++++-- fs/btrfs/qgroup.h | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 0f3e8e74b7682e..94a7e9059a7b3f 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1496,7 +1496,9 @@ static int cleaner_kthread(void *arg) btrfs_run_delayed_iputs(fs_info); + set_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, &fs_info->qgroup_flags); again = btrfs_clean_one_deleted_snapshot(fs_info); + clear_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, &fs_info->qgroup_flags); mutex_unlock(&fs_info->cleaner_mutex); /* diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 2c2ac0f16b1e5b..e01b31aa0b1bf0 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1103,7 +1103,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, struct btrfs_qgroup_status_item); btrfs_set_qgroup_status_generation(leaf, ptr, trans->transid); btrfs_set_qgroup_status_version(leaf, ptr, BTRFS_QGROUP_STATUS_VERSION); - fs_info->qgroup_flags = (1UL << BTRFS_QGROUP_STATUS_BIT_ON); + set_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); if (simple) { set_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags); btrfs_set_fs_incompat(fs_info, SIMPLE_QUOTA); @@ -1405,8 +1405,14 @@ int btrfs_quota_disable(struct btrfs_fs_info *fs_info) spin_lock(&fs_info->qgroup_lock); quota_root = fs_info->quota_root; fs_info->quota_root = NULL; + /* + * Clear all on-disk and runtime bits, except RESCAN related ones, that + * are either handled by rescan thread, or the caller who rejects rescan. + */ clear_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); clear_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags); + clear_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); + clear_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags); fs_info->qgroup_drop_subtree_thres = BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT; spin_unlock(&fs_info->qgroup_lock); @@ -3990,7 +3996,10 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid, mutex_lock(&fs_info->qgroup_rescan_lock); if (init_flags) { - if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) { + if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, + &fs_info->qgroup_flags) || + test_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, + &fs_info->qgroup_flags)) { ret = -EINPROGRESS; } else if (!test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags)) { btrfs_debug(fs_info, diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h index b3aaad5e617d51..090ba536787269 100644 --- a/fs/btrfs/qgroup.h +++ b/fs/btrfs/qgroup.h @@ -124,6 +124,17 @@ struct btrfs_qgroup_swapped_blocks; #define BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN (BITS_PER_LONG - 1) #define BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING (BITS_PER_LONG - 2) +/* + * No new rescan allowed when set. + * + * During huge subtree dropping, qgroup will be marked inconsistent, and skip + * all future accounting to avoid long stall. But, an immediate rescan will + * re-enable qgroup and still stall the system. + * + * This bit is to avoid such rescan during the duration of a subvolume dropping. + */ +#define BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN (BITS_PER_LONG - 3) + #define BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT (3) /* From 310a6aa00cc80c749f5d71b7e2fa35dc6eacd065 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 27 Aug 2026 16:25:30 +0930 Subject: [PATCH 1630/2286] btrfs: avoid long stall when dropping a non-shared large subvolume Commit 011b46c30476 ("btrfs: skip subtree scan if it's too high to avoid low stall in btrfs_commit_transaction()") introduced a mechanism to skip large subtree during snapshot dropping. But even for a subvolume without any shared tree blocks, we can still queue quite a lot of qgroup records into one transaction, and cause a long qgroup related stall. So also add a check against the subvolume root level, to determine if we need to mark qgroup inconsistent. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/extent-tree.c | 10 ++++++++++ fs/btrfs/qgroup.c | 18 ++++++++++++++++++ fs/btrfs/qgroup.h | 1 + 3 files changed, 29 insertions(+) diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index d6a4390ee34ac9..a0d5ab03aae264 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -6315,6 +6315,16 @@ int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc set_bit(BTRFS_ROOT_DELETING, &root->state); unfinished_drop = test_bit(BTRFS_ROOT_UNFINISHED_DROP, &root->state); + /* + * For subvolume dropping, check if the subvolume is large enough so + * that we need to mark qgroup inconsistent to avoid long qgroup stall. + * + * Even for a subvolume without any snapshot, there can still be + * a lot of qgroup records queued into one transaction. + */ + if (!for_reloc) + btrfs_qgroup_check_tree_drop(fs_info, rootid, + btrfs_header_level(root->node)); if (btrfs_disk_key_objectid(&root_item->drop_progress) == 0) { level = btrfs_header_level(root->node); path->nodes[level] = btrfs_lock_root_node(root); diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index e01b31aa0b1bf0..05e35eb126dc5b 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -2748,6 +2748,24 @@ int btrfs_qgroup_trace_subtree(struct btrfs_trans_handle *trans, return 0; } +void btrfs_qgroup_check_tree_drop(struct btrfs_fs_info *fs_info, u64 rootid, u8 level) +{ + u8 drop_subtree_thres; + + if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_FULL) + return; + + if (!btrfs_is_fstree(rootid)) + return; + + spin_lock(&fs_info->qgroup_lock); + drop_subtree_thres = fs_info->qgroup_drop_subtree_thres; + spin_unlock(&fs_info->qgroup_lock); + + if (level >= drop_subtree_thres) + qgroup_mark_inconsistent(fs_info, "subtree level reached threshold"); +} + static void qgroup_iterator_nested_add(struct list_head *head, struct btrfs_qgroup *qgroup) { if (!list_empty(&qgroup->nested_iterator)) diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h index 090ba536787269..c64b26b09c22f2 100644 --- a/fs/btrfs/qgroup.h +++ b/fs/btrfs/qgroup.h @@ -376,6 +376,7 @@ int btrfs_qgroup_trace_leaf_items(struct btrfs_trans_handle *trans, int btrfs_qgroup_trace_subtree(struct btrfs_trans_handle *trans, struct extent_buffer *root_eb, u64 root_gen, int root_level); +void btrfs_qgroup_check_tree_drop(struct btrfs_fs_info *fs_info, u64 rootid, u8 level); int btrfs_qgroup_account_extent(struct btrfs_trans_handle *trans, u64 bytenr, u64 num_bytes, struct ulist *old_roots, struct ulist *new_roots); From 9a30f15f0f6e94312020969b0a32ac5be6a2ce0c Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Fri, 21 Aug 2026 19:42:10 +0930 Subject: [PATCH 1631/2286] btrfs: remove runtime tweakable feature sysfs interface There are 2 features that are marked runtime tweakable inside /sys/fs/btrfs/features/ - acl Which is a mount option, and it will not show up in /sys/fs/btrfs//features/ directory anyway. - extended_iref This feature can only be enabled, but not disabled at runtime. Furthermore it's already the default behavior since 3.12. So it means this feature is always enabled and cannot be disabled for modern btrfs. So there is no need to maintain the ability to modify btrfs' runtime features through sysfs. And furthermore, the existing btrfs_feature_attr_store() is race-prone, it relies on fs_info->transaction_kthread, but our sysfs interfaces are enabled before transaction_kthread. Meaning at mount time a sysfs write can trigger NULL pointer dereference if the transaction_kthread is not yet initialized. The opposite is also possible during unmount. Thankfully that race is not possible in the real world, as the only supported feature is already enabled. But it also means we do not really need to keep the race-prone infrastructure, so just remove it completely, and make the per-module and per-mount features files to be completely read-only. Even with the sysfs tweakable features removed, we can still enable extended_iref feature through ioctl. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/sysfs.c | 121 ++--------------------------------------------- 1 file changed, 4 insertions(+), 117 deletions(-) diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 1df6340a71234f..c5bb1c7eac6afe 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -83,8 +83,7 @@ struct raid_kobject { #define BTRFS_FEAT_ATTR(_name, _feature_set, _feature_prefix, _feature_bit) \ static struct btrfs_feature_attr btrfs_attr_features_##_name = { \ .kobj_attr = __INIT_KOBJ_ATTR(_name, S_IRUGO, \ - btrfs_feature_attr_show, \ - btrfs_feature_attr_store), \ + btrfs_feature_attr_show, NULL), \ .feature_set = _feature_set, \ .feature_bit = _feature_prefix ##_## _feature_bit, \ } @@ -130,130 +129,20 @@ static u64 get_features(struct btrfs_fs_info *fs_info, return btrfs_super_incompat_flags(disk_super); } -static void set_features(struct btrfs_fs_info *fs_info, - enum btrfs_feature_set set, u64 features) -{ - struct btrfs_super_block *disk_super = fs_info->super_copy; - if (set == FEAT_COMPAT) - btrfs_set_super_compat_flags(disk_super, features); - else if (set == FEAT_COMPAT_RO) - btrfs_set_super_compat_ro_flags(disk_super, features); - else - btrfs_set_super_incompat_flags(disk_super, features); -} - -static int can_modify_feature(struct btrfs_feature_attr *fa) -{ - int val = 0; - u64 set, clear; - switch (fa->feature_set) { - case FEAT_COMPAT: - set = BTRFS_FEATURE_COMPAT_SAFE_SET; - clear = BTRFS_FEATURE_COMPAT_SAFE_CLEAR; - break; - case FEAT_COMPAT_RO: - set = BTRFS_FEATURE_COMPAT_RO_SAFE_SET; - clear = BTRFS_FEATURE_COMPAT_RO_SAFE_CLEAR; - break; - case FEAT_INCOMPAT: - set = BTRFS_FEATURE_INCOMPAT_SAFE_SET; - clear = BTRFS_FEATURE_INCOMPAT_SAFE_CLEAR; - break; - default: - btrfs_warn(NULL, "sysfs: unknown feature set %d", fa->feature_set); - return 0; - } - - if (set & fa->feature_bit) - val |= 1; - if (clear & fa->feature_bit) - val |= 2; - - return val; -} - static ssize_t btrfs_feature_attr_show(struct kobject *kobj, struct kobj_attribute *a, char *buf) { int val = 0; struct btrfs_fs_info *fs_info = to_fs_info(kobj); struct btrfs_feature_attr *fa = to_btrfs_feature_attr(a); + if (fs_info) { u64 features = get_features(fs_info, fa->feature_set); if (features & fa->feature_bit) val = 1; - } else - val = can_modify_feature(fa); - - return sysfs_emit(buf, "%d\n", val); -} - -static ssize_t btrfs_feature_attr_store(struct kobject *kobj, - struct kobj_attribute *a, - const char *buf, size_t count) -{ - struct btrfs_fs_info *fs_info; - struct btrfs_feature_attr *fa = to_btrfs_feature_attr(a); - u64 features, set, clear; - unsigned long val; - int ret; - - fs_info = to_fs_info(kobj); - if (!fs_info) - return -EPERM; - - if (sb_rdonly(fs_info->sb)) - return -EROFS; - - ret = kstrtoul(skip_spaces(buf), 0, &val); - if (ret) - return ret; - - if (fa->feature_set == FEAT_COMPAT) { - set = BTRFS_FEATURE_COMPAT_SAFE_SET; - clear = BTRFS_FEATURE_COMPAT_SAFE_CLEAR; - } else if (fa->feature_set == FEAT_COMPAT_RO) { - set = BTRFS_FEATURE_COMPAT_RO_SAFE_SET; - clear = BTRFS_FEATURE_COMPAT_RO_SAFE_CLEAR; - } else { - set = BTRFS_FEATURE_INCOMPAT_SAFE_SET; - clear = BTRFS_FEATURE_INCOMPAT_SAFE_CLEAR; } - features = get_features(fs_info, fa->feature_set); - - /* Nothing to do */ - if ((val && (features & fa->feature_bit)) || - (!val && !(features & fa->feature_bit))) - return count; - - if ((val && !(set & fa->feature_bit)) || - (!val && !(clear & fa->feature_bit))) { - btrfs_info(fs_info, - "%sabling feature %s on mounted fs is not supported.", - val ? "En" : "Dis", fa->kobj_attr.attr.name); - return -EPERM; - } - - btrfs_info(fs_info, "%s %s feature flag", - val ? "Setting" : "Clearing", fa->kobj_attr.attr.name); - - spin_lock(&fs_info->super_lock); - features = get_features(fs_info, fa->feature_set); - if (val) - features |= fa->feature_bit; - else - features &= ~fa->feature_bit; - set_features(fs_info, fa->feature_set, features); - spin_unlock(&fs_info->super_lock); - - /* - * We don't want to do full transaction commit from inside sysfs - */ - set_bit(BTRFS_FS_NEED_TRANS_COMMIT, &fs_info->flags); - wake_up_process(fs_info->transaction_kthread); - - return count; + return sysfs_emit(buf, "%d\n", val); } static umode_t btrfs_feature_visible(struct kobject *kobj, @@ -269,9 +158,7 @@ static umode_t btrfs_feature_visible(struct kobject *kobj, fa = attr_to_btrfs_feature_attr(attr); features = get_features(fs_info, fa->feature_set); - if (can_modify_feature(fa)) - mode |= S_IWUSR; - else if (!(features & fa->feature_bit)) + if (!(features & fa->feature_bit)) mode = 0; } From 76c119fa458bd13c7f82855c29604e54d840b84f Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 1 Sep 2026 09:31:01 +0930 Subject: [PATCH 1632/2286] btrfs: use ordered extent to grab the logical address for submission In submit_one_sector() we call btrfs_get_extent() to grab the IO extent map so that we know where the logical location to submit the block. However there is no guarantee that there is an IO extent map for the block, and if there is no IO extent map nor ordered extent, btrfs_get_extent() can grab the file extent from on-disk metadata. That's why we have ASSERT()s to reject holes and compressed file extents. On the other hand, for the write range we should have both an IO extent map and an ordered extent, so there is no reason not to grab the ordered extent instead. There is some minor advantages: - No hole ordered extent So no need to rely on ASSERT()s to reject hole extents. And the ASSERT()s are depending on the kernel config, without CONFIG_BTRFS_ASSERT those ASSERT()s won't even trigger. - No IO errors Unlike btrfs_get_extent() which can return IO error when doing the metadata tree search, btrfs_lookup_ordered_extent() will either return an OE or not found. - Cached OE in bio_ctrl->bbio At bbio allocation we have already did an OE lookup, and we have a high chance that the current block also belongs to that OE. Use that cached OE can reduce the frequency to do an rb-tree search. - Smaller rb-tree Unlike extent-map-tree, which can contain cached extent maps, the life span of ordered extents are much shorter, they get removed from the ordered tree after the file extent item is inserted into the subvolume tree. So doing ordered extent tree search can be a tiny faster. And since we're here, also address some minor points: - Add error message for every EUCLEAN error - Remove a dead comment on btrfs_folio_clear_dirty() We no longer call folio_clear_dirty_for_io() since commit 095be159f3eb ("btrfs: unify folio dirty flag clearing"), so the folio flag is still dirty, and the folio dirty flag will be cleared by the last dirty block. Reviewed-by: Boris Burkov Reviewed-by: Johannes Thumshirn Reviewed-by: Daniel Vacek Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 64 +++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index d7600e5fa3d95d..a221b63bdb205d 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1808,6 +1808,22 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, return 0; } +static struct btrfs_ordered_extent *get_oe_from_bbio(const struct btrfs_bio *bbio, + u64 filepos) +{ + struct btrfs_ordered_extent *oe; + + if (!bbio || !bbio->ordered) + return NULL; + + oe = bbio->ordered; + if (!in_range(filepos, oe->file_offset, oe->num_bytes)) + return NULL; + + refcount_inc(&oe->refs); + return oe; +} + /* * Return 0 if we have submitted or queued the sector for submission. * Return <0 for critical errors, and the involved sector will be cleaned up. @@ -1820,11 +1836,10 @@ static int submit_one_sector(struct btrfs_inode *inode, loff_t i_size) { struct btrfs_fs_info *fs_info = inode->root->fs_info; - struct extent_map *em; + struct btrfs_ordered_extent *oe; u64 block_start; u64 disk_bytenr; u64 extent_offset; - u64 em_end; const u32 sectorsize = fs_info->sectorsize; unsigned int queued; @@ -1833,8 +1848,11 @@ static int submit_one_sector(struct btrfs_inode *inode, /* @filepos >= i_size case should be handled by the caller. */ ASSERT(filepos < i_size); - em = btrfs_get_extent(inode, NULL, filepos, sectorsize); - if (IS_ERR(em)) { + /* Try to reuse the existing OE from bbio first. */ + oe = get_oe_from_bbio(bio_ctrl->bbio, filepos); + if (!oe) + oe = btrfs_lookup_ordered_extent(inode, filepos); + if (unlikely(!oe)) { /* * bio_ctrl may contain a bio crossing several folios. * Submit it immediately so that the bio has a chance @@ -1857,31 +1875,25 @@ static int submit_one_sector(struct btrfs_inode *inode, */ btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize, false); - return PTR_ERR(em); + btrfs_err_rl(fs_info, + "no ordered extent for root %lld ino %llu filepos %llu", + btrfs_root_id(inode->root), btrfs_ino(inode), + filepos); + return -EUCLEAN; } - extent_offset = filepos - em->start; - em_end = btrfs_extent_map_end(em); - ASSERT(filepos <= em_end); - ASSERT(IS_ALIGNED(em->start, sectorsize)); - ASSERT(IS_ALIGNED(em->len, sectorsize)); - - block_start = btrfs_extent_map_block_start(em); - disk_bytenr = btrfs_extent_map_block_start(em) + extent_offset; + extent_offset = filepos - oe->file_offset; + ASSERT(filepos < oe->file_offset + oe->num_bytes); + ASSERT(IS_ALIGNED(oe->file_offset, sectorsize)); + ASSERT(IS_ALIGNED(oe->num_bytes, sectorsize)); + ASSERT(oe->compress_type == BTRFS_COMPRESS_NONE); + ASSERT(!test_bit(BTRFS_ORDERED_COMPRESSED, &oe->flags)); - ASSERT(!btrfs_extent_map_is_compressed(em)); - ASSERT(block_start != EXTENT_MAP_HOLE); - ASSERT(block_start != EXTENT_MAP_INLINE); + block_start = oe->disk_bytenr + oe->offset; + disk_bytenr = block_start + extent_offset; - btrfs_free_extent_map(em); - em = NULL; + btrfs_put_ordered_extent(oe); - /* - * Although the PageDirty bit is cleared before entering this - * function, subpage dirty bit is not cleared. - * So clear subpage dirty bit here so next time we won't submit - * a folio for a range already written to disk. - */ btrfs_folio_clear_dirty(fs_info, folio, filepos, sectorsize); btrfs_folio_set_writeback(fs_info, folio, filepos, sectorsize); /* @@ -1898,6 +1910,10 @@ static int submit_one_sector(struct btrfs_inode *inode, btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize, false); + btrfs_err_rl(fs_info, + "failed to queue sector for root %lld ino %llu filepos %llu", + btrfs_root_id(inode->root), + btrfs_ino(inode), filepos); return -EUCLEAN; } return 0; From 45f86814783337092a4df6f656f7987c769b9579 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 1 Sep 2026 10:01:33 +0930 Subject: [PATCH 1633/2286] btrfs: tree-checker: reject file extent items for special files File extent items are only utilized by regular files or symlinks, other files like directory/char/block/FIFO/sock files should not have any file extent item. Previously we were unable to reject such cases, as the inode item may not be in the same leaf. But we already have @prev_key in check_leaf_item(), this means we just need a new way to pass the mode of the previously hit inode item, then we can detect such problems. Introduce a new helper structure, saved_inode_info, to record the inode number and its mode hit in the same leaf, and keep it across the whole leaf. Then if we hit a file extent item, and the inode item is in the same leaf, we can refer to that to determine if we need to reject the file extent item. Now with the following corrupted fs tree, the kernel can safely reject the leaf: item 0 key (256 INODE_ITEM 0) itemoff 16123 itemsize 160 generation 3 transid 9 size 12 nbytes 16384 block group 0 mode 40755 links 1 uid 0 gid 0 rdev 0 sequence 1 flags 0x0(none) item 1 key (256 INODE_REF 256) itemoff 16111 itemsize 12 index 0 namelen 2 name: .. item 2 key (256 DIR_ITEM 496027801) itemoff 16075 itemsize 36 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 6 name: foobar item 3 key (256 DIR_INDEX 2) itemoff 16039 itemsize 36 location key (257 INODE_ITEM 0) type FILE transid 9 data_len 0 name_len 6 name: foobar item 4 key (257 INODE_ITEM 0) itemoff 15879 itemsize 160 generation 9 transid 9 size 8192 nbytes 8192 block group 0 mode 60600 links 1 uid 0 gid 0 rdev 0 ^^ This is BLK type, not REG. sequence 2 flags 0x0(none) item 5 key (257 INODE_REF 256) itemoff 15863 itemsize 16 index 2 namelen 6 name: foobar item 6 key (257 EXTENT_DATA 0) itemoff 15810 itemsize 53 generation 9 type 1 (regular) extent data disk byte 13631488 nr 8192 extent data offset 0 nr 8192 ram 8192 extent compression 0 (none) extent encryption 0 With the patch, kernel will reject it with the following tree-checker errors: BTRFS critical (device loop0): corrupt leaf: root=5 block=30408704 slot=6 ino=257 file_offset=0, unexpected file extent item type 1 for inode mode 060600 BTRFS error (device loop0): read time tree block corruption detected on logical 30408704 mirror 1 Reported-by: ZhengYuan Huang Link: https://lore.kernel.org/linux-btrfs/20260817132051.267646-1-gality369@gmail.com/ Assisted-by: LLM (for generating the corrupted image) Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 66 ++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 4b1e47173c637c..857ded43c3b9bd 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -163,6 +163,12 @@ static void dir_item_err(const struct extent_buffer *eb, int slot, va_end(args); } +/* Record info for the last hit inode. */ +struct saved_inode_info { + u64 ino; + u32 mode; +}; + /* * This functions checks prev_key->objectid, to ensure current key and prev_key * share the same objectid as inode number. @@ -204,15 +210,41 @@ static bool check_prev_ino(struct extent_buffer *leaf, prev_key->objectid, key->objectid); return false; } + +static bool can_have_extent_data(struct extent_buffer *leaf, + struct btrfs_key *key, int slot, u8 fi_type, + const struct saved_inode_info *inode_info) +{ + /* No inode item in this leaf. */ + if (inode_info->ino != key->objectid) + return true; + if (S_ISREG(inode_info->mode)) + return true; + if (S_ISLNK(inode_info->mode)) { + /* For a symlink, the file extent item should always be inlined. */ + if (unlikely(fi_type != BTRFS_FILE_EXTENT_INLINE)) + return false; + return true; + } + + /* + * The rest are special files, e.g. block/FIFO files, which cannnot + * have any file extent. + */ + return false; +} + static int check_extent_data_item(struct extent_buffer *leaf, struct btrfs_key *key, int slot, - struct btrfs_key *prev_key) + struct btrfs_key *prev_key, + const struct saved_inode_info *inode_info) { struct btrfs_fs_info *fs_info = leaf->fs_info; struct btrfs_file_extent_item *fi; u32 sectorsize = fs_info->sectorsize; u32 item_size = btrfs_item_size(leaf, slot); u64 extent_end; + u8 fi_type; if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) { file_extent_err(leaf, slot, @@ -243,12 +275,18 @@ static int check_extent_data_item(struct extent_buffer *leaf, SZ_4K); return -EUCLEAN; } - if (unlikely(btrfs_file_extent_type(leaf, fi) >= - BTRFS_NR_FILE_EXTENT_TYPES)) { + fi_type = btrfs_file_extent_type(leaf, fi); + if (unlikely(fi_type >= BTRFS_NR_FILE_EXTENT_TYPES)) { file_extent_err(leaf, slot, "invalid type for file extent, have %u expect range [0, %u]", - btrfs_file_extent_type(leaf, fi), - BTRFS_NR_FILE_EXTENT_TYPES - 1); + fi_type, BTRFS_NR_FILE_EXTENT_TYPES - 1); + return -EUCLEAN; + } + + if (unlikely(!can_have_extent_data(leaf, key, slot, fi_type, inode_info))) { + file_extent_err(leaf, slot, + "unexpected file extent item type %u for inode mode 0%o", + fi_type, inode_info->mode); return -EUCLEAN; } @@ -270,7 +308,8 @@ static int check_extent_data_item(struct extent_buffer *leaf, btrfs_file_extent_encryption(leaf, fi)); return -EUCLEAN; } - if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE) { + + if (fi_type == BTRFS_FILE_EXTENT_INLINE) { /* Inline extent must have 0 as key offset */ if (unlikely(key->offset)) { file_extent_err(leaf, slot, @@ -1206,7 +1245,8 @@ static int check_dev_item(struct extent_buffer *leaf, } static int check_inode_item(struct extent_buffer *leaf, - struct btrfs_key *key, int slot) + struct btrfs_key *key, int slot, + struct saved_inode_info *inode_info) { struct btrfs_fs_info *fs_info = leaf->fs_info; struct btrfs_inode_item *iitem; @@ -1291,6 +1331,8 @@ static int check_inode_item(struct extent_buffer *leaf, ro_flags); return -EUCLEAN; } + inode_info->ino = key->objectid; + inode_info->mode = mode; return 0; } @@ -2348,14 +2390,15 @@ static int check_free_space_bitmap(struct extent_buffer *leaf, static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf, struct btrfs_key *key, int slot, - struct btrfs_key *prev_key) + struct btrfs_key *prev_key, + struct saved_inode_info *inode_info) { int ret = 0; struct btrfs_chunk *chunk; switch (key->type) { case BTRFS_EXTENT_DATA_KEY: - ret = check_extent_data_item(leaf, key, slot, prev_key); + ret = check_extent_data_item(leaf, key, slot, prev_key, inode_info); break; case BTRFS_EXTENT_CSUM_KEY: ret = check_csum_item(leaf, key, slot, prev_key); @@ -2385,7 +2428,7 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf, ret = check_dev_extent_item(leaf, key, slot, prev_key); break; case BTRFS_INODE_ITEM_KEY: - ret = check_inode_item(leaf, key, slot); + ret = check_inode_item(leaf, key, slot, inode_info); break; case BTRFS_ROOT_ITEM_KEY: ret = check_root_item(leaf, key, slot); @@ -2433,6 +2476,7 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf, enum btrfs_tree_block_status __btrfs_check_leaf(struct extent_buffer *leaf) { struct btrfs_fs_info *fs_info = leaf->fs_info; + struct saved_inode_info inode_info = { 0 }; /* No valid key type is 0, so all key should be larger than this key */ struct btrfs_key prev_key = {0, 0, 0}; struct btrfs_key key; @@ -2568,7 +2612,7 @@ enum btrfs_tree_block_status __btrfs_check_leaf(struct extent_buffer *leaf) } /* Check if the item size and content meet other criteria. */ - ret = check_leaf_item(leaf, &key, slot, &prev_key); + ret = check_leaf_item(leaf, &key, slot, &prev_key, &inode_info); if (unlikely(ret != BTRFS_TREE_BLOCK_CLEAN)) return ret; From 57c6777ebd249f79bb935366cd8cbe28d06f6101 Mon Sep 17 00:00:00 2001 From: Daniel Vacek Date: Wed, 2 Sep 2026 11:20:24 +0200 Subject: [PATCH 1634/2286] btrfs: consume given iter directly instead of copying in csum_one_bio() Avoid copying the iter twice in async case. We already have a copy csum_one_bio() can consume directly. No need to copy it again the second time. We can use this copy also in the sync case and get rid of the parameter. Reviewed-by: Qu Wenruo Signed-off-by: Daniel Vacek Signed-off-by: David Sterba --- fs/btrfs/file-item.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index 581ca5653be93a..2c6c14dfe39320 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -797,18 +797,18 @@ int btrfs_lookup_csums_bitmap(struct btrfs_root *root, struct btrfs_path *path, return ret; } -static void csum_one_bio(struct btrfs_bio *bbio, struct bvec_iter *src) +static void csum_one_bio(struct btrfs_bio *bbio) { struct btrfs_inode *inode = bbio->inode; struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_ordered_sum *sums = bbio->sums; - struct bvec_iter iter; const u32 blocksize = fs_info->sectorsize; int index = 0; - for (iter = *src; iter.bi_size; bio_advance_iter(&bbio->bio, &iter, blocksize)) { - btrfs_csum_one_bio_block(fs_info, &bbio->bio, &iter, - sums->sums + index); + for (struct bvec_iter *iter = &bbio->csum_saved_iter; + iter->bi_size; + bio_advance_iter(&bbio->bio, iter, blocksize)) { + btrfs_csum_one_bio_block(fs_info, &bbio->bio, iter, sums->sums + index); index += fs_info->csum_size; } @@ -820,7 +820,7 @@ static void csum_one_bio_work(struct work_struct *work) ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE); ASSERT(bbio->async_csum == true); - csum_one_bio(bbio, &bbio->csum_saved_iter); + csum_one_bio(bbio); complete(&bbio->csum_done); } @@ -850,13 +850,13 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async) bbio->sums = sums; btrfs_add_ordered_sum(ordered, sums); + bbio->csum_saved_iter = bio->bi_iter; if (!async) { - csum_one_bio(bbio, &bbio->bio.bi_iter); + csum_one_bio(bbio); return 0; } init_completion(&bbio->csum_done); bbio->async_csum = true; - bbio->csum_saved_iter = bbio->bio.bi_iter; INIT_WORK(&bbio->csum_work, csum_one_bio_work); schedule_work(&bbio->csum_work); return 0; From e54f700d6ae3203221745c168dae20d88f2be4f1 Mon Sep 17 00:00:00 2001 From: Daniel Vacek Date: Wed, 2 Sep 2026 15:56:27 +0200 Subject: [PATCH 1635/2286] btrfs: use bio::remaining for async checksumming synchronization We can use bio::remaining counter to sync the offloaded checksumming. As a result we can slim down the btrfs_bio structure by 24 bytes and simplify the code a bit. Difference in pahole output: - /* size: 328, cachelines: 6, members: 15 */ + /* size: 304, cachelines: 5, members: 14 */ Moreover this will allow us enabling async checksumming with encryption where we need to checksum the bounce bio instead of our regular one embedded in btrfs_bio. And so we need to extend it's lifetime. This is the preferred way to do so. This also fixes a bug in experimental build where the async checksumming was using the system workqueue instead of fs_info::endio_workers. Fixes: dd57c78aec39 ("btrfs: introduce btrfs_bio::async_csum") Reviewed-by: Qu Wenruo Signed-off-by: Daniel Vacek Signed-off-by: David Sterba --- fs/btrfs/bio.c | 4 ---- fs/btrfs/bio.h | 4 ---- fs/btrfs/file-item.c | 8 +++----- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index 19b4855969f536..771b7d598aeeeb 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -103,7 +103,6 @@ static struct btrfs_bio *btrfs_split_bio(struct btrfs_fs_info *fs_info, bbio->can_use_append = orig_bbio->can_use_append; bbio->is_scrub = orig_bbio->is_scrub; bbio->is_remap = orig_bbio->is_remap; - bbio->async_csum = orig_bbio->async_csum; atomic_inc(&orig_bbio->pending_ios); return bbio; @@ -114,9 +113,6 @@ void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status) /* Make sure we're already in task context. */ ASSERT(in_task()); - if (bbio->async_csum) - wait_for_completion(&bbio->csum_done); - bbio->bio.bi_status = status; if (bbio->bio.bi_pool == &btrfs_clone_bioset) { struct btrfs_bio *orig_bbio = bbio->private; diff --git a/fs/btrfs/bio.h b/fs/btrfs/bio.h index b7bd377a016249..bbf362b8668bbc 100644 --- a/fs/btrfs/bio.h +++ b/fs/btrfs/bio.h @@ -58,7 +58,6 @@ struct btrfs_bio { struct btrfs_ordered_extent *ordered; struct btrfs_ordered_sum *sums; struct work_struct csum_work; - struct completion csum_done; struct bvec_iter csum_saved_iter; u64 orig_physical; u64 orig_logical; @@ -93,9 +92,6 @@ struct btrfs_bio { /* Whether the bio is coming from copy_remapped_data_io(). */ bool is_remap:1; - /* Whether the csum generation for data write is async. */ - bool async_csum:1; - /* Whether the bio is written using zone append. */ bool can_use_append:1; diff --git a/fs/btrfs/file-item.c b/fs/btrfs/file-item.c index 2c6c14dfe39320..ae1fd4da38d31b 100644 --- a/fs/btrfs/file-item.c +++ b/fs/btrfs/file-item.c @@ -819,9 +819,8 @@ static void csum_one_bio_work(struct work_struct *work) struct btrfs_bio *bbio = container_of(work, struct btrfs_bio, csum_work); ASSERT(btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE); - ASSERT(bbio->async_csum == true); csum_one_bio(bbio); - complete(&bbio->csum_done); + bio_endio(&bbio->bio); } /* @@ -855,10 +854,9 @@ int btrfs_csum_one_bio(struct btrfs_bio *bbio, bool async) csum_one_bio(bbio); return 0; } - init_completion(&bbio->csum_done); - bbio->async_csum = true; + bio_inc_remaining(bio); INIT_WORK(&bbio->csum_work, csum_one_bio_work); - schedule_work(&bbio->csum_work); + queue_work(fs_info->endio_workers, &bbio->csum_work); return 0; } From 49c313970a5e7437e6b4ca1be33ec506ffb1843e Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 10:18:01 +0530 Subject: [PATCH 1636/2286] btrfs: fix typos and repeated words in comments Fix misspellings and repeated words in comments, found with scripts/checkpatch.pl and codespell. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/block-group.h | 2 +- fs/btrfs/extent-io-tree.c | 2 +- fs/btrfs/fs.c | 2 +- fs/btrfs/raid56.c | 11 ++++------- fs/btrfs/send.c | 2 +- fs/btrfs/transaction.h | 2 +- fs/btrfs/tree-checker.c | 2 +- include/uapi/linux/btrfs_tree.h | 4 ++-- 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h index 69d56864d4baa0..b349f94cf929ab 100644 --- a/fs/btrfs/block-group.h +++ b/fs/btrfs/block-group.h @@ -135,7 +135,7 @@ struct btrfs_block_group { u64 global_root_id; u64 remap_bytes; u32 identity_remap_count; - /* The last commited identity_remap_count value of this block group. */ + /* The last committed identity_remap_count value of this block group. */ u32 last_identity_remap_count; /* * The last committed used bytes of this block group, if the above @used diff --git a/fs/btrfs/extent-io-tree.c b/fs/btrfs/extent-io-tree.c index d6df11f6088c44..992b8b42bdb4e3 100644 --- a/fs/btrfs/extent-io-tree.c +++ b/fs/btrfs/extent-io-tree.c @@ -751,7 +751,7 @@ int btrfs_clear_extent_bit_changeset(struct extent_io_tree *tree, u64 start, u64 btrfs_split_delalloc_extent(tree->inode, state, start); /* - * Temporarilly ajdust this state's range to match the + * Temporarily ajdust this state's range to match the * range for which we are clearing bits. */ state->start = start; diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c index de160d29dde82f..75a1217727a7bb 100644 --- a/fs/btrfs/fs.c +++ b/fs/btrfs/fs.c @@ -79,7 +79,7 @@ void btrfs_csum_init(struct btrfs_csum_ctx *ctx, u16 csum_type) blake2b_init(&ctx->blake2b, 32); break; default: - /* Checksume type is validated at mount time. */ + /* Checksum type is validated at mount time. */ BUG(); } } diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index a5d0ef09d92abb..8ec24dbb180f92 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -953,7 +953,7 @@ static void rbio_orig_end_io(struct btrfs_raid_bio *rbio, blk_status_t status) /* * Clear the data bitmap, as the rbio may be cached for later usage. - * do this before before unlock_stripe() so there will be no new bio + * do this before unlock_stripe() so there will be no new bio * for this bio. */ bitmap_clear(&rbio->dbitmap, 0, rbio->stripe_nsectors); @@ -988,7 +988,7 @@ static void rbio_orig_end_io(struct btrfs_raid_bio *rbio, blk_status_t status) * as possible, and only use stripe_sectors as fallback. * * Return NULL if bio_list_only is set but the specified sector has no - * coresponding bio. + * corresponding bio. */ static phys_addr_t *sector_paddrs_in_rbio(struct btrfs_raid_bio *rbio, int stripe_nr, int sector_nr, @@ -1450,10 +1450,7 @@ static int rmw_assemble_write_bios(struct btrfs_raid_bio *rbio, /* We should have at least one data sector. */ ASSERT(bitmap_weight(&rbio->dbitmap, rbio->stripe_nsectors)); - /* - * Reset errors, as we may have errors inherited from from degraded - * write. - */ + /* Reset errors, as we may have errors inherited from degraded write. */ bitmap_clear(rbio->error_bitmap, 0, rbio->nr_sectors); /* @@ -2632,7 +2629,7 @@ static int alloc_rbio_essential_pages(struct btrfs_raid_bio *rbio) return 0; } -/* Return true if the content of the step matches the caclulated one. */ +/* Return true if the content of the step matches the calculated one. */ static bool verify_one_parity_step(struct btrfs_raid_bio *rbio, void *pointers[], unsigned int sector_nr, unsigned int step_nr) diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index 5c59b9abedcd71..c523bf950c898f 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -7023,7 +7023,7 @@ static int changed_extent(struct send_ctx *sctx, * get modified or replaced with a new one). Note that deduplication * updates the inode item, but it only changes the iversion (sequence * field in the inode item) of the inode, so if a file is deduplicated - * the same amount of times in both the parent and send snapshots, its + * the same number of times in both the parent and send snapshots, its * iversion becomes the same in both snapshots, whence the inode item is * the same on both snapshots. */ diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h index 3a57f227b5ed28..89153cd2259678 100644 --- a/fs/btrfs/transaction.h +++ b/fs/btrfs/transaction.h @@ -288,7 +288,7 @@ do { \ * Call btrfs_abort_transaction() as early as possible when an error condition * is detected, that way the exact stack trace is reported for some errors. * - * Error number must be negative as it encodes wheather it's the first abort. + * Error number must be negative as it encodes whether it's the first abort. */ #define btrfs_abort_transaction(trans, error) \ do { \ diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 857ded43c3b9bd..594266c9c116ff 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -228,7 +228,7 @@ static bool can_have_extent_data(struct extent_buffer *leaf, } /* - * The rest are special files, e.g. block/FIFO files, which cannnot + * The rest are special files, e.g. block/FIFO files, which cannot * have any file extent. */ return false; diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index b6ccaf848e4b3e..47ee52859b4589 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -230,7 +230,7 @@ * * Stored as an inline ref rather to avoid wasting space on a separate item on * top of the existing extent item. However, unlike the other inline refs, - * there is one one owner ref per extent rather than one per extent. + * there is one owner ref per extent rather than one per extent. * * Because of this, it goes at the front of the list of inline refs, and thus * must have a lower type value than any other inline ref type (to satisfy the @@ -243,7 +243,7 @@ #define BTRFS_EXTENT_DATA_REF_KEY 178 /* - * Obsolete key. Defintion removed in 6.6, value may be reused in the future. + * Obsolete key. Definition removed in 6.6, value may be reused in the future. * * #define BTRFS_EXTENT_REF_V0_KEY 180 */ From 24d966dea969f9ee327de2e85d50eff1e459fbce Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 25 Aug 2026 12:04:17 -0400 Subject: [PATCH 1637/2286] btrfs: split btrfs_insert_delayed_dir_index() into prealloc and commit phases Split btrfs_insert_delayed_dir_index() into three functions using a new btrfs_dir_index_prealloc struct to bundle the pre-allocated resources: - btrfs_prealloc_delayed_dir_index(): allocates the struct and performs the two GFP_NOFS allocations (delayed node + delayed item) that can fail with -ENOMEM. Returns the struct, or ERR_PTR on failure. - btrfs_insert_delayed_dir_index_prealloc(): populates the item data, inserts into the rb-tree, and reserves metadata space. Cannot fail with -ENOMEM since all allocations were done in the prealloc step. - btrfs_free_delayed_dir_index_prealloc(): frees pre-allocated resources when the caller's btree insertion fails. Tolerates NULL. The prealloc is returned as a pointer rather than filled into a caller-provided struct, so that a plain NULL means "no prealloc" and callers do not need a separate flag to track whether one exists. It is consumed (and freed) by either the commit or the free helper, so ownership is unambiguous. The original btrfs_insert_delayed_dir_index() is refactored into a thin wrapper that calls the prealloc and commit functions. This split allows callers to move the fallible memory allocations before the point of no return (the DIR_ITEM btree insertion), so that -ENOMEM can be returned cleanly without aborting the transaction. Assisted-by: LLM Reviewed-by: Qu Wenruo Signed-off-by: Jeff Layton Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/delayed-inode.c | 128 +++++++++++++++++++++++++++++++-------- fs/btrfs/delayed-inode.h | 17 ++++++ 2 files changed, 121 insertions(+), 24 deletions(-) diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c index db2ffab0941a6a..21a2d123df14f1 100644 --- a/fs/btrfs/delayed-inode.c +++ b/fs/btrfs/delayed-inode.c @@ -6,6 +6,7 @@ #include #include +#include #include "ctree.h" #include "fs.h" #include "messages.h" @@ -1469,35 +1470,93 @@ static void btrfs_release_dir_index_item_space(struct btrfs_trans_handle *trans) trans->bytes_reserved -= bytes; } -/* Will return 0, -ENOMEM or -EEXIST (index number collision, unexpected). */ -int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, - const char *name, int name_len, - struct btrfs_inode *dir, - const struct btrfs_disk_key *disk_key, u8 flags, - u64 index) +/* + * Pre-allocate a delayed node and delayed item for a dir index insertion and + * copy the name into the item. Call this before modifying the btree so that + * ENOMEM can be returned before any on-disk state has changed. + * + * The returned prealloc is consumed by either + * btrfs_insert_delayed_dir_index_prealloc() or + * btrfs_free_delayed_dir_index_prealloc(); it must not be used afterwards. + * + * Returns a prealloc on success, ERR_PTR on allocation failure. + */ +struct btrfs_dir_index_prealloc *btrfs_prealloc_delayed_dir_index(struct btrfs_inode *dir, + const char *name, + int name_len) { + struct btrfs_dir_index_prealloc *prealloc; + struct btrfs_delayed_node *node; + struct btrfs_delayed_item *item; + + prealloc = kzalloc_obj(*prealloc, GFP_NOFS); + if (!prealloc) + return ERR_PTR(-ENOMEM); + + node = btrfs_get_or_create_delayed_node(dir, &prealloc->tracker); + if (IS_ERR(node)) { + kfree(prealloc); + return ERR_CAST(node); + } + + item = btrfs_alloc_delayed_item(sizeof(struct btrfs_dir_item) + name_len, + node, BTRFS_DELAYED_INSERTION_ITEM); + if (!item) { + btrfs_release_delayed_node(node, &prealloc->tracker); + kfree(prealloc); + return ERR_PTR(-ENOMEM); + } + + memcpy(item->data + sizeof(struct btrfs_dir_item), name, name_len); + + prealloc->node = node; + prealloc->item = item; + return prealloc; +} +ALLOW_ERROR_INJECTION(btrfs_prealloc_delayed_dir_index, ERRNO); + +/* + * Free resources from btrfs_prealloc_delayed_dir_index() when the btree + * insertion failed and we will not commit the delayed dir index. Does nothing + * if @prealloc is NULL. + */ +void btrfs_free_delayed_dir_index_prealloc(struct btrfs_trans_handle *trans, + struct btrfs_dir_index_prealloc *prealloc) +{ + if (!prealloc) + return; + + btrfs_release_delayed_item(prealloc->item); + btrfs_release_dir_index_item_space(trans); + btrfs_release_delayed_node(prealloc->node, &prealloc->tracker); + kfree(prealloc); +} + +/* + * Commit a pre-allocated delayed dir index item. @prealloc must have been + * returned by btrfs_prealloc_delayed_dir_index(). This populates the item, + * adds it to the delayed node's rb-tree, and reserves metadata space. It + * cannot fail with ENOMEM. @prealloc is freed here in all cases. + * + * Return 0 or -EEXIST (index number collision, unexpected). + */ +int btrfs_insert_delayed_dir_index_prealloc(struct btrfs_trans_handle *trans, + struct btrfs_inode *dir, + struct btrfs_dir_index_prealloc *prealloc, + const struct btrfs_disk_key *disk_key, + u8 flags, u64 index) +{ + struct btrfs_delayed_node *delayed_node = prealloc->node; + struct btrfs_ref_tracker *tracker = &prealloc->tracker; + struct btrfs_delayed_item *delayed_item = prealloc->item; struct btrfs_fs_info *fs_info = trans->fs_info; const unsigned int leaf_data_size = BTRFS_LEAF_DATA_SIZE(fs_info); - struct btrfs_delayed_node *delayed_node; - struct btrfs_ref_tracker delayed_node_tracker; - struct btrfs_delayed_item *delayed_item; + const int name_len = delayed_item->data_len - sizeof(struct btrfs_dir_item); struct btrfs_dir_item *dir_item; bool reserve_leaf_space; u32 data_len; int ret; - delayed_node = btrfs_get_or_create_delayed_node(dir, &delayed_node_tracker); - if (IS_ERR(delayed_node)) - return PTR_ERR(delayed_node); - - delayed_item = btrfs_alloc_delayed_item(sizeof(*dir_item) + name_len, - delayed_node, - BTRFS_DELAYED_INSERTION_ITEM); - if (!delayed_item) { - ret = -ENOMEM; - goto release_node; - } - delayed_item->index = index; dir_item = (struct btrfs_dir_item *)delayed_item->data; @@ -1506,7 +1565,7 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, btrfs_set_stack_dir_data_len(dir_item, 0); btrfs_set_stack_dir_name_len(dir_item, name_len); btrfs_set_stack_dir_flags(dir_item, flags); - memcpy((char *)(dir_item + 1), name, name_len); + /* Name was already copied by btrfs_prealloc_delayed_dir_index(). */ data_len = delayed_item->data_len + sizeof(struct btrfs_item); @@ -1524,7 +1583,9 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, if (unlikely(ret)) { btrfs_err(trans->fs_info, "error adding delayed dir index item, name: %.*s, index: %llu, root: %llu, dir: %llu, dir->index_cnt: %llu, delayed_node->index_cnt: %llu, error: %pe", - name_len, name, index, btrfs_root_id(delayed_node->root), + name_len, + (const char *)(dir_item + 1), + index, btrfs_root_id(delayed_node->root), delayed_node->inode_id, dir->index_cnt, delayed_node->index_cnt, ERR_PTR(ret)); btrfs_release_delayed_item(delayed_item); @@ -1562,10 +1623,29 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, mutex_unlock(&delayed_node->mutex); release_node: - btrfs_release_delayed_node(delayed_node, &delayed_node_tracker); + /* Must release the node before freeing @tracker's containing struct. */ + btrfs_release_delayed_node(delayed_node, tracker); + kfree(prealloc); return ret; } +/* Return 0, -ENOMEM or -EEXIST (index number collision, unexpected). */ +int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, + const char *name, int name_len, + struct btrfs_inode *dir, + const struct btrfs_disk_key *disk_key, u8 flags, + u64 index) +{ + struct btrfs_dir_index_prealloc *prealloc; + + prealloc = btrfs_prealloc_delayed_dir_index(dir, name, name_len); + if (IS_ERR(prealloc)) + return PTR_ERR(prealloc); + + return btrfs_insert_delayed_dir_index_prealloc(trans, dir, prealloc, + disk_key, flags, index); +} + static bool btrfs_delete_delayed_insertion_item(struct btrfs_delayed_node *node, u64 index) { diff --git a/fs/btrfs/delayed-inode.h b/fs/btrfs/delayed-inode.h index fc752863f89bcd..6d12a145489f2b 100644 --- a/fs/btrfs/delayed-inode.h +++ b/fs/btrfs/delayed-inode.h @@ -121,6 +121,23 @@ int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, const struct btrfs_disk_key *disk_key, u8 flags, u64 index); +struct btrfs_dir_index_prealloc { + struct btrfs_delayed_node *node; + struct btrfs_ref_tracker tracker; + struct btrfs_delayed_item *item; +}; + +struct btrfs_dir_index_prealloc *btrfs_prealloc_delayed_dir_index(struct btrfs_inode *dir, + const char *name, + int name_len); +void btrfs_free_delayed_dir_index_prealloc(struct btrfs_trans_handle *trans, + struct btrfs_dir_index_prealloc *prealloc); +int btrfs_insert_delayed_dir_index_prealloc(struct btrfs_trans_handle *trans, + struct btrfs_inode *dir, + struct btrfs_dir_index_prealloc *prealloc, + const struct btrfs_disk_key *disk_key, + u8 flags, u64 index); + int btrfs_delete_delayed_dir_index(struct btrfs_trans_handle *trans, struct btrfs_inode *dir, u64 index); From f95420e3068fe62ce659e150f81ddf894b56c8c9 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 25 Aug 2026 12:04:18 -0400 Subject: [PATCH 1638/2286] btrfs: pre-allocate delayed dir index before btree modification Move the delayed dir index allocation in btrfs_insert_dir_item() before the insert_with_overflow() call that modifies the btree. Previously, the allocations happened after the DIR_ITEM was already inserted, meaning an ENOMEM failure left the btree in a partially-modified state that could only be resolved by aborting the transaction. Add an optional caller-provided btrfs_dir_index_prealloc parameter to btrfs_insert_dir_item(). When non-NULL, ownership of the prealloc transfers to btrfs_insert_dir_item(). When NULL, it allocates internally. All existing callers pass NULL to preserve the current behavior. Since ownership transfers, btrfs_insert_dir_item() must free the prealloc on every path that does not commit it. Route all such exits (including the early path allocation failure) through a common out_free_prealloc label, rather than keying cleanup on need_delayed_index. Remove the btrfs_insert_delayed_dir_index() wrapper, as there are no more callers. Assisted-by: LLM Suggested-by: Qu Wenruo Signed-off-by: Jeff Layton Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/delayed-inode.c | 21 ++------------------ fs/btrfs/delayed-inode.h | 5 ----- fs/btrfs/dir-item.c | 41 ++++++++++++++++++++++++++-------------- fs/btrfs/dir-item.h | 5 +++-- fs/btrfs/inode.c | 2 +- fs/btrfs/transaction.c | 3 +-- 6 files changed, 34 insertions(+), 43 deletions(-) diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c index 21a2d123df14f1..1f20148c1fd44e 100644 --- a/fs/btrfs/delayed-inode.c +++ b/fs/btrfs/delayed-inode.c @@ -687,7 +687,7 @@ static int btrfs_insert_delayed_item(struct btrfs_trans_handle *trans, /* * For delayed items to insert, we track reserved metadata bytes based * on the number of leaves that we will use. - * See btrfs_insert_delayed_dir_index() and + * See btrfs_insert_delayed_dir_index_prealloc() and * btrfs_delayed_item_reserve_metadata()). */ ASSERT(first_item->bytes_reserved == 0); @@ -1629,23 +1629,6 @@ int btrfs_insert_delayed_dir_index_prealloc(struct btrfs_trans_handle *trans, return ret; } -/* Return 0, -ENOMEM or -EEXIST (index number collision, unexpected). */ -int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, - const char *name, int name_len, - struct btrfs_inode *dir, - const struct btrfs_disk_key *disk_key, u8 flags, - u64 index) -{ - struct btrfs_dir_index_prealloc *prealloc; - - prealloc = btrfs_prealloc_delayed_dir_index(dir, name, name_len); - if (IS_ERR(prealloc)) - return PTR_ERR(prealloc); - - return btrfs_insert_delayed_dir_index_prealloc(trans, dir, prealloc, - disk_key, flags, index); -} - static bool btrfs_delete_delayed_insertion_item(struct btrfs_delayed_node *node, u64 index) { @@ -1661,7 +1644,7 @@ static bool btrfs_delete_delayed_insertion_item(struct btrfs_delayed_node *node, /* * For delayed items to insert, we track reserved metadata bytes based * on the number of leaves that we will use. - * See btrfs_insert_delayed_dir_index() and + * See btrfs_insert_delayed_dir_index_prealloc() and * btrfs_delayed_item_reserve_metadata()). */ ASSERT(item->bytes_reserved == 0); diff --git a/fs/btrfs/delayed-inode.h b/fs/btrfs/delayed-inode.h index 6d12a145489f2b..57ba96cfaf9cb3 100644 --- a/fs/btrfs/delayed-inode.h +++ b/fs/btrfs/delayed-inode.h @@ -115,11 +115,6 @@ struct btrfs_delayed_item { }; void btrfs_init_delayed_root(struct btrfs_delayed_root *delayed_root); -int btrfs_insert_delayed_dir_index(struct btrfs_trans_handle *trans, - const char *name, int name_len, - struct btrfs_inode *dir, - const struct btrfs_disk_key *disk_key, u8 flags, - u64 index); struct btrfs_dir_index_prealloc { struct btrfs_delayed_node *node; diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c index 84f1c64423d328..3a90736915af6f 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -106,8 +106,11 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans, * Will return 0 or -ENOMEM */ int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, - const struct fscrypt_str *name, struct btrfs_inode *dir, - const struct btrfs_key *location, u8 type, u64 index) + const struct fscrypt_str *name, + struct btrfs_inode *dir, + const struct btrfs_key *location, u8 type, + u64 index, + struct btrfs_dir_index_prealloc *prealloc) { int ret = 0; int ret2 = 0; @@ -119,17 +122,27 @@ int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, struct btrfs_key key; struct btrfs_disk_key disk_key; u32 data_size; + const bool need_delayed_index = (root != root->fs_info->tree_root); key.objectid = btrfs_ino(dir); key.type = BTRFS_DIR_ITEM_KEY; key.offset = btrfs_name_hash(name->name, name->len); path = btrfs_alloc_path(); - if (!path) - return -ENOMEM; + if (!path) { + ret = -ENOMEM; + goto out_free_prealloc; + } btrfs_cpu_key_to_disk(&disk_key, location); + /* Pre-allocate the delayed dir index before modifying the btree. */ + if (need_delayed_index && !prealloc) { + prealloc = btrfs_prealloc_delayed_dir_index(dir, name->name, name->len); + if (IS_ERR(prealloc)) + return PTR_ERR(prealloc); + } + data_size = sizeof(*dir_item) + name->len; dir_item = insert_with_overflow(trans, root, path, &key, data_size, name->name, name->len); @@ -137,7 +150,7 @@ int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, ret = PTR_ERR(dir_item); if (ret == -EEXIST) goto second_insert; - goto out_free; + goto out_free_prealloc; } if (IS_ENCRYPTED(&dir->vfs_inode)) @@ -154,21 +167,21 @@ int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, write_extent_buffer(leaf, name->name, name_ptr, name->len); second_insert: - /* FIXME, use some real flag for selecting the extra index */ - if (root == root->fs_info->tree_root) { + if (!need_delayed_index) { ret = 0; - goto out_free; + goto out_free_prealloc; } btrfs_release_path(path); - ret2 = btrfs_insert_delayed_dir_index(trans, name->name, name->len, dir, - &disk_key, type, index); -out_free: + ret2 = btrfs_insert_delayed_dir_index_prealloc(trans, dir, prealloc, + &disk_key, type, index); if (ret) return ret; - if (ret2) - return ret2; - return 0; + return ret2; + +out_free_prealloc: + btrfs_free_delayed_dir_index_prealloc(trans, prealloc); + return ret; } static struct btrfs_dir_item *btrfs_lookup_match_dir( diff --git a/fs/btrfs/dir-item.h b/fs/btrfs/dir-item.h index e52174a8baf92c..8d22976a0a7712 100644 --- a/fs/btrfs/dir-item.h +++ b/fs/btrfs/dir-item.h @@ -13,12 +13,14 @@ struct btrfs_path; struct btrfs_inode; struct btrfs_root; struct btrfs_trans_handle; +struct btrfs_dir_index_prealloc; int btrfs_check_dir_item_collision(struct btrfs_root *root, u64 dir_ino, const struct fscrypt_str *name); int btrfs_insert_dir_item(struct btrfs_trans_handle *trans, const struct fscrypt_str *name, struct btrfs_inode *dir, - const struct btrfs_key *location, u8 type, u64 index); + const struct btrfs_key *location, u8 type, u64 index, + struct btrfs_dir_index_prealloc *prealloc); struct btrfs_dir_item *btrfs_lookup_dir_item(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, u64 dir, @@ -53,5 +55,4 @@ static inline u64 btrfs_name_hash(const char *name, int len) { return crc32c((u32)~1, name, len); } - #endif diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 54005045e12303..eb25ef83b61cf7 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6905,7 +6905,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, return ret; ret = btrfs_insert_dir_item(trans, name, parent_inode, &key, - btrfs_inode_type(inode), index); + btrfs_inode_type(inode), index, NULL); if (ret == -EEXIST || ret == -EOVERFLOW) goto fail_dir_item; else if (unlikely(ret)) { diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 11b38133f737e6..c1555621ae4e68 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -1891,8 +1891,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans, goto fail; ret = btrfs_insert_dir_item(trans, &fname.disk_name, - parent_inode, &key, BTRFS_FT_DIR, - index); + parent_inode, &key, BTRFS_FT_DIR, index, NULL); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto fail; From 83fc65e09088b62a424ce13d8162f451a1415457 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 25 Aug 2026 12:04:19 -0400 Subject: [PATCH 1639/2286] btrfs: handle ENOMEM from btrfs_insert_dir_item() without aborting Now that btrfs_insert_dir_item() returns -ENOMEM before modifying the btree (thanks to delayed dir index pre-allocation), callers can handle ENOMEM gracefully instead of aborting the transaction. - btrfs_add_link(): add -ENOMEM to the recoverable errors alongside -EEXIST and -EOVERFLOW. - btrfs_create_new_inode(): on -ENOMEM from btrfs_add_link(), orphan the newly-created inode instead of aborting. The inode item was already written with nlink 1, and discard_new_inode() marks it bad so eviction won't delete it. So clear_nlink() alone is not enough: persist nlink 0 via btrfs_update_inode(), otherwise orphan cleanup would see nlink > 0, drop the orphan item, and leak the inode. Fall back to aborting only if that update also fails. This turns a filesystem-killing abort into a graceful -ENOMEM return for create(), mkdir(), mknod(), symlink(), and link() under memory pressure. Assisted-by: LLM Signed-off-by: Jeff Layton Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index eb25ef83b61cf7..cf25615f9b566b 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6844,7 +6844,27 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, } else { ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name, false, BTRFS_I(inode)->dir_index); - if (unlikely(ret)) { + if (ret == -ENOMEM) { + /* + * Orphan the new inode instead of aborting. The inode + * item was already written with nlink 1, and discard's + * eviction won't delete a bad inode, so nlink 0 must be + * persisted here or orphan cleanup would see nlink > 0, + * drop the orphan item, and leak the inode. + */ + clear_nlink(inode); + /* btrfs_orphan_add() aborts the transaction on failure. */ + ret = btrfs_orphan_add(trans, BTRFS_I(inode)); + if (ret) + goto discard; + ret = btrfs_update_inode(trans, BTRFS_I(inode)); + if (ret) { + btrfs_abort_transaction(trans, ret); + goto discard; + } + ret = -ENOMEM; + goto discard; + } else if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto discard; } @@ -6906,7 +6926,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, ret = btrfs_insert_dir_item(trans, name, parent_inode, &key, btrfs_inode_type(inode), index, NULL); - if (ret == -EEXIST || ret == -EOVERFLOW) + if (ret == -EEXIST || ret == -EOVERFLOW || ret == -ENOMEM) goto fail_dir_item; else if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); From 1c9fdf06d7c2ab67ea3b9e88ddcc1c3150d5f10c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 25 Aug 2026 12:04:20 -0400 Subject: [PATCH 1640/2286] btrfs: pre-allocate delayed dir index for non-overwrite rename For rename() without an overwrite target, pre-allocate the delayed dir index before any btree modifications so that ENOMEM can be returned before the source is unlinked from the old directory. Add a prealloc parameter to btrfs_add_link() that allows callers to pass pre-allocated delayed dir index resources. When provided, btrfs_add_link() takes ownership: it either passes the prealloc to btrfs_insert_dir_item() (which commits or frees it), or frees it on early error. All existing callers pass NULL to preserve the current behavior. In btrfs_rename(), when new_inode is NULL (no overwrite), call btrfs_prealloc_delayed_dir_index() before the first btree modification and pass the result through to btrfs_add_link(). If the prealloc fails, -ENOMEM is returned before any btree state has changed. The local prealloc pointer is cleared once ownership passes to btrfs_add_link(), so the out_fail path only frees one we still own. For overwrite rename (new_inode != NULL), the transaction still aborts on ENOMEM since earlier unlink operations have already made irreversible btree modifications. Assisted-by: LLM Signed-off-by: Jeff Layton Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 4 +++- fs/btrfs/inode.c | 39 +++++++++++++++++++++++++++++++-------- fs/btrfs/tree-log.c | 4 ++-- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 89e5e9c0c904f6..46c62f980c24ac 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -32,6 +32,7 @@ struct btrfs_trans_handle; struct btrfs_bio; struct btrfs_file_extent; struct btrfs_delayed_node; +struct btrfs_dir_index_prealloc; /* * Since we search a directory based on f_pos (struct dir_context::pos) we have @@ -523,7 +524,8 @@ int btrfs_unlink_inode(struct btrfs_trans_handle *trans, const struct fscrypt_str *name); int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_inode *parent_inode, struct btrfs_inode *inode, - const struct fscrypt_str *name, bool add_backref, u64 index); + const struct fscrypt_str *name, bool add_backref, u64 index, + struct btrfs_dir_index_prealloc *prealloc); int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry); int btrfs_truncate_block(struct btrfs_inode *inode, u64 offset, u64 start, u64 end); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index cf25615f9b566b..c7bbcd11d01e8c 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6843,7 +6843,7 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, } } else { ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), name, - false, BTRFS_I(inode)->dir_index); + false, BTRFS_I(inode)->dir_index, NULL); if (ret == -ENOMEM) { /* * Orphan the new inode instead of aborting. The inode @@ -6895,7 +6895,8 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans, */ int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_inode *parent_inode, struct btrfs_inode *inode, - const struct fscrypt_str *name, bool add_backref, u64 index) + const struct fscrypt_str *name, bool add_backref, u64 index, + struct btrfs_dir_index_prealloc *prealloc) { int ret = 0; struct btrfs_key key; @@ -6921,11 +6922,13 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, } /* Nothing to clean up yet */ - if (ret) + if (ret) { + btrfs_free_delayed_dir_index_prealloc(trans, prealloc); return ret; + } ret = btrfs_insert_dir_item(trans, name, parent_inode, &key, - btrfs_inode_type(inode), index, NULL); + btrfs_inode_type(inode), index, prealloc); if (ret == -EEXIST || ret == -EOVERFLOW || ret == -ENOMEM) goto fail_dir_item; else if (unlikely(ret)) { @@ -7079,7 +7082,7 @@ static int btrfs_link(struct dentry *old_dentry, struct inode *dir, inode_set_ctime_current(inode); ret = btrfs_add_link(trans, BTRFS_I(dir), BTRFS_I(inode), - &fname.disk_name, true, index); + &fname.disk_name, true, index, NULL); if (ret) goto fail; @@ -8493,14 +8496,14 @@ static int btrfs_rename_exchange(struct inode *old_dir, } ret = btrfs_add_link(trans, BTRFS_I(new_dir), BTRFS_I(old_inode), - new_name, false, old_idx); + new_name, false, old_idx, NULL); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto out_fail; } ret = btrfs_add_link(trans, BTRFS_I(old_dir), BTRFS_I(new_inode), - old_name, false, new_idx); + old_name, false, new_idx, NULL); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto out_fail; @@ -8573,6 +8576,7 @@ static int btrfs_rename(struct mnt_idmap *idmap, struct inode *new_inode = d_inode(new_dentry); struct inode *old_inode = d_inode(old_dentry); struct btrfs_rename_ctx rename_ctx; + struct btrfs_dir_index_prealloc *prealloc = NULL; u64 index = 0; int ret; int ret2; @@ -8696,6 +8700,23 @@ static int btrfs_rename(struct mnt_idmap *idmap, if (ret) goto out_fail; + /* + * When not overwriting an existing entry, pre-allocate the delayed dir + * index now so that ENOMEM is returned before any btree modifications. + * For the overwrite case, too many btree changes have already happened + * by the time btrfs_add_link() is called. + */ + if (!new_inode) { + prealloc = btrfs_prealloc_delayed_dir_index(BTRFS_I(new_dir), + new_fname.disk_name.name, + new_fname.disk_name.len); + if (IS_ERR(prealloc)) { + ret = PTR_ERR(prealloc); + prealloc = NULL; + goto out_fail; + } + } + BTRFS_I(old_inode)->dir_index = 0ULL; if (unlikely(old_ino == BTRFS_FIRST_FREE_OBJECTID)) { /* force full log commit if subvolume involved. */ @@ -8791,7 +8812,8 @@ static int btrfs_rename(struct mnt_idmap *idmap, } ret = btrfs_add_link(trans, BTRFS_I(new_dir), BTRFS_I(old_inode), - &new_fname.disk_name, false, index); + &new_fname.disk_name, false, index, prealloc); + prealloc = NULL; if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); goto out_fail; @@ -8816,6 +8838,7 @@ static int btrfs_rename(struct mnt_idmap *idmap, } } out_fail: + btrfs_free_delayed_dir_index_prealloc(trans, prealloc); if (logs_pinned) { btrfs_end_log_trans(root); btrfs_end_log_trans(dest); diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index a00094604e544e..cfb0b0e9e1248e 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -1683,7 +1683,7 @@ static noinline int add_inode_ref(struct walk_control *wc) } /* insert our name */ - ret = btrfs_add_link(trans, dir, inode, &name, false, ref_index); + ret = btrfs_add_link(trans, dir, inode, &name, false, ref_index, NULL); if (ret) { btrfs_abort_log_replay(wc, ret, "failed to add link for inode %llu in dir %llu ref_index %llu name %.*s root %llu", @@ -2031,7 +2031,7 @@ static noinline int insert_one_name(struct btrfs_trans_handle *trans, return PTR_ERR(dir); } - ret = btrfs_add_link(trans, dir, inode, name, true, index); + ret = btrfs_add_link(trans, dir, inode, name, true, index, NULL); /* FIXME, put inode into FIXUP list */ From 7d5319be57fea4b06fe1d157f4696ddc1e3927bd Mon Sep 17 00:00:00 2001 From: Usama Arif Date: Fri, 4 Sep 2026 09:41:48 -0700 Subject: [PATCH 1641/2286] btrfs: zstd: avoid a copy in zstd_decompress_bio() zstd_decompress_bio() gives zstd a sectorsize-sized scratch buffer, and btrfs_decompress_buf2page() then copies the part overlapping the read bio into the destination folios. Every delivered byte is written twice. Instead, choose the output buffer per streaming call. zstd_map_dest() kmaps the current page-bounded segment of the read bio, so zstd writes into the page cache directly. The scratch buffer is kept only for output with no destination: the prefix before a read starting inside a compressed extent, which zstd cannot skip, and gaps left by folios already in the page cache. Varying the output buffer across calls is safe: btrfs uses the default ZSTD_bm_buffered mode, where the sliding window lives in the dstream's internal buffer and the caller's dst is a pure sink. The read bio's iterator must still advance by exactly the bytes delivered, since btrfs_decompress_bio() zero-fills from it; that used to happen inside btrfs_decompress_buf2page() and is now an explicit bio_advance(), made only for output that reached a folio. bio_iter_iovec() exposes at most one base page, so direct output is page-bounded. Compared to the old sectorsize-sized chunks, this can increase stream calls when sectorsize exceeds PAGE_SIZE, but eliminates the extra btrfs copy for output delivered to the read bio; the 64 KiB sectorsize row below shows the copy still wins there. Benchmarked the change in 2-vCPU x86-64 KVM guests (4 KiB pages, RAM disk) using a 64 MiB zstd-compressed file. Results are medians of seven cold-cache reads in each of six interleaved A/B boot pairs; mincore confirmed zero resident pages before every run. Normal sequential reads with readahead produced: sectorsize base patched reduction 4 KiB 8.678 ms 8.004 ms 7.80% 16 KiB 8.216 ms 7.934 ms 3.64% 64 KiB 7.875 ms 7.344 ms 6.88% Random 4 KiB preads at 4 KiB sectorsize, means of six interleaved A/B boot pairs, patched better in all six: base patched gain 264.33 MB/s 272.67 MB/s 3.2% Signed-off-by: Usama Arif Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/zstd.c | 69 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c index 58d9ff76fe07bb..280ac5273438b7 100644 --- a/fs/btrfs/zstd.c +++ b/fs/btrfs/zstd.c @@ -589,10 +589,48 @@ int zstd_compress_bio(struct list_head *ws, struct compressed_bio *cb) return ret; } +/* + * Map the destination for the next chunk of output. + * + * @decompressed is the offset of the next output byte inside the fully + * decompressed extent. If that offset has reached the current destination + * segment, its page-bounded bio_vec is kmapped so that zstd can write into the + * page cache directly, and the number of bytes writable there is returned. + * Otherwise @kaddr_ret is set to NULL and the number of bytes to skip before + * that segment is returned. This covers both the initial prefix and gaps in + * the destination bio. + */ +static u32 zstd_map_dest(struct compressed_bio *cb, u32 decompressed, + void **kaddr_ret) +{ + struct bio *orig_bio = &cb->orig_bbio->bio; + struct bio_vec bvec; + u32 bvec_offset; + u32 off; + + bvec = bio_iter_iovec(orig_bio, orig_bio->bi_iter); + /* + * cb->start may underflow, but subtracting that value can still give us + * the correct offset inside the full decompressed extent. + */ + bvec_offset = page_offset(bvec.bv_page) + bvec.bv_offset - cb->start; + + if (decompressed < bvec_offset) { + *kaddr_ret = NULL; + return bvec_offset - decompressed; + } + + off = decompressed - bvec_offset; + ASSERT(off < bvec.bv_len); + *kaddr_ret = bvec_kmap_local(&bvec) + off; + return bvec.bv_len - off; +} + int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) { struct btrfs_fs_info *fs_info = cb_to_fs_info(cb); struct workspace *workspace = list_entry(ws, struct workspace, list); + struct bio *orig_bio = &cb->orig_bbio->bio; struct folio_iter fi; size_t srclen = bio_get_size(&cb->bbio.bio); zstd_dstream *stream; @@ -600,7 +638,6 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) const unsigned int min_folio_size = btrfs_min_folio_size(fs_info); unsigned long folio_in_index = 0; unsigned long total_folios_in = DIV_ROUND_UP(srclen, min_folio_size); - unsigned long buf_start; unsigned long total_out = 0; bio_first_folio(&fi, &cb->bbio.bio, 0); @@ -624,15 +661,26 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) workspace->in_buf.pos = 0; workspace->in_buf.size = min_t(size_t, srclen, min_folio_size); - workspace->out_buf.dst = workspace->buf; - workspace->out_buf.pos = 0; - workspace->out_buf.size = fs_info->sectorsize; - - while (1) { + while (orig_bio->bi_iter.bi_size) { size_t ret2; + void *kaddr; + u32 dstlen; + + dstlen = zstd_map_dest(cb, total_out, &kaddr); + if (kaddr) { + workspace->out_buf.dst = kaddr; + workspace->out_buf.size = dstlen; + } else { + workspace->out_buf.dst = workspace->buf; + workspace->out_buf.size = min_t(u32, dstlen, + fs_info->sectorsize); + } + workspace->out_buf.pos = 0; ret2 = zstd_decompress_stream(stream, &workspace->out_buf, &workspace->in_buf); + if (kaddr) + kunmap_local(kaddr); if (unlikely(zstd_is_error(ret2))) { struct btrfs_inode *inode = cb->bbio.inode; @@ -643,14 +691,9 @@ int zstd_decompress_bio(struct list_head *ws, struct compressed_bio *cb) ret = -EIO; goto done; } - buf_start = total_out; total_out += workspace->out_buf.pos; - workspace->out_buf.pos = 0; - - ret = btrfs_decompress_buf2page(workspace->out_buf.dst, - total_out - buf_start, cb, buf_start); - if (ret == 0) - break; + if (kaddr) + bio_advance(orig_bio, workspace->out_buf.pos); if (workspace->in_buf.pos >= srclen) break; From 052434104f1e9e1770a22aa6b724f8dd3df1ff55 Mon Sep 17 00:00:00 2001 From: Zhen Ni Date: Mon, 22 Dec 2025 11:59:42 +0800 Subject: [PATCH 1642/2286] btrfs: replace is_data_bbio() with is_data_inode() for direct usage After commit 81cea6cd7041 ("btrfs: remove btrfs_bio::fs_info by extracting it from btrfs_bio::inode"), the btrfs_bio::inode field is mandatory for all btrfs_bio allocations. The NULL check is redundant and can be removed. As is_data_bbio() would be a trivial wrapper for is_data_bbio() replace all calls in in bio.c Link: https://lore.kernel.org/linux-btrfs/20251219084316.1164580-1-zhen.ni@easystack.cn Signed-off-by: Zhen Ni Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/bio.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/bio.c b/fs/btrfs/bio.c index 771b7d598aeeeb..2c6234f6182f89 100644 --- a/fs/btrfs/bio.c +++ b/fs/btrfs/bio.c @@ -27,15 +27,9 @@ struct btrfs_failed_bio { atomic_t repair_count; }; -/* Is this a data path I/O that needs storage layer checksum and repair? */ -static inline bool is_data_bbio(const struct btrfs_bio *bbio) -{ - return bbio->inode && is_data_inode(bbio->inode); -} - static bool bbio_has_ordered_extent(const struct btrfs_bio *bbio) { - return is_data_bbio(bbio) && btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE; + return is_data_inode(bbio->inode) && btrfs_op(&bbio->bio) == BTRFS_MAP_WRITE; } /* @@ -356,7 +350,7 @@ static void simple_end_io_work(struct work_struct *work) if (bio_op(bio) == REQ_OP_READ) { /* Metadata reads are checked and repaired by the submitter. */ - if (is_data_bbio(bbio)) + if (is_data_inode(bbio->inode)) return btrfs_check_read_bio(bbio, bbio->bio.bi_private); return btrfs_bio_end_io(bbio, bbio->bio.bi_status); } @@ -390,7 +384,7 @@ static void btrfs_raid56_end_io(struct bio *bio) btrfs_bio_counter_dec(bioc->fs_info); bbio->mirror_num = bioc->mirror_num; - if (bio_op(bio) == REQ_OP_READ && is_data_bbio(bbio)) + if (bio_op(bio) == REQ_OP_READ && is_data_inode(bbio->inode)) btrfs_check_read_bio(bbio, NULL); else btrfs_bio_end_io(bbio, bbio->bio.bi_status); @@ -753,7 +747,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) * our bio to the physical disk location, so we need to save the * original bytenr so we know what we're checksumming. */ - if (bio_op(bio) == REQ_OP_WRITE && is_data_bbio(bbio)) + if (bio_op(bio) == REQ_OP_WRITE && is_data_inode(bbio->inode)) bbio->orig_logical = logical; bbio->can_use_append = btrfs_use_zone_append(bbio); @@ -779,7 +773,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) * Save the iter for the end_io handler and preload the checksums for * data reads. */ - if (bio_op(bio) == REQ_OP_READ && is_data_bbio(bbio)) { + if (bio_op(bio) == REQ_OP_READ && is_data_inode(bbio->inode)) { bbio->saved_iter = bio->bi_iter; ret = btrfs_lookup_bio_sums(bbio); status = errno_to_blk_status(ret); @@ -788,7 +782,7 @@ static bool btrfs_submit_chunk(struct btrfs_bio *bbio, int mirror_num) } if (btrfs_op(bio) == BTRFS_MAP_WRITE) { - if (is_data_bbio(bbio) && bioc && bioc->use_rst) { + if (is_data_inode(bbio->inode) && bioc && bioc->use_rst) { /* * No locking for the list update, as we only add to * the list in the I/O submission path, and list From b2ba99cf6f7ba3cd53fe6bb1b24a4c8cc5d74ba7 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 8 Sep 2026 07:47:39 +0930 Subject: [PATCH 1643/2286] btrfs: use kvmalloc() for b-tree split_item() [BUG] There is a bug report that the kmalloc() call inside split_item() failed with the following call trace, and triggered a transaction abort: kworker/u69:8: page allocation failure: order:4, mode:0x40c40(GFP_NOFS|__GFP_COMP), nodemask=(null) CPU: 3 UID: 0 PID: 1154528 Comm: kworker/u69:8 Not tainted 7.0.2 #1 PREEMPTLAZY Workqueue: events_unbound btrfs_async_reclaim_metadata_space Call Trace: dump_stack_lvl+0x47/0x60 warn_alloc.cold+0x67/0xec __alloc_pages_slowpath.constprop.0+0x9bf/0xed0 __alloc_frozen_pages_noprof+0x1ac/0x1c0 ___kmalloc_large_node+0x9d/0xc0 __kmalloc_noprof+0x17b/0x1f0 split_item+0x9e/0x2e0 btrfs_del_csums+0x285/0x400 __btrfs_free_extent.isra.0+0x6de/0x12b0 __btrfs_run_delayed_refs+0x522/0x10c0 btrfs_run_delayed_refs+0x4d/0x1d0 flush_space+0x34d/0x4e0 do_async_reclaim_metadata_space+0x89/0x1d0 btrfs_async_reclaim_metadata_space+0x44/0x60 process_one_work+0x145/0x230 worker_thread+0x185/0x2e0 kthread+0xca/0x100 ret_from_fork+0x14e/0x200 ret_from_fork_asm+0x11/0x20 BTRFS error (device dm-3 state A): Transaction aborted (error -12) BTRFS: error (device dm-3 state A) in btrfs_del_csums:1053: errno=-12 Out of memory BTRFS info (device dm-3 state EA): forced readonly BTRFS: error (device dm-3 state EA) in do_free_extent_accounting:3168: errno=-12 Out of memory BTRFS error (device dm-3 state EA): failed to run delayed ref for logical 1202913873920 num_bytes 274432 type 184 action 2 ref_mod 1: -12 BTRFS: error (device dm-3 state EA) in btrfs_run_delayed_refs:2247: errno=-12 Out of memory [CAUSE] The kmalloc() call is to allocate a buffer to store the full item. However as shown in the above call trace, the order can be high (4), and since we're using GFP_NOFS, it's impossible to reclaim memory by writing back dirty pages. When there is no physically contiguous memory left, such high order allocation can easily fail, and if such kmalloc() happens in a critical path we can trigger a transaction abort. [FIX] Instead of kmalloc(), which requires physically contiguous pages, use kvmalloc(). There is no special requirement for physically contiguous pages here, we just want virtually contiguous memory as a buffer. Reported-by: xavierbachmeyer182 Link: https://lore.kernel.org/linux-btrfs/250decb0-d940-4fe6-9b54-d06e1b293a1b@suse.com/ Reviewed-by: Johannes Thumshirn Reviewed-by: Daniel Vacek Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ctree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c index 8fe330d81b8ff3..fef0e49dd91882 100644 --- a/fs/btrfs/ctree.c +++ b/fs/btrfs/ctree.c @@ -3943,7 +3943,7 @@ static noinline int split_item(struct btrfs_trans_handle *trans, orig_offset = btrfs_item_offset(leaf, path->slots[0]); item_size = btrfs_item_size(leaf, path->slots[0]); - buf = kmalloc(item_size, GFP_NOFS); + buf = kvmalloc(item_size, GFP_NOFS); if (!buf) return -ENOMEM; @@ -3981,7 +3981,7 @@ static noinline int split_item(struct btrfs_trans_handle *trans, btrfs_mark_buffer_dirty(trans, leaf); BUG_ON(btrfs_leaf_free_space(leaf) < 0); - kfree(buf); + kvfree(buf); return 0; } From 0d8c05fe050ab243489c0b5998e31d7d30003791 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 8 Sep 2026 16:45:39 +0930 Subject: [PATCH 1644/2286] btrfs: tree-log: use kvmalloc() for overwrite_item() The @src_copy buffer utilized inside overwrite_item() can be as large as the nodesize. For an existing btrfs with 64KiB nodesize, it means there is a high chance to fail the kmalloc() call if there is not enough physically contiguous pages. Meanwhile there is really no need for such physically contiguous pages, as we only use that buffer to compare the content of the item. Use kvmalloc() to replace the kmalloc() call. For most cases that kvmalloc() call will be easily fulfilled by regular kmalloc(), but for really large items and large nodes, kvmalloc() will have a much higher chance to get memory allocated. Reviewed-by: Daniel Vacek Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tree-log.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index cfb0b0e9e1248e..7fb476b19eb457 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -503,7 +503,7 @@ static int overwrite_item(struct walk_control *wc) btrfs_release_path(wc->subvol_path); return 0; } - src_copy = kmalloc(item_size, GFP_NOFS); + src_copy = kvmalloc(item_size, GFP_NOFS); if (!src_copy) { btrfs_abort_log_replay(wc, -ENOMEM, "failed to allocate memory for log leaf item"); @@ -514,7 +514,7 @@ static int overwrite_item(struct walk_control *wc) dst_ptr = btrfs_item_ptr_offset(dst_eb, dst_slot); ret = memcmp_extent_buffer(dst_eb, src_copy, dst_ptr, item_size); - kfree(src_copy); + kvfree(src_copy); /* * they have the same contents, just return, this saves * us from cowing blocks in the destination tree and doing From 883a1a260cf84c954bc6295dc052425d51dff923 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 8 Sep 2026 16:45:40 +0930 Subject: [PATCH 1645/2286] btrfs: use kvmalloc() for uncompress_inline() Although btrfs doesn't support inlined extents larger than PAGE_SIZE for bs > ps cases, it's still possible for the experimental bs > ps support to mount a btrfs created on a system with a much larger page size, thus can still hit an inlined extent that is way larger than the current page size. E.g. a compressed inline extent which has 32K compressed size, is created on 64K page sized ARM64 with 64K sectorsize, then mounted on x86_64 with the experimental bs > ps support. In that case, when reading the compressed inline extent, we need to allocate a buffer that is the same size as the compressed inline extent (32K). That kmalloc() call will request physically contiguous memory for that 32K allocation, and if the system has a very fragmented memory space, such allocation can fail. But there is really no reason that we require such buffer to be physically contiguous, so change it to kvmalloc() to reduce the chance of allocation failure for bs > ps cases. And for all bs <= ps cases, the kvmalloc() call will just be fulfilled by kmalloc() so this will not bring any change to the most common cases. Only bs > ps will get the benefit of less memory allocation failure. Reviewed-by: Daniel Vacek Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index c7bbcd11d01e8c..e7046e9a0e70de 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -7149,7 +7149,7 @@ static noinline int uncompress_inline(struct btrfs_path *path, compress_type = btrfs_file_extent_compression(leaf, item); max_size = btrfs_file_extent_ram_bytes(leaf, item); inline_size = btrfs_file_extent_inline_item_len(leaf, path->slots[0]); - tmp = kmalloc(inline_size, GFP_NOFS); + tmp = kvmalloc(inline_size, GFP_NOFS); if (!tmp) return -ENOMEM; ptr = btrfs_file_extent_inline_start(item); @@ -7170,7 +7170,7 @@ static noinline int uncompress_inline(struct btrfs_path *path, if (max_size < blocksize) folio_zero_range(folio, max_size, blocksize - max_size); - kfree(tmp); + kvfree(tmp); return ret; } From 0f2cb9a9e95f3992e760de2d531c241e32c750eb Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Tue, 8 Sep 2026 16:45:41 +0930 Subject: [PATCH 1646/2286] btrfs: use kvmalloc() to allocate compression workspace buffer for zlib and zstd With the experimental bs > ps support, the workspace buffer for both zlib and zstd can be as large as 64K, and on 4K page sized systems such kmalloc() calls have a much higher chance to fail, as that requires physically contiguous memory to fulfill such allocation. The same also applies to S390's hardware accelerated path, which requires a buffer size of 4 pages. Meanwhile lzo is already using kvmalloc() for its buffer, and there is no special requirement for any physically contiguous memory anyway. So change the zlib and zstd workspace buffer allocation to use kvmalloc() to reduce the chance of memory allocation failure. Reviewed-by: Daniel Vacek Reviewed-by: Johannes Thumshirn Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/zlib.c | 10 +++++----- fs/btrfs/zstd.c | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/btrfs/zlib.c b/fs/btrfs/zlib.c index 486b52db583ecb..e995d0b1452b92 100644 --- a/fs/btrfs/zlib.c +++ b/fs/btrfs/zlib.c @@ -49,7 +49,7 @@ void zlib_free_workspace(struct list_head *ws) struct workspace *workspace = list_entry(ws, struct workspace, list); kvfree(workspace->strm.workspace); - kfree(workspace->buf); + kvfree(workspace->buf); kfree(workspace); } @@ -84,13 +84,13 @@ struct list_head *zlib_alloc_workspace(struct btrfs_fs_info *fs_info, unsigned i workspace->level = level; workspace->buf = NULL; if (need_special_buffer(fs_info)) { - workspace->buf = kmalloc(ZLIB_DFLTCC_BUF_SIZE, - __GFP_NOMEMALLOC | __GFP_NORETRY | - __GFP_NOWARN | GFP_NOIO); + workspace->buf = kvmalloc(ZLIB_DFLTCC_BUF_SIZE, + __GFP_NOMEMALLOC | __GFP_NORETRY | + __GFP_NOWARN | GFP_NOIO); workspace->buf_size = ZLIB_DFLTCC_BUF_SIZE; } if (!workspace->buf) { - workspace->buf = kmalloc(fs_info->sectorsize, GFP_KERNEL); + workspace->buf = kvmalloc(fs_info->sectorsize, GFP_KERNEL); workspace->buf_size = fs_info->sectorsize; } if (!workspace->strm.workspace || !workspace->buf) diff --git a/fs/btrfs/zstd.c b/fs/btrfs/zstd.c index 280ac5273438b7..cc92d0b1b948cd 100644 --- a/fs/btrfs/zstd.c +++ b/fs/btrfs/zstd.c @@ -373,7 +373,7 @@ void zstd_free_workspace(struct list_head *ws) struct workspace *workspace = list_entry(ws, struct workspace, list); kvfree(workspace->mem); - kfree(workspace->buf); + kvfree(workspace->buf); kfree(workspace); } @@ -391,7 +391,7 @@ struct list_head *zstd_alloc_workspace(struct btrfs_fs_info *fs_info, int level) workspace->req_level = level; workspace->last_used = jiffies; workspace->mem = kvmalloc(workspace->size, GFP_KERNEL | __GFP_NOWARN); - workspace->buf = kmalloc(fs_info->sectorsize, GFP_KERNEL); + workspace->buf = kvmalloc(fs_info->sectorsize, GFP_KERNEL); if (!workspace->mem || !workspace->buf) goto fail; From 9ef5239d53e1f18a038358481d4cc3448e98d652 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:19:55 -0400 Subject: [PATCH 1647/2286] btrfs: tests: rename process_page_range() to process_folio_range() It already operates on folios. No functional change. Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tests/extent-io-tests.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index 23459cd4e50388..6eb55bfb2bd4e6 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -18,8 +18,8 @@ #define PROCESS_RELEASE (1U << 1) #define PROCESS_TEST_LOCKED (1U << 2) -static noinline int process_page_range(struct inode *inode, u64 start, u64 end, - unsigned long flags) +static noinline int process_folio_range(struct inode *inode, u64 start, u64 end, + unsigned long flags) { int ret; struct folio_batch fbatch; @@ -221,8 +221,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) test_start, max_bytes - 1, start, end); goto out_bits; } - if (process_page_range(inode, start, end, - PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { + if (process_folio_range(inode, start, end, + PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { test_err("there were unlocked pages in the range"); goto out_bits; } @@ -276,8 +276,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) test_start, total_dirty - 1, start, end); goto out_bits; } - if (process_page_range(inode, start, end, - PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { + if (process_folio_range(inode, start, end, + PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { test_err("pages in range were not all locked"); goto out_bits; } @@ -317,8 +317,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) test_start, test_start + PAGE_SIZE - 1, start, end); goto out_bits; } - if (process_page_range(inode, start, end, PROCESS_TEST_LOCKED | - PROCESS_UNLOCK)) { + if (process_folio_range(inode, start, end, PROCESS_TEST_LOCKED | + PROCESS_UNLOCK)) { test_err("pages in range were not all locked"); goto out_bits; } @@ -330,8 +330,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) out: if (locked_page) put_page(locked_page); - process_page_range(inode, 0, total_dirty - 1, - PROCESS_UNLOCK | PROCESS_RELEASE); + process_folio_range(inode, 0, total_dirty - 1, + PROCESS_UNLOCK | PROCESS_RELEASE); iput(inode); out_root_info: btrfs_free_dummy_root(root); From a6f0c4b59ddb252d45e5027fec5c5a929e50f5a8 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:19:56 -0400 Subject: [PATCH 1648/2286] btrfs: tests: convert test_find_delalloc() to use folios This removes the last btrfs callers of find_or_create_page(), find_lock_page(), SetPageDirty(), ClearPageDirty(), and get_page(), and 15 calls to compound_head(). The folio lookups return an ERR_PTR instead of NULL, so adjust the error handling. Update the comments and test messages accordingly. The test still works in PAGE_SIZE units, which relies on the test inode never getting large folios, so assert that the folios are order-0 where that matters. Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tests/extent-io-tests.c | 97 ++++++++++++++++---------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index 6eb55bfb2bd4e6..ee8eabac47f110 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -112,8 +112,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) struct btrfs_root *root = NULL; struct inode *inode = NULL; struct extent_io_tree *tmp; - struct page *page; - struct page *locked_page = NULL; + struct folio *folio; + struct folio *locked_folio = NULL; /* In this test we need at least 2 file extents at its maximum size */ u64 max_bytes = BTRFS_MAX_EXTENT_SIZE; u64 total_dirty = 2 * max_bytes; @@ -152,23 +152,27 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) btrfs_extent_io_tree_init(NULL, tmp, IO_TREE_SELFTEST); /* - * First go through and create and mark all of our pages dirty, we pin - * everything to make sure our pages don't get evicted and screw up our + * First go through and create and mark all of our folios dirty, we pin + * everything to make sure our folios don't get evicted and screw up our * test. */ for (pgoff_t index = 0; index < (total_dirty >> PAGE_SHIFT); index++) { - page = find_or_create_page(inode->i_mapping, index, GFP_KERNEL); - if (!page) { - test_err("failed to allocate test page"); - ret = -ENOMEM; + folio = __filemap_get_folio(inode->i_mapping, index, + FGP_LOCK | FGP_ACCESSED | FGP_CREAT, + GFP_KERNEL); + if (IS_ERR(folio)) { + test_err("failed to allocate test folio"); + ret = PTR_ERR(folio); goto out; } - SetPageDirty(page); + /* The ranges below assume page sized folios. */ + ASSERT(folio_order(folio) == 0); + folio_set_dirty(folio); if (index) { - unlock_page(page); + folio_unlock(folio); } else { - get_page(page); - locked_page = page; + folio_get(folio); + locked_folio = folio; } } @@ -179,8 +183,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) btrfs_set_extent_bit(tmp, 0, sectorsize - 1, EXTENT_DELALLOC, NULL); start = 0; end = start + PAGE_SIZE - 1; - found = find_lock_delalloc_range(inode, page_folio(locked_page), &start, - &end); + found = find_lock_delalloc_range(inode, locked_folio, &start, &end); if (!found) { test_err("should have found at least one delalloc"); goto out_bits; @@ -191,8 +194,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) goto out_bits; } btrfs_unlock_extent(tmp, start, end, NULL); - unlock_page(locked_page); - put_page(locked_page); + folio_unlock(locked_folio); + folio_put(locked_folio); /* * Test this scenario @@ -201,17 +204,17 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) * |--- search ---| */ test_start = SZ_64M; - locked_page = find_lock_page(inode->i_mapping, - test_start >> PAGE_SHIFT); - if (!locked_page) { - test_err("couldn't find the locked page"); + locked_folio = filemap_lock_folio(inode->i_mapping, test_start >> PAGE_SHIFT); + if (IS_ERR(locked_folio)) { + test_err("couldn't find the locked folio"); + locked_folio = NULL; goto out_bits; } + ASSERT(folio_order(locked_folio) == 0); btrfs_set_extent_bit(tmp, sectorsize, max_bytes - 1, EXTENT_DELALLOC, NULL); start = test_start; end = start + PAGE_SIZE - 1; - found = find_lock_delalloc_range(inode, page_folio(locked_page), &start, - &end); + found = find_lock_delalloc_range(inode, locked_folio, &start, &end); if (!found) { test_err("couldn't find delalloc in our range"); goto out_bits; @@ -223,12 +226,12 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) } if (process_folio_range(inode, start, end, PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { - test_err("there were unlocked pages in the range"); + test_err("there were unlocked folios in the range"); goto out_bits; } btrfs_unlock_extent(tmp, start, end, NULL); - /* locked_page was unlocked above */ - put_page(locked_page); + /* locked_folio was unlocked above */ + folio_put(locked_folio); /* * Test this scenario @@ -236,16 +239,16 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) * |--- search ---| */ test_start = max_bytes + sectorsize; - locked_page = find_lock_page(inode->i_mapping, test_start >> - PAGE_SHIFT); - if (!locked_page) { - test_err("couldn't find the locked page"); + locked_folio = filemap_lock_folio(inode->i_mapping, test_start >> PAGE_SHIFT); + if (IS_ERR(locked_folio)) { + test_err("couldn't find the locked folio"); + locked_folio = NULL; goto out_bits; } + ASSERT(folio_order(locked_folio) == 0); start = test_start; end = start + PAGE_SIZE - 1; - found = find_lock_delalloc_range(inode, page_folio(locked_page), &start, - &end); + found = find_lock_delalloc_range(inode, locked_folio, &start, &end); if (found) { test_err("found range when we shouldn't have"); goto out_bits; @@ -265,8 +268,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) btrfs_set_extent_bit(tmp, max_bytes, total_dirty - 1, EXTENT_DELALLOC, NULL); start = test_start; end = start + PAGE_SIZE - 1; - found = find_lock_delalloc_range(inode, page_folio(locked_page), &start, - &end); + found = find_lock_delalloc_range(inode, locked_folio, &start, &end); if (!found) { test_err("didn't find our range"); goto out_bits; @@ -278,36 +280,35 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) } if (process_folio_range(inode, start, end, PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { - test_err("pages in range were not all locked"); + test_err("folios in range were not all locked"); goto out_bits; } btrfs_unlock_extent(tmp, start, end, NULL); /* - * Now to test where we run into a page that is no longer dirty in the + * Now to test where we run into a folio that is no longer dirty in the * range we want to find. */ - page = find_get_page(inode->i_mapping, - (max_bytes + SZ_1M) >> PAGE_SHIFT); - if (!page) { - test_err("couldn't find our page"); + folio = filemap_get_folio(inode->i_mapping, (max_bytes + SZ_1M) >> PAGE_SHIFT); + if (IS_ERR(folio)) { + test_err("couldn't find our folio"); goto out_bits; } - ClearPageDirty(page); - put_page(page); + ASSERT(folio_order(folio) == 0); + folio_clear_dirty(folio); + folio_put(folio); /* We unlocked it in the previous test */ - lock_page(locked_page); + folio_lock(locked_folio); start = test_start; end = start + PAGE_SIZE - 1; /* - * Currently if we fail to find dirty pages in the delalloc range we + * Currently if we fail to find dirty folios in the delalloc range we * will adjust max_bytes down to PAGE_SIZE and then re-search. If * this changes at any point in the future we will need to fix this * tests expected behavior. */ - found = find_lock_delalloc_range(inode, page_folio(locked_page), &start, - &end); + found = find_lock_delalloc_range(inode, locked_folio, &start, &end); if (!found) { test_err("didn't find our range"); goto out_bits; @@ -319,7 +320,7 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) } if (process_folio_range(inode, start, end, PROCESS_TEST_LOCKED | PROCESS_UNLOCK)) { - test_err("pages in range were not all locked"); + test_err("folios in range were not all locked"); goto out_bits; } ret = 0; @@ -328,8 +329,8 @@ static int test_find_delalloc(u32 sectorsize, u32 nodesize) dump_extent_io_tree(tmp); btrfs_clear_extent_bit(tmp, 0, total_dirty - 1, (unsigned)-1, NULL); out: - if (locked_page) - put_page(locked_page); + if (locked_folio) + folio_put(locked_folio); process_folio_range(inode, 0, total_dirty - 1, PROCESS_UNLOCK | PROCESS_RELEASE); iput(inode); From 768906912413ca1acbc0144a4863bbda4efc49c7 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:19:57 -0400 Subject: [PATCH 1649/2286] btrfs: tests: use eb folio helpers in extent buffer memory checks dump_eb_and_memory_contents() and verify_eb_and_memory() hardcode one page per folio instead of using get_eb_folio_index() and get_eb_offset_in_folio() like the rest of the extent buffer code. Use the helpers and folio_address(). This removes the last struct page usage in the file. No functional change. The tests only run with sectorsize == PAGE_SIZE, and the test extent buffers are backed by order-0 folios. Assisted-by: Claude:claude-fable-5-1 Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/tests/extent-io-tests.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/tests/extent-io-tests.c b/fs/btrfs/tests/extent-io-tests.c index ee8eabac47f110..cd045778400dc0 100644 --- a/fs/btrfs/tests/extent-io-tests.c +++ b/fs/btrfs/tests/extent-io-tests.c @@ -672,8 +672,9 @@ static void dump_eb_and_memory_contents(struct extent_buffer *eb, void *memory, const char *test_name) { for (int i = 0; i < eb->len; i++) { - struct page *page = folio_page(eb->folios[i >> PAGE_SHIFT], 0); - void *addr = page_address(page) + offset_in_page(i); + const unsigned long idx = get_eb_folio_index(eb, i); + void *addr = folio_address(eb->folios[idx]) + + get_eb_offset_in_folio(eb, i); if (memcmp(addr, memory + i, 1) != 0) { test_err("%s failed", test_name); @@ -688,9 +689,12 @@ static int verify_eb_and_memory(struct extent_buffer *eb, void *memory, const char *test_name) { for (int i = 0; i < (eb->len >> PAGE_SHIFT); i++) { - void *eb_addr = folio_address(eb->folios[i]); + const unsigned long offset = i << PAGE_SHIFT; + const unsigned long idx = get_eb_folio_index(eb, offset); + void *eb_addr = folio_address(eb->folios[idx]) + + get_eb_offset_in_folio(eb, offset); - if (memcmp(memory + (i << PAGE_SHIFT), eb_addr, PAGE_SIZE) != 0) { + if (memcmp(memory + offset, eb_addr, PAGE_SIZE) != 0) { dump_eb_and_memory_contents(eb, memory, test_name); return -EUCLEAN; } From 3c5ff39a3899a03c2aa14217b233f40642e009ca Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:19:58 -0400 Subject: [PATCH 1650/2286] btrfs: convert btrfs_compr_pool_scan() to use folios The compression pool holds order-0 folios, but btrfs_compr_pool_scan() walks it as struct page through page->lru. Walk it as folios, matching the other compression pool functions. This removes the last use of page->lru in btrfs and saves a call to compound_head() per freed folio. Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/compression.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index c62b5148d5ac19..979b2ffbd8fc7e 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -168,10 +168,10 @@ static unsigned long btrfs_compr_pool_scan(struct shrinker *sh, struct shrink_co spin_unlock(&compr_pool.lock); list_for_each_safe(tmp, next, &remove) { - struct page *page = list_entry(tmp, struct page, lru); + struct folio *folio = list_entry(tmp, struct folio, lru); - ASSERT(page_ref_count(page) == 1); - put_page(page); + ASSERT(folio_ref_count(folio) == 1); + folio_put(folio); } return freed; From d8fe0fda534371ff5838d038b8cc81412a9b92d3 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:19:59 -0400 Subject: [PATCH 1651/2286] btrfs: convert heuristic_collect_sample() to use folios Convert the sampling loop to folios. This removes the last caller of find_get_page() in btrfs and saves a call to compound_head() per sampled page. Document that the lookup is not supposed to fail with an ASSERT(). Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/compression.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 979b2ffbd8fc7e..fa8b92592321ee 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -1488,7 +1488,7 @@ static bool sample_repeated_patterns(struct heuristic_ws *ws) static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, struct heuristic_ws *ws) { - struct page *page; + struct folio *folio; pgoff_t index, index_end; u32 i, curr_sample_pos; u8 *in_data; @@ -1514,8 +1514,10 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, curr_sample_pos = 0; while (index < index_end) { - page = find_get_page(inode->i_mapping, index); - in_data = kmap_local_page(page); + folio = filemap_get_folio(inode->i_mapping, index); + ASSERT(!IS_ERR(folio)); + in_data = kmap_local_folio(folio, + offset_in_folio(folio, (u64)index << PAGE_SHIFT)); /* Handle case where the start is not aligned to PAGE_SIZE */ i = start % PAGE_SIZE; while (i < PAGE_SIZE - SAMPLING_READ_SIZE) { @@ -1529,7 +1531,7 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, curr_sample_pos += SAMPLING_READ_SIZE; } kunmap_local(in_data); - put_page(page); + folio_put(folio); index++; } From dcfc00dc024347775d52101c2c3c4501bde486c7 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:20:00 -0400 Subject: [PATCH 1652/2286] btrfs: fix stale function references in compression comments add_ra_bio_pages() was renamed to add_ra_bio_folios(), and btrfs_compress_filemap_get_folio() wraps filemap_get_folio(), not find_get_page(). Update the comments accordingly. Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/compression.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index fa8b92592321ee..20169d02896191 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -431,7 +431,7 @@ static noinline int add_ra_bio_folios(struct inode *inode, u64 compressed_end, } /* - * Since add_ra_bio_pages() is always speculative, suppress + * Since add_ra_bio_folios() is always speculative, suppress * allocation warnings. */ masked_constraint_gfp = mapping_gfp_constraint(mapping, constraint_gfp); @@ -960,7 +960,7 @@ bool btrfs_compress_level_valid(unsigned int type, int level) return levels->min_level <= level && level <= levels->max_level; } -/* Wrapper around find_get_page(), with extra error message. */ +/* Wrapper around filemap_get_folio(), with extra error message. */ int btrfs_compress_filemap_get_folio(struct address_space *mapping, u64 start, struct folio **in_folio_ret) { From cf7ded644ed72747748b62dd7eda7e67c2d1b547 Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Mon, 7 Sep 2026 16:20:01 -0400 Subject: [PATCH 1653/2286] btrfs: use folios for reading super blocks from the block device btrfs_read_disk_super() and the zoned super block log comparison go through read_cache_page_gfp() and page_address(), and btrfs_release_disk_super() recovers the page with virt_to_page(). Use mapping_read_folio_gfp(), folio_address(), and virt_to_folio() instead. This removes the last callers of read_cache_page_gfp() and put_page() in btrfs. Compute the super block address with offset_in_folio() as write_dev_supers() does, rather than assuming it is at the start of the page. Assisted-by: Claude:claude-fable-5-1 Reviewed-by: Qu Wenruo Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 16 +++++++--------- fs/btrfs/zoned.c | 12 ++++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 1fad1aa716debf..7c040f22dbc3e5 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -1358,16 +1358,14 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, void btrfs_release_disk_super(struct btrfs_super_block *super) { - struct page *page = virt_to_page(super); - - put_page(page); + folio_put(virt_to_folio(super)); } struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev, int copy_num, bool drop_cache) { struct btrfs_super_block *super; - struct page *page; + struct folio *folio; u64 bytenr, bytenr_orig; struct address_space *mapping = bdev->bd_mapping; int ret; @@ -1388,7 +1386,7 @@ struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev, ASSERT(copy_num == 0); /* - * Drop the page of the primary superblock, so later read will + * Drop the folio of the primary superblock, so later read will * always read from the device. */ invalidate_inode_pages2_range(mapping, bytenr >> PAGE_SHIFT, @@ -1396,12 +1394,12 @@ struct btrfs_super_block *btrfs_read_disk_super(struct block_device *bdev, } filemap_invalidate_lock_shared(mapping); - page = read_cache_page_gfp(mapping, bytenr >> PAGE_SHIFT, GFP_NOFS); + folio = mapping_read_folio_gfp(mapping, bytenr >> PAGE_SHIFT, GFP_NOFS); filemap_invalidate_unlock_shared(mapping); - if (IS_ERR(page)) - return ERR_CAST(page); + if (IS_ERR(folio)) + return ERR_CAST(folio); - super = page_address(page); + super = folio_address(folio) + offset_in_folio(folio, bytenr); if (btrfs_super_magic(super) != BTRFS_MAGIC || btrfs_super_bytenr(super) != bytenr_orig) { btrfs_release_disk_super(super); diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index 08a15465a0877d..a1ef8caaacdab4 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -123,24 +123,24 @@ static int sb_write_pointer(struct block_device *bdev, struct blk_zone *zones, } else if (full[0] && full[1]) { /* Compare two super blocks */ struct address_space *mapping = bdev->bd_mapping; - struct page *page[BTRFS_NR_SB_LOG_ZONES]; struct btrfs_super_block *super[BTRFS_NR_SB_LOG_ZONES]; for (int i = 0; i < BTRFS_NR_SB_LOG_ZONES; i++) { u64 zone_end = (zones[i].start + zones[i].capacity) << SECTOR_SHIFT; u64 bytenr = ALIGN_DOWN(zone_end, BTRFS_SUPER_INFO_SIZE) - BTRFS_SUPER_INFO_SIZE; + struct folio *folio; filemap_invalidate_lock_shared(mapping); - page[i] = read_cache_page_gfp(mapping, - bytenr >> PAGE_SHIFT, GFP_NOFS); + folio = mapping_read_folio_gfp(mapping, bytenr >> PAGE_SHIFT, + GFP_NOFS); filemap_invalidate_unlock_shared(mapping); - if (IS_ERR(page[i])) { + if (IS_ERR(folio)) { if (i == 1) btrfs_release_disk_super(super[0]); - return PTR_ERR(page[i]); + return PTR_ERR(folio); } - super[i] = page_address(page[i]); + super[i] = folio_address(folio) + offset_in_folio(folio, bytenr); } if (btrfs_super_generation(super[0]) > From d8fb1db2dba2b0a23d8952cf79ba4f95a32000da Mon Sep 17 00:00:00 2001 From: Tal Zussman Date: Wed, 9 Sep 2026 13:10:48 -0400 Subject: [PATCH 1654/2286] btrfs: use u64 for the page indices in heuristic_collect_sample() index and index_end are derived from the u64 start and end offsets, and index is shifted back into a byte offset for offset_in_folio(), which needs a cast to u64 to be safe on 32-bit. Make them u64 instead so the cast goes away. They still fit pgoff_t where they are passed to filemap_get_folio(), as they came from a valid file offset. Signed-off-by: Tal Zussman Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/compression.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 20169d02896191..228d1cdd7c2950 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -1489,7 +1489,7 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, struct heuristic_ws *ws) { struct folio *folio; - pgoff_t index, index_end; + u64 index, index_end; u32 i, curr_sample_pos; u8 *in_data; @@ -1517,7 +1517,7 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, folio = filemap_get_folio(inode->i_mapping, index); ASSERT(!IS_ERR(folio)); in_data = kmap_local_folio(folio, - offset_in_folio(folio, (u64)index << PAGE_SHIFT)); + offset_in_folio(folio, index << PAGE_SHIFT)); /* Handle case where the start is not aligned to PAGE_SIZE */ i = start % PAGE_SIZE; while (i < PAGE_SIZE - SAMPLING_READ_SIZE) { From 4ef2c403d24c33eb772d484e45ded5c0d915a7ec Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 9 Sep 2026 12:26:30 +0100 Subject: [PATCH 1655/2286] btrfs: increment extent count once when logging extents during fast fsync In btrfs_log_changed_extents() we increment the extent count twice, and we then fallback to a transaction commit if the count reaches a threshold of 32K. However we increment the count twice, which is confusing and pointless. So increment the count only once and reduce the threshold to half (16K). Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-log.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c index 7fb476b19eb457..1d9dfb63fc5a4f 100644 --- a/fs/btrfs/tree-log.c +++ b/fs/btrfs/tree-log.c @@ -5350,7 +5350,7 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans, * have a bunch of extents we just want to commit since it will * be faster. */ - if (++num > 32768) { + if (++num > SZ_16K) { list_del_init(&tree->modified_extents); ret = -EFBIG; goto process; @@ -5368,7 +5368,6 @@ static int btrfs_log_changed_extents(struct btrfs_trans_handle *trans, refcount_inc(&em->refs); em->flags |= EXTENT_FLAG_LOGGING; list_add_tail(&em->list, &extents); - num++; } list_sort(NULL, &extents, extent_cmp); From 7a881b1139bb34c48cddea20dae9f53b2289de78 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 11 Sep 2026 11:56:29 +0100 Subject: [PATCH 1656/2286] btrfs: tree-checker: cache accessor return value in CHECK_FE_ALIGNED() We are calling an accessor for a file extent item multiple times in the CHECK_FE_ALIGNED() macro, even when we don't find a corruption (once in the if statement's expression and then once again in the expression for the return value). This adds extra runtime overhead (which is critical since the tree checker runs against every extent buffer when it's read or before persisting it) and increases the module's size. So cache the accessor's return value in a variable and use it, reducing runtime, object size and making the source code shorter too. Also avoid repeating twice the IS_ALIGNED() computation. Before: $ size fs/btrfs/btrfs.ko text data bss dec hex filename 2073340 217928 15624 2306892 23334c fs/btrfs/btrfs.ko After: $ size fs/btrfs/btrfs.ko text data bss dec hex filename 2073076 217928 15624 2306628 233244 fs/btrfs/btrfs.ko Also running the following fsstress test and capturing the runtime of check_extent_data_item() (the only caller of CHECK_FE_ALIGNED()) in nanoseconds (using bpftrace), showed the following runtime improvements: Test: mkfs.btrfs -f /dev/nullb0 mount /dev/nullb0 /mnt fsstress -w -p 8 -n 5000 -s 12345 -d /mnt umount /mnt Before: Count: 2033671 Range: 0.000 - 1336060.000; Mean: 679.642; Median: 656.000; Stddev: 2031.323 Percentiles: 90th: 850.000; 95th: 909.000; 99th: 1272.000 0.000 - 6.647: 11 | 6.647 - 28.241: 36 | 28.241 - 110.807: 176 | 110.807 - 426.512: 38012 # 426.512 - 1633.663: 1983372 ##################################################### 1633.663 - 6249.399: 8315 | 6249.399 - 23898.422: 3243 | 23898.422 - 91382.340: 153 | 91382.340 - 349418.114: 36 | 349418.114 - 1336060.000: 11 | After: Count: 2092797 Range: 0.000 - 1677284.000; Mean: 627.650; Median: 617.000; Stddev: 1848.010 Percentiles: 90th: 809.000; 95th: 859.000; 99th: 1209.000 0.000 - 6.823: 18 | 6.823 - 29.602: 82 | 29.602 - 118.702: 416 | 118.702 - 467.232: 515774 ################# 467.232 - 1830.549: 1565599 ##################################################### 1830.549 - 7163.339: 5297 | 7163.339 - 28023.237: 4186 | 28023.237 - 109619.427: 166 | 109619.427 - 428793.471: 25 | 428793.471 - 1677284.000: 4 | Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-checker.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 594266c9c116ff..0f4f1b347c9e8e 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -108,13 +108,14 @@ static void file_extent_err(const struct extent_buffer *eb, int slot, */ #define CHECK_FE_ALIGNED(leaf, slot, fi, name, alignment) \ ({ \ - if (unlikely(!IS_ALIGNED(btrfs_file_extent_##name((leaf), (fi)), \ - (alignment)))) \ + const u64 val = btrfs_file_extent_##name((leaf), (fi)); \ + const bool not_aligned = !IS_ALIGNED(val, (alignment)); \ + \ + if (unlikely(not_aligned)) \ file_extent_err((leaf), (slot), \ "invalid %s for file extent, have %llu, should be aligned to %u", \ - (#name), btrfs_file_extent_##name((leaf), (fi)), \ - (alignment)); \ - (!IS_ALIGNED(btrfs_file_extent_##name((leaf), (fi)), (alignment))); \ + (#name), val, (alignment)); \ + not_aligned; \ }) static u64 file_extent_end(struct extent_buffer *leaf, From 5a593fe11e9f08bb743a13a283e71da971cbffbc Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 13 Sep 2026 20:59:15 +0930 Subject: [PATCH 1657/2286] btrfs: cleanup and rename submit_extent_folio() Cleanup submit_extent_folio() by: - Remove @size parameter Since commit b2e743927fdd ("btrfs: make btrfs_do_readpage() to do block-by-block read"), all callers are passing sectorsize as @size, so there is no need for such parameter. Furthermore since we only write one block at a time, there is no need for a while() loop, nor the advance of various local variables. - Update the comments on the parameter list * @disk_bytenr is shared for both read and write * rename @page to @folio - Update the return value to return 0 or error Since we won't queue multiple blocks anyway, there is no point in returning the queued bytes. It makes more sense to return an error code, although the only error code will be -EUCLEAN for writes. - Rename submit_extent_folio() to submit_one_block() - Rename submit_one_sector() to submit_write_sector() - Update the error message to utilize the new returned error code inside submit_write_sector() Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/extent_io.c | 187 +++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 106 deletions(-) diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index a221b63bdb205d..55e9144d47595f 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -110,14 +110,14 @@ struct btrfs_bio_ctrl { * make the decision when submitting the bio. * * The pattern between do_readpage(), submit_one_bio() and - * submit_extent_folio() is quite subtle, so tracking this is tricky. + * submit_one_block() is quite subtle, so tracking this is tricky. * * As we process extent E, we might submit a bio with existing built up * extents before adding E to a new bio, or we might just add E to the * bio. As a result, E's generation could apply to the current bio or * to the next one, so we need to be careful to update the bio_ctrl's * generation with E's only when we are sure E is added to bio_ctrl->bbio - * in submit_extent_folio(). + * in submit_one_block(). * * See the comment in btrfs_lookup_bio_sums() for more detail on the * need for this optimization. @@ -797,118 +797,95 @@ static int alloc_new_bio(struct btrfs_inode *inode, } /* - * @disk_bytenr: logical bytenr where the write will be - * @page: page to add to the bio - * @size: portion of page that we want to write to - * @pg_offset: offset of the new bio or to check whether we are adding - * a contiguous page to the previous one + * @disk_bytenr: logical bytenr where the read/write will be + * @folio: the folio the block belongs to + * @pg_offset: the offset inside the folio * @read_em_generation: generation of the extent_map we are submitting * (only used for read) * - * The will either add the page into the existing @bio_ctrl->bbio, or allocate a + * This will either add the block into the existing @bio_ctrl->bbio, or allocate a * new one in @bio_ctrl->bbio. * The mirror number for this IO should already be initialized in * @bio_ctrl->mirror_num. * - * Return the number of bytes that are queued into a bio. - * If the returned bytes is smaller than @size, it means we hit a critical error - * for data write, where there is no ordered extent for the range. + * Return 0 if the block is queued or submitted. + * Return <0 for error. */ -static unsigned int submit_extent_folio(struct btrfs_bio_ctrl *bio_ctrl, - u64 disk_bytenr, struct folio *folio, - size_t size, unsigned long pg_offset, - u64 read_em_generation) +static int submit_one_block(struct btrfs_bio_ctrl *bio_ctrl, + u64 disk_bytenr, struct folio *folio, + unsigned long pg_offset, u64 read_em_generation) { struct btrfs_inode *inode = folio_to_inode(folio); + const struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u32 blocksize = fs_info->sectorsize; loff_t file_offset = folio_pos(folio) + pg_offset; - unsigned int queued = 0; - ASSERT(pg_offset + size <= folio_size(folio)); + ASSERT(pg_offset + blocksize <= folio_size(folio)); ASSERT(bio_ctrl->end_io_func); if (bio_ctrl->bbio && !btrfs_bio_is_contig(bio_ctrl, disk_bytenr, file_offset)) submit_one_bio(bio_ctrl); - do { - u32 len = size; - - /* Allocate new bio if needed */ - if (!bio_ctrl->bbio) { - int ret; - - ret = alloc_new_bio(inode, bio_ctrl, disk_bytenr, file_offset); - if (ret < 0) - break; - } +again: + /* Allocate new bio if needed */ + if (!bio_ctrl->bbio) { + int ret; - /* Cap to the current ordered extent boundary if there is one. */ - if (len > bio_ctrl->len_to_oe_boundary) { - ASSERT(bio_ctrl->compress_type == BTRFS_COMPRESS_NONE); - ASSERT(is_data_inode(inode)); - len = bio_ctrl->len_to_oe_boundary; - } + ret = alloc_new_bio(inode, bio_ctrl, disk_bytenr, file_offset); + if (ret < 0) + return ret; + } - if (!bio_add_folio(&bio_ctrl->bbio->bio, folio, len, pg_offset)) { - /* bio full: move on to a new one */ - submit_one_bio(bio_ctrl); - continue; - } - /* - * Now that the folio is definitely added to the bio, include its - * generation in the max generation calculation. - */ - bio_ctrl->generation = max(bio_ctrl->generation, read_em_generation); - bio_ctrl->next_file_offset += len; + if (!bio_add_folio(&bio_ctrl->bbio->bio, folio, blocksize, pg_offset)) { + /* bio full: move on to a new one */ + submit_one_bio(bio_ctrl); + goto again; + } - if (bio_ctrl->wbc) - wbc_account_cgroup_owner(bio_ctrl->wbc, folio, len); + /* + * Now that the folio is definitely added to the bio, include its + * generation in the max generation calculation. + */ + bio_ctrl->generation = max(bio_ctrl->generation, read_em_generation); + bio_ctrl->next_file_offset += blocksize; - size -= len; - pg_offset += len; - disk_bytenr += len; - file_offset += len; - queued += len; + if (bio_ctrl->wbc) + wbc_account_cgroup_owner(bio_ctrl->wbc, folio, blocksize); - /* - * len_to_oe_boundary defaults to U32_MAX, which isn't folio or - * sector aligned. alloc_new_bio() then sets it to the end of - * our ordered extent for writes into zoned devices. - * - * When len_to_oe_boundary is tracking an ordered extent, we - * trust the ordered extent code to align things properly, and - * the check above to cap our write to the ordered extent - * boundary is correct. - * - * When len_to_oe_boundary is U32_MAX, the cap above would - * result in a 4095 byte IO for the last folio right before - * we hit the bio limit of UINT_MAX. bio_add_folio() has all - * the checks required to make sure we don't overflow the bio, - * and we should just ignore len_to_oe_boundary completely - * unless we're using it to track an ordered extent. - * - * It's pretty hard to make a bio sized U32_MAX, but it can - * happen when the page cache is able to feed us contiguous - * folios for large extents. - */ - if (bio_ctrl->len_to_oe_boundary != U32_MAX) - bio_ctrl->len_to_oe_boundary -= len; - - /* Ordered extent boundary: move on to a new bio. */ - if (bio_ctrl->len_to_oe_boundary == 0) - submit_one_bio(bio_ctrl); - /* - * If we have accumulated decent amount of IO, send it to the - * block layer so that IO can run while we are accumulating - * more folios to write. - */ - else if (bio_ctrl->wbc && - bio_ctrl->bbio->bio.bi_iter.bi_size >= - inode->root->fs_info->writeback_bio_size) - submit_one_bio(bio_ctrl); + /* + * len_to_oe_boundary defaults to U32_MAX, which isn't folio or sector + * aligned. alloc_new_bio() then sets it to the end of our ordered + * extent for writes into zoned devices. + * + * When len_to_oe_boundary is tracking an ordered extent, the + * len_to_oe_boundary should follow that OE and never go beyond the max + * extent size (128MiB). + * + * When len_to_oe_boundary is U32_MAX, decreasing the length by + * blocksize will never make it reach 0, thus skipping the later + * submit_one_bio() call. So if len_to_oe_boundary() is not tracking + * an OE, do not decrease it. + * + * It's pretty hard to make a bio sized U32_MAX, but it can happen when + * the page cache is able to feed us contiguous folios for large + * extents. + */ + if (bio_ctrl->len_to_oe_boundary != U32_MAX) + bio_ctrl->len_to_oe_boundary -= blocksize; - } while (size); - return queued; + /* Ordered extent boundary: move on to a new bio. */ + if (bio_ctrl->len_to_oe_boundary == 0) + submit_one_bio(bio_ctrl); + /* + * If we have accumulated decent amount of IO, send it to the block + * layer so that IO can run while we are accumulating more folios to + * write. + */ + else if (bio_ctrl->wbc && + bio_ctrl->bbio->bio.bi_iter.bi_size >= fs_info->writeback_bio_size) + submit_one_bio(bio_ctrl); + return 0; } static int attach_extent_buffer_folio(struct extent_buffer *eb, @@ -1092,7 +1069,6 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached, u64 disk_bytenr; u64 block_start; u64 em_gen; - unsigned int queued; ASSERT(IS_ALIGNED(cur, fs_info->sectorsize)); if (cur >= last_byte) { @@ -1206,10 +1182,9 @@ static int btrfs_do_readpage(struct folio *folio, struct extent_map **em_cached, if (force_bio_submit) submit_one_bio(bio_ctrl); - queued = submit_extent_folio(bio_ctrl, disk_bytenr, folio, blocksize, - pg_offset, em_gen); + ret = submit_one_block(bio_ctrl, disk_bytenr, folio, pg_offset, em_gen); /* Read submission should not fail. */ - ASSERT(queued == blocksize); + ASSERT(ret == 0); } return 0; } @@ -1830,10 +1805,10 @@ static struct btrfs_ordered_extent *get_oe_from_bbio(const struct btrfs_bio *bbi * * Caller should make sure filepos < i_size and handle filepos >= i_size case. */ -static int submit_one_sector(struct btrfs_inode *inode, - struct folio *folio, - u64 filepos, struct btrfs_bio_ctrl *bio_ctrl, - loff_t i_size) +static int submit_write_sector(struct btrfs_inode *inode, + struct folio *folio, + u64 filepos, struct btrfs_bio_ctrl *bio_ctrl, + loff_t i_size) { struct btrfs_fs_info *fs_info = inode->root->fs_info; struct btrfs_ordered_extent *oe; @@ -1841,7 +1816,7 @@ static int submit_one_sector(struct btrfs_inode *inode, u64 disk_bytenr; u64 extent_offset; const u32 sectorsize = fs_info->sectorsize; - unsigned int queued; + int ret; ASSERT(IS_ALIGNED(filepos, sectorsize)); @@ -1904,17 +1879,17 @@ static int submit_one_sector(struct btrfs_inode *inode, */ ASSERT(folio_test_writeback(folio)); - queued = submit_extent_folio(bio_ctrl, disk_bytenr, folio, - sectorsize, filepos - folio_pos(folio), 0); - if (unlikely(queued < sectorsize)) { + ret = submit_one_block(bio_ctrl, disk_bytenr, folio, + offset_in_folio(folio, filepos), 0); + if (unlikely(ret < 0)) { btrfs_folio_clear_writeback(fs_info, folio, filepos, sectorsize); btrfs_mark_ordered_io_finished(inode, filepos, fs_info->sectorsize, false); btrfs_err_rl(fs_info, - "failed to queue sector for root %lld ino %llu filepos %llu", + "failed to queue sector for root %lld ino %llu filepos %llu: %pe", btrfs_root_id(inode->root), - btrfs_ino(inode), filepos); - return -EUCLEAN; + btrfs_ino(inode), filepos, ERR_PTR(ret)); + return ret; } return 0; } @@ -1999,7 +1974,7 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode, btrfs_folio_clear_dirty(fs_info, folio, cur, fs_info->sectorsize); continue; } - ret = submit_one_sector(inode, folio, cur, bio_ctrl, i_size); + ret = submit_write_sector(inode, folio, cur, bio_ctrl, i_size); if (unlikely(ret < 0)) { if (!found_error) found_error = ret; From 44edf5160b3479877fb8f76b4ebe72af676138ed Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 10 Sep 2026 11:35:52 +0930 Subject: [PATCH 1658/2286] btrfs: fix off-by-one end related to inode_need_compress() In most cases btrfs uses @end as the inclusive end bytenr for a range, and this applies to inode_need_compress(). However we have several sites not following the inclusive bytenr: - run_delalloc_inline() Which assigned @blocksize as @end for inode_need_compress() This makes inode_need_compress() always skip the disk_i_size check. - heuristic_collect_sample() Which assigned "start + BTRFS_MAX_UNCOMPRESSED" to @end, which is the exclusive bytenr. Neither is really causing any real problem, as heuristic_collect_sample() has proper checks to avoid reading anything beyond @end, and the sampling read size is 16 bytes, so it has enough headroom to handle that off-by-one problem. But still I do not like anything out of the common scheme, so fix the off-by-one @end for both call sites, and add extra ASSERT()s to catch such unaligned parameters. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/compression.c | 15 +++++++-------- fs/btrfs/inode.c | 5 ++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 228d1cdd7c2950..7c1c018dbd6fd7 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -1488,11 +1488,14 @@ static bool sample_repeated_patterns(struct heuristic_ws *ws) static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, struct heuristic_ws *ws) { + const u32 blocksize = BTRFS_I(inode)->root->fs_info->sectorsize; struct folio *folio; u64 index, index_end; u32 i, curr_sample_pos; u8 *in_data; + ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize)); + /* * Compression handles the input data by chunks of 128KiB * (defined by BTRFS_MAX_UNCOMPRESSED) @@ -1502,18 +1505,14 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, * MAX_SAMPLE_SIZE - calculated under assumption that heuristic will * process no more than BTRFS_MAX_UNCOMPRESSED at a time. */ - if (end - start > BTRFS_MAX_UNCOMPRESSED) - end = start + BTRFS_MAX_UNCOMPRESSED; + if (end + 1 - start > BTRFS_MAX_UNCOMPRESSED) + end = start + BTRFS_MAX_UNCOMPRESSED - 1; index = start >> PAGE_SHIFT; index_end = end >> PAGE_SHIFT; - /* Don't miss unaligned end */ - if (!PAGE_ALIGNED(end)) - index_end++; - curr_sample_pos = 0; - while (index < index_end) { + while (index <= index_end) { folio = filemap_get_folio(inode->i_mapping, index); ASSERT(!IS_ERR(folio)); in_data = kmap_local_folio(folio, @@ -1522,7 +1521,7 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, i = start % PAGE_SIZE; while (i < PAGE_SIZE - SAMPLING_READ_SIZE) { /* Don't sample any garbage from the last page */ - if (start > end - SAMPLING_READ_SIZE) + if (start > end + 1 - SAMPLING_READ_SIZE) break; memcpy(&ws->sample[curr_sample_pos], &in_data[i], SAMPLING_READ_SIZE); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index e7046e9a0e70de..2a32072849cbd4 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -730,6 +730,9 @@ static inline int inode_need_compress(struct btrfs_inode *inode, u64 start, u64 end, bool check_inline) { struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u32 blocksize = fs_info->sectorsize; + + ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize)); if (unlikely(!btrfs_inode_can_compress(inode))) { DEBUG_WARN("BTRFS: unexpected compression for ino %llu", btrfs_ino(inode)); @@ -2331,7 +2334,7 @@ static int run_delalloc_inline(struct btrfs_inode *inode, struct folio *locked_f btrfs_check_folio_write_protected(locked_folio); if (btrfs_inode_can_compress(inode) && - inode_need_compress(inode, 0, blocksize, true)) { + inode_need_compress(inode, 0, blocksize - 1, true)) { if (inode->defrag_compress > 0 && inode->defrag_compress < BTRFS_NR_COMPRESS_TYPES) { compress_type = inode->defrag_compress; From 70fc94d2e0ba38debe47ce6704ca033531a66419 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 10 Sep 2026 11:35:53 +0930 Subject: [PATCH 1659/2286] btrfs: simplify heuristic_collect_sample() to handle large folios better Currently heuristic_collect_sample() is purely page size based, and it has a lot of extra handling just inside the page. However we already have large folio support, there is no need to look up the same folio repeatedly. Simplify the handling by: - Use @cur as the iterator instead of page index - Handle the sample copying on a per-folio basis Although kmap_local_folio() requires an offset to handle HIGHMEM page mapping, we have rejected large folios for HIGHMEM systems completely. So we can safely handle all sample copying inside the folio in one go. - Remove unnecessary unaligned range handling All the range passed in should be block aligned, thus there is no need to handle cases where sample crosses the block boundary. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/compression.c | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 7c1c018dbd6fd7..ad90e14032dbb3 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -1489,10 +1489,8 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, struct heuristic_ws *ws) { const u32 blocksize = BTRFS_I(inode)->root->fs_info->sectorsize; - struct folio *folio; - u64 index, index_end; - u32 i, curr_sample_pos; - u8 *in_data; + u64 cur = start; + u32 curr_sample_pos = 0; ASSERT(IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize)); @@ -1508,33 +1506,27 @@ static void heuristic_collect_sample(struct inode *inode, u64 start, u64 end, if (end + 1 - start > BTRFS_MAX_UNCOMPRESSED) end = start + BTRFS_MAX_UNCOMPRESSED - 1; - index = start >> PAGE_SHIFT; - index_end = end >> PAGE_SHIFT; + while (cur < end) { + struct folio *folio; + void *in_data; + u64 next_pos; - curr_sample_pos = 0; - while (index <= index_end) { - folio = filemap_get_folio(inode->i_mapping, index); + folio = filemap_get_folio(inode->i_mapping, cur >> PAGE_SHIFT); + /* All folios inside the range should exist and be locked. */ ASSERT(!IS_ERR(folio)); - in_data = kmap_local_folio(folio, - offset_in_folio(folio, index << PAGE_SHIFT)); - /* Handle case where the start is not aligned to PAGE_SIZE */ - i = start % PAGE_SIZE; - while (i < PAGE_SIZE - SAMPLING_READ_SIZE) { - /* Don't sample any garbage from the last page */ - if (start > end + 1 - SAMPLING_READ_SIZE) - break; - memcpy(&ws->sample[curr_sample_pos], &in_data[i], - SAMPLING_READ_SIZE); - i += SAMPLING_INTERVAL; - start += SAMPLING_INTERVAL; + next_pos = min_t(u64, end + 1, folio_next_pos(folio)); + in_data = kmap_local_folio(folio, 0); + + for (; cur < next_pos; cur += SAMPLING_INTERVAL) { + memcpy(&ws->sample[curr_sample_pos], + in_data + offset_in_folio(folio, cur), + SAMPLING_READ_SIZE); curr_sample_pos += SAMPLING_READ_SIZE; } kunmap_local(in_data); folio_put(folio); - - index++; + cur = next_pos; } - ws->sample_size = curr_sample_pos; } From a6e5521ecc5e7129f1e4f4391ae4c24a75062d30 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 17:11:51 +0100 Subject: [PATCH 1660/2286] btrfs: add missing unlikely to a couple error checks during sys chunk array validation It's unexpected to find errors during sys chunk array validation and all checks use the unlikely tag except for two of them, so add it. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 2 +- fs/btrfs/tree-checker.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 94a7e9059a7b3f..21b72c90bd90f9 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2387,7 +2387,7 @@ static int validate_sys_chunk_array(const struct btrfs_fs_info *fs_info, } ret = btrfs_check_chunk_valid(fs_info, NULL, chunk, key.offset, sectorsize); - if (ret < 0) + if (unlikely(ret < 0)) return ret; cur += btrfs_chunk_item_size(num_stripes); } diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 0f4f1b347c9e8e..9cd79d97b9b5e7 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -1121,9 +1121,9 @@ int btrfs_check_chunk_valid(const struct btrfs_fs_info *fs_info, return -EUCLEAN; } - if (!remapped && - !valid_stripe_count(type & BTRFS_BLOCK_GROUP_PROFILE_MASK, - num_stripes, sub_stripes)) { + if (unlikely(!remapped && + !valid_stripe_count(type & BTRFS_BLOCK_GROUP_PROFILE_MASK, + num_stripes, sub_stripes))) { chunk_err(fs_info, leaf, chunk, logical, "invalid num_stripes:sub_stripes %u:%u for profile %llu", num_stripes, sub_stripes, From a85b9336d551d4204678baf9f850feae1440c817 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 17:20:52 +0100 Subject: [PATCH 1661/2286] btrfs: remove redundant eb generation check in btrfs_buffer_uptodate() It's pointless to check if the extent buffer's generation does not match the value of 'parent_transid' because if it does, then we have already entered the previous if statement and returned from the function. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 21b72c90bd90f9..161a7b9bb27a5d 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -125,15 +125,13 @@ int btrfs_buffer_uptodate(struct extent_buffer *eb, u64 parent_transid, return 1; } - if (btrfs_header_generation(eb) != parent_transid) { - btrfs_err_rl(eb->fs_info, + btrfs_err_rl(eb->fs_info, "parent transid verify failed on logical %llu mirror %u wanted %llu found %llu", - eb->start, eb->read_mirror, - parent_transid, btrfs_header_generation(eb)); - clear_extent_buffer_uptodate(eb); - return 0; - } - return 1; + eb->start, eb->read_mirror, + parent_transid, btrfs_header_generation(eb)); + clear_extent_buffer_uptodate(eb); + + return 0; } static bool btrfs_supported_super_csum(u16 csum_type) From fa9194e3ebcbdc1ca16cc4faa99932569efb870b Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 16 Sep 2026 17:36:11 +0100 Subject: [PATCH 1662/2286] btrfs: remove duplicate error message when writing super blocks If the total error count is greater the maximum allowed number of errors, we print exactly the same error message twice. Remove one of the messages. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 161a7b9bb27a5d..a8535e309b5760 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4228,8 +4228,6 @@ int write_all_supers(struct btrfs_trans_handle *trans) total_errors++; } if (unlikely(total_errors > max_errors)) { - btrfs_err(fs_info, "%d errors while writing supers", - total_errors); mutex_unlock(&fs_info->fs_devices->device_list_mutex); /* FUA is masked off if unsupported and can't be the reason */ From 3bb5ed05c1ead17a12456d77591c773e1032c16a Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Wed, 16 Sep 2026 15:17:24 -0700 Subject: [PATCH 1663/2286] btrfs: keep unused block groups queued when a pass fails Once any block_group sets ret!=0 in the main loop of btrfs_delete_unused_bgs(), the check if (ret || btrfs_mixed_space_info(space_info)) { btrfs_put_block_group(block_group); continue; } skips the rest of the unused bgs while unlinking them from fs_info->unused_bgs. There is no "level triggered" re-queueing of empty block groups onto fs_info->unused_bgs so it is possible to leak quite a bit of space this way and unless we happen to get a balance or re-use/re-empty one of these bgs, they are leaked for good, which can lead to a spurious enospc later. While I have observed such leaked blocked groups that are empty but not on the unused_bgs list on production systems, I have not observed that it is definitely due to this issue. I also reproduced this behavior by injecting an ENOSPC error from btrfs_start_trans_remove_block_group which can also fail with ENOMEM, so this feels like a legitimate injection point. To fix it, instead of checking ret in the loop, just break out of the loop when ret != 0. Also, link the bg to the retry list at the individual failure sites so that the failing bg is not leaked. Assisted-by: LLM (reproducer/error injection) Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index ee182369254c08..2eb09c9901c9e1 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -1612,7 +1612,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) space_info = block_group->space_info; - if (ret || btrfs_mixed_space_info(space_info)) { + if (btrfs_mixed_space_info(space_info)) { btrfs_put_block_group(block_group); continue; } @@ -1727,6 +1727,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) ret = inc_block_group_ro(block_group, false); up_write(&space_info->groups_sem); if (ret < 0) { + btrfs_link_bg_list(block_group, &retry_list); ret = 0; goto next; } @@ -1749,6 +1750,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) block_group->start); if (IS_ERR(trans)) { btrfs_dec_block_group_ro(block_group); + btrfs_link_bg_list(block_group, &retry_list); ret = PTR_ERR(trans); goto next; } @@ -1759,6 +1761,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) */ if (!clean_pinned_extents(trans, block_group)) { btrfs_dec_block_group_ro(block_group); + btrfs_link_bg_list(block_group, &retry_list); goto end_trans; } @@ -1845,6 +1848,8 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) next: btrfs_put_block_group(block_group); spin_lock(&fs_info->unused_bgs_lock); + if (ret) + break; } list_splice_tail(&retry_list, &fs_info->unused_bgs); spin_unlock(&fs_info->unused_bgs_lock); From 220c44b1b90fbff746472cbb3dde778b02afbef9 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Tue, 15 Sep 2026 10:58:31 -0700 Subject: [PATCH 1664/2286] btrfs: pre-flush reflink source before taking inode locks Consider the following sketch of a shell script: dd if=/dev/urandom of=/mnt/src bs=1M count=4096 cp --reflink=always /mnt/src /mnt/dst & sleep 0.1 # let the clone reach its flush time dd if=/mnt/src of=/dev/null bs=4K count=1 iflag=direct The current logic in reflink ensures the existence and stability of both the src and destination inode by locking them both and then flushing all dirty pages / ordered_extents under the inode lock. This blocks concurrent usage by even readers of the inode locks, like direct reads or seeks for the duration of writeback on the entirety of the two files. We observe this particular contention frequently in the Meta fleet. It is also a relatively common pattern to write the src file, then reflink it while it is still dirty, so that typical path naturally hits this contention. This workload motivates a relatively simple optimization: trigger the unavoidable src flushing outside the locked region. Therefore, do an optimistic btrfs_wait_ordered_range() without the locks while relying on the calls to btrfs_wait_ordered_range() under the locks in btrfs_remap_file_range_prep() to ensure correctness. In the worst case with concurrent writes while unlocked, we can end up doing the writeback twice, which I think is a reasonable price to pay to avoid victimizing innocent readers in a common case. With and without this patch, the above reproducer runs the reflink in the same ~0.5s on my system. Without the patch, the direct read blocks for basically the full duration of the reflink while with the patch it returns in a few milliseconds. Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/reflink.c | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c index d2a4101912bdb2..24c0b226bc3165 100644 --- a/fs/btrfs/reflink.c +++ b/fs/btrfs/reflink.c @@ -833,6 +833,17 @@ static noinline int btrfs_clone_files(struct file *file, struct file *file_src, return 0; } +static u64 calc_remap_wb_len(struct btrfs_inode *src_inode, loff_t off, loff_t len, + unsigned int remap_flags) +{ + const u32 bs = src_inode->root->fs_info->sectorsize; + const loff_t isize = i_size_read(&src_inode->vfs_inode); + + if (len == 0 && !(remap_flags & REMAP_FILE_DEDUP)) + return ALIGN(isize, bs) - ALIGN_DOWN(off, bs); + return ALIGN(len, bs); +} + static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, loff_t *len, unsigned int remap_flags) @@ -876,10 +887,7 @@ static int btrfs_remap_file_range_prep(struct file *file_in, loff_t pos_in, * not for the ordered extents to complete. We need to wait for them * to complete so that new file extent items are in the fs tree. */ - if (*len == 0 && !(remap_flags & REMAP_FILE_DEDUP)) - wb_len = ALIGN(inode_in->vfs_inode.i_size, bs) - ALIGN_DOWN(pos_in, bs); - else - wb_len = ALIGN(*len, bs); + wb_len = calc_remap_wb_len(inode_in, pos_in, *len, remap_flags); /* * Workaround to make sure NOCOW buffered write reach disk as NOCOW. @@ -930,6 +938,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, struct btrfs_inode *src_inode = BTRFS_I(file_inode(src_file)); struct btrfs_inode *dst_inode = BTRFS_I(file_inode(dst_file)); bool same_inode = dst_inode == src_inode; + u64 wb_start, wb_len; int ret; if (btrfs_is_shutdown(src_inode->root->fs_info)) @@ -938,6 +947,17 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY)) return -EINVAL; + /* + * Optimistically write out the src inode before taking locks. + * Consistency is properly ensured by the btrfs_wait_ordered_range() + * inside btrfs_remap_file_range_prep(). + */ + wb_start = ALIGN_DOWN(off, src_inode->root->fs_info->sectorsize); + wb_len = calc_remap_wb_len(src_inode, off, len, remap_flags); + ret = btrfs_wait_ordered_range(src_inode, wb_start, wb_len); + if (ret < 0) + return ret; + if (same_inode) { btrfs_inode_lock(src_inode, BTRFS_ILOCK_MMAP); } else { From e0c9d01770ef56792fbb2947128e5f43f7d49c61 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Tue, 15 Sep 2026 10:58:32 -0700 Subject: [PATCH 1665/2286] btrfs: skip unlocked reflink source flush if the inode has writers The optimistic unlocked flushing is counter-productive if there is an active writer dirtying pages, as it results in doubly writing back any pages that get dirtied again before the locked flush. Therefore, pessimize it slightly and don't do the unlocked flushing if we have outstanding writable file descriptors or memory mappings. Since the inode isn't locked, this is only advisory, any new mapping or fd that sneaks in after the check but before the locking can still cause extra writeback. But it trivially helps quite a bit in simple cases with a long open file descriptor or mapping. I tested this with two similar workloads. One was keeping and fd open and doing continuous writes into the src file while cloning it and doing lseek on it and the other was slightly meaner, constantly opening and closing the fd to try to race this check. Workload 1 shows a huge regression in write amplification and writers and readers who experience userspace wall time stalls of 100ms+ with just the pre-flush, but recovers to normal with this check. Workload 2 is a smaller regression which is mostly helped but not fully resolved with this check. So we must weigh this regression against the benefit of the unlocked flush in the allegedly more common simpler case. Workload 1: fd kept open, 60s of clone/write/lseek looping. kernel clones clone mean s device MiB/clone writer stalls reader stalls for-next 564 0.095 119 10 10 pre-flush (p1) 179 0.338 405 94 93 skip (p1+p2) 540 0.100 116 9 9 Workload 2: same writer, but closing and reopening the fd around each 64 MiB write with some random sleeping to encourage racing. kernel clones clone mean s device MiB/clone writer stalls reader stalls for-next 740 0.070 83 9 9 pre-flush (p1) 255 0.224 246 27 26 skip (p1+p2) 518 0.107 121 11 11 Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/reflink.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c index 24c0b226bc3165..8e37706b98c31c 100644 --- a/fs/btrfs/reflink.c +++ b/fs/btrfs/reflink.c @@ -950,13 +950,19 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, /* * Optimistically write out the src inode before taking locks. * Consistency is properly ensured by the btrfs_wait_ordered_range() - * inside btrfs_remap_file_range_prep(). + * inside btrfs_remap_file_range_prep(). This optimization causes + * redundant writeback if there is a concurrent writer to the src file + * so try to catch anyone holding the file open for writes and skip + * the optimization in that case. */ wb_start = ALIGN_DOWN(off, src_inode->root->fs_info->sectorsize); wb_len = calc_remap_wb_len(src_inode, off, len, remap_flags); - ret = btrfs_wait_ordered_range(src_inode, wb_start, wb_len); - if (ret < 0) - return ret; + if (!inode_is_open_for_write(&src_inode->vfs_inode) && + !mapping_writably_mapped(src_inode->vfs_inode.i_mapping)) { + ret = btrfs_wait_ordered_range(src_inode, wb_start, wb_len); + if (ret < 0) + return ret; + } if (same_inode) { btrfs_inode_lock(src_inode, BTRFS_ILOCK_MMAP); From cf02c310d018c7e5363b80dfd68ffdf159f3c914 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Tue, 15 Sep 2026 10:58:33 -0700 Subject: [PATCH 1666/2286] btrfs: downgrade the reflink source inode lock for tree walking When doing a reflink, we are not writing to the src inode or modifying it in any way, so it is counter-intuitive to have to lock it exclusively. This has the cost of blocking other shared lock users of the src file for the duration of any new writeback since the optimistic pre-flush and all the actual reflinking. So we would like to downgrade the lock to shared as much as possible. There are two important gotchas to do with direct io that must be addressed to make this idea work. The first gotcha is that dio writes that come in under EOF try to take inode->i_rwsem shared, which would result in messed up views of the extents in the reflinked file. Therefore, add "being used as a reflink src" as another inode flag exception for the dio write shared tests that already exist to check for other dangerous conditions. In order to ensure that this flag is visible to the dio writer, reflink must first take i_rwsem exclusive, then set the flag, then downgrade to shared. The second is that dio reads increment inode->i_dio_count which blocks inode_dio_wait() in __generic_remap_file_range_prep(). Therefore, we must remain exclusive to dio reads through finishing the prep or risk starving the clone on a stream of dio readers. There is still O(extents) work in btrfs_clone() which can be done without holding the lock exclusive, so we should downgrade to shared and admit dio readers (and other shared lock users like lseek) after we are finished with inode_dio_wait(). By running concurrent clones and lseeks or dio reads on a src file with 32k extents on a vm, I can demonstrate 100ms+ stalls on the readers without this patch and they go away entirely with the patch. Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/btrfs_inode.h | 6 ++++++ fs/btrfs/direct-io.c | 7 +++++++ fs/btrfs/reflink.c | 17 +++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 46c62f980c24ac..03c00c99099d53 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -99,6 +99,12 @@ enum { * range). */ BTRFS_INODE_COW_WRITE_ERROR, + /* + * Set by reflink while it holds the source's VFS inode lock shared. + * Direct IO writes also take that lock shared; one that finds this bit + * set after acquiring it must retry with the exclusive lock. + */ + BTRFS_INODE_REFLINK_SRC, /* * Indicate this is a directory that points to a subvolume for which * there is no root reference item. That's a case like the following: diff --git a/fs/btrfs/direct-io.c b/fs/btrfs/direct-io.c index 3075d79927130d..ed3914257d3959 100644 --- a/fs/btrfs/direct-io.c +++ b/fs/btrfs/direct-io.c @@ -913,6 +913,13 @@ ssize_t btrfs_direct_write(struct kiocb *iocb, struct iov_iter *from) goto relock; } + if ((ilock_flags & BTRFS_ILOCK_SHARED) && + test_bit(BTRFS_INODE_REFLINK_SRC, &BTRFS_I(inode)->runtime_flags)) { + btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); + ilock_flags &= ~BTRFS_ILOCK_SHARED; + goto relock; + } + ret = generic_write_checks(iocb, from); if (ret <= 0) { btrfs_inode_unlock(BTRFS_I(inode), ilock_flags); diff --git a/fs/btrfs/reflink.c b/fs/btrfs/reflink.c index 8e37706b98c31c..b5d2c094941df6 100644 --- a/fs/btrfs/reflink.c +++ b/fs/btrfs/reflink.c @@ -939,6 +939,7 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, struct btrfs_inode *dst_inode = BTRFS_I(file_inode(dst_file)); bool same_inode = dst_inode == src_inode; u64 wb_start, wb_len; + bool src_downgraded = false; int ret; if (btrfs_is_shutdown(src_inode->root->fs_info)) @@ -976,6 +977,12 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, if (ret < 0 || len == 0) goto out_unlock; + if (!same_inode) { + set_bit(BTRFS_INODE_REFLINK_SRC, &src_inode->runtime_flags); + downgrade_write(&src_inode->vfs_inode.i_rwsem); + src_downgraded = true; + } + if (remap_flags & REMAP_FILE_DEDUP) ret = btrfs_extent_same(src_inode, off, len, dst_inode, destoff); else @@ -986,8 +993,14 @@ loff_t btrfs_remap_file_range(struct file *src_file, loff_t off, btrfs_inode_unlock(src_inode, BTRFS_ILOCK_MMAP); } else { btrfs_double_mmap_unlock(src_inode, dst_inode); - unlock_two_nondirectories(&src_inode->vfs_inode, - &dst_inode->vfs_inode); + if (src_downgraded) { + clear_bit(BTRFS_INODE_REFLINK_SRC, &src_inode->runtime_flags); + inode_unlock_shared(&src_inode->vfs_inode); + inode_unlock(&dst_inode->vfs_inode); + } else { + unlock_two_nondirectories(&src_inode->vfs_inode, + &dst_inode->vfs_inode); + } } /* From 7884a8f3279c28ff990e2eacd5c2479bb1c930d0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 17 Sep 2026 11:38:04 +0100 Subject: [PATCH 1667/2286] btrfs: fix off by one super block end offset calculation when writing super blocks We are skipping the write of a super block mirror if its end offset matches exactly the device's end offset. This happens because we are using a greater than or equals comparison instead of just a greater than comparison. Fixes: 935e5cc935bc ("Btrfs: fix wrong disk size when writing super blocks") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index a8535e309b5760..90cf0647c47e36 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -3909,8 +3909,7 @@ static int write_dev_supers(struct btrfs_device *device, atomic_inc(&device->sb_write_errors); continue; } - if (bytenr + BTRFS_SUPER_INFO_SIZE >= - device->commit_total_bytes) + if (bytenr + BTRFS_SUPER_INFO_SIZE > device->commit_total_bytes) break; btrfs_set_super_bytenr(sb, bytenr_orig); @@ -3988,8 +3987,7 @@ static int wait_dev_supers(struct btrfs_device *device, int max_mirrors) primary_failed = true; continue; } - if (bytenr + BTRFS_SUPER_INFO_SIZE >= - device->commit_total_bytes) + if (bytenr + BTRFS_SUPER_INFO_SIZE > device->commit_total_bytes) break; folio = filemap_get_folio(device->bdev->bd_mapping, From 1b934cad5c0e025e85f76aafec6edf55795b5252 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 18 Sep 2026 12:24:37 +0100 Subject: [PATCH 1668/2286] btrfs: fix barrier usage in btrfs_record_root_in_trans() The barrier usage in btrfs_record_root_in_trans() is wrong, as the writer side, in record_root_in_trans(), sets BTRFS_ROOT_IN_TRANS_SETUP, does a write barrier and then sets the root's last transaction. This means the reader side must check the root's last transaction, issue a read barrier and then check for BTRFS_ROOT_IN_TRANS_SETUP. However, currently we issue a read barrier and then check the root's last transaction and the bit BTRFS_ROOT_IN_TRANS_SETUP, which can be problematic because the CPU is free to reorder the checks and the following can happen: 1) Before reading the root's last_trans, it checks that BTRFS_ROOT_IN_TRANS_SETUP is not set. 2) A writer sets BTRFS_ROOT_IN_TRANS_SETUP, does smp_wmb() and updates the root's last_trans. 3) The reader then sees the root's last_trans matches the current transaction and falsely concludes the root setup is completes and returns without waiting for the writer task to complete the setup (calling btrfs_init_reloc_root()). So fix the reading ordered as previously described: check the root's last_trans, issue read barrier and then check BTRFS_ROOT_IN_TRANS_SETUP (the reverse of what the writer side does). Assisted-by: LLM Reviewed-by: Boris Burkov Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index c1555621ae4e68..13203ea9e1163f 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -511,10 +511,11 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans, * see record_root_in_trans for comments about IN_TRANS_SETUP usage * and barriers */ - smp_rmb(); - if (btrfs_get_root_last_trans(root) == trans->transid && - !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state)) - return 0; + if (btrfs_get_root_last_trans(root) == trans->transid) { + smp_rmb(); + if (!test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state)) + return 0; + } mutex_lock(&fs_info->reloc_mutex); ret = record_root_in_trans(trans, root, false); From 21721221e5cc9357366137e8dd1dbf349dc58ff0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 18 Sep 2026 12:46:22 +0100 Subject: [PATCH 1669/2286] btrfs: assert reloc mutex is held in record_root_in_trans() The fs_info->reloc_mutex is supposed to be held when record_root_in_trans() is called and we mention that in a comment inside the function. Add a lockdep assertion to check the lock is held. Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 13203ea9e1163f..0a3913b61f164a 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -413,6 +413,8 @@ static int record_root_in_trans(struct btrfs_trans_handle *trans, struct btrfs_fs_info *fs_info = root->fs_info; int ret = 0; + lockdep_assert_held(&fs_info->reloc_mutex); + if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) && btrfs_get_root_last_trans(root) < trans->transid) || force) { WARN_ON(!force && root->commit_root != root->node); From 057e3c350a9185df598c88b4081168ed047c9247 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 18 Sep 2026 17:41:28 +0100 Subject: [PATCH 1670/2286] btrfs: fix dangling nodes in tree-mod-log after error in btrfs_tree_mod_log_insert_root() If in btrfs_tree_mod_log_insert_root() there is an error in the call to tree_mod_log_insert() (the only possible error is -EEXIST, which means we have a bug or some memory corruption maybe) we free all the nodes in the 'tm_list' array but we don't remove them from the tree-mod-log rbtree, which can result in use-after-free bugs later. So make sure we remove the nodes from the rbtree before freeing them after an error. Fixes: 5de865eebb83 ("Btrfs: fix tree mod logging") Assisted-by: LLM (found the bug) Reviewed-by: Boris Burkov Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/tree-mod-log.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/tree-mod-log.c b/fs/btrfs/tree-mod-log.c index a8094928f4c9b8..bfa348d6483f3a 100644 --- a/fs/btrfs/tree-mod-log.c +++ b/fs/btrfs/tree-mod-log.c @@ -483,10 +483,17 @@ int btrfs_tree_mod_log_insert_root(struct extent_buffer *old_root, goto out_unlock; } - if (tm_list) + if (tm_list) { ret = tree_mod_log_free_eb(fs_info, tm_list, nritems); - if (!ret) - ret = tree_mod_log_insert(fs_info, tm); + if (ret) + goto out_unlock; + } + + ret = tree_mod_log_insert(fs_info, tm); + if (ret && tm_list) { + for (i = 0; i < nritems; i++) + rb_erase(&tm_list[i]->node, &fs_info->tree_mod_log); + } out_unlock: write_unlock(&fs_info->tree_mod_log_lock); From 886f1aa6e6b30a02901aa7f140149d466e10a873 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 16 Sep 2026 17:17:16 +0000 Subject: [PATCH 1671/2286] btrfs: zoned: fix block group reference leak in btrfs_repair_one_zone() btrfs_repair_one_zone() hands the block group reference taken by btrfs_lookup_block_group() over to relocating_repair_kthread(), which drops it. However the return value of kthread_run() is not checked, so when the kernel thread fails to spawn, nothing executes the put and the block group reference is leaked. Check kthread_run() and drop the reference if the thread failed to start. Fixes: f7ef5287a63d ("btrfs: zoned: relocate block group to repair IO failure in zoned filesystems") CC: stable@vger.kernel.org Reviewed-by: Boris Burkov Signed-off-by: Wentao Liang Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 7c040f22dbc3e5..116d882eecfec2 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -9006,6 +9006,7 @@ static int relocating_repair_kthread(void *data) bool btrfs_repair_one_zone(struct btrfs_fs_info *fs_info, u64 logical) { struct btrfs_block_group *cache; + struct task_struct *task; if (!btrfs_is_zoned(fs_info)) return false; @@ -9023,8 +9024,9 @@ bool btrfs_repair_one_zone(struct btrfs_fs_info *fs_info, u64 logical) return true; } - kthread_run(relocating_repair_kthread, cache, - "btrfs-relocating-repair"); + task = kthread_run(relocating_repair_kthread, cache, "btrfs-relocating-repair"); + if (IS_ERR(task)) + btrfs_put_block_group(cache); return true; } From 1b214d3884f07da76b45475a86f6296092e42a7b Mon Sep 17 00:00:00 2001 From: Yang Xiuwei Date: Wed, 19 Aug 2026 10:54:36 +0800 Subject: [PATCH 1672/2286] btrfs: drop unused uring encoded IO REISSUE stash helpers After not keeping state across -EAGAIN, restoring bc->data on REISSUE is dead. Remove it, stop using the cmd PDU on the write path, and fold the read -EAGAIN check into the existing error path. Signed-off-by: Yang Xiuwei Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 25d9feaa90030f..52aab510aea0d1 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -4749,9 +4749,6 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue struct io_btrfs_cmd *bc = io_uring_cmd_to_pdu(cmd, struct io_btrfs_cmd); struct btrfs_uring_encoded_data *data = NULL; - if (cmd->flags & IORING_URING_CMD_REISSUE) - data = bc->data; - if (!capable(CAP_SYS_ADMIN)) { ret = -EPERM; goto out_acct; @@ -4833,8 +4830,6 @@ static int btrfs_uring_encoded_read(struct io_uring_cmd *cmd, unsigned int issue ret = btrfs_encoded_read(&kiocb, &data->iter, &data->args, &cached_state, &disk_bytenr, &disk_io_size); - if (ret == -EAGAIN) - goto out_free; if (ret < 0 && ret != -EIOCBQUEUED) goto out_free; @@ -4891,12 +4886,8 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu struct kiocb kiocb; ssize_t ret; void __user *sqe_addr; - struct io_btrfs_cmd *bc = io_uring_cmd_to_pdu(cmd, struct io_btrfs_cmd); struct btrfs_uring_encoded_data *data = NULL; - if (cmd->flags & IORING_URING_CMD_REISSUE) - data = bc->data; - if (!capable(CAP_SYS_ADMIN)) { ret = -EPERM; goto out_acct; @@ -4920,8 +4911,6 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu goto out_acct; } - bc->data = data; - if (issue_flags & IO_URING_F_COMPAT) { #if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT) struct btrfs_ioctl_encoded_io_args_32 args32; @@ -5007,7 +4996,6 @@ static int btrfs_uring_encoded_write(struct io_uring_cmd *cmd, unsigned int issu inc_syscw(current); kfree(data); - bc->data = NULL; return ret; } From 7d69a3c9b519ac9a3987919eb529d4b8c31c0274 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 20 Sep 2026 10:27:29 +0800 Subject: [PATCH 1673/2286] btrfs: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit and their non-atomic __set_bit/__clear_bit variants to the assign_bit/__assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); // clear_bit -> set_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); // __set_bit -> __clear_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __set_bit(bit, addr); -else - __clear_bit(bit, addr); +__assign_bit(bit, addr, cond); // __clear_bit -> __set_bit => __assign_bit @@ expression cond, bit, addr; @@ -if (cond) - __clear_bit(bit, addr); -else - __set_bit(bit, addr); +__assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 6 ++---- fs/btrfs/sysfs.c | 5 +---- fs/btrfs/volumes.c | 6 ++---- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 05e35eb126dc5b..bf06962d49f3df 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -3177,10 +3177,8 @@ int btrfs_run_qgroups(struct btrfs_trans_handle *trans) "qgroup limit item update error %d", ret); spin_lock(&fs_info->qgroup_lock); } - if (btrfs_qgroup_enabled(fs_info)) - set_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); - else - clear_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags); + assign_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags, + btrfs_qgroup_enabled(fs_info)); spin_unlock(&fs_info->qgroup_lock); ret = update_qgroup_status_item(trans); diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index c5bb1c7eac6afe..3ac1fa608d3b56 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -1117,10 +1117,7 @@ static ssize_t quota_override_store(struct kobject *kobj, if (knob > 1) return -EINVAL; - if (knob) - set_bit(BTRFS_FS_QUOTA_OVERRIDE, &fs_info->flags); - else - clear_bit(BTRFS_FS_QUOTA_OVERRIDE, &fs_info->flags); + assign_bit(BTRFS_FS_QUOTA_OVERRIDE, &fs_info->flags, knob); return len; } diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index 116d882eecfec2..d3e6a9b429dd9d 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -698,10 +698,8 @@ static int btrfs_open_one_device(struct btrfs_fs_devices *fs_devices, clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state); fs_devices->seeding = true; } else { - if (bdev_read_only(file_bdev(bdev_file))) - clear_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state); - else - set_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state); + assign_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state, + !bdev_read_only(file_bdev(bdev_file))); } if (bdev_rot(file_bdev(bdev_file))) From 6fa6630f952237b47dadd5981af3a1fcd88b99ea Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 25 Sep 2026 16:45:34 +0100 Subject: [PATCH 1674/2286] btrfs: simplify dir item location setup in btrfs_insert_xattr_item() The location (a btrfs_disk_key) of a btrfs_dir_item used for a xattr is always zeroed and we have a complex setup in btrfs_insert_xattr_item() where we declare an on stack btrfs_key, zero it out, convert it into a btrfs_disk_key, also declared on stack, and then pass that btrfs_disk_key to a btrfs_set_dir_item_key() call. We can simplify this, and save stack space (one btrfs_disk_key and one btrfs_key), by simply using memzero_extent_buffer(). Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/dir-item.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/dir-item.c b/fs/btrfs/dir-item.c index 3a90736915af6f..d39f85035b4344 100644 --- a/fs/btrfs/dir-item.c +++ b/fs/btrfs/dir-item.c @@ -62,8 +62,7 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans, int ret = 0; struct btrfs_dir_item *dir_item; unsigned long name_ptr, data_ptr; - struct btrfs_key key, location; - struct btrfs_disk_key disk_key; + struct btrfs_key key; struct extent_buffer *leaf; u32 data_size; @@ -79,11 +78,11 @@ int btrfs_insert_xattr_item(struct btrfs_trans_handle *trans, name, name_len); if (IS_ERR(dir_item)) return PTR_ERR(dir_item); - memset(&location, 0, sizeof(location)); leaf = path->nodes[0]; - btrfs_cpu_key_to_disk(&disk_key, &location); - btrfs_set_dir_item_key(leaf, dir_item, &disk_key); + memzero_extent_buffer(leaf, (unsigned long)dir_item + + offsetof(struct btrfs_dir_item, location), + sizeof(struct btrfs_disk_key)); btrfs_set_dir_flags(leaf, dir_item, BTRFS_FT_XATTR); btrfs_set_dir_name_len(leaf, dir_item, name_len); btrfs_set_dir_transid(leaf, dir_item, trans->transid); From 93aac90b2d5bce5697d43d7b664102abe3f649c1 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 24 Sep 2026 15:28:14 +0930 Subject: [PATCH 1675/2286] btrfs: move __TRANS_* and TRANS_* flags out of transaction.h Among all those flags, only __TRANS_DUMMY is used outside of transaction.[ch], and there are only two places using it: - find_parent_nodes() Introduce a helper, btrfs_trans_is_dummy(), for this call site. - btrfs_init_dummy_trans() Move the function into transaction.[ch], and hide it behind CONFIG_BTRFS_FS_RUN_SANITY_TESTS. With the above changes, we can hide __TRANS_* and TRANS_* flags inside transaction.c. This allows us to modify those flags in the future without causing any changes to existing callers. After the flags relocation, now we can also hide __TRANS_DUMMY behind CONFIG_BTRFS_FS_RUN_SANITY_TESTS. Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/backref.c | 2 +- fs/btrfs/tests/btrfs-tests.c | 9 --------- fs/btrfs/tests/btrfs-tests.h | 2 -- fs/btrfs/transaction.c | 36 ++++++++++++++++++++++++++++++++++++ fs/btrfs/transaction.h | 29 +++++++++++------------------ 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/fs/btrfs/backref.c b/fs/btrfs/backref.c index 1be632c742bdde..9c93c0c0d6f57a 100644 --- a/fs/btrfs/backref.c +++ b/fs/btrfs/backref.c @@ -1432,7 +1432,7 @@ static int find_parent_nodes(struct btrfs_backref_walk_ctx *ctx, goto out; } - if (ctx->trans && likely(ctx->trans->type != __TRANS_DUMMY) && + if (ctx->trans && likely(!btrfs_is_dummy_transaction(ctx->trans)) && ctx->time_seq != BTRFS_SEQ_LAST) { /* * We have a specific time_seq we care about and trans which diff --git a/fs/btrfs/tests/btrfs-tests.c b/fs/btrfs/tests/btrfs-tests.c index 6287d940323d69..cebc9a17b94dbd 100644 --- a/fs/btrfs/tests/btrfs-tests.c +++ b/fs/btrfs/tests/btrfs-tests.c @@ -247,15 +247,6 @@ void btrfs_init_dummy_transaction(struct btrfs_transaction *trans, struct btrfs_ spin_lock_init(&trans->delayed_refs.lock); } -void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, - struct btrfs_fs_info *fs_info) -{ - memset(trans, 0, sizeof(*trans)); - trans->transid = 1; - trans->type = __TRANS_DUMMY; - trans->fs_info = fs_info; -} - int btrfs_run_sanity_tests(void) { int ret, i; diff --git a/fs/btrfs/tests/btrfs-tests.h b/fs/btrfs/tests/btrfs-tests.h index cea58fe84a6d5f..a1685513810594 100644 --- a/fs/btrfs/tests/btrfs-tests.h +++ b/fs/btrfs/tests/btrfs-tests.h @@ -59,8 +59,6 @@ btrfs_alloc_dummy_block_group(struct btrfs_fs_info *fs_info, unsigned long lengt void btrfs_free_dummy_block_group(struct btrfs_block_group *cache); DEFINE_FREE(btrfs_free_dummy_block_group, struct btrfs_block_group *, btrfs_free_dummy_block_group(_T)); -void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, - struct btrfs_fs_info *fs_info); void btrfs_init_dummy_transaction(struct btrfs_transaction *trans, struct btrfs_fs_info *fs_info); struct btrfs_device *btrfs_alloc_dummy_device(struct btrfs_fs_info *fs_info); diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 0a3913b61f164a..dfab2f7f07760a 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -38,6 +38,26 @@ static struct kmem_cache *btrfs_trans_handle_cachep; +enum { + ENUM_BIT(__TRANS_FREEZABLE), + ENUM_BIT(__TRANS_START), + ENUM_BIT(__TRANS_ATTACH), + ENUM_BIT(__TRANS_JOIN), + ENUM_BIT(__TRANS_JOIN_NOLOCK), +#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS + ENUM_BIT(__TRANS_DUMMY), +#endif + ENUM_BIT(__TRANS_JOIN_NOSTART), +}; + +#define TRANS_START (__TRANS_START | __TRANS_FREEZABLE) +#define TRANS_ATTACH (__TRANS_ATTACH) +#define TRANS_JOIN (__TRANS_JOIN | __TRANS_FREEZABLE) +#define TRANS_JOIN_NOLOCK (__TRANS_JOIN_NOLOCK) +#define TRANS_JOIN_NOSTART (__TRANS_JOIN_NOSTART) + +#define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) + /* * Transaction states and transitions * @@ -139,6 +159,22 @@ static const unsigned int btrfs_blocked_trans_types[TRANS_STATE_MAX] = { __TRANS_JOIN_NOSTART), }; +#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS +bool btrfs_is_dummy_transaction(const struct btrfs_trans_handle *trans) +{ + return trans->type == __TRANS_DUMMY; +} + +void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, + struct btrfs_fs_info *fs_info) +{ + memset(trans, 0, sizeof(*trans)); + trans->transid = 1; + trans->type = __TRANS_DUMMY; + trans->fs_info = fs_info; +} +#endif + void btrfs_put_transaction(struct btrfs_transaction *transaction) { if (refcount_dec_and_test(&transaction->use_count)) { diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h index 89153cd2259678..0b2e69293b3b90 100644 --- a/fs/btrfs/transaction.h +++ b/fs/btrfs/transaction.h @@ -119,24 +119,6 @@ struct btrfs_transaction { wait_queue_head_t pending_wait; }; -enum { - ENUM_BIT(__TRANS_FREEZABLE), - ENUM_BIT(__TRANS_START), - ENUM_BIT(__TRANS_ATTACH), - ENUM_BIT(__TRANS_JOIN), - ENUM_BIT(__TRANS_JOIN_NOLOCK), - ENUM_BIT(__TRANS_DUMMY), - ENUM_BIT(__TRANS_JOIN_NOSTART), -}; - -#define TRANS_START (__TRANS_START | __TRANS_FREEZABLE) -#define TRANS_ATTACH (__TRANS_ATTACH) -#define TRANS_JOIN (__TRANS_JOIN | __TRANS_FREEZABLE) -#define TRANS_JOIN_NOLOCK (__TRANS_JOIN_NOLOCK) -#define TRANS_JOIN_NOSTART (__TRANS_JOIN_NOSTART) - -#define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) - /* * Number of extent buffers a transaction handle tracks for writeback * inhibition. The CLOCK reference bits pack into a u32 so this must not exceed @@ -305,6 +287,17 @@ do { \ __LINE__, __error); \ } while (0) +#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS +bool btrfs_is_dummy_transaction(const struct btrfs_trans_handle *trans); +void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, + struct btrfs_fs_info *fs_info); +#else +static inline bool btrfs_is_dummy_transaction(const struct btrfs_trans_handle *trans) +{ + return false; +} +#endif + int btrfs_end_transaction(struct btrfs_trans_handle *trans); struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root, unsigned int num_items); From 4c13c58bcd447f4b92a7a190bf9ec18a45c1654c Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 24 Sep 2026 15:28:15 +0930 Subject: [PATCH 1676/2286] btrfs: remove __TRANS_FREEZABLE Inside transaction.c most TRANS_* flags are just a single bit, but there are 2 exceptions: - TRANS_START Which is (__TRANS_START | __TRANS_FREEZABLE) - TRANS_JOIN Which is (__TRANS_JOIN | __TRANS_FREEZABLE) The extra __TRANS_FREEZABLE flag indicates that those operations need to acquire sb intwrite lock to handle fs freezing. However since there are only two operations requiring sb intwrite lock, there is no need to introduce a dedicated flag for it, we can introduce a new TRANS_SB_INTWRITER_MASK to cover the only two cases, then use that new mask to determine whether the type requires sb intwrite lock. This makes all TRANS_* flags a single bit. Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index dfab2f7f07760a..7e716f5bdf7419 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -39,7 +39,6 @@ static struct kmem_cache *btrfs_trans_handle_cachep; enum { - ENUM_BIT(__TRANS_FREEZABLE), ENUM_BIT(__TRANS_START), ENUM_BIT(__TRANS_ATTACH), ENUM_BIT(__TRANS_JOIN), @@ -50,12 +49,15 @@ enum { ENUM_BIT(__TRANS_JOIN_NOSTART), }; -#define TRANS_START (__TRANS_START | __TRANS_FREEZABLE) +#define TRANS_START (__TRANS_START) #define TRANS_ATTACH (__TRANS_ATTACH) -#define TRANS_JOIN (__TRANS_JOIN | __TRANS_FREEZABLE) +#define TRANS_JOIN (__TRANS_JOIN) #define TRANS_JOIN_NOLOCK (__TRANS_JOIN_NOLOCK) #define TRANS_JOIN_NOSTART (__TRANS_JOIN_NOSTART) +/* Those types need to hold sb intwrite lock. */ +#define TRANS_SB_INTWRITER_MASK (__TRANS_START | __TRANS_JOIN) + #define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) /* @@ -750,6 +752,8 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, } /* + * Only TRANS_START and TRANS_JOIN require sb intwrite lock. + * * If we are JOIN_NOLOCK we're already committing a transaction and * waiting on this guy, so we don't need to do the sb_start_intwrite * because we're already holding a ref. We need this because we could @@ -759,7 +763,7 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, * If we are ATTACH, it means we just want to catch the current * transaction and commit it, so we needn't do sb_start_intwrite(). */ - if (type & __TRANS_FREEZABLE) + if (type & TRANS_SB_INTWRITER_MASK) sb_start_intwrite(fs_info->sb); if (may_wait_transaction(fs_info, type)) @@ -861,7 +865,7 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, return h; join_fail: - if (type & __TRANS_FREEZABLE) + if (type & TRANS_SB_INTWRITER_MASK) sb_end_intwrite(fs_info->sb); kmem_cache_free(btrfs_trans_handle_cachep, h); alloc_fail: @@ -1142,7 +1146,7 @@ static int __btrfs_end_transaction(struct btrfs_trans_handle *trans, btrfs_trans_release_chunk_metadata(trans); - if (trans->type & __TRANS_FREEZABLE) + if (trans->type & TRANS_SB_INTWRITER_MASK) sb_end_intwrite(info->sb); /* @@ -2154,7 +2158,7 @@ static void cleanup_transaction(struct btrfs_trans_handle *trans, int err) fs_info->running_transaction = NULL; spin_unlock(&fs_info->trans_lock); - if (trans->type & __TRANS_FREEZABLE) + if (trans->type & TRANS_SB_INTWRITER_MASK) sb_end_intwrite(fs_info->sb); btrfs_put_transaction(cur_trans); btrfs_put_transaction(cur_trans); @@ -2686,7 +2690,7 @@ int btrfs_commit_transaction(struct btrfs_trans_handle *trans) btrfs_put_transaction(cur_trans); btrfs_put_transaction(cur_trans); - if (trans->type & __TRANS_FREEZABLE) + if (trans->type & TRANS_SB_INTWRITER_MASK) sb_end_intwrite(fs_info->sb); btrfs_scrub_continue(fs_info); From d658eb2bda4c67836cf49a88c082b3719258f814 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 24 Sep 2026 15:28:16 +0930 Subject: [PATCH 1677/2286] btrfs: remove __TRANS_* flags After patch "btrfs: remove __TRANS_FREEZABLE", each TRANS_* flag is just the corresponding single-bit __TRANS_* flag. There is no need to split __TRANS_* and TRANS_* flags, just remove __TRANS_* flags and use TRANS_* flags instead. Since every TRANS_* flag is a single bit, do extra cleanups: - Add an ASSERT() in start_transaction() To make sure there is only a single bit set in @type - Rename TRANS_EXTWRITERS to TRANS_EXTWRITERS_MASK To follow the naming scheme that a multi-bit value has the _MASK suffix. Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 77 ++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 7e716f5bdf7419..367be91fc649cb 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -39,26 +39,20 @@ static struct kmem_cache *btrfs_trans_handle_cachep; enum { - ENUM_BIT(__TRANS_START), - ENUM_BIT(__TRANS_ATTACH), - ENUM_BIT(__TRANS_JOIN), - ENUM_BIT(__TRANS_JOIN_NOLOCK), + ENUM_BIT(TRANS_START), + ENUM_BIT(TRANS_ATTACH), + ENUM_BIT(TRANS_JOIN), + ENUM_BIT(TRANS_JOIN_NOLOCK), #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS - ENUM_BIT(__TRANS_DUMMY), + ENUM_BIT(TRANS_DUMMY), #endif - ENUM_BIT(__TRANS_JOIN_NOSTART), + ENUM_BIT(TRANS_JOIN_NOSTART), }; -#define TRANS_START (__TRANS_START) -#define TRANS_ATTACH (__TRANS_ATTACH) -#define TRANS_JOIN (__TRANS_JOIN) -#define TRANS_JOIN_NOLOCK (__TRANS_JOIN_NOLOCK) -#define TRANS_JOIN_NOSTART (__TRANS_JOIN_NOSTART) - /* Those types need to hold sb intwrite lock. */ -#define TRANS_SB_INTWRITER_MASK (__TRANS_START | __TRANS_JOIN) +#define TRANS_SB_INTWRITER_MASK (TRANS_START | TRANS_JOIN) -#define TRANS_EXTWRITERS (__TRANS_START | __TRANS_ATTACH) +#define TRANS_EXTWRITERS_MASK (TRANS_START | TRANS_ATTACH) /* * Transaction states and transitions @@ -139,32 +133,32 @@ enum { static const unsigned int btrfs_blocked_trans_types[TRANS_STATE_MAX] = { [TRANS_STATE_RUNNING] = 0U, [TRANS_STATE_COMMIT_PREP] = 0U, - [TRANS_STATE_COMMIT_START] = (__TRANS_START | __TRANS_ATTACH), - [TRANS_STATE_COMMIT_DOING] = (__TRANS_START | - __TRANS_ATTACH | - __TRANS_JOIN | - __TRANS_JOIN_NOSTART), - [TRANS_STATE_UNBLOCKED] = (__TRANS_START | - __TRANS_ATTACH | - __TRANS_JOIN | - __TRANS_JOIN_NOLOCK | - __TRANS_JOIN_NOSTART), - [TRANS_STATE_SUPER_COMMITTED] = (__TRANS_START | - __TRANS_ATTACH | - __TRANS_JOIN | - __TRANS_JOIN_NOLOCK | - __TRANS_JOIN_NOSTART), - [TRANS_STATE_COMPLETED] = (__TRANS_START | - __TRANS_ATTACH | - __TRANS_JOIN | - __TRANS_JOIN_NOLOCK | - __TRANS_JOIN_NOSTART), + [TRANS_STATE_COMMIT_START] = (TRANS_START | TRANS_ATTACH), + [TRANS_STATE_COMMIT_DOING] = (TRANS_START | + TRANS_ATTACH | + TRANS_JOIN | + TRANS_JOIN_NOSTART), + [TRANS_STATE_UNBLOCKED] = (TRANS_START | + TRANS_ATTACH | + TRANS_JOIN | + TRANS_JOIN_NOLOCK | + TRANS_JOIN_NOSTART), + [TRANS_STATE_SUPER_COMMITTED] = (TRANS_START | + TRANS_ATTACH | + TRANS_JOIN | + TRANS_JOIN_NOLOCK | + TRANS_JOIN_NOSTART), + [TRANS_STATE_COMPLETED] = (TRANS_START | + TRANS_ATTACH | + TRANS_JOIN | + TRANS_JOIN_NOLOCK | + TRANS_JOIN_NOSTART), }; #ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS bool btrfs_is_dummy_transaction(const struct btrfs_trans_handle *trans) { - return trans->type == __TRANS_DUMMY; + return trans->type == TRANS_DUMMY; } void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, @@ -172,7 +166,7 @@ void btrfs_init_dummy_trans(struct btrfs_trans_handle *trans, { memset(trans, 0, sizeof(*trans)); trans->transid = 1; - trans->type = __TRANS_DUMMY; + trans->type = TRANS_DUMMY; trans->fs_info = fs_info; } #endif @@ -261,21 +255,21 @@ static noinline void switch_commit_roots(struct btrfs_trans_handle *trans) static inline void extwriter_counter_inc(struct btrfs_transaction *trans, unsigned int type) { - if (type & TRANS_EXTWRITERS) + if (type & TRANS_EXTWRITERS_MASK) atomic_inc(&trans->num_extwriters); } static inline void extwriter_counter_dec(struct btrfs_transaction *trans, unsigned int type) { - if (type & TRANS_EXTWRITERS) + if (type & TRANS_EXTWRITERS_MASK) atomic_dec(&trans->num_extwriters); } static inline void extwriter_counter_init(struct btrfs_transaction *trans, unsigned int type) { - atomic_set(&trans->num_extwriters, ((type & TRANS_EXTWRITERS) ? 1 : 0)); + atomic_set(&trans->num_extwriters, ((type & TRANS_EXTWRITERS_MASK) ? 1 : 0)); } static inline int extwriter_counter_read(struct btrfs_transaction *trans) @@ -666,11 +660,14 @@ start_transaction(struct btrfs_root *root, unsigned int num_items, bool do_chunk_alloc = false; int ret; + /* The type must be a single TRANS_* bit set. */ + ASSERT(has_single_bit_set(type)); + if (unlikely(BTRFS_FS_ERROR(fs_info))) return ERR_PTR(-EROFS); if (current->journal_info) { - WARN_ON(type & TRANS_EXTWRITERS); + WARN_ON(type & TRANS_EXTWRITERS_MASK); h = current->journal_info; refcount_inc(&h->use_count); WARN_ON(refcount_read(&h->use_count) > 2); From cd5e2d2f2ea2b2c644ac8a01b50a8f22bd23610f Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 28 Sep 2026 17:20:33 -0700 Subject: [PATCH 1678/2286] btrfs: allocate additional SYSTEM space earlier Currently, reserve_chunk_space() allocates a new system chunk when the space left is smaller than the reservation for a single chunk tree update. Therefore, a filesystem in single metadata mode on a very large block device can accumulate tens of thousands (TB) of chunks while never allocating more than the original 4MiB SYSTEM block group created by mkfs. If it then fills up (with large fallocates for example) and then that data is freed, en-masse, we end up trying to delete thousands of block groups in a single transaction in btrfs_delete_unused_bgs(). This process touches most of the nodes and leaves of the chunk tree, essentially trying to allocate roughly double the current usage of the chunk tree for cow. When this runs into needing a fresh system chunk, the fs is full of the empty bgs and we abort with ENOSPC in btrfs_remove_chunk() like: BTRFS error (device loop0 state A): Transaction 30733 aborted (-ENOSPC) space_info SYSTEM (sub-group id 0) has 0 free, is not full space_info total=4194304, used=3276800, pinned=0, reserved=917504, may_use=0, readonly=0 zone_unusable=0 BTRFS: error (device loop0 state A) in btrfs_remove_chunk:3643: errno=-28 No space left This was seen on a 30TiB production filesystem with 10TiB of unused block groups and reproduces on a 30TiB sparse loop device: mkfs with -m single, fallocate 1GiB files until ENOSPC, delete two of every three adjacent files, sync. To fix this, we should allocate the (relatively tiny) system chunks a little bit more eagerly. If we do it when it is half full, we ensure we can delete all of the block groups in one go. To fill up half of a 4MiB system bg requires thousands of block_groups so this should only affect very large fileystems. Assisted-by: LLM Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 42 +++++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 2eb09c9901c9e1..0a41b16ed27021 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -1574,6 +1574,19 @@ static bool btrfs_link_bg_list(struct btrfs_block_group *bg, struct list_head *l return added; } +/* + * Compute a rough bound on the bytes needed to modify every leaf in the chunk + * tree. Normally, we could just allocate a new system chunk then, but that can + * fail if there is no free space for a new dev extent. This can happen when + * we fill up a large filesystem then rapidly delete a large portion of it. + */ +static u64 system_space_target(struct btrfs_space_info *sinfo) +{ + lockdep_assert_held(&sinfo->lock); + + return btrfs_space_info_used(sinfo, true) + sinfo->bytes_used; +} + /* * Process the unused_bgs list and remove any that don't have any allocated * space inside of them. @@ -1661,6 +1674,9 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) if (btrfs_is_block_group_used(block_group) || (block_group->ro && !(block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED)) || list_is_singular(&block_group->list) || + ((block_group->flags & BTRFS_BLOCK_GROUP_SYSTEM) && + space_info->total_bytes - block_group->length < + system_space_target(space_info)) || test_bit(BLOCK_GROUP_FLAG_FULLY_REMAPPED, &block_group->runtime_flags)) { /* * We want to bail if we made new allocations or have @@ -1674,6 +1690,10 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) * next block group of this type would be created with a * "single" profile (even if we're in a raid fs) because * fs_info->avail_*_alloc_bits would be 0. + * + * Also bail out if this is a system block group that + * system_space_target() relies on to ensure head room for + * mass deletion. */ trace_btrfs_skip_unused_block_group(block_group); spin_unlock(&block_group->lock); @@ -4509,6 +4529,7 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans, struct btrfs_fs_info *fs_info = trans->fs_info; struct btrfs_space_info *info; u64 left; + bool want_system_chunk, need_system_chunk; int ret = 0; /* @@ -4520,15 +4541,17 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans, info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_SYSTEM); spin_lock(&info->lock); left = info->total_bytes - btrfs_space_info_used(info, true); + want_system_chunk = info->total_bytes < system_space_target(info); + need_system_chunk = left < bytes; spin_unlock(&info->lock); - if (left < bytes && btrfs_test_opt(fs_info, ENOSPC_DEBUG)) { + if (need_system_chunk && btrfs_test_opt(fs_info, ENOSPC_DEBUG)) { btrfs_info(fs_info, "left=%llu, need=%llu, flags=%llu", left, bytes, type); btrfs_dump_space_info(info, 0, false); } - if (left < bytes) { + if (want_system_chunk || need_system_chunk) { u64 flags = btrfs_system_alloc_profile(fs_info); struct btrfs_block_group *bg; struct btrfs_space_info *space_info; @@ -4572,13 +4595,14 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans, } } - if (!ret) { - ret = btrfs_block_rsv_add(fs_info, - &fs_info->chunk_block_rsv, - bytes, BTRFS_RESERVE_NO_FLUSH); - if (!ret) - trans->chunk_bytes_reserved += bytes; - } + if (ret && need_system_chunk) + return; + + ret = btrfs_block_rsv_add(fs_info, + &fs_info->chunk_block_rsv, + bytes, BTRFS_RESERVE_NO_FLUSH); + if (!ret) + trans->chunk_bytes_reserved += bytes; } /* From 5612507486cdbd6da23b5bb972a8fcc192570d15 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 28 Sep 2026 17:20:34 -0700 Subject: [PATCH 1679/2286] btrfs: commit after deleting an unused block group if system space is low If the device is fully allocated and the system space_info has no room for a second copy of the chunk tree, deleting many unused block groups in one transaction aborts it with -ENOSPC in btrfs_remove_chunk(). While we can get ahead of this problem in practice by allocating the system space more aggressively, that doesn't help a system that already got into this state anyway. In particular, this would be a problem on systems that got into this state on old code from before that improvement, but it is also a backstop in case we do mess up and still end up with too little system space. The way we end up needing a lot of system chunk space in practice is if we need to delete many bgs at once, resulting in touching many nodes/leaves in the chunk tree. Therefore, if we detect we are in a condition where we are low on system space, commit a single transaction deleting a single bg, which should not need much system chunk space. After this we have the freed bg and can proceed as usual. Any additional chunk allocations (for any reason) will also check the system space and are very likely to allocate more system space, so this should be a relatively robust mechanism. Assisted-by: LLM Reviewed-by: Filipe Manana Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 0a41b16ed27021..c148b9d6e597fd 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -1596,6 +1596,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) LIST_HEAD(retry_list); struct btrfs_block_group *block_group; struct btrfs_space_info *space_info; + struct btrfs_space_info *system_info; struct btrfs_trans_handle *trans; const bool async_trim_enabled = btrfs_test_opt(fs_info, DISCARD_ASYNC); int ret = 0; @@ -1613,10 +1614,13 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) if (!mutex_trylock(&fs_info->reclaim_bgs_lock)) return; + system_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_SYSTEM); + spin_lock(&fs_info->unused_bgs_lock); while (!list_empty(&fs_info->unused_bgs)) { u64 used; int trimming; + bool low; block_group = list_first_entry(&fs_info->unused_bgs, struct btrfs_block_group, @@ -1864,7 +1868,18 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info) btrfs_get_block_group(block_group); } end_trans: - btrfs_end_transaction(trans); + /* + * If we are low on system space, commit immediately to get back + * unallocated space to greatly improve the chances of + * successfully allocating a system chunk. + */ + spin_lock(&system_info->lock); + low = system_info->total_bytes < system_space_target(system_info); + spin_unlock(&system_info->lock); + if (unlikely(!ret && low)) + ret = btrfs_commit_transaction(trans); + else + btrfs_end_transaction(trans); next: btrfs_put_block_group(block_group); spin_lock(&fs_info->unused_bgs_lock); From 9bdef63e2b5d4a88810329a1cab3642d6378a895 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Tue, 29 Sep 2026 16:02:19 +0200 Subject: [PATCH 1680/2286] btrfs: zoned: fix double list add in btrfs_load_block_group_zone_info For DUP and RAID profiles with zones in a mismatching active state, i.e. due to one of the zones of the block-group being on a conventional zone, btrfs_zone_activate() has already put the block group on the list. This leads to a double list add and refcounting problems. Fixes: 265f7237dd25 ("btrfs: zoned: allow DUP on meta-data block groups") Fixes: 568220fa9657 ("btrfs: zoned: support RAID0/1/10 on top of raid stripe tree") Reviewed-by: Daniel Vacek Signed-off-by: Johannes Thumshirn Signed-off-by: David Sterba --- fs/btrfs/zoned.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index a1ef8caaacdab4..9f562cc34e3836 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2005,10 +2005,12 @@ int btrfs_load_block_group_zone_info(struct btrfs_block_group *cache, bool new) if (!ret) { cache->meta_write_pointer = cache->alloc_offset + cache->start; if (test_bit(BLOCK_GROUP_FLAG_ZONE_IS_ACTIVE, &cache->runtime_flags)) { - btrfs_get_block_group(cache); spin_lock(&fs_info->zone_active_bgs_lock); - list_add_tail(&cache->active_bg_list, - &fs_info->zone_active_bgs); + if (list_empty(&cache->active_bg_list)) { + btrfs_get_block_group(cache); + list_add_tail(&cache->active_bg_list, + &fs_info->zone_active_bgs); + } spin_unlock(&fs_info->zone_active_bgs_lock); } } else { From d1d74665ab9a8971d42177ca865cfbe2897c1fc7 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Tue, 29 Sep 2026 15:58:34 +0100 Subject: [PATCH 1681/2286] btrfs: fix incomplete iteration over fs state when logging messages In btrfs_state_to_string() we iterate over all the bits set in fs_info->fs_state using for_each_set_bit(), which requires its third argument to match the number of bits, but we pass it the number of bytes of the type used by fs_info->fs_state, an unsigned long, which is 8 on 64 bits platforms and 4 on 32 bits platforms. This means we are not iterating over bits in the ranges [8, 63] or [4, 31]. Fix this by passing BTRFS_FS_STATE_COUNT instead of sizeof(fs_state). Note that we could pass BITS_PER_TYPE(fs_state), but since BTRFS_FS_STATE_COUNT is 11 and we have no bits beyond that, it would cause pointless iterations over fs_state. This also makes pointless the checks for the bit being inside the limit of BTRFS_FS_STATE_COUNT inside the loop. Fixes: c067da878156 ("btrfs: add filesystems state details to error messages") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/messages.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c index 198d1747c80aae..6debf0d4acadf5 100644 --- a/fs/btrfs/messages.c +++ b/fs/btrfs/messages.c @@ -42,9 +42,8 @@ static void btrfs_state_to_string(const struct btrfs_fs_info *info, char *buf) states_printed = true; } - for_each_set_bit(bit, &fs_state, sizeof(fs_state)) { - WARN_ON_ONCE(bit >= BTRFS_FS_STATE_COUNT); - if ((bit < BTRFS_FS_STATE_COUNT) && fs_state_chars[bit]) { + for_each_set_bit(bit, &fs_state, BTRFS_FS_STATE_COUNT) { + if (fs_state_chars[bit]) { *curr++ = fs_state_chars[bit]; states_printed = true; } From 3edda5249553b708c378a0d766018976315c63ad Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 11:52:32 +0100 Subject: [PATCH 1682/2286] btrfs: fix duplicated code in fs state string in logged messages We are using the code 'E' when the fs is in an error state (transaction aborted) and when it's in the emergency shutdown state, which is confusing and not intentional. Fix this by using an unused code such as 'H' for the emergency shutdown state and add a comment over the fs_state_chars array to warn to not use 'E' as its use is not defined in the array but instead in the function btrfs_state_to_string(), since the fs state error is not defined as a bit but as an error code in fs_info->fs_error. Fixes: 9b2839451dfc ("btrfs: introduce a new shutdown state") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/messages.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c index 6debf0d4acadf5..5fe5d34ab629c4 100644 --- a/fs/btrfs/messages.c +++ b/fs/btrfs/messages.c @@ -13,6 +13,8 @@ /* * Characters to print to indicate error conditions or uncommon filesystem state. * RO is not an error. + * Don't use 'E' because that's used to signal that there's an error stored in + * fs_info->fs_error (see btrfs_state_to_string() below). */ static const char fs_state_chars[] = { [BTRFS_FS_STATE_REMOUNTING] = 'M', @@ -24,7 +26,7 @@ static const char fs_state_chars[] = { [BTRFS_FS_STATE_NO_DATA_CSUMS] = 'C', [BTRFS_FS_STATE_SKIP_META_CSUMS] = 'S', [BTRFS_FS_STATE_LOG_CLEANUP_ERROR] = 'L', - [BTRFS_FS_STATE_EMERGENCY_SHUTDOWN] = 'E', + [BTRFS_FS_STATE_EMERGENCY_SHUTDOWN] = 'H', }; static void btrfs_state_to_string(const struct btrfs_fs_info *info, char *buf) From 38361baddd61cdf343609497b17d58507e8f9782 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 13:31:13 +0100 Subject: [PATCH 1683/2286] btrfs: add missing code for no delayed iput fs state in log messages Add a letter code for the BTRFS_FS_STATE_NO_DELAYED_IPUT fs state, which is currently missing and it's useful to have. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/messages.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c index 5fe5d34ab629c4..2610b447165e0a 100644 --- a/fs/btrfs/messages.c +++ b/fs/btrfs/messages.c @@ -26,6 +26,7 @@ static const char fs_state_chars[] = { [BTRFS_FS_STATE_NO_DATA_CSUMS] = 'C', [BTRFS_FS_STATE_SKIP_META_CSUMS] = 'S', [BTRFS_FS_STATE_LOG_CLEANUP_ERROR] = 'L', + [BTRFS_FS_STATE_NO_DELAYED_IPUT] = 'I', [BTRFS_FS_STATE_EMERGENCY_SHUTDOWN] = 'H', }; From 719aee83960b3e20099247c2d0bb47b969487b4c Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 12:03:11 +0100 Subject: [PATCH 1684/2286] btrfs: remove unnecessary pointer increment in btrfs_state_to_string() There's no point in incrementing the local pointer after we set the NUL terminator, as we don't use the pointer anymore after that. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/messages.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c index 2610b447165e0a..c42a121393776e 100644 --- a/fs/btrfs/messages.c +++ b/fs/btrfs/messages.c @@ -56,7 +56,7 @@ static void btrfs_state_to_string(const struct btrfs_fs_info *info, char *buf) if (!states_printed) curr = buf; - *curr++ = 0; + *curr = 0; } #endif From 22bdddf40e046e1c9a9c690a2bc3b4790578ad24 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 12:09:43 +0100 Subject: [PATCH 1685/2286] btrfs: avoid overhead when logging messages if fs state is clean If the fs state does not have any errors and is not in some special state (like emergency shutdown or mounted with rescue options), then in btrfs_state_to_string() we end up doing a memcpy() into the buffer and then later just undo that work by setting the buffer's first element to the NUL terminator, since fs_info->fs_error is 0 and there is no bit set in fs_info->fs_state. Improve on this by returning early if there's no error in the fs and the fs state is clean, which is the expected scenario for a healthy fs that is not mounted with special options like the rescue options. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/messages.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/btrfs/messages.c b/fs/btrfs/messages.c index c42a121393776e..0704be7be8719a 100644 --- a/fs/btrfs/messages.c +++ b/fs/btrfs/messages.c @@ -33,27 +33,27 @@ static const char fs_state_chars[] = { static void btrfs_state_to_string(const struct btrfs_fs_info *info, char *buf) { unsigned int bit; - bool states_printed = false; unsigned long fs_state = READ_ONCE(info->fs_state); char *curr = buf; + if (likely(BTRFS_FS_ERROR(info) == 0 && fs_state == 0)) { + *curr = 0; + return; + } + memcpy(curr, STATE_STRING_PREFACE, sizeof(STATE_STRING_PREFACE)); curr += sizeof(STATE_STRING_PREFACE) - 1; - if (unlikely(BTRFS_FS_ERROR(info))) { + if (BTRFS_FS_ERROR(info)) *curr++ = 'E'; - states_printed = true; - } for_each_set_bit(bit, &fs_state, BTRFS_FS_STATE_COUNT) { - if (fs_state_chars[bit]) { + if (fs_state_chars[bit]) *curr++ = fs_state_chars[bit]; - states_printed = true; - } } - /* If no states were printed, reset the buffer */ - if (!states_printed) + /* If nothing printed, ensure we return an empty string. */ + if (curr == (buf + sizeof(STATE_STRING_PREFACE) - 1)) curr = buf; *curr = 0; From 08f750d9f8930e4ca053913b5ca3fd49fd9268c9 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 15:37:00 +0100 Subject: [PATCH 1686/2286] btrfs: fix off-by-one end offset check in report_setget_bounds() The addressable range with an extent buffer goes from byte 0 to byte eb->len - 1, so if a byte address matches eb->len, it's already beyond the addressable range and we should log that it's bad start address and not end address. Fixes: 234fdd2815ce ("btrfs: remove redundant check in up check_setget_bounds") Reviewed-by: Qu Wenruo Reviewed-by: Daniel Vacek Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/accessors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/accessors.c b/fs/btrfs/accessors.c index 1248aa2535d306..d63ac6eb92deb9 100644 --- a/fs/btrfs/accessors.c +++ b/fs/btrfs/accessors.c @@ -16,7 +16,7 @@ static void __cold report_setget_bounds(const struct extent_buffer *eb, btrfs_warn(eb->fs_info, "bad eb member %s: ptr 0x%lx start %llu member offset %lu size %d", - (member_offset > eb->len ? "start" : "end"), + (member_offset >= eb->len ? "start" : "end"), (unsigned long)ptr, eb->start, member_offset, size); } From 2f19d4a02c82712891d6a6469f62695a9c6c2816 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 16:26:49 +0100 Subject: [PATCH 1687/2286] btrfs: remove fs_info argument from btrfs_block_rsv_add() The fs_info argument is not used, so remove it. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/block-group.c | 5 ++--- fs/btrfs/block-rsv.c | 3 +-- fs/btrfs/block-rsv.h | 3 +-- fs/btrfs/delayed-inode.c | 3 +-- fs/btrfs/props.c | 3 +-- fs/btrfs/relocation.c | 2 +- fs/btrfs/root-tree.c | 3 +-- fs/btrfs/transaction.c | 3 +-- 8 files changed, 9 insertions(+), 16 deletions(-) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index c148b9d6e597fd..8ff21dde9a46ea 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -4613,9 +4613,8 @@ static void reserve_chunk_space(struct btrfs_trans_handle *trans, if (ret && need_system_chunk) return; - ret = btrfs_block_rsv_add(fs_info, - &fs_info->chunk_block_rsv, - bytes, BTRFS_RESERVE_NO_FLUSH); + ret = btrfs_block_rsv_add(&fs_info->chunk_block_rsv, bytes, + BTRFS_RESERVE_NO_FLUSH); if (!ret) trans->chunk_bytes_reserved += bytes; } diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index c68a8f4b7d19c3..ecbf66d6cc7341 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -209,8 +209,7 @@ void btrfs_free_block_rsv(struct btrfs_fs_info *fs_info, kfree(rsv); } -int btrfs_block_rsv_add(struct btrfs_fs_info *fs_info, - struct btrfs_block_rsv *block_rsv, u64 num_bytes, +int btrfs_block_rsv_add(struct btrfs_block_rsv *block_rsv, u64 num_bytes, enum btrfs_reserve_flush_enum flush) { int ret; diff --git a/fs/btrfs/block-rsv.h b/fs/btrfs/block-rsv.h index 8359fb96bc3c7a..992272a01964fd 100644 --- a/fs/btrfs/block-rsv.h +++ b/fs/btrfs/block-rsv.h @@ -69,8 +69,7 @@ void btrfs_init_metadata_block_rsv(struct btrfs_fs_info *fs_info, enum btrfs_rsv_type type); void btrfs_free_block_rsv(struct btrfs_fs_info *fs_info, struct btrfs_block_rsv *rsv); -int btrfs_block_rsv_add(struct btrfs_fs_info *fs_info, - struct btrfs_block_rsv *block_rsv, u64 num_bytes, +int btrfs_block_rsv_add(struct btrfs_block_rsv *block_rsv, u64 num_bytes, enum btrfs_reserve_flush_enum flush); int btrfs_block_rsv_check(struct btrfs_block_rsv *block_rsv, int min_percent); int btrfs_block_rsv_refill(struct btrfs_fs_info *fs_info, diff --git a/fs/btrfs/delayed-inode.c b/fs/btrfs/delayed-inode.c index 1f20148c1fd44e..333ce8b5248999 100644 --- a/fs/btrfs/delayed-inode.c +++ b/fs/btrfs/delayed-inode.c @@ -600,8 +600,7 @@ static int btrfs_delayed_inode_reserve_metadata( ret = btrfs_qgroup_reserve_meta_prealloc(root, num_bytes, true, true); if (ret < 0) return ret; - ret = btrfs_block_rsv_add(fs_info, dst_rsv, num_bytes, - BTRFS_RESERVE_NO_FLUSH); + ret = btrfs_block_rsv_add(dst_rsv, num_bytes, BTRFS_RESERVE_NO_FLUSH); /* NO_FLUSH could only fail with -ENOSPC */ ASSERT(ret == 0 || ret == -ENOSPC); if (ret) diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c index bb77d46376d4bb..7cc82366a1dfc5 100644 --- a/fs/btrfs/props.c +++ b/fs/btrfs/props.c @@ -440,8 +440,7 @@ int btrfs_inode_inherit_props(struct btrfs_trans_handle *trans, */ if (need_reserve) { num_bytes = btrfs_calc_insert_metadata_size(fs_info, 1); - ret = btrfs_block_rsv_add(fs_info, trans->block_rsv, - num_bytes, + ret = btrfs_block_rsv_add(trans->block_rsv, num_bytes, BTRFS_RESERVE_NO_FLUSH); if (ret) return ret; diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index da54db75e7a9b9..b7de08c7e9e2d2 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -1795,7 +1795,7 @@ int prepare_to_merge(struct reloc_control *rc, int err) again: if (!err) { num_bytes = rc->merging_rsv_size; - ret = btrfs_block_rsv_add(fs_info, rc->block_rsv, num_bytes, + ret = btrfs_block_rsv_add(rc->block_rsv, num_bytes, BTRFS_RESERVE_FLUSH_ALL); if (ret) err = ret; diff --git a/fs/btrfs/root-tree.c b/fs/btrfs/root-tree.c index 2e4c3efbd02f7e..524210671ec437 100644 --- a/fs/btrfs/root-tree.c +++ b/fs/btrfs/root-tree.c @@ -510,8 +510,7 @@ int btrfs_subvolume_reserve_metadata(struct btrfs_root *root, num_bytes = btrfs_calc_insert_metadata_size(fs_info, items); rsv->space_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_METADATA); - ret = btrfs_block_rsv_add(fs_info, rsv, num_bytes, - BTRFS_RESERVE_FLUSH_ALL); + ret = btrfs_block_rsv_add(rsv, num_bytes, BTRFS_RESERVE_FLUSH_ALL); if (ret == -ENOSPC && use_global_rsv) ret = btrfs_block_rsv_migrate(global_rsv, rsv, num_bytes, true); diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 367be91fc649cb..286a9d455d26f1 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -1767,8 +1767,7 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans, btrfs_reloc_pre_snapshot(pending, &to_reserve); if (to_reserve > 0) { - pending->error = btrfs_block_rsv_add(fs_info, - &pending->block_rsv, + pending->error = btrfs_block_rsv_add(&pending->block_rsv, to_reserve, BTRFS_RESERVE_NO_FLUSH); if (unlikely(pending->error)) From dec07422bc9293ba6938f8c1bf3fa05625530b46 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 16:31:01 +0100 Subject: [PATCH 1688/2286] btrfs: remove fs_info argument from btrfs_block_rsv_refill() The fs_info argument is not used, so remove it. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/block-rsv.c | 3 +-- fs/btrfs/block-rsv.h | 3 +-- fs/btrfs/inode.c | 4 ++-- fs/btrfs/relocation.c | 11 ++++------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index ecbf66d6cc7341..55e84ff0932ea5 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -238,8 +238,7 @@ int btrfs_block_rsv_check(struct btrfs_block_rsv *block_rsv, int min_percent) return ret; } -int btrfs_block_rsv_refill(struct btrfs_fs_info *fs_info, - struct btrfs_block_rsv *block_rsv, u64 num_bytes, +int btrfs_block_rsv_refill(struct btrfs_block_rsv *block_rsv, u64 num_bytes, enum btrfs_reserve_flush_enum flush) { int ret = -ENOSPC; diff --git a/fs/btrfs/block-rsv.h b/fs/btrfs/block-rsv.h index 992272a01964fd..39b8928e1559f7 100644 --- a/fs/btrfs/block-rsv.h +++ b/fs/btrfs/block-rsv.h @@ -72,8 +72,7 @@ void btrfs_free_block_rsv(struct btrfs_fs_info *fs_info, int btrfs_block_rsv_add(struct btrfs_block_rsv *block_rsv, u64 num_bytes, enum btrfs_reserve_flush_enum flush); int btrfs_block_rsv_check(struct btrfs_block_rsv *block_rsv, int min_percent); -int btrfs_block_rsv_refill(struct btrfs_fs_info *fs_info, - struct btrfs_block_rsv *block_rsv, u64 num_bytes, +int btrfs_block_rsv_refill(struct btrfs_block_rsv *block_rsv, u64 num_bytes, enum btrfs_reserve_flush_enum flush); int btrfs_block_rsv_migrate(struct btrfs_block_rsv *src_rsv, struct btrfs_block_rsv *dst_rsv, u64 num_bytes, diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 2a32072849cbd4..343b6402bb3528 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -5595,10 +5595,10 @@ static struct btrfs_trans_handle *evict_refill_and_join(struct btrfs_root *root, * if we fail to make this reservation we can re-try without the * delayed_refs_extra so we can make some forward progress. */ - ret = btrfs_block_rsv_refill(fs_info, rsv, rsv->size + delayed_refs_extra, + ret = btrfs_block_rsv_refill(rsv, rsv->size + delayed_refs_extra, BTRFS_RESERVE_FLUSH_EVICT); if (ret) { - ret = btrfs_block_rsv_refill(fs_info, rsv, rsv->size, + ret = btrfs_block_rsv_refill(rsv, rsv->size, BTRFS_RESERVE_FLUSH_EVICT); if (ret) { btrfs_warn(fs_info, diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index b7de08c7e9e2d2..65cb9dcf0aac5f 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -1675,8 +1675,7 @@ static noinline_for_stack int merge_reloc_root(struct reloc_control *rc, memset(&next_key, 0, sizeof(next_key)); while (1) { - ret = btrfs_block_rsv_refill(fs_info, rc->block_rsv, - min_reserved, + ret = btrfs_block_rsv_refill(rc->block_rsv, min_reserved, BTRFS_RESERVE_FLUSH_LIMIT); if (ret) goto out; @@ -2274,7 +2273,7 @@ static int refill_metadata_space(struct btrfs_trans_handle *trans, * If we get an enospc just kick back -EAGAIN so we know to drop the * transaction and try to refill when we can flush all the things. */ - ret = btrfs_block_rsv_refill(fs_info, rc->block_rsv, num_bytes, + ret = btrfs_block_rsv_refill(rc->block_rsv, num_bytes, BTRFS_RESERVE_FLUSH_LIMIT); if (ret) { u64 tmp = fs_info->nodesize * RELOCATION_RESERVED_NODES; @@ -3584,8 +3583,7 @@ int prepare_to_relocate(struct reloc_control *rc) rc->reserved_bytes = 0; rc->block_rsv->size = rc->extent_root->fs_info->nodesize * RELOCATION_RESERVED_NODES; - ret = btrfs_block_rsv_refill(rc->extent_root->fs_info, - rc->block_rsv, rc->block_rsv->size, + ret = btrfs_block_rsv_refill(rc->block_rsv, rc->block_rsv->size, BTRFS_RESERVE_FLUSH_ALL); if (ret) return ret; @@ -3637,8 +3635,7 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc) while (1) { rc->reserved_bytes = 0; - ret = btrfs_block_rsv_refill(fs_info, rc->block_rsv, - rc->block_rsv->size, + ret = btrfs_block_rsv_refill(rc->block_rsv, rc->block_rsv->size, BTRFS_RESERVE_FLUSH_ALL); if (ret) { err = ret; From 1dd5d95b3fc8a337d11026c3d6e9a4236b276225 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 16:41:15 +0100 Subject: [PATCH 1689/2286] btrfs: remove pointless qgroup assignment in block_rsv_release_bytes() If num_bytes == (u64)-1, there's no point to set qgroup_to_release to block_rsv->qgroup_rsv_size as later on we will overwrite that value without ever reading the variable before that. So simplify things and remove that assignment. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/block-rsv.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index 55e84ff0932ea5..fd604631b9ca8e 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -111,10 +111,8 @@ static u64 block_rsv_release_bytes(struct btrfs_fs_info *fs_info, u64 ret; spin_lock(&block_rsv->lock); - if (num_bytes == (u64)-1) { + if (num_bytes == (u64)-1) num_bytes = block_rsv->size; - qgroup_to_release = block_rsv->qgroup_rsv_size; - } block_rsv->size -= num_bytes; if (block_rsv->reserved >= block_rsv->size) { num_bytes = block_rsv->reserved - block_rsv->size; @@ -128,8 +126,6 @@ static u64 block_rsv_release_bytes(struct btrfs_fs_info *fs_info, qgroup_to_release = block_rsv->qgroup_rsv_reserved - block_rsv->qgroup_rsv_size; block_rsv->qgroup_rsv_reserved = block_rsv->qgroup_rsv_size; - } else { - qgroup_to_release = 0; } spin_unlock(&block_rsv->lock); From 95e3b8dc32179b9d2895fca5605d11a763e9d658 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 30 Sep 2026 17:16:39 +0100 Subject: [PATCH 1690/2286] btrfs: remove unnecessary forward struct declarations in volumes.h There is no need to forward declare the structures as their definitions are brought in by the include of the linux/blkdev.h and fs.h files. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/volumes.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h index 337d7007d9e225..6eddda23922152 100644 --- a/fs/btrfs/volumes.h +++ b/fs/btrfs/volumes.h @@ -24,9 +24,6 @@ #include "extent-io-tree.h" #include "fs.h" -struct block_device; -struct bdev_handle; -struct btrfs_fs_info; struct btrfs_block_group; struct btrfs_trans_handle; struct btrfs_transaction; From dd906477cc2c33c82ec17e6301bd4be4ea588565 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 1 Oct 2026 14:46:15 +0930 Subject: [PATCH 1691/2286] btrfs: qgroup: do not treat "ret > 0" as error when deleting a qgroup [BUG] There is a bug report that when removing a child qgroup, if removing the qgroup requires a rescan, it will delete the on-disk relation and qgroup items but not the in-memory qgroup rb entry. This makes all later qgroup rescan to fail. [CAUSE] The call chain looks like the following: btrfs_remove_qgroup() |- __del_qgroup_relation() | |- quick_update_accounting() | Which returns 1, showing this operation will require | a qgroup rescan |- if (ret) goto out; Which skips the del_qgroup_rb() call, leaving the in-memory qgroup rb entry still intact. Then later qgroup rescan/update will always fail and mark qgroup inconsistent: btrfs_run_qgroups() |- update_qgroup_info_item() | |- btrfs_search_slot() | Which returns -ENOENT, as the qgroup info item is already | deleted. |- qgroup_mark_inconsistent() |- return -ENOENT; [FIX] Do not treat "ret > 0" as an error. Reported-by: c.f.a@posteo.de Link: https://lore.kernel.org/linux-btrfs/14846642-85d3-4afb-ad99-1b74b22f5f90@posteo.de/ Fixes: 9c8b35b1ba21 ("btrfs: quota: Automatically update related qgroups or mark INCONSISTENT flags when assigning/deleting a qgroup relations.") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index bf06962d49f3df..dfe3827ec36970 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1853,7 +1853,7 @@ int btrfs_remove_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid) struct btrfs_qgroup_list, next_group); ret = __del_qgroup_relation(trans, qgroupid, list->group->qgroupid); - if (ret) + if (ret < 0) goto out; } From 65583d4749af1bede388425b5ab70cb6cbb68125 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 1 Oct 2026 14:46:16 +0930 Subject: [PATCH 1692/2286] btrfs: qgroup: use qgroup_mark_inconsistent() in quick_update_accounting() When quick_update_accounting() fails to handle a qgroup which is not fully exclusive (its exclusive number doesn't match the reference one), we have to mark the qgroup inconsistent. Unlike other locations we didn't use qgroup_mark_inconsistent(), thus when such problem happens no extra error message is shown. Use the helper in quick_update_accounting(), which does not only show the reason of marking qgroup inconsistent, but also cancels any running rescan and skips dirty accounting, avoiding wasting CPU time on already inconsistent qgroup numbers. Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index dfe3827ec36970..d3401a6df530fe 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1562,7 +1562,10 @@ static int quick_update_accounting(struct btrfs_fs_info *fs_info, } out: if (ret) - set_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags); + qgroup_mark_inconsistent(fs_info, + "unable to do quick excl updating for qgroup %hu/%llu, ret=%d", + btrfs_qgroup_level(src), + btrfs_qgroup_subvolid(src), ret); return ret; } From 727bb633fea9a3b37061b837c85647218dc28485 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 1 Oct 2026 14:46:17 +0930 Subject: [PATCH 1693/2286] btrfs: qgroup: do not leak -ENOENT from btrfs_remove_qgroup() If btrfs_remove_qgroup() is called on a qgroup that only exists in the rb-tree, then del_qgroup_item() will return -ENOENT, and we tolerate that -ENOENT. However if the to-be-deleted qgroup has no parent qgroups, the @ret will not be updated, and will leak to user space. It will make the ioctl() caller incorrectly assume the operation failed. Fix it by resetting @ret to 0 after a successful or tolerated del_qgroup_item() call, so the -ENOENT from del_qgroup_item() won't be leaked. Assisted-by: LLM (exposing this pre-existing problem) Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index d3401a6df530fe..46de0a5e80d5c4 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1850,6 +1850,7 @@ int btrfs_remove_qgroup(struct btrfs_trans_handle *trans, u64 qgroupid) ret = del_qgroup_item(trans, qgroupid); if (ret && ret != -ENOENT) goto out; + ret = 0; while (!list_empty(&qgroup->groups)) { list = list_first_entry(&qgroup->groups, From d00b85308abee300dfb7e1ac642f38bab9ec7492 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 30 Sep 2026 18:29:58 +0930 Subject: [PATCH 1694/2286] btrfs: raid56: do not verify the content if there is no data checksum [BUG] There is a bug report that fio can fail to sync the data on a degraded RAID56 mount: # mkfs.btrfs -m raid1 -d raid5 -f $dev1 $dev2 $dev3 # mount $dev1 $mnt # btrfs balance start -d $mnt # umount $mnt # btrfs dev scan -u $dev2 # wipefs -fa $dev2 # mount -o degraded $dev1 $mnt # fio --name=degraded-write --directory=$mnt --rw=randwrite --fdatasync=4 \ --bs=4k --size=512M --randrepeat=1 The run will fail at fsync: fio: io_u error on file /mnt/btrfs/degraded-write.0.0: Input/output error: datasync offset=142254080, buflen=0 fio: pid=2064, err=5/file:io_u.c:2008, func=io_u error, error=Input/output error [CAUSE] With extra debug output, it shows the problem pretty well: rbio=2876243968 faila=0 sectornr=0 rbio=2876243968 error=0-15 csum=2 bio=15 The first line shows the rbio's full stripe logical, and stripe/sector number when verify_one_sector() failed. The second line shows more detail about the rbio, the sector range [0, 16) is corrupted, which indicates the first data stripe is corrupted, matching the missing device. The important part is that the csum bitmap shows the data csum is only for sector number 2, meanwhile the failed verification is at sector number 0. This means we did the checksum verification even if no corresponding checksum is populated. [FIX] There is already a similar check at verify_bio_data_sectors(), but unfortunately there is no such check at verify_one_sector(). Add the missing check to skip the csum verification if there is no corresponding data checksum. Reported-by: Bartosz Fenski Link: https://lore.kernel.org/linux-btrfs/a72409af-45ef-45df-9970-2e38bcb22ea6@fenski.pl/ Fixes: 7a3150723061 ("btrfs: raid56: do data csum verification during RMW cycle") CC: stable@vger.kernel.org Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/raid56.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index 8ec24dbb180f92..5ec31299f1c301 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -1889,6 +1889,8 @@ static int verify_one_sector(struct btrfs_raid_bio *rbio, { struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; phys_addr_t *paddrs; + const unsigned int total_sector_nr = + stripe_nr * rbio->stripe_nsectors + sector_nr; u8 csum_buf[BTRFS_CSUM_SIZE]; u8 *csum_expected; @@ -1898,6 +1900,11 @@ static int verify_one_sector(struct btrfs_raid_bio *rbio, /* No way to verify P/Q as they are not covered by data csum. */ if (stripe_nr >= rbio->nr_data) return 0; + + /* No data checksum for this sector. */ + if (!test_bit(total_sector_nr, rbio->csum_bitmap)) + return 0; + /* * If we're rebuilding a read, we have to use pages from the * bio list if possible. @@ -1908,9 +1915,7 @@ static int verify_one_sector(struct btrfs_raid_bio *rbio, paddrs = rbio_stripe_paddrs(rbio, stripe_nr, sector_nr); } - csum_expected = rbio->csum_buf + - (stripe_nr * rbio->stripe_nsectors + sector_nr) * - fs_info->csum_size; + csum_expected = rbio->csum_buf + total_sector_nr * fs_info->csum_size; calculate_block_csum_paddrs(fs_info, paddrs, csum_buf); if (unlikely(memcmp(csum_buf, csum_expected, fs_info->csum_size) != 0)) return -EIO; From c73eb6f727414a0a78ee993eccd6c2d2065e9571 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 6 Sep 2026 16:33:37 +0930 Subject: [PATCH 1695/2286] btrfs: always use the second newest slot for rescue=usebackuproot [RESCUE FAILURE] There is a bug report that "rescue=usebackuproot" failed to properly mount the fs, but if using btrfs-mod-sb to use the second newest slot, then the fs can even pass the full "btrfs check" and be mounted RW. [CAUSE] During init_tree_roots(), "rescue=usebackuproot" only makes a difference if the tree and chunk roots failed to be load. The loading only requires the root node/leaf to be properly loaded: - tree root - extent tree - block group tree - device tree - data reloc tree - quota tree (optional) - uuid tree - raid stripe tree (for RST feature) And it doesn't really check any child tree blocks, thus for a lot of cases, "rescue=usebackuproot" won't make a difference. [ENHANCEMENT] Instead of trying every slot and settling down on the first passing backup slot, always use the second newest slot. The newest slot should always match the root info in the super block, and if it worked the end user should not even need to bother to use backup root. Older backup slots normally have a very low chance to work. Link: https://github.com/kdave/btrfs-progs/issues/1162#issuecomment-5475477108 Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 96 +++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 90cf0647c47e36..d3d3d67ced3e80 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2711,73 +2711,55 @@ static int __cold init_tree_roots(struct btrfs_fs_info *fs_info) int backup_index = find_newest_super_backup(fs_info); struct btrfs_super_block *sb = fs_info->super_copy; struct btrfs_root *tree_root = fs_info->tree_root; - bool handle_error = false; + bool use_backup = btrfs_test_opt(fs_info, USEBACKUPROOT); int ret = 0; - int i; - - for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS; i++) { - if (handle_error) { - if (!IS_ERR(tree_root->node)) - free_extent_buffer(tree_root->node); - tree_root->node = NULL; - - if (!btrfs_test_opt(fs_info, USEBACKUPROOT)) - break; - - free_root_pointers(fs_info, 0); - - /* - * Don't use the log in recovery mode, it won't be - * valid - */ - btrfs_set_super_log_root(sb, 0); - - btrfs_warn(fs_info, "try to load backup roots slot %d", i); - ret = read_backup_root(fs_info, i); - backup_index = ret; - if (ret < 0) - return ret; - } - ret = load_important_roots(fs_info); - if (ret) { - handle_error = true; - continue; + if (use_backup) { + ret = read_backup_root(fs_info, 1); + if (ret < 0) { + btrfs_err(fs_info, + "failed to load backup roots at slot %d", 1); + return ret; } - /* - * No need to hold btrfs_root::objectid_mutex since the fs - * hasn't been fully initialised and we are the only user + * Don't use the log in recovery mode, it won't be + * valid */ - ret = btrfs_init_root_free_objectid(tree_root); - if (ret < 0) { - handle_error = true; - continue; - } + btrfs_set_super_log_root(sb, 0); + backup_index = ret; + btrfs_warn(fs_info, "loaded backup roots at slot %d", 1); + } - ASSERT(tree_root->free_objectid <= BTRFS_LAST_FREE_OBJECTID); + ret = load_important_roots(fs_info); + if (ret) + return ret; - ret = btrfs_read_roots(fs_info); - if (ret < 0) { - handle_error = true; - continue; - } + /* + * No need to hold btrfs_root::objectid_mutex since the fs + * hasn't been fully initialised and we are the only user + */ + ret = btrfs_init_root_free_objectid(tree_root); + if (ret < 0) + return ret; - /* All successful */ - fs_info->generation = btrfs_header_generation(tree_root->node); - btrfs_set_last_trans_committed(fs_info, fs_info->generation); - fs_info->last_reloc_trans = 0; + ASSERT(tree_root->free_objectid <= BTRFS_LAST_FREE_OBJECTID); - /* Always begin writing backup roots after the one being used */ - if (backup_index < 0) { - fs_info->backup_root_index = 0; - } else { - fs_info->backup_root_index = backup_index + 1; - fs_info->backup_root_index %= BTRFS_NUM_BACKUP_ROOTS; - } - break; - } + ret = btrfs_read_roots(fs_info); + if (ret < 0) + return ret; + + /* All successful */ + fs_info->generation = btrfs_header_generation(tree_root->node); + btrfs_set_last_trans_committed(fs_info, fs_info->generation); + fs_info->last_reloc_trans = 0; + /* Always begin writing backup roots after the one being used */ + if (backup_index < 0) { + fs_info->backup_root_index = 0; + } else { + fs_info->backup_root_index = backup_index + 1; + fs_info->backup_root_index %= BTRFS_NUM_BACKUP_ROOTS; + } return ret; } From 9421d1897ba2185cd2c3cf7bf517fd99d4f58516 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Sun, 6 Sep 2026 16:33:38 +0930 Subject: [PATCH 1696/2286] btrfs: introduce more accurate usebackuproot options Previously we had no way to tell btrfs to use which backup slot, but always use the newest backup, and retry until all backup roots are exhausted. This doesn't give any control to the end user, even if the end user wants to use a specific backup slot. To address this problem, allow more fine-control on usebackuproot rescue option by introducing the following 4 rescue mount options: - rescue=usebackuproot_0 - rescue=usebackuproot_1 - rescue=usebackuproot_2 - rescue=usebackuproot_3 The number 0 means the newest slot, which should be the same as the one recorded in the super block and no need to load any backup root. 1/2/3 means the second/third/fourth(oldest) backup slot to use. The existing "rescue=usebackuproot" is just an alias of "rescue=usebackuproot_1". This will allow users to choose which backup root to use, thus can have a better control than blindly choose any slot that can pass the very basic root node checks. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 12 +++++++++--- fs/btrfs/fs.h | 1 + fs/btrfs/super.c | 28 ++++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index d3d3d67ced3e80..66b054eb98c6eb 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2715,10 +2715,14 @@ static int __cold init_tree_roots(struct btrfs_fs_info *fs_info) int ret = 0; if (use_backup) { - ret = read_backup_root(fs_info, 1); + ASSERT(fs_info->use_backup_slot >= 0 && + fs_info->use_backup_slot < BTRFS_NUM_BACKUP_ROOTS); + + ret = read_backup_root(fs_info, fs_info->use_backup_slot); if (ret < 0) { btrfs_err(fs_info, - "failed to load backup roots at slot %d", 1); + "failed to load backup roots at slot %d", + fs_info->use_backup_slot); return ret; } /* @@ -2727,7 +2731,8 @@ static int __cold init_tree_roots(struct btrfs_fs_info *fs_info) */ btrfs_set_super_log_root(sb, 0); backup_index = ret; - btrfs_warn(fs_info, "loaded backup roots at slot %d", 1); + btrfs_warn(fs_info, "loaded backup roots at slot %d", + fs_info->use_backup_slot); } ret = load_important_roots(fs_info); @@ -2898,6 +2903,7 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) init_waitqueue_head(&fs_info->async_submit_wait); init_waitqueue_head(&fs_info->delayed_iputs_wait); + fs_info->use_backup_slot = -1; /* Usable values until the real ones are cached from the superblock */ fs_info->nodesize = 4096; fs_info->sectorsize = 4096; diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 3eba8438593cde..649623c603e855 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -857,6 +857,7 @@ struct btrfs_fs_info { /* Next backup root to be overwritten */ int backup_root_index; + int use_backup_slot; /* Device replace state */ struct btrfs_dev_replace dev_replace; diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index ddb620ac241b42..8d01409ed382ab 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -87,6 +87,7 @@ struct btrfs_fs_context { unsigned long long mount_opt; unsigned long compress_type:4; int compress_level; + int use_backup_slot; refcount_t refs; }; @@ -175,6 +176,10 @@ static const struct constant_table btrfs_parameter_space_cache[] = { enum { Opt_rescue_usebackuproot, + Opt_rescue_usebackup_slot_0, + Opt_rescue_usebackup_slot_1, + Opt_rescue_usebackup_slot_2, + Opt_rescue_usebackup_slot_3, Opt_rescue_nologreplay, Opt_rescue_ignorebadroots, Opt_rescue_ignoredatacsums, @@ -185,6 +190,10 @@ enum { static const struct constant_table btrfs_parameter_rescue[] = { { "usebackuproot", Opt_rescue_usebackuproot }, + { "usebackuproot_0", Opt_rescue_usebackup_slot_0}, + { "usebackuproot_1", Opt_rescue_usebackup_slot_1}, + { "usebackuproot_2", Opt_rescue_usebackup_slot_2}, + { "usebackuproot_3", Opt_rescue_usebackup_slot_3}, { "nologreplay", Opt_rescue_nologreplay }, { "ignorebadroots", Opt_rescue_ignorebadroots }, { "ibadroots", Opt_rescue_ignorebadroots }, @@ -588,6 +597,14 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) switch (result.uint_32) { case Opt_rescue_usebackuproot: btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + ctx->use_backup_slot = 1; + break; + case Opt_rescue_usebackup_slot_0: + case Opt_rescue_usebackup_slot_1: + case Opt_rescue_usebackup_slot_2: + case Opt_rescue_usebackup_slot_3: + btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + ctx->use_backup_slot = result.uint_32 - Opt_rescue_usebackup_slot_0; break; case Opt_rescue_nologreplay: btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); @@ -611,6 +628,7 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param) btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS); btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY); btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT); + ctx->use_backup_slot = 1; break; default: btrfs_info(NULL, "unrecognized rescue option '%s'", @@ -1089,8 +1107,11 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry) seq_puts(seq, ",notreelog"); if (btrfs_test_opt(info, NOLOGREPLAY)) print_rescue_option(seq, "nologreplay", &printed); - if (btrfs_test_opt(info, USEBACKUPROOT)) - print_rescue_option(seq, "usebackuproot", &printed); + if (btrfs_test_opt(info, USEBACKUPROOT)) { + seq_printf(seq, "%susebackuproot_%d", printed ? ":" : ",rescue=", + info->use_backup_slot); + printed = true; + } if (btrfs_test_opt(info, IGNOREBADROOTS)) print_rescue_option(seq, "ignorebadroots", &printed); if (btrfs_test_opt(info, IGNOREDATACSUMS)) @@ -1406,6 +1427,7 @@ static void btrfs_ctx_to_info(struct btrfs_fs_info *fs_info, struct btrfs_fs_con fs_info->mount_opt = ctx->mount_opt; fs_info->compress_type = ctx->compress_type; fs_info->compress_level = ctx->compress_level; + fs_info->use_backup_slot = ctx->use_backup_slot; } static void btrfs_info_to_ctx(struct btrfs_fs_info *fs_info, struct btrfs_fs_context *ctx) @@ -1417,6 +1439,7 @@ static void btrfs_info_to_ctx(struct btrfs_fs_info *fs_info, struct btrfs_fs_con ctx->mount_opt = fs_info->mount_opt; ctx->compress_type = fs_info->compress_type; ctx->compress_level = fs_info->compress_level; + ctx->use_backup_slot = fs_info->use_backup_slot; } #define btrfs_info_if_set(fs_info, old_ctx, opt, fmt, args...) \ @@ -2209,6 +2232,7 @@ static int btrfs_init_fs_context(struct fs_context *fc) if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) { btrfs_info_to_ctx(btrfs_sb(fc->root->d_sb), ctx); } else { + ctx->use_backup_slot = -1; ctx->thread_pool_size = min_t(unsigned long, num_online_cpus() + 2, 8); ctx->max_inline = BTRFS_DEFAULT_MAX_INLINE; From 0388b5bd3a6eb8bcb20208c3b6dbee96fd30bbe0 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Thu, 1 Oct 2026 17:21:22 +0100 Subject: [PATCH 1697/2286] btrfs: qgroup: fix swapped blocks existence check when tracing after COW In btrfs_qgroup_trace_subtree_after_cow(), after we erase the swapped block, iterate over all possible tree levels to check if we sill have other swapped blocks. However the check is wrong, it considers that there are other swapped blocks if any rbtree for any level is empty, instead of not empty. As a consequence we set blocks->swapped to true basically every time since we always find an empty rbtree at least at level 7, since in practice it's nearly impossible to find such a huge btree. This makes every future call to btrfs_qgroup_trace_subtree_after_cow() do an unnecessary rbtree search instead of returning immediately. Fix this by updating the condition to set swapped to true if we find an rbtree that is not empty. Fixes: f616f5cd9da7 ("btrfs: qgroup: Use delayed subtree rescan for balance") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/ctree.h | 2 +- fs/btrfs/qgroup.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/ctree.h b/fs/btrfs/ctree.h index 22ba2b4505b35d..00d4c4184e76af 100644 --- a/fs/btrfs/ctree.h +++ b/fs/btrfs/ctree.h @@ -160,7 +160,7 @@ enum { */ struct btrfs_qgroup_swapped_blocks { spinlock_t lock; - /* RM_EMPTY_ROOT() of above blocks[] */ + /* True if any of the rbtrees in the blocks array below is not empty. */ bool swapped; struct rb_root blocks[BTRFS_MAX_LEVEL]; }; diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 46de0a5e80d5c4..798f21608de598 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -4913,7 +4913,7 @@ int btrfs_qgroup_trace_subtree_after_cow(struct btrfs_trans_handle *trans, /* Found one, remove it from @blocks first and update blocks->swapped */ rb_erase(&block->node, &blocks->blocks[level]); for (i = 0; i < BTRFS_MAX_LEVEL; i++) { - if (RB_EMPTY_ROOT(&blocks->blocks[i])) { + if (!RB_EMPTY_ROOT(&blocks->blocks[i])) { swapped = true; break; } From ba35b470084d97d729da87e6d2a1e55571a9aada Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 2 Oct 2026 16:45:47 +0100 Subject: [PATCH 1698/2286] btrfs: qgroup: abort transaction on failure to add qgroup relation When adding a qgroup relation, if we fail to add the relation item from "dst" to "src", we attempt to delete the relation item from "src" to "dst" that we added just before, however we ignore the deletion result and if we failed to delete we leave an inconsistency in the quota root. So check the result of the deletion and if it fails, abort the transaction to avoid persisting an inconsistent state. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 798f21608de598..4b6c82a24bc5a1 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1616,7 +1616,11 @@ int btrfs_add_qgroup_relation(struct btrfs_trans_handle *trans, u64 src, u64 dst ret = add_qgroup_relation_item(trans, dst, src); if (ret) { - del_qgroup_relation_item(trans, src, dst); + int ret2; + + ret2 = del_qgroup_relation_item(trans, src, dst); + if (ret2 < 0) + btrfs_abort_transaction(trans, ret); goto out; } From 1cf81f2196914c9a0ad192b68e1eb2ba7747a6d4 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 2 Oct 2026 17:37:15 +0100 Subject: [PATCH 1699/2286] btrfs: qgroup: fix leak of qgroups in rb tree after failure to enable quotas When enabling quotas we add struct btrfs_qroup items to the rbtree fs_info->qgroup_tree, however if there's an error during the quota enable operation after adding those items, we exit without removing the items from the rbtree and freeing them. Fix this by calling btrfs_free_qgroup_config() on error, which also deletes all sysfs entries (it calls btrfs_sysfs_del_qgroups()). Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 4b6c82a24bc5a1..9f21b091c545b2 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1293,8 +1293,13 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, if (ret) btrfs_put_root(quota_root); out: - if (ret) - btrfs_sysfs_del_qgroups(fs_info); + if (ret) { + /* + * Free all qgroups previously added with add_qgroup_rb() and + * sysfs entries. + */ + btrfs_free_qgroup_config(fs_info); + } mutex_unlock(&fs_info->qgroup_ioctl_lock); if (ret && trans) btrfs_end_transaction(trans); From aad3c3c84f065bbe8644233192ae143a583e4e23 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 2 Oct 2026 17:52:02 +0100 Subject: [PATCH 1700/2286] btrfs: qgroup: merge error labels in btrfs_quota_enable() There is no need to have 3 different labels to which we goto on error. Simplify this and have a single one, named 'out', where we always free the path and put the root, as btrfs_free_path() and btrfs_put_root() accept NULL arguments and do nothing in that case. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 9f21b091c545b2..c9fc3b6a51323b 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -999,7 +999,7 @@ static int btrfs_clean_quota_tree(struct btrfs_trans_handle *trans, int btrfs_quota_enable(struct btrfs_fs_info *fs_info, struct btrfs_ioctl_quota_ctl_args *quota_ctl_args) { - struct btrfs_root *quota_root; + struct btrfs_root *quota_root = NULL; struct btrfs_root *tree_root = fs_info->tree_root; struct btrfs_path *path = NULL; struct btrfs_qgroup_status_item *ptr; @@ -1076,6 +1076,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, quota_root = btrfs_create_tree(trans, BTRFS_QUOTA_TREE_OBJECTID); if (IS_ERR(quota_root)) { ret = PTR_ERR(quota_root); + quota_root = NULL; btrfs_abort_transaction(trans, ret); goto out; } @@ -1084,7 +1085,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, if (unlikely(!path)) { ret = -ENOMEM; btrfs_abort_transaction(trans, ret); - goto out_free_root; + goto out; } key.objectid = 0; @@ -1095,7 +1096,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, sizeof(*ptr)); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } leaf = path->nodes[0]; @@ -1131,7 +1132,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, goto out_add_root; if (unlikely(ret < 0)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } while (1) { @@ -1150,14 +1151,14 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, if (unlikely(!prealloc)) { ret = -ENOMEM; btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } ret = add_qgroup_item(trans, quota_root, found_key.offset); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } qgroup = add_qgroup_rb(fs_info, prealloc, found_key.offset); @@ -1165,13 +1166,13 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup); if (unlikely(ret < 0)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } ret = btrfs_search_slot_for_read(tree_root, &found_key, path, 1, 0); if (unlikely(ret < 0)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } if (ret > 0) { /* @@ -1188,7 +1189,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, ret = btrfs_next_item(tree_root, path); if (unlikely(ret < 0)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } if (ret) break; @@ -1199,21 +1200,21 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, ret = add_qgroup_item(trans, quota_root, BTRFS_FS_TREE_OBJECTID); if (unlikely(ret)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } ASSERT(prealloc == NULL); prealloc = kzalloc_obj(*prealloc, GFP_NOFS); if (!prealloc) { ret = -ENOMEM; - goto out_free_path; + goto out; } qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID); prealloc = NULL; ret = btrfs_sysfs_add_one_qgroup(fs_info, qgroup); if (unlikely(ret < 0)) { btrfs_abort_transaction(trans, ret); - goto out_free_path; + goto out; } /* @@ -1245,7 +1246,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, clear_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags); fs_info->qgroup_enable_gen = 0; } - goto out_free_path; + goto out; } /* @@ -1262,7 +1263,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, /* Skip rescan for simple qgroups. */ if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE) - goto out_free_path; + goto out; ret = qgroup_rescan_init(fs_info, 0, 1); if (!ret) { @@ -1287,18 +1288,15 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, ret = 0; } -out_free_path: - btrfs_free_path(path); -out_free_root: - if (ret) - btrfs_put_root(quota_root); out: + btrfs_free_path(path); if (ret) { /* * Free all qgroups previously added with add_qgroup_rb() and * sysfs entries. */ btrfs_free_qgroup_config(fs_info); + btrfs_put_root(quota_root); } mutex_unlock(&fs_info->qgroup_ioctl_lock); if (ret && trans) From 599fdbf3e309e0e6227c67629a520c98059e2fff Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 2 Oct 2026 18:03:06 +0100 Subject: [PATCH 1701/2286] btrfs: abort transaction on qgroup failures in create_pending_snapshot() If we get an error when calling one of the qgroup functions, we jump to the 'fail' label without aborting the transaction. This is not a bug as up the call chain (transaction commit path), we will end up aborting the transaction. However having the explicit transaction abort in create_pending_snapshot() allows us to get a stack trace and log message that tells us exactly where we failed, useful for troubleshooting, and also adds consistency since in that function we abort the transaction in every other error path. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/transaction.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index 286a9d455d26f1..8df7b47b09e08f 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -1920,14 +1920,21 @@ static noinline int create_pending_snapshot(struct btrfs_trans_handle *trans, * To co-operate with that hack, we do hack again. * Or snapshot will be greatly slowed down by a subtree qgroup rescan */ - if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_FULL) + if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_FULL) { ret = qgroup_account_snapshot(trans, root, parent_root, pending->inherit, objectid); - else if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE) + if (unlikely(ret < 0)) { + btrfs_abort_transaction(trans, ret); + goto fail; + } + } else if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE) { ret = btrfs_qgroup_inherit(trans, btrfs_root_id(root), objectid, btrfs_root_id(parent_root), pending->inherit); - if (unlikely(ret < 0)) - goto fail; + if (unlikely(ret < 0)) { + btrfs_abort_transaction(trans, ret); + goto fail; + } + } ret = btrfs_insert_dir_item(trans, &fname.disk_name, parent_inode, &key, BTRFS_FT_DIR, index, NULL); From 9685445df748a7a33edb6b121350fae4af09c681 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Fri, 2 Oct 2026 19:44:02 +0100 Subject: [PATCH 1702/2286] btrfs: qgroup: fix off-by-one max level check in qgroup_trace_new_subtree_blocks() We have maximum of 8 (BTRFS_MAX_LEVEL) levels in a btree, going from 0 to 7, so the upper bound checks should be: >= BTRFS_MAX_LEVEL and not: >= BTRFS_MAX_LEVEL - 1 In practice this will hardly ever be a problem since having a btree with 8 levels would imply a massively huge fs. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index c9fc3b6a51323b..54d82d6fd4bb0e 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -2491,8 +2491,8 @@ static int qgroup_trace_new_subtree_blocks(struct btrfs_trans_handle* trans, int i; /* Level sanity check */ - if (unlikely(cur_level < 0 || cur_level >= BTRFS_MAX_LEVEL - 1 || - root_level < 0 || root_level >= BTRFS_MAX_LEVEL - 1 || + if (unlikely(cur_level < 0 || cur_level >= BTRFS_MAX_LEVEL || + root_level < 0 || root_level >= BTRFS_MAX_LEVEL || root_level < cur_level)) { btrfs_err_rl(fs_info, "%s: bad levels, cur_level=%d root_level=%d", From 784cd5a83476c6c720521f21350fb823928555ff Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 5 Oct 2026 12:47:09 +0100 Subject: [PATCH 1703/2286] btrfs: fix lost error return value in btrfs_alloc_logged_file_extent() If we fail to allocate a reserved file extent we then call btrfs_record_squota_delta() and assign its return value to 'ret', discarding the error from the failed allocation attempt and allowing log replay to proceed as if no errors happened, resulting in some replayed file extent item to point to an unallocated extent. At the moment btrfs_record_squota_delta() always returns 0 (success). Fixes: cecbb533b5fc ("btrfs: record simple quota deltas in delayed refs") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/extent-tree.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index a0d5ab03aae264..15b4ed56ce7180 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -5227,7 +5227,8 @@ int btrfs_alloc_logged_file_extent(struct btrfs_trans_handle *trans, offset, ins, 1, root_objectid); if (ret) btrfs_pin_extent(trans, ins->objectid, ins->offset); - ret = btrfs_record_squota_delta(fs_info, &delta); + else + ret = btrfs_record_squota_delta(fs_info, &delta); btrfs_put_block_group(block_group); return ret; } From c97b139c6ceeb69b264e0a87f2a47bab651a51a8 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 5 Oct 2026 13:03:53 +0100 Subject: [PATCH 1704/2286] btrfs: qgroup: make btrfs_record_squota_delta() return void The function never returns any error, so simplify it and its callers by making it return void. Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/extent-tree.c | 10 +++------- fs/btrfs/qgroup.c | 14 +++++--------- fs/btrfs/qgroup.h | 4 ++-- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index 15b4ed56ce7180..9b0a5c6b96f3ce 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -1658,7 +1658,7 @@ static int run_delayed_data_ref(struct btrfs_trans_handle *trans, href->owning_root); free_head_ref_squota_rsv(trans->fs_info, href); if (!ret) - ret = btrfs_record_squota_delta(trans->fs_info, &delta); + btrfs_record_squota_delta(trans->fs_info, &delta); } else if (node->action == BTRFS_ADD_DELAYED_REF) { ret = __btrfs_inc_extent_ref(trans, node, extent_op); } else if (node->action == BTRFS_DROP_DELAYED_REF) { @@ -3180,11 +3180,7 @@ static int do_free_extent_accounting(struct btrfs_trans_handle *trans, } } - ret = btrfs_record_squota_delta(trans->fs_info, delta); - if (unlikely(ret)) { - btrfs_abort_transaction(trans, ret); - return ret; - } + btrfs_record_squota_delta(trans->fs_info, delta); /* If remapped, FST has already been taken care of in remove_range_from_remap_tree(). */ if (!remapped) { @@ -5228,7 +5224,7 @@ int btrfs_alloc_logged_file_extent(struct btrfs_trans_handle *trans, if (ret) btrfs_pin_extent(trans, ins->objectid, ins->offset); else - ret = btrfs_record_squota_delta(fs_info, &delta); + btrfs_record_squota_delta(fs_info, &delta); btrfs_put_block_group(block_group); return ret; } diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 54d82d6fd4bb0e..cf8dfd5c692be4 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -4966,10 +4966,9 @@ void btrfs_qgroup_destroy_extent_records(struct btrfs_transaction *trans) xa_destroy(&trans->delayed_refs.dirty_extents); } -int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, - const struct btrfs_squota_delta *delta) +void btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, + const struct btrfs_squota_delta *delta) { - int ret; struct btrfs_qgroup *qgroup; struct btrfs_qgroup *qg; LIST_HEAD(qgroup_list); @@ -4979,24 +4978,22 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, if (btrfs_qgroup_mode(fs_info) != BTRFS_QGROUP_MODE_SIMPLE && !test_bit(BTRFS_FS_SQUOTA_ENABLING, &fs_info->flags)) - return 0; + return; if (!btrfs_is_fstree(root)) - return 0; + return; /* If the extent predates enabling quotas, don't count it. */ if (delta->generation < fs_info->qgroup_enable_gen) - return 0; + return; spin_lock(&fs_info->qgroup_lock); qgroup = find_qgroup_rb(fs_info, root); if (WARN_ON_ONCE(!qgroup)) { btrfs_warn(fs_info, "squota failed to find qgroup for root %llu", root); - ret = 0; goto out; } - ret = 0; qgroup_iterator_add(&qgroup_list, qgroup); list_for_each_entry(qg, &qgroup_list, iterator) { struct btrfs_qgroup_list *glist; @@ -5022,5 +5019,4 @@ int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, out: spin_unlock(&fs_info->qgroup_lock); - return ret; } diff --git a/fs/btrfs/qgroup.h b/fs/btrfs/qgroup.h index c64b26b09c22f2..a29b4e216f25d4 100644 --- a/fs/btrfs/qgroup.h +++ b/fs/btrfs/qgroup.h @@ -427,7 +427,7 @@ int btrfs_qgroup_trace_subtree_after_cow(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct extent_buffer *eb); void btrfs_qgroup_destroy_extent_records(struct btrfs_transaction *trans); bool btrfs_check_quota_leak(const struct btrfs_fs_info *fs_info); -int btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, - const struct btrfs_squota_delta *delta); +void btrfs_record_squota_delta(struct btrfs_fs_info *fs_info, + const struct btrfs_squota_delta *delta); #endif From bbb86c6dc1a8168c4f718b246dc0857321c7ba81 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Mon, 5 Oct 2026 13:19:41 +0100 Subject: [PATCH 1705/2286] btrfs: use READ_ONCE() to read the chunk size from the data space_info The chunk size of a space_info is always updated with WRITE_ONCE() (see btrfs_update_space_info_chunk_size()), so when reading it in calc_effective_data_chunk_size() we should use READ_ONCE() to prevent load tearing. We are already using READ_ONCE() in every other function that accesses the chunk size of a space_info. Reviewed-by: Daniel Vacek Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/space-info.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 39a28e1bec8ad8..72fa0150246188 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -443,8 +443,8 @@ static u64 calc_effective_data_chunk_size(const struct btrfs_fs_info *fs_info) */ data_sinfo = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_DATA); if (btrfs_is_zoned(fs_info)) - return data_sinfo->chunk_size; - data_chunk_size = min(data_sinfo->chunk_size, + return READ_ONCE(data_sinfo->chunk_size); + data_chunk_size = min(READ_ONCE(data_sinfo->chunk_size), mult_perc(fs_info->fs_devices->total_rw_bytes, 10)); return min_t(u64, data_chunk_size, SZ_1G); } From 04136cd21c64cc7036d768e92a781347bb425d25 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 7 Oct 2026 15:41:21 +0100 Subject: [PATCH 1706/2286] btrfs: defrag: fix key comparison with max defrag key in btrfs_defrag_leaves() The comparison can not be done in isolation for each key component (type and offset) after the objectid component. For example if the defrag_max key is (300 2 0) and the defrag_progress key is (301 1 0), the existing comparison makes us jump to the 'done' label with 'ret' set to -EAGAIN because 2 > 1, resulting in continuing to defrag despite having passed the objectid of 300. Fix this by doing the comparison correctly with btrfs_comp_cpu_keys(). Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/defrag.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c index 6ec5dd760d4299..75c87aee5de94c 100644 --- a/fs/btrfs/defrag.c +++ b/fs/btrfs/defrag.c @@ -530,11 +530,7 @@ static int btrfs_defrag_leaves(struct btrfs_trans_handle *trans, out: btrfs_free_path(path); if (ret == -EAGAIN) { - if (root->defrag_max.objectid > root->defrag_progress.objectid) - goto done; - if (root->defrag_max.type > root->defrag_progress.type) - goto done; - if (root->defrag_max.offset > root->defrag_progress.offset) + if (btrfs_comp_cpu_keys(&root->defrag_max, &root->defrag_progress) > 0) goto done; ret = 0; } From eb55bc2bfe204e6bfccfe179226d7fb5024cb010 Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Wed, 7 Oct 2026 16:08:50 +0100 Subject: [PATCH 1707/2286] btrfs: defrag: fix check to see if next extent can be defragged too We want to check if the next extent is at maximum capacity but we are passing the current extent map to get_extent_max_capacity() and not the next extent map. For example if the current extent is compressed (its maximum capacity is 128K) and the next extent is uncompressed (its maximum capacity is 128M), then we can bail out and not defrag the next extent too - when for example the next extent as a size of 128K, is uncompressed, and the extent threshold is >= 256K. Fixes: 979b25c300db ("btrfs: defrag: don't defrag extents which are already at max capacity") Reviewed-by: Qu Wenruo Signed-off-by: Filipe Manana Signed-off-by: David Sterba --- fs/btrfs/defrag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/defrag.c b/fs/btrfs/defrag.c index 75c87aee5de94c..7010d714cfe100 100644 --- a/fs/btrfs/defrag.c +++ b/fs/btrfs/defrag.c @@ -811,7 +811,7 @@ static bool defrag_check_next_extent(struct inode *inode, struct extent_map *em, * If the next extent is at its max capacity, defragging current extent * makes no sense, as the total number of extents won't change. */ - if (next->len >= get_extent_max_capacity(fs_info, em)) + if (next->len >= get_extent_max_capacity(fs_info, next)) goto out; /* Skip older extent */ if (next->generation < newer_than) From 9985f1643ac3f8595862e2f82108f0ccae4cf959 Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Fri, 9 Oct 2026 13:33:59 -0400 Subject: [PATCH 1708/2286] btrfs: return -ENOTTY for unknown ioctls on the control device Commit 5ab2b180884c ("btrfs: factor out validation of btrfs_ioctl_vol_args::name") made btrfs_control_ioctl() store the result of btrfs_check_ioctl_vol_args_path() in ret before it looks at the command. However, ret is initialized to -ENOTTY and the switch has no default case, so this initial value was what an unknown command returned. As of now, every unknown ioctl on /dev/btrfs-control returns 0. A tool that tries a control ioctl which the running kernel does not have is told that the ioctl succeeded. The issue can be reproduced with a simple C program, run as root: #include #include #include int main(void) { static char buf[4096]; int fd = open("/dev/btrfs-control", O_RDWR); if (ioctl(fd, TCGETS, buf) < 0) perror("ioctl"); else printf("ioctl succeeded\n"); return 0; } Before commit 5ab2b180884c ("btrfs: factor out validation of btrfs_ioctl_vol_args::name"), the result is: ioctl: Inappropriate ioctl for device After that commit, the result is: ioctl succeeded This patch will add a default case that returns -ENOTTY. Fixes: 5ab2b180884c ("btrfs: factor out validation of btrfs_ioctl_vol_args::name") CC: stable@vger.kernel.org Reviewed-by: Qu Wenruo Signed-off-by: Hengyu Liang Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/super.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index 8d01409ed382ab..7fabb775d4e3f6 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -2329,6 +2329,9 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd, case BTRFS_IOC_GET_SUPPORTED_FEATURES: ret = btrfs_ioctl_get_supported_features((void __user*)arg); break; + default: + ret = -ENOTTY; + break; } out: From 940983a3000361ddff834a1c55bdb0e9e47ed88b Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Fri, 9 Oct 2026 08:43:39 +1030 Subject: [PATCH 1709/2286] btrfs: prevent use-after-free in btrfs_transaction::switch_commits If a transaction is aborted during committing, btrfs_transaction::switch_commits can be non-empty, but the btrfs_transaction structure can be freed at the last btrfs_put_transaction() inside cleanup_transaction(). The btrfs_root::dirty_list entry still linked into switch_commits will point to already released memory, causing use-after-free. Address this problem by manually deleting all entries inside btrfs_transaction::switch_commits. And since we're here, also release the dirty_log_pages and qgroup swapped blocks for that root, which are only released during a successful switch_commit_roots() call. Reviewed-by: Boris Burkov Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 66b054eb98c6eb..02adda0650978e 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -4966,6 +4966,16 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans) list_del_init(&dev->post_commit_list); } + while (!list_empty(&cur_trans->switch_commits)) { + struct btrfs_root *root; + + root = list_first_entry(&cur_trans->switch_commits, + struct btrfs_root, dirty_list); + list_del_init(&root->dirty_list); + btrfs_extent_io_tree_release(&root->dirty_log_pages); + btrfs_qgroup_clean_swapped_blocks(root); + } + btrfs_destroy_delayed_refs(cur_trans); cur_trans->state = TRANS_STATE_COMMIT_START; From 087d1581f03ec4f924c22620c4970602f0ca734e Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Thu, 8 Oct 2026 22:28:31 +0100 Subject: [PATCH 1710/2286] MAINTAINERS: cpufreq: Replace CPPC reviewer Take over CPPC cpufreq review duties from Pierre Gondois. Thanks to Pierre for his reviews and contributions to the driver. Signed-off-by: Christian Loehle Reviewed-by: Pierre Gondois Reviewed-by: Jie Zhan Link: https://patch.msgid.link/20261008212832.354382-2-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index c2414447892c24..e69c888f1ec82f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6763,7 +6763,7 @@ M: "Rafael J. Wysocki" M: Viresh Kumar R: Jie Zhan R: Lifeng Zheng -R: Pierre Gondois +R: Christian Loehle R: Sumit Gupta L: linux-pm@vger.kernel.org S: Maintained From ff8944361a7b28b80ca3a77a36877b4a45f377b4 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Thu, 8 Oct 2026 22:28:32 +0100 Subject: [PATCH 1711/2286] MAINTAINERS: ACPI: Add a separate CPPC entry CPPC is a distinct part of ACPI, with its own CPU performance control semantics and register access mechanisms. Add a dedicated MAINTAINERS entry to help direct changes to maintainers familiar with those details and the various platforms implementing CPPC. List Rafael and myself as maintainers for the implementation, public header and sysfs documentation. Signed-off-by: Christian Loehle Acked-by: Sudeep Holla Reviewed-by: K Prateek Nayak Link: https://patch.msgid.link/20261008212832.354382-3-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index e69c888f1ec82f..68be35f710dedc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -327,6 +327,17 @@ F: drivers/acpi/acpica/ F: include/acpi/ F: tools/power/acpi/ +ACPI CPPC +M: "Rafael J. Wysocki" +M: Christian Loehle +L: linux-acpi@vger.kernel.org +L: linux-pm@vger.kernel.org +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm +F: Documentation/admin-guide/acpi/cppc_sysfs.rst +F: drivers/acpi/cppc_acpi.c +F: include/acpi/cppc_acpi.h + ACPI FOR ARM64 (ACPI/arm64) M: Lorenzo Pieralisi M: Hanjun Guo From 029d11df81278d99d8a0441ff45a5dfdfce0168b Mon Sep 17 00:00:00 2001 From: Vibhor Goel Date: Sat, 3 Oct 2026 22:10:19 +0530 Subject: [PATCH 1712/2286] ACPI: dock: Retry probing battery docks after hotplug On a ThinkPad T480 booted with the removable battery absent, reinserting that battery can leave BAT1 missing from /sys/class/power_supply even after firmware sends an ACPI add notification. The battery dock platform device already exists, but its driver did not bind while the battery was absent. During hotplug, acpi_bus_scan() updates the ACPI device and attempts to attach it on the ACPI bus. acpi_create_platform_device() does not create another platform device because the existing physical node is still associated with the ACPI device. Consequently, the existing platform device remains unbound. Retry probing the battery dock platform device after hotplug_dock_devices() has refreshed the ACPI state. Restrict this to battery docks. If the platform device is already bound, device_initial_probe() leaves that binding in place. Signed-off-by: Vibhor Goel [ rjw: Add empty code line after a conditional ] Link: https://patch.msgid.link/20261003164019.11195-1-vibhor1102@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/dock.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index d7f3a8912e6a16..24c5813ca2913b 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -445,6 +445,15 @@ int dock_notify(struct acpi_device *adev, u32 event) break; } hotplug_dock_devices(ds, event); + /* + * The dock platform device already exists when an empty battery + * bay becomes occupied, but its driver may not have bound while + * the battery was absent. Retry after the ACPI scan updates its + * status, since the scan only attaches the ACPI device. + */ + if (ds->flags & DOCK_IS_BAT) + device_initial_probe(&ds->dock_device->dev); + complete_dock(ds); dock_event(ds, event, DOCK_EVENT); acpi_evaluate_lck(ds->handle, 1); From 23ec17231cc6a83bb11d8660db5bfc5336f53a0e Mon Sep 17 00:00:00 2001 From: Hakan Abay Date: Mon, 5 Oct 2026 20:49:39 +0300 Subject: [PATCH 1713/2286] ACPI: PM: Add unused power resource quirk for Casper Excalibur G900 On the Casper Excalibur G900, the NVIDIA discrete GPU and the second NVMe device become inaccessible when unused ACPI power resources are turned off during initialization. The failure is similar to the THUNDEROBOT ZERO case addressed by commit cd7ef20ba8c6 ("ACPI: PM: Add unused power resource quirk for THUNDEROBOT ZERO"). Without the quirk, the NVIDIA GPU and the second NVMe device are left inaccessible in D3cold and attempts to transition them back to D0 fail. Add a DMI quirk for the affected Casper Excalibur G900 with NLCC 001 mainboard to leave unused ACPI power resources on. With the quirk applied, the second NVMe device remains accessible. The NVIDIA GPU can enter D3cold while idle and subsequently return to D0 for accelerated rendering. Link: https://bugs.kali.org/view.php?id=9891 Signed-off-by: Hakan Abay Link: https://patch.msgid.link/20261005174939.53873-1-hakan.abay84@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index c6955d1ad29db7..34b73f320b1844 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1134,6 +1134,18 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { } }, + { + /* + * CASPER EXCALIBUR G900 laptop: Keep unused ACPI power + * resources on to prevent the NVIDIA dGPU and NVMe #2 + * from becoming inaccessible. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "CASPER BILGISAYAR SISTEMLERI"), + DMI_MATCH(DMI_PRODUCT_NAME, "EXCALIBUR G900"), + DMI_MATCH(DMI_BOARD_NAME, "NLCC 001"), + }, + }, {} }; From f43cdd75b38a29c96896ed1f7bf9b3ec04a6c3b7 Mon Sep 17 00:00:00 2001 From: Riwen Lu Date: Thu, 8 Oct 2026 10:51:52 +0800 Subject: [PATCH 1714/2286] ACPI: PM: sleep: Drop unused sleep_no_lps0 define The sleep_no_lps0 stub define in the !CONFIG_ACPI_SLEEP branch of sleep.c has no users left in that file: the LPS0 code consuming it was moved to drivers/acpi/x86/s2idle.c by commit fef98671194b ("ACPI: PM: s2idle: Move x86-specific code to the x86 directory"), which has its own static module parameter of the same name, so the define is no longer used. Drop it. Signed-off-by: Riwen Lu Link: https://patch.msgid.link/20261008025152.153073-1-luriwen@kylinos.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 132a9df9847137..a94bac4422d44f 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -548,7 +548,6 @@ static void acpi_pm_end(void) acpi_sleep_tts_switch(acpi_target_sleep_state); } #else /* !CONFIG_ACPI_SLEEP */ -#define sleep_no_lps0 (1) #define acpi_target_sleep_state ACPI_STATE_S0 #define acpi_sleep_default_s3 (1) static inline void acpi_sleep_dmi_check(void) {} From 5d76269ee75391812bfdf18df4b2db690d7e264d Mon Sep 17 00:00:00 2001 From: Chitransh Mandhaniya Date: Sat, 10 Oct 2026 18:18:30 +0530 Subject: [PATCH 1715/2286] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W The Dell OptiPlex AIO 7410 35W (board 01KP8D) declares DELL0501 in its ACPI DSDT (status = 0x0F) even though no UART scalar board is physically present. This causes acpi_video_get_backlight_type() to return acpi_backlight_dell_uart, which makes i915 skip registering intel_backlight. The dell-uart-backlight driver then times out talking to the absent board and the probe fails, leaving the machine with zero backlight control. A BIOS update fixes this on some models (e.g. OptiPlex 7420, board 0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a corrected BIOS for the 7410 even in the latest release (1.37.0, 2026-05-28). Add a DMI quirk to force native backlight on this model so that i915 registers intel_backlight and brightness control works out of the box. Tested on: - OptiPlex AIO 7410 35W (01KP8D, BIOS 1.37.0) -- confirmed acpi_backlight=native produces working intel_backlight (raw, max=96000), DMI quirk has the same effect. - OptiPlex AIO 7420 35W (0914RT, BIOS 1.27.0) -- DELL0501 absent in updated BIOS, intel_backlight works natively (no regression). Signed-off-by: Chitransh Mandhaniya Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20261010124830.116750-1-chitranshmandhaniya@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 3037fd18d42064..326d4c6d132ab2 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -883,6 +883,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7770 AIO"), }, }, + { + .callback = video_detect_force_native, + /* Dell OptiPlex AIO 7410 35W */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex AIO 7410 35W"), + }, + }, /* * Models which have nvidia-ec-wmi support, but should not use it. From 58e844c3ba387ceb0f527a6ba82aa50c81c4d7b7 Mon Sep 17 00:00:00 2001 From: Yalagada Pavan Kumar Date: Fri, 9 Oct 2026 08:43:40 +1030 Subject: [PATCH 1716/2286] btrfs: fix use-after-free on quota enable allocation failure [BUG] There is a syzbot report that a use-after-free bug is triggered after a failed btrfs_quota_enable(): loop0: detected capacity change from 32768 to 0 ================================================================== BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29 Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324 CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 Call Trace: dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120 print_address_description+0x55/0x1e0 mm/kasan/report.c:378 print_report+0x58/0x70 mm/kasan/report.c:482 kasan_report+0x117/0x150 mm/kasan/report.c:595 __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29 __list_add_valid include/linux/list.h:104 [inline] __list_add include/linux/list.h:169 [inline] list_add include/linux/list.h:192 [inline] list_move include/linux/list.h:345 [inline] add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232 btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553 btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696 btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1 btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline] remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline] __btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline] btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866 alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976 alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline] run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline] run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline] btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline] __btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134 btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246 btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267 sync_filesystem+0x1d2/0x240 fs/sync.c:66 btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511 reconfigure_super+0x232/0x8f0 fs/super.c:1128 do_remount fs/namespace.c:3410 [inline] path_mount+0xd4e/0x1050 fs/namespace.c:4160 do_mount fs/namespace.c:4181 [inline] __do_sys_mount fs/namespace.c:4397 [inline] __se_sys_mount+0x31d/0x420 fs/namespace.c:4374 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f9c8dd9f3ca Allocated by task 5323: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 poison_kmalloc_redzone mm/kasan/common.c:409 [inline] __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426 kasan_kmalloc include/linux/kasan.h:263 [inline] __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563 _kmalloc_noprof include/linux/slab.h:991 [inline] _kzalloc_noprof include/linux/slab.h:1312 [inline] btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638 btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832 btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074 btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 5323: kasan_save_stack mm/kasan/common.c:57 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:78 kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584 poison_slab_object mm/kasan/common.c:264 [inline] __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296 kasan_slab_free include/linux/kasan.h:235 [inline] slab_free_hook mm/slub.c:2748 [inline] slab_free mm/slub.c:6508 [inline] kfree+0x1c5/0x650 mm/slub.c:6801 btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292 btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:597 [inline] __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline] do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84 entry_SYSCALL_64_after_hwframe+0x77/0x7f [CAUSE] During btrfs_quota_enable(), a temporary quota_root is created, then qgroup items are created for each subvolume and inserted into that root. If there are enough subvolumes, the level of quota_root will be bumped, and add_root_to_dirty_list() will be called, adding quota_root->dirty_list into fs_info->dirty_cowonly_roots. But if a problem is hit during btrfs_quota_enable() later, that quota_root will be released through btrfs_put_root(), causing the whole quota_root structure to be released, while leaving fs_info->dirty_cowonly_roots still pointing to the released quota_root->dirty_list. Later access through fs_info->dirty_cowonly_roots will trigger a use-after-free bug. [FIX] We can just call list_del_init(&root->dirty_list) when btrfs_quota_enable() failed. Also to follow the existing error handling behavior, abort the transaction when the memory preallocation failed. Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/ Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8 Reviewed-by: Filipe Manana Signed-off-by: Yalagada Pavan Kumar Reviewed-by: Boris Burkov Reviewed-by: Qu Wenruo [ Change the commit message to include a proper analysis. ] Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/qgroup.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index cf8dfd5c692be4..a0651fbfe77820 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, prealloc = kzalloc_obj(*prealloc, GFP_NOFS); if (!prealloc) { ret = -ENOMEM; + btrfs_abort_transaction(trans, ret); goto out; } qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID); @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info, * sysfs entries. */ btrfs_free_qgroup_config(fs_info); + + if (quota_root) { + spin_lock(&fs_info->trans_lock); + list_del_init("a_root->dirty_list); + spin_unlock(&fs_info->trans_lock); + } btrfs_put_root(quota_root); } mutex_unlock(&fs_info->qgroup_ioctl_lock); From c1703c8772630f09e46b20eb6f3c8cc040c1a6eb Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Mon, 5 Oct 2026 12:06:58 +0000 Subject: [PATCH 1717/2286] ARC: Emulate two-byte cmpxchg ARC has no two-byte atomic compare and swap, so the arch_cmpxchg_relaxed() macro switch lets case 2 fall through to BUILD_BUG() via default, and a two-byte cmpxchg() fails the build. Route case 2 through the new cmpxchg_emu_u16(), which narrows the old and new values itself, so the dispatch keeps the (uintptr_t) casts the case 1 path already uses. The sizeof(*(_p_)) fix that v4 folded in here landed separately through Vineet's f050c3e61d2a, so this patch is now only the case 2 wiring. Signed-off-by: Bradley Morgan Signed-off-by: Paul E. McKenney Acked-by: Vineet Gupta --- arch/arc/include/asm/cmpxchg.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arc/include/asm/cmpxchg.h b/arch/arc/include/asm/cmpxchg.h index 76f43db0890fcd..7157718f147c1f 100644 --- a/arch/arc/include/asm/cmpxchg.h +++ b/arch/arc/include/asm/cmpxchg.h @@ -50,6 +50,9 @@ case 1: \ _prev_ = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *__force)_p_, (uintptr_t)_o_, (uintptr_t)_n_); \ break; \ + case 2: \ + _prev_ = (__typeof__(*(ptr)))cmpxchg_emu_u16((volatile u16 *__force)_p_, (unsigned long)_o_, (unsigned long)_n_); \ + break; \ case 4: \ _prev_ = __cmpxchg(_p_, _o_, _n_); \ break; \ From 2213947c08fa9c44595aa0e84e359985ed40d3b6 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Mon, 5 Oct 2026 12:06:59 +0000 Subject: [PATCH 1718/2286] csky: Emulate two-byte cmpxchg C-SKY has no two-byte atomic compare and swap, so the __cmpxchg() macros let case 2 fall through to BUILD_BUG() via default, and a two-byte cmpxchg() fails the build. Route case 2 through the new cmpxchg_emu_u16(), which narrows the old and new values itself, so all three orderings, relaxed, acquire and full, keep the (uintptr_t) casts the case 1 path already uses. Signed-off-by: Bradley Morgan Signed-off-by: Paul E. McKenney Acked-by: GUO Ren --- arch/csky/include/asm/cmpxchg.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/csky/include/asm/cmpxchg.h b/arch/csky/include/asm/cmpxchg.h index db6dda47184e4b..6f3ca79e54acfe 100644 --- a/arch/csky/include/asm/cmpxchg.h +++ b/arch/csky/include/asm/cmpxchg.h @@ -64,6 +64,9 @@ switch (size) { \ case 1: \ __ret = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *)__ptr, (uintptr_t)__old, (uintptr_t)__new); \ + break; \ + case 2: \ + __ret = (__typeof__(*(ptr)))cmpxchg_emu_u16((volatile u16 *)__ptr, (unsigned long)__old, (unsigned long)__new); \ break; \ case 4: \ asm volatile ( \ @@ -97,6 +100,9 @@ switch (size) { \ case 1: \ __ret = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *)__ptr, (uintptr_t)__old, (uintptr_t)__new); \ + break; \ + case 2: \ + __ret = (__typeof__(*(ptr)))cmpxchg_emu_u16((volatile u16 *)__ptr, (unsigned long)__old, (unsigned long)__new); \ break; \ case 4: \ asm volatile ( \ @@ -131,6 +137,9 @@ switch (size) { \ case 1: \ __ret = (__typeof__(*(ptr)))cmpxchg_emu_u8((volatile u8 *)__ptr, (uintptr_t)__old, (uintptr_t)__new); \ + break; \ + case 2: \ + __ret = (__typeof__(*(ptr)))cmpxchg_emu_u16((volatile u16 *)__ptr, (unsigned long)__old, (unsigned long)__new); \ break; \ case 4: \ asm volatile ( \ From 558ccc91ddf98d58ae726ed7ccaf5838f284084f Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Mon, 5 Oct 2026 12:07:00 +0000 Subject: [PATCH 1719/2286] xtensa: Emulate two-byte cmpxchg Xtensa has no two-byte atomic compare and swap, so the __cmpxchg() switch lets case 2 fall through to __cmpxchg_called_with_bad_pointer(), which is declared but never defined, so a two-byte cmpxchg() fails at link time. Route case 2 through the new cmpxchg_emu_u16(), which takes the old and new values as unsigned long and narrows them itself, so the call needs no extra casts. Signed-off-by: Bradley Morgan Reviewed-by: Max Filippov Signed-off-by: Paul E. McKenney --- arch/xtensa/include/asm/cmpxchg.h | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/xtensa/include/asm/cmpxchg.h b/arch/xtensa/include/asm/cmpxchg.h index b6db4838b175ab..8dea8e357fc0f0 100644 --- a/arch/xtensa/include/asm/cmpxchg.h +++ b/arch/xtensa/include/asm/cmpxchg.h @@ -76,6 +76,7 @@ __cmpxchg(volatile void *ptr, unsigned long old, unsigned long new, int size) { switch (size) { case 1: return cmpxchg_emu_u8(ptr, old, new); + case 2: return cmpxchg_emu_u16(ptr, old, new); case 4: return __cmpxchg_u32(ptr, old, new); default: __cmpxchg_called_with_bad_pointer(); return old; From 60df507b60919bdd44ab4ca7352b5ed24808746c Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 18 Aug 2026 17:10:15 -0700 Subject: [PATCH 1720/2286] srcutree: Add an atomic Tree SRCU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some dedicated srcu_struct users have read-side critical sections which are short, never sleep, and never block on anything which may itself depend on memory allocation — because they were, until recently, spinlock or rwlock critical sections. For such a domain the update side can safely wait for readers by spinning, from contexts where sleeping is undesirable or the grace-period machinery's latency (workqueue scheduling, jiffy-paced retries) dominates the actual reader drain time. But that is only safe if every reader keeps the promise. Make the promise explicit and machine-checkable: - srcu_read_lock_atomic() / srcu_read_unlock_atomic() enter the usual (smp_mb-based) read-side critical section with preemption disabled and (except in hardirq, where it is redundant) non_block_start() armed, recording SRCU_READ_FLAVOR_ATOMIC in the per-CPU reader flavor. Disabling preemption enforces the no-sleeping promise on every configuration and bounds the section, so it is always running on some CPU; non_block_start() extends the enforcement to even potentially-sleeping calls on paths which happen not to block. The existing reader-flavor consistency checks complain about any mixing with other flavors. - synchronize_srcu_atomic() waits for all pre-existing readers by repeating the try_synchronize_srcu() both-epoch counter proof with cpu_relax() until it succeeds: no sleeping, no index flip, no grace-period sequence update, and therefore no interaction with concurrent call_srcu(), synchronize_srcu() or srcu_barrier(). It always provides the full grace-period guarantee: if the domain turns out to have had readers of any other flavor — a caller bug, since such a reader may be asleep and spinning on it would be unbounded — it complains and falls back to a real (sleeping) grace period internally, that being the only correct wait for a possibly-sleeping reader. The flavor mask is rechecked on every iteration so a first non-atomic reader appearing mid-spin takes the same path. The immediate motivation is the proposed conversion of KVM's gfn_to_pfn_cache to SRCU¹, whose mmu_notifier invalidation path drains readers before the primary MMU zaps a page. With the readers declared atomic, that drain becomes spin-only: no sleeping at all in the notifier, bounded by the longest reader section, satisfying even the strictest reading of the OOM-reaper non-blocking requirement without needing to touch the non_block_start() annotation². This commit also adds atomic-SRCU-specific initializers: init_srcu_struct_atomic(), DEFINE_SRCU_ATOMIC(), and DEFINE_STATIC_SRCU_ATOMIC(). This commit implements only Tree SRCU. Tiny SRCU will follow. ¹ https://lore.kernel.org/all/20260811132237.102400-1-dwmw2@infradead.org/ ² https://lore.kernel.org/all/20260812134934.GC662699@ziepe.ca/ [ paulmck: Apply Kunwu Chan feedback. ] Co-developed-by: David Woodhouse Signed-off-by: David Woodhouse Assisted-by: Claude:claude-mythos-5 Signed-off-by: Paul E. McKenney --- include/linux/srcu.h | 81 ++++++++++++++++++-- include/linux/srcutree.h | 9 ++- kernel/rcu/srcutree.c | 154 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 227 insertions(+), 17 deletions(-) diff --git a/include/linux/srcu.h b/include/linux/srcu.h index 7d9bc06df98d0b..3f232f2e052446 100644 --- a/include/linux/srcu.h +++ b/include/linux/srcu.h @@ -36,6 +36,8 @@ static inline int __init_srcu_struct(struct srcu_struct *ssp, const char *name, int __init_srcu_struct_fast(struct srcu_struct *ssp, const char *name, struct lock_class_key *key); int __init_srcu_struct_fast_updown(struct srcu_struct *ssp, const char *name, struct lock_class_key *key); +int __init_srcu_struct_atomic(struct srcu_struct *ssp, const char *name, + struct lock_class_key *key); #endif // #ifndef CONFIG_TINY_SRCU #define init_srcu_struct_fast(ssp) \ @@ -52,6 +54,13 @@ int __init_srcu_struct_fast_updown(struct srcu_struct *ssp, const char *name, __init_srcu_struct_fast_updown((ssp), #ssp, &__srcu_key); \ }) +#define init_srcu_struct_atomic(ssp) \ +({ \ + static struct lock_class_key __srcu_key; \ + \ + __init_srcu_struct_atomic((ssp), #ssp, &__srcu_key); \ +}) + #define __SRCU_DEP_MAP_INIT(srcu_name) .dep_map = { .name = #srcu_name }, #else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */ @@ -64,6 +73,7 @@ static inline int __init_srcu_struct(struct srcu_struct *ssp, const char *name, #ifndef CONFIG_TINY_SRCU int init_srcu_struct_fast(struct srcu_struct *ssp); int init_srcu_struct_fast_updown(struct srcu_struct *ssp); +int init_srcu_struct_atomic(struct srcu_struct *ssp); #endif // #ifndef CONFIG_TINY_SRCU #define __SRCU_DEP_MAP_INIT(srcu_name) @@ -77,14 +87,18 @@ int init_srcu_struct_fast_updown(struct srcu_struct *ssp); }) /* Values for SRCU Tree srcu_data ->srcu_reader_flavor, but also used by rcutorture. */ -#define SRCU_READ_FLAVOR_NORMAL 0x1 // srcu_read_lock(). -#define SRCU_READ_FLAVOR_NMI 0x2 // srcu_read_lock_nmisafe(). -// 0x4 // SRCU-lite is no longer with us. -#define SRCU_READ_FLAVOR_FAST 0x4 // srcu_read_lock_fast(), also NMI-safe. -#define SRCU_READ_FLAVOR_FAST_UPDOWN 0x8 // srcu_read_lock_fast_updown(). +#define SRCU_READ_FLAVOR_NORMAL 0x01 // srcu_read_lock(). +#define SRCU_READ_FLAVOR_NMI 0x02 // srcu_read_lock_nmisafe(). +// 0x04 // SRCU-lite is no longer with us. +#define SRCU_READ_FLAVOR_FAST 0x04 // srcu_read_lock_fast(), also NMI-safe. +#define SRCU_READ_FLAVOR_FAST_UPDOWN 0x08 // srcu_read_lock_fast_updown(). +#define SRCU_READ_FLAVOR_ATOMIC 0x10 // srcu_read_lock_atomic(). #define SRCU_READ_FLAVOR_ALL (SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_NMI | \ - SRCU_READ_FLAVOR_FAST | SRCU_READ_FLAVOR_FAST_UPDOWN) + SRCU_READ_FLAVOR_FAST | SRCU_READ_FLAVOR_FAST_UPDOWN | \ + SRCU_READ_FLAVOR_ATOMIC) // All of the above. +#define SRCU_READ_FLAVOR_PREDEF (SRCU_READ_FLAVOR_FAST | SRCU_READ_FLAVOR_ATOMIC) + // Flavors special DEFINE_SRCU() flavors. #define SRCU_READ_FLAVOR_SLOWGP (SRCU_READ_FLAVOR_FAST | SRCU_READ_FLAVOR_FAST_UPDOWN) // Flavors requiring synchronize_rcu() // instead of smp_mb(). @@ -102,6 +116,7 @@ void call_srcu(struct srcu_struct *ssp, struct rcu_head *head, void (*func)(struct rcu_head *head)); void cleanup_srcu_struct(struct srcu_struct *ssp); void synchronize_srcu(struct srcu_struct *ssp); +void synchronize_srcu_atomic(struct srcu_struct *ssp); #define SRCU_GET_STATE_COMPLETED 0x1 @@ -306,6 +321,43 @@ static inline int srcu_read_lock(struct srcu_struct *ssp) return retval; } +/** + * srcu_read_lock_atomic - register a new reader promising an atomic section + * @ssp: srcu_struct in which to register the new reader. + * + * As srcu_read_lock(), but the caller promises that the read-side + * critical section never sleeps and never blocks on anything which + * may itself depend on memory allocation to make progress. Preemption + * is disabled for the duration, which both enforces that promise (any + * sleepable call in the section will splat on every configuration) + * and bounds the section so that the update side may spin rather + * than sleep when waiting for readers: see synchronize_srcu_atomic(). + * + * The lock and matching srcu_read_unlock_atomic() must be invoked on + * the same CPU, from the same context; passing the return value to + * another task is not permitted for this flavor. + */ +static inline int srcu_read_lock_atomic(struct srcu_struct *ssp) + __acquires_shared(ssp) +{ + int retval; + + preempt_disable(); + /* + * Arm might_sleep() to catch even a *potentially* sleeping call + * in the section, not just an actual schedule: the atomic-domain + * promise must hold on every path, contended or not. In hardirq + * the annotation would land on the interrupted task; it is also + * redundant there, so skip it. + */ + if (!in_hardirq()) + non_block_start(); + srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_ATOMIC); + retval = __srcu_read_lock(ssp); + srcu_lock_acquire(&ssp->dep_map); + return retval; +} + /** * srcu_read_lock_fast - register a new reader for an SRCU-protected structure. * @ssp: srcu_struct in which to register the new reader. @@ -498,6 +550,23 @@ static inline void srcu_read_unlock(struct srcu_struct *ssp, int idx) __srcu_read_unlock(ssp, idx); } +/** + * srcu_read_unlock_atomic - unregister an atomic-section reader + * @ssp: srcu_struct from which to unregister the old reader. + * @idx: return value from corresponding srcu_read_lock_atomic(). + */ +static inline void srcu_read_unlock_atomic(struct srcu_struct *ssp, int idx) + __releases_shared(ssp) +{ + WARN_ON_ONCE(idx & ~0x1); + srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_ATOMIC); + srcu_lock_release(&ssp->dep_map); + __srcu_read_unlock(ssp, idx); + if (!in_hardirq()) + non_block_end(); + preempt_enable(); +} + /** * srcu_read_unlock_fast - unregister a old reader from an SRCU-protected structure. * @ssp: srcu_struct in which to unregister the old reader. diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h index 1ce759fb70948b..ad9d9658b0a2f5 100644 --- a/include/linux/srcutree.h +++ b/include/linux/srcutree.h @@ -80,6 +80,7 @@ struct srcu_usage { struct mutex srcu_cb_mutex; /* Serialize CB preparation. */ raw_spinlock_t __private lock; /* Protect counters and size state. */ struct mutex srcu_gp_mutex; /* Serialize GP work. */ + atomic_t srcu_atomic_gp_flag; /* Serialize atomic GP work. */ unsigned long srcu_gp_seq; /* Grace-period seq #. */ unsigned long srcu_gp_seq_needed; /* Latest gp_seq needed. */ unsigned long srcu_gp_seq_needed_exp; /* Furthest future exp GP. */ @@ -229,14 +230,16 @@ struct srcu_struct { is_static struct srcu_struct name = \ __SRCU_STRUCT_INIT(name, name##_srcu_usage, name##_srcu_data, fast) #endif -#define DEFINE_SRCU(name) __DEFINE_SRCU(name, 0, /* not static */) +#define DEFINE_SRCU(name) __DEFINE_SRCU(name, 0, /* !static */) #define DEFINE_STATIC_SRCU(name) __DEFINE_SRCU(name, 0, static) -#define DEFINE_SRCU_FAST(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_FAST, /* not static */) +#define DEFINE_SRCU_FAST(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_FAST, /* !static */) #define DEFINE_STATIC_SRCU_FAST(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_FAST, static) #define DEFINE_SRCU_FAST_UPDOWN(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_FAST_UPDOWN, \ - /* not static */) + /* !static */) #define DEFINE_STATIC_SRCU_FAST_UPDOWN(name) \ __DEFINE_SRCU(name, SRCU_READ_FLAVOR_FAST_UPDOWN, static) +#define DEFINE_SRCU_ATOMIC(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_ATOMIC, /* !static */) +#define DEFINE_STATIC_SRCU_ATOMIC(name) __DEFINE_SRCU(name, SRCU_READ_FLAVOR_ATOMIC, static) int __srcu_read_lock(struct srcu_struct *ssp) __acquires_shared(ssp); void synchronize_srcu_expedited(struct srcu_struct *ssp); diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 0292d3f9a83aa5..7ff45e992c0110 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -253,6 +253,7 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static, bool ssp->srcu_sup->node = NULL; mutex_init(&ssp->srcu_sup->srcu_cb_mutex); mutex_init(&ssp->srcu_sup->srcu_gp_mutex); + atomic_set(&ssp->srcu_sup->srcu_atomic_gp_flag, 0); ssp->srcu_sup->srcu_gp_seq = SRCU_GP_SEQ_INITIAL_VAL; ssp->srcu_sup->srcu_barrier_seq = 0; mutex_init(&ssp->srcu_sup->srcu_barrier_mutex); @@ -330,6 +331,13 @@ int __init_srcu_struct_fast_updown(struct srcu_struct *ssp, const char *name, } EXPORT_SYMBOL_GPL(__init_srcu_struct_fast_updown); +int __init_srcu_struct_atomic(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) +{ + ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_ATOMIC; + return __init_srcu_struct_common(ssp, name, key); +} +EXPORT_SYMBOL_GPL(__init_srcu_struct_atomic); + #else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */ /** @@ -385,6 +393,26 @@ int init_srcu_struct_fast_updown(struct srcu_struct *ssp) } EXPORT_SYMBOL_GPL(init_srcu_struct_fast_updown); +/** + * init_srcu_struct_atomic - initialize an atomic sleep-RCU structure + * @ssp: structure to initialize. + * + * Use this in place of DEFINE_SRCU_ATOMIC() and DEFINE_STATIC_SRCU_ATOMIC() + * for non-static srcu_struct structures that are to be passed to + * srcu_read_lock_atomic() and friends. It is necessary to invoke this on a + * given srcu_struct before passing that srcu_struct to any other function. + * Each srcu_struct represents a separate domain of SRCU protection. + * + * And yes, we really are defining a sleepable RCU implementation that + * cannot sleep. Strange universe we live in, isn't it? + */ +int init_srcu_struct_atomic(struct srcu_struct *ssp) +{ + ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_ATOMIC; + return init_srcu_struct_fields(ssp, false, false); +} +EXPORT_SYMBOL_GPL(init_srcu_struct_atomic); + #endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */ /* @@ -802,7 +830,10 @@ void __srcu_check_read_flavor(struct srcu_struct *ssp, int read_flavor) WARN_ON_ONCE(ssp->srcu_reader_flavor && read_flavor != ssp->srcu_reader_flavor); WARN_ON_ONCE(old_read_flavor && ssp->srcu_reader_flavor && old_read_flavor != ssp->srcu_reader_flavor); - WARN_ON_ONCE(read_flavor == SRCU_READ_FLAVOR_FAST && !ssp->srcu_reader_flavor); + WARN_ON_ONCE(!!(read_flavor & SRCU_READ_FLAVOR_PREDEF) && + read_flavor != ssp->srcu_reader_flavor); + WARN_ON_ONCE(!!(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_PREDEF) && + read_flavor != ssp->srcu_reader_flavor); if (!old_read_flavor) { old_read_flavor = cmpxchg(&sdp->srcu_reader_flavor, 0, read_flavor); if (!old_read_flavor) @@ -942,7 +973,7 @@ static void srcu_schedule_cbs_snp(struct srcu_struct *ssp, struct srcu_node *snp * are initiating callback invocation. This allows the ->srcu_have_cbs[] * array to have a finite number of elements. */ -static void srcu_gp_end(struct srcu_struct *ssp) +static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) { unsigned long cbdelay = 1; bool cbs; @@ -958,7 +989,8 @@ static void srcu_gp_end(struct srcu_struct *ssp) struct srcu_usage *sup = ssp->srcu_sup; /* Prevent more than one additional grace period. */ - mutex_lock(&sup->srcu_cb_mutex); + if (!is_atomic) + mutex_lock(&sup->srcu_cb_mutex); /* End the current grace period. */ raw_spin_lock_irq_rcu_node(sup); @@ -973,7 +1005,8 @@ static void srcu_gp_end(struct srcu_struct *ssp) if (ULONG_CMP_LT(sup->srcu_gp_seq_needed_exp, gpseq)) WRITE_ONCE(sup->srcu_gp_seq_needed_exp, gpseq); raw_spin_unlock_irq_rcu_node(sup); - mutex_unlock(&sup->srcu_gp_mutex); + if (!is_atomic) + mutex_unlock(&sup->srcu_gp_mutex); /* A new grace period can start at this point. But only one. */ /* Initiate callback invocation as needed. */ @@ -1018,13 +1051,15 @@ static void srcu_gp_end(struct srcu_struct *ssp) } /* Callback initiation done, allow grace periods after next. */ - mutex_unlock(&sup->srcu_cb_mutex); + if (!is_atomic) + mutex_unlock(&sup->srcu_cb_mutex); /* Start a new grace period if needed. */ raw_spin_lock_irq_rcu_node(sup); gpseq = rcu_seq_current(&sup->srcu_gp_seq); if (!rcu_seq_state(gpseq) && ULONG_CMP_LT(gpseq, sup->srcu_gp_seq_needed)) { + WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); raw_spin_unlock_irq_rcu_node(sup); srcu_reschedule(ssp, 0); @@ -1148,6 +1183,7 @@ static void srcu_funnel_gp_start(struct srcu_struct *ssp, struct srcu_data *sdp, /* If grace period not already in progress, start it. */ if (!WARN_ON_ONCE(rcu_seq_done(&sup->srcu_gp_seq, s)) && rcu_seq_state(sup->srcu_gp_seq) == SRCU_STATE_IDLE) { + WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); // And how can that list_add() in the "else" clause @@ -1482,6 +1518,8 @@ static void srcu_do_enqueue(struct srcu_struct *ssp, struct rcu_head *rhp, static void __call_srcu(struct srcu_struct *ssp, struct rcu_head *rhp, rcu_callback_t func, bool do_norm) { + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) + return; // Leak the callback rather than corrupt SRCU state. if (should_rcu_defer()) { struct srcu_defer *sndp = this_cpu_ptr(&srcu_defer); struct srcu_data *sdp; @@ -1621,6 +1659,11 @@ static void __synchronize_srcu(struct srcu_struct *ssp, bool do_norm) if (rcu_scheduler_active == RCU_SCHEDULER_INACTIVE) return; + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) { + // This works, and exposes a possible bug. + synchronize_srcu_atomic(ssp); + return; + } might_sleep(); check_init_srcu_struct(ssp, false); init_completion(&rcu.completion); @@ -1741,10 +1784,21 @@ EXPORT_SYMBOL_GPL(get_state_synchronize_srcu); * period has elapsed in the meantime. Unlike get_state_synchronize_srcu(), * this function also ensures that any needed SRCU grace period will be * started. This convenience does come at a cost in terms of CPU overhead. + * + * This function cannot be used with atomic SRCU, which only has + * atomic grace periods. Give a warning if someone tries, and return + * the same cookie that would have been returned, but refrain from + * messing up state by starting a grace period. If someone somewhere + * somehow invokes synchronize_srcu_atomic(), passing this cookie to + * poll_state_synchronize_srcu() will return true. If no one ever invokes + * synchronize_srcu_atomic(), too bad. */ unsigned long start_poll_synchronize_srcu(struct srcu_struct *ssp) { - return srcu_gp_start_if_needed(ssp, NULL, true); + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) + return get_state_synchronize_srcu(ssp); + else + return srcu_gp_start_if_needed(ssp, NULL, true); } EXPORT_SYMBOL_GPL(start_poll_synchronize_srcu); @@ -1833,6 +1887,12 @@ void srcu_barrier(struct srcu_struct *ssp) unsigned long s; check_init_srcu_struct(ssp, false); + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) { + // There shouldn't be any callbacks for atomic SRCU, + // but just in case. + schedule_timeout_uninterruptible(HZ/10); + return; + } /* * Register any deferred callbacks before snapshotting the sequence. The @@ -1904,6 +1964,9 @@ static void srcu_expedite_current_cb(struct rcu_head *rhp) * no current grace period, one might be created. If the current grace * period is currently sleeping, that sleep will complete before expediting * will take effect. + * + * This function must not be invoked on srcu_struct structures that are + * used with srcu_read_lock_atomic() and synchronize_srcu_atomic(). */ void srcu_expedite_current(struct srcu_struct *ssp) { @@ -1911,6 +1974,9 @@ void srcu_expedite_current(struct srcu_struct *ssp) bool needcb = false; struct srcu_data *sdp; + // Atomic SRCU has no callbacks, so there is nothing to expedite. + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) + return; migrate_disable(); sdp = this_cpu_ptr(ssp->sda); raw_spin_lock_irqsave_sdp_contention(sdp, &flags); @@ -1978,8 +2044,10 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) return; } idx = rcu_seq_state(READ_ONCE(ssp->srcu_sup->srcu_gp_seq)); - if (idx == SRCU_STATE_IDLE) + if (idx == SRCU_STATE_IDLE) { + WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); + } raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); if (idx != SRCU_STATE_IDLE) { if (!is_atomic) @@ -2015,9 +2083,78 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) return; /* readers present, retry later. */ } ssp->srcu_sup->srcu_n_exp_nodelay = 0; - srcu_gp_end(ssp); /* Releases ->srcu_gp_mutex. */ + srcu_gp_end(ssp, is_atomic); /* Releases ->srcu_gp_mutex. */ + } +} + +/** + * synchronize_srcu_atomic - spin for prior SRCU read-side critical-section completion + * @ssp: srcu_struct with which to synchronize. + * + * Similar to synchronize_srcu(), but spins rather than blocking. + * Use only with srcu_read_lock_atomic() and srcu_read_unlock_atomic(), + * which are forbidden from voluntarily context switching. If + * synchronize_srcu_atomic() is invoked from a more restrictive context + * (for example, interrupts disabled) for a given srcu_struct structure, + * then for that structure, all calls to both srcu_read_lock_atomic() + * and srcu_read_unlock_atomic() must be invoked from that same context, + * or one that is even more strict. + * + * If synchronize_srcu_atomic() is invoked on a given srcu_struct + * structure, then none of call_srcu(), synchronize_srcu(), + * synchronize_srcu_expedited(), start_poll_synchronize_srcu(), + * srcu_barrier(), or srcu_expedite_current() may be invoked on that + * same structure. + * + * Because synchronize_srcu_atomic() is even more expedited than is + * synchronize_srcu_expedited(), there is no expedited counterpart to + * this function. + */ +void synchronize_srcu_atomic(struct srcu_struct *ssp) +{ + unsigned long srcu_state; + struct srcu_usage *sup = ssp->srcu_sup; + + // Initialize. Either init_srcu_struct() was invoked or + // DEFINE_SRCU() or similar was used. Therefore, no allocation + // will be done here. + check_init_srcu_struct(ssp, true); + srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_ATOMIC); + + // Perhaps others will do our work for us. + srcu_state = get_state_synchronize_srcu(ssp); + while (atomic_read(&sup->srcu_atomic_gp_flag) || + atomic_xchg(&sup->srcu_atomic_gp_flag, 1)) { + if (poll_state_synchronize_srcu(ssp, srcu_state)) + return; + cpu_relax(); + } + + // One last check for others doing our work for us under the lock. + raw_spin_lock_irq_rcu_node(sup); + if (poll_state_synchronize_srcu(ssp, srcu_state)) { + raw_spin_unlock_irq_rcu_node(sup); + atomic_set(&sup->srcu_atomic_gp_flag, 0); + return; + } + + // OK, we really have to do it ourselves. Start the grace period. + non_block_start(); // We must not voluntarily block! + smp_store_release(&sup->srcu_gp_seq_needed, srcu_state); // See srcu_funnel_gp_start(). + ASSERT_EXCLUSIVE_WRITER(ssp->srcu_sup->srcu_gp_seq); + srcu_gp_start(ssp); + raw_spin_unlock_irq_rcu_node(sup); + + // Wait for it to complete, helping it along. + while (!poll_state_synchronize_srcu(ssp, srcu_state)) { + cpu_relax(); + srcu_advance_state(ssp, true); } + ASSERT_EXCLUSIVE_WRITER(sup->srcu_atomic_gp_flag); + atomic_set_release(&sup->srcu_atomic_gp_flag, 0); + non_block_end(); } +EXPORT_SYMBOL_GPL(synchronize_srcu_atomic); /* * Invoke a limited number of SRCU callbacks that have passed through @@ -2098,6 +2235,7 @@ static void srcu_reschedule(struct srcu_struct *ssp, unsigned long delay) } } else if (!rcu_seq_state(ssp->srcu_sup->srcu_gp_seq)) { /* Outstanding request and no GP. Start one. */ + WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); } raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); From a48669f0c49c4074079f5d96478a3d47f66135fd Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Tue, 18 Aug 2026 17:10:15 -0700 Subject: [PATCH 1721/2286] srcutiny: Add an atomic Tiny SRCU This commit adds the Tiny SRCU counterpart to Tree SRCU's synchronize_srcu_atomic(). One might hope that this could be as trivial as Tiny RCU's synchronize_rcu(), and there was a time when it would have been. But lazy preemption really can preempt an SRCU read-side critical section, which means that synchronize_srcu_atomic() really must be prepared to spin waiting for it. This spinning currently consists of cond_resched_tasks_rcu_qs() and cpu_relax(). It would be better to have some way of telling the scheduler that there is nothing useful for us to do. We cannot use the traditional wait_event() approach because synchronize_srcu_atomic() is not permitted to block. [ paulmck: Apply Kunwu Chan and Joel Fernandes feedback. ] Co-developed-by: David Woodhouse Signed-off-by: David Woodhouse Assisted-by: Claude:claude-mythos-5 Signed-off-by: Paul E. McKenney --- include/linux/srcutiny.h | 6 +++ kernel/rcu/srcutiny.c | 80 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h index 85b5de438450b3..47a368f945e349 100644 --- a/include/linux/srcutiny.h +++ b/include/linux/srcutiny.h @@ -19,6 +19,7 @@ struct srcu_struct { short srcu_lock_nesting[2]; /* srcu_read_lock() nesting depth. */ u8 srcu_gp_running; /* GP workqueue running? */ u8 srcu_gp_waiting; /* GP waiting for readers? */ + u8 srcu_atomic_gp_flag; /* Serialize atomic GP work.*/ unsigned long srcu_idx; /* Current reader array element in bit 0x2. */ unsigned long srcu_idx_max; /* Furthest future srcu_idx request. */ struct swait_queue_head srcu_wq; @@ -64,15 +65,20 @@ void srcu_defer_drain(struct irq_work *irq_work); #define DEFINE_SRCU_FAST_UPDOWN(name) DEFINE_SRCU(name) #define DEFINE_STATIC_SRCU_FAST_UPDOWN(name) \ static struct srcu_struct name = __SRCU_STRUCT_INIT(name, name, name, name) +#define DEFINE_SRCU_ATOMIC(name) DEFINE_SRCU(name) +#define DEFINE_STATIC_SRCU_ATOMIC(name) \ + static struct srcu_struct name = __SRCU_STRUCT_INIT(name, name, name, name) // Dummy structure for srcu_notifier_head. struct srcu_usage { }; #define __SRCU_USAGE_INIT(name) { } #define __init_srcu_struct_fast __init_srcu_struct #define __init_srcu_struct_fast_updown __init_srcu_struct +#define __init_srcu_struct_atomic __init_srcu_struct #ifndef CONFIG_DEBUG_LOCK_ALLOC #define init_srcu_struct_fast init_srcu_struct #define init_srcu_struct_fast_updown init_srcu_struct +#define init_srcu_struct_atomic init_srcu_struct #endif // #ifndef CONFIG_DEBUG_LOCK_ALLOC void synchronize_srcu(struct srcu_struct *ssp); diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c index 32b37d63d58aa4..08e089e3e969e6 100644 --- a/kernel/rcu/srcutiny.c +++ b/kernel/rcu/srcutiny.c @@ -41,6 +41,7 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp) ssp->srcu_cb_tail = &ssp->srcu_cb_head; ssp->srcu_gp_running = false; ssp->srcu_gp_waiting = false; + ssp->srcu_atomic_gp_flag = 0; ssp->srcu_idx = 0; ssp->srcu_idx_max = 0; INIT_WORK(&ssp->srcu_work, srcu_drive_gp); @@ -339,6 +340,80 @@ void synchronize_srcu(struct srcu_struct *ssp) } EXPORT_SYMBOL_GPL(synchronize_srcu); +/* + * synchronize_srcu_atomic - spinning grace period for atomic-reader domains + * @ssp: srcu_struct with which to synchronize. + * + * On !SMP this cannot spin: a reader observed mid-section is preempted + * or interrupted-out, and can only finish if we yield the CPU. But it + * is also never needed: an atomic-flavor reader (preemption disabled) + * cannot be observed mid-section from process context on the sole CPU. + * So a reader observed here has broken the atomic-domain promise, and + * the only correct wait for it is a real grace period. + * + * (Actual kernel-doc header is in Tree SRCU.) + */ +void synchronize_srcu_atomic(struct srcu_struct *ssp) +{ + int idx; + bool ret; + unsigned long srcu_state = get_state_synchronize_srcu(ssp); + + srcu_lock_sync(&ssp->dep_map); + + if (IS_ENABLED(CONFIG_PREEMPTION)) + synchronize_rcu(); // Needed for RCU Tasks Trace to imply RCU grace period. + // And in Tiny RCU, it is near zero cost and doesn't block. + + // Usually, there will be no readers. + preempt_disable(); // Guard against lazy preemption and some other grace period. + ret = !READ_ONCE(ssp->srcu_lock_nesting[0]) && !READ_ONCE(ssp->srcu_lock_nesting[1]); + if (ret) { + WRITE_ONCE(ssp->srcu_idx_max, ssp->srcu_idx + 2); + WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 2); + preempt_enable(); + return; + } + + // Wait to drive a grace period or for someone else to do it + // for us while we are lazily preempted. + while (ssp->srcu_atomic_gp_flag) { + if (poll_state_synchronize_srcu(ssp, srcu_state)) { + preempt_enable(); + return; + } + preempt_enable(); + cpu_relax(); + cond_resched_tasks_rcu_qs(); + preempt_disable(); + } + ssp->srcu_atomic_gp_flag = 1; // Acquire SRCU grace-period "lock". + preempt_enable(); + + // We get here if a reader has been lazily preempted. + // First, wait for old readers, which are quite unlikely. + WRITE_ONCE(ssp->srcu_idx_max, get_state_synchronize_srcu(ssp)); + idx = !(((READ_ONCE(ssp->srcu_idx) + 1) & 0x2) >> 1); + while (READ_ONCE(ssp->srcu_lock_nesting[idx])) { + cond_resched_tasks_rcu_qs(); + cpu_relax(); + } + + // Next, flip the index and wait for the other group of readers. + WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 1); + idx = !idx; + while (READ_ONCE(ssp->srcu_lock_nesting[idx])) { + cond_resched_tasks_rcu_qs(); + cpu_relax(); + } + + // Finally, flip the index again for poll_state_synchronize_srcu(). + WRITE_ONCE(ssp->srcu_idx, ssp->srcu_idx + 1); + WARN_ON_ONCE(!poll_state_synchronize_srcu(ssp, srcu_state)); + ssp->srcu_atomic_gp_flag = 0; // Release SRCU grace-period "lock". +} +EXPORT_SYMBOL_GPL(synchronize_srcu_atomic); + /* Register any deferred callbacks, then wait for all in-flight ones. */ void srcu_barrier(struct srcu_struct *ssp) { @@ -367,6 +442,11 @@ EXPORT_SYMBOL_GPL(get_state_synchronize_srcu); * The difference between this and get_state_synchronize_srcu() is that * this function ensures that the poll_state_synchronize_srcu() will * eventually return the value true. + * + * This function cannot be used with atomic SRCU, which only has + * atomic grace periods. Doing so will silently corrupt internal + * SRCU state. Tree SRCU has appropriate checking with splats, + * so please test with CONFIG_SMP=y as well as CONFIG_SMP=n. */ unsigned long start_poll_synchronize_srcu(struct srcu_struct *ssp) { From 7fb2388e0da08cd1b3591b651736ce43144d3e6d Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Fri, 21 Aug 2026 16:30:31 -0700 Subject: [PATCH 1722/2286] rcutorture: Add support for testing synchronize_srcu_atomic() This commit adds support for the value 0x10 for reader_flavor, which specifies SRCU_READ_FLAVOR_ATOMIC, that is, srcu_read_lock_atomic(), srcu_read_unlock_atomic(), and synchronize_srcu_atomic(). [ paulmck: Apply Kunwu Chan feedback. ] Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 46 +++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 4d1be2a49f011b..182d47975efd18 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -710,10 +710,21 @@ static struct rcu_torture_ops rcu_busted_ops = { DEFINE_STATIC_SRCU(srcu_ctl); DEFINE_STATIC_SRCU_FAST(srcu_ctlf); DEFINE_STATIC_SRCU_FAST_UPDOWN(srcu_ctlfud); +DEFINE_STATIC_SRCU_ATOMIC(srcu_ctla); static struct srcu_struct srcu_ctld; static struct srcu_struct *srcu_ctlp = &srcu_ctl; static struct rcu_torture_ops srcud_ops; +// Restrict APIs permitted for atomic SRCU. +static void srcu_torture_init_forbidden_apis(void) +{ + cur_ops->call = NULL; + cur_ops->cb_barrier = NULL; + cur_ops->deferred_free = NULL; + cur_ops->exp_current = NULL; + cur_ops->start_gp_poll = NULL; +} + static void srcu_torture_init(void) { rcu_sync_torture_init(); @@ -729,6 +740,11 @@ static void srcu_torture_init(void) srcu_ctlp = &srcu_ctlfud; VERBOSE_TOROUT_STRING("srcu_torture_init fast-up/down SRCU"); } + if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) { + srcu_ctlp = &srcu_ctla; + VERBOSE_TOROUT_STRING("srcu_torture_init atomic SRCU"); + srcu_torture_init_forbidden_apis(); + } } static void srcu_get_gp_data(int *flags, unsigned long *gp_seq) @@ -766,6 +782,11 @@ static int srcu_torture_read_lock(void) WARN_ON_ONCE(idx & ~0x1); ret += idx << 3; } + if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) { + idx = srcu_read_lock_atomic(srcu_ctlp); + WARN_ON_ONCE(idx & ~0x1); + ret += idx << 4; + } return ret; } @@ -785,7 +806,8 @@ srcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp) delay = torture_random(rrsp) % (nrealreaders * 2 * longdelay * uspertick); - if (!delay && !in_atomic() && !rcu_preempt_depth() && !irqs_disabled()) { + if (!delay && !in_atomic() && !rcu_preempt_depth() && !irqs_disabled() && + !(reader_flavor & SRCU_READ_FLAVOR_ATOMIC)) { schedule_timeout_interruptible(longdelay); rtrsp->rt_delay_jiffies = longdelay; } else { @@ -796,6 +818,8 @@ srcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp) static void srcu_torture_read_unlock(int idx) { WARN_ON_ONCE((reader_flavor && (idx & ~reader_flavor)) || (!reader_flavor && (idx & ~0x1))); + if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) + srcu_read_unlock_atomic(srcu_ctlp, (idx & 0x10) >> 4); if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) srcu_read_unlock_fast_updown(srcu_ctlp, __srcu_ctr_to_ptr(srcu_ctlp, (idx & 0x8) >> 3)); @@ -875,7 +899,10 @@ static void srcu_torture_deferred_free(struct rcu_torture *rp) static void srcu_torture_synchronize(void) { - synchronize_srcu(srcu_ctlp); + if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) + synchronize_srcu_atomic(srcu_ctlp); + else + synchronize_srcu(srcu_ctlp); } static unsigned long srcu_torture_get_gp_state(void) @@ -911,7 +938,10 @@ static void srcu_torture_stats(void) static void srcu_torture_synchronize_expedited(void) { - synchronize_srcu_expedited(srcu_ctlp); + if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) + synchronize_srcu_atomic(srcu_ctlp); + else + synchronize_srcu_expedited(srcu_ctlp); } static void srcu_torture_expedite_current(void) @@ -969,6 +999,10 @@ static void srcud_torture_init(void) } else if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) { WARN_ON(init_srcu_struct_fast_updown(&srcu_ctld)); VERBOSE_TOROUT_STRING("srcud_torture_init fast-up/down SRCU"); + } else if (reader_flavor & SRCU_READ_FLAVOR_ATOMIC) { + WARN_ON(init_srcu_struct_atomic(&srcu_ctld)); + VERBOSE_TOROUT_STRING("srcud_torture_init atomic SRCU"); + srcu_torture_init_forbidden_apis(); } else { WARN_ON(init_srcu_struct(&srcu_ctld)); } @@ -1749,7 +1783,7 @@ rcu_torture_writer(void *arg) pr_alert("%s" TORTURE_FLAG " Waited %lu jiffies for boot to complete.\n", torture_type, jiffies - j); - if (IS_ENABLED(CONFIG_RCU_LAZY)) + if (IS_ENABLED(CONFIG_RCU_LAZY) && cur_ops->call) INIT_WORK_ONSTACK(&lazy_work, rcu_torture_writer_work); do { @@ -1944,7 +1978,7 @@ rcu_torture_writer(void *arg) !rcu_gp_is_normal(); } rcu_torture_writer_state = RTWS_STUTTER; - if (IS_ENABLED(CONFIG_RCU_LAZY)) + if (IS_ENABLED(CONFIG_RCU_LAZY) && cur_ops->call) queue_work(system_percpu_wq, &lazy_work); stutter_waited = stutter_wait("rcu_torture_writer"); if (stutter_waited && @@ -1977,7 +2011,7 @@ rcu_torture_writer(void *arg) " Dynamic grace-period expediting was disabled.\n", torture_type); - if (IS_ENABLED(CONFIG_RCU_LAZY)) { + if (IS_ENABLED(CONFIG_RCU_LAZY) && cur_ops->call) { cancel_work_sync(&lazy_work); destroy_work_on_stack(&lazy_work); } From 18c55d888b0afa3d8156b812e76c3d29a1bb2a2d Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 26 Aug 2026 09:03:57 -0700 Subject: [PATCH 1723/2286] srcutree: Disable preemption across synchronize_srcu_atomic() Because synchronize_srcu_atomic() cannot sleep, if a pair of them run concurrently pinned to the same CPU, it is possible that one will spin uselessly waiting for the other while at the same time preventing that other from running. This commit therefore disables preemption to prevent this failure mode. Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 7ff45e992c0110..672fb41345c91b 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -2123,11 +2123,14 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) // Perhaps others will do our work for us. srcu_state = get_state_synchronize_srcu(ssp); + preempt_disable(); while (atomic_read(&sup->srcu_atomic_gp_flag) || atomic_xchg(&sup->srcu_atomic_gp_flag, 1)) { + preempt_enable(); if (poll_state_synchronize_srcu(ssp, srcu_state)) return; cpu_relax(); + preempt_disable(); } // One last check for others doing our work for us under the lock. @@ -2135,6 +2138,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) if (poll_state_synchronize_srcu(ssp, srcu_state)) { raw_spin_unlock_irq_rcu_node(sup); atomic_set(&sup->srcu_atomic_gp_flag, 0); + preempt_enable(); return; } @@ -2152,6 +2156,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) } ASSERT_EXCLUSIVE_WRITER(sup->srcu_atomic_gp_flag); atomic_set_release(&sup->srcu_atomic_gp_flag, 0); + preempt_enable(); non_block_end(); } EXPORT_SYMBOL_GPL(synchronize_srcu_atomic); From f8d95888093348be1235815946359f9cbc2f7012 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Fri, 21 Aug 2026 20:38:09 +0200 Subject: [PATCH 1724/2286] srcu: Use IRQ_WORK_INIT_HARD for srcu's irq_work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The irq_work in srcu is used to schedule a delayed work. The work item is not scheduled directly because it is not always possible wake a thread directly. On PREEMPT_RT the default irq_work is initialized with IRQ_WORK_LAZY and is delayed to the irq_work thread. A system booted with the command line "trace_event=…" will freeze during boot because the irq_work thread is not yet deployed (and tracing uses synchronize_srcu() in tp_rcu_cond_sync()). The irq_work performs just a wakeup a thread, there is nothing wrong with doing this from hardirq context on PREEMPT_RT. Use IRQ_WORK_INIT_HARD for srcu's irq_work. Fixes: 7c405fb3279b3 ("rcu: Use an intermediate irq_work to start process_srcu()") Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 672fb41345c91b..64081c8eacc84a 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -259,7 +259,12 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static, bool mutex_init(&ssp->srcu_sup->srcu_barrier_mutex); atomic_set(&ssp->srcu_sup->srcu_barrier_cpu_cnt, 0); INIT_DELAYED_WORK(&ssp->srcu_sup->work, process_srcu); - init_irq_work(&ssp->srcu_sup->irq_work, srcu_irq_work); + /* + * trace events started on the command line require SRCU before + * the irq_work kthread starts. Since all it does is a simple + * wakeup, having it as a hard irq, even on PREEMPT_RT is fine. + */ + ssp->srcu_sup->irq_work = IRQ_WORK_INIT_HARD(srcu_irq_work); ssp->srcu_sup->sda_is_static = is_static; if (!is_static) { ssp->sda = alloc_percpu(struct srcu_data); From 494fa5fcb1de0a1d3b7f864db35877ad552f39ad Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 2 Sep 2026 08:30:38 -0700 Subject: [PATCH 1725/2286] srcutree: Warn if Tiny SRCU readers are preempted The fastpath of synchronize_srcu_atomic() should always be taken because readers always disable preemption. Therefore, the fact that synchronize_srcu_atomic() is running at all should mean that all readers have ended. But there are always bugs. And responding to a usage bug with a too-short SRCU grace period, and thus possibly corrupting memory is at best a sadistic response so such a bug. For this reason, synchronize_srcu_atomic() explicitly waits for readers. Except that it does so silently, possibly failing to flag this bug. This commit therefore adds a splat if synchronize_srcu_atomic() fails to take the early exit. Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutiny.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c index 08e089e3e969e6..a31398def0b189 100644 --- a/kernel/rcu/srcutiny.c +++ b/kernel/rcu/srcutiny.c @@ -375,6 +375,14 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) return; } + // Because readers disable preemption, we should never get here. + // However, a splat and some spinning is usually preferable to + // memory corruption due to a too-short grace period. There is + // the possibility that this will hang if the preempted reader is + // not looked upon favorably by the scheduler, but this is still + // preferable to memory corruption. + WARN_ON_ONCE(1); + // Wait to drive a grace period or for someone else to do it // for us while we are lazily preempted. while (ssp->srcu_atomic_gp_flag) { From 06726ed5906d6a403a79e78f80a428ffcbe0bbb4 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Wed, 2 Sep 2026 12:58:24 -0700 Subject: [PATCH 1726/2286] srcutree: Explicitly note DEFINE_SRCU() needs for srcu_barrier() In the core kernel, an srcu_struct structure created by DEFINE_SRCU() or friends lives as long as the kernel does, so there are no particular requirements surrounding the end of that structure's life. In contrast, when DEFINE_SRCU() and friends are used within a module, the corresponding srcu_struct structures' lifetimes end when that module exits. This in turn means that if such a structure was passed to call_srcu(), then srcu_barrier() must be invoked after the last call_srcu() invocation but before the module exits. This commit therefore adds a comment stating this. Reported-by: Alexander Aring Signed-off-by: Paul E. McKenney --- include/linux/srcutree.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h index ad9d9658b0a2f5..93b76e54389432 100644 --- a/include/linux/srcutree.h +++ b/include/linux/srcutree.h @@ -214,6 +214,12 @@ struct srcu_struct { * instead of smp_mb(), and given that the first (for example) * srcu_read_lock_fast() might race with the first synchronize_srcu(), * this different must be specified at initialization time. + * + * If you use any of the DEFINE_SRCU() functions within a module, the + * module-entry code will invoke init_srcu_struct() and the module-exit + * code will invoke cleanup_srcu_struct(). This means that if your module + * passes the resulting srcu_struct structure to call_srcu(), you will + * need to also pass this structure to srcu_barrier() prior to module exit. */ #ifdef MODULE # define __DEFINE_SRCU(name, fast, is_static) \ From ddd326d8f36d6943a510e93ddf16c43037fef1c1 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:20 +0800 Subject: [PATCH 1727/2286] rcutorture: Add atomic-SRCU support to torture.sh Add the --do-atomic-srcu argument to torture.sh, which runs the SRCU-N, SRCU-P, and SRCU-T scenarios, thus covering both Tree SRCU (SRCU-N and SRCU-P) and Tiny SRCU (SRCU-T), with rcutorture.reader_flavor=0x10 appended to the boot parameters so that it takes precedence over each scenario's own reader-flavor setting. This exercises srcu_read_lock_atomic(), srcu_read_unlock_atomic(), and synchronize_srcu_atomic(). As with other torture.sh tests, the --do-kcsan argument runs a KCSAN+PROVE_LOCKING variant of this test. [ paulmck: Make --do-atomic-srcu be default-on. ] Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- .../selftests/rcutorture/bin/torture.sh | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/testing/selftests/rcutorture/bin/torture.sh b/tools/testing/selftests/rcutorture/bin/torture.sh index f0083891ee8147..e7b578ec152f16 100755 --- a/tools/testing/selftests/rcutorture/bin/torture.sh +++ b/tools/testing/selftests/rcutorture/bin/torture.sh @@ -68,6 +68,7 @@ do_clocksourcewd="${ifnotaarch64}" do_rt=yes do_rcutasksflavors="${ifnotaarch64}" # FIXME: Back to "yes" when SMP=n auto-avoided do_srcu_lockdep=yes +do_atomic_srcu=yes do_rcu_rust=no # doyesno - Helper function for yes/no arguments @@ -103,6 +104,7 @@ usage () { echo " --do-rcu-rust / --do-no-rcu-rust / --no-rcu-rust" echo " --do-scftorture / --do-no-scftorture / --no-scftorture" echo " --do-srcu-lockdep / --do-no-srcu-lockdep / --no-srcu-lockdep" + echo " --do-atomic-srcu / --do-no-atomic-srcu / --no-atomic-srcu" echo " --duration [ | h | d ]" echo " --guest-cpu-limit N" echo " --kcsan-kmake-arg kernel-make-arguments" @@ -148,6 +150,7 @@ do do_kcsan=yes do_clocksourcewd="${ifnotaarch64}" do_srcu_lockdep=yes + do_atomic_srcu=yes ;; --do-allmodconfig|--do-no-allmodconfig|--no-allmodconfig) do_allmodconfig=`doyesno "$1" --do-allmodconfig` @@ -183,6 +186,7 @@ do do_kcsan=no do_clocksourcewd=no do_srcu_lockdep=no + do_atomic_srcu=no ;; --do-normal|--do-norm|--do-no-normal|--do-no-norm|--no-normal|--no-norm) do_normal=`doyesno "$1" --do-normal` @@ -212,6 +216,9 @@ do --do-srcu-lockdep|--do-no-srcu-lockdep|--no-srcu-lockdep) do_srcu_lockdep=`doyesno "$1" --do-srcu-lockdep` ;; + --do-atomic-srcu|--do-no-atomic-srcu|--no-atomic-srcu) + do_atomic_srcu=`doyesno "$1" --do-atomic-srcu` + ;; --duration) checkarg --duration "(minutes)" $# "$2" '^[0-9][0-9]*\(m\|h\|d\|\)$' '^error' mult=1 @@ -497,6 +504,23 @@ then torture_set "rcutorture" tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration "$duration_rcutorture" --configs "$configs_rcutorture" --trust-make fi +# Test atomic SRCU across Tree SRCU (SRCU-N and SRCU-P) and Tiny SRCU +# (SRCU-T). The reader flavor selects srcu_read_lock_atomic() and +# synchronize_srcu_atomic(). Tiny SRCU requires SMP=n, which aarch64 +# does not support. +if test "$do_atomic_srcu" = "yes" +then + torture_bootargs="rcutorture.reader_flavor=0x10" + configs_atomic_srcu="SRCU-N SRCU-P" + if test "$ifnotaarch64" = yes + then + configs_atomic_srcu="$configs_atomic_srcu SRCU-T" + fi + torture_set "atomic-srcu" tools/testing/selftests/rcutorture/bin/kvm.sh \ + --allcpus --duration "$duration_rcutorture" \ + --configs "$configs_atomic_srcu" --trust-make +fi + if test "$do_locktorture" = "yes" then torture_bootargs="torture.disable_onoff_at_boot" From 30d89cea5fa0118e4aec52511ab8210da4c363d9 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:19 +0800 Subject: [PATCH 1728/2286] srcutree: Add reader-free fastpath to synchronize_srcu_atomic() synchronize_srcu_atomic() is restricted to srcu_read_lock_atomic() and srcu_read_unlock_atomic(), whose read-side critical sections disable preemption. In the common case where there are no readers at all, the grace period therefore need not do the index flip. Add a fastpath that sums both ranks of the per-CPU ->srcu_ctrs[] counters and, if the lock counts match the unlock counts on both ranks, ends the grace period immediately, skipping the srcu_advance_state() scans, mirroring the similar Tiny SRCU fastpath. Correctness requires the counter-sum proof to follow the grace-period anchor written by srcu_gp_start(); placing it before the anchor could let this grace period miss a pre-existing reader and return without waiting for it. The smp_mb() between the unlock and lock sums pairs with the smp_mb() in __srcu_read_lock(). The grace period is ended manually under ->lock and ->srcu_atomic_gp_flag. In theory, this is slower than David Woodhouse's earlier patch, but David's measurements showed that the performance was close enough that it makes sense to keep the get_state_synchronize_srcu() and poll_state_synchronize_srcu semantics. Link: https://lore.kernel.org/all/20260907075829.2073224-4-kunwu.chan@linux.dev/ Link: https://lore.kernel.org/all/0f4dea21bac43685d3286df329401177e4452b36.camel@infradead.org/ Link: https://lore.kernel.org/all/0d4af6318ac67486858be1df8d436147b444a2d2.camel@infradead.org/ Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney Cc: David Woodhouse --- kernel/rcu/srcutree.c | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 64081c8eacc84a..93b8d1088abe83 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -2119,6 +2119,8 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) { unsigned long srcu_state; struct srcu_usage *sup = ssp->srcu_sup; + unsigned long rdm0, rdm1; + unsigned long unlocks0, unlocks1; // Initialize. Either init_srcu_struct() was invoked or // DEFINE_SRCU() or similar was used. Therefore, no allocation @@ -2154,6 +2156,52 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) srcu_gp_start(ssp); raw_spin_unlock_irq_rcu_node(sup); + // + // Fastpath: If there are no readers at all, neither grace-period + // scan need wait, so both can be satisfied at once without doing + // the index flip. The counter-sum proof is the same as that of + // srcu_readers_active_idx_check(), but spanning both indices. + // Atomic SRCU guarantees that all readers are of + // SRCU_READ_FLAVOR_ATOMIC, so the SLOWGP check never triggers and + // the ->srcu_reader_flavor masks returned by + // srcu_readers_unlock_idx() are unused. + // + // This proof must follow the grace-period anchor written by the + // srcu_gp_start() above, never precede it. With the anchor first, + // a reader whose lock increment is missed by the sums below cannot + // have incremented its lock counter before the anchor, and therefore + // cannot be a pre-existing reader of this grace period. Placing the + // proof before the anchor would let this grace period miss a + // pre-existing reader and return without waiting for it. + // + // The smp_mb() pairs with the smp_mb() in __srcu_read_lock() + // (store-buffering pattern), which guarantees that a lock is always + // counted if the corresponding unlock is counted, the same + // memory-ordering guarantee as is provided by + // srcu_readers_active_idx_check(). + // + unlocks0 = srcu_readers_unlock_idx(ssp, 0, &rdm0); + unlocks1 = srcu_readers_unlock_idx(ssp, 1, &rdm1); + smp_mb(); /* A */ + if (srcu_readers_lock_idx(ssp, 0, false, unlocks0) && + srcu_readers_lock_idx(ssp, 1, false, unlocks1)) { + // No readers, so end this grace period manually, skipping + // the index flip. Advancing the sequence number via + // rcu_seq_start() in srcu_gp_start() above and rcu_seq_end() + // below keeps get_state_synchronize_srcu() and + // poll_state_synchronize_srcu() working, all under ->lock + // and ->srcu_atomic_gp_flag, which excludes concurrent + // sequence-number updates. + raw_spin_lock_irq_rcu_node(sup); + rcu_seq_end(&sup->srcu_gp_seq); + raw_spin_unlock_irq_rcu_node(sup); + WARN_ON_ONCE(!poll_state_synchronize_srcu(ssp, srcu_state)); + atomic_set_release(&sup->srcu_atomic_gp_flag, 0); + preempt_enable(); + non_block_end(); + return; + } + // Wait for it to complete, helping it along. while (!poll_state_synchronize_srcu(ssp, srcu_state)) { cpu_relax(); From 41f899f18d0e1f00acd83b3f85ee3baa5da3d8fe Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:26 +0800 Subject: [PATCH 1729/2286] srcutree: Skip callback scheduling for atomic SRCU grace periods call_srcu() is forbidden on atomic srcu_struct, so srcu_gp_end() never has callbacks to invoke for them. Yet it schedules callback invocation, which for atomic SRCU's SRCU_SIZE_SMALL state arms the boot CPU's ->delay_work timer every grace period, only for srcu_invoke_callbacks() to find nothing to do. Skip this for atomic SRCU. Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 93b8d1088abe83..0954486880c7d1 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -1016,10 +1016,10 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) /* Initiate callback invocation as needed. */ ss_state = smp_load_acquire(&sup->srcu_size_state); - if (ss_state < SRCU_SIZE_WAIT_BARRIER) { + if (!is_atomic && ss_state < SRCU_SIZE_WAIT_BARRIER) { srcu_schedule_cbs_sdp(per_cpu_ptr(ssp->sda, get_boot_cpu_id()), cbdelay); - } else { + } else if (!is_atomic) { idx = rcu_seq_ctr(gpseq) % ARRAY_SIZE(snp->srcu_have_cbs); srcu_for_each_node_breadth_first(ssp, snp) { raw_spin_lock_irq_rcu_node(snp); From 47138812bc786b74a9f96874782668c64860dcc1 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:27 +0800 Subject: [PATCH 1730/2286] srcutree: Remove srcu_barrier() sleep for atomic SRCU The atomic-SRCU path in srcu_barrier() sleeps for 100 milliseconds just in case there are callbacks to wait for. But call_srcu() refuses atomic SRCU with a WARN_ON_ONCE() before reaching the deferred-enqueue path, so there can be no callbacks, deferred or otherwise. Drop the sleep. Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 0954486880c7d1..82e61421142e5d 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -1892,12 +1892,9 @@ void srcu_barrier(struct srcu_struct *ssp) unsigned long s; check_init_srcu_struct(ssp, false); - if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) { - // There shouldn't be any callbacks for atomic SRCU, - // but just in case. - schedule_timeout_uninterruptible(HZ/10); + // Atomic SRCU has no callbacks, so there is nothing to wait on. + if (WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) return; - } /* * Register any deferred callbacks before snapshotting the sequence. The From cc7287f9ee696e71c3adc23baf21a2dbe0e94874 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:29 +0800 Subject: [PATCH 1731/2286] srcu: Restrict atomic-SRCU non_block annotation to task context srcu_read_lock_atomic() and srcu_read_unlock_atomic() arm and disarm might_sleep() checks via non_block_start()/non_block_end(), skipping hardirq so the update does not land on the interrupted task's ->non_block_count. Inline softirqs run on the interrupted task's stack as well, so a timer callback running atomic-SRCU readers races with the interrupted task's own ->non_block_count updates, as KCSAN reports: BUG: KCSAN: data-race in srcu_torture_read_lock / srcu_torture_read_unlock write to 0xffffa00f818ea418 of 4 bytes by interrupt on cpu 0: srcu_torture_read_lock+0x422/0x470 rcutorture_one_extend+0xdc/0x600 rcu_torture_one_read+0xd1/0x330 rcu_torture_timer+0x75/0x140 call_timer_fn+0xe6/0x2f0 ... run_timer_softirq+0xb7/0x130 handle_softirqs+0xfc/0x3f0 __irq_exit_rcu+0x8e/0x100 Use in_task() so the annotation is applied only in task context; it is redundant elsewhere because might_sleep() already warns about sleeping from atomic context. Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- include/linux/srcu.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/linux/srcu.h b/include/linux/srcu.h index 3f232f2e052446..1a8a465a5650dc 100644 --- a/include/linux/srcu.h +++ b/include/linux/srcu.h @@ -346,11 +346,13 @@ static inline int srcu_read_lock_atomic(struct srcu_struct *ssp) /* * Arm might_sleep() to catch even a *potentially* sleeping call * in the section, not just an actual schedule: the atomic-domain - * promise must hold on every path, contended or not. In hardirq - * the annotation would land on the interrupted task; it is also + * promise must hold on every path, contended or not. In hardirq, + * softirq, or NMI the annotation would land on the interrupted + * task, and can also result in data races against that task's + * own non_block_start()/non_block_end() invocations; it is also * redundant there, so skip it. */ - if (!in_hardirq()) + if (in_task()) non_block_start(); srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_ATOMIC); retval = __srcu_read_lock(ssp); @@ -562,7 +564,7 @@ static inline void srcu_read_unlock_atomic(struct srcu_struct *ssp, int idx) srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_ATOMIC); srcu_lock_release(&ssp->dep_map); __srcu_read_unlock(ssp, idx); - if (!in_hardirq()) + if (in_task()) non_block_end(); preempt_enable(); } From cbd520f619714e88cca1b814ae3486427133e9ed Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 7 Sep 2026 15:58:22 +0800 Subject: [PATCH 1732/2286] srcutree: Make init_srcu_struct_atomic() prevent transition to big The is_atomic parameter of init_srcu_struct_fields() exists so that atomic SRCU never transitions to big, but neither init_srcu_struct_atomic() nor its lockdep counterpart __init_srcu_struct_atomic() sets it. On systems where srcutree.convert_to_big selects SRCU_SIZING_INIT, this needlessly allocates a full srcu_node combining tree for any dynamically initialized atomic srcu_struct, despite atomic SRCU having neither callbacks nor srcu_barrier() operations. Pass true from both atomic entry points, adding an is_atomic parameter to __init_srcu_struct_common(). Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 82e61421142e5d..4e9a0b8ea34494 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -305,26 +305,27 @@ static int init_srcu_struct_fields(struct srcu_struct *ssp, bool is_static, bool #ifdef CONFIG_DEBUG_LOCK_ALLOC static int -__init_srcu_struct_common(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) +__init_srcu_struct_common(struct srcu_struct *ssp, const char *name, + struct lock_class_key *key, bool is_atomic) { /* Don't re-initialize a lock while it is held. */ debug_check_no_locks_freed((void *)ssp, sizeof(*ssp)); lockdep_init_map(&ssp->dep_map, name, key, 0); - return init_srcu_struct_fields(ssp, false, false); + return init_srcu_struct_fields(ssp, false, is_atomic); } int init_srcu_struct_lockdep(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) { ssp->srcu_reader_flavor = 0; - return __init_srcu_struct_common(ssp, name, key); + return __init_srcu_struct_common(ssp, name, key, false); } EXPORT_SYMBOL_GPL(init_srcu_struct_lockdep); int __init_srcu_struct_fast(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_FAST; - return __init_srcu_struct_common(ssp, name, key); + return __init_srcu_struct_common(ssp, name, key, false); } EXPORT_SYMBOL_GPL(__init_srcu_struct_fast); @@ -332,14 +333,14 @@ int __init_srcu_struct_fast_updown(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_FAST_UPDOWN; - return __init_srcu_struct_common(ssp, name, key); + return __init_srcu_struct_common(ssp, name, key, false); } EXPORT_SYMBOL_GPL(__init_srcu_struct_fast_updown); int __init_srcu_struct_atomic(struct srcu_struct *ssp, const char *name, struct lock_class_key *key) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_ATOMIC; - return __init_srcu_struct_common(ssp, name, key); + return __init_srcu_struct_common(ssp, name, key, true); } EXPORT_SYMBOL_GPL(__init_srcu_struct_atomic); @@ -414,7 +415,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct_fast_updown); int init_srcu_struct_atomic(struct srcu_struct *ssp) { ssp->srcu_reader_flavor = SRCU_READ_FLAVOR_ATOMIC; - return init_srcu_struct_fields(ssp, false, false); + return init_srcu_struct_fields(ssp, false, true); } EXPORT_SYMBOL_GPL(init_srcu_struct_atomic); From 378ee30c813edf9e015eb1493cf2188f25682a31 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Fri, 11 Sep 2026 15:21:25 +0800 Subject: [PATCH 1733/2286] srcutree: Don't transition atomic SRCU to big in srcu_gp_end() Atomic SRCU must remain in the small size state. Warn if this invariant is violated and avoid transitioning to big in that case. [ paulmck: Folded "if" onto one line for 100-character limit. ] Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney Reviewed-by: Bradley Morgan --- kernel/rcu/srcutree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 4e9a0b8ea34494..6a05bd8a6f390a 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -1073,8 +1073,10 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) raw_spin_unlock_irq_rcu_node(sup); } - /* Transition to big if needed. */ - if (ss_state != SRCU_SIZE_SMALL && ss_state != SRCU_SIZE_BIG) { + /* Transition to big if needed, but never for atomic SRCU. */ + if (ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC && ss_state != SRCU_SIZE_SMALL) { + WARN_ON_ONCE(1); + } else if (ss_state != SRCU_SIZE_SMALL && ss_state != SRCU_SIZE_BIG) { if (ss_state == SRCU_SIZE_ALLOC) init_srcu_struct_nodes(ssp, GFP_KERNEL); else From 0150a00d829e866931775d9503969402d2274251 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Fri, 11 Sep 2026 12:02:51 +0800 Subject: [PATCH 1734/2286] srcutree: Skip torture to-big transition for atomic SRCU Atomic SRCU does not use the srcu_node combining tree. Exclude it from the SRCU_SIZING_IS_TORTURE() transition in srcu_torture_stats_print(), and warn if such a transition is requested. Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 6a05bd8a6f390a..6e37d53b412e1a 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -2422,8 +2422,10 @@ void srcu_torture_stats_print(struct srcu_struct *ssp, char *tt, char *tf) } pr_cont(" T(%ld,%ld)\n", s0, s1); } - if (SRCU_SIZING_IS_TORTURE()) - srcu_transition_to_big(ssp); + if (SRCU_SIZING_IS_TORTURE()) { + if (!WARN_ON_ONCE(ssp->srcu_reader_flavor == SRCU_READ_FLAVOR_ATOMIC)) + srcu_transition_to_big(ssp); + } } EXPORT_SYMBOL_GPL(srcu_torture_stats_print); From 91d2ab160c7c3f001847fe56126eb2f65d681f1e Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Fri, 11 Sep 2026 17:35:15 +0800 Subject: [PATCH 1735/2286] srcutree: Add lockdep and early-boot checks to synchronize_srcu_atomic() Add the lockdep annotation, same-type SRCU nesting warning, and early-boot check used by __synchronize_srcu(). Suggested-by: Zqiang Signed-off-by: Kunwu Chan Reviewed-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 6e37d53b412e1a..7890f7c7c2ee7c 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -2122,6 +2122,14 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) unsigned long rdm0, rdm1; unsigned long unlocks0, unlocks1; + srcu_lock_sync(&ssp->dep_map); + + RCU_LOCKDEP_WARN(lockdep_is_held(ssp), + "Illegal synchronize_srcu_atomic() in same-type SRCU read-side critical section"); + + if (rcu_scheduler_active == RCU_SCHEDULER_INACTIVE) + return; + // Initialize. Either init_srcu_struct() was invoked or // DEFINE_SRCU() or similar was used. Therefore, no allocation // will be done here. From 8d99291b14f1cb441279ba800ad7535e0e760b63 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Sat, 12 Sep 2026 00:09:13 +0800 Subject: [PATCH 1736/2286] srcutiny: Add lockdep and early-boot checks to synchronize_srcu_atomic() Add the RCU_LOCKDEP_WARN and early-boot check that the Tree version gained, srcu_lock_sync() was already present. Suggested-by: Zqiang Signed-off-by: Kunwu Chan Reviewed-by: Bradley Morgan Reviewed-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutiny.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/rcu/srcutiny.c b/kernel/rcu/srcutiny.c index a31398def0b189..63a3f0a8bc53cc 100644 --- a/kernel/rcu/srcutiny.c +++ b/kernel/rcu/srcutiny.c @@ -361,6 +361,12 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) srcu_lock_sync(&ssp->dep_map); + RCU_LOCKDEP_WARN(lockdep_is_held(ssp), + "Illegal synchronize_srcu_atomic() in same-type SRCU read-side critical section"); + + if (rcu_scheduler_active == RCU_SCHEDULER_INACTIVE) + return; + if (IS_ENABLED(CONFIG_PREEMPTION)) synchronize_rcu(); // Needed for RCU Tasks Trace to imply RCU grace period. // And in Tiny RCU, it is near zero cost and doesn't block. From 13fbbaefb0ac4e4a37e357e0083ca67e84302cd6 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 14 Sep 2026 17:34:17 +0800 Subject: [PATCH 1737/2286] srcutree: Preserve IRQ state in synchronize_srcu_atomic() callchain synchronize_srcu_atomic() may be called with interrupts disabled, but its callchain uses raw_spin_lock_irq_rcu_node() / raw_spin_unlock_irq_rcu_node(), which unconditionally enables interrupts on unlock. This can incorrectly change the caller's IRQ state. Use the irqsave/irqrestore variants for the locks in this callchain so that the caller's IRQ state is preserved. Co-developed-by: Zqiang Signed-off-by: Zqiang Signed-off-by: Kunwu Chan Acked-by: Boqun Feng Signed-off-by: Paul E. McKenney --- kernel/rcu/srcutree.c | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/kernel/rcu/srcutree.c b/kernel/rcu/srcutree.c index 7890f7c7c2ee7c..c8209aa6552a1e 100644 --- a/kernel/rcu/srcutree.c +++ b/kernel/rcu/srcutree.c @@ -985,6 +985,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) bool cbs; bool last_lvl; int cpu; + unsigned long flags; unsigned long gpseq; int idx; unsigned long mask; @@ -999,7 +1000,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) mutex_lock(&sup->srcu_cb_mutex); /* End the current grace period. */ - raw_spin_lock_irq_rcu_node(sup); + raw_spin_lock_irqsave_rcu_node(sup, flags); idx = rcu_seq_state(sup->srcu_gp_seq); WARN_ON_ONCE(idx != SRCU_STATE_SCAN2); if (srcu_gp_is_expedited(ssp)) @@ -1010,7 +1011,7 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) gpseq = rcu_seq_current(&sup->srcu_gp_seq); if (ULONG_CMP_LT(sup->srcu_gp_seq_needed_exp, gpseq)) WRITE_ONCE(sup->srcu_gp_seq_needed_exp, gpseq); - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); if (!is_atomic) mutex_unlock(&sup->srcu_gp_mutex); /* A new grace period can start at this point. But only one. */ @@ -1048,12 +1049,12 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) if (!(gpseq & counter_wrap_check)) for_each_possible_cpu(cpu) { sdp = per_cpu_ptr(ssp->sda, cpu); - raw_spin_lock_irq_rcu_node(sdp); + raw_spin_lock_irqsave_rcu_node(sdp, flags); if (ULONG_CMP_GE(gpseq, sdp->srcu_gp_seq_needed + 100)) sdp->srcu_gp_seq_needed = gpseq; if (ULONG_CMP_GE(gpseq, sdp->srcu_gp_seq_needed_exp + 100)) sdp->srcu_gp_seq_needed_exp = gpseq; - raw_spin_unlock_irq_rcu_node(sdp); + raw_spin_unlock_irqrestore_rcu_node(sdp, flags); } /* Callback initiation done, allow grace periods after next. */ @@ -1061,16 +1062,16 @@ static void srcu_gp_end(struct srcu_struct *ssp, bool is_atomic) mutex_unlock(&sup->srcu_cb_mutex); /* Start a new grace period if needed. */ - raw_spin_lock_irq_rcu_node(sup); + raw_spin_lock_irqsave_rcu_node(sup, flags); gpseq = rcu_seq_current(&sup->srcu_gp_seq); if (!rcu_seq_state(gpseq) && ULONG_CMP_LT(gpseq, sup->srcu_gp_seq_needed)) { WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); srcu_reschedule(ssp, 0); } else { - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); } /* Transition to big if needed, but never for atomic SRCU. */ @@ -1220,10 +1221,11 @@ static void srcu_funnel_gp_start(struct srcu_struct *ssp, struct srcu_data *sdp, static bool try_check_zero(struct srcu_struct *ssp, int idx, int trycount) { unsigned long curdelay; + unsigned long flags; - raw_spin_lock_irq_rcu_node(ssp->srcu_sup); + raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags); curdelay = !srcu_get_delay(ssp); - raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); + raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); for (;;) { if (srcu_readers_active_idx_check(ssp, idx)) @@ -2023,6 +2025,7 @@ EXPORT_SYMBOL_GPL(srcu_batches_completed); */ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) { + unsigned long flags; int idx; if (!is_atomic) @@ -2040,10 +2043,10 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) */ idx = rcu_seq_state(smp_load_acquire(&ssp->srcu_sup->srcu_gp_seq)); /* ^^^ */ if (idx == SRCU_STATE_IDLE) { - raw_spin_lock_irq_rcu_node(ssp->srcu_sup); + raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags); if (ULONG_CMP_GE(ssp->srcu_sup->srcu_gp_seq, ssp->srcu_sup->srcu_gp_seq_needed)) { WARN_ON_ONCE(rcu_seq_state(ssp->srcu_sup->srcu_gp_seq)); - raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); + raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); if (!is_atomic) mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); return; @@ -2053,7 +2056,7 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) WARN_ON_ONCE(ssp->srcu_reader_flavor & SRCU_READ_FLAVOR_ATOMIC); srcu_gp_start(ssp); } - raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); + raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); if (idx != SRCU_STATE_IDLE) { if (!is_atomic) mutex_unlock(&ssp->srcu_sup->srcu_gp_mutex); @@ -2069,10 +2072,10 @@ static void srcu_advance_state(struct srcu_struct *ssp, bool is_atomic) return; /* readers present, retry later. */ } srcu_flip(ssp); - raw_spin_lock_irq_rcu_node(ssp->srcu_sup); + raw_spin_lock_irqsave_rcu_node(ssp->srcu_sup, flags); rcu_seq_set_state(&ssp->srcu_sup->srcu_gp_seq, SRCU_STATE_SCAN2); ssp->srcu_sup->srcu_n_exp_nodelay = 0; - raw_spin_unlock_irq_rcu_node(ssp->srcu_sup); + raw_spin_unlock_irqrestore_rcu_node(ssp->srcu_sup, flags); } if (rcu_seq_state(READ_ONCE(ssp->srcu_sup->srcu_gp_seq)) == SRCU_STATE_SCAN2) { @@ -2119,6 +2122,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) { unsigned long srcu_state; struct srcu_usage *sup = ssp->srcu_sup; + unsigned long flags; unsigned long rdm0, rdm1; unsigned long unlocks0, unlocks1; @@ -2149,9 +2153,9 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) } // One last check for others doing our work for us under the lock. - raw_spin_lock_irq_rcu_node(sup); + raw_spin_lock_irqsave_rcu_node(sup, flags); if (poll_state_synchronize_srcu(ssp, srcu_state)) { - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); atomic_set(&sup->srcu_atomic_gp_flag, 0); preempt_enable(); return; @@ -2162,7 +2166,7 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) smp_store_release(&sup->srcu_gp_seq_needed, srcu_state); // See srcu_funnel_gp_start(). ASSERT_EXCLUSIVE_WRITER(ssp->srcu_sup->srcu_gp_seq); srcu_gp_start(ssp); - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); // // Fastpath: If there are no readers at all, neither grace-period @@ -2200,9 +2204,9 @@ void synchronize_srcu_atomic(struct srcu_struct *ssp) // poll_state_synchronize_srcu() working, all under ->lock // and ->srcu_atomic_gp_flag, which excludes concurrent // sequence-number updates. - raw_spin_lock_irq_rcu_node(sup); + raw_spin_lock_irqsave_rcu_node(sup, flags); rcu_seq_end(&sup->srcu_gp_seq); - raw_spin_unlock_irq_rcu_node(sup); + raw_spin_unlock_irqrestore_rcu_node(sup, flags); WARN_ON_ONCE(!poll_state_synchronize_srcu(ssp, srcu_state)); atomic_set_release(&sup->srcu_atomic_gp_flag, 0); preempt_enable(); From 5b748d8aa6833f2dec3f545311be69a2064bc6d1 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Thu, 20 Aug 2026 17:42:28 +0800 Subject: [PATCH 1738/2286] rcutorture: Fix divide-by-zero with fwd_progress_div=1 When fwd_progress_div=1, the forward-progress test computes: sd4 = (sd + div - 1) / div = sd dur = sd4 + torture_random(&trs) % (sd - sd4) = sd4 + % 0 The modulo operation with a zero divisor triggers an integer division by zero (undefined behavior at the C level, #DE trap on x86), causing a kernel Oops and panic. On x86_64, this manifests as: rcu_torture_fwd_prog_nr: Starting forward-progress test 0 Oops: divide error: 0000 [#1] SMP PTI RIP: 0010:rcu_torture_fwd_prog+0x90b/0x1160 R12: 0000000000000000 The existing guard only handles non-positive values. However, fwd_progress_div=1 also makes the random range empty because sd4 == sd. Change the guard to reject values below 2. The forward-progress test only reaches this calculation when stall_dur() is positive, so sd = stall_dur() + 1 >= 2. For fwd_progress_div >= 2, sd4 < sd, ensuring that sd - sd4 is at least 1. Keep the existing fallback to the default value of 4 for invalid values. Verified with QEMU/KVM: a 138-second run with fwd_progress_div=1 completed 81 forward-progress test cycles without a crash. Fixes: 1b27291b1ea4f ("rcutorture: Add forward-progress tests for RCU grace periods") Signed-off-by: Kunwu Chan Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 182d47975efd18..79807475b67245 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -4024,7 +4024,7 @@ static int __init rcu_torture_fwd_prog_init(void) } if (fwd_progress_holdoff <= 0) fwd_progress_holdoff = 1; - if (fwd_progress_div <= 0) + if (fwd_progress_div < 2) fwd_progress_div = 4; rfp = kzalloc_objs(*rfp, fwd_progress); fwd_prog_tasks = kzalloc_objs(*fwd_prog_tasks, fwd_progress); From 704e59c721d9156201705860c5d0a5e85415f8cc Mon Sep 17 00:00:00 2001 From: Zqiang Date: Mon, 24 Aug 2026 17:24:27 +0800 Subject: [PATCH 1739/2286] rcutorture: Synchronously wait for all rcu_torture_irq() callbacks to complete The rcu_torture_reader() drives RCU readers from interrupt context via smp_call_function_single(cpu, rcu_torture_irq, NULL, 0) with wait=0, to runs rcu_torture_irq() on a remote CPU. this is async, nothing waits for the remote handler to run. On shutdown, torture_stop_kthread() only waits for each reader kthread to return, and the reader's timer_delete_sync() only drains its timer. Neither waits for a rcu_torture_irq() which still pending or executing on a remote CPU, so it can run after all readers have exited and rcu_torture_cleanup() has already advanced. 1. rcu_torture_irq() may issue cur_ops->call(rhp, rcu_torture_timer_cb) after cur_ops->cb_barrier() has been waiting for all outstanding callbacks complete. once the module is unloaded, fires into freed module text, a use-after-free happen. 2. rcu_torture_irq() may still be inside rcu_torture_one_read(), holding a read-side critical section, when cur_ops->cleanup() tears the flavor down (e.g. cleanup_srcu_struct()), triggering an active-reader warning or use-after-free of the torn-down structure. This commit therefore issue a kick_all_cpus_sync() after all readers kthread have returned and before cur_ops->cb_barrier(), synchronous IPI round trip to every CPU guarantees that every rcu_torture_irq() which previously issued by any reader has completed. Signed-off-by: Zqiang Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 79807475b67245..51ed35f5aab171 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -4491,6 +4491,8 @@ rcu_torture_cleanup(void) for (i = 0; i < nrealreaders; i++) torture_stop_kthread(rcu_torture_reader, reader_tasks[i]); + if (irqreader && cur_ops->irq_capable) + kick_all_cpus_sync(); kfree(reader_tasks); reader_tasks = NULL; } From 88dcdd9e6837b0e24eab4d8287f0401f23009181 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Thu, 27 Aug 2026 11:25:51 -0700 Subject: [PATCH 1740/2286] torture: Allow specifying alternative ssh command to kvm-remote.sh Some environments require use of alternative commands to access the test hosts. This commit therefore adds a KVM_REMOTE_SSH environment variable for this purpose. If this variable is unset, ssh is used. Any alternative ssh command must support the usual ssh arguments, including the command to be executed remotely. In some cases, you may need a wrapper script to make the alternative ssh-like command look enough like ssh to satisfy kvm-remote.sh. Signed-off-by: Paul E. McKenney --- .../selftests/rcutorture/bin/kvm-remote.sh | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/rcutorture/bin/kvm-remote.sh b/tools/testing/selftests/rcutorture/bin/kvm-remote.sh index 48a8052d5dae37..a8397f86e49dc7 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm-remote.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm-remote.sh @@ -6,6 +6,11 @@ # Usage: kvm-remote.sh "systems" [ ] # kvm-remote.sh "systems" /path/to/old/run [ ] # +# The caller may set the KVM_REMOTE_SSH environment in order to specify +# an alternative ssh command, which is necessary in some environments +# for authentication purposes. This alternative ssn command must support +# ssh's usual arguments. +# # Copyright (C) 2021 Facebook, Inc. # # Authors: Paul E. McKenney @@ -13,6 +18,11 @@ scriptname=$0 args="$*" +if test -z "${KVM_REMOTE_SSH}" +then + KVM_REMOTE_SSH=ssh; export KVM_REMOTE_SSH +fi + if ! test -d tools/testing/selftests/rcutorture/bin then echo $scriptname must be run from top-level directory of kernel source tree. @@ -137,7 +147,7 @@ chmod +x $T/bin/kvm-remote-*.sh # Check first to avoid the need for cleanup for system-name typos for i in $systems do - ssh -o BatchMode=yes $i getconf _NPROCESSORS_ONLN > $T/ssh.stdout 2> $T/ssh.stderr + ${KVM_REMOTE_SSH} -o BatchMode=yes $i getconf _NPROCESSORS_ONLN > $T/ssh.stdout 2> $T/ssh.stderr ret=$? if test "$ret" -ne 0 then @@ -158,14 +168,14 @@ echo Build-products tarball: `du -h $T/binres.tgz` | tee -a "$oldrun/remote-log" for i in $systems do echo Downloading tarball to $i `date` | tee -a "$oldrun/remote-log" - cat $T/binres.tgz | ssh -o BatchMode=yes $i "cd /tmp; tar -xzf -" + cat $T/binres.tgz | ${KVM_REMOTE_SSH} -o BatchMode=yes $i "cd /tmp; tar -xzf -" ret=$? tries=0 while test "$ret" -ne 0 do echo Unable to download $T/binres.tgz to system $i, waiting and then retrying. $tries prior retries. | tee -a "$oldrun/remote-log" sleep 60 - cat $T/binres.tgz | ssh -o BatchMode=yes $i "cd /tmp; tar -xzf -" + cat $T/binres.tgz | ${KVM_REMOTE_SSH} -o BatchMode=yes $i "cd /tmp; tar -xzf -" ret=$? if test "$ret" -ne 0 then @@ -191,7 +201,7 @@ checkremotefile () { while : do - ssh -o BatchMode=yes $1 "test -f \"$2\"" + ${KVM_REMOTE_SSH} -o BatchMode=yes $1 "test -f \"$2\"" ret=$? if test "$ret" -eq 255 then @@ -239,7 +249,7 @@ startbatches () { then continue # System still running last test, skip. fi - ssh -o BatchMode=yes "$i" "cd \"$resdir/$ds\"; touch remote.run; PATH=\"$T/bin:$PATH\" nohup kvm-remote-$curbatch.sh > kvm-remote-$curbatch.sh.out 2>&1 &" 1>&2 + ${KVM_REMOTE_SSH} -o BatchMode=yes "$i" "cd \"$resdir/$ds\"; touch remote.run; PATH=\"$T/bin:$PATH\" nohup kvm-remote-$curbatch.sh > kvm-remote-$curbatch.sh.out 2>&1 &" 1>&2 ret=$? if test "$ret" -ne 0 then @@ -281,7 +291,7 @@ do if test "$ret" -eq 1 then echo " ---" Collecting results from $i `date` | tee -a "$oldrun/remote-log" - ( cd "$oldrun"; ssh -o BatchMode=yes $i "cd $rundir; tar -czf - kvm-remote-*.sh.out */console.log */kvm-test-1-run*.sh.out */qemu[_-]pid */qemu-retval */qemu-affinity; rm -rf $T > /dev/null 2>&1" | tar -xzf - ) + ( cd "$oldrun"; ${KVM_REMOTE_SSH} -o BatchMode=yes $i "cd $rundir; tar -czf - kvm-remote-*.sh.out */console.log */kvm-test-1-run*.sh.out */qemu[_-]pid */qemu-retval */qemu-affinity; rm -rf $T > /dev/null 2>&1" | tar -xzf - ) break; fi if test "$ret" -eq 255 From c23eb6c48cf3b8b7392214d0406b0f997025c9db Mon Sep 17 00:00:00 2001 From: Zhouyi Zhou Date: Sat, 19 Sep 2026 01:06:06 +0000 Subject: [PATCH 1741/2286] rcutorture: Fix kvm-again.sh re-run failure on ppc64 Discovered in the Open Source Lab of Oregon State University when running torture.sh on a ppc64le VM. Two bugs were exposed: 1. The kvm-transform.sh call hard-coded "bzImage" as the kernel image name. On ppc64, the boot image is vmlinux, not bzImage, so the re-run failed to find the image. Fix this by extracting the QEMU binary from the qemu-cmd file and passing it to identify_boot_image() to obtain the correct architecture-specific image name, the same way kvm.sh already does. 2. The rm -f invocation on re-run listed vmlinux among the files to delete. On ppc64 vmlinux is the boot image, so deleting it broke the re-run on that architecture. Drop vmlinux from the unconditional list, and instead conditionally delete it only when the boot image basename is not vmlinux. In addition, identify qemu_binary before the copy step so that on non-PowerPC systems (where vmlinux is not the boot image) the vmlinux file can be removed immediately after the run directory is copied, saving storage before any tests run. This also eliminates the per-iteration re-computation of qemu_binary and boot_image inside the qemu-cmd transform loop. Tested on a local x86_64 machine and on a PPC VM of the Open Source Lab of Oregon State University. Signed-off-by: Zhouyi Zhou Reviewed-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- tools/testing/selftests/rcutorture/bin/kvm-again.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/rcutorture/bin/kvm-again.sh b/tools/testing/selftests/rcutorture/bin/kvm-again.sh index b5239b52cb5da7..9060b3dc892626 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm-again.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm-again.sh @@ -180,6 +180,9 @@ fi echo ---- Re-run results directory: $rundir +qemu_binary="$(find "$oldrun" -maxdepth 2 -name 'qemu-cmd' -type f 2>/dev/null | head -1 | xargs -r grep -v '^#' 2>/dev/null | awk 'NF { print $1; exit }')" +boot_image="`identify_boot_image "$qemu_binary"`" + if test "$oldrun" != "$rundir" then # Copy old run directory tree over and adjust. @@ -189,7 +192,7 @@ then echo "Cannot copy from $oldrun to $rundir." usage fi - rm -f "$rundir"/*/{console.log,console.log.diags,qemu_pid,qemu-pid,qemu-retval,Warnings,kvm-test-1-run.sh.out,kvm-test-1-run-qemu.sh.out,vmlinux} "$rundir"/log + rm -f "$rundir"/*/{console.log,console.log.diags,qemu_pid,qemu-pid,qemu-retval,Warnings,kvm-test-1-run.sh.out,kvm-test-1-run-qemu.sh.out} "$rundir"/log touch "$rundir/log" echo $scriptname $args | tee -a "$rundir/log" echo $oldrun > "$rundir/re-run" @@ -197,6 +200,10 @@ then then $arg_link "$oldrun/../../bin" "$rundir/../.." fi + if test "`basename "$boot_image"`" != vmlinux + then + rm -f "$rundir"/*/vmlinux + fi else # Check for a run having already happened. find "$rundir" -name console.log -print > $T/oldrun-console.log @@ -217,7 +224,7 @@ do qemu_cmd_dir="`dirname "$i"`" kernel_dir="`echo $qemu_cmd_dir | sed -e 's/\.[0-9]\+$//'`" jitter_dir="`dirname "$kernel_dir"`" - kvm-transform.sh "$kernel_dir/bzImage" "$qemu_cmd_dir/console.log" "$jitter_dir" "$dur" "$bootargs" < $T/qemu-cmd > $i + kvm-transform.sh "$kernel_dir/`basename $boot_image`" "$qemu_cmd_dir/console.log" "$jitter_dir" "$dur" "$bootargs" < $T/qemu-cmd > $i if test -n "$arg_remote" then echo "# TORTURE_KCONFIG_GDB_ARG=''" >> $i From a692cb8e40d9d7475a3f27b714acc7fa46f9d1df Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 14 Sep 2026 17:34:15 +0800 Subject: [PATCH 1742/2286] rcutorture: Add atomic SRCU lockdep support Add three testtypes covering atomic SRCU lockdep behavior: testtype 4: atomic SRCU same-type deadlock testtype 5: atomic SRCU + raw spinlock dependency cycle testtype 6: synchronize_srcu_atomic() inside rcu_read_lock() Co-developed-by: Zqiang Signed-off-by: Zqiang Signed-off-by: Kunwu Chan Acked-by: Boqun Feng Reviewed-by: Joel Fernandes Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 84 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 51ed35f5aab171..35dc61d871b981 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -4662,6 +4662,17 @@ static DECLARE_RWSEM(rwsem7); static DECLARE_RWSEM(rwsem8); static DECLARE_RWSEM(rwsem9); +static DEFINE_RAW_SPINLOCK(spin0); +static DEFINE_RAW_SPINLOCK(spin1); +static DEFINE_RAW_SPINLOCK(spin2); +static DEFINE_RAW_SPINLOCK(spin3); +static DEFINE_RAW_SPINLOCK(spin4); +static DEFINE_RAW_SPINLOCK(spin5); +static DEFINE_RAW_SPINLOCK(spin6); +static DEFINE_RAW_SPINLOCK(spin7); +static DEFINE_RAW_SPINLOCK(spin8); +static DEFINE_RAW_SPINLOCK(spin9); + DEFINE_STATIC_SRCU(srcu0); DEFINE_STATIC_SRCU(srcu1); DEFINE_STATIC_SRCU(srcu2); @@ -4673,6 +4684,17 @@ DEFINE_STATIC_SRCU(srcu7); DEFINE_STATIC_SRCU(srcu8); DEFINE_STATIC_SRCU(srcu9); +DEFINE_STATIC_SRCU_ATOMIC(srcu0_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu1_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu2_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu3_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu4_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu5_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu6_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu7_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu8_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu9_atomic); + static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i, int cyclelen, int deadlock) { @@ -4702,6 +4724,12 @@ static void rcu_torture_init_srcu_lockdep(void) &rwsem5, &rwsem6, &rwsem7, &rwsem8, &rwsem9 }; struct srcu_struct *srcus[] = { &srcu0, &srcu1, &srcu2, &srcu3, &srcu4, &srcu5, &srcu6, &srcu7, &srcu8, &srcu9 }; + raw_spinlock_t *spins[] = { &spin0, &spin1, &spin2, &spin3, &spin4, + &spin5, &spin6, &spin7, &spin8, &spin9 }; + struct srcu_struct *srcus_atomic[] = { &srcu0_atomic, &srcu1_atomic, &srcu2_atomic, + &srcu3_atomic, &srcu4_atomic, &srcu5_atomic, + &srcu6_atomic, &srcu7_atomic, &srcu8_atomic, + &srcu9_atomic }; int testtype; if (!test_srcu_lockdep) @@ -4812,11 +4840,65 @@ static void rcu_torture_init_srcu_lockdep(void) } #endif // #ifdef CONFIG_TASKS_TRACE_RCU + if (testtype == 4) { + pr_info("%s: test_srcu_lockdep = %05d: SRCU_ATOMIC %d-way %sdeadlock.\n", + __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); + if (deadlock && cyclelen == 1) + pr_info("%s: Expect hang.\n", __func__); + for (i = 0; i < cyclelen; i++) { + j = srcu_lockdep_next(__func__, "srcu_read_lock_atomic", + "synchronize_srcu_atomic", + "srcu_read_unlock_atomic", i, + cyclelen, deadlock); + idx = srcu_read_lock_atomic(srcus_atomic[i]); + if (j >= 0) + synchronize_srcu_atomic(srcus_atomic[j]); + srcu_read_unlock_atomic(srcus_atomic[i], idx); + } + return; + } + + if (testtype == 5) { + pr_info("%s: test_srcu_lockdep = %05d: SRCU_ATOMIC/raw_spinlock %d-way %sdeadlock.\n", + __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); + for (i = 0; i < cyclelen; i++) { + pr_info("%s: srcu_read_lock_atomic(%d), raw_spin_lock(%d), raw_spin_unlock(%d), srcu_read_unlock_atomic(%d)\n", + __func__, i, i, i, i); + idx = srcu_read_lock_atomic(srcus_atomic[i]); + raw_spin_lock(spins[i]); + raw_spin_unlock(spins[i]); + srcu_read_unlock_atomic(srcus_atomic[i], idx); + + j = srcu_lockdep_next(__func__, "raw_spin_lock", + "synchronize_srcu_atomic", + "raw_spin_unlock", i, cyclelen, + deadlock); + raw_spin_lock(spins[i]); + if (j >= 0) + synchronize_srcu_atomic(srcus_atomic[j]); + raw_spin_unlock(spins[i]); + } + return; + } + + if (testtype == 6) { + pr_info("%s: test_srcu_lockdep = %05d: synchronize_srcu_atomic() inside rcu_read_lock() %d-way.\n", + __func__, test_srcu_lockdep, cyclelen); + for (i = 0; i < cyclelen; i++) { + rcu_read_lock(); + synchronize_srcu_atomic(srcus_atomic[i]); + rcu_read_unlock(); + } + return; + } + err_out: pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep); pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__); pr_info("%s: D: Deadlock if nonzero.\n", __func__); - pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU.\n", __func__); + pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU, 4=SRCU_ATOMIC, ", + __func__); + pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock.\n"); pr_info("%s: L: Cycle length.\n", __func__); if (!IS_ENABLED(CONFIG_TASKS_TRACE_RCU)) pr_info("%s: NN=3 disallowed because kernel is built with CONFIG_TASKS_TRACE_RCU=n\n", __func__); From 321846596c63482e53d23789cb203bd6f965439e Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 14 Sep 2026 17:34:16 +0800 Subject: [PATCH 1743/2286] selftests/rcutorture: Wire atomic SRCU into srcu_lockdep.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add testtypes 4 (atomic SRCU same-type deadlock) and 5 (atomic SRCU + raw_spinlock dependency cycle) to the deadlock-detection loop. Add a separate loop for testtype 6 (rcu_read_lock() → synchronize_srcu_atomic()), which also verifies via console.log that no lockdep warning is triggered. Co-developed-by: Zqiang Signed-off-by: Zqiang Signed-off-by: Kunwu Chan Acked-by: Boqun Feng Signed-off-by: Paul E. McKenney --- .../selftests/rcutorture/bin/srcu_lockdep.sh | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh index 4e98c697def487..72791499dd96bb 100755 --- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh +++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh @@ -44,7 +44,7 @@ nerrs=0 # Test lockdep's handling of deadlocks. for d in 0 1 do - for t in 0 1 2 + for t in 0 1 2 4 5 do for c in 1 2 3 do @@ -79,6 +79,43 @@ do done done +# Verify that synchronize_srcu_atomic() does not trigger lockdep +# warnings when called inside rcu_read_lock(). +for c in 1 2 3 +do + err= + val=$((6*10+c)) + tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 5s \ + --configs "SRCU-P" \ + --kconfig "CONFIG_FORCE_NEED_SRCU_NMI_SAFE=y" \ + --bootargs "rcutorture.test_srcu_lockdep=$val" \ + --trust-make --datestamp "$ds/$val" > "$T/kvm.sh.out" 2>&1 + ret=$? + mv "$T/kvm.sh.out" "$RCUTORTURE/res/$ds/$val" + if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config + then + echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING" \ + "disabled in rcutorture SRCU-P scenario" + err=1 + fi + if test "$ret" -ne 0 + then + err=1 + echo -n Unexpected failure for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + elif grep -qE "WARNING: possible (recursive locking|circular locking dependency)" \ + "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" + then + err=1 + echo -n Unexpected lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + fi + if test -n "$err" + then + grep "rcu_torture_init_srcu_lockdep: test_srcu_lockdep = " "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" | sed -e 's/^.*rcu_torture_init_srcu_lockdep://' >> "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + cat "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + nerrs=$((nerrs+1)) + fi +done + # Test lockdep-enabled testing of mixed SRCU readers. for val in 0x1 0xf do From 7872141f2e5ec3bbcd7097e8084a5a0eff019ba1 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 14 Sep 2026 17:34:18 +0800 Subject: [PATCH 1744/2286] selftests/rcutorture: Fix double nerrs count in srcu_lockdep.sh When CONFIG_PROVE_LOCKING is missing, the deadlock-detection loop and the mixed-SRCU-readers section each increment nerrs once in the configuration check and again in the common error path, counting the same error twice. Remove the stray increments so that each error advances nerrs by one. Signed-off-by: Kunwu Chan Acked-by: Boqun Feng Signed-off-by: Paul E. McKenney --- tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh index 72791499dd96bb..3acaffe52c784d 100755 --- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh +++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh @@ -56,7 +56,6 @@ do if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config then echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING disabled in rcutorture SRCU-P scenario" - nerrs=$((nerrs+1)) err=1 fi if test "$d" -ne 0 && test "$ret" -eq 0 @@ -126,7 +125,6 @@ do if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config then echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING disabled in rcutorture SRCU-P scenario" - nerrs=$((nerrs+1)) err=1 fi if test "$val" = 0xf && test "$ret" -eq 0 From 945f87baf00af62ce47237b3c766caa0269f0522 Mon Sep 17 00:00:00 2001 From: Kunwu Chan Date: Mon, 14 Sep 2026 17:34:19 +0800 Subject: [PATCH 1745/2286] rcutorture: Add atomic SRCU cross-CPU IRQ context mismatch test Add testtype 7 to verify that lockdep detects an IRQ-context mismatch when an atomic SRCU read-side critical section is held with IRQs enabled on one CPU and synchronize_srcu_atomic() is called from an IPI handler on another CPU. This covers the cross-CPU case that cannot be detected by checking the current task's held locks. Co-developed-by: Zqiang Signed-off-by: Zqiang Signed-off-by: Kunwu Chan Acked-by: Boqun Feng Signed-off-by: Paul E. McKenney --- kernel/rcu/rcutorture.c | 27 +++++++++++++- .../selftests/rcutorture/bin/srcu_lockdep.sh | 36 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 35dc61d871b981..968343be4e0d1a 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -4695,6 +4695,8 @@ DEFINE_STATIC_SRCU_ATOMIC(srcu7_atomic); DEFINE_STATIC_SRCU_ATOMIC(srcu8_atomic); DEFINE_STATIC_SRCU_ATOMIC(srcu9_atomic); +DEFINE_STATIC_SRCU_ATOMIC(srcu_irq); + static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i, int cyclelen, int deadlock) { @@ -4709,6 +4711,11 @@ static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, cons return j; } +static void srcu_sync_irq(void *unused) +{ + synchronize_srcu_atomic(&srcu_irq); +} + // Test lockdep on SRCU-based deadlock scenarios. static void rcu_torture_init_srcu_lockdep(void) { @@ -4892,13 +4899,31 @@ static void rcu_torture_init_srcu_lockdep(void) return; } + if (testtype == 7) { + int cpu; + + for (i = 0; i < cyclelen; i++) { + idx = srcu_read_lock_atomic(&srcu_irq); + cpu = cpumask_any_but(cpu_online_mask, + smp_processor_id()); + if (cpu < nr_cpu_ids) { + pr_info("%s: CPU%d sending IPI to CPU%d\n", + __func__, smp_processor_id(), cpu); + smp_call_function_single(cpu, srcu_sync_irq, + NULL, 1); + } + srcu_read_unlock_atomic(&srcu_irq, idx); + } + return; + } + err_out: pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep); pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__); pr_info("%s: D: Deadlock if nonzero.\n", __func__); pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU, 4=SRCU_ATOMIC, ", __func__); - pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock.\n"); + pr_cont("5=SRCU_ATOMIC/raw_spinlock, 6=synchronize_srcu_atomic inside rcu_read_lock, 7=atomic SRCU cross-CPU IRQ context mismatch.\n"); pr_info("%s: L: Cycle length.\n", __func__); if (!IS_ENABLED(CONFIG_TASKS_TRACE_RCU)) pr_info("%s: NN=3 disallowed because kernel is built with CONFIG_TASKS_TRACE_RCU=n\n", __func__); diff --git a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh index 3acaffe52c784d..c29a39f7d8914d 100755 --- a/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh +++ b/tools/testing/selftests/rcutorture/bin/srcu_lockdep.sh @@ -115,6 +115,42 @@ do fi done +# Verify that synchronize_srcu_atomic() detects IRQ context mismatch +# when SRCU reader previously ran with IRQs enabled. +for c in 1 2 3 +do + err= + val=$((1000+7*10+c)) + tools/testing/selftests/rcutorture/bin/kvm.sh --allcpus --duration 5s \ + --configs "SRCU-P" \ + --kconfig "CONFIG_FORCE_NEED_SRCU_NMI_SAFE=y" \ + --bootargs "rcutorture.test_srcu_lockdep=$val" \ + --trust-make --datestamp "$ds/$val" > "$T/kvm.sh.out" 2>&1 + ret=$? + mv "$T/kvm.sh.out" "$RCUTORTURE/res/$ds/$val" + if ! grep -q '^CONFIG_PROVE_LOCKING=y' .config + then + echo "rcu_torture_init_srcu_lockdep:Error: CONFIG_PROVE_LOCKING" \ + "disabled in rcutorture SRCU-P scenario" + err=1 + fi + if test "$ret" -eq 0 + then + err=1 + echo -n Missing lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + elif ! grep -q "inconsistent {HARDIRQ-ON-R}" "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" + then + err=1 + echo -n Missing lockdep warning for > "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + fi + if test -n "$err" + then + grep "rcu_torture_init_srcu_lockdep: test_srcu_lockdep = " "$RCUTORTURE/res/$ds/$val/SRCU-P/console.log" | sed -e 's/^.*rcu_torture_init_srcu_lockdep://' >> "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + cat "$RCUTORTURE/res/$ds/$val/kvm.sh.err" + nerrs=$((nerrs+1)) + fi +done + # Test lockdep-enabled testing of mixed SRCU readers. for val in 0x1 0xf do From 80f49404968d680f07f33f57ed9a9a1ddd2f2418 Mon Sep 17 00:00:00 2001 From: Enzo Matsumiya Date: Thu, 8 Oct 2026 15:26:09 -0300 Subject: [PATCH 1746/2286] smb: client: fast fail sends if need to reconnect smb_send_kvec() keeps retrying when server needs to reconnect, which makes no sense as cifs_reconnect() will never run in parallel (because both need server mutex). IOW, retrying will never succeed, but only delay reconnects further. Bail out early from smb_send_kvec() when need to reconnect. Also shutdown socket queues when CifsNeedReconnect is first detected, so any sends/receives fails immediately (and don't wait for socket to timeout). Add TCP_Server_Info::need_sock_shutdown to ensure socket is shutdown only once -- even though socket/TCP API will prevent multiple shutdowns, we need to prevent, from cifs side, concurrent tasks from accidentally shutting down the socket again in case cifs_reconnect() is successful. kernel_sock_shutdown() will be called from whichever task detects it first; cifsd (cifs_tcp_ses_needs_reconnect()), or sending tasks, through cifs_call_async() or compound_send_recv(). Signed-off-by: Enzo Matsumiya Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsglob.h | 1 + fs/smb/client/connect.c | 25 +++++++++++++++++++++++-- fs/smb/client/transport.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 5c5b76a9e9fefb..d74cc9e8c413d1 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -802,6 +802,7 @@ struct TCP_Server_Info { bool posix_ext_supported; struct delayed_work reconnect; /* reconnect workqueue job */ struct mutex reconnect_mutex; /* prevent simultaneous reconnects */ + bool need_sock_shutdown; /* shutdown socket when need to reconnect */ unsigned long echo_interval; /* diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 28e1ddeb618207..ff8637301fa9bd 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -130,8 +130,11 @@ void smb2_query_server_interfaces(struct work_struct *work) #define set_need_reco(server) \ do { \ spin_lock(&server->srv_lock); \ - if (server->tcpStatus != CifsExiting) \ + if (server->tcpStatus != CifsExiting) { \ + if (server->tcpStatus != CifsNeedReconnect) \ + server->need_sock_shutdown = true; \ server->tcpStatus = CifsNeedReconnect; \ + } \ spin_unlock(&server->srv_lock); \ } while (0) @@ -353,6 +356,8 @@ cifs_abort_connection(struct TCP_Server_Info *server) static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets) { + bool shutdown; + spin_lock(&server->srv_lock); server->nr_targets = num_targets; if (server->tcpStatus == CifsExiting) { @@ -365,9 +370,25 @@ static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num cifs_dbg(FYI, "Mark tcp session as need reconnect\n"); trace_smb3_reconnect(server->current_mid, server->conn_id, server->hostname); - server->tcpStatus = CifsNeedReconnect; + /* + * Cover cases where sender tasks didn't manage to shutdown the socket + * for some reason. + */ + shutdown = server->tcpStatus != CifsNeedReconnect || + server->need_sock_shutdown; + server->need_sock_shutdown = false; + server->tcpStatus = CifsNeedReconnect; spin_unlock(&server->srv_lock); + + if (shutdown) { + cifs_server_lock(server); + if (server->ssocket) + /* Don't release it here/yet! */ + kernel_sock_shutdown(server->ssocket, SHUT_RDWR); + cifs_server_unlock(server); + } + return true; } diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c index 6e21b5f8754a1e..cf11af490b0d87 100644 --- a/fs/smb/client/transport.c +++ b/fs/smb/client/transport.c @@ -179,6 +179,17 @@ smb_send_kvec(struct TCP_Server_Info *server, struct msghdr *smb_msg, * to avoid unnecessary reconnects. */ rc = sock_sendmsg(ssocket, smb_msg); + + /* + * If need to reconnect, blame it for any non-interrupt error + * and bail out early, even if -EAGAIN; reconnect will never + * happen in parallel as cifs_reconnect() needs server mutex + * (which we're already holding), so it's pointless to retry. + */ + if (unlikely(rc <= 0 && !is_interrupt_error(rc) && + READ_ONCE(server->tcpStatus) == CifsNeedReconnect)) + return -ECONNRESET; + if (rc == -EAGAIN || unlikely(rc == -EINTR && task_work_pending(current))) { retries++; if (retries >= 14 || @@ -654,6 +665,26 @@ int wait_for_response(struct TCP_Server_Info *server, struct mid_q_entry *mid) return 0; } +static void cond_sock_shutdown(struct TCP_Server_Info *server) +{ + bool shutdown; + + lockdep_assert_held(&server->_srv_mutex); + + spin_lock(&server->srv_lock); + /* + * No need to check status, ->need_sock_shutdown is only set when it's + * CifsNeedReconnect. + */ + shutdown = server->need_sock_shutdown && server->ssocket; + server->need_sock_shutdown = false; + spin_unlock(&server->srv_lock); + + if (shutdown) + /* Don't release it here/yet! */ + kernel_sock_shutdown(server->ssocket, SHUT_RDWR); +} + /* * Send a SMB request and set the callback function in the mid to handle * the result. Caller is responsible for dealing with timeouts. @@ -724,6 +755,7 @@ cifs_call_async(struct TCP_Server_Info *server, struct smb_rqst *rqst, revert_current_mid(server, mid->credits); server->sequence_number -= 2; delete_mid(server, mid); + cond_sock_shutdown(server); } cifs_server_unlock(server); @@ -982,6 +1014,7 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses, delete_mid(server, mid[i]); cancelled_mid[i] = true; } + cond_sock_shutdown(server); } cifs_server_unlock(server); From 031278577380e7875d95aed81a2b2bcac39be923 Mon Sep 17 00:00:00 2001 From: Henry Martin Date: Sat, 10 Oct 2026 11:28:56 +0800 Subject: [PATCH 1747/2286] smb: client: fix use-after-free in cifs_ses_add_channel() The error path of cifs_ses_add_channel() calls cifs_put_tcp_session() before cifs_chan_clear_need_reconnect(). The latter reaches cifs_ses_get_chan_index(), which dereferences server->terminate; if the put was the final reference, TCP_Server_Info is already freed and this is a use-after-free. Same put-then-use pattern as the recently fixed sibling in commit 717e0a25036b ("cifs: Fix server use-after-free in cifs_chan_skip_or_disable()"). Move the put last, but do not read chan->server after dropping chan_lock: chan points into ses->chans[], and once chan_count is decremented a concurrent add_channel can reuse the slot and overwrite the pointer, so the put would act on the new channel's server. Cache the pointer under chan_lock, clear the reconnect bit and decrement chan_count while still holding the reference, and release it after the unlock. This issue was discovered by Tencent CodeBuddy Security. Fixes: ee1d21794e55a ("cifs: handle when server stops supporting multichannel") Signed-off-by: Henry Martin Cc: stable@vger.kernel.org Reviewed-by: Namjae Jeon Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/sess.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index e095f41b58828b..0ab3f37d480949 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -626,12 +626,13 @@ cifs_ses_add_channel(struct cifs_ses *ses, out: if (rc && chan->server) { - cifs_put_tcp_session(chan->server, 0); + struct TCP_Server_Info *pserver; spin_lock(&ses->chan_lock); + pserver = chan->server; /* we rely on all bits beyond chan_count to be clear */ - cifs_chan_clear_need_reconnect(ses, chan->server); + cifs_chan_clear_need_reconnect(ses, pserver); ses->chan_count--; /* * chan_count should never reach 0 as at least the primary @@ -639,6 +640,7 @@ cifs_ses_add_channel(struct cifs_ses *ses, */ WARN_ON(ses->chan_count < 1); spin_unlock(&ses->chan_lock); + cifs_put_tcp_session(pserver, 0); } kfree(ctx->UNC); From 59131dd72f62b198fc9ea1489ad9d3573154f684 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:30 +0100 Subject: [PATCH 1748/2286] smb: client: fix use-after-free infoleak via the readdir resume name CIFSFindNext() copies the resume name that cifs_save_resume_key() recorded from the last entry of the previous response into its request; psrch_inf->presume_name points into the response buffer that entry came from, and nothing ever clears it. find_cifs_entry() records a new name only from a response with a usable last entry, and cifs_readdir() only from an entry it walks to, so a response that carries no entries and leaves last_entry NULL, because CIFSFindFirst() or CIFSFindNext() rejected LastNameOffset, refreshes neither and presume_name keeps pointing into the previous response. That buffer has already been released: by CIFSFindNext() before it installs the new response, or by find_cifs_entry() when it rewinds the search, which does not drop the name either and can then fail in initiate_cifs_search() before a new response replaces it. The next CIFSFindNext() then copies up to PATH_MAX - 1 bytes of the resume name out of the released buffer into its request and sends them to the server: a use-after-free read that leaks kernel memory to a malicious or compromised server, which can provoke it with a single FindNext response that carries no entries and a LastNameOffset above CIFSMaxBufSize, which is all the existing check rejects. With KASAN enabled, a server that answers a listing that way gives: CIFS: VFS: ignoring corrupt resume name ================================================================== BUG: KASAN: slab-use-after-free in CIFSFindNext+0x8ca/0x14c0 Read of size 4080 at addr ffff88807c823f98 by task ls/83 CPU: 0 UID: 0 PID: 83 Comm: ls Tainted: G B 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy) Tainted: [B]=BAD_PAGE Call Trace: kasan_report+0xdf/0x1a0 kasan_check_range+0x10f/0x1e0 __asan_memcpy+0x23/0x60 CIFSFindNext+0x8ca/0x14c0 cifs_readdir+0xf51/0x29c0 iterate_dir+0x1c0/0x570 __x64_sys_getdents64+0x133/0x270 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 83 on cpu 1 at 13.497119s: cifs_buf_get+0x36/0x90 smb_init+0x4f/0x110 CIFSFindNext+0xf7/0x14c0 cifs_readdir+0xf51/0x29c0 Freed by task 83 on cpu 0 at 13.584018s: cifs_buf_release+0x41/0x80 CIFSFindNext+0xfce/0x14c0 cifs_readdir+0xf51/0x29c0 The buggy address is located 16280 bytes inside of freed 16588-byte region [ffff88807c820000, ffff88807c8240cc) ================================================================== The same memcpy() first reads past the end of that object while it is still live, a slab-out-of-bounds read that the following patches bound; that is the taint the report above carries. SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Clear presume_name and resume_name_len whenever a new response is installed and when the rewind path releases the search buffer, so that the name never outlives the buffer it points into, and reset resume_key with them, since it was taken from the same entry and would otherwise be sent with an empty name. A response without a usable last entry then continues the search with an empty resume name rather than a stale one; CIFSFindNext() skips the copy of a zero-length name so that it never runs on the NULL pointer. The next patch in the series leaves last_entry NULL for a response that carries no entries, and bounds LastNameOffset against the received response rather than against CIFSMaxBufSize. Responses that are given a last entry today then take the path fixed here, so that patch must not be applied without this one. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Fixes: b77d753c413e ("[CIFS] Check that last search entry resume key is valid") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 11 ++++++++++- fs/smb/client/readdir.c | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 6dddbd84b93b57..95f339f48ad721 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -4543,6 +4543,9 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount); psrch_inf->index_of_last_entry = 2 /* skip . and .. */ + psrch_inf->entries_in_buffer; + psrch_inf->presume_name = NULL; + psrch_inf->resume_name_len = 0; + psrch_inf->resume_key = 0; lnoff = le16_to_cpu(parms->LastNameOffset); if (CIFSMaxBufSize < lnoff) { cifs_dbg(VFS, "ignoring corrupt resume name\n"); @@ -4607,7 +4610,10 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, name_len = psrch_inf->resume_name_len; params += name_len; if (name_len < PATH_MAX) { - memcpy(pSMB->ResumeFileName, psrch_inf->presume_name, name_len); + if (name_len) { + memcpy(pSMB->ResumeFileName, + psrch_inf->presume_name, name_len); + } byte_count += name_len; /* 14 byte parm len above enough for 2 byte null terminator */ pSMB->ResumeFileName[name_len] = 0; @@ -4662,6 +4668,9 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, psrch_inf->endOfSearch = !!parms->EndofSearch; psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount); psrch_inf->index_of_last_entry += psrch_inf->entries_in_buffer; + psrch_inf->presume_name = NULL; + psrch_inf->resume_name_len = 0; + psrch_inf->resume_key = 0; lnoff = le16_to_cpu(parms->LastNameOffset); if (CIFSMaxBufSize < lnoff) { cifs_dbg(VFS, "ignoring corrupt resume name\n"); diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 9530e5b0156429..6ab4e687c3e48b 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -752,6 +752,9 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos, cfile->srch_inf.ntwrk_buf_start = NULL; cfile->srch_inf.srch_entries_start = NULL; cfile->srch_inf.last_entry = NULL; + cfile->srch_inf.presume_name = NULL; + cfile->srch_inf.resume_name_len = 0; + cfile->srch_inf.resume_key = 0; } rc = initiate_cifs_search(xid, file, full_path); if (rc) { From 570c783a96f054dd3ac409a92772e9afb705d21c Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:31 +0100 Subject: [PATCH 1749/2286] smb: client: fix OOB last_entry pointer from unbounded LastNameOffset CIFSFindFirst() and CIFSFindNext() locate the last entry of a TRANS2_FIND_FIRST2 or TRANS2_FIND_NEXT2 response by adding the LastNameOffset of the response parameters to the start of the data area, and store the result in psrch_inf->last_entry. MS-CIFS 2.2.6.2.2 defines that offset as the offset of the entry's file name; the client follows the reading of NT and Samba servers, which point it at the entry. LastNameOffset is only checked against CIFSMaxBufSize, which says nothing about the response actually received: validate_t2() lets the data area start up to 1024 bytes into the buffer, and SendReceive() copies at most CIFSMaxBufSize + MAX_CIFS_HDR_SIZE bytes of a response, 16468 by default, into a cifs_request allocation of CIFSMaxBufSize + MAX_SMB2_HDR_SIZE bytes, 16588 by default. The pointer itself can therefore be placed anywhere from the end of the received data to 820 bytes past the end of the allocation. The read through that pointer is far larger than its displacement. find_cifs_entry() hands last_entry to cifs_save_resume_key() after every FindNext and after the FindFirst of a search rewind, and cifs_fill_dirent() takes the length of the name from the entry itself: a __le32 FileNameLength for most levels, a __u8 for SMB_FIND_FILE_INFO_STANDARD, and for SMB_FIND_FILE_UNIX the result of a scan for the terminating NUL over up to PATH_MAX + 1 UTF-16 units. The name is recorded as the resume name of the search, and the next CIFSFindNext() rejects only a recorded length of PATH_MAX or more before copying the name into the ResumeFileName of its request and sending it with the resume key taken from the same entry: up to 4 KiB of memory from past the end of the allocation leaks to the server, and for SMB_FIND_FILE_UNIX the NUL scan runs about twice as far before the length is even known. The entry does not have to lie outside the response for that either: a maximal response ends about 120 bytes before the end of the allocation, so an entry in its last bytes with a name just under PATH_MAX has nearly all of that name copied from past the allocation. It needs nothing unusual from the client: a FindNext response that reports no entries and does not end the search makes find_cifs_entry() send the next FindNext at once, carrying whatever was parsed at LastNameOffset, and a search rewind can do the same after its FindFirst. SMB1 is not negotiated by default: this takes a malicious or compromised server, a mount of it with an explicit vers=1.0, which CONFIG_CIFS_ALLOW_INSECURE_LEGACY (default y) permits, and a directory listing on that mount. With KASAN enabled, a response that places the last entry past the buffer, with a large DataOffset and a LastNameOffset near the end of its range, gives: ================================================================== BUG: KASAN: slab-out-of-bounds in cifs_save_resume_key.isra.0+0x7aa/0x7f0 Read of size 4 at addr ffff88807b55c33b by task ls/83 CPU: 1 UID: 0 PID: 83 Comm: ls Not tainted 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy) Call Trace: kasan_report+0xdf/0x1a0 cifs_save_resume_key.isra.0+0x7aa/0x7f0 cifs_readdir+0x1ed6/0x29c0 iterate_dir+0x1c0/0x570 __x64_sys_getdents64+0x133/0x270 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 83 on cpu 0 at 19.317556s: cifs_buf_get+0x36/0x90 smb_init+0x4f/0x110 CIFSFindNext+0xf7/0x14c0 cifs_readdir+0xf51/0x29c0 The buggy address belongs to the object at ffff88807b558000 which belongs to the cache cifs_request of size 16588 The buggy address is located 623 bytes to the right of allocated 16588-byte region [ffff88807b558000, ffff88807b55c0cc) ================================================================== The four bytes read there are the FileNameLength of the entry, which cifs_fill_dirent() takes before anything checks where the entry lies. Ignore the last entry when it does not start inside the received response or the response holds no entries; for a response without entries, last_entry pointed into or past its empty data area and cifs_save_resume_key() parsed whatever the buffer held there. The entry is bounded by the response that was received, which is what the buffer holds, rather than by the data area the response declares, which a later patch checks against the received length. Record the search handle before the last entry is examined, since a response without a usable last entry still carries the handle that the search is continued and closed with; leaving the assignment in the else branch would drop the handle of every response without entries. This bounds where the last entry starts, not how far it is parsed: an entry that starts within the final bytes of the response still has its fixed part read past the end of the received data and its name, up to 4095 bytes, copied from past the end of the allocation, until the following patch bounds the parse itself. A response without a usable last entry continues the search with an empty resume name, since the preceding patch, "smb: client: fix use-after-free infoleak via the readdir resume name", drops the recorded name whenever a new response is installed. This patch applies on top of that one and relies on it: without it, every response rejected here would leave the previous name in place, pointing into a buffer that has been released. A conforming server is not affected: the last entry lies inside the data area of the response. Fixes: 0752f1522a91 ("[CIFS] make sure we have the right resume info before calling CIFSFindNext") Fixes: b77d753c413e ("[CIFS] Check that last search entry resume key is valid") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 95f339f48ad721..d8b1740b50fabb 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -4546,14 +4546,17 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, psrch_inf->presume_name = NULL; psrch_inf->resume_name_len = 0; psrch_inf->resume_key = 0; + if (pnetfid) + *pnetfid = parms->SearchHandle; lnoff = le16_to_cpu(parms->LastNameOffset); - if (CIFSMaxBufSize < lnoff) { + if (!psrch_inf->entries_in_buffer) { + psrch_inf->last_entry = NULL; + } else if (psrch_inf->srch_entries_start + lnoff >= + psrch_inf->ntwrk_buf_start + bytes_returned) { cifs_dbg(VFS, "ignoring corrupt resume name\n"); psrch_inf->last_entry = NULL; } else { psrch_inf->last_entry = psrch_inf->srch_entries_start + lnoff; - if (pnetfid) - *pnetfid = parms->SearchHandle; } return 0; } @@ -4672,7 +4675,10 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, psrch_inf->resume_name_len = 0; psrch_inf->resume_key = 0; lnoff = le16_to_cpu(parms->LastNameOffset); - if (CIFSMaxBufSize < lnoff) { + if (!psrch_inf->entries_in_buffer) { + psrch_inf->last_entry = NULL; + } else if (psrch_inf->srch_entries_start + lnoff >= + psrch_inf->ntwrk_buf_start + bytes_returned) { cifs_dbg(VFS, "ignoring corrupt resume name\n"); psrch_inf->last_entry = NULL; } else { From f8171804a39ee89a11cd462f67862899de9c6b10 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:32 +0100 Subject: [PATCH 1750/2286] smb: client: fix OOB read of the resume name sent in TRANS2_FIND_NEXT2 cifs_fill_dirent() reads the fixed part of a directory entry and the name length it holds, or for SMB_FIND_FILE_UNIX scans the name for its terminating NUL, without knowing where the response ends. cifs_save_resume_key() calls it for the entry at the LastNameOffset the server chose, after every FindNext and after the FindFirst of a search rewind, and records the name it returns as the resume name of the search without bounding it; the next CIFSFindNext() copies up to PATH_MAX - 1 bytes from that name into its request and sends them to the server. cifs_filldir() bounds the name against the end of the response since commit f8cf09a53a0d ("smb: client: bound dirent name against end of SMB response in cifs_filldir"), but only after cifs_fill_dirent() has read the entry, and nxt_dir_entry() checks the entries it walks only against sizeof(FILE_DIRECTORY_INFO), for every level but SMB_FIND_FILE_INFO_STANDARD, and never sees the first entry of a response. Bounding where last_entry starts, as the preceding patch does, does not bound the read. A last entry that starts within the final bytes of the response still has its fixed part read past the end of the received data, and the name length it declares, a __le32 for every level but SMB_FIND_FILE_INFO_STANDARD and SMB_FIND_FILE_UNIX, is recorded without being checked against anything. CIFSFindNext() rejects only a length of PATH_MAX or more, then copies the name, up to 4095 bytes, into its request and sends it, and as the report below shows that copy can run past the end of the cifs_request allocation that holds the response. For SMB_FIND_FILE_UNIX the name is in addition scanned for its terminating NUL over up to PATH_MAX + 1 UTF-16 units, or PATH_MAX bytes, before any check. The FindNext that carries the name is only sent by the SMB1 dialect, which is not negotiated by default; reaching it requires an explicit vers=1.0 mount. With KASAN enabled, a response whose last entry declares a name longer than the bytes that follow it gives: ================================================================== BUG: KASAN: slab-out-of-bounds in CIFSFindNext+0x8ca/0x14c0 Read of size 4080 at addr ffff88807c823f98 by task ls/83 CPU: 0 UID: 0 PID: 83 Comm: ls Not tainted 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy) Call Trace: kasan_report+0xdf/0x1a0 kasan_check_range+0x10f/0x1e0 __asan_memcpy+0x23/0x60 CIFSFindNext+0x8ca/0x14c0 cifs_readdir+0xf51/0x29c0 iterate_dir+0x1c0/0x570 __x64_sys_getdents64+0x133/0x270 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 83 on cpu 1 at 13.497119s: cifs_buf_get+0x36/0x90 smb_init+0x4f/0x110 CIFSFindNext+0xf7/0x14c0 cifs_readdir+0xf51/0x29c0 The buggy address belongs to the object at ffff88807c820000 which belongs to the cache cifs_request of size 16588 The buggy address is located 16280 bytes inside of allocated 16588-byte region [ffff88807c820000, ffff88807c8240cc) ================================================================== The read is the memcpy() that copies the recorded name into the next request, so those bytes go to the server. Pass the end of the response to cifs_fill_dirent() and have it reject an entry whose fixed part does not fit or whose name extends past the end, before either is used, so that both callers are covered; cifs_save_resume_key() computes the end the same way cifs_readdir() does. The name bound compares the bytes left between the name and the end with the name length rather than adding the length to the name pointer, since the length is a 32-bit value taken from the entry and the sum could wrap on 32-bit systems. cifs_fill_dirent() is also used on the SMB2 readdir path; there smb2_parse_query_directory() has already checked every entry against the end of the response, so a valid SMB2 response is not rejected by the new checks. The check cifs_filldir() applies after the parse is left in place. The NUL scan for SMB_FIND_FILE_UNIX names is capped by a separate patch. An entry and its name lie inside the data area of the response, so a server that points LastNameOffset at the entry is not affected. One that points it at the file name, as MS-CIFS 2.2.6.2.2 describes, has its last entry rejected here; the listing is unaffected, because cifs_readdir() records the resume name from an entry it emitted, and before this series such a response could instead end the listing early, since a name length read from the wrong place is usually PATH_MAX or more, which CIFSFindNext() rejects with -EINVAL. On trees before v6.19 the type for the level 0x105 fixed part is spelled SEARCH_ID_FULL_DIR_INFO; adjust when backporting. Fixes: 0752f1522a91 ("[CIFS] make sure we have the right resume info before calling CIFSFindNext") Cc: stable@vger.kernel.org # 6.1.x: f8cf09a53a0d: smb: client: bound dirent name against end of SMB response in cifs_filldir Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/readdir.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 6ab4e687c3e48b..441cafcdc8890d 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -586,30 +586,46 @@ static void cifs_fill_dirent_std(struct cifs_dirent *de, } static int cifs_fill_dirent(struct cifs_dirent *de, const void *info, - u16 level, bool is_unicode) + const char *end, u16 level, bool is_unicode) { + size_t len = end > (const char *)info ? end - (const char *)info : 0; + memset(de, 0, sizeof(*de)); switch (level) { case SMB_FIND_FILE_POSIX_INFO: + if (len < sizeof(struct smb2_posix_info)) + goto too_short; cifs_fill_dirent_posix(de, info); break; case SMB_FIND_FILE_UNIX: + if (len < offsetof(FILE_UNIX_INFO, FileName)) + goto too_short; cifs_fill_dirent_unix(de, info, is_unicode); break; case SMB_FIND_FILE_DIRECTORY_INFO: + if (len < offsetof(FILE_DIRECTORY_INFO, FileName)) + goto too_short; cifs_fill_dirent_dir(de, info); break; case SMB_FIND_FILE_FULL_DIRECTORY_INFO: + if (len < offsetof(FILE_FULL_DIRECTORY_INFO, FileName)) + goto too_short; cifs_fill_dirent_full(de, info); break; case SMB_FIND_FILE_ID_FULL_DIR_INFO: + if (len < offsetof(FILE_ID_FULL_DIR_INFO, FileName)) + goto too_short; cifs_fill_dirent_search(de, info); break; case SMB_FIND_FILE_BOTH_DIRECTORY_INFO: + if (len < offsetof(FILE_BOTH_DIRECTORY_INFO, FileName)) + goto too_short; cifs_fill_dirent_both(de, info); break; case SMB_FIND_FILE_INFO_STANDARD: + if (len < offsetof(FIND_FILE_STANDARD_INFO, FileName)) + goto too_short; cifs_fill_dirent_std(de, info); break; default: @@ -617,7 +633,19 @@ static int cifs_fill_dirent(struct cifs_dirent *de, const void *info, return -EINVAL; } + if (de->name && + (end < de->name || (size_t)(end - de->name) < de->namelen)) { + cifs_dbg(VFS, "%s: search entry name extends past end of SMB\n", + __func__); + return -EINVAL; + } + return 0; + +too_short: + cifs_dbg(VFS, "%s: search entry extends past end of SMB\n", + __func__); + return -EINVAL; } #define UNICODE_DOT cpu_to_le16(0x2e) @@ -672,10 +700,14 @@ static int is_dir_changed(struct file *file) static int cifs_save_resume_key(const char *current_entry, struct cifsFileInfo *file_info) { + struct TCP_Server_Info *server = tlink_tcon(file_info->tlink)->ses->server; + char *buf = file_info->srch_inf.ntwrk_buf_start; + const char *end = buf + server->ops->calc_smb_size(buf); struct cifs_dirent de; int rc; - rc = cifs_fill_dirent(&de, current_entry, file_info->srch_inf.info_level, + rc = cifs_fill_dirent(&de, current_entry, end, + file_info->srch_inf.info_level, file_info->srch_inf.unicode); if (!rc) { file_info->srch_inf.presume_name = de.name; @@ -977,7 +1009,8 @@ static int cifs_filldir(char *find_entry, struct file *file, struct qstr name; int rc = 0; - rc = cifs_fill_dirent(&de, find_entry, file_info->srch_inf.info_level, + rc = cifs_fill_dirent(&de, find_entry, end_of_smb, + file_info->srch_inf.info_level, file_info->srch_inf.unicode); if (rc) return rc; From fa0c2f082946917c3f07d2475c8ce73a8c96d0e2 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:33 +0100 Subject: [PATCH 1751/2286] smb: client: fix OOB read in the SMB_FIND_FILE_UNIX name scan cifs_fill_dirent_unix() finds the length of an SMB_FIND_FILE_UNIX entry name by scanning for its terminating NUL, over up to PATH_MAX + 1 UTF-16 units in cifs_unicode_bytelen() or PATH_MAX bytes in strnlen(). Neither scan knows where the response ends. cifs_fill_dirent() only requires the fixed part of the entry to fit, so the name can start exactly at the end of the received data, and a name that holds no terminator is then scanned for 8194 bytes, or 4096 bytes without unicode, past the end of the received data. SendReceive() copies at most 16468 bytes into the 16588-byte cifs_request allocation, so for a response of that length up to 8074 of the bytes scanned lie past the end of the allocation. Before the preceding patches bounded where the entry starts, the entry itself could start up to 820 bytes past the end of the allocation at the default CIFSMaxBufSize, and its name 928 bytes past it, so all 8194 scanned bytes would lie outside. cifs_fill_dirent() rejects the name afterwards because it extends past the end of the response, so nothing of what was read is used, but the out-of-bounds read itself remains. SMB_FIND_FILE_UNIX is only used with SMB1 servers that offer the Unix extensions; SMB1 is not negotiated by default and requires an explicit vers=1.0 mount. Before the preceding patches, an entry placed past the buffer left the scan out of bounds from its first read. They bound where the entry starts, but nothing bounds the scan, so a name that holds no terminator still runs off the end of the allocation. Reproduced on the unpatched tree with KASAN enabled, for an entry that starts inside the response, which is the case the preceding patches leave open: ================================================================== BUG: KASAN: slab-out-of-bounds in cifs_save_resume_key.isra.0+0x75c/0x7f0 Read of size 2 at addr ffff88807ee640cc by task ls/84 CPU: 0 UID: 0 PID: 84 Comm: ls Not tainted 7.3.0-rc4-00457-gf14572c203d5 #69 PREEMPT(lazy) Call Trace: kasan_report+0xdf/0x1a0 cifs_save_resume_key.isra.0+0x75c/0x7f0 cifs_readdir+0x1ed6/0x29c0 iterate_dir+0x1c0/0x570 __x64_sys_getdents64+0x133/0x270 do_syscall_64+0x109/0x5d0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Allocated by task 84 on cpu 1 at 15.751078s: cifs_buf_get+0x36/0x90 smb_init+0x4f/0x110 CIFSFindNext+0xf7/0x14c0 cifs_readdir+0xf51/0x29c0 The buggy address belongs to the object at ffff88807ee60000 which belongs to the cache cifs_request of size 16588 The buggy address is located 0 bytes to the right of allocated 16588-byte region [ffff88807ee60000, ffff88807ee640cc) ================================================================== The two bytes read there are one UTF-16 unit of the scan, at the first byte past the end of the object. The scan carries on from there, reported again 2, 4 and 6 bytes to the right. Pass the number of bytes that follow the fixed part of the entry to cifs_fill_dirent_unix() and stop both scans there. A scan that reaches the end of the response without finding a terminator now fails: it returns a name that ends exactly at the end of the response and would otherwise pass the bound, so cifs_fill_dirent() requires the position of the terminator of an SMB_FIND_FILE_UNIX name to lie inside the response as well. Accepting such a name truncated at the end of the response would turn a malformed entry into a directory entry, or a resume name, that no server sent. A name longer than the PATH_MAX cap of the scans is still truncated there, as before, when the response extends past the cap. The message cifs_unicode_bytelen() logs when it finds no terminator now covers both limits. This patch depends on "smb: client: fix OOB read of the resume name sent in TRANS2_FIND_NEXT2", which adds the end argument of cifs_fill_dirent() and the length it derives from it; the backport note in that patch applies to this one as well. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/readdir.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 441cafcdc8890d..1667bba40e4454 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -456,17 +456,20 @@ initiate_cifs_search(const unsigned int xid, struct file *file, } /* return length of unicode string in bytes */ -static int cifs_unicode_bytelen(const char *str) +static size_t cifs_unicode_bytelen(const char *str, size_t maxlen) { - int len; const __le16 *ustr = (const __le16 *)str; + size_t len; - for (len = 0; len <= PATH_MAX; len++) { + maxlen = umin(PATH_MAX + 1, maxlen / sizeof(__le16)); + for (len = 0; len < maxlen; len++) { if (ustr[len] == 0) - return len << 1; + return len * sizeof(__le16); } - cifs_dbg(FYI, "Unicode string longer than PATH_MAX found\n"); - return len << 1; + len *= sizeof(__le16); + cifs_dbg(FYI, "%s: Unicode string not terminated within %zu bytes\n", + __func__, len); + return len; } static char *nxt_dir_entry(char *old_entry, char *end_of_smb, int level) @@ -532,13 +535,13 @@ static void cifs_fill_dirent_posix(struct cifs_dirent *de, } static void cifs_fill_dirent_unix(struct cifs_dirent *de, - const FILE_UNIX_INFO *info, bool is_unicode) + const FILE_UNIX_INFO *info, bool is_unicode, size_t maxlen) { de->name = &info->FileName[0]; if (is_unicode) - de->namelen = cifs_unicode_bytelen(de->name); + de->namelen = cifs_unicode_bytelen(de->name, maxlen); else - de->namelen = strnlen(de->name, PATH_MAX); + de->namelen = strnlen(de->name, min_t(size_t, maxlen, PATH_MAX)); de->resume_key = info->ResumeKey; de->ino = le64_to_cpu(info->basic.UniqueId); } @@ -589,6 +592,7 @@ static int cifs_fill_dirent(struct cifs_dirent *de, const void *info, const char *end, u16 level, bool is_unicode) { size_t len = end > (const char *)info ? end - (const char *)info : 0; + size_t nul_len = 0; memset(de, 0, sizeof(*de)); @@ -601,7 +605,9 @@ static int cifs_fill_dirent(struct cifs_dirent *de, const void *info, case SMB_FIND_FILE_UNIX: if (len < offsetof(FILE_UNIX_INFO, FileName)) goto too_short; - cifs_fill_dirent_unix(de, info, is_unicode); + cifs_fill_dirent_unix(de, info, is_unicode, + len - offsetof(FILE_UNIX_INFO, FileName)); + nul_len = is_unicode ? sizeof(__le16) : sizeof(__u8); break; case SMB_FIND_FILE_DIRECTORY_INFO: if (len < offsetof(FILE_DIRECTORY_INFO, FileName)) @@ -634,7 +640,8 @@ static int cifs_fill_dirent(struct cifs_dirent *de, const void *info, } if (de->name && - (end < de->name || (size_t)(end - de->name) < de->namelen)) { + (end < de->name || + (size_t)(end - de->name) < de->namelen + nul_len)) { cifs_dbg(VFS, "%s: search entry name extends past end of SMB\n", __func__); return -EINVAL; From 29bd6773187cb5c1bbbdf90d677c6add7d58af26 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:34 +0100 Subject: [PATCH 1752/2286] smb: client: reject FIND data areas that run past the received response CIFSFindFirst() and CIFSFindNext() take the directory entries of a TRANS2_FIND_FIRST2 or TRANS2_FIND_NEXT2 response from DataOffset for DataCount bytes. validate_t2() bounds DataOffset at 1024 and ParameterCount + DataCount by the byte count, and nothing compares DataOffset + DataCount with the number of bytes SendReceive() copied into the buffer. smb_init() places the request and the response in the same cifs_request allocation and cifs_buf_get() clears only the start of it, so a short response whose data area extends past its end has srch_entries_start point at bytes of the request that was just sent, or at whatever the allocation held before, and those bytes are parsed as directory entries. cifs_readdir() and find_cifs_entry() walk the entries with nxt_dir_entry(), which checks every entry but the first of a response against the end of the SMB; cifs_query_path_info() and cifs_backup_query_path_info() read the first entry with no check at all. Those reads stay inside the allocation, but what they return is not part of the response. The last entry, which LastNameOffset locates relative to the data area, is bounded against the received response by an earlier patch of this series, as is every entry that cifs_fill_dirent() parses; the walk itself and the two query functions are not. SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Reject a response whose data area extends past the received length, with the -EINVAL that validate_t2() returns for the other malformed transaction responses. cifs_query_path_info() and cifs_backup_query_path_info() still read a whole entry from a data area that may be smaller than one; that is not changed here. A conforming server is not affected. The data block of a transaction response is part of the SMB, so DataOffset + DataCount cannot exceed the smbCalcSize() bytes that SendReceive() copies; coalesce_t2() appends each secondary response to the data area and adds its length to both DataCount and the byte count, which keeps that relation. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index d8b1740b50fabb..bb71fed940a5b8 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -4410,8 +4410,9 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, /* level 257 SMB_ */ TRANSACTION2_FFIRST_REQ *pSMB = NULL; TRANSACTION2_FFIRST_RSP *pSMBr = NULL; - T2_FFIRST_RSP_PARMS *parms; + unsigned int data_off, data_count; struct nls_table *nls_codepage; + T2_FFIRST_RSP_PARMS *parms; unsigned int in_len, lnoff; __u16 params, byte_count; int bytes_returned = 0; @@ -4530,11 +4531,19 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, return rc; } + data_off = le16_to_cpu(pSMBr->t2.DataOffset); + data_count = le16_to_cpu(pSMBr->t2.DataCount); + if (data_off + data_count > (unsigned int)bytes_returned) { + cifs_dbg(VFS, "%s: data area offset %u count %u past response length %d\n", + __func__, data_off, data_count, bytes_returned); + cifs_buf_release(pSMB); + return -EINVAL; + } + psrch_inf->unicode = !!(pSMBr->hdr.Flags2 & SMBFLG2_UNICODE); psrch_inf->ntwrk_buf_start = (char *)pSMBr; psrch_inf->smallBuf = false; - psrch_inf->srch_entries_start = (char *)&pSMBr->hdr.Protocol + - le16_to_cpu(pSMBr->t2.DataOffset); + psrch_inf->srch_entries_start = (char *)&pSMBr->hdr.Protocol + data_off; parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + le16_to_cpu(pSMBr->t2.ParameterOffset)); @@ -4567,8 +4576,9 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, { TRANSACTION2_FNEXT_REQ *pSMB = NULL; TRANSACTION2_FNEXT_RSP *pSMBr = NULL; - T2_FNEXT_RSP_PARMS *parms; + unsigned int data_off, data_count; unsigned int name_len, in_len; + T2_FNEXT_RSP_PARMS *parms; unsigned int lnoff; __u16 params, byte_count; char *response_data; @@ -4652,13 +4662,22 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, cifs_buf_release(pSMB); return rc; } + + data_off = le16_to_cpu(pSMBr->t2.DataOffset); + data_count = le16_to_cpu(pSMBr->t2.DataCount); + if (data_off + data_count > (unsigned int)bytes_returned) { + cifs_dbg(VFS, "%s: data area offset %u count %u past response length %d\n", + __func__, data_off, data_count, bytes_returned); + cifs_buf_release(pSMB); + return -EINVAL; + } + /* BB fixme add lock for file (srch_info) struct here */ psrch_inf->unicode = !!(pSMBr->hdr.Flags2 & SMBFLG2_UNICODE); response_data = (char *)&pSMBr->hdr.Protocol + le16_to_cpu(pSMBr->t2.ParameterOffset); parms = (T2_FNEXT_RSP_PARMS *)response_data; - response_data = (char *)&pSMBr->hdr.Protocol + - le16_to_cpu(pSMBr->t2.DataOffset); + response_data = (char *)&pSMBr->hdr.Protocol + data_off; if (psrch_inf->smallBuf) cifs_small_buf_release(psrch_inf->ntwrk_buf_start); From 531aa4239b0d80d7146b102b98f7aa7da989ccdd Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:35 +0100 Subject: [PATCH 1753/2286] smb: client: reject short or out-of-range FIND response parameters CIFSFindFirst() and CIFSFindNext() read the SearchHandle, SearchCount, EndofSearch and LastNameOffset of a TRANS2_FIND_FIRST2 or TRANS2_FIND_NEXT2 response at ParameterOffset. validate_t2() bounds the offset at 1024 and ParameterCount below 512, and nothing compares them with the number of bytes SendReceive() copied into the buffer or with the size of the response parameters. smb_init() places the request and the response in the same cifs_request allocation and cifs_buf_get() clears only the start of it, so a short response with a ParameterOffset past its end has the search handle, the entry count, the end-of-search flag and the last-entry offset read out of the request that was just sent, or out of whatever the allocation held before, and a ParameterCount smaller than the parameters has them read past the end of the parameter block. The reads stay inside the allocation, but what they return is not part of the response, and CIFSFindFirst() then hands back a search handle the server never sent. That handle is sent back to the server in the next FIND_NEXT2 or FIND_CLOSE2, so two bytes of the buffer leak at an offset the server picks. SMB1 is not negotiated by default; reaching this code requires an explicit vers=1.0 mount. Reject a response whose parameter area is smaller than the response parameters or extends past the received length, returning smb_EIO2() with a new smb_eio_traces value for each of the two responses, like the other response checks this file gained since v6.19. Backports to trees before v6.19 need smb_EIO2() replaced with -EINVAL and the trace.h hunk dropped. A conforming server is not affected. The parameter block of a transaction response is part of the SMB, so ParameterOffset + ParameterCount cannot exceed the smbCalcSize() bytes that SendReceive() copies, and the response parameters are 10 bytes for FIND_FIRST2 and 8 bytes for FIND_NEXT2, which is what T2_FFIRST_RSP_PARMS and T2_FNEXT_RSP_PARMS hold. This patch depends on "smb: client: reject FIND data areas that run past the received response", whose declarations it extends. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org # 6.19.x Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifssmb.c | 34 ++++++++++++++++++++++++++++------ fs/smb/client/trace.h | 2 ++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index bb71fed940a5b8..94ed6a2d3541c3 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -4408,9 +4408,9 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_search_info *psrch_inf, bool msearch) { /* level 257 SMB_ */ + unsigned int parm_off, parm_count, data_off, data_count; TRANSACTION2_FFIRST_REQ *pSMB = NULL; TRANSACTION2_FFIRST_RSP *pSMBr = NULL; - unsigned int data_off, data_count; struct nls_table *nls_codepage; T2_FFIRST_RSP_PARMS *parms; unsigned int in_len, lnoff; @@ -4531,6 +4531,18 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, return rc; } + parm_off = le16_to_cpu(pSMBr->t2.ParameterOffset); + parm_count = le16_to_cpu(pSMBr->t2.ParameterCount); + if (parm_count < sizeof(*parms) || + parm_off + parm_count > (unsigned int)bytes_returned) { + cifs_dbg(FYI, "%s: bad parameter area offset %u count %u for response of %d\n", + __func__, parm_off, parm_count, bytes_returned); + rc = smb_EIO2(smb_eio_trace_ffirst_param_area, + parm_off, parm_count); + cifs_buf_release(pSMB); + return rc; + } + data_off = le16_to_cpu(pSMBr->t2.DataOffset); data_count = le16_to_cpu(pSMBr->t2.DataCount); if (data_off + data_count > (unsigned int)bytes_returned) { @@ -4545,8 +4557,7 @@ CIFSFindFirst(const unsigned int xid, struct cifs_tcon *tcon, psrch_inf->smallBuf = false; psrch_inf->srch_entries_start = (char *)&pSMBr->hdr.Protocol + data_off; - parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + - le16_to_cpu(pSMBr->t2.ParameterOffset)); + parms = (T2_FFIRST_RSP_PARMS *)((char *)&pSMBr->hdr.Protocol + parm_off); psrch_inf->endOfSearch = !!parms->EndofSearch; psrch_inf->entries_in_buffer = le16_to_cpu(parms->SearchCount); @@ -4574,9 +4585,9 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, __u16 searchHandle, __u16 search_flags, struct cifs_search_info *psrch_inf) { + unsigned int parm_off, parm_count, data_off, data_count; TRANSACTION2_FNEXT_REQ *pSMB = NULL; TRANSACTION2_FNEXT_RSP *pSMBr = NULL; - unsigned int data_off, data_count; unsigned int name_len, in_len; T2_FNEXT_RSP_PARMS *parms; unsigned int lnoff; @@ -4663,6 +4674,18 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, return rc; } + parm_off = le16_to_cpu(pSMBr->t2.ParameterOffset); + parm_count = le16_to_cpu(pSMBr->t2.ParameterCount); + if (parm_count < sizeof(*parms) || + parm_off + parm_count > (unsigned int)bytes_returned) { + cifs_dbg(FYI, "%s: bad parameter area offset %u count %u for response of %d\n", + __func__, parm_off, parm_count, bytes_returned); + rc = smb_EIO2(smb_eio_trace_fnext_param_area, + parm_off, parm_count); + cifs_buf_release(pSMB); + return rc; + } + data_off = le16_to_cpu(pSMBr->t2.DataOffset); data_count = le16_to_cpu(pSMBr->t2.DataCount); if (data_off + data_count > (unsigned int)bytes_returned) { @@ -4674,8 +4697,7 @@ int CIFSFindNext(const unsigned int xid, struct cifs_tcon *tcon, /* BB fixme add lock for file (srch_info) struct here */ psrch_inf->unicode = !!(pSMBr->hdr.Flags2 & SMBFLG2_UNICODE); - response_data = (char *)&pSMBr->hdr.Protocol + - le16_to_cpu(pSMBr->t2.ParameterOffset); + response_data = (char *)&pSMBr->hdr.Protocol + parm_off; parms = (T2_FNEXT_RSP_PARMS *)response_data; response_data = (char *)&pSMBr->hdr.Protocol + data_off; diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h index bb8d0197cb54f4..2708bedb49ab64 100644 --- a/fs/smb/client/trace.h +++ b/fs/smb/client/trace.h @@ -30,6 +30,8 @@ EM(smb_eio_trace_ea_next_offset, "ea_next_offset") \ EM(smb_eio_trace_ea_overrun, "ea_overrun") \ EM(smb_eio_trace_extract_will_pin, "extract_will_pin") \ + EM(smb_eio_trace_ffirst_param_area, "ffirst_param_area") \ + EM(smb_eio_trace_fnext_param_area, "fnext_param_area") \ EM(smb_eio_trace_forced_shutdown, "forced_shutdown") \ EM(smb_eio_trace_getacl_bcc_too_small, "getacl_bcc_too_small") \ EM(smb_eio_trace_getcifsacl_param_count, "getcifsacl_param_count") \ From 276dcdc55b5dd238b2ec5b5db6c16ee37792a118 Mon Sep 17 00:00:00 2001 From: Diego Oliva Date: Tue, 29 Sep 2026 10:16:36 +0100 Subject: [PATCH 1754/2286] smb: client: drop the redundant name bounds in cifs_filldir() cifs_fill_dirent() rejects an entry whose name extends past the end of the response before it returns, for both of its callers. The two checks cifs_filldir() applies after the parse can no longer fail: the one that commit f8cf09a53a0d ("smb: client: bound dirent name against end of SMB response in cifs_filldir") added tests the same name against the same end, and the older one, which compares the name length with the length of the response, is implied by it because the name starts inside the response. Remove both. This patch depends on "smb: client: fix OOB read of the resume name sent in TRANS2_FIND_NEXT2", which added the check to cifs_fill_dirent(). No functional change intended. Assisted-by: Bynario AI Signed-off-by: Diego Oliva Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/readdir.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c index 1667bba40e4454..525f9918148589 100644 --- a/fs/smb/client/readdir.c +++ b/fs/smb/client/readdir.c @@ -1022,17 +1022,6 @@ static int cifs_filldir(char *find_entry, struct file *file, if (rc) return rc; - if (de.namelen > max_len) { - cifs_dbg(VFS, "bad search response length %zd past smb end\n", - de.namelen); - return -EINVAL; - } - - if (de.name + de.namelen > end_of_smb) { - cifs_dbg(VFS, "search entry name extends past end of SMB\n"); - return -EINVAL; - } - /* skip . and .. since we added them first */ if (cifs_entry_is_dot(&de, file_info->srch_inf.unicode)) return 0; From 689ab4c8d1597360064cddc0299089ab19ff7e3d Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 17:08:52 -0300 Subject: [PATCH 1755/2286] smb: client: rename mfsymlink helpers to the smb2_ prefix smb3_query_mf_symlink() and smb3_create_mf_symlink() are installed from smb20_operations through smb311_operations, so they serve every SMB2 dialect rather than only SMB3. Rename them with the smb2_ prefix to match. No functional change. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/link.c | 4 ++-- fs/smb/client/smb2ops.c | 16 ++++++++-------- fs/smb/client/smb2proto.h | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c index 8d5d6aca742aa1..694de8864db731 100644 --- a/fs/smb/client/link.c +++ b/fs/smb/client/link.c @@ -309,7 +309,7 @@ cifs_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, * SMB 2.1/SMB3 Protocol specific functions */ int -smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +smb2_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_read) { @@ -363,7 +363,7 @@ smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, } int -smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +smb2_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_written) { diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index 583ab4c4d77383..c441b6d9a693b1 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -5810,8 +5810,8 @@ struct smb_version_operations smb20_operations = { .rename = smb2_rename_path, .create_hardlink = smb2_create_hardlink, .get_reparse_point_buffer = smb2_get_reparse_point_buffer, - .query_mf_symlink = smb3_query_mf_symlink, - .create_mf_symlink = smb3_create_mf_symlink, + .query_mf_symlink = smb2_query_mf_symlink, + .create_mf_symlink = smb2_create_mf_symlink, .create_reparse_inode = smb2_create_reparse_inode, .open = smb2_open_file, .set_fid = smb2_set_fid, @@ -5913,8 +5913,8 @@ struct smb_version_operations smb21_operations = { .rename = smb2_rename_path, .create_hardlink = smb2_create_hardlink, .get_reparse_point_buffer = smb2_get_reparse_point_buffer, - .query_mf_symlink = smb3_query_mf_symlink, - .create_mf_symlink = smb3_create_mf_symlink, + .query_mf_symlink = smb2_query_mf_symlink, + .create_mf_symlink = smb2_create_mf_symlink, .create_reparse_inode = smb2_create_reparse_inode, .open = smb2_open_file, .set_fid = smb2_set_fid, @@ -6020,8 +6020,8 @@ struct smb_version_operations smb30_operations = { .rename = smb2_rename_path, .create_hardlink = smb2_create_hardlink, .get_reparse_point_buffer = smb2_get_reparse_point_buffer, - .query_mf_symlink = smb3_query_mf_symlink, - .create_mf_symlink = smb3_create_mf_symlink, + .query_mf_symlink = smb2_query_mf_symlink, + .create_mf_symlink = smb2_create_mf_symlink, .create_reparse_inode = smb2_create_reparse_inode, .open = smb2_open_file, .set_fid = smb2_set_fid, @@ -6136,8 +6136,8 @@ struct smb_version_operations smb311_operations = { .rename = smb2_rename_path, .create_hardlink = smb2_create_hardlink, .get_reparse_point_buffer = smb2_get_reparse_point_buffer, - .query_mf_symlink = smb3_query_mf_symlink, - .create_mf_symlink = smb3_create_mf_symlink, + .query_mf_symlink = smb2_query_mf_symlink, + .create_mf_symlink = smb2_create_mf_symlink, .create_reparse_inode = smb2_create_reparse_inode, .open = smb2_open_file, .set_fid = smb2_set_fid, diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 2e9f7009682512..2425ad4beeea23 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -91,11 +91,11 @@ int smb2_rename_path(const unsigned int xid, struct cifs_tcon *tcon, int smb2_create_hardlink(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, const char *from_name, const char *to_name, struct cifs_sb_info *cifs_sb); -int smb3_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +int smb2_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_written); -int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +int smb2_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_read); From 54a53dd9b6d987d887aa29e2aaf7e89860bb496b Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 17:08:52 -0300 Subject: [PATCH 1756/2286] smb: client: set lease_granted before revalidating inode in cifs_nt_open() cifs_new_fileinfo() -> set_fid() sets CIFS_I(inode)->lease_granted only after cifs_get_inode_info() has run. Opens that cifs_get_inode_info() issues for the inode -- such as the Minshall+French symlink probe or an open compounded in smb2_compound_op() -- therefore find lease_granted clear, go out leaseless, and make the server break the lease just granted to the file. Set lease_granted right after the open succeeds, before cifs_get_inode_info(), so those opens reuse the inode's lease key. Only raise it: lease_granted is kept set for the inode's lifetime, so lowering it here could clobber a lease still held through another open. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index 11355d6bf89d6f..e08ed5072c298d 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -622,6 +622,10 @@ static int cifs_nt_open(const char *full_path, struct inode *inode, struct cifs_ if (rdwr_for_fscache == 2) cifs_invalidate_cache(inode, FSCACHE_INVAL_DIO_WRITE); + if (server->vals->protocol_id > SMB20_PROT_ID && + *oplock != SMB2_OPLOCK_LEVEL_NONE) + CIFS_I(inode)->lease_granted = true; + /* TODO: Add support for calling posix query info but with passing in fid */ if (tcon->unix_ext) rc = cifs_get_inode_info_unix(&inode, full_path, inode->i_sb, From 7c4bf530aefad8b5b47ffb464daa90b3f0662930 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 17:08:52 -0300 Subject: [PATCH 1757/2286] smb: client: avoid breaking the file lease when probing mfsymlinks cifs_check_mf_symlink() probes any file whose size matches CIFS_MF_SYMLINK_FILE_SIZE as a possible Minshall+French symlink. smb2_query_mf_symlink() did so with a fresh leaseless SMB2_open, so the server saw a second conflicting opener and broke the RWH lease the client had just been granted on the same file, needlessly churning the cache. Rework smb2_query_mf_symlink() to issue a single OPEN/READ/CLOSE compound that either reuses an already open handle via cifs_get_readable_path() or, failing that, opens with the inode's lease key so the server treats it as the same lease holder. Thread the inode down to the ->query_mf_symlink() handlers, rename check_mf_symlink() to cifs_check_mf_symlink(), and validate the read payload against the response buffer before copying it. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 9 +- fs/smb/client/cifsproto.h | 13 +- fs/smb/client/inode.c | 22 ++-- fs/smb/client/link.c | 269 ++++++++++++++++++++++++++++---------- fs/smb/client/smb2glob.h | 8 ++ fs/smb/client/smb2inode.c | 3 - fs/smb/client/smb2pdu.c | 13 +- fs/smb/client/smb2proto.h | 10 +- 8 files changed, 244 insertions(+), 103 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index d74cc9e8c413d1..a3f54dd19fb04c 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -543,9 +543,12 @@ struct smb_version_operations { struct cifsFileInfo *src_file, void __user *); int (*notify)(const unsigned int xid, struct file *pfile, void __user *pbuf, bool return_changes); - int (*query_mf_symlink)(unsigned int, struct cifs_tcon *, - struct cifs_sb_info *, const unsigned char *, - char *, unsigned int *); + int (*query_mf_symlink)(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + struct inode *inode, char *pbuf, + unsigned int *pbytes_read); int (*create_mf_symlink)(unsigned int, struct cifs_tcon *, struct cifs_sb_info *, const unsigned char *, char *, unsigned int *); diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 00168839c12319..582cd182c6165b 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -319,9 +319,10 @@ int generate_smb311signingkey(struct cifs_ses *ses, #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ void cifs_autodisable_serverino(struct cifs_sb_info *cifs_sb, const char *reason, int rc); bool couldbe_mf_symlink(const struct cifs_fattr *fattr); -int check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, struct cifs_fattr *fattr, - const unsigned char *path); +int cifs_check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, struct inode *inode, + struct cifs_fattr *fattr); int E_md4hash(const unsigned char *passwd, unsigned char *p16, const struct nls_table *codepage); @@ -337,9 +338,11 @@ struct cifs_ses *cifs_get_smb_ses(struct TCP_Server_Info *server, int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid); -int cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +int cifs_query_mf_symlink(const unsigned int xid, + struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, - const unsigned char *path, char *pbuf, + const char *full_path, + struct inode *inode, char *pbuf, unsigned int *pbytes_read); int cifs_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index f29555f3c7d015..00dbf5342c1086 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -456,7 +456,7 @@ static int cifs_get_unix_fattr(const unsigned char *full_path, FILE_UNIX_BASIC_INFO find_data; struct cifs_tcon *tcon; struct tcon_link *tlink; - int rc, tmprc; + int rc; cifs_dbg(FYI, "Getting info on %s\n", full_path); @@ -486,8 +486,10 @@ static int cifs_get_unix_fattr(const unsigned char *full_path, /* check for Minshall+French symlinks */ if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_MF_SYMLINKS) { - tmprc = check_mf_symlink(xid, tcon, cifs_sb, fattr, full_path); - cifs_dbg(FYI, "check_mf_symlink: %d\n", tmprc); + rc = cifs_check_mf_symlink(xid, tcon, cifs_sb, full_path, + *pinode, fattr); + if (rc) + return rc; } if (S_ISLNK(fattr->cf_mode) && !fattr->cf_symlink_target) { @@ -1437,8 +1439,8 @@ static int cifs_get_fattr(struct cifs_open_info_data *data, /* check for Minshall+French symlinks */ if (sbflags & CIFS_MOUNT_MF_SYMLINKS) { - tmprc = check_mf_symlink(xid, tcon, cifs_sb, fattr, full_path); - cifs_dbg(FYI, "check_mf_symlink: %d\n", tmprc); + rc = cifs_check_mf_symlink(xid, tcon, cifs_sb, full_path, + *inode, fattr); } out: @@ -1475,6 +1477,7 @@ int cifs_get_inode_info(struct inode **inode, static int smb311_posix_get_fattr(struct cifs_open_info_data *data, struct cifs_fattr *fattr, const char *full_path, + struct inode *inode, struct super_block *sb, const unsigned int xid) { @@ -1483,7 +1486,6 @@ static int smb311_posix_get_fattr(struct cifs_open_info_data *data, struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_tcon *tcon; struct tcon_link *tlink; - int tmprc; int rc = 0; tlink = cifs_sb_tlink(cifs_sb); @@ -1537,8 +1539,8 @@ static int smb311_posix_get_fattr(struct cifs_open_info_data *data, */ /* check for Minshall+French symlinks */ if (cifs_sb_flags(cifs_sb) & CIFS_MOUNT_MF_SYMLINKS) { - tmprc = check_mf_symlink(xid, tcon, cifs_sb, fattr, full_path); - cifs_dbg(FYI, "check_mf_symlink: %d\n", tmprc); + rc = cifs_check_mf_symlink(xid, tcon, cifs_sb, full_path, + inode, fattr); } out: @@ -1561,7 +1563,7 @@ int smb311_posix_get_inode_info(struct inode **inode, return 0; } - rc = smb311_posix_get_fattr(data, &fattr, full_path, sb, xid); + rc = smb311_posix_get_fattr(data, &fattr, full_path, *inode, sb, xid); if (rc) goto out; @@ -1713,7 +1715,7 @@ struct inode *cifs_root_iget(struct super_block *sb) convert_delimiter(path, CIFS_DIR_SEP(cifs_sb)); if (tcon->posix_extensions) - rc = smb311_posix_get_fattr(NULL, &fattr, path, sb, xid); + rc = smb311_posix_get_fattr(NULL, &fattr, path, inode, sb, xid); else rc = cifs_get_fattr(NULL, sb, xid, NULL, &fattr, &inode, path); diff --git a/fs/smb/client/link.c b/fs/smb/client/link.c index 694de8864db731..034c9174018d48 100644 --- a/fs/smb/client/link.c +++ b/fs/smb/client/link.c @@ -17,6 +17,7 @@ #include "cifs_fs_sb.h" #include "cifs_unicode.h" #include "smb2proto.h" +#include "smb2glob.h" #include "cifs_ioctl.h" #include "fs_context.h" #include "reparse.h" @@ -166,30 +167,34 @@ create_mf_symlink(const unsigned int xid, struct cifs_tcon *tcon, return rc; } -int -check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, struct cifs_fattr *fattr, - const unsigned char *path) +int cifs_check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, struct inode *inode, + struct cifs_fattr *fattr) { - int rc; - u8 *buf = NULL; - unsigned int link_len = 0; + struct TCP_Server_Info *server = tcon->ses->server; + char *buf __free(kfree) = NULL; unsigned int bytes_read = 0; + unsigned int link_len = 0; char *symlink = NULL; + int rc = 0; if (!couldbe_mf_symlink(fattr)) - /* it's not a symlink */ - return 0; + goto out; buf = kmalloc(CIFS_MF_SYMLINK_FILE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; + if (!buf) { + rc = -ENOMEM; + goto out; + } - if (tcon->ses->server->ops->query_mf_symlink) - rc = tcon->ses->server->ops->query_mf_symlink(xid, tcon, - cifs_sb, path, buf, &bytes_read); - else + if (server->ops->query_mf_symlink) { + rc = server->ops->query_mf_symlink(xid, tcon, + cifs_sb, full_path, + inode, buf, &bytes_read); + } else { rc = -ENOSYS; + } if (rc) goto out; @@ -214,19 +219,17 @@ check_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, fattr->cf_dtype = DT_LNK; fattr->cf_symlink_target = symlink; out: - kfree(buf); - return rc; + cifs_dbg(FYI, "%s: rc = %d\n", __func__, rc); + return rc == -ENOMEM ? rc : 0; } #ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY -/* - * SMB 1.0 Protocol specific functions - */ - -int -cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, const unsigned char *path, - char *pbuf, unsigned int *pbytes_read) +int cifs_query_mf_symlink(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + struct inode *inode, char *pbuf, + unsigned int *pbytes_read) { int rc; int oplock = 0; @@ -242,7 +245,7 @@ cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, .desired_access = GENERIC_READ, .create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR), .disposition = FILE_OPEN, - .path = path, + .path = full_path, .fid = &fid, }; @@ -305,60 +308,184 @@ cifs_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ -/* - * SMB 2.1/SMB3 Protocol specific functions - */ -int -smb2_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, - struct cifs_sb_info *cifs_sb, const unsigned char *path, - char *pbuf, unsigned int *pbytes_read) +int smb2_query_mf_symlink(const unsigned int xid, + struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, + const char *full_path, + struct inode *inode, char *pbuf, + unsigned int *pbytes_read) { - int rc; + struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; + struct kvec read_iov[2], close_iov; + struct cifsFileInfo *cfile = NULL; + struct smb2_read_rsp *read_rsp; + struct TCP_Server_Info *server; + int retries = 0, cur_sleep = 0; + struct cifs_io_parms io_parms; + struct smb2_read_req *req; + struct smb_rqst rqst[3]; + unsigned int total_len; + struct kvec rsp_iov[3]; struct cifs_fid fid; - struct cifs_open_parms oparms; - struct cifs_io_parms io_parms = {0}; - int buf_type = CIFS_NO_BUFFER; - __le16 *utf16_path; - __u8 oplock = SMB2_OPLOCK_LEVEL_NONE; - struct cifs_open_info_data data = {}; + int rsp_buftype[3]; + struct kvec *riov; + int num_rqst; + __u8 oplock; + int flags; + u32 dlen; + int rc; - oparms = (struct cifs_open_parms) { - .tcon = tcon, - .cifs_sb = cifs_sb, - .path = path, - .desired_access = GENERIC_READ, - .create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR), - .disposition = FILE_OPEN, - .fid = &fid, - }; + cifs_get_readable_path(tcon, full_path, &cfile); + *pbytes_read = 0; +again: + flags = 0; + num_rqst = 0; + req = NULL; + oplock = SMB2_OPLOCK_LEVEL_NONE; + server = cifs_pick_channel(tcon->ses); - utf16_path = cifs_convert_path_to_utf16(path, cifs_sb); - if (utf16_path == NULL) - return -ENOMEM; + if (smb3_encryption_required(tcon)) + flags |= CIFS_TRANSFORM_REQ; - rc = SMB2_open(xid, &oparms, utf16_path, &oplock, &data, NULL, - NULL, NULL); + for (int i = 0; i < ARRAY_SIZE(rsp_buftype); i++) + rsp_buftype[i] = CIFS_NO_BUFFER; + + memset(rqst, 0, sizeof(rqst)); + memset(rsp_iov, 0, sizeof(rsp_iov)); + + if (!cfile) { + __le16 *utf16_path __free(kfree) = NULL; + struct cifs_open_parms oparms; + + utf16_path = cifs_convert_path_to_utf16(full_path, cifs_sb); + if (!utf16_path) { + rc = -ENOMEM; + goto out; + } + + if (inode && CIFS_I(inode)->lease_granted && + server->ops->get_lease_key) { + oplock = SMB2_OPLOCK_LEVEL_LEASE; + server->ops->get_lease_key(inode, &fid); + } + + oparms = CIFS_OPARMS(cifs_sb, tcon, full_path, GENERIC_READ, + FILE_OPEN, CREATE_NOT_DIR, ACL_NO_MODE); + oparms.fid = &fid; + + memset(open_iov, 0, sizeof(open_iov)); + rqst[num_rqst].rq_iov = open_iov; + rqst[num_rqst].rq_nvec = SMB2_CREATE_IOV_SIZE; + + rc = SMB2_open_init(tcon, server, &rqst[num_rqst], + &oplock, &oparms, utf16_path); + if (rc) + goto out; + smb2_set_next_command(tcon, &rqst[num_rqst++]); + } + + io_parms = (struct cifs_io_parms) { + .pid = current->tgid, + .tcon = tcon, + .length = CIFS_MF_SYMLINK_FILE_SIZE, + .server = server, + .persistent_fid = COMP_PID(cfile), + .volatile_fid = COMP_VID(cfile), + }; + + rc = smb2_new_read_req((void **)&req, &total_len, + &io_parms, NULL, 0, 0); if (rc) - goto qmf_out_open_fail; + goto out; - if (data.fi.EndOfFile != cpu_to_le64(CIFS_MF_SYMLINK_FILE_SIZE)) { - /* it's not a symlink */ - rc = -ENOENT; /* Is there a better rc to return? */ - goto qmf_out; + read_iov[0].iov_base = req; + read_iov[0].iov_len = total_len; + rqst[num_rqst].rq_iov = read_iov; + rqst[num_rqst].rq_nvec = 1; + if (!cfile) { + smb2_set_next_command(tcon, &rqst[num_rqst]); + smb2_set_related(&rqst[num_rqst]); + } + num_rqst++; + + if (!cfile) { + flags |= CIFS_CP_CREATE_CLOSE_OP; + memset(&close_iov, 0, sizeof(close_iov)); + rqst[num_rqst].rq_iov = &close_iov; + rqst[num_rqst].rq_nvec = 1; + rc = SMB2_close_init(tcon, server, &rqst[num_rqst], + COMPOUND_FID, COMPOUND_FID, false); + smb2_set_related(&rqst[num_rqst++]); + if (rc) + goto out; } - io_parms.netfid = fid.netfid; - io_parms.pid = current->tgid; - io_parms.tcon = tcon; - io_parms.offset = 0; - io_parms.length = CIFS_MF_SYMLINK_FILE_SIZE; - io_parms.persistent_fid = fid.persistent_fid; - io_parms.volatile_fid = fid.volatile_fid; - rc = SMB2_read(xid, &io_parms, pbytes_read, &pbuf, &buf_type); -qmf_out: - SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid); -qmf_out_open_fail: - kfree(utf16_path); + if (retries) { + if (cur_sleep) + msleep(cur_sleep); + for (int i = 0; i < num_rqst; i++) + smb2_set_replay(server, &rqst[i]); + } + + rc = compound_send_recv(xid, tcon->ses, server, flags, + num_rqst, rqst, rsp_buftype, rsp_iov); + if (rc) { + if (rc != -ENODATA) { + if (rc != -ENOENT) + cifs_stats_fail_inc(tcon, SMB2_READ_HE); + cifs_dbg(FYI, "%s: failed to read file: %d\n", + __func__, rc); + trace_smb3_read_err(0, 0, xid, req->PersistentFileId, + tcon->tid, tcon->ses->Suid, + io_parms.offset, io_parms.length, + rc); + } else { + trace_smb3_read_done(0, 0, xid, req->PersistentFileId, + tcon->tid, tcon->ses->Suid, + io_parms.offset, 0); + } + rc = rc == -ENODATA ? 0 : rc; + goto out; + } + trace_smb3_read_done(0, 0, xid, req->PersistentFileId, + tcon->tid, tcon->ses->Suid, + io_parms.offset, io_parms.length); + + riov = &rsp_iov[!cfile ? 1 : 0]; + read_rsp = riov->iov_base; + dlen = le32_to_cpu(read_rsp->DataLength); + if (dlen > CIFS_MAX_MSGSIZE || dlen > io_parms.length) { + cifs_dbg(FYI, "%s: bad length %u for count %u\n", + __func__, dlen, io_parms.length); + rc = smb_EIO2(smb_eio_trace_read_overlarge, + dlen, io_parms.length); + } else if (dlen != CIFS_MF_SYMLINK_FILE_SIZE) { + rc = -ENOENT; + } else { + rc = smb2_validate_iov(read_rsp->DataOffset, dlen, riov, + CIFS_MF_SYMLINK_FILE_SIZE); + } + + if (!rc) { + memcpy(pbuf, (u8 *)read_rsp + read_rsp->DataOffset, dlen); + *pbytes_read = dlen; + } + +out: + if (!cfile) { + SMB2_open_free(&rqst[0]); + SMB2_close_free(&rqst[2]); + } + cifs_small_buf_release(req); + for (int i = 0; i < ARRAY_SIZE(rsp_buftype); i++) + free_rsp_buf(rsp_buftype[i], rsp_iov[i].iov_base); + + if (is_replayable_error(rc) && + smb2_should_replay(tcon, &retries, &cur_sleep)) + goto again; + + if (cfile) + cifsFileInfo_put(cfile); return rc; } diff --git a/fs/smb/client/smb2glob.h b/fs/smb/client/smb2glob.h index 19da74b1edab00..99c6f5a635fc33 100644 --- a/fs/smb/client/smb2glob.h +++ b/fs/smb/client/smb2glob.h @@ -13,6 +13,14 @@ #ifndef _SMB2_GLOB_H #define _SMB2_GLOB_H +#define COMP_PID(cfile) ((cfile) ? (cfile)->fid.persistent_fid : COMPOUND_FID) +#define COMP_VID(cfile) ((cfile) ? (cfile)->fid.volatile_fid : COMPOUND_FID) + +static inline void cifs_stats_fail_inc(struct cifs_tcon *tcon, uint16_t code) +{ + cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_failed[code]); +} + /* ***************************************************************** * Constants go here diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index f46a62eae65912..7c1472fe5b9a7a 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -200,9 +200,6 @@ static void set_next_compound(struct cifs_tcon *tcon, (*num_rqst)++; } -#define COMP_PID(cfile) ((cfile) ? (cfile)->fid.persistent_fid : COMPOUND_FID) -#define COMP_VID(cfile) ((cfile) ? (cfile)->fid.volatile_fid : COMPOUND_FID) - /* * note: If cfile is passed, the reference to it is dropped here. * So make sure that you do not reuse cfile after return from this func. diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index cfe3c4b4abe748..d7479418b94f15 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2113,11 +2113,6 @@ SMB2_logoff(const unsigned int xid, struct cifs_ses *ses) return rc; } -static inline void cifs_stats_fail_inc(struct cifs_tcon *tcon, uint16_t code) -{ - cifs_stats_inc(&tcon->stats.smb2_stats.smb2_com_failed[code]); -} - #define MAX_SHARENAME_LENGTH (255 /* server */ + 80 /* share */ + 1 /* NULL */) /* These are similar values to what Windows uses */ @@ -4581,10 +4576,10 @@ static inline bool smb3_use_rdma_offload(struct cifs_io_parms *io_parms) * To form a chain of read requests, any read requests after the first should * have the end_of_chain boolean set to true. */ -static int -smb2_new_read_req(void **buf, unsigned int *total_len, - struct cifs_io_parms *io_parms, struct cifs_io_subrequest *rdata, - unsigned int remaining_bytes, int request_type) +int smb2_new_read_req(void **buf, unsigned int *total_len, + struct cifs_io_parms *io_parms, + struct cifs_io_subrequest *rdata, + unsigned int remaining_bytes, int request_type) { int rc = -EACCES; struct smb2_read_req *req = NULL; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 2425ad4beeea23..69a32f8311ca4c 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -95,9 +95,11 @@ int smb2_create_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_written); -int smb2_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, +int smb2_query_mf_symlink(const unsigned int xid, + struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, - const unsigned char *path, char *pbuf, + const char *full_path, + struct inode *inode, char *pbuf, unsigned int *pbytes_read); int smb2_fix_symlink_target_type(char **target, bool directory, struct cifs_sb_info *cifs_sb); @@ -172,6 +174,10 @@ int SMB2_flush_init(const unsigned int xid, struct smb_rqst *rqst, struct cifs_tcon *tcon, struct TCP_Server_Info *server, u64 persistent_fid, u64 volatile_fid); void SMB2_flush_free(struct smb_rqst *rqst); +int smb2_new_read_req(void **buf, unsigned int *total_len, + struct cifs_io_parms *io_parms, + struct cifs_io_subrequest *rdata, + unsigned int remaining_bytes, int request_type); int SMB2_query_info(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, u64 volatile_fid, struct smb2_file_all_info *data); From a4111beeade900346726acb2746f22a6313aeaf0 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 17:39:11 -0300 Subject: [PATCH 1758/2286] smb: client: log the selected security type on session setup failure When mounting without an explicit sec= option, ses->sectype stays Unspecified while the real mechanism is chosen later through ->select_sectype(), so a failed session setup logs a useless name: CIFS: VFS: \\server failed to create a new SMB session with Unknown: -13 Resolve the mechanism through ->select_sectype() before formatting it, as cifs_debug.c already does, so the message names the actual security type. Fixes: a8fce7c807b1 ("smb: client: improve error message when creating SMB session") Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/connect.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index ff8637301fa9bd..90eda91d9da97d 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -4195,8 +4195,12 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, if (rc) { if (new_ses) { + enum securityEnum sectype; + + sectype = server->ops->select_sectype(server, + ses->sectype); cifs_server_dbg(VFS, "failed to create a new SMB session with %s: %d\n", - get_security_type_str(ses->sectype), rc); + get_security_type_str(sectype), rc); } spin_lock(&ses->ses_lock); if (ses->ses_status == SES_IN_SETUP) From 2bcbf169f311776dfc100d6fdb935da864cdd485 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1759/2286] smb: client: remove commented-out ntlmv2_support definition Drop this leftover commented-out definition. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsfs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 98b610b2a11428..e774ae0a3465b6 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -71,7 +71,6 @@ bool enable_gcm_256 = true; bool require_gcm_256; /* false by default */ bool enable_negotiate_signing; /* false by default */ unsigned int global_secflags = CIFSSEC_DEF; -/* unsigned int ntlmv2_support = 0; */ /* * Global transaction id (XID) information From fe3fc9f7797145d9962091557973e96187386b36 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1760/2286] smb: client: remove commented-out i_flags assignment Drop this leftover commented-out assignment. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsfs.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index e774ae0a3465b6..e705b7255b351d 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -463,7 +463,6 @@ cifs_alloc_inode(struct super_block *sb) * Can not set i_flags here - they get immediately overwritten to zero * by the VFS. */ - /* cifs_inode->netfs.inode.i_flags = S_NOATIME | S_NOCMTIME; */ INIT_LIST_HEAD(&cifs_inode->openFileList); INIT_LIST_HEAD(&cifs_inode->llist); INIT_LIST_HEAD(&cifs_inode->deferred_closes); From a40b17cc9ecaaa85c3615963ec21cfed1c734340 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1761/2286] smb: client: remove commented-out cancel_*_requests() calls Drop these leftover commented-out calls. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsfs.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index e705b7255b351d..ac9e3155c3f7f7 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -856,8 +856,6 @@ static void cifs_umount_begin(struct super_block *sb) spin_unlock(&tcon->tc_lock); spin_unlock(&cifs_tcp_ses_lock); - /* cancel_brl_requests(tcon); */ /* BB mark all brl mids as exiting */ - /* cancel_notify_requests(tcon); */ if (tcon->ses && tcon->ses->server) { cifs_dbg(FYI, "wake up tasks now - umount begin not complete\n"); wake_up_all(&tcon->ses->server->request_q); From 775bf158872f875e887d1ab4c2a52de8e40b53e8 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1762/2286] smb: client: remove commented-out oplock assignment Drop this leftover commented-out assignment. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 94ed6a2d3541c3..8e648097cf1649 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1308,9 +1308,6 @@ SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, cifs_dbg(FYI, "Error in Open = %d\n", rc); } else { /* BB verify if wct == 15 */ - -/* *pOplock = pSMBr->OplockLevel; */ /* BB take from action field*/ - *netfid = pSMBr->Fid; /* cifs fid stays in le */ /* Let caller know file was created so we can set the mode. */ /* Do we care about the CreateAction in any other cases? */ From 1e0e189998c0018d45485b2dfcf7d6be599f8e89 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1763/2286] smb: client: remove commented-out write debug message Drop this leftover commented-out debug message. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 8e648097cf1649..cd4e119c8f4eb4 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1793,7 +1793,6 @@ CIFSSMBWrite(const unsigned int xid, struct cifs_io_parms *io_parms, *nbytes = 0; - /* cifs_dbg(FYI, "write at %lld %d bytes\n", offset, count);*/ if (tcon->ses == NULL) return -ECONNABORTED; From a59aeb074cacd14388f0489f3c52f29fb956a567 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1764/2286] smb: client: remove commented-out LOCK_RSP declaration Drop this leftover commented-out declaration. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index cd4e119c8f4eb4..ddb6301f9c0747 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -2225,7 +2225,6 @@ CIFSSMBLock(const unsigned int xid, struct cifs_tcon *tcon, { int rc = 0; LOCK_REQ *pSMB = NULL; -/* LOCK_RSP *pSMBr = NULL; */ /* No response data other than rc to parse */ unsigned int in_len; int bytes_returned; int flags = CIFS_WINDOWS_LOCK | CIFS_INTERRUPTIBLE_WAIT; From bdf9ccc58bc6e64ccf1302f8c9f0f204bd185777 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:34 -0300 Subject: [PATCH 1765/2286] smb: client: remove commented-out QPathInfo debug message Drop this leftover commented-out debug message. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index ddb6301f9c0747..f09a29fc353386 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -4151,7 +4151,6 @@ CIFSSMBQPathInfo(const unsigned int xid, struct cifs_tcon *tcon, int name_len; __u16 params, byte_count; - /* cifs_dbg(FYI, "In QPathInfo path %s\n", search_name); */ QPathInfoRetry: rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, (void **) &pSMBr); From 5c2824d98b30cb649bb2b5a0abde83c8a8b30077 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:35 -0300 Subject: [PATCH 1766/2286] smb: client: remove commented-out CIFSSMBWinSetPerms() call Drop this leftover commented-out call. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/dir.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 1a56fa4d0e8905..26c8adbb8e4e83 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -419,7 +419,6 @@ static int __cifs_do_create(struct inode *dir, struct dentry *direntry, * BB implement mode setting via Windows security * descriptors e.g. */ - /* CIFSSMBWinSetPerms(xid,tcon,path,mode,-1,-1,nls);*/ /* Could set r/o dos attribute if mode & 0222 == 0 */ } From e4a1508b8b3f2f4d72795ff2a68c95c24d38941c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:02:35 -0300 Subject: [PATCH 1767/2286] smb: client: remove commented-out time conversion debug message Drop this leftover commented-out debug message. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/netmisc.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/fs/smb/client/netmisc.c b/fs/smb/client/netmisc.c index bddadee82d0c07..cf737ede1ab33a 100644 --- a/fs/smb/client/netmisc.c +++ b/fs/smb/client/netmisc.c @@ -204,9 +204,6 @@ struct timespec64 cnvrtDosUnixTm(__le16 le_date, __le16 le_time, int offset) sec += 24 * 60 * 60 * days; ts.tv_sec = sec + offset; - - /* cifs_dbg(FYI, "sec after cnvrt dos to unix time %d\n",sec); */ - ts.tv_nsec = 0; return ts; } From bb274d81559771e292a517180c327993604f6111 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:03:02 -0300 Subject: [PATCH 1768/2286] smb: client: remove commented-out Unix caps assignment Drop this leftover commented-out assignment. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/ioctl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index 2f152d76be7d88..71f92ebb3432ee 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -513,7 +513,6 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) if (pSMBFile == NULL) break; tcon = tlink_tcon(pSMBFile->tlink); - /* caps = le64_to_cpu(tcon->fsUnixInfo.Capability); */ if (get_user(ExtAttrBits, (int __user *)arg)) { rc = -EFAULT; From 5dce507a8ba4ac3c5d7a065659f44b8732969e3e Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:03:02 -0300 Subject: [PATCH 1769/2286] smb: client: clean up cifs_buf_release() Remove the leftover commented-out debug message, drop the now-unnecessary braces around the single return, and test !buf_to_free instead of comparing against NULL. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/misc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c index 05168284f20512..ec28f102ea08a1 100644 --- a/fs/smb/client/misc.c +++ b/fs/smb/client/misc.c @@ -215,10 +215,8 @@ cifs_buf_get(void) void cifs_buf_release(void *buf_to_free) { - if (buf_to_free == NULL) { - /* cifs_dbg(FYI, "Null buffer passed to cifs_buf_release\n");*/ + if (!buf_to_free) return; - } mempool_free(buf_to_free, cifs_req_poolp); atomic_dec(&buf_alloc_count); From c684280717a8e044ca99d594427cfb85be9d8d39 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:24:02 -0300 Subject: [PATCH 1770/2286] smb: client: remove the internal CIFS version number CIFS carried a hand-maintained internal version (CIFS_VERSION and SMB3_PRODUCT_BUILD) that had to be bumped by hand on every change. The kernel version already identifies which features and fixes are present, so drop it. Remove MODULE_VERSION() and the "CIFS Version" line in DebugData, and send LINUX_VERSION_SUBLEVEL in the NTLMSSP ProductBuild field so the advertised version is derived entirely from the kernel version -- its major and minor were already taken from there. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifs_debug.c | 1 - fs/smb/client/cifsfs.c | 1 - fs/smb/client/cifsfs.h | 3 --- fs/smb/client/ntlmssp.h | 2 +- fs/smb/client/sess.c | 4 ++-- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c index 3761d3ad60882f..577f0ddede90e5 100644 --- a/fs/smb/client/cifs_debug.c +++ b/fs/smb/client/cifs_debug.c @@ -450,7 +450,6 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) seq_puts(m, "Display Internal CIFS Data Structures for Debugging\n" "---------------------------------------------------\n"); - seq_printf(m, "CIFS Version %s\n", CIFS_VERSION); seq_printf(m, "Features:"); #ifdef CONFIG_CIFS_DFS_UPCALL seq_printf(m, " DFS"); diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index ac9e3155c3f7f7..5d7c786d1801eb 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -2249,7 +2249,6 @@ MODULE_LICENSE("GPL"); /* combination of LGPL + GPL source behaves as GPL */ MODULE_DESCRIPTION ("VFS to access SMB3 servers e.g. Samba, Macs, Azure and Windows (and " "also older servers complying with the SNIA CIFS Specification)"); -MODULE_VERSION(CIFS_VERSION); MODULE_SOFTDEP("nls"); MODULE_SOFTDEP("aes"); MODULE_SOFTDEP("aead2"); diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index e3d820c9778bc8..0b064236b4dc22 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -167,7 +167,4 @@ char *cifs_silly_fullpath(struct dentry *dentry); extern const struct export_operations cifs_export_ops; #endif /* CONFIG_CIFS_NFSD_EXPORT */ -/* when changing internal version - update following two lines at same time */ -#define SMB3_PRODUCT_BUILD 61 -#define CIFS_VERSION "2.61" #endif /* _CIFSFS_H */ diff --git a/fs/smb/client/ntlmssp.h b/fs/smb/client/ntlmssp.h index be0365f08396ff..7d68153985f847 100644 --- a/fs/smb/client/ntlmssp.h +++ b/fs/smb/client/ntlmssp.h @@ -93,7 +93,7 @@ typedef struct _NEGOTIATE_MESSAGE { struct ntlmssp_version { __u8 ProductMajorVersion; __u8 ProductMinorVersion; - __le16 ProductBuild; /* we send the cifs.ko module version here */ + __le16 ProductBuild; /* we send the kernel sublevel here */ __u8 Reserved[3]; __u8 NTLMRevisionCurrent; /* currently 0x0F */ } __packed; diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 0ab3f37d480949..fc105308226c20 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -890,7 +890,7 @@ int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, sec_blob->Version.ProductMajorVersion = LINUX_VERSION_MAJOR; sec_blob->Version.ProductMinorVersion = LINUX_VERSION_PATCHLEVEL; - sec_blob->Version.ProductBuild = cpu_to_le16(SMB3_PRODUCT_BUILD); + sec_blob->Version.ProductBuild = cpu_to_le16(LINUX_VERSION_SUBLEVEL); sec_blob->Version.NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; tmp = *pbuffer + sizeof(struct negotiate_message); @@ -956,7 +956,7 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer, sec_blob->Version.ProductMajorVersion = LINUX_VERSION_MAJOR; sec_blob->Version.ProductMinorVersion = LINUX_VERSION_PATCHLEVEL; - sec_blob->Version.ProductBuild = cpu_to_le16(SMB3_PRODUCT_BUILD); + sec_blob->Version.ProductBuild = cpu_to_le16(LINUX_VERSION_SUBLEVEL); sec_blob->Version.NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; tmp = *pbuffer + sizeof(AUTHENTICATE_MESSAGE); From 1f0cd146ba93133cbee9b7202f9fd092078cd273 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:45:56 -0300 Subject: [PATCH 1771/2286] smb: client: remove unused overrideSecFlg from struct cifs_ses overrideSecFlg has no readers or writers anywhere in the tree; drop it. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index a3f54dd19fb04c..d80853008f909f 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1070,7 +1070,6 @@ struct cifs_ses { struct TCP_Server_Info *server; /* pointer to server info */ int ses_count; /* reference counter */ enum ses_status_enum ses_status; /* updates protected by cifs_tcp_ses_lock */ - unsigned int overrideSecFlg; /* if non-zero override global sec flags */ char *serverOS; /* name of operating system underlying server */ char *serverNOS; /* name of network operating system of server */ char *serverDomain; /* security realm of server */ From 5be685f3516c3ffe8a005285a465cf3de23b1a83 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:53:24 -0300 Subject: [PATCH 1772/2286] smb: client: remove write-only max_rw from TCP_Server_Info max_rw is assigned from the SMB1 negotiate response but never read, so drop it. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 3 --- fs/smb/client/cifssmb.c | 1 - 2 files changed, 4 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index d80853008f909f..7c2ab3f56faec1 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -733,9 +733,6 @@ struct TCP_Server_Info { /* message size the server can send or receive for non-raw SMBs */ /* maxBuf is returned by SMB NegotiateProtocol so maxBuf is only 0 */ /* when socket is setup (and during reconnect) before NegProt sent */ - unsigned int max_rw; /* maxRw specifies the maximum */ - /* message size the server can send or receive for */ - /* SMB_COM_WRITE_RAW or SMB_COM_READ_RAW. */ unsigned int capabilities; /* selective disabling of caps by smb sess */ int timeAdj; /* Adjust for difference in server time zone in sec */ __u64 current_mid; /* multiplex id - rotating counter, protected by mid_counter_lock */ diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index f09a29fc353386..31695f1efe8932 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -502,7 +502,6 @@ CIFSSMBNegotiate(const unsigned int xid, server->maxBuf = le32_to_cpu(pSMBr->MaxBufferSize); /* set up max_read for readahead check */ server->max_read = server->maxBuf; - server->max_rw = le32_to_cpu(pSMBr->MaxRawSize); cifs_dbg(NOISY, "Max buf = %d\n", ses->server->maxBuf); server->capabilities = le32_to_cpu(pSMBr->Capabilities); server->session_key_id = pSMBr->SessionKey; From 6b56bb27466f1908462bc06bd06e3fc1848e5373 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:53:46 -0300 Subject: [PATCH 1773/2286] smb: client: remove write-only sec_kerberosu2u from TCP_Server_Info sec_kerberosu2u is set when the krb5 user-to-user OID is seen but is never read, so the OID branch that sets it has no effect. Drop both. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/asn1.c | 2 -- fs/smb/client/cifsglob.h | 1 - 2 files changed, 3 deletions(-) diff --git a/fs/smb/client/asn1.c b/fs/smb/client/asn1.c index 214a44509e7b99..b610780c68025f 100644 --- a/fs/smb/client/asn1.c +++ b/fs/smb/client/asn1.c @@ -46,8 +46,6 @@ int cifs_neg_token_init_mech_type(void *context, size_t hdrlen, oid = look_up_OID(value, vlen); if (oid == OID_mskrb5) server->sec_mskerberos = true; - else if (oid == OID_krb5u2u) - server->sec_kerberosu2u = true; else if (oid == OID_krb5) server->sec_kerberos = true; else if (oid == OID_ntlmssp) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 7c2ab3f56faec1..65ed7a8fb574ca 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -752,7 +752,6 @@ struct TCP_Server_Info { char negflavor; /* NEGOTIATE response flavor */ /* extended security flavors that server supports */ bool sec_ntlmssp; /* supports NTLMSSP */ - bool sec_kerberosu2u; /* supports U2U Kerberos */ bool sec_kerberos; /* supports plain Kerberos */ bool sec_mskerberos; /* supports legacy MS Kerberos */ bool sec_iakerb; /* supports pass-through auth for Kerberos (krb5 proxy) */ From 5ef569b5b78913e8dfda4bbd2c49fe1d1366ca19 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:54:08 -0300 Subject: [PATCH 1774/2286] smb: client: remove write-only signing_negotiated from TCP_Server_Info signing_negotiated is set when a signing context is received but never read; drop it. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 1 - fs/smb/client/smb2pdu.c | 1 - 2 files changed, 2 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 65ed7a8fb574ca..96b464cf8bc934 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -797,7 +797,6 @@ struct TCP_Server_Info { __le16 cipher_type; /* save initial negprot hash */ __u8 preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE]; - bool signing_negotiated; /* true if valid signing context rcvd from server */ bool posix_ext_supported; struct delayed_work reconnect; /* reconnect workqueue job */ struct mutex reconnect_mutex; /* prevent simultaneous reconnects */ diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index d7479418b94f15..71d44c6da4b5b4 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -960,7 +960,6 @@ static void decode_signing_ctx(struct TCP_Server_Info *server, return; } - server->signing_negotiated = true; server->signing_algorithm = le16_to_cpu(pctxt->SigningAlgorithms[0]); cifs_dbg(FYI, "signing algorithm %d chosen\n", server->signing_algorithm); From 62d973b788e05c3c428bd328e68dd48f45492eaf Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 18:54:30 -0300 Subject: [PATCH 1775/2286] smb: client: remove unused num_acl_set stat from cifs_tcon num_acl_set is only ever reset to 0 -- never incremented or reported -- so drop the dead counter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 1 - fs/smb/client/smb1ops.c | 1 - 2 files changed, 2 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 96b464cf8bc934..7bf8f372697d6e 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -1231,7 +1231,6 @@ struct cifs_tcon { atomic_t num_symlinks; atomic_t num_locks; atomic_t num_acl_get; - atomic_t num_acl_set; } cifs_stats; struct { atomic_t smb2_com_sent[NUMBER_OF_SMB2_COMMANDS]; diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 3ac4126267f6ad..7d2ddf2dcad985 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -815,7 +815,6 @@ cifs_clear_stats(struct cifs_tcon *tcon) atomic_set(&tcon->stats.cifs_stats.num_symlinks, 0); atomic_set(&tcon->stats.cifs_stats.num_locks, 0); atomic_set(&tcon->stats.cifs_stats.num_acl_get, 0); - atomic_set(&tcon->stats.cifs_stats.num_acl_set, 0); } static void From 273913b7c33a591e1747ffdc2bfc4daaf88bbb88 Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Wed, 5 Aug 2026 09:54:46 +0800 Subject: [PATCH 1776/2286] smb: client: implement fileattr_set for compression flags CIFS handles FS_IOC_SETFLAGS in cifs_ioctl() because it does not implement ->fileattr_set. This causes the request to fall back from the generic fileattr path and bypass its permission and serialization checks. Implement ->fileattr_set for regular files and directories and use the existing compression helper for FS_COMPR_FL. Accept an unchanged FS_CASEFOLD_FL reported by fileattr_get so chattr can update compression on case-insensitive shares. Tested chattr +c and -c on files and directories against Samba. Non-owner and read-only mount requests were rejected by the VFS checks. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsfs.c | 4 +- fs/smb/client/cifsfs.h | 2 + fs/smb/client/ioctl.c | 89 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index 5d7c786d1801eb..f65141e64adf2f 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1194,7 +1194,7 @@ int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) u32 attrs; /* Preserve FS_COMPR_FL previously reported by cifs_ioctl(). */ - if (CIFS_I(inode)->cifsAttrs & ATTR_COMPRESSED) + if (READ_ONCE(CIFS_I(inode)->cifsAttrs) & ATTR_COMPRESSED) fa->flags |= FS_COMPR_FL; /* @@ -1255,6 +1255,7 @@ const struct inode_operations cifs_dir_inode_ops = { .get_acl = cifs_get_acl, .set_acl = cifs_set_acl, .fileattr_get = cifs_fileattr_get, + .fileattr_set = cifs_fileattr_set, }; const struct inode_operations cifs_file_inode_ops = { @@ -1266,6 +1267,7 @@ const struct inode_operations cifs_file_inode_ops = { .get_acl = cifs_get_acl, .set_acl = cifs_set_acl, .fileattr_get = cifs_fileattr_get, + .fileattr_set = cifs_fileattr_set, }; const char *cifs_get_link(struct dentry *dentry, struct inode *inode, diff --git a/fs/smb/client/cifsfs.h b/fs/smb/client/cifsfs.h index 0b064236b4dc22..3c96521513cbd6 100644 --- a/fs/smb/client/cifsfs.h +++ b/fs/smb/client/cifsfs.h @@ -91,6 +91,8 @@ extern const struct inode_operations cifs_namespace_inode_operations; struct file_kattr; int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa); +int cifs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa); /* Functions related to files and directories */ diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index 71f92ebb3432ee..eb5cf580628d77 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -67,11 +68,11 @@ static long cifs_ioctl_query_info(unsigned int xid, struct file *filep, return rc; } -static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, +static int cifs_set_compression_by_path(unsigned int xid, struct dentry *dentry, struct cifs_tcon *tcon, __u16 compression_state) { - struct inode *inode = file_inode(filep); + struct inode *inode = d_inode(dentry); struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct TCP_Server_Info *server = tcon->ses->server; struct cifs_open_parms oparms; @@ -92,11 +93,11 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, cifs_sb->mnt_cifs_serverino_autodisabled) return -EOPNOTSUPP; - if (d_unhashed(filep->f_path.dentry)) + if (d_unhashed(dentry)) return -ESTALE; page = alloc_dentry_path(); - full_path = build_path_from_dentry(filep->f_path.dentry, page); + full_path = build_path_from_dentry(dentry, page); if (IS_ERR(full_path)) { free_dentry_path(page); return PTR_ERR(full_path); @@ -123,6 +124,10 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, goto close; uniqueid = le64_to_cpu(data.fi.IndexNumber); + if (!uniqueid) { + rc = -EOPNOTSUPP; + goto close; + } if (uniqueid != CIFS_I(inode)->uniqueid) { rc = -ESTALE; goto close; @@ -140,14 +145,14 @@ static int cifs_set_compression_by_path(unsigned int xid, struct file *filep, return rc; } -static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep, - struct cifs_tcon *tcon, - struct cifsFileInfo *cfile, - __u16 compression_state) +static int cifs_set_compression(unsigned int xid, struct dentry *dentry, + struct cifs_tcon *tcon, + struct cifsFileInfo *cfile, + __u16 compression_state) { struct cifsFileInfo *wfile; struct cifs_tcon *wtcon; - struct inode *inode = file_inode(filep); + struct inode *inode = d_inode(dentry); int rc; if (!tcon->ses->server->ops->set_compression) @@ -172,10 +177,68 @@ static int cifs_ioctl_set_compression(unsigned int xid, struct file *filep, return rc; } - return cifs_set_compression_by_path(xid, filep, tcon, + return cifs_set_compression_by_path(xid, dentry, tcon, compression_state); } +int cifs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, + struct file_kattr *fa) +{ + struct cifs_sb_info *cifs_sb = CIFS_SB(dentry); + struct inode *inode = d_inode(dentry); + struct file_kattr current_fa = {}; + u32 allowed = FS_COMPR_FL; + struct tcon_link *tlink; + __u16 compression_state; + bool enable_compression; + struct cifs_tcon *tcon; + unsigned int xid; + int rc; + + if (!fa->flags_valid) + return -EOPNOTSUPP; + + /* + * chattr writes back all flags returned by fileattr_get(). Accept + * FS_CASEFOLD_FL only when it reflects the share's casefold state. + */ + if (fa->flags & FS_CASEFOLD_FL) { + rc = cifs_fileattr_get(dentry, ¤t_fa); + if (rc) + return rc; + if (current_fa.flags & FS_CASEFOLD_FL) + allowed |= FS_CASEFOLD_FL; + } + if (fa->flags & ~allowed) + return -EOPNOTSUPP; + + enable_compression = fa->flags & FS_COMPR_FL; + compression_state = enable_compression ? COMPRESSION_FORMAT_DEFAULT : + COMPRESSION_FORMAT_NONE; + + tlink = cifs_sb_tlink(cifs_sb); + if (IS_ERR(tlink)) + return PTR_ERR(tlink); + tcon = tlink_tcon(tlink); + + xid = get_xid(); + rc = cifs_set_compression(xid, dentry, tcon, NULL, compression_state); + free_xid(xid); + cifs_put_tlink(tlink); + + if (!rc) { + spin_lock(&inode->i_lock); + if (enable_compression) + CIFS_I(inode)->cifsAttrs |= FILE_ATTRIBUTE_COMPRESSED; + else + CIFS_I(inode)->cifsAttrs &= ~FILE_ATTRIBUTE_COMPRESSED; + spin_unlock(&inode->i_lock); + } + + cifs_dbg(FYI, "set compress flag rc %d\n", rc); + return rc; +} + static long cifs_ioctl_copychunk(unsigned int xid, struct file *dst_file, unsigned long srcfd) { @@ -540,9 +603,9 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE; - rc = cifs_ioctl_set_compression(xid, filep, tcon, - pSMBFile, - compression_state); + rc = cifs_set_compression(xid, filep->f_path.dentry, + tcon, pSMBFile, + compression_state); if (rc == 0) { spin_lock(&inode->i_lock); if (enable_compression) From c38f4e84c9356a9e14d602a00c4afca09c79dc7a Mon Sep 17 00:00:00 2001 From: Huiwen He Date: Wed, 5 Aug 2026 09:54:47 +0800 Subject: [PATCH 1777/2286] smb: client: remove unused file flags ioctl handlers FS_IOC_GETFLAGS and FS_IOC_SETFLAGS are now handled by the generic VFS fileattr path, so their cifs_ioctl() handlers are unreachable. Remove the handlers and associated state. Signed-off-by: Huiwen He Reviewed-by: ChenXiaoSong Reviewed-by: David Howells Signed-off-by: Paulo Alcantara --- fs/smb/client/cifsfs.c | 2 +- fs/smb/client/ioctl.c | 81 ------------------------------------------ 2 files changed, 1 insertion(+), 82 deletions(-) diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c index f65141e64adf2f..f4743b463ebd1e 100644 --- a/fs/smb/client/cifsfs.c +++ b/fs/smb/client/cifsfs.c @@ -1193,7 +1193,7 @@ int cifs_fileattr_get(struct dentry *dentry, struct file_kattr *fa) struct inode *inode = d_inode(dentry); u32 attrs; - /* Preserve FS_COMPR_FL previously reported by cifs_ioctl(). */ + /* Report the cached SMB compressed attribute as FS_COMPR_FL. */ if (READ_ONCE(CIFS_I(inode)->cifsAttrs) & ATTR_COMPRESSED) fa->flags |= FS_COMPR_FL; diff --git a/fs/smb/client/ioctl.c b/fs/smb/client/ioctl.c index eb5cf580628d77..af4664f5b12026 100644 --- a/fs/smb/client/ioctl.c +++ b/fs/smb/client/ioctl.c @@ -527,14 +527,6 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) struct cifs_tcon *tcon; struct tcon_link *tlink; struct cifs_sb_info *cifs_sb; - __u64 ExtAttrBits = 0; - bool enable_compression; - __u16 compression_state; -#ifdef CONFIG_CIFS_POSIX -#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY - __u64 caps; -#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ -#endif /* CONFIG_CIFS_POSIX */ xid = get_xid(); @@ -545,79 +537,6 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) trace_smb3_ioctl(xid, pSMBFile->fid.persistent_fid, command); switch (command) { - case FS_IOC_GETFLAGS: - if (pSMBFile == NULL) - break; - tcon = tlink_tcon(pSMBFile->tlink); -#ifdef CONFIG_CIFS_POSIX -#ifdef CONFIG_CIFS_ALLOW_INSECURE_LEGACY - caps = le64_to_cpu(tcon->fsUnixInfo.Capability); - if (CIFS_UNIX_EXTATTR_CAP & caps) { - __u64 ExtAttrMask = 0; - rc = CIFSGetExtAttr(xid, tcon, - pSMBFile->fid.netfid, - &ExtAttrBits, &ExtAttrMask); - if (rc == 0) - rc = put_user(ExtAttrBits & - FS_FL_USER_VISIBLE, - (int __user *)arg); - if (rc != -EOPNOTSUPP) - break; - } -#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ -#endif /* CONFIG_CIFS_POSIX */ - if (CIFS_I(inode)->cifsAttrs & FILE_ATTRIBUTE_COMPRESSED) - ExtAttrBits |= FS_COMPR_FL; - - rc = put_user(ExtAttrBits & FS_FL_USER_VISIBLE, - (int __user *)arg); - break; - case FS_IOC_SETFLAGS: - if (pSMBFile == NULL) - break; - tcon = tlink_tcon(pSMBFile->tlink); - - if (get_user(ExtAttrBits, (int __user *)arg)) { - rc = -EFAULT; - break; - } - - /* - * if (CIFS_UNIX_EXTATTR_CAP & caps) - * rc = CIFSSetExtAttr(xid, tcon, - * pSMBFile->fid.netfid, - * extAttrBits, - * &ExtAttrMask); - * if (rc != -EOPNOTSUPP) - * break; - */ - - /* Currently only flag we can set or clear is compressed. */ - if (ExtAttrBits & ~FS_COMPR_FL) { - rc = -EOPNOTSUPP; - break; - } - - enable_compression = ExtAttrBits & FS_COMPR_FL; - compression_state = enable_compression ? - COMPRESSION_FORMAT_DEFAULT : - COMPRESSION_FORMAT_NONE; - - rc = cifs_set_compression(xid, filep->f_path.dentry, - tcon, pSMBFile, - compression_state); - if (rc == 0) { - spin_lock(&inode->i_lock); - if (enable_compression) - CIFS_I(inode)->cifsAttrs |= - FILE_ATTRIBUTE_COMPRESSED; - else - CIFS_I(inode)->cifsAttrs &= - ~FILE_ATTRIBUTE_COMPRESSED; - spin_unlock(&inode->i_lock); - } - cifs_dbg(FYI, "set compress flag rc %d\n", rc); - break; case CIFS_IOC_COPYCHUNK_FILE: rc = cifs_ioctl_copychunk(xid, filep, arg); break; From 740619915257969048c5fd3b94c6a55b5be11032 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:08:54 -0300 Subject: [PATCH 1778/2286] smb: client: size smb2_compound_vars.qi_iov for the padding iov A non-last, non-encrypted compound request gets an 8-byte alignment padding iov appended by smb2_set_next_command() at rq_iov[rq_nvec]. qi_iov was a single struct kvec used for the query_info link of such chains, so with rq_nvec == 1 the padding was written into the adjacent io_iov[0] member. That write stays within struct smb2_compound_vars and io_iov is unused on the query_info paths, so it is harmless today, but it relies on field ordering and would corrupt a live member if that changed. Add SMB2_QUERY_INFO_IOV_SIZE and make qi_iov an array so the padding iov gets its own slot. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 2 +- fs/smb/client/smb2inode.c | 4 ++-- fs/smb/client/smb2ops.c | 4 ++-- fs/smb/client/smb2pdu.h | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 7bf8f372697d6e..3199dec9a5b2d2 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -2256,7 +2256,7 @@ struct smb2_compound_vars { struct kvec rsp_iov[MAX_COMPOUND]; struct smb_rqst rqst[MAX_COMPOUND]; struct kvec open_iov[SMB2_CREATE_IOV_SIZE]; - struct kvec qi_iov; + struct kvec qi_iov[SMB2_QUERY_INFO_IOV_SIZE]; struct kvec io_iov[SMB2_IOCTL_IOV_SIZE]; struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE]; struct kvec hl_iov[SMB2_SET_INFO_IOV_SIZE]; diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 7c1472fe5b9a7a..cc29ff042c56fa 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -317,7 +317,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, /* Operation */ switch (cmds[i]) { case SMB2_OP_QUERY_INFO: - rqst[num_rqst].rq_iov = &vars->qi_iov; + rqst[num_rqst].rq_iov = vars->qi_iov; rqst[num_rqst].rq_nvec = 1; rc = SMB2_query_info_init(tcon, server, @@ -334,7 +334,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, ses->Suid, full_path); break; case SMB2_OP_POSIX_QUERY_INFO: - rqst[num_rqst].rq_iov = &vars->qi_iov; + rqst[num_rqst].rq_iov = vars->qi_iov; rqst[num_rqst].rq_nvec = 1; /* TBD: fix following to allow for longer SIDs */ diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index c441b6d9a693b1..d969f1a98b699c 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1728,7 +1728,7 @@ smb2_ioctl_query_info(const unsigned int xid, SMB2_O_INFO_FILE, 0, data, size); free_req1_func = SMB2_set_info_free; } else if (qi.flags == PASSTHRU_QUERY_INFO) { - rqst[1].rq_iov = &vars->qi_iov; + rqst[1].rq_iov = vars->qi_iov; rqst[1].rq_nvec = 1; rc = SMB2_query_info_init(tcon, server, @@ -3026,7 +3026,7 @@ smb2_query_info_compound(const unsigned int xid, struct cifs_tcon *tcon, goto qic_exit; smb2_set_next_command(tcon, &rqst[0]); - rqst[1].rq_iov = &vars->qi_iov; + rqst[1].rq_iov = vars->qi_iov; rqst[1].rq_nvec = 1; if (cfid) { diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h index b37a1e3941a1d9..04e755d2cdd86a 100644 --- a/fs/smb/client/smb2pdu.h +++ b/fs/smb/client/smb2pdu.h @@ -178,6 +178,9 @@ struct network_resiliency_req { */ #define SMB2_IOCTL_IOV_SIZE 2 +/* extra iov for the alignment padding smb2_set_next_command() may add */ +#define SMB2_QUERY_INFO_IOV_SIZE 2 + /* * PDU query infolevel structure definitions * BB consider moving to a different header From 78abbc67787d22328c14ba6f477c7e33ba496127 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:33:26 -0300 Subject: [PATCH 1779/2286] smb: client: drop redundant nls_table arg from SMBSetInformation() SMBSetInformation() already gets the cifs_sb whose local_nls the caller passes separately as nls_codepage, and that is the same table as tcon->ses->local_nls. Derive it from the session and drop the redundant parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 11 +++++------ fs/smb/client/smb1ops.c | 6 +++--- fs/smb/client/smb1proto.h | 3 +-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 31695f1efe8932..77c10bb7353e76 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -5728,11 +5728,9 @@ CIFSSMBSetFileSize(const unsigned int xid, struct cifs_tcon *tcon, return rc; } -int -SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, __le32 attributes, __le64 write_time, - const struct nls_table *nls_codepage, - struct cifs_sb_info *cifs_sb) +int SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, __le32 attributes, + __le64 write_time, struct cifs_sb_info *cifs_sb) { SETATTR_REQ *pSMB; SETATTR_RSP *pSMBr; @@ -5754,7 +5752,8 @@ SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon, if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { name_len = cifsConvertToUTF16((__le16 *) pSMB->fileName, - fileName, PATH_MAX, nls_codepage, + fileName, PATH_MAX, + tcon->ses->local_nls, cifs_remap(cifs_sb)); name_len++; /* trailing null */ name_len *= 2; diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 7d2ddf2dcad985..85548e8a601201 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -873,7 +873,7 @@ cifs_mkdir_setinfo(struct inode *inode, const char *full_path, rc = SMBSetInformation(xid, tcon, full_path, info.Attributes, 0 /* do not change write time */, - cifs_sb->local_nls, cifs_sb); + cifs_sb); if (rc == 0) cifsInode->cifsAttrs = dosattrs; } @@ -1072,7 +1072,7 @@ smb_set_file_info(struct inode *inode, const char *full_path, rc = SMBSetInformation(xid, tcon, full_path, buf->Attributes != 0 ? buf->Attributes : cpu_to_le32(cinode->cifsAttrs), write_time, - cifs_sb->local_nls, cifs_sb); + cifs_sb); if (rc == 0) cinode->cifsAttrs = le32_to_cpu(buf->Attributes); else @@ -1111,7 +1111,7 @@ smb_set_file_info(struct inode *inode, const char *full_path, SMBSetInformation(xid, tcon, full_path, buf->Attributes, 0 /* do not change write time */, - cifs_sb->local_nls, cifs_sb); + cifs_sb); } out: if (tlink != NULL) diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index 80eaeb3dd2ecb3..bb4efc7c1094ba 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -186,8 +186,7 @@ int CIFSSMBSetFileSize(const unsigned int xid, struct cifs_tcon *tcon, bool set_allocation); int SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, __le32 attributes, - __le64 write_time, const struct nls_table *nls_codepage, - struct cifs_sb_info *cifs_sb); + __le64 write_time, struct cifs_sb_info *cifs_sb); int CIFSSMBSetFileInfo(const unsigned int xid, struct cifs_tcon *tcon, const FILE_BASIC_INFO *data, __u16 fid, __u32 pid_of_opener); From cd040ff11dae8f6ba3a97cd0cee583d84d6a9be3 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:37:21 -0300 Subject: [PATCH 1780/2286] smb: client: drop redundant nls_table arg from CIFSSMBSetPathInfo() CIFSSMBSetPathInfo() already gets the cifs_sb whose local_nls the caller passes separately as nls_codepage, and that is the same table as tcon->ses->local_nls. Derive it from the session and drop the redundant parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 10 ++++------ fs/smb/client/smb1ops.c | 6 ++---- fs/smb/client/smb1proto.h | 1 - 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 77c10bb7353e76..4ab5f8ad3c58df 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -5914,11 +5914,9 @@ CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon, return rc; } -int -CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, const FILE_BASIC_INFO *data, - const struct nls_table *nls_codepage, - struct cifs_sb_info *cifs_sb) +int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, const FILE_BASIC_INFO *data, + struct cifs_sb_info *cifs_sb) { TRANSACTION2_SPI_REQ *pSMB = NULL; TRANSACTION2_SPI_RSP *pSMBr = NULL; @@ -5942,7 +5940,7 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { name_len = cifsConvertToUTF16((__le16 *) pSMB->FileName, fileName, - PATH_MAX, nls_codepage, remap); + PATH_MAX, tcon->ses->local_nls, remap); name_len++; /* trailing null */ name_len *= 2; } else { diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 85548e8a601201..313ef84ec22f60 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -867,8 +867,7 @@ cifs_mkdir_setinfo(struct inode *inode, const char *full_path, cifsInode = CIFS_I(inode); dosattrs = cifsInode->cifsAttrs|ATTR_READONLY; info.Attributes = cpu_to_le32(dosattrs); - rc = CIFSSMBSetPathInfo(xid, tcon, full_path, &info, cifs_sb->local_nls, - cifs_sb); + rc = CIFSSMBSetPathInfo(xid, tcon, full_path, &info, cifs_sb); if (rc == -EOPNOTSUPP || rc == -EINVAL) rc = SMBSetInformation(xid, tcon, full_path, info.Attributes, @@ -1022,8 +1021,7 @@ smb_set_file_info(struct inode *inode, const char *full_path, if (open_file) goto set_via_filehandle; - rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb->local_nls, - cifs_sb); + rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb); if (rc == 0) { cinode->cifsAttrs = le32_to_cpu(buf->Attributes); goto out; diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index bb4efc7c1094ba..fc9e1191c3cb77 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -195,7 +195,6 @@ int CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon, __u32 pid_of_opener); int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const FILE_BASIC_INFO *data, - const struct nls_table *nls_codepage, struct cifs_sb_info *cifs_sb); int CIFSSMBUnixSetFileInfo(const unsigned int xid, struct cifs_tcon *tcon, const struct cifs_unix_set_info_args *args, u16 fid, From 64fd7c38697b07a75537670c5dad19fd041001f4 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:42:09 -0300 Subject: [PATCH 1781/2286] smb: client: drop redundant nls_table arg from ->set_EA() The ->set_EA() operation already receives the cifs_sb whose local_nls the callers pass separately, and that is the same table as ses->local_nls (the session match requires an identical iocharset). The SMB2 implementation never used it. Drop the parameter: CIFSSMBSetEA() derives the codepage from tcon->ses->local_nls and smb2_set_ea() no longer takes it. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 2 +- fs/smb/client/cifssmb.c | 12 +++++------- fs/smb/client/smb1proto.h | 1 - fs/smb/client/smb2ops.c | 9 ++++----- fs/smb/client/xattr.c | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 3199dec9a5b2d2..14865bdbf3fd3e 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -574,7 +574,7 @@ struct smb_version_operations { size_t, struct cifs_sb_info *); int (*set_EA)(const unsigned int, struct cifs_tcon *, const char *, const char *, const void *, const __u16, - const struct nls_table *, struct cifs_sb_info *); + struct cifs_sb_info *); struct smb_ntsd * (*get_acl)(struct cifs_sb_info *cifssb, struct inode *ino, const char *patch, u32 *plen, u32 info); struct smb_ntsd * (*get_acl_by_fid)(struct cifs_sb_info *cifssmb, diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 4ab5f8ad3c58df..e1ef04c93fab2e 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -3163,7 +3163,6 @@ struct inode *cifs_create_reparse_inode(struct cifs_open_info_data *data, &ea->ea_data[0], &ea->ea_data[ea->ea_name_length+1], le16_to_cpu(ea->ea_value_length), - cifs_sb->local_nls, cifs_sb); if (rc) goto out_close; @@ -6383,11 +6382,10 @@ CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon, return (ssize_t)rc; } -int -CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, const char *ea_name, const void *ea_value, - const __u16 ea_value_len, const struct nls_table *nls_codepage, - struct cifs_sb_info *cifs_sb) +int CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, const char *ea_name, + const void *ea_value, const __u16 ea_value_len, + struct cifs_sb_info *cifs_sb) { struct smb_com_transaction2_spi_req *pSMB = NULL; struct smb_com_transaction2_spi_rsp *pSMBr = NULL; @@ -6412,7 +6410,7 @@ CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { name_len = cifsConvertToUTF16((__le16 *) pSMB->FileName, fileName, - PATH_MAX, nls_codepage, remap); + PATH_MAX, tcon->ses->local_nls, remap); name_len++; /* trailing null */ name_len *= 2; } else { diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index fc9e1191c3cb77..ebb2cf396b8f69 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -210,7 +210,6 @@ ssize_t CIFSSMBQAllEAs(const unsigned int xid, struct cifs_tcon *tcon, int CIFSSMBSetEA(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const char *ea_name, const void *ea_value, const __u16 ea_value_len, - const struct nls_table *nls_codepage, struct cifs_sb_info *cifs_sb); /* diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c index d969f1a98b699c..207aa2c6acf4c5 100644 --- a/fs/smb/client/smb2ops.c +++ b/fs/smb/client/smb2ops.c @@ -1186,11 +1186,10 @@ smb2_query_eas(const unsigned int xid, struct cifs_tcon *tcon, return rc; } -static int -smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, - const char *path, const char *ea_name, const void *ea_value, - const __u16 ea_value_len, const struct nls_table *nls_codepage, - struct cifs_sb_info *cifs_sb) +static int smb2_set_ea(const unsigned int xid, struct cifs_tcon *tcon, + const char *path, const char *ea_name, + const void *ea_value, const __u16 ea_value_len, + struct cifs_sb_info *cifs_sb) { struct smb2_compound_vars *vars; struct cifs_ses *ses = tcon->ses; diff --git a/fs/smb/client/xattr.c b/fs/smb/client/xattr.c index 5091f6c0d7fef8..86c778650d22d1 100644 --- a/fs/smb/client/xattr.c +++ b/fs/smb/client/xattr.c @@ -155,7 +155,7 @@ static int cifs_xattr_set(const struct xattr_handler *handler, if (pTcon->ses->server->ops->set_EA) { rc = pTcon->ses->server->ops->set_EA(xid, pTcon, full_path, name, value, (__u16)size, - cifs_sb->local_nls, cifs_sb); + cifs_sb); if (rc == 0) inode_set_ctime_current(inode); } From b07c7b781f89d400692763e6c45e48bd75d5f629 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:45:43 -0300 Subject: [PATCH 1782/2286] smb: client: drop redundant nls_table arg from build_avpair_blob() The nls codepage passed to build_avpair_blob() is always the session's ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsencrypt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index 71a2a59123f57e..fad3fd5e4f69b8 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -100,8 +100,7 @@ int __cifs_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server, * and client time (total five av pairs and + one end of fields indicator. * Allocate domain name which gets freed when session struct is deallocated. */ -static int -build_avpair_blob(struct cifs_ses *ses, const struct nls_table *nls_cp) +static int build_avpair_blob(struct cifs_ses *ses) { unsigned int dlen; unsigned int size = 2 * sizeof(struct ntlmssp2_name); @@ -141,7 +140,8 @@ build_avpair_blob(struct cifs_ses *ses, const struct nls_table *nls_cp) attrptr->type = cpu_to_le16(NTLMSSP_AV_NB_DOMAIN_NAME); attrptr->length = cpu_to_le16(2 * dlen); blobptr = (unsigned char *)attrptr + sizeof(struct ntlmssp2_name); - cifs_strtoUTF16((__le16 *)blobptr, ses->domainName, dlen, nls_cp); + cifs_strtoUTF16((__le16 *)blobptr, ses->domainName, dlen, + ses->local_nls); return 0; } @@ -414,7 +414,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) if (rc) goto setup_ntlmv2_rsp_ret; } else { - rc = build_avpair_blob(ses, nls_cp); + rc = build_avpair_blob(ses); if (rc) { cifs_dbg(VFS, "error %d building av pair blob\n", rc); goto setup_ntlmv2_rsp_ret; From 71098da2195fa242a99f8b1ef3f152d259025106 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:47:42 -0300 Subject: [PATCH 1783/2286] smb: client: drop redundant nls_table arg from calc_ntlmv2_hash() The nls codepage passed to calc_ntlmv2_hash() is always ses->local_nls; derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsencrypt.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index fad3fd5e4f69b8..7ce0c38cfbb975 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -235,8 +235,7 @@ static __le64 find_timestamp(struct cifs_ses *ses) return cpu_to_le64(cifs_UnixTimeToNT(ts)); } -static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, - const struct nls_table *nls_cp) +static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash) { int len; char nt_hash[CIFS_NTHASH_SIZE]; @@ -246,7 +245,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, wchar_t *server; /* calculate md4 hash of password */ - E_md4hash(ses->password, nt_hash, nls_cp); + E_md4hash(ses->password, nt_hash, ses->local_nls); hmac_md5_init_usingrawkey(&hmac_ctx, nt_hash, CIFS_NTHASH_SIZE); memzero_explicit(nt_hash, sizeof(nt_hash)); @@ -258,7 +257,8 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, goto out_nomem; if (len) { - len = cifs_strtoUTF16(user, ses->user_name, len, nls_cp); + len = cifs_strtoUTF16(user, ses->user_name, len, + ses->local_nls); UniStrupr(user); } else { *(u16 *)user = 0; @@ -276,7 +276,7 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, goto out_nomem; len = cifs_strtoUTF16((__le16 *)domain, ses->domainName, len, - nls_cp); + ses->local_nls); hmac_md5_update(&hmac_ctx, (const u8 *)domain, 2 * len); kfree(domain); } else { @@ -287,7 +287,8 @@ static int calc_ntlmv2_hash(struct cifs_ses *ses, char *ntlmv2_hash, if (server == NULL) goto out_nomem; - len = cifs_strtoUTF16((__le16 *)server, ses->ip_addr, len, nls_cp); + len = cifs_strtoUTF16((__le16 *)server, ses->ip_addr, len, + ses->local_nls); hmac_md5_update(&hmac_ctx, (const u8 *)server, 2 * len); kfree(server); } @@ -452,7 +453,7 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) } /* calculate ntlmv2_hash */ - rc = calc_ntlmv2_hash(ses, ntlmv2_hash, nls_cp); + rc = calc_ntlmv2_hash(ses, ntlmv2_hash); if (rc) { cifs_dbg(VFS, "Could not get NTLMv2 hash, rc=%d\n", rc); goto unlock; From 6d16358ac43d2f784f9e0ac0c12b372bf76fb41c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:50:04 -0300 Subject: [PATCH 1784/2286] smb: client: drop redundant nls_table arg from setup_ntlmv2_rsp() The nls codepage passed to setup_ntlmv2_rsp() is always ses->local_nls, which is never NULL, so derive it from @ses and drop both the parameter and the now-dead NULL check. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsencrypt.c | 8 +------- fs/smb/client/cifsproto.h | 2 +- fs/smb/client/sess.c | 2 +- fs/smb/client/smb1session.c | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c index 7ce0c38cfbb975..c0bd4049f65f37 100644 --- a/fs/smb/client/cifsencrypt.c +++ b/fs/smb/client/cifsencrypt.c @@ -373,8 +373,7 @@ static int set_auth_key_response(struct cifs_ses *ses) return rc; } -int -setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) +int setup_ntlmv2_rsp(struct cifs_ses *ses) { unsigned char *tiblob = NULL; /* target info blob */ struct ntlmv2_resp *ntlmv2; @@ -383,11 +382,6 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp) __u64 cc; int rc; - if (nls_cp == NULL) { - cifs_dbg(VFS, "%s called with nls_cp==NULL\n", __func__); - return -EINVAL; - } - if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) { if (!ses->domainName) { if (ses->domainAuto) { diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 582cd182c6165b..46b7ac2885f2aa 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -307,7 +307,7 @@ struct cifs_tcon *tcon_info_alloc(bool dir_leases_enabled, enum smb3_tcon_ref_trace trace); void tconInfoFree(struct cifs_tcon *tcon, enum smb3_tcon_ref_trace trace); -int setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp); +int setup_ntlmv2_rsp(struct cifs_ses *ses); void cifs_crypto_secmech_release(struct TCP_Server_Info *server); int calc_seckey(struct cifs_ses *ses); int generate_smb30signingkey(struct cifs_ses *ses, diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index fc105308226c20..06a894794741ea 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -929,7 +929,7 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer, unsigned char *tmp; int len; - rc = setup_ntlmv2_rsp(ses, nls_cp); + rc = setup_ntlmv2_rsp(ses); if (rc) { cifs_dbg(VFS, "Error %d during NTLMSSP authentication\n", rc); *buflen = 0; diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 83bfbf0c068e31..9673ecd87f864b 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -443,7 +443,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) if (ses->user_name != NULL) { /* calculate nlmv2 response and session key */ - rc = setup_ntlmv2_rsp(ses, sess_data->nls_cp); + rc = setup_ntlmv2_rsp(ses); if (rc) { cifs_dbg(VFS, "Error %d during NTLMv2 authentication\n", rc); goto out; From a8ef7f94f90136c1650fad5f748949f75ee44778 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:59:03 -0300 Subject: [PATCH 1785/2286] smb: client: drop redundant nls_table arg from build_ntlmssp_negotiate_blob() The nls codepage passed to build_ntlmssp_negotiate_blob() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/ntlmssp.h | 3 +-- fs/smb/client/sess.c | 7 +++---- fs/smb/client/smb1session.c | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/ntlmssp.h b/fs/smb/client/ntlmssp.h index 7d68153985f847..658d7795db3ee5 100644 --- a/fs/smb/client/ntlmssp.h +++ b/fs/smb/client/ntlmssp.h @@ -146,8 +146,7 @@ int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, struct cifs_ses *ses); int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp); + struct TCP_Server_Info *server); int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, struct TCP_Server_Info *server, diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 06a894794741ea..01f73e540d89bc 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -794,8 +794,7 @@ static inline void cifs_security_buffer_from_str(SECURITY_BUFFER *pbuf, int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp) + struct TCP_Server_Info *server) { int rc = 0; NEGOTIATE_MESSAGE *sec_blob; @@ -835,13 +834,13 @@ int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, NULL, CIFS_MAX_DOMAINNAME_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); cifs_security_buffer_from_str(&sec_blob->WorkstationName, NULL, CIFS_MAX_WORKSTATION_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); *buflen = tmp - *pbuffer; setup_ntlm_neg_ret: diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 9673ecd87f864b..b4a334d89cb151 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -741,8 +741,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data) /* Build security blob before we assemble the request */ rc = build_ntlmssp_negotiate_blob(&ntlmsspblob, - &blob_len, ses, server, - sess_data->nls_cp); + &blob_len, ses, server); if (rc) goto out_free_ntlmsspblob; From aadcd752e34b8afde1becd07fac4bd471dc66b5d Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Sun, 11 Oct 2026 23:59:45 -0300 Subject: [PATCH 1786/2286] smb: client: drop redundant nls_table arg from build_ntlmssp_smb3_negotiate_blob() The nls codepage passed to build_ntlmssp_smb3_negotiate_blob() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/ntlmssp.h | 3 +-- fs/smb/client/sess.c | 7 +++---- fs/smb/client/smb2pdu.c | 3 +-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/ntlmssp.h b/fs/smb/client/ntlmssp.h index 658d7795db3ee5..1f8c33835110f1 100644 --- a/fs/smb/client/ntlmssp.h +++ b/fs/smb/client/ntlmssp.h @@ -149,8 +149,7 @@ int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct TCP_Server_Info *server); int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp); + struct TCP_Server_Info *server); int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, struct TCP_Server_Info *server, diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 01f73e540d89bc..86662a4dd9fa77 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -855,8 +855,7 @@ int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp) + struct TCP_Server_Info *server) { int rc = 0; struct negotiate_message *sec_blob; @@ -901,13 +900,13 @@ int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, NULL, CIFS_MAX_DOMAINNAME_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); cifs_security_buffer_from_str(&sec_blob->WorkstationName, NULL, CIFS_MAX_WORKSTATION_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); *buflen = tmp - *pbuffer; setup_ntlm_smb3_neg_ret: diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 71d44c6da4b5b4..d117d61cb8c2e8 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -1833,8 +1833,7 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data) goto out_err; rc = build_ntlmssp_smb3_negotiate_blob(&ntlmssp_blob, - &blob_length, ses, server, - sess_data->nls_cp); + &blob_length, ses, server); if (rc) goto out; From 4acec99c4c1ad31234f4eb9c3508886443d73b11 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:00:47 -0300 Subject: [PATCH 1787/2286] smb: client: drop redundant nls_table arg from build_ntlmssp_auth_blob() The nls codepage passed to build_ntlmssp_auth_blob() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/ntlmssp.h | 3 +-- fs/smb/client/sess.c | 9 ++++----- fs/smb/client/smb1session.c | 3 +-- fs/smb/client/smb2pdu.c | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/fs/smb/client/ntlmssp.h b/fs/smb/client/ntlmssp.h index 1f8c33835110f1..0957b4002b9c32 100644 --- a/fs/smb/client/ntlmssp.h +++ b/fs/smb/client/ntlmssp.h @@ -152,5 +152,4 @@ int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, u16 *buflen, struct TCP_Server_Info *server); int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp); + struct TCP_Server_Info *server); diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index 86662a4dd9fa77..f86a7a8cd1f7f4 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -918,8 +918,7 @@ int build_ntlmssp_smb3_negotiate_blob(unsigned char **pbuffer, int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp) + struct TCP_Server_Info *server) { int rc; AUTHENTICATE_MESSAGE *sec_blob; @@ -988,19 +987,19 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer, ses->domainName, CIFS_MAX_DOMAINNAME_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); cifs_security_buffer_from_str(&sec_blob->UserName, ses->user_name, CIFS_MAX_USERNAME_LEN, *pbuffer, &tmp, - nls_cp); + ses->local_nls); cifs_security_buffer_from_str(&sec_blob->WorkstationName, ses->workstation_name, ntlmssp_workstation_name_size(ses), *pbuffer, &tmp, - nls_cp); + ses->local_nls); if ((ses->ntlmssp->server_flags & NTLMSSP_NEGOTIATE_KEY_XCH) && (!ses->server->session_estab || ses->ntlmssp->sesskey_per_smbsess) && diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index b4a334d89cb151..68eabde27ac854 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -835,8 +835,7 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data) pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base; smb_buf = (struct smb_hdr *)pSMB; rc = build_ntlmssp_auth_blob(&ntlmsspblob, - &blob_len, ses, server, - sess_data->nls_cp); + &blob_len, ses, server); if (rc) goto out_free_ntlmsspblob; sess_data->iov[1].iov_len = blob_len; diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index d117d61cb8c2e8..5c06f9d42fc64a 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -1917,8 +1917,7 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data) req->hdr.SessionId = cpu_to_le64(ses->Suid); rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, - ses, server, - sess_data->nls_cp); + ses, server); if (rc) { cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc); goto out; From 6b3884e9d0acbaf6f7ed6e2cca49308d298b0e29 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:02:41 -0300 Subject: [PATCH 1788/2286] smb: client: drop unused nls_table arg from ascii_oslm_strings() ascii_oslm_strings() never uses the nls codepage, so drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 68eabde27ac854..74b1e9683ba443 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -106,8 +106,7 @@ unicode_oslm_strings(char **pbcc_area, const struct nls_table *nls_cp) *pbcc_area = bcc_ptr; } -static void -ascii_oslm_strings(char **pbcc_area, const struct nls_table *nls_cp) +static void ascii_oslm_strings(char **pbcc_area) { char *bcc_ptr = *pbcc_area; @@ -213,7 +212,7 @@ static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, /* BB check for overflow here */ ascii_domain_string(&bcc_ptr, ses, nls_cp); - ascii_oslm_strings(&bcc_ptr, nls_cp); + ascii_oslm_strings(&bcc_ptr); *pbcc_area = bcc_ptr; } @@ -595,7 +594,7 @@ sess_auth_kerberos(struct sess_data *sess_data) unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp); unicode_domain_string(&bcc_ptr, ses, sess_data->nls_cp); } else { - ascii_oslm_strings(&bcc_ptr, sess_data->nls_cp); + ascii_oslm_strings(&bcc_ptr); ascii_domain_string(&bcc_ptr, ses, sess_data->nls_cp); } @@ -694,7 +693,7 @@ _sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data) } unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp); } else { - ascii_oslm_strings(&bcc_ptr, sess_data->nls_cp); + ascii_oslm_strings(&bcc_ptr); } sess_data->iov[2].iov_len = (long) bcc_ptr - From 40c21e0e41ba3cc199ea088b9dde9651145909d8 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:03:36 -0300 Subject: [PATCH 1789/2286] smb: client: drop redundant nls_table arg from unicode_domain_string() The nls codepage passed to unicode_domain_string() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 74b1e9683ba443..c00d3e6e17cba1 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -121,8 +121,7 @@ static void ascii_oslm_strings(char **pbcc_area) *pbcc_area = bcc_ptr; } -static void unicode_domain_string(char **pbcc_area, struct cifs_ses *ses, - const struct nls_table *nls_cp) +static void unicode_domain_string(char **pbcc_area, struct cifs_ses *ses) { char *bcc_ptr = *pbcc_area; int bytes_ret = 0; @@ -138,7 +137,8 @@ static void unicode_domain_string(char **pbcc_area, struct cifs_ses *ses, bytes_ret = 0; } else bytes_ret = cifs_strtoUTF16((__le16 *) bcc_ptr, ses->domainName, - CIFS_MAX_DOMAINNAME_LEN, nls_cp); + CIFS_MAX_DOMAINNAME_LEN, + ses->local_nls); bcc_ptr += 2 * bytes_ret; bcc_ptr += 2; /* account for null terminator */ @@ -184,7 +184,7 @@ static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, bcc_ptr += 2 * bytes_ret; bcc_ptr += 2; /* account for null termination */ - unicode_domain_string(&bcc_ptr, ses, nls_cp); + unicode_domain_string(&bcc_ptr, ses); unicode_oslm_strings(&bcc_ptr, nls_cp); *pbcc_area = bcc_ptr; @@ -592,7 +592,7 @@ sess_auth_kerberos(struct sess_data *sess_data) bcc_ptr++; } unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp); - unicode_domain_string(&bcc_ptr, ses, sess_data->nls_cp); + unicode_domain_string(&bcc_ptr, ses); } else { ascii_oslm_strings(&bcc_ptr); ascii_domain_string(&bcc_ptr, ses, sess_data->nls_cp); From 40e439b61fd96204a5163c149b00bd348dd8abf6 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:03:59 -0300 Subject: [PATCH 1790/2286] smb: client: drop unused nls_table arg from ascii_domain_string() ascii_domain_string() never uses the nls codepage, so drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index c00d3e6e17cba1..8c8b42b2704de2 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -145,8 +145,7 @@ static void unicode_domain_string(char **pbcc_area, struct cifs_ses *ses) *pbcc_area = bcc_ptr; } -static void ascii_domain_string(char **pbcc_area, struct cifs_ses *ses, - const struct nls_table *nls_cp) +static void ascii_domain_string(char **pbcc_area, struct cifs_ses *ses) { char *bcc_ptr = *pbcc_area; int len; @@ -211,7 +210,7 @@ static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, /* BB check for overflow here */ - ascii_domain_string(&bcc_ptr, ses, nls_cp); + ascii_domain_string(&bcc_ptr, ses); ascii_oslm_strings(&bcc_ptr); *pbcc_area = bcc_ptr; @@ -595,7 +594,7 @@ sess_auth_kerberos(struct sess_data *sess_data) unicode_domain_string(&bcc_ptr, ses); } else { ascii_oslm_strings(&bcc_ptr); - ascii_domain_string(&bcc_ptr, ses, sess_data->nls_cp); + ascii_domain_string(&bcc_ptr, ses); } sess_data->iov[2].iov_len = (long) bcc_ptr - From f945ba31422ca963666dd16e6e3207cc5f4f12b1 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:04:38 -0300 Subject: [PATCH 1791/2286] smb: client: drop redundant nls_table arg from unicode_ssetup_strings() The nls codepage passed to unicode_ssetup_strings() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 8c8b42b2704de2..db7ce16b140be6 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -163,8 +163,7 @@ static void ascii_domain_string(char **pbcc_area, struct cifs_ses *ses) *pbcc_area = bcc_ptr; } -static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, - const struct nls_table *nls_cp) +static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses) { char *bcc_ptr = *pbcc_area; int bytes_ret = 0; @@ -178,13 +177,14 @@ static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, *(bcc_ptr+1) = 0; } else { bytes_ret = cifs_strtoUTF16((__le16 *) bcc_ptr, ses->user_name, - CIFS_MAX_USERNAME_LEN, nls_cp); + CIFS_MAX_USERNAME_LEN, + ses->local_nls); } bcc_ptr += 2 * bytes_ret; bcc_ptr += 2; /* account for null termination */ unicode_domain_string(&bcc_ptr, ses); - unicode_oslm_strings(&bcc_ptr, nls_cp); + unicode_oslm_strings(&bcc_ptr, ses->local_nls); *pbcc_area = bcc_ptr; } @@ -465,7 +465,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) *bcc_ptr = 0; bcc_ptr++; } - unicode_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp); + unicode_ssetup_strings(&bcc_ptr, ses); } else { ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp); } From 34e037c98a5de07559ec2693a1e4998762f0c00d Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:05:01 -0300 Subject: [PATCH 1792/2286] smb: client: drop unused nls_table arg from ascii_ssetup_strings() ascii_ssetup_strings() no longer uses the nls codepage, so drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index db7ce16b140be6..df2d3eea898d21 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -189,8 +189,7 @@ static void unicode_ssetup_strings(char **pbcc_area, struct cifs_ses *ses) *pbcc_area = bcc_ptr; } -static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses, - const struct nls_table *nls_cp) +static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses) { char *bcc_ptr = *pbcc_area; int len; @@ -467,7 +466,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) } unicode_ssetup_strings(&bcc_ptr, ses); } else { - ascii_ssetup_strings(&bcc_ptr, ses, sess_data->nls_cp); + ascii_ssetup_strings(&bcc_ptr, ses); } From ea0261b23fa5baedbfef1c8d45aeeb2627c0330a Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:06:53 -0300 Subject: [PATCH 1793/2286] smb: client: drop redundant nls_table arg from decode_unicode_ssetup() The nls codepage passed to decode_unicode_ssetup() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index df2d3eea898d21..d1ea7e0b2d5843 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -215,9 +215,8 @@ static void ascii_ssetup_strings(char **pbcc_area, struct cifs_ses *ses) *pbcc_area = bcc_ptr; } -static void -decode_unicode_ssetup(char **pbcc_area, int bleft, struct cifs_ses *ses, - const struct nls_table *nls_cp) +static void decode_unicode_ssetup(char **pbcc_area, int bleft, + struct cifs_ses *ses) { int len; char *data = *pbcc_area; @@ -225,7 +224,8 @@ decode_unicode_ssetup(char **pbcc_area, int bleft, struct cifs_ses *ses, cifs_dbg(FYI, "bleft %d\n", bleft); kfree(ses->serverOS); - ses->serverOS = cifs_strndup_from_utf16(data, bleft, true, nls_cp); + ses->serverOS = cifs_strndup_from_utf16(data, bleft, true, + ses->local_nls); cifs_dbg(FYI, "serverOS=%s\n", ses->serverOS); len = (UniStrnlen((wchar_t *) data, bleft / 2) * 2) + 2; data += len; @@ -234,7 +234,8 @@ decode_unicode_ssetup(char **pbcc_area, int bleft, struct cifs_ses *ses, return; kfree(ses->serverNOS); - ses->serverNOS = cifs_strndup_from_utf16(data, bleft, true, nls_cp); + ses->serverNOS = cifs_strndup_from_utf16(data, bleft, true, + ses->local_nls); cifs_dbg(FYI, "serverNOS=%s\n", ses->serverNOS); len = (UniStrnlen((wchar_t *) data, bleft / 2) * 2) + 2; data += len; @@ -243,7 +244,8 @@ decode_unicode_ssetup(char **pbcc_area, int bleft, struct cifs_ses *ses, return; kfree(ses->serverDomain); - ses->serverDomain = cifs_strndup_from_utf16(data, bleft, true, nls_cp); + ses->serverDomain = cifs_strndup_from_utf16(data, bleft, true, + ses->local_nls); cifs_dbg(FYI, "serverDomain=%s\n", ses->serverDomain); return; @@ -504,8 +506,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) ++bcc_ptr; --bytes_remaining; } - decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, sess_data->nls_cp); @@ -640,8 +641,7 @@ sess_auth_kerberos(struct sess_data *sess_data) ++bcc_ptr; --bytes_remaining; } - decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, sess_data->nls_cp); @@ -891,8 +891,7 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data) ++bcc_ptr; --bytes_remaining; } - decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, sess_data->nls_cp); From 9f9ab02ddfcbac6174de72745e09dcb460b28c68 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:07:15 -0300 Subject: [PATCH 1794/2286] smb: client: drop unused nls_table arg from decode_ascii_ssetup() decode_ascii_ssetup() never uses the nls codepage, so drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index d1ea7e0b2d5843..2f6eb57dd1791e 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -252,8 +252,7 @@ static void decode_unicode_ssetup(char **pbcc_area, int bleft, } static void decode_ascii_ssetup(char **pbcc_area, __u16 bleft, - struct cifs_ses *ses, - const struct nls_table *nls_cp) + struct cifs_ses *ses) { int len; char *bcc_ptr = *pbcc_area; @@ -508,8 +507,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data) } decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { - decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses); } rc = sess_establish_session(sess_data); @@ -643,8 +641,7 @@ sess_auth_kerberos(struct sess_data *sess_data) } decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { - decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses); } rc = sess_establish_session(sess_data); @@ -893,8 +890,7 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data) } decode_unicode_ssetup(&bcc_ptr, bytes_remaining, ses); } else { - decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses, - sess_data->nls_cp); + decode_ascii_ssetup(&bcc_ptr, bytes_remaining, ses); } out_free_ntlmsspblob: From ca67b7abefda27f8e898efc2c7e4e0ade65ab58f Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:10:57 -0300 Subject: [PATCH 1795/2286] smb: client: drop redundant nls_table arg from ->sess_setup() The nls codepage threaded through ->sess_setup() and stored in sess_data->nls_cp is always ses->local_nls, so derive it from @ses and drop both the parameter and the now-dead sess_data->nls_cp member. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 3 +-- fs/smb/client/connect.c | 2 +- fs/smb/client/smb1proto.h | 3 +-- fs/smb/client/smb1session.c | 9 +++------ fs/smb/client/smb2pdu.c | 8 ++------ fs/smb/client/smb2proto.h | 3 +-- 6 files changed, 9 insertions(+), 19 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 14865bdbf3fd3e..6a345ffd51d40c 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -373,8 +373,7 @@ struct smb_version_operations { unsigned int (*negotiate_rsize)(struct cifs_tcon *tcon, struct smb3_fs_context *ctx); /* setup smb sessionn */ int (*sess_setup)(const unsigned int, struct cifs_ses *, - struct TCP_Server_Info *server, - const struct nls_table *); + struct TCP_Server_Info *server); /* close smb session */ int (*logoff)(const unsigned int, struct cifs_ses *); /* connect to a server share */ diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 90eda91d9da97d..067330fa0069f7 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -4188,7 +4188,7 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, if (!rc) { if (server->ops->sess_setup) - rc = server->ops->sess_setup(xid, ses, server, nls_info); + rc = server->ops->sess_setup(xid, ses, server); else rc = -ENOSYS; } diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index ebb2cf396b8f69..adaa3fd1ef1600 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -271,8 +271,7 @@ void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon, * smb1session.c */ int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp); + struct TCP_Server_Info *server); /* * smb1transport.c diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index 2f6eb57dd1791e..c45d3254dfac7a 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -20,7 +20,6 @@ struct sess_data { unsigned int xid; struct cifs_ses *ses; struct TCP_Server_Info *server; - struct nls_table *nls_cp; void (*func)(struct sess_data *); int result; unsigned int in_len; @@ -588,7 +587,7 @@ sess_auth_kerberos(struct sess_data *sess_data) *bcc_ptr = 0; bcc_ptr++; } - unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp); + unicode_oslm_strings(&bcc_ptr, sess_data->ses->local_nls); unicode_domain_string(&bcc_ptr, ses); } else { ascii_oslm_strings(&bcc_ptr); @@ -686,7 +685,7 @@ _sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data) *bcc_ptr = 0; bcc_ptr++; } - unicode_oslm_strings(&bcc_ptr, sess_data->nls_cp); + unicode_oslm_strings(&bcc_ptr, sess_data->ses->local_nls); } else { ascii_oslm_strings(&bcc_ptr); } @@ -948,8 +947,7 @@ static int select_sec(struct sess_data *sess_data) } int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp) + struct TCP_Server_Info *server) { int rc = 0; struct sess_data *sess_data; @@ -967,7 +965,6 @@ int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, sess_data->ses = ses; sess_data->server = server; sess_data->buf0_type = CIFS_NO_BUFFER; - sess_data->nls_cp = (struct nls_table *) nls_cp; rc = select_sec(sess_data); if (rc) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 5c06f9d42fc64a..cb72735a4553ed 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -1529,7 +1529,6 @@ struct SMB2_sess_data { unsigned int xid; struct cifs_ses *ses; struct TCP_Server_Info *server; - struct nls_table *nls_cp; void (*func)(struct SMB2_sess_data *); int result; u64 previous_session; @@ -2002,10 +2001,8 @@ SMB2_select_sec(struct SMB2_sess_data *sess_data) return 0; } -int -SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp) +int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server) { int rc = 0; struct SMB2_sess_data *sess_data; @@ -2025,7 +2022,6 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, sess_data->ses = ses; sess_data->server = server; sess_data->buf0_type = CIFS_NO_BUFFER; - sess_data->nls_cp = (struct nls_table *) nls_cp; sess_data->previous_session = ses->Suid; rc = SMB2_select_sec(sess_data); diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 69a32f8311ca4c..aa1340fec27b09 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -131,8 +131,7 @@ bool smb2_should_replay(struct cifs_tcon *tcon, int *pretries, int SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server); int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - const struct nls_table *nls_cp); + struct TCP_Server_Info *server); int SMB2_logoff(const unsigned int xid, struct cifs_ses *ses); int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, struct cifs_tcon *tcon, const struct nls_table *cp); From 172cbcd178c876113b29df2b33c53fa2ddeb393a Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:11:58 -0300 Subject: [PATCH 1796/2286] smb: client: drop redundant nls_table arg from cifs_setup_session() The nls codepage passed to cifs_setup_session() is always ses->local_nls and, now that ->sess_setup() no longer takes it, is unused, so drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsproto.h | 3 +-- fs/smb/client/cifssmb.c | 2 +- fs/smb/client/connect.c | 11 ++++------- fs/smb/client/sess.c | 2 +- fs/smb/client/smb2pdu.c | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 46b7ac2885f2aa..7b9eca22281fc3 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -290,8 +290,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon); int cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server); int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - struct nls_table *nls_info); + struct TCP_Server_Info *server); int cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required); diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index e1ef04c93fab2e..06438d3fff9045 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -163,7 +163,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) return -EHOSTDOWN; goto again; } - rc = cifs_setup_session(0, ses, server, ses->local_nls); + rc = cifs_setup_session(0, ses, server); if ((rc == -EACCES) || (rc == -EHOSTDOWN) || (rc == -EKEYREVOKED)) { /* * Try alternate password for next reconnect if an alternate diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 067330fa0069f7..fba926347682fb 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -2403,8 +2403,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) return ERR_PTR(rc); } - rc = cifs_setup_session(xid, ses, server, - ctx->local_nls); + rc = cifs_setup_session(xid, ses, server); if (rc) { if (((rc == -EACCES) || (rc == -EKEYEXPIRED) || (rc == -EKEYREVOKED)) && !retries && ses->password2) { @@ -2521,7 +2520,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) mutex_lock(&ses->session_mutex); rc = cifs_negotiate_protocol(xid, ses, server); if (!rc) - rc = cifs_setup_session(xid, ses, server, ctx->local_nls); + rc = cifs_setup_session(xid, ses, server); mutex_unlock(&ses->session_mutex); /* each channel uses a different signing key */ @@ -4093,10 +4092,8 @@ cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses, return rc; } -int -cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, - struct TCP_Server_Info *server, - struct nls_table *nls_info) +int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, + struct TCP_Server_Info *server) { int rc = 0; struct TCP_Server_Info *pserver = SERVER_IS_CHAN(server) ? server->primary_server : server; diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c index f86a7a8cd1f7f4..c1fc90726e2d15 100644 --- a/fs/smb/client/sess.c +++ b/fs/smb/client/sess.c @@ -620,7 +620,7 @@ cifs_ses_add_channel(struct cifs_ses *ses, rc = cifs_negotiate_protocol(xid, ses, chan->server); if (!rc) - rc = cifs_setup_session(xid, ses, chan->server, ses->local_nls); + rc = cifs_setup_session(xid, ses, chan->server); mutex_unlock(&ses->session_mutex); diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index cb72735a4553ed..41ca0853831686 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -395,7 +395,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, } } - rc = cifs_setup_session(0, ses, server, ses->local_nls); + rc = cifs_setup_session(0, ses, server); if ((rc == -EACCES) || (rc == -EKEYEXPIRED) || (rc == -EKEYREVOKED)) { /* * Try alternate password for next reconnect (key rotation From fd8ac150455df9b5a08f67c2fb628465f28c4f33 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:14:15 -0300 Subject: [PATCH 1797/2286] smb: client: drop redundant nls_table arg from ->tree_connect() The nls codepage passed to ->tree_connect() is always ses->local_nls, so derive it from @ses and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifsglob.h | 2 +- fs/smb/client/cifssmb.c | 11 +++++------ fs/smb/client/connect.c | 8 +++----- fs/smb/client/dfs.c | 6 +++--- fs/smb/client/smb1proto.h | 2 +- fs/smb/client/smb2pdu.c | 8 ++++---- fs/smb/client/smb2proto.h | 2 +- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 6a345ffd51d40c..73b49dcb5215aa 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -378,7 +378,7 @@ struct smb_version_operations { int (*logoff)(const unsigned int, struct cifs_ses *); /* connect to a server share */ int (*tree_connect)(const unsigned int, struct cifs_ses *, const char *, - struct cifs_tcon *, const struct nls_table *); + struct cifs_tcon *); /* close tree connection */ int (*tree_disconnect)(const unsigned int, struct cifs_tcon *); /* get DFS referrals */ diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 06438d3fff9045..910f454fabfa1d 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -536,10 +536,8 @@ CIFSSMBNegotiate(const unsigned int xid, /* * Issue a TREE_CONNECT request. */ -int -CIFSTCon(const unsigned int xid, struct cifs_ses *ses, - const char *tree, struct cifs_tcon *tcon, - const struct nls_table *nls_codepage) +int CIFSTCon(const unsigned int xid, struct cifs_ses *ses, const char *tree, + struct cifs_tcon *tcon) { struct smb_hdr *smb_buffer; struct smb_hdr *smb_buffer_response; @@ -590,7 +588,8 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, length = cifs_strtoUTF16((__le16 *) bcc_ptr, tree, 6 /* max utf8 char length in bytes */ * - (/* server len*/ + 256 /* share len */), nls_codepage); + (/* server len*/ + 256 /* share len */), + ses->local_nls); bcc_ptr += 2 * length; /* convert num 16 bit words to bytes */ bcc_ptr += 2; /* skip trailing null */ } else { /* ASCII */ @@ -648,7 +647,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, kfree(tcon->nativeFileSystem); tcon->nativeFileSystem = cifs_strndup_from_utf16(bcc_ptr, bytes_left, is_unicode, - nls_codepage); + ses->local_nls); cifs_dbg(FYI, "nativeFileSystem=%s\n", tcon->nativeFileSystem); diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index fba926347682fb..550e687a1b15fa 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -2085,7 +2085,7 @@ struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal) tcon->ses = ses; tcon->ipc = true; tcon->seal = seal; - rc = server->ops->tree_connect(xid, ses, unc, tcon, ses->local_nls); + rc = server->ops->tree_connect(xid, ses, unc, tcon); free_xid(xid); if (rc) { @@ -2815,8 +2815,7 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx) } xid = get_xid(); - rc = ses->server->ops->tree_connect(xid, ses, ctx->UNC, tcon, - ctx->local_nls); + rc = ses->server->ops->tree_connect(xid, ses, ctx->UNC, tcon); free_xid(xid); cifs_dbg(FYI, "Tcon rc = %d\n", rc); if (rc) @@ -4561,8 +4560,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) tcon->status = TID_IN_TCON; spin_unlock(&tcon->tc_lock); - rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, - tcon, tcon->ses->local_nls); + rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, tcon); if (rc) { spin_lock(&tcon->tc_lock); if (tcon->status == TID_IN_TCON) diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c index f65a8a90ba279a..b7143335d70a5a 100644 --- a/fs/smb/client/dfs.c +++ b/fs/smb/client/dfs.c @@ -371,7 +371,7 @@ static int tree_connect_dfs_target(const unsigned int xid, dfs_cache_noreq_update_tgthint(server->leaf_fullpath + 1, tit); scnprintf(tree, MAX_TREE_SIZE, "\\%s", share); rc = ops->tree_connect(xid, tcon->ses, tree, - tcon, tcon->ses->local_nls); + tcon); if (islink && !rc && cifs_sb) rc = cifs_update_super_prepath(cifs_sb, prefix); break; @@ -426,7 +426,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname); cifs_server_unlock(server); rc = ops->tree_connect(xid, tcon->ses, tree, - tcon, tcon->ses->local_nls); + tcon); goto out; } @@ -438,7 +438,7 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon) if (!server->leaf_fullpath || dfs_cache_noreq_find(server->leaf_fullpath + 1, &ref, &tl)) { rc = ops->tree_connect(xid, tcon->ses, tcon->tree_name, - tcon, tcon->ses->local_nls); + tcon); goto out; } diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index adaa3fd1ef1600..12a9e35ad423b7 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -33,7 +33,7 @@ int small_smb_init_no_tc(const int smb_command, const int wct, int CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server); int CIFSTCon(const unsigned int xid, struct cifs_ses *ses, const char *tree, - struct cifs_tcon *tcon, const struct nls_table *nls_codepage); + struct cifs_tcon *tcon); int CIFSSMBTDis(const unsigned int xid, struct cifs_tcon *tcon); int CIFSSMBEcho(struct TCP_Server_Info *server); int CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses); diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index 41ca0853831686..f10feec98b382e 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2116,9 +2116,8 @@ static inline void init_copy_chunk_defaults(struct cifs_tcon *tcon) tcon->max_bytes_copy = 16777216; } -int -SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, - struct cifs_tcon *tcon, const struct nls_table *cp) +int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, + struct cifs_tcon *tcon) { struct smb_rqst rqst; struct smb2_tree_connect_req *req; @@ -2142,7 +2141,8 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, if (unc_path == NULL) return -ENOMEM; - unc_path_len = cifs_strtoUTF16(unc_path, tree, strlen(tree), cp); + unc_path_len = cifs_strtoUTF16(unc_path, tree, strlen(tree), + ses->local_nls); if (unc_path_len <= 0) { rc = -EINVAL; goto free_unc_path; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index aa1340fec27b09..c79e5cb2ce26ad 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -134,7 +134,7 @@ int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server); int SMB2_logoff(const unsigned int xid, struct cifs_ses *ses); int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, - struct cifs_tcon *tcon, const struct nls_table *cp); + struct cifs_tcon *tcon); int SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon); int SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path, __u8 *oplock, struct cifs_open_info_data *buf, From 8552a75e219ccedc377bc14cbc4225644de04ae7 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:17:37 -0300 Subject: [PATCH 1798/2286] smb: client: drop redundant nls_table arg from CIFSPOSIXDelFile() The nls codepage passed to CIFSPOSIXDelFile() is always tcon->ses->local_nls, so derive it from @tcon and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 8 +++----- fs/smb/client/inode.c | 3 +-- fs/smb/client/smb1proto.h | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 910f454fabfa1d..67273e7adbebf8 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -836,10 +836,8 @@ CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses) return rc; } -int -CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, __u16 type, - const struct nls_table *nls_codepage, int remap) +int CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, __u16 type, int remap) { TRANSACTION2_SPI_REQ *pSMB = NULL; TRANSACTION2_SPI_RSP *pSMBr = NULL; @@ -861,7 +859,7 @@ CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { name_len = cifsConvertToUTF16((__le16 *) pSMB->FileName, fileName, - PATH_MAX, nls_codepage, remap); + PATH_MAX, tcon->ses->local_nls, remap); name_len++; /* trailing null */ name_len *= 2; } else { diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 00dbf5342c1086..b104135888fa58 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2019,8 +2019,7 @@ static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyren if (cap_unix(tcon->ses) && (CIFS_UNIX_POSIX_PATH_OPS_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability))) { rc = CIFSPOSIXDelFile(xid, tcon, full_path, - SMB_POSIX_UNLINK_FILE_TARGET, cifs_sb->local_nls, - cifs_remap(cifs_sb)); + SMB_POSIX_UNLINK_FILE_TARGET, cifs_remap(cifs_sb)); cifs_dbg(FYI, "posix del rc %d\n", rc); if ((rc == 0) || (rc == -ENOENT)) goto psx_del_no_retry; diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index 12a9e35ad423b7..d953dc82144a58 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -38,8 +38,7 @@ int CIFSSMBTDis(const unsigned int xid, struct cifs_tcon *tcon); int CIFSSMBEcho(struct TCP_Server_Info *server); int CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses); int CIFSPOSIXDelFile(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, __u16 type, - const struct nls_table *nls_codepage, int remap); + const char *fileName, __u16 type, int remap); int CIFSSMBDelFile(const unsigned int xid, struct cifs_tcon *tcon, const char *name, struct cifs_sb_info *cifs_sb, struct dentry *dentry); From b2c49578d3423c81eb1e6ac4c506d57ea4c3a87c Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:18:18 -0300 Subject: [PATCH 1799/2286] smb: client: drop redundant nls_table arg from CIFSPOSIXCreate() The nls codepage passed to CIFSPOSIXCreate() is always tcon->ses->local_nls, so derive it from @tcon and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 12 +++++------- fs/smb/client/file.c | 3 +-- fs/smb/client/inode.c | 2 +- fs/smb/client/smb1proto.h | 3 +-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 67273e7adbebf8..5ee659287f572b 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1047,12 +1047,10 @@ CIFSSMBMkDir(const unsigned int xid, struct inode *inode, umode_t mode, return rc; } -int -CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon, - __u32 posix_flags, __u64 mode, __u16 *netfid, - FILE_UNIX_BASIC_INFO *pRetData, __u32 *pOplock, - const char *name, const struct nls_table *nls_codepage, - int remap) +int CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon, + __u32 posix_flags, __u64 mode, __u16 *netfid, + FILE_UNIX_BASIC_INFO *pRetData, __u32 *pOplock, + const char *name, int remap) { TRANSACTION2_SPI_REQ *pSMB = NULL; TRANSACTION2_SPI_RSP *pSMBr = NULL; @@ -1075,7 +1073,7 @@ CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon, if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { name_len = cifsConvertToUTF16((__le16 *) pSMB->FileName, name, - PATH_MAX, nls_codepage, remap); + PATH_MAX, tcon->ses->local_nls, remap); name_len++; /* trailing null */ name_len *= 2; } else { diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c index e08ed5072c298d..0767eb5f4687ae 100644 --- a/fs/smb/client/file.c +++ b/fs/smb/client/file.c @@ -516,8 +516,7 @@ int cifs_posix_open(const char *full_path, struct inode **pinode, posix_flags = cifs_posix_convert_flags(f_flags); rc = CIFSPOSIXCreate(xid, tcon, posix_flags, mode, pnetfid, presp_data, - poplock, full_path, cifs_sb->local_nls, - cifs_remap(cifs_sb)); + poplock, full_path, cifs_remap(cifs_sb)); cifs_put_tlink(tlink); if (rc) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index b104135888fa58..79e7f252641cd1 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2240,7 +2240,7 @@ cifs_posix_mkdir(struct inode *inode, struct dentry *dentry, umode_t mode, mode &= ~current_umask(); rc = CIFSPOSIXCreate(xid, tcon, SMB_O_DIRECTORY | SMB_O_CREAT, mode, NULL /* netfid */, info, &oplock, full_path, - cifs_sb->local_nls, cifs_remap(cifs_sb)); + cifs_remap(cifs_sb)); if (rc == -EOPNOTSUPP) goto posix_mkdir_out; else if (rc) { diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index d953dc82144a58..cd0966164db9db 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -50,8 +50,7 @@ int CIFSSMBMkDir(const unsigned int xid, struct inode *inode, umode_t mode, int CIFSPOSIXCreate(const unsigned int xid, struct cifs_tcon *tcon, __u32 posix_flags, __u64 mode, __u16 *netfid, FILE_UNIX_BASIC_INFO *pRetData, __u32 *pOplock, - const char *name, const struct nls_table *nls_codepage, - int remap); + const char *name, int remap); int SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const int openDisposition, const int access_flags, const int create_options, From 34c6fdb03b0560c5942945086201bd4caaaa46f8 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:19:14 -0300 Subject: [PATCH 1800/2286] smb: client: drop redundant nls_table arg from SMBLegacyOpen() The nls codepage passed to SMBLegacyOpen() is always tcon->ses->local_nls, so derive it from @tcon and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 14 +++++++------- fs/smb/client/smb1ops.c | 1 - fs/smb/client/smb1proto.h | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index 5ee659287f572b..dee7c0958284db 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -1229,12 +1229,11 @@ access_flags_to_smbopen_mode(const int access_flags) return SMBOPEN_READ; } -int -SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, - const char *fileName, const int openDisposition, - const int access_flags, const int create_options, __u16 *netfid, - int *pOplock, FILE_ALL_INFO *pfile_info, - const struct nls_table *nls_codepage, int remap) +int SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, + const char *fileName, const int openDisposition, + const int access_flags, const int create_options, + __u16 *netfid, int *pOplock, FILE_ALL_INFO *pfile_info, + int remap) { int rc; OPENX_REQ *pSMB = NULL; @@ -1257,7 +1256,8 @@ SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, count = 1; /* account for one byte pad to word boundary */ name_len = cifsConvertToUTF16((__le16 *) (pSMB->fileName + 1), - fileName, PATH_MAX, nls_codepage, remap); + fileName, PATH_MAX, + tcon->ses->local_nls, remap); name_len++; /* trailing null */ name_len *= 2; } else { diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index 313ef84ec22f60..c0d0eb0cdd2c5e 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -890,7 +890,6 @@ static int cifs_open_file(const unsigned int xid, struct cifs_open_parms *oparms oparms->desired_access, oparms->create_options, &oparms->fid->netfid, oplock, &fi, - oparms->cifs_sb->local_nls, cifs_remap(oparms->cifs_sb)); else rc = CIFS_open(xid, oparms, oplock, &fi); diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index cd0966164db9db..3dfabff9533e71 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -55,7 +55,7 @@ int SMBLegacyOpen(const unsigned int xid, struct cifs_tcon *tcon, const char *fileName, const int openDisposition, const int access_flags, const int create_options, __u16 *netfid, int *pOplock, FILE_ALL_INFO *pfile_info, - const struct nls_table *nls_codepage, int remap); + int remap); int CIFS_open(const unsigned int xid, struct cifs_open_parms *oparms, int *oplock, FILE_ALL_INFO *buf); int cifs_async_readv(struct cifs_io_subrequest *rdata); From b012ffcd45b0c5bb9eeb57ec1e7e2a71d3f36e06 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 00:20:13 -0300 Subject: [PATCH 1801/2286] smb: client: drop redundant nls_table arg from CIFSSMBRenameOpenFile() The nls codepage passed to CIFSSMBRenameOpenFile() is always pTcon->ses->local_nls, so derive it from @pTcon and drop the parameter. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/cifssmb.c | 10 +++++----- fs/smb/client/inode.c | 5 ++--- fs/smb/client/smb1proto.h | 3 +-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/fs/smb/client/cifssmb.c b/fs/smb/client/cifssmb.c index dee7c0958284db..50ca062a22bf41 100644 --- a/fs/smb/client/cifssmb.c +++ b/fs/smb/client/cifssmb.c @@ -2545,8 +2545,7 @@ int CIFSSMBRename(const unsigned int xid, struct cifs_tcon *tcon, } int CIFSSMBRenameOpenFile(const unsigned int xid, struct cifs_tcon *pTcon, - int netfid, const char *target_name, - const struct nls_table *nls_codepage, int remap) + int netfid, const char *target_name, int remap) { struct smb_com_transaction2_sfi_req *pSMB = NULL; struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; @@ -2595,12 +2594,13 @@ int CIFSSMBRenameOpenFile(const unsigned int xid, struct cifs_tcon *pTcon, sprintf(dummy_string, "cifs%x", pSMB->hdr.Mid); len_of_str = cifsConvertToUTF16((__le16 *)rename_info->target_name, - dummy_string, 24, nls_codepage, remap); + dummy_string, 24, + pTcon->ses->local_nls, remap); } else { len_of_str = cifsConvertToUTF16((__le16 *)rename_info->target_name, - target_name, PATH_MAX, nls_codepage, - remap); + target_name, PATH_MAX, + pTcon->ses->local_nls, remap); } rename_info->target_name_len = cpu_to_le32(2 * len_of_str); count = sizeof(struct set_file_rename) + (2 * len_of_str); diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 79e7f252641cd1..43a2d7982488aa 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1889,7 +1889,6 @@ cifs_rename_pending_delete(const char *full_path, struct dentry *dentry, /* rename the file */ rc = CIFSSMBRenameOpenFile(xid, tcon, fid.netfid, NULL, - cifs_sb->local_nls, cifs_remap(cifs_sb)); if (rc != 0) { rc = -EBUSY; @@ -1931,7 +1930,7 @@ cifs_rename_pending_delete(const char *full_path, struct dentry *dentry, */ undo_rename: CIFSSMBRenameOpenFile(xid, tcon, fid.netfid, dentry->d_name.name, - cifs_sb->local_nls, cifs_remap(cifs_sb)); + cifs_remap(cifs_sb)); undo_setattr: if (dosattr != origattr) { info_buf->Attributes = cpu_to_le32(origattr); @@ -2522,7 +2521,7 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry, if (rc == 0) { rc = CIFSSMBRenameOpenFile(xid, tcon, fid.netfid, (const char *) to_dentry->d_name.name, - cifs_sb->local_nls, cifs_remap(cifs_sb)); + cifs_remap(cifs_sb)); CIFSSMBClose(xid, tcon, fid.netfid); } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ diff --git a/fs/smb/client/smb1proto.h b/fs/smb/client/smb1proto.h index 3dfabff9533e71..7175b458724c59 100644 --- a/fs/smb/client/smb1proto.h +++ b/fs/smb/client/smb1proto.h @@ -88,8 +88,7 @@ int CIFSSMBRename(const unsigned int xid, struct cifs_tcon *tcon, struct dentry *source_dentry, const char *from_name, const char *to_name, struct cifs_sb_info *cifs_sb); int CIFSSMBRenameOpenFile(const unsigned int xid, struct cifs_tcon *pTcon, - int netfid, const char *target_name, - const struct nls_table *nls_codepage, int remap); + int netfid, const char *target_name, int remap); int CIFSUnixCreateSymLink(const unsigned int xid, struct cifs_tcon *tcon, const char *fromName, const char *toName, const struct nls_table *nls_codepage, int remap); From f13208f26c052d06a10c53eda4c78c930f2b9c47 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 01:52:46 -0300 Subject: [PATCH 1802/2286] smb: client: allocate struct sess_data on the stack CIFS_SessSetup() kzalloc()s struct sess_data only to run a synchronous state machine and free it before returning; the object never escapes the function. At 96 bytes it fits comfortably on the stack even under CONFIG_FRAME_WARN=1024, so drop the allocation and its failure path. memzero_explicit() preserves the kfree_sensitive() scrubbing. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb1session.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/smb1session.c b/fs/smb/client/smb1session.c index c45d3254dfac7a..5f469b4d70f747 100644 --- a/fs/smb/client/smb1session.c +++ b/fs/smb/client/smb1session.c @@ -949,34 +949,31 @@ static int select_sec(struct sess_data *sess_data) int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server) { + struct sess_data sess_data; int rc = 0; - struct sess_data *sess_data; if (ses == NULL) { WARN(1, "%s: ses == NULL!", __func__); return -EINVAL; } - sess_data = kzalloc_obj(struct sess_data); - if (!sess_data) - return -ENOMEM; + sess_data = (struct sess_data) { + .xid = xid, + .ses = ses, + .server = server, + .buf0_type = CIFS_NO_BUFFER, + }; - sess_data->xid = xid; - sess_data->ses = ses; - sess_data->server = server; - sess_data->buf0_type = CIFS_NO_BUFFER; - - rc = select_sec(sess_data); + rc = select_sec(&sess_data); if (rc) goto out; - while (sess_data->func) - sess_data->func(sess_data); + while (sess_data.func) + sess_data.func(&sess_data); - /* Store result before we free sess_data */ - rc = sess_data->result; + rc = sess_data.result; out: - kfree_sensitive(sess_data); + memzero_explicit(&sess_data, sizeof(sess_data)); return rc; } From cafbf020e437365beaeacc682fecd50450dff669 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 12 Oct 2026 01:53:14 -0300 Subject: [PATCH 1803/2286] smb: client: allocate struct SMB2_sess_data on the stack SMB2_sess_setup() kzalloc()s struct SMB2_sess_data only to run a synchronous state machine and free it before returning; the object never escapes the function. At 88 bytes it fits comfortably on the stack even under CONFIG_FRAME_WARN=1024, so drop the allocation and its failure path. memzero_explicit() preserves the kfree_sensitive() scrubbing. Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM --- fs/smb/client/smb2pdu.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c index f10feec98b382e..b140ec0a71a8f4 100644 --- a/fs/smb/client/smb2pdu.c +++ b/fs/smb/client/smb2pdu.c @@ -2004,8 +2004,8 @@ SMB2_select_sec(struct SMB2_sess_data *sess_data) int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, struct TCP_Server_Info *server) { + struct SMB2_sess_data sess_data; int rc = 0; - struct SMB2_sess_data *sess_data; cifs_dbg(FYI, "Session Setup\n"); @@ -2014,17 +2014,15 @@ int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, return smb_EIO(smb_eio_trace_null_pointers); } - sess_data = kzalloc_obj(struct SMB2_sess_data); - if (!sess_data) - return -ENOMEM; - - sess_data->xid = xid; - sess_data->ses = ses; - sess_data->server = server; - sess_data->buf0_type = CIFS_NO_BUFFER; - sess_data->previous_session = ses->Suid; + sess_data = (struct SMB2_sess_data) { + .xid = xid, + .ses = ses, + .server = server, + .buf0_type = CIFS_NO_BUFFER, + .previous_session = ses->Suid, + }; - rc = SMB2_select_sec(sess_data); + rc = SMB2_select_sec(&sess_data); if (rc) goto out; @@ -2034,14 +2032,14 @@ int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, memcpy(ses->preauth_sha_hash, server->preauth_sha_hash, SMB2_PREAUTH_HASH_SIZE); - while (sess_data->func) - sess_data->func(sess_data); + while (sess_data.func) + sess_data.func(&sess_data); if ((ses->session_flags & SMB2_SESSION_FLAG_IS_GUEST) && (ses->sign)) cifs_server_dbg(VFS, "signing requested but authenticated as guest\n"); - rc = sess_data->result; + rc = sess_data.result; out: - kfree_sensitive(sess_data); + memzero_explicit(&sess_data, sizeof(sess_data)); return rc; } From 171be684bfa3020f81bf0b85bc7ac42024575fbf Mon Sep 17 00:00:00 2001 From: Ard Biesheuvel Date: Tue, 13 Oct 2026 09:30:08 +0200 Subject: [PATCH 1804/2286] arm64/mm: Treat faults taken by EFI runtime firmware as kernel faults When EFI runtime services are invoked from a preemptible kthread, the kthread runs with efi_mm installed via kthread_use_mm() and preemption enabled, so neither faulthandler_disabled() nor !mm applies in do_page_fault(). A permission fault taken by the firmware on one of its own (TTBR0) mappings then hits the 'access to user memory outside uaccess routines' check and oopses, without efi_runtime_fixup_exception() ever being consulted. efi_mm has no VMAs, so there is nothing for the mm to resolve. Send such faults to no_context directly, so that __do_kernel_fault() can recover from them as it does when the call is made with preemption disabled. Fixes: a5baf582f4c0 ("arm64/efi: Call EFI runtime services without disabling preemption") Cc: Signed-off-by: Ard Biesheuvel Signed-off-by: Catalin Marinas --- arch/arm64/mm/fault.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/mm/fault.c b/arch/arm64/mm/fault.c index 75c3e463df2ef2..357be4c9106df1 100644 --- a/arch/arm64/mm/fault.c +++ b/arch/arm64/mm/fault.c @@ -620,9 +620,10 @@ static int __kprobes do_page_fault(unsigned long far, unsigned long esr, /* * If we're in an interrupt or have no user context, we must not take - * the fault. + * the fault. EFI runtime services may execute with preemption enabled + * and mm == &efi_mm, but faults need to be treated as kernel faults. */ - if (faulthandler_disabled() || !mm) + if (faulthandler_disabled() || !mm || mm_is_efi(mm)) goto no_context; if (user_mode(regs)) From 1d09c9ee13be740ec0bbf976f14b6f942396cabd Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Tue, 13 Oct 2026 14:58:06 +0900 Subject: [PATCH 1805/2286] rust: num: add cv! macro to create values from constant expressions Currently, using NonZero/Bounded constants is quite verbose. It's unfortunate because it disincentivizes using it in interface boundaries. Introduce a macro to make it nicer to use. The macro `cv!` (for constant value) takes a const integer expression and widens it to i128 (at build time only) before passing it as a const generic value to a new trait `FromConst`. The value is then converted and appears in the associated constant `FromConst::VALUE`. The trait is implemented by NonZero, Bounded, and Alignment and lets values of each be constructed from constants without a verbose turbofish syntax. For example, `const { NonZero::new(1).unwrap() }` can be written as `cv!(1)`. Add the `cv!` macro to the prelude so it doesn't need to be imported explicitly to use it. Suggested-by: Gary Guo Reviewed-by: Gary Guo Reviewed-by: Alice Ryhl Acked-by: Danilo Krummrich Signed-off-by: Eliot Courtney Acked-by: Alexandre Courbot Link: https://patch.msgid.link/20261013-cv-v5-1-113a538dfaef@nvidia.com [ Added a few intra-doc links. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/num.rs | 149 +++++++++++++++++++++++++++++++++++++ rust/kernel/num/bounded.rs | 16 ++++ rust/kernel/prelude.rs | 1 + rust/kernel/ptr.rs | 12 ++- 4 files changed, 177 insertions(+), 1 deletion(-) diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs index de589792a77a96..f81f0c1250d4d6 100644 --- a/rust/kernel/num.rs +++ b/rust/kernel/num.rs @@ -86,3 +86,152 @@ impl_integer!( i128: Signed, isize: Signed ); + +/// Creates a constant value ("cv") from an integer constant expression, with validity checked at +/// compile time. +/// +/// This works for any type that implements [`FromConst`], with the target type inferred from +/// the context, or named explicitly with `cv!(value => Type)`. It can also be used in const +/// contexts. +/// +/// # Pitfalls +/// +/// Only the target type is inferred from the context. The expression passed to [`cv!`] is typed by +/// itself. So, unsuffixed integer literal expressions default to [`i32`]. For example +/// `cv!(2 << 31)` does not evaluate to the same value as `cv!(2i64 << 31)`, since `2 << 31` is +/// treated as an [`i32`] before being widened to [`i128`] internally. Literals and arithmetic +/// expressions that overflow [`i32`] are a compile error, it's just left shifts that require care. +/// +/// # Examples +/// +/// ``` +/// use core::num::NonZero; +/// +/// use kernel::{ +/// num::Bounded, +/// ptr::Alignment, // +/// }; +/// +/// let v: NonZero = cv!(8); +/// assert_eq!(v.get(), 8); +/// +/// // Any integer constant expression works, not only literals. +/// let m: NonZero = cv!(usize::MAX); +/// assert_eq!(m.get(), usize::MAX); +/// +/// let b: Bounded = cv!(15); +/// assert_eq!(b.get(), 15); +/// +/// let a: Alignment = cv!(4096); +/// assert_eq!(a.as_usize(), 4096); +/// +/// // Checked narrowing of integer constants, including in `const` items. +/// const SMALL: u8 = cv!(200u32); +/// assert_eq!(SMALL, 200); +/// +/// const N: NonZero = cv!(5); +/// assert_eq!(N.get(), 5); +/// +/// // The target type can be given explicitly. +/// let e = cv!(200u32 => u8); +/// assert_eq!(e, 200); +/// +/// // With an explicit primitive target, the expression can use generic parameters. +/// const fn as_u64() -> u64 { +/// cv!(KEY => u64) +/// } +/// assert_eq!(as_u64::<0x40>(), 0x40); +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! cv { + (@cast $v:expr => $t:ty) => { + const { + #[allow(unused_comparisons, unused_assignments, clippy::as_underscore)] + { + let v = $v; + let r = v as $t; + // Pin `back` to `v`'s type so `as _` casts back to the source type. + let mut back = v; + back = r as _; + + ::core::assert!( + back == v && (v < 0) == (r < 0), + "value does not fit into the target type" + ); + + r + } + } + }; + + // Using `FromConst::VALUE` means const generic expressions can't be used, since it requires + // the `generic_const_exprs` feature. Provide a special path for each primitive type that allows + // const generic expressions. + ($v:expr => u8) => { $crate::cv!(@cast $v => u8) }; + ($v:expr => u16) => { $crate::cv!(@cast $v => u16) }; + ($v:expr => u32) => { $crate::cv!(@cast $v => u32) }; + ($v:expr => u64) => { $crate::cv!(@cast $v => u64) }; + ($v:expr => u128) => { $crate::cv!(@cast $v => u128) }; + ($v:expr => usize) => { $crate::cv!(@cast $v => usize) }; + ($v:expr => i8) => { $crate::cv!(@cast $v => i8) }; + ($v:expr => i16) => { $crate::cv!(@cast $v => i16) }; + ($v:expr => i32) => { $crate::cv!(@cast $v => i32) }; + ($v:expr => i64) => { $crate::cv!(@cast $v => i64) }; + ($v:expr => i128) => { $crate::cv!(@cast $v => i128) }; + ($v:expr => isize) => { $crate::cv!(@cast $v => isize) }; + ($v:expr => $t:ty) => { + <$t as $crate::num::FromConst<{ $crate::cv!(@cast $v => i128) }>>::VALUE + }; + ($v:expr) => { + <_ as $crate::num::FromConst<{ $crate::cv!(@cast $v => i128) }>>::VALUE + }; +} +#[doc(inline)] +pub use cv; + +/// Types that can be created from an integer constant expression validated at compile time. +/// +/// Implement this trait to make a type usable with [`cv!`]. Use the [`cv!`] macro, not this trait +/// directly, for creating values. +#[diagnostic::on_unimplemented(message = "`{Self}` cannot be converted from a constant")] +pub trait FromConst: Sized { + /// The value that corresponds to the constant `V`. + /// + /// Fails the build if `V` is not a valid value for [`Self`]. + const VALUE: Self; +} + +/// Implements [`FromConst`] for primitive integer types and their [`NonZero`](core::num::NonZero) +/// versions. +macro_rules! impl_from_const { + ($($type:ty)*) => { + $( + impl FromConst for $type { + const VALUE: Self = { + // CAST: The macro is only used for types up to 64 bits wide, so `MIN` and `MAX` + // widen to `i128` losslessly. + assert!( + V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128, + "constant cannot be represented by the target type" + ); + + // CAST: The assert above confirmed that `V` fits in `$type`. + V as $type + }; + } + + impl FromConst for core::num::NonZero<$type> { + const VALUE: Self = match core::num::NonZero::new(<$type as FromConst>::VALUE) { + Some(value) => value, + None => panic!("constant cannot be zero"), + }; + } + )* + }; +} + +impl_from_const!( + u8 u16 u32 u64 usize + i8 i16 i32 i64 isize +); diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index 2a2b0a4bca5e53..0b32f434392203 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -14,6 +14,7 @@ use core::{ use kernel::{ num::{ + FromConst, Integer, Unsigned, // }, @@ -272,6 +273,21 @@ macro_rules! impl_const_new { unsafe { Self::__new(VALUE) } } } + + impl FromConst for Bounded<$type, N> { + const VALUE: Self = { + let value = <$type as FromConst>::VALUE; + // Statically assert that `value` fits within the set number of bits. + assert!( + fits_within!(value, $type, N), + "constant cannot be represented within the given number of bits" + ); + + // SAFETY: The assert above confirmed that `value` can be represented within `N` + // bits. + unsafe { Self::__new(value) } + }; + } )* }; } diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index bcaa232205becd..edaf1e4c174bb4 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -106,6 +106,7 @@ pub use super::{ Result, // }, init::InPlaceInit, + num::cv, pr_alert, pr_alert_ratelimited, pr_crit, diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs index 82acb531b17bd5..2c12e4fd45726e 100644 --- a/rust/kernel/ptr.rs +++ b/rust/kernel/ptr.rs @@ -11,7 +11,10 @@ use core::mem::{ }; use core::num::NonZero; -use crate::const_assert; +use crate::{ + const_assert, + num::FromConst, // +}; /// Type representing an alignment, which is always a power of two. /// @@ -166,6 +169,13 @@ impl Alignment { } } +impl FromConst for Alignment { + const VALUE: Self = match Alignment::new_checked(>::VALUE) { + Some(alignment) => alignment, + None => panic!("constant is not a power of two"), + }; +} + /// Trait for items that can be aligned against an [`Alignment`]. pub trait Alignable: Sized { /// Aligns `self` down to `alignment`. From 8d26ced7b496f4cb9c5eafbad7ee21adf0fb6673 Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:50 +0900 Subject: [PATCH 1806/2286] rust: bitmap: use function-level cfg on kunit test Since commit c652dc44192d ("rust: kunit: allow `cfg` on `test`s"), we no longer need this workaround. Reviewed-by: Alice Ryhl Reviewed-by: Yury Norov Reviewed-by: Burak Emir Reviewed-by: Gary Guo Signed-off-by: Eliot Courtney Link: https://patch.msgid.link/20260930-chid-v9-1-0d6cca376cff@nvidia.com Signed-off-by: Miguel Ojeda --- rust/kernel/bitmap.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs index b27e0ec80d643d..a43bfe0ec3dc26 100644 --- a/rust/kernel/bitmap.rs +++ b/rust/kernel/bitmap.rs @@ -572,24 +572,21 @@ mod tests { } #[test] + #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] fn owned_bitmap_out_of_bounds() -> Result<(), AllocError> { - // TODO: Kunit #[test]s do not support `cfg` yet, - // so we add it here in the body. - #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] - { - let mut b = BitmapVec::new(128, GFP_KERNEL)?; - b.set_bit(2048); - b.set_bit_atomic(2048); - b.clear_bit(2048); - b.clear_bit_atomic(2048); - assert_eq!(None, b.next_bit(2048)); - assert_eq!(None, b.next_zero_bit(2048)); - assert_eq!(None, b.last_bit()); - } + let mut b = BitmapVec::new(128, GFP_KERNEL)?; + + b.set_bit(2048); + b.set_bit_atomic(2048); + b.clear_bit(2048); + b.clear_bit_atomic(2048); + assert_eq!(None, b.next_bit(2048)); + assert_eq!(None, b.next_zero_bit(2048)); + assert_eq!(None, b.last_bit()); Ok(()) } - // TODO: uncomment once kunit supports [should_panic] and `cfg`. + // TODO: uncomment once kunit supports `#[should_panic]`. // #[cfg(CONFIG_RUST_BITMAP_HARDENED)] // #[test] // #[should_panic] From 931790a91378f6513ba82730eb5607479c3184a0 Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:51 +0900 Subject: [PATCH 1807/2286] rust: bitmap: restrict bitmap length to at most i32::MAX It is currently possible to construct a non-`BitmapVec` backed `Bitmap` using `Bitmap::from_raw` that is larger than `i32::MAX`, and it is not part of the unsafe requirements. Restricting all bitmaps (even non-`BitmapVec` backed ones) to a maximum size of `i32::MAX` simplifies a few things and matches `BitmapVec::MAX_LEN`. For example, `copy_and_extend` truncates `len` to u32, which is wrong for > u32::MAX size. `__bitmap_set` and `__bitmap_clear` need i32 for `size` and u32 for `start` - so they can't be run on a `Bitmap` with a > i32::MAX size. Rather than adding runtime checks to account for the case of a non `BitmapVec` backed `Bitmap`, just include that in the requirements for `Bitmap`. Add that requirement to the unsafe requirements on `Bitmap::from_raw` and `Bitmap::from_raw_mut`, and to the invariants on `Bitmap`. This also fixes u32 casts truncating in `copy_and_extend`, which could otherwise lead to OOB writes. Fixes: 11eca92a2cae ("rust: add bitmap API.") Link: https://lore.kernel.org/DKG0U8RLO7LZ.2I1AIH0S38PAP@nvidia.com Reviewed-by: Yury Norov Reviewed-by: Burak Emir Signed-off-by: Eliot Courtney Link: https://patch.msgid.link/20260930-chid-v9-2-0d6cca376cff@nvidia.com Cc: stable@vger.kernel.org [ Added a few intra-doc links. Added backticks to C types. Capitalize tagged comments. - Miguel ] Signed-off-by: Miguel Ojeda --- rust/kernel/bitmap.rs | 70 ++++++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs index a43bfe0ec3dc26..9098ee062d80a6 100644 --- a/rust/kernel/bitmap.rs +++ b/rust/kernel/bitmap.rs @@ -17,24 +17,59 @@ use core::ptr::NonNull; /// # Invariants /// /// Must reference a `[c_ulong]` long enough to fit `data.len()` bits. +/// Must not be longer than [`i32::MAX`] bits, so offsets and lengths used with +/// [`Bitmap`] functions fit in the `int` and `unsigned int` arguments of the C bitmap API. +/// This also matches [`BitmapVec::MAX_LEN`]. #[cfg_attr(CONFIG_64BIT, repr(align(8)))] #[cfg_attr(not(CONFIG_64BIT), repr(align(4)))] pub struct Bitmap { data: [()], } +macro_rules! bitmap_assert { + ($cond:expr, $($arg:tt)+) => { + #[cfg(CONFIG_RUST_BITMAP_HARDENED)] + assert!($cond, $($arg)*); + } +} + +macro_rules! bitmap_assert_return { + ($cond:expr, $($arg:tt)+) => { + #[cfg(CONFIG_RUST_BITMAP_HARDENED)] + assert!($cond, $($arg)*); + + #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] + if !($cond) { + pr_err!($($arg)*); + return + } + } +} + impl Bitmap { /// Borrows a C bitmap. /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `nbits` exceeds [`i32::MAX`]. + /// /// # Safety /// /// * `ptr` holds a non-null address of an initialized array of `unsigned long` /// that is large enough to hold `nbits` bits. + /// * `nbits` must not exceed [`i32::MAX`]. /// * the array must not be freed for the lifetime of this [`Bitmap`] /// * concurrent access only happens through atomic operations pub unsafe fn from_raw<'a>(ptr: *const usize, nbits: usize) -> &'a Bitmap { + bitmap_assert!( + nbits <= i32::MAX as usize, + "`nbits` must be <= {}, was {}", + i32::MAX, + nbits + ); let data: *const [()] = core::ptr::slice_from_raw_parts(ptr.cast(), nbits); // INVARIANT: `data` references an initialized array that can hold `nbits` bits. + // INVARIANT: The caller guarantees that `nbits` does not exceed `i32::MAX`. // SAFETY: // The caller guarantees that `data` (derived from `ptr` and `nbits`) // points to a valid, initialized, and appropriately sized memory region @@ -51,15 +86,27 @@ impl Bitmap { /// Borrows a C bitmap exclusively. /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `nbits` exceeds [`i32::MAX`]. + /// /// # Safety /// /// * `ptr` holds a non-null address of an initialized array of `unsigned long` /// that is large enough to hold `nbits` bits. + /// * `nbits` must not exceed [`i32::MAX`]. /// * the array must not be freed for the lifetime of this [`Bitmap`] /// * no concurrent access may happen. pub unsafe fn from_raw_mut<'a>(ptr: *mut usize, nbits: usize) -> &'a mut Bitmap { + bitmap_assert!( + nbits <= i32::MAX as usize, + "`nbits` must be <= {}, was {}", + i32::MAX, + nbits + ); let data: *mut [()] = core::ptr::slice_from_raw_parts_mut(ptr.cast(), nbits); // INVARIANT: `data` references an initialized array that can hold `nbits` bits. + // INVARIANT: The caller guarantees that `nbits` does not exceed `i32::MAX`. // SAFETY: // The caller guarantees that `data` (derived from `ptr` and `nbits`) // points to a valid, initialized, and appropriately sized memory region @@ -96,26 +143,6 @@ union BitmapRepr { ptr: NonNull, } -macro_rules! bitmap_assert { - ($cond:expr, $($arg:tt)+) => { - #[cfg(CONFIG_RUST_BITMAP_HARDENED)] - assert!($cond, $($arg)*); - } -} - -macro_rules! bitmap_assert_return { - ($cond:expr, $($arg:tt)+) => { - #[cfg(CONFIG_RUST_BITMAP_HARDENED)] - assert!($cond, $($arg)*); - - #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] - if !($cond) { - pr_err!($($arg)*); - return - } - } -} - /// Represents an owned bitmap. /// /// Wraps underlying C bitmap API. See [`Bitmap`] for available @@ -415,7 +442,8 @@ impl Bitmap { #[inline] pub fn copy_and_extend(&mut self, src: &Bitmap) { let len = core::cmp::min(src.len(), self.len()); - // SAFETY: access to `self` and `src` is within bounds. + // SAFETY: Access to `self` and `src` is within bounds. Both lengths fit in `u32` + // because a `Bitmap` is at most `i32::MAX` bits, so the casts are lossless. unsafe { bindings::bitmap_copy_and_extend( self.as_mut_ptr(), From bf461edb197bd7c0462c1d13bb1c3b51d6892fd3 Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:52 +0900 Subject: [PATCH 1808/2286] rust: sizes: add sub-1K size constants Add some more size constants, mirroring include/linux/sizes.h. This is useful for making `Alignment` implement `SizeConstants` in a following patch, because these are more common alignment values. Reviewed-by: Alexandre Courbot Signed-off-by: Eliot Courtney Link: https://patch.msgid.link/20260930-chid-v9-3-0d6cca376cff@nvidia.com Signed-off-by: Miguel Ojeda --- rust/kernel/sizes.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rust/kernel/sizes.rs b/rust/kernel/sizes.rs index 521b2b38bfe772..7d03361eae3d7f 100644 --- a/rust/kernel/sizes.rs +++ b/rust/kernel/sizes.rs @@ -35,6 +35,26 @@ macro_rules! define_sizes { ($($type:ty),* $(,)?) => { define_sizes!(@internal [$($type),*] + /// `0x0000_0001`. + SZ_1, + /// `0x0000_0002`. + SZ_2, + /// `0x0000_0004`. + SZ_4, + /// `0x0000_0008`. + SZ_8, + /// `0x0000_0010`. + SZ_16, + /// `0x0000_0020`. + SZ_32, + /// `0x0000_0040`. + SZ_64, + /// `0x0000_0080`. + SZ_128, + /// `0x0000_0100`. + SZ_256, + /// `0x0000_0200`. + SZ_512, /// `0x0000_0400`. SZ_1K, /// `0x0000_0800`. From d21cfabdd8aee0a837d31d945844e5c177154389 Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:53 +0900 Subject: [PATCH 1809/2286] rust: sizes: implement SizeConstants for Alignment Currently, constructing an alignment is quite verbose [1]: Alignment::new::<8>() It's unfortunate because it disincentivizes using it at interface boundaries. Implement `SizeConstants` for `Alignment` so we can write e.g. `Alignment::SZ_8` instead. Link: https://lore.kernel.org/an4xDp29VX8Am0uR@yury [1] Reviewed-by: Alexandre Courbot Signed-off-by: Eliot Courtney Link: https://patch.msgid.link/20260930-chid-v9-4-0d6cca376cff@nvidia.com Signed-off-by: Miguel Ojeda --- rust/kernel/sizes.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/rust/kernel/sizes.rs b/rust/kernel/sizes.rs index 7d03361eae3d7f..188e00c2b8b465 100644 --- a/rust/kernel/sizes.rs +++ b/rust/kernel/sizes.rs @@ -13,6 +13,11 @@ //! these constants as [`u64`] (or [`u32`]) rather than [`usize`], because //! device address spaces are sized independently of the CPU pointer width. //! +//! The trait is also implemented for [`Alignment`], providing each size as a +//! compile-time validated alignment. +//! +//! [`Alignment`]: crate::ptr::Alignment +//! //! # Examples //! //! ``` @@ -105,6 +110,7 @@ macro_rules! define_sizes { (@internal [$($type:ty),*] $($names_and_metas:tt)*) => { define_sizes!(@consts_and_trait $($names_and_metas)*); define_sizes!(@impls [$($type),*] $($names_and_metas)*); + define_sizes!(@impl_alignment $($names_and_metas)*); }; (@consts_and_trait $($(#[$meta:meta])* $name:ident,)*) => { @@ -119,13 +125,22 @@ macro_rules! define_sizes { /// choose the width that matches their hardware. All `SZ_*` values fit /// in a [`u32`], so all implementations are lossless. /// + /// Also implemented for [`Alignment`], providing each size as a + /// compile-time validated alignment. + /// + /// [`Alignment`]: crate::ptr::Alignment + /// /// # Examples /// /// ``` - /// use kernel::sizes::SizeConstants; + /// use kernel::{ + /// ptr::Alignment, + /// sizes::SizeConstants, // + /// }; /// /// let gpu_heap = 14 * u64::SZ_1M; /// let mmio_window = u32::SZ_16M; + /// let page_align = Alignment::SZ_4K; /// ``` pub trait SizeConstants { $( @@ -137,6 +152,16 @@ macro_rules! define_sizes { (@impls [] $($(#[$meta:meta])* $name:ident,)*) => {}; + (@impl_alignment $($(#[$meta:meta])* $name:ident,)*) => { + impl SizeConstants for crate::ptr::Alignment { + $( + $(#[$meta])* + // A non-power-of-two constant will fail the build here if used. + const $name: Self = crate::ptr::Alignment::new::<{ self::$name }>(); + )* + } + }; + (@impls [$first:ty $(, $rest:ty)*] $($(#[$meta:meta])* $name:ident,)*) => { impl SizeConstants for $first { $( From b666bd22be8f211aceeeb8fbc827ebdb7abe98c0 Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:55 +0900 Subject: [PATCH 1810/2286] rust: bitmap: add contiguous area operations Add bindings for area operations on bitmaps. Each one is made safe by adding some extra checks compared to the underlying C code (for example, checking bounds) and with additional checks to catch likely erroneous usage if `CONFIG_RUST_BITMAP_HARDENED` is on. Add tests demonstrating the edge cases. Reviewed-by: Burak Emir Signed-off-by: Eliot Courtney Reviewed-by: Yury Norov Link: https://patch.msgid.link/20260930-chid-v9-6-0d6cca376cff@nvidia.com Signed-off-by: Miguel Ojeda --- rust/kernel/bitmap.rs | 241 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 239 insertions(+), 2 deletions(-) diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs index 9098ee062d80a6..c676b0e44eb5dc 100644 --- a/rust/kernel/bitmap.rs +++ b/rust/kernel/bitmap.rs @@ -10,7 +10,11 @@ use crate::alloc::{AllocError, Flags}; use crate::bindings; #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] use crate::pr_err; -use core::ptr::NonNull; +use crate::ptr::Alignment; +use core::{ + num::NonZero, + ptr::NonNull, // +}; /// Represents a C bitmap. Wraps underlying C bitmap API. /// @@ -525,13 +529,159 @@ impl Bitmap { Some(index) } } + + /// Finds a contiguous area of `nbits` zero bits at or after `start`, where the area plus + /// `align_offset` is aligned to `align`. + /// + /// Returns the bit index of the start of the area, or [`None`] if no such area fitting in + /// the bitmap exists. + /// + /// The returned index plus `align_offset` is a multiple of `align`. + /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `start` is out of bounds. + #[inline] + pub fn next_zero_area_off( + &self, + start: usize, + nbits: NonZero, + align: Alignment, + align_offset: usize, + ) -> Option { + bitmap_assert!( + start < self.len(), + "`start` must be < {}, was {}", + self.len(), + start + ); + + let nr = u32::try_from(nbits.get()).ok()?; + let align_mask = align.as_usize() - 1; + + // The C alignment and end arithmetic must not overflow, or it can read out of bounds. + // Overflow is only possible on 32-bit. + #[cfg(not(CONFIG_64BIT))] + align_mask + .checked_add(self.len())? + .checked_add(nbits.get())?; + + // SAFETY: `bitmap_find_next_zero_area_off` is safe to use with an out of bounds `start` + // value and, given the overflow check above, never reads beyond `self.len()` bits. + let index = unsafe { + bindings::bitmap_find_next_zero_area_off( + self.as_ptr().cast_mut(), + self.len(), + start, + nr, + align_mask, + align_offset, + ) + }; + + (index < self.len()).then_some(index) + } + + /// Finds a contiguous area of `nbits` zero bits at or after `start`, aligned to `align`. + /// + /// Returns the bit index of the start of the area, or [`None`] if no such area fitting in + /// the bitmap exists. + /// + /// The returned index is a multiple of `align`. + /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `start` is out of bounds. + /// + /// # Examples + /// + /// ``` + /// use kernel::{ + /// alloc::{AllocError, flags::GFP_KERNEL}, + /// bitmap::BitmapVec, + /// ptr::Alignment, + /// sizes::SizeConstants, // + /// }; + /// + /// let mut b = BitmapVec::new(64, GFP_KERNEL)?; + /// + /// assert_eq!(Some(0), b.next_zero_area(0, cv!(8), Alignment::SZ_1)); + /// b.set(0, cv!(5)); + /// assert_eq!(Some(5), b.next_zero_area(0, cv!(8), Alignment::SZ_1)); + /// assert_eq!(Some(8), b.next_zero_area(0, cv!(8), Alignment::SZ_8)); + /// assert_eq!(None, b.next_zero_area(0, cv!(65), Alignment::SZ_1)); + /// # Ok::<(), AllocError>(()) + /// ``` + #[inline] + pub fn next_zero_area( + &self, + start: usize, + nbits: NonZero, + align: Alignment, + ) -> Option { + self.next_zero_area_off(start, nbits, align, 0) + } + + /// Sets a contiguous area of `nbits` bits starting at `start`. + /// + /// If CONFIG_RUST_BITMAP_HARDENED is not enabled and the area `start..start + nbits` is out of + /// bounds, does nothing. + /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and the area `start..start + nbits` is out + /// of bounds. + #[inline] + pub fn set(&mut self, start: usize, nbits: NonZero) { + bitmap_assert_return!( + start + .checked_add(nbits.get()) + .is_some_and(|end| end <= self.len()), + "Area `start..start + nbits` ({}..{}) must be within bounds {}", + start, + start.saturating_add(nbits.get()), + self.len() + ); + // SAFETY: The area `start..start + nbits` is within bounds and a `Bitmap` is at most + // `i32::MAX` bits, so the casts are lossless. + unsafe { bindings::__bitmap_set(self.as_mut_ptr(), start as u32, nbits.get() as i32) }; + } + + /// Clears a contiguous area of `nbits` bits starting at `start`. + /// + /// If CONFIG_RUST_BITMAP_HARDENED is not enabled and the area `start..start + nbits` is out of + /// bounds, does nothing. + /// + /// # Panics + /// + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and the area `start..start + nbits` is out + /// of bounds. + #[inline] + pub fn clear(&mut self, start: usize, nbits: NonZero) { + bitmap_assert_return!( + start + .checked_add(nbits.get()) + .is_some_and(|end| end <= self.len()), + "Area `start..start + nbits` ({}..{}) must be within bounds {}", + start, + start.saturating_add(nbits.get()), + self.len() + ); + // SAFETY: The area `start..start + nbits` is within bounds and a `Bitmap` is at most + // `i32::MAX` bits, so the casts are lossless. + unsafe { bindings::__bitmap_clear(self.as_mut_ptr(), start as u32, nbits.get() as i32) }; + } } #[cfg(CONFIG_RUST_BITMAP_KUNIT_TEST)] #[macros::kunit_tests(rust_kernel_bitmap)] mod tests { use super::*; - use kernel::alloc::flags::GFP_KERNEL; + use kernel::{ + alloc::flags::GFP_KERNEL, + num::cv, + sizes::SizeConstants, // + }; #[test] fn bitmap_borrow() { @@ -642,4 +792,91 @@ mod tests { assert_eq!(Some(17), long_bitmap.last_bit()); Ok(()) } + + #[test] + fn bitmap_area_set_clear_find() -> Result<(), AllocError> { + let mut b = BitmapVec::new(128, GFP_KERNEL)?; + + assert_eq!(Some(0), b.next_zero_area(0, cv!(5), Alignment::SZ_1)); + b.set(0, cv!(5)); // Now contains {[0, 5)}. + + assert_eq!(Some(0), b.next_bit(0)); + assert_eq!(Some(4), b.next_bit(4)); + assert_eq!(Some(5), b.next_zero_bit(0)); + assert_eq!(Some(5), b.next_zero_area(0, cv!(5), Alignment::SZ_1)); + assert_eq!(Some(8), b.next_zero_area(0, cv!(5), Alignment::SZ_8)); + + b.set(8, cv!(8)); // Now contains {[0, 5), [8, 16)}. + assert_eq!(Some(16), b.next_zero_area(0, cv!(4), Alignment::SZ_16)); + assert_eq!(Some(16), b.next_zero_area(0, cv!(4), Alignment::SZ_1)); + + b.clear(0, cv!(5)); // Now contains {[8, 16)}. + assert_eq!(Some(0), b.next_zero_area(0, cv!(5), Alignment::SZ_1)); + assert_eq!(Some(8), b.next_bit(0)); + assert_eq!(Some(15), b.last_bit()); + + b.set(60, cv!(10)); // Now contains {[8, 16), [60, 70)}. + assert_eq!(Some(60), b.next_bit(16)); + assert_eq!(Some(69), b.last_bit()); + assert_eq!(Some(16), b.next_zero_area(9, cv!(40), Alignment::SZ_1)); + assert_eq!(Some(70), b.next_zero_area(0, cv!(45), Alignment::SZ_1)); + + b.clear(62, cv!(6)); // Now contains {[8, 16), [60, 62), [68, 70)}. + assert_eq!(Some(62), b.next_zero_area(60, cv!(6), Alignment::SZ_1)); + assert_eq!(Some(61), b.next_bit(61)); + assert_eq!(Some(69), b.last_bit()); + Ok(()) + } + + #[test] + fn bitmap_area_exhaustion() -> Result<(), AllocError> { + let mut b = BitmapVec::new(64, GFP_KERNEL)?; + + assert_eq!(None, b.next_zero_area(0, cv!(65), Alignment::SZ_1)); + assert_eq!(None, b.next_zero_area(0, cv!(usize::MAX), Alignment::SZ_1)); + assert_eq!(None, b.next_zero_area(1, cv!(usize::MAX), Alignment::SZ_1)); + + b.set_bit(0); // Now contains {[0, 1)}. + assert_eq!(None, b.next_zero_area(0, cv!(usize::MAX), Alignment::SZ_1)); + + b.set(0, cv!(61)); // Now contains {[0, 61)}. + assert_eq!(None, b.next_zero_area(0, cv!(4), Alignment::SZ_1)); + assert_eq!(Some(61), b.next_zero_area(0, cv!(3), Alignment::SZ_1)); + assert_eq!(None, b.next_zero_area(0, cv!(1), Alignment::SZ_64)); + Ok(()) + } + + #[test] + fn bitmap_area_off() -> Result<(), AllocError> { + let mut b = BitmapVec::new(64, GFP_KERNEL)?; + + b.set(0, cv!(5)); // Now contains {[0, 5)}. + + // The area plus align_offset starts at a multiple of the alignment. + assert_eq!(Some(7), b.next_zero_area_off(0, cv!(8), Alignment::SZ_8, 1)); + assert_eq!(Some(5), b.next_zero_area_off(0, cv!(8), Alignment::SZ_8, 3)); + + // A zero offset behaves like next_zero_area(). + assert_eq!( + b.next_zero_area(0, cv!(8), Alignment::SZ_8), + b.next_zero_area_off(0, cv!(8), Alignment::SZ_8, 0) + ); + Ok(()) + } + + #[test] + #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] + fn owned_bitmap_area_out_of_bounds() -> Result<(), AllocError> { + let mut b = BitmapVec::new(64, GFP_KERNEL)?; + + // Should be ignored since out of bounds. + b.set(64, cv!(4)); + b.set(62, cv!(8)); + b.set(usize::MAX, cv!(1)); + b.clear(usize::MAX, cv!(1)); + b.clear(2048, cv!(8)); + assert_eq!(None, b.next_bit(0)); + assert_eq!(None, b.next_zero_area(64, cv!(1), Alignment::SZ_1)); + Ok(()) + } } From 3132a951a7e4ec2e18bc314877137deee96ab83e Mon Sep 17 00:00:00 2001 From: Eliot Courtney Date: Wed, 30 Sep 2026 11:42:56 +0900 Subject: [PATCH 1811/2286] rust: id_pool: add contiguous ID reservation Add `IdPool::reserve_ids` which allocates a contiguous range with the given offset, count, and alignment. Reviewed-by: Burak Emir Reviewed-by: Alexandre Courbot Signed-off-by: Eliot Courtney Reviewed-by: Yury Norov Link: https://patch.msgid.link/20260930-chid-v9-7-0d6cca376cff@nvidia.com Signed-off-by: Miguel Ojeda --- rust/kernel/id_pool.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rust/kernel/id_pool.rs b/rust/kernel/id_pool.rs index 384753fe0e44a3..06a4c71c4c6c9b 100644 --- a/rust/kernel/id_pool.rs +++ b/rust/kernel/id_pool.rs @@ -4,8 +4,14 @@ //! Rust API for an ID pool backed by a [`BitmapVec`]. +use core::{ + num::NonZero, + ops::Range, // +}; + use crate::alloc::{AllocError, Flags}; use crate::bitmap::BitmapVec; +use crate::ptr::Alignment; /// Represents a dynamic ID pool backed by a [`BitmapVec`]. /// @@ -240,6 +246,32 @@ impl IdPool { pub fn release_id(&mut self, id: usize) { self.map.clear_bit(id); } + + /// Reserves a contiguous area of `count` IDs at or after `offset`. + /// + /// The start of the returned area is a multiple of `align`. + /// + /// Returns the reserved range upon success, or [`None`] if no such area could be found. + #[inline] + #[must_use] + pub fn reserve_ids( + &mut self, + offset: usize, + count: NonZero, + align: Alignment, + ) -> Option> { + let start = self.map.next_zero_area(offset, count, align)?; + self.map.set(start, count); + Some(start..start + count.get()) + } + + /// Releases a contiguous area of IDs. + #[inline] + pub fn release_ids(&mut self, range: &Range) { + if let Some(nbits) = NonZero::new(range.len()) { + self.map.clear(range.start, nbits); + } + } } /// Represents an unused id in an [`IdPool`]. From 8496a1d234e8cf9cb15c4282cb3a11c6550bd242 Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Fri, 2 Oct 2026 09:19:26 +0200 Subject: [PATCH 1812/2286] clocksource/drivers/timer-mediatek: Fix random crash at boot MediaTek bootloaders generally enable APXGPT timers at boot. This means that a timer IRQ can sometimes happen between the IRQ line request and the configuration of the clock event handler. This leads to a hard crash with an instruction abort, branch to pc 0x0 in mtk_gpt_interrupt handler as clkevt is null. To avoid this issue request the IRQ with IRQF_NO_AUTOEN and enable it explicitly after the clock event is configured. Issue was reported and fix tested on the Lenovo Tab E7 (TB-7104F, mt8167). Fixes: a0858f937960 ("clocksource/drivers/timer-mediatek: Convert the driver to timer-of") Signed-off-by: Luca Leonardo Scorcia Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20261002072115.169250-2-l.scorcia@gmail.com --- drivers/clocksource/timer-mediatek.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c index 7bcb4a3f26fb0e..e20075b09fab0b 100644 --- a/drivers/clocksource/timer-mediatek.c +++ b/drivers/clocksource/timer-mediatek.c @@ -277,7 +277,7 @@ static struct timer_of to = { }, .of_irq = { - .flags = IRQF_TIMER | IRQF_IRQPOLL, + .flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_NO_AUTOEN, }, }; @@ -298,6 +298,7 @@ static int __init mtk_syst_init(struct device_node *node) clockevents_config_and_register(&to.clkevt, timer_of_rate(&to), TIMER_SYNC_TICKS, 0xffffffff); + enable_irq(timer_of_irq(&to)); return 0; } @@ -334,6 +335,7 @@ static int __init mtk_gpt_init(struct device_node *node) TIMER_SYNC_TICKS, 0xffffffff); mtk_gpt_enable_irq(&to, TIMER_CLK_EVT); + enable_irq(timer_of_irq(&to)); return 0; } From 7a097aba725501c399b5e93d0199aa3a2f6e9c88 Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Fri, 2 Oct 2026 09:19:27 +0200 Subject: [PATCH 1813/2286] clocksource/drivers/timer-mediatek: Convert to OF probing MediaTek APXGPT general purpose timer driver is registered via TIMER_OF_DECLARE to be loaded early in the boot process. It depends on a reference 13 MHz clock that is assigned via the clocks property of its DT node. On platforms like mt8516/mt8167 this clock is provided by a driver that is loaded using OF probing and becomes available only later during boot. This leads to a silent EDEFER error during timer init, which is not recovered by TIMER_OF_DECLARE and GPT clocks are not registered at all. Since those clocks are not critical for boot, convert the timer-mediatek to OF probing and drop the TIMER_OF_DECLARE instructions. Signed-off-by: Luca Leonardo Scorcia Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20261002072115.169250-3-l.scorcia@gmail.com --- drivers/clocksource/timer-mediatek.c | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c index e20075b09fab0b..a9e7ff6908a34d 100644 --- a/drivers/clocksource/timer-mediatek.c +++ b/drivers/clocksource/timer-mediatek.c @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include #include "timer-of.h" @@ -215,8 +218,7 @@ static irqreturn_t mtk_gpt_interrupt(int irq, void *dev_id) return IRQ_HANDLED; } -static void -__init mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option) +static void mtk_gpt_setup(struct timer_of *to, u8 timer, u8 option) { writel(GPT_CTRL_CLEAR | GPT_CTRL_DISABLE, timer_of_base(to) + GPT_CTRL_REG(timer)); @@ -281,7 +283,7 @@ static struct timer_of to = { }, }; -static int __init mtk_syst_init(struct device_node *node) +static int mtk_syst_init(struct device_node *node) { int ret; @@ -339,5 +341,29 @@ static int __init mtk_gpt_init(struct device_node *node) return 0; } -TIMER_OF_DECLARE(mtk_mt6577, "mediatek,mt6577-timer", mtk_gpt_init); -TIMER_OF_DECLARE(mtk_mt6765, "mediatek,mt6765-timer", mtk_syst_init); + +static int mtk_timer_probe(struct platform_device *pdev) +{ + int (*timer_init)(struct device_node *node); + struct device_node *np = pdev->dev.of_node; + + timer_init = of_device_get_match_data(&pdev->dev); + return timer_init(np); +} + +static const struct of_device_id mtk_timer_match_table[] = { + { .compatible = "mediatek,mt6577-timer", .data = mtk_gpt_init }, + { .compatible = "mediatek,mt6765-timer", .data = mtk_syst_init }, + { /* sentinel */ } +}; + +static struct platform_driver mtk_timer_driver = { + .probe = mtk_timer_probe, + .driver = { + .name = "mediatek-timer", + .of_match_table = mtk_timer_match_table, + }, +}; +module_platform_driver(mtk_timer_driver); + +MODULE_DESCRIPTION("MediaTek Timer driver"); From e234d40823808c1727e88a40d8c9ee960de1baed Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Fri, 2 Oct 2026 09:19:28 +0200 Subject: [PATCH 1814/2286] clocksource/drivers/timer-mediatek: Request and enable the bus clock MediaTek APXGPT general purpose timers may optionally depend on a bus clock. If this clock is not enabled the timers don't actually run. Boot loaders usually turn it on, but later the kernel turns it off since it's never requested by any driver. Some platforms (like mt6735) declare it as a critical clock in their clock definitions to keep it running, but at least on mt8167 it's not actually required for a successful boot. If a bus clock is declared in the device tree, make sure it is enabled by driver probe and associated to the device. Signed-off-by: Luca Leonardo Scorcia Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20261002072115.169250-4-l.scorcia@gmail.com --- drivers/clocksource/timer-mediatek.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c index a9e7ff6908a34d..842d6ee4259e93 100644 --- a/drivers/clocksource/timer-mediatek.c +++ b/drivers/clocksource/timer-mediatek.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -283,8 +284,9 @@ static struct timer_of to = { }, }; -static int mtk_syst_init(struct device_node *node) +static int mtk_syst_init(struct platform_device *pdev) { + struct device_node *node = pdev->dev.of_node; int ret; to.clkevt.features = CLOCK_EVT_FEAT_DYNIRQ | CLOCK_EVT_FEAT_ONESHOT; @@ -305,10 +307,14 @@ static int mtk_syst_init(struct device_node *node) return 0; } -static int __init mtk_gpt_init(struct device_node *node) +static int mtk_gpt_init(struct platform_device *pdev) { + struct device_node *node = pdev->dev.of_node; + struct clk *clk_bus; int ret; + clk_bus = devm_clk_get_optional_enabled(&pdev->dev, "bus"); + to.clkevt.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; to.clkevt.set_state_shutdown = mtk_gpt_clkevt_shutdown; to.clkevt.set_state_periodic = mtk_gpt_clkevt_set_periodic; @@ -344,11 +350,10 @@ static int __init mtk_gpt_init(struct device_node *node) static int mtk_timer_probe(struct platform_device *pdev) { - int (*timer_init)(struct device_node *node); - struct device_node *np = pdev->dev.of_node; + int (*timer_init)(struct platform_device *pdev); timer_init = of_device_get_match_data(&pdev->dev); - return timer_init(np); + return timer_init(pdev); } static const struct of_device_id mtk_timer_match_table[] = { From 9fc91ae6852aa6e63c08b6093634cfcc85021b4e Mon Sep 17 00:00:00 2001 From: Yiwei Lin Date: Mon, 5 Oct 2026 17:32:33 +0800 Subject: [PATCH 1815/2286] rbtree_test: Use rb_add() and rb_add_cached() for the basic tests insert() and insert_cached() open-code exactly what the generic rb_add() and rb_add_cached() helpers do. Use the helpers instead. Besides removing duplicated code, this makes rbtree_test cover the helpers themselves, which is what most in-tree rbtree users call nowadays. Signed-off-by: Yiwei Lin Assisted-by: LLM Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20261005093236.62702-2-s921975628@gmail.com --- lib/rbtree_test.c | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/lib/rbtree_test.c b/lib/rbtree_test.c index 768c5e6453f375..c1386d328cfb1c 100644 --- a/lib/rbtree_test.c +++ b/lib/rbtree_test.c @@ -30,41 +30,20 @@ static struct test_node *nodes = NULL; static struct rnd_state rnd; -static void insert(struct test_node *node, struct rb_root_cached *root) +static inline bool less(struct rb_node *a, const struct rb_node *b) { - struct rb_node **new = &root->rb_root.rb_node, *parent = NULL; - u32 key = node->key; - - while (*new) { - parent = *new; - if (key < rb_entry(parent, struct test_node, rb)->key) - new = &parent->rb_left; - else - new = &parent->rb_right; - } + return rb_entry(a, struct test_node, rb)->key < + rb_entry(b, struct test_node, rb)->key; +} - rb_link_node(&node->rb, parent, new); - rb_insert_color(&node->rb, &root->rb_root); +static void insert(struct test_node *node, struct rb_root_cached *root) +{ + rb_add(&node->rb, &root->rb_root, less); } static void insert_cached(struct test_node *node, struct rb_root_cached *root) { - struct rb_node **new = &root->rb_root.rb_node, *parent = NULL; - u32 key = node->key; - bool leftmost = true; - - while (*new) { - parent = *new; - if (key < rb_entry(parent, struct test_node, rb)->key) - new = &parent->rb_left; - else { - new = &parent->rb_right; - leftmost = false; - } - } - - rb_link_node(&node->rb, parent, new); - rb_insert_color_cached(&node->rb, root, leftmost); + rb_add_cached(&node->rb, root, less); } static inline void erase(struct test_node *node, struct rb_root_cached *root) From c2c620f4345c50006af90c37fd104fed8e9dea14 Mon Sep 17 00:00:00 2001 From: "Peter Zijlstra (Intel)" Date: Mon, 5 Oct 2026 17:32:34 +0800 Subject: [PATCH 1816/2286] rbtree: Declare augmented callbacks per field with RB_AUG() RB_DECLARE_CALLBACKS_MULTI() asks its user for a function that copies the augmented fields and one that recomputes them from the children, with the early-exit protocol of ->propagate() hand-coded in the latter. sched/eevdf, its only user, needs five helpers for three fields. Describe each augmented field instead: RB_AUG(val, aug, fold) names the per-node value, the member holding the subtree aggregate and how two aggregates combine (min, max, a sum, ...). RB_AUG_FUNC() takes a function for the per-node value. The template then generates, per field, a recompute that works on a local and stores once, and a copy, and combines them into the callbacks; the early exit is the AND of the per-field results. RB_DECLARE_CALLBACKS() is the only template left. RB_DECLARE_CALLBACKS_MAX() becomes RB_AUG_FUNC(RBVALUE, RBAUGMENTED, max) in a wrapper, with its RBCOMPUTE argument renamed to RBVALUE, since it returns the per-node scalar and is a different thing from the RBCOMPUTE of the generic template it used to build on. RB_DECLARE_CALLBACKS_MULTI() goes away. sched/eevdf shrinks to a wrapping-safe min() for min_vruntime and three RB_AUG() lines. No functional change intended. [yiwei: take a fold(a, b) instead of a "replace?" compare so that sums and counts can be expressed too, which also lets min()/max() replace RB_MIN()/RB_MAX(); wrapped the lines over 100 columns] Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Yiwei Lin Assisted-by: LLM Signed-off-by: Peter Zijlstra (Intel) Link: https://lore.kernel.org/r/20260928213736.GA2947991@noisy.programming.kicks-ass.net Link: https://patch.msgid.link/20261005093236.62702-3-s921975628@gmail.com --- include/linux/rbtree_augmented.h | 174 +++++++++++++++++++++---------- kernel/sched/fair.c | 70 ++----------- 2 files changed, 126 insertions(+), 118 deletions(-) diff --git a/include/linux/rbtree_augmented.h b/include/linux/rbtree_augmented.h index d2fa1c41bfd2b2..5a336ac203c184 100644 --- a/include/linux/rbtree_augmented.h +++ b/include/linux/rbtree_augmented.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include /* * Please note - only struct rb_augment_callbacks and the prototypes for @@ -86,6 +88,95 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, return leftmost ? node : NULL; } +#define RB_FOR_EACH_1(what, RBNAME, RBSTRUCT, RBFIELD, x) \ + what(1, RBNAME, RBSTRUCT, RBFIELD, x) + +#define RB_FOR_EACH_2(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(2, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_1(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_3(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(3, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_2(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_4(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(4, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_3(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_5(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(5, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_4(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_6(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(6, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_5(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_7(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(7, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_6(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH_8(what, RBNAME, RBSTRUCT, RBFIELD, x, ...) \ + what(8, RBNAME, RBSTRUCT, RBFIELD, x) \ + RB_FOR_EACH_7(what, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +#define RB_FOR_EACH(action, RBNAME, RBSTRUCT, RBFIELD, ...) \ + CONCATENATE(RB_FOR_EACH_, COUNT_ARGS(__VA_ARGS__)) \ + (action, RBNAME, RBSTRUCT, RBFIELD, __VA_ARGS__) + +/* + * One augmented field: @val is the node's own contribution (a member for + * RB_AUG(), a function of the node for RB_AUG_FUNC()), @aug the member + * holding the aggregate over the subtree, and @fold(a, b) combines two + * aggregates: min, max, a sum, ... It must be commutative and associative. + */ +#define RB_AUG_FUNC(val, aug, fold) (val(s), aug, fold) +#define RB_AUG(val, aug, fold) (s->val, aug, fold) +#define RB_UNPACK(...) __VA_ARGS__ + +#define __RB_INST(n, RBNAME, RBSTRUCT, RBFIELD, val, aug, fold) \ +static inline void \ +RBNAME ## _copy_ ## n(RBSTRUCT *old, RBSTRUCT *new) \ +{ \ + new->aug = old->aug; \ +} \ +static inline bool \ +RBNAME ## _compute_ ## n(RBSTRUCT *s, bool exit) \ +{ \ + TYPEOF_UNQUAL(s->aug) _old_aug = s->aug; \ + TYPEOF_UNQUAL(s->aug) _val = val; \ + struct rb_node *_node = &s->RBFIELD; \ + if (_node->rb_right) { \ + RBSTRUCT *_c = container_of(_node->rb_right, typeof(*s), RBFIELD); \ + _val = fold(_val, _c->aug); \ + } \ + if (_node->rb_left) { \ + RBSTRUCT *_c = container_of(_node->rb_left, typeof(*s), RBFIELD); \ + _val = fold(_val, _c->aug); \ + } \ + if (exit && _old_aug == _val) \ + return true; \ + s->aug = _val; \ + return false; \ +} +#define _RB_INST(n, RBNAME, RBSTRUCT, RBFIELD, args) \ + __RB_INST(n, RBNAME, RBSTRUCT, RBFIELD, args) +#define RB_INST(n, RBNAME, RBSTRUCT, RBFIELD, x) \ + _RB_INST(n, RBNAME, RBSTRUCT, RBFIELD, RB_UNPACK x) + +#define __RB_COPY(n, RBNAME, RBSTRUCT, RBFIELD, val, aug, fold) \ + RBNAME ## _copy_ ## n(old, new); +#define _RB_COPY(n, RBNAME, RBSTRUCT, RBFIELD, args) \ + __RB_COPY(n, RBNAME, RBSTRUCT, RBFIELD, args) +#define RB_COPY(n, RBNAME, RBSTRUCT, RBFIELD, x) \ + _RB_COPY(n, RBNAME, RBSTRUCT, RBFIELD, RB_UNPACK x) + +#define __RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, val, aug, fold) \ + ret &= RBNAME ## _compute_ ## n(node, exit); +#define _RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, args) \ + __RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, args) +#define RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, x) \ + _RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, RB_UNPACK x) + /* * Template for declaring augmented rbtree callbacks (generic multi fields) * @@ -93,18 +184,29 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, * RBNAME: name of the rb_augment_callbacks structure * RBSTRUCT: struct type of the tree nodes * RBFIELD: name of struct rb_node field within RBSTRUCT - * RBCOPY: name of function that copies the RBAUGMENTED datas - * RBCOMPUTE: name of function that recomputes the RBAUGMENTED datas + * RBAUG...: list of RB_AUG() describing the augmented data */ - -#define RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBCOPY, RBCOMPUTE) \ +#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ + RBSTRUCT, RBFIELD, RBAUG...) \ +RB_FOR_EACH(RB_INST, RBNAME, RBSTRUCT, RBFIELD, RBAUG) \ +static inline void \ +RBNAME ## __copy(RBSTRUCT *old, RBSTRUCT *new) \ +{ \ + RB_FOR_EACH(RB_COPY, RBNAME, RBSTRUCT, RBFIELD, RBAUG); \ +} \ +static inline bool \ +RBNAME ## __compute(RBSTRUCT *node, bool exit) \ +{ \ + bool ret = true; \ + RB_FOR_EACH(RB_COMPUTE, RBNAME, RBSTRUCT, RBFIELD, RBAUG); \ + return ret; \ +} \ static inline void \ RBNAME ## _propagate(struct rb_node *rb, struct rb_node *stop) \ { \ while (rb != stop) { \ RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ - if (RBCOMPUTE(node, true)) \ + if (RBNAME ## __compute(node, true)) \ break; \ rb = rb_parent(&node->RBFIELD); \ } \ @@ -114,15 +216,15 @@ RBNAME ## _copy(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - RBCOPY(new, old); \ + RBNAME ## __copy(old, new); \ } \ static void \ RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ { \ RBSTRUCT *old = rb_entry(rb_old, RBSTRUCT, RBFIELD); \ RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ - RBCOPY(new, old); \ - RBCOMPUTE(old, false); \ + RBNAME ## __copy(old, new); \ + RBNAME ## __compute(old, false); \ } \ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ .propagate = RBNAME ## _propagate, \ @@ -130,27 +232,6 @@ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ .rotate = RBNAME ## _rotate \ }; -/* - * Template for declaring augmented rbtree callbacks (generic single field) - * - * RBSTATIC: 'static' or empty - * RBNAME: name of the rb_augment_callbacks structure - * RBSTRUCT: struct type of the tree nodes - * RBFIELD: name of struct rb_node field within RBSTRUCT - * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree - * RBCOMPUTE: name of function that recomputes the RBAUGMENTED data - */ - -#define RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBCOMPUTE) \ -static inline void \ -RBNAME ## _copy_single(RBSTRUCT *new, RBSTRUCT *old) \ -{ \ - new->RBAUGMENTED = old->RBAUGMENTED; \ -} \ -RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBNAME ## _copy_single, RBCOMPUTE) - /* * Template for declaring augmented rbtree callbacks, * computing RBAUGMENTED scalar as max(RBCOMPUTE(node)) for all subtree nodes. @@ -159,34 +240,15 @@ RB_DECLARE_CALLBACKS_MULTI(RBSTATIC, RBNAME, \ * RBNAME: name of the rb_augment_callbacks structure * RBSTRUCT: struct type of the tree nodes * RBFIELD: name of struct rb_node field within RBSTRUCT - * RBTYPE: type of the RBAUGMENTED field - * RBAUGMENTED: name of RBTYPE field within RBSTRUCT holding data for subtree - * RBCOMPUTE: name of function that returns the per-node RBTYPE scalar + * RBTYPE: type of the RBAUGMENTED field -- unused, assumed typeof(RBAUGMENTED) + * RBAUGMENTED: name of field within RBSTRUCT holding data for subtree + * RBVALUE: name of function that returns the per-node RBTYPE scalar */ -#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ - RBTYPE, RBAUGMENTED, RBCOMPUTE) \ -static inline bool RBNAME ## _compute_max(RBSTRUCT *node, bool exit) \ -{ \ - RBSTRUCT *child; \ - RBTYPE max = RBCOMPUTE(node); \ - if (node->RBFIELD.rb_left) { \ - child = rb_entry(node->RBFIELD.rb_left, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (node->RBFIELD.rb_right) { \ - child = rb_entry(node->RBFIELD.rb_right, RBSTRUCT, RBFIELD); \ - if (child->RBAUGMENTED > max) \ - max = child->RBAUGMENTED; \ - } \ - if (exit && node->RBAUGMENTED == max) \ - return true; \ - node->RBAUGMENTED = max; \ - return false; \ -} \ -RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, \ - RBSTRUCT, RBFIELD, RBAUGMENTED, RBNAME ## _compute_max) +#define RB_DECLARE_CALLBACKS_MAX(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ + RBTYPE, RBAUGMENTED, RBVALUE) \ +RB_DECLARE_CALLBACKS(RBSTATIC, RBNAME, RBSTRUCT, RBFIELD, \ + RB_AUG_FUNC(RBVALUE, RBAUGMENTED, max)) #define RB_RED 0 diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 5c98d8dfce5d89..a58d513b3d634e 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -999,71 +999,17 @@ static inline bool __entity_less(struct rb_node *a, const struct rb_node *b) return entity_before(__node_2_se(a), __node_2_se(b)); } -static inline void __min_vruntime_update(struct sched_entity *se, struct rb_node *node) +/* min() for wrapping vruntimes */ +static inline u64 __min_vruntime(u64 a, u64 b) { - if (node) { - struct sched_entity *rse = __node_2_se(node); - - if (vruntime_cmp(se->min_vruntime, ">", rse->min_vruntime)) - se->min_vruntime = rse->min_vruntime; - } -} - -static inline void __min_slice_update(struct sched_entity *se, struct rb_node *node) -{ - if (node) { - struct sched_entity *rse = __node_2_se(node); - if (rse->min_slice < se->min_slice) - se->min_slice = rse->min_slice; - } -} - -static inline void __max_slice_update(struct sched_entity *se, struct rb_node *node) -{ - if (node) { - struct sched_entity *rse = __node_2_se(node); - if (rse->max_slice > se->max_slice) - se->max_slice = rse->max_slice; - } -} - -static inline void min_vruntime_copy(struct sched_entity *new, struct sched_entity *old) -{ - new->min_vruntime = old->min_vruntime; - new->min_slice = old->min_slice; - new->max_slice = old->max_slice; + return vruntime_cmp(a, "<", b) ? a : b; } -/* - * se->min_vruntime = min(se->vruntime, {left,right}->min_vruntime) - */ -static inline bool min_vruntime_update(struct sched_entity *se, bool exit) -{ - u64 old_min_vruntime = se->min_vruntime; - u64 old_min_slice = se->min_slice; - u64 old_max_slice = se->max_slice; - struct rb_node *node = &se->run_node; - - se->min_vruntime = se->vruntime; - __min_vruntime_update(se, node->rb_right); - __min_vruntime_update(se, node->rb_left); - - se->min_slice = se->slice; - __min_slice_update(se, node->rb_right); - __min_slice_update(se, node->rb_left); - - se->max_slice = se->slice; - __max_slice_update(se, node->rb_right); - __max_slice_update(se, node->rb_left); - - return se->min_vruntime == old_min_vruntime && - se->min_slice == old_min_slice && - se->max_slice == old_max_slice; -} - - -RB_DECLARE_CALLBACKS_MULTI(static, min_vruntime_cb, struct sched_entity, - run_node, min_vruntime_copy, min_vruntime_update); +RB_DECLARE_CALLBACKS(static, min_vruntime_cb, + struct sched_entity, run_node, + RB_AUG(vruntime, min_vruntime, __min_vruntime), + RB_AUG(slice, min_slice, min), + RB_AUG(slice, max_slice, max)); /* * Enqueue an entity into the rb-tree: From cd047867ee5fc7c47cf9461c8a59c7117de9f680 Mon Sep 17 00:00:00 2001 From: Yiwei Lin Date: Mon, 5 Oct 2026 17:32:35 +0800 Subject: [PATCH 1817/2286] rbtree: Update augmented data on the way down in rb_add_augmented_cached() rb_add_augmented_cached() links the new node and then calls ->propagate() from the parent up to the root to bring the augmented data of the ancestors up to date. It marks that as "suboptimal": every augmented user that open-codes its insertion (interval trees, vmalloc, drm_mm, gpu buddy, ...) instead folds the new node's value into each ancestor while walking down, which touches no sibling and needs no second pass. ->propagate() has to read both children of every ancestor until it finds one whose value did not change. Add a ->merge() callback to struct rb_augment_callbacks that folds the augmented data of a node being inserted into one of its future ancestors, and use it on the descent. RB_DECLARE_CALLBACKS() derives it from the RB_AUG() list, so every user of the template gets it for free, and sched/eevdf, the only caller of the helper, needs no change. The new node's augmented data must describe the node alone before the call. That was already required, since ->propagate() read it, and sched/eevdf sets it up that way. Measured with rbtree_test's cached augmented test switched to rb_add_augmented_cached() (next patch), nnodes cached insert+delete, median of 5 runs, for reference also the open-coded descent used by augmented test 1. Raspberry Pi 4 Model B (Cortex-A72, 1.8 GHz), arch timer ticks (54 MHz): nnodes open-coded before after 100 459 500 442 (-11.6%) 10000 182046 186144 180208 (-3.2%) 100000 5533679 5711647 5579444 (-2.3%) x86-64 KVM guest (Intel i7-13800H), cycles: nnodes open-coded before after 100 7657 7622 7059 (-7.4%) 10000 4738075 5075847 4627462 (-8.8%) 100000 75618429 79840335 71324700 (-10.7%) The other rbtree_test numbers are unchanged and the augmented invariant checks still pass on both. Signed-off-by: Yiwei Lin Assisted-by: LLM Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20261005093236.62702-4-s921975628@gmail.com --- Documentation/core-api/rbtree.rst | 27 ++++++++++++++++++++++++--- include/linux/rbtree_augmented.h | 27 +++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Documentation/core-api/rbtree.rst b/Documentation/core-api/rbtree.rst index cce80e19087b42..210045f128d650 100644 --- a/Documentation/core-api/rbtree.rst +++ b/Documentation/core-api/rbtree.rst @@ -257,8 +257,14 @@ When erasing a node, the user must call rb_erase_augmented() instead of rb_erase(). rb_erase_augmented() calls back into user provided functions to update the augmented information on affected subtrees. -In both cases, the callbacks are provided through struct rb_augment_callbacks. -3 callbacks must be defined: +Alternatively, rb_add_augmented_cached() performs the whole insertion into +a cached tree: it walks down to the insertion point, merges the new node's +augmented information into every node on that path, links the node and +rebalances. The new node's augmented information must already describe +the node alone when it is called. + +In all cases, the callbacks are provided through struct rb_augment_callbacks. +3 callbacks must be defined, plus a fourth one for rb_add_augmented_cached(): - A propagation callback, which updates the augmented value for a given node and its ancestors, up to a given stop point (or NULL to update @@ -271,6 +277,10 @@ In both cases, the callbacks are provided through struct rb_augment_callbacks. subtree to a newly assigned subtree root AND recomputes the augmented information for the former subtree root. +- A merge callback, which folds the augmented value of a node being inserted + into the augmented value of one of its future ancestors. It is only used + by rb_add_augmented_cached(). + The compiled code for rb_erase_augmented() may inline the propagation and copy callbacks, which results in a large function, so each augmented rbtree user should have a single rb_erase_augmented() call site in order to limit @@ -395,8 +405,19 @@ Insertion/removal are defined using the following augmented callbacks:: old->__subtree_last = compute_subtree_last(old); } + static void augment_merge(struct rb_node *rb, struct rb_node *rb_new) + { + struct interval_tree_node *node = + rb_entry(rb, struct interval_tree_node, rb); + struct interval_tree_node *new = + rb_entry(rb_new, struct interval_tree_node, rb); + + if (node->__subtree_last < new->__subtree_last) + node->__subtree_last = new->__subtree_last; + } + static const struct rb_augment_callbacks augment_callbacks = { - augment_propagate, augment_copy, augment_rotate + augment_propagate, augment_copy, augment_rotate, augment_merge }; void interval_tree_insert(struct interval_tree_node *node, diff --git a/include/linux/rbtree_augmented.h b/include/linux/rbtree_augmented.h index 5a336ac203c184..b9fc1be86ae392 100644 --- a/include/linux/rbtree_augmented.h +++ b/include/linux/rbtree_augmented.h @@ -30,6 +30,7 @@ struct rb_augment_callbacks { void (*propagate)(struct rb_node *node, struct rb_node *stop); void (*copy)(struct rb_node *old, struct rb_node *new); void (*rotate)(struct rb_node *old, struct rb_node *new); + void (*merge)(struct rb_node *node, struct rb_node *new); }; extern void __rb_insert_augmented(struct rb_node *node, struct rb_root *root, @@ -62,6 +63,12 @@ rb_insert_augmented_cached(struct rb_node *node, rb_insert_augmented(node, &root->rb_root, augment); } +/* + * Insert @node into the leftmost cached augmented tree @tree. + * + * The augmented data of @node must already describe @node alone; it is + * merged into every ancestor on the way down through augment->merge(). + */ static __always_inline struct rb_node * rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, bool (*less)(struct rb_node *, const struct rb_node *), @@ -73,6 +80,7 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, while (*link) { parent = *link; + augment->merge(parent, node); if (less(node, parent)) { link = &parent->rb_left; } else { @@ -82,7 +90,6 @@ rb_add_augmented_cached(struct rb_node *node, struct rb_root_cached *tree, } rb_link_node(node, parent, link); - augment->propagate(parent, NULL); /* suboptimal */ rb_insert_augmented_cached(node, tree, leftmost, augment); return leftmost ? node : NULL; @@ -177,6 +184,14 @@ RBNAME ## _compute_ ## n(RBSTRUCT *s, bool exit) \ #define RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, x) \ _RB_COMPUTE(n, RBNAME, RBSTRUCT, RBFIELD, RB_UNPACK x) +/* fold @new, about to become a descendant of @node, into @node */ +#define __RB_MERGE(n, RBNAME, RBSTRUCT, RBFIELD, val, aug, fold) \ + node->aug = fold(node->aug, new->aug); +#define _RB_MERGE(n, RBNAME, RBSTRUCT, RBFIELD, args) \ + __RB_MERGE(n, RBNAME, RBSTRUCT, RBFIELD, args) +#define RB_MERGE(n, RBNAME, RBSTRUCT, RBFIELD, x) \ + _RB_MERGE(n, RBNAME, RBSTRUCT, RBFIELD, RB_UNPACK x) + /* * Template for declaring augmented rbtree callbacks (generic multi fields) * @@ -226,10 +241,18 @@ RBNAME ## _rotate(struct rb_node *rb_old, struct rb_node *rb_new) \ RBNAME ## __copy(old, new); \ RBNAME ## __compute(old, false); \ } \ +static inline void \ +RBNAME ## _merge(struct rb_node *rb, struct rb_node *rb_new) \ +{ \ + RBSTRUCT *node = rb_entry(rb, RBSTRUCT, RBFIELD); \ + RBSTRUCT *new = rb_entry(rb_new, RBSTRUCT, RBFIELD); \ + RB_FOR_EACH(RB_MERGE, RBNAME, RBSTRUCT, RBFIELD, RBAUG); \ +} \ RBSTATIC const struct rb_augment_callbacks RBNAME = { \ .propagate = RBNAME ## _propagate, \ .copy = RBNAME ## _copy, \ - .rotate = RBNAME ## _rotate \ + .rotate = RBNAME ## _rotate, \ + .merge = RBNAME ## _merge \ }; /* From cc459ecd9388d96dc20404b157f5a4eedb66c355 Mon Sep 17 00:00:00 2001 From: Yiwei Lin Date: Mon, 5 Oct 2026 17:32:36 +0800 Subject: [PATCH 1818/2286] rbtree_test: Use rb_add_augmented_cached() for the cached augmented test insert_augmented_cached() open-codes the descent that the generic rb_add_augmented_cached() helper provides. Use the helper instead, so that rbtree_test covers it: it is what sched/eevdf relies on and nothing in lib/ exercises it today. Signed-off-by: Yiwei Lin Assisted-by: LLM Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20261005093236.62702-5-s921975628@gmail.com --- lib/rbtree_test.c | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/lib/rbtree_test.c b/lib/rbtree_test.c index c1386d328cfb1c..00f62e4f9d10e9 100644 --- a/lib/rbtree_test.c +++ b/lib/rbtree_test.c @@ -89,29 +89,8 @@ static void insert_augmented(struct test_node *node, static void insert_augmented_cached(struct test_node *node, struct rb_root_cached *root) { - struct rb_node **new = &root->rb_root.rb_node, *rb_parent = NULL; - u32 key = node->key; - u32 val = node->val; - struct test_node *parent; - bool leftmost = true; - - while (*new) { - rb_parent = *new; - parent = rb_entry(rb_parent, struct test_node, rb); - if (parent->augmented < val) - parent->augmented = val; - if (key < parent->key) - new = &parent->rb.rb_left; - else { - new = &parent->rb.rb_right; - leftmost = false; - } - } - - node->augmented = val; - rb_link_node(&node->rb, rb_parent, new); - rb_insert_augmented_cached(&node->rb, root, - leftmost, &augment_callbacks); + node->augmented = node->val; + rb_add_augmented_cached(&node->rb, root, less, &augment_callbacks); } From 159b172906110f736fdbd7af8a21d0e0b469f671 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Sun, 11 Oct 2026 10:50:19 +0900 Subject: [PATCH 1819/2286] sched/wait: Avoid modifying entry on failed priority registration add_wait_queue_priority_exclusive() sets WQ_FLAG_EXCLUSIVE and WQ_FLAG_PRIORITY before checking whether another priority waiter is already present. Thus, when registration fails with -EBUSY, the entry is left unqueued but with modified flags. There is no need to modify the entry before the failure check. Move the flag update after the check so that a rejected registration does not unnecessarily alter the entry. Signed-off-by: Hui Su Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Sean Christopherson Link: https://patch.msgid.link/20261011015019.4056529-1-sh_def@163.com --- kernel/sched/wait.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c index 477e4bf9c01e19..f8d6d8b9d03e66 100644 --- a/kernel/sched/wait.c +++ b/kernel/sched/wait.c @@ -54,14 +54,13 @@ int add_wait_queue_priority_exclusive(struct wait_queue_head *wq_head, { struct list_head *head = &wq_head->head; - wq_entry->flags |= WQ_FLAG_EXCLUSIVE | WQ_FLAG_PRIORITY; - guard(spinlock_irqsave)(&wq_head->lock); if (!list_empty(head) && (list_first_entry(head, typeof(*wq_entry), entry)->flags & WQ_FLAG_PRIORITY)) return -EBUSY; + wq_entry->flags |= WQ_FLAG_EXCLUSIVE | WQ_FLAG_PRIORITY; list_add(&wq_entry->entry, head); return 0; } From b0f1d7a34d3099973f1ba45d1ce6f36578e510a2 Mon Sep 17 00:00:00 2001 From: Elif Topuz Date: Mon, 12 Oct 2026 11:50:01 +0100 Subject: [PATCH 1820/2286] sched/eevdf: Remove redundant RUN_TO_PARITY check update_protect_slice() is only called from wakeup_preempt_fair() when RUN_TO_PARITY is enabled. Remove RUN_TO_PARITY check inside update_protect_slice() and use cfs_rq_min_slice() directly. Signed-off-by: Elif Topuz Signed-off-by: Peter Zijlstra (Intel) Reviewed-by: Vincent Guittot Link: https://patch.msgid.link/20261012105001.131311-1-elif.topuz@arm.com --- kernel/sched/fair.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index a58d513b3d634e..7bf31931fe0982 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -1089,13 +1089,8 @@ static inline void set_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity static inline void update_protect_slice(struct cfs_rq *cfs_rq, struct sched_entity *se) { u64 vruntime = min_vruntime(se->vruntime, avg_vruntime(cfs_rq)); - u64 slice = normalized_sysctl_sched_base_slice; - u64 vprot; - - if (sched_feat(RUN_TO_PARITY)) - slice = cfs_rq_min_slice(cfs_rq); - - vprot = min_vruntime(se->vprot, vruntime + calc_delta_fair(slice, se)); + u64 slice = cfs_rq_min_slice(cfs_rq); + u64 vprot = min_vruntime(se->vprot, vruntime + calc_delta_fair(slice, se)); if (sched_feat(PREEMPT_SHORT) && slice != se->slice) vprot = min_vruntime(vprot, ineligible_vruntime(cfs_rq)); From 7fa0d9b2ec070d9555801fecc5d30953a256122c Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 16 Sep 2026 12:03:56 +0200 Subject: [PATCH 1821/2286] arm64: clear_page[s] using memset There is no need to try to second-guess the compiler when clearing memory. Just call memset() like everyone else. Since memset() already has an architecture-local MOPS optimization, we do not need to do anything else to preserve the MOPS optimization. While at it, implement the shorthand for directly calling the new prototype clear_pages() for larger page chunks. No performance regressions can be seen, the fastpath benchmarks differences are in the noise. Usama Anjum tested next-20260821 with one warm-up and three repeats in four sessions, for a total of 12 measured runs. The commands were: perf bench mem memset -k 1GB -f default -s 16GB perf bench mem mmap -p 1GB -f demand -s 32GB -l 5 perf bench mem mmap -p 4KB -f demand -s 32GB -l 5 The results were: aws-m7g.metal: Benchmark Base bytes/sec Change with patch memset 1GB 63932542232.12 1.92% mmap 1GB 63272579168.03 -0.57% mmap 4KB 49692830849.48 -1.11% cesw-aarch64-ampereone-1s-a192-32x: Benchmark Base bytes/sec Change with patch memset 1GB 33895562998.71 0.25% mmap 1GB 34338454210.17 1.16% mmap 4KB 25687107580.90 -0.85% Assisted-by: LLM Suggested-by: Will Deacon Tested-by: Usama Anjum Link: https://lore.kernel.org/linux-arm-kernel/20260303-aarch64-clear-pages-v1-1-ad0c3ee9a555@kernel.org/ Link: https://lore.kernel.org/linux-arm-kernel/67ab6c51-4511-46ab-931b-a4323551147a@arm.com/ Signed-off-by: Linus Walleij Reviewed-by: Leonardo Bras Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/page.h | 13 +++++++- arch/arm64/kernel/image-vars.h | 1 - arch/arm64/kvm/hyp/nvhe/Makefile | 2 +- arch/arm64/lib/Makefile | 2 +- arch/arm64/lib/clear_page.S | 53 -------------------------------- 5 files changed, 14 insertions(+), 57 deletions(-) delete mode 100644 arch/arm64/lib/clear_page.S diff --git a/arch/arm64/include/asm/page.h b/arch/arm64/include/asm/page.h index 58200de8a221a6..b2eaa63d90568e 100644 --- a/arch/arm64/include/asm/page.h +++ b/arch/arm64/include/asm/page.h @@ -13,6 +13,7 @@ #ifndef __ASSEMBLER__ #include /* for READ_IMPLIES_EXEC */ +#include /* for memset() */ #include /* for gfp_t */ #include @@ -20,7 +21,17 @@ struct page; struct vm_area_struct; extern void copy_page(void *to, const void *from); -extern void clear_page(void *to); + +static inline void clear_pages(void *addr, unsigned int npages) +{ + memset(addr, 0, npages * PAGE_SIZE); +} +#define clear_pages clear_pages + +static inline void clear_page(void *addr) +{ + clear_pages(addr, 1); +} void copy_user_highpage(struct page *to, struct page *from, unsigned long vaddr, struct vm_area_struct *vma); diff --git a/arch/arm64/kernel/image-vars.h b/arch/arm64/kernel/image-vars.h index 14beb7b9d304c1..facda8137d9b19 100644 --- a/arch/arm64/kernel/image-vars.h +++ b/arch/arm64/kernel/image-vars.h @@ -119,7 +119,6 @@ KVM_NVHE_ALIAS(__start___kvm_ex_table); KVM_NVHE_ALIAS(__stop___kvm_ex_table); /* Position-independent library routines */ -KVM_NVHE_ALIAS_HYP(clear_page, __pi_clear_page); KVM_NVHE_ALIAS_HYP(copy_page, __pi_copy_page); KVM_NVHE_ALIAS_HYP(memcpy, __pi_memcpy); KVM_NVHE_ALIAS_HYP(memset, __pi_memset); diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile index f57450ebcb4989..2ed489bf544e0e 100644 --- a/arch/arm64/kvm/hyp/nvhe/Makefile +++ b/arch/arm64/kvm/hyp/nvhe/Makefile @@ -17,7 +17,7 @@ ccflags-y += -fno-stack-protector \ hostprogs := gen-hyprel HOST_EXTRACFLAGS += -I$(objtree)/include -lib-objs := clear_page.o copy_page.o memcpy.o memset.o tishift.o +lib-objs := copy_page.o memcpy.o memset.o tishift.o lib-objs := $(addprefix ../../../lib/, $(lib-objs)) CFLAGS_switch.nvhe.o += -Wno-override-init diff --git a/arch/arm64/lib/Makefile b/arch/arm64/lib/Makefile index b33e1ca4a7815b..38ffd359123cc3 100644 --- a/arch/arm64/lib/Makefile +++ b/arch/arm64/lib/Makefile @@ -5,7 +5,7 @@ KCSAN_SANITIZE_delay.o := n lib-y := clear_user.o delay.o copy_from_user.o \ copy_to_user.o copy_page.o \ - clear_page.o csum.o insn.o memchr.o memcpy.o \ + csum.o insn.o memchr.o memcpy.o \ memset.o memcmp.o strcmp.o strncmp.o strlen.o \ strnlen.o strchr.o strrchr.o tishift.o diff --git a/arch/arm64/lib/clear_page.S b/arch/arm64/lib/clear_page.S deleted file mode 100644 index bd6f7d5eb6eb65..00000000000000 --- a/arch/arm64/lib/clear_page.S +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2012 ARM Ltd. - */ - -#include -#include -#include -#include - -/* - * Clear page @dest - * - * Parameters: - * x0 - dest - */ -SYM_FUNC_START(__pi_clear_page) -#ifdef CONFIG_AS_HAS_MOPS - .arch_extension mops -alternative_if_not ARM64_HAS_MOPS - b .Lno_mops -alternative_else_nop_endif - - mov x1, #PAGE_SIZE - setpn [x0]!, x1!, xzr - setmn [x0]!, x1!, xzr - seten [x0]!, x1!, xzr - ret -.Lno_mops: -#endif - mrs x1, dczid_el0 - tbnz x1, #4, 2f /* Branch if DC ZVA is prohibited */ - and w1, w1, #0xf - mov x2, #4 - lsl x1, x2, x1 - -1: dc zva, x0 - add x0, x0, x1 - tst x0, #(PAGE_SIZE - 1) - b.ne 1b - ret - -2: stnp xzr, xzr, [x0] - stnp xzr, xzr, [x0, #16] - stnp xzr, xzr, [x0, #32] - stnp xzr, xzr, [x0, #48] - add x0, x0, #64 - tst x0, #(PAGE_SIZE - 1) - b.ne 2b - ret -SYM_FUNC_END(__pi_clear_page) -SYM_FUNC_ALIAS(clear_page, __pi_clear_page) -EXPORT_SYMBOL(clear_page) From 0b173877fbc07f4922f0c6a6fc43fd99088befd2 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 11 Oct 2026 15:48:41 +0200 Subject: [PATCH 1822/2286] lib/crypto: x86/sha1: Follow the normal %rbp convention Some of the SHA-1 assembly functions push callee-save GPRs to the stack *before* they set up the frame pointer (%rbp). (They are leaf functions, but they need to set up a frame pointer anyway because they allocate a 16- or 32-byte-aligned buffer on the stack.) As a result, during these functions, 8(%rbp) doesn't contain a return address like it is supposed to. This differs from the convention used by C code, and it interferes with the frame pointer unwinder (CONFIG_UNWINDER_FRAME_POINTER=y). Specifically, if a call trace is dumped in these functions, then all entries are populated purely from the fallback scan of the entire stack: RIP: 0010:sha1_transform_avx2+0x1921/0x1ea0 [...] Call Trace: ? sha1_transform_avx+0x2a9/0xc90 ? sha1_blocks_avx2+0x78/0x140 ? kernel_fpu_end+0x2d/0x40 ? sha1_blocks_avx2+0x52/0x140 ? sha1_update+0x77/0x220 [...] ? do_syscall_64+0x6f/0x3e0 With this fix to follow the normal %rbp convention, the call trace is generated reliably: RIP: 0010:sha1_transform_avx2+0x66e/0x1ea0 [...] Call Trace: sha1_blocks_avx2+0x78/0x140 sha1_update+0x77/0x220 [...] do_syscall_64+0x6f/0x3e0 Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20261011134843.166585-2-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/sha1-avx2-asm.S | 10 +++++----- lib/crypto/x86/sha1-ssse3-and-avx.S | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/crypto/x86/sha1-avx2-asm.S b/lib/crypto/x86/sha1-avx2-asm.S index 91b2dc6db179fd..ca64d7104a4792 100644 --- a/lib/crypto/x86/sha1-avx2-asm.S +++ b/lib/crypto/x86/sha1-avx2-asm.S @@ -622,6 +622,9 @@ .macro SHA1_VECTOR_ASM name SYM_FUNC_START(\name) + push %rbp + mov %rsp, %rbp + push %rbx push %r12 push %r13 @@ -631,8 +634,6 @@ RESERVE_STACK = (W_SIZE*4 + 8+24) /* Align stack */ - push %rbp - mov %rsp, %rbp and $~(0x20-1), %rsp sub $RESERVE_STACK, %rsp @@ -651,14 +652,13 @@ avx2_zeroupper - mov %rbp, %rsp - pop %rbp - + lea -40(%rbp), %rsp pop %r15 pop %r14 pop %r13 pop %r12 pop %rbx + pop %rbp RET diff --git a/lib/crypto/x86/sha1-ssse3-and-avx.S b/lib/crypto/x86/sha1-ssse3-and-avx.S index 78b48cb09c5b97..fe0faaf494c7df 100644 --- a/lib/crypto/x86/sha1-ssse3-and-avx.S +++ b/lib/crypto/x86/sha1-ssse3-and-avx.S @@ -69,11 +69,12 @@ .macro SHA1_VECTOR_ASM name SYM_FUNC_START(\name) - push %rbx - push %r12 push %rbp mov %rsp, %rbp + push %rbx + push %r12 + sub $64, %rsp # allocate workspace and $~15, %rsp # align stack @@ -95,10 +96,10 @@ xor %eax, %eax rep stosq - mov %rbp, %rsp # deallocate workspace - pop %rbp + lea -16(%rbp), %rsp # deallocate workspace pop %r12 pop %rbx + pop %rbp RET SYM_FUNC_END(\name) From cc76c9b82bf5272ce9bcc17df8c2aa21b6dfef0f Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 11 Oct 2026 15:48:42 +0200 Subject: [PATCH 1823/2286] lib/crypto: x86/sha256: Follow the normal %rbp convention Some of the SHA-256 assembly functions push callee-save GPRs to the stack *before* they set up the frame pointer (%rbp). (They are leaf functions, but they need to set up a frame pointer anyway because they allocate a 16- or 32-byte-aligned buffer on the stack.) As a result, during these functions, 8(%rbp) doesn't contain a return address like it is supposed to. This differs from the convention used by C code, and it interferes with the frame pointer unwinder (CONFIG_UNWINDER_FRAME_POINTER=y). Specifically, if a call trace is dumped in these functions, then all entries are populated purely from the fallback scan of the entire stack: RIP: 0010:sha256_transform_rorx+0xd78/0x1120 [...] Call Trace: ? sha256_blocks_avx2+0x37/0x70 ? __sha256_update+0x77/0x220 ? sha256+0x9d/0x110 [...] ? do_syscall_64+0x6f/0x3e0 With this fix to follow the normal %rbp convention, the call trace is generated reliably: RIP: 0010:sha256_transform_rorx+0xdae/0x1120 [...] Call Trace: sha256_blocks_avx2+0x37/0x70 __sha256_update+0x77/0x220 sha256+0x9d/0x110 [...] do_syscall_64+0x6f/0x3e0 Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20261011134843.166585-3-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/sha256-avx-asm.S | 10 +++++----- lib/crypto/x86/sha256-avx2-asm.S | 10 ++++------ lib/crypto/x86/sha256-ni-asm.S | 9 +++++---- lib/crypto/x86/sha256-ssse3-asm.S | 8 ++++---- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/crypto/x86/sha256-avx-asm.S b/lib/crypto/x86/sha256-avx-asm.S index c1aceb3ba3a3a6..efb8dff5e8a9ae 100644 --- a/lib/crypto/x86/sha256-avx-asm.S +++ b/lib/crypto/x86/sha256-avx-asm.S @@ -345,13 +345,13 @@ a = TMP_ ######################################################################## .text SYM_FUNC_START(sha256_transform_avx) + pushq %rbp + movq %rsp, %rbp pushq %rbx pushq %r12 pushq %r13 pushq %r14 pushq %r15 - pushq %rbp - movq %rsp, %rbp subq $STACK_SIZE, %rsp # allocate stack space and $~15, %rsp # align stack pointer @@ -445,13 +445,13 @@ SYM_FUNC_START(sha256_transform_avx) cmp _INP_END(%rsp), INP jne .Lloop0 - mov %rbp, %rsp - popq %rbp + lea -40(%rbp), %rsp popq %r15 popq %r14 popq %r13 - popq %r12 + popq %r12 popq %rbx + popq %rbp RET SYM_FUNC_END(sha256_transform_avx) diff --git a/lib/crypto/x86/sha256-avx2-asm.S b/lib/crypto/x86/sha256-avx2-asm.S index eb8836fb9695c1..a719c2173b61a8 100644 --- a/lib/crypto/x86/sha256-avx2-asm.S +++ b/lib/crypto/x86/sha256-avx2-asm.S @@ -522,15 +522,14 @@ STACK_SIZE = _CTX + _CTX_SIZE ######################################################################## .text SYM_FUNC_START(sha256_transform_rorx) + pushq %rbp + mov %rsp, %rbp pushq %rbx pushq %r12 pushq %r13 pushq %r14 pushq %r15 - push %rbp - mov %rsp, %rbp - subq $STACK_SIZE, %rsp and $-32, %rsp # align rsp to 32 byte boundary @@ -704,14 +703,13 @@ SYM_FUNC_START(sha256_transform_rorx) .Ldone_hash: - mov %rbp, %rsp - pop %rbp - + lea -40(%rbp), %rsp popq %r15 popq %r14 popq %r13 popq %r12 popq %rbx + popq %rbp vzeroupper RET SYM_FUNC_END(sha256_transform_rorx) diff --git a/lib/crypto/x86/sha256-ni-asm.S b/lib/crypto/x86/sha256-ni-asm.S index de5f707e7ef71b..e47263273f43c2 100644 --- a/lib/crypto/x86/sha256-ni-asm.S +++ b/lib/crypto/x86/sha256-ni-asm.S @@ -276,10 +276,11 @@ SYM_FUNC_END(sha256_ni_transform) // to taking better advantage of the CPU's SHA-256 and SIMD throughput. // SYM_FUNC_START(sha256_ni_finup2x) - // Allocate 128 bytes of stack space, 16-byte aligned. - push %rbx push %rbp mov %rsp, %rbp + push %rbx + + // Allocate 128 bytes of stack space, 16-byte aligned. sub $128, %rsp and $~15, %rsp @@ -527,9 +528,9 @@ SYM_FUNC_START(sha256_ni_finup2x) movdqu STATE1_A, 0*16(OUT1) movdqu STATE1_B, 0*16(OUT2) - mov %rbp, %rsp - pop %rbp + lea -8(%rbp), %rsp pop %rbx + pop %rbp RET SYM_FUNC_END(sha256_ni_finup2x) diff --git a/lib/crypto/x86/sha256-ssse3-asm.S b/lib/crypto/x86/sha256-ssse3-asm.S index 383b8eec7ebe90..cc91b3e15a83b8 100644 --- a/lib/crypto/x86/sha256-ssse3-asm.S +++ b/lib/crypto/x86/sha256-ssse3-asm.S @@ -352,13 +352,13 @@ a = TMP_ ######################################################################## .text SYM_FUNC_START(sha256_transform_ssse3) + pushq %rbp + mov %rsp, %rbp pushq %rbx pushq %r12 pushq %r13 pushq %r14 pushq %r15 - pushq %rbp - mov %rsp, %rbp subq $STACK_SIZE, %rsp and $~15, %rsp @@ -456,13 +456,13 @@ SYM_FUNC_START(sha256_transform_ssse3) cmp _INP_END(%rsp), INP jne .Lloop0 - mov %rbp, %rsp - popq %rbp + lea -40(%rbp), %rsp popq %r15 popq %r14 popq %r13 popq %r12 popq %rbx + popq %rbp RET SYM_FUNC_END(sha256_transform_ssse3) From 40f47fa10a1d19c74f48a13d97b2eb19d5fb938d Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 11 Oct 2026 15:48:43 +0200 Subject: [PATCH 1824/2286] lib/crypto: x86/sha512: Follow the normal %rbp convention The SHA-512 assembly functions push callee-save GPRs to the stack *before* they set up the frame pointer (%rbp). (They are leaf functions, but they need to set up a frame pointer anyway because they allocate a 32-byte-aligned buffer on the stack.) As a result, during these functions, 8(%rbp) doesn't contain a return address like it is supposed to. This differs from the convention used by C code, and it interferes with the frame pointer unwinder (CONFIG_UNWINDER_FRAME_POINTER=y). Specifically, if a call trace is dumped in these functions, then all entries are populated purely from the fallback scan of the entire stack: RIP: 0010:sha512_transform_rorx+0x673/0xdf0 [...] Call Trace: ? sha512_blocks_avx2+0x37/0x60 ? __sha512_update+0x80/0x230 ? sha512+0x31/0x190 ? sha512+0x114/0x190 [...] ? do_syscall_64+0x6f/0x3e0 With this fix to follow the normal %rbp convention, the call trace is generated reliably: RIP: 0010:sha512_transform_rorx+0x90c/0xdf0 [...] Call Trace: sha512_blocks_avx2+0x37/0x60 __sha512_update+0x80/0x230 sha512+0x114/0x190 [...] do_syscall_64+0x6f/0x3e0 Reviewed-by: Ard Biesheuvel Link: https://patch.msgid.link/20261011134843.166585-4-ebiggers@kernel.org Signed-off-by: Eric Biggers --- lib/crypto/x86/sha512-avx-asm.S | 11 ++++++----- lib/crypto/x86/sha512-avx2-asm.S | 11 ++++++----- lib/crypto/x86/sha512-ssse3-asm.S | 11 ++++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/crypto/x86/sha512-avx-asm.S b/lib/crypto/x86/sha512-avx-asm.S index 7732aa8fd85068..a17dfedc1c5196 100644 --- a/lib/crypto/x86/sha512-avx-asm.S +++ b/lib/crypto/x86/sha512-avx-asm.S @@ -276,6 +276,9 @@ frame_size = frame_WK + WK_SIZE ######################################################################## SYM_FUNC_START(sha512_transform_avx) + push %rbp + mov %rsp, %rbp + # Save GPRs push %rbx push %r12 @@ -284,8 +287,6 @@ SYM_FUNC_START(sha512_transform_avx) push %r15 # Allocate Stack Space - push %rbp - mov %rsp, %rbp sub $frame_size, %rsp and $~(0x20 - 1), %rsp @@ -348,9 +349,8 @@ SYM_FUNC_START(sha512_transform_avx) dec msglen jnz .Lupdateblock - # Restore Stack Pointer - mov %rbp, %rsp - pop %rbp + # Deallocate Stack Space + lea -40(%rbp), %rsp # Restore GPRs pop %r15 @@ -358,6 +358,7 @@ SYM_FUNC_START(sha512_transform_avx) pop %r13 pop %r12 pop %rbx + pop %rbp RET SYM_FUNC_END(sha512_transform_avx) diff --git a/lib/crypto/x86/sha512-avx2-asm.S b/lib/crypto/x86/sha512-avx2-asm.S index 22bdbfd899d0fa..2256d51297e18e 100644 --- a/lib/crypto/x86/sha512-avx2-asm.S +++ b/lib/crypto/x86/sha512-avx2-asm.S @@ -568,6 +568,9 @@ frame_size = frame_CTX + CTX_SIZE ######################################################################## SYM_FUNC_START(sha512_transform_rorx) + push %rbp + mov %rsp, %rbp + # Save GPRs push %rbx push %r12 @@ -576,8 +579,6 @@ SYM_FUNC_START(sha512_transform_rorx) push %r15 # Allocate Stack Space - push %rbp - mov %rsp, %rbp sub $frame_size, %rsp and $~(0x20 - 1), %rsp @@ -667,9 +668,8 @@ SYM_FUNC_START(sha512_transform_rorx) cmp frame_INPEND(%rsp), INP jne .Lloop0 - # Restore Stack Pointer - mov %rbp, %rsp - pop %rbp + # Deallocate Stack Space + lea -40(%rbp), %rsp # Restore GPRs pop %r15 @@ -677,6 +677,7 @@ SYM_FUNC_START(sha512_transform_rorx) pop %r13 pop %r12 pop %rbx + pop %rbp vzeroupper RET diff --git a/lib/crypto/x86/sha512-ssse3-asm.S b/lib/crypto/x86/sha512-ssse3-asm.S index 4cae7445b2a864..1a37b7a735cf94 100644 --- a/lib/crypto/x86/sha512-ssse3-asm.S +++ b/lib/crypto/x86/sha512-ssse3-asm.S @@ -275,6 +275,9 @@ frame_size = frame_WK + WK_SIZE ######################################################################## SYM_FUNC_START(sha512_transform_ssse3) + push %rbp + mov %rsp, %rbp + # Save GPRs push %rbx push %r12 @@ -283,8 +286,6 @@ SYM_FUNC_START(sha512_transform_ssse3) push %r15 # Allocate Stack Space - push %rbp - mov %rsp, %rbp sub $frame_size, %rsp and $~(0x20 - 1), %rsp @@ -347,9 +348,8 @@ SYM_FUNC_START(sha512_transform_ssse3) dec msglen jnz .Lupdateblock - # Restore Stack Pointer - mov %rbp, %rsp - pop %rbp + # Deallocate Stack Space + lea -40(%rbp), %rsp # Restore GPRs pop %r15 @@ -357,6 +357,7 @@ SYM_FUNC_START(sha512_transform_ssse3) pop %r13 pop %r12 pop %rbx + pop %rbp RET SYM_FUNC_END(sha512_transform_ssse3) From 50a2c5d4062a95146f71f974e1feb2ec5c786ccd Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:17 +0000 Subject: [PATCH 1825/2286] thermal/drivers/samsung: Add Exynos ACPM TMU driver GS101 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add driver for the Thermal Management Unit (TMU) managed via the Alive Clock and Power Manager (ACPM), found on Samsung Exynos SoCs such as the Google GS101. The TMU on the GS101 utilizes a hybrid management model shared between the Application Processor (AP) and the ACPM firmware. The driver maintains direct memory-mapped access to the TMU interrupt pending registers to identify thermal events, while delegating functional tasks - such as sensor initialization, threshold configuration, and temperature acquisition, to the ACPM firmware via the ACPM IPC protocol. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Acked-by: Peter Griffin Reviewed-by: Alexey Klimov Reviewed-by: André Draszik Link: https://patch.msgid.link/20260603-acpm-tmu-v8-2-0f1810a356e6@linaro.org --- drivers/thermal/samsung/Kconfig | 19 + drivers/thermal/samsung/Makefile | 2 + drivers/thermal/samsung/acpm-tmu.c | 651 +++++++++++++++++++++++++++++ 3 files changed, 672 insertions(+) create mode 100644 drivers/thermal/samsung/acpm-tmu.c diff --git a/drivers/thermal/samsung/Kconfig b/drivers/thermal/samsung/Kconfig index e1e8035d24fb22..f63eeae304b7b6 100644 --- a/drivers/thermal/samsung/Kconfig +++ b/drivers/thermal/samsung/Kconfig @@ -10,3 +10,22 @@ config EXYNOS_THERMAL the TMU, reports temperature and handles cooling action if defined. This driver uses the Exynos core thermal APIs and TMU configuration data from the supported SoCs. + +config EXYNOS_ACPM_THERMAL + tristate "Exynos ACPM thermal management unit driver" + depends on THERMAL_OF + depends on HAS_IOMEM + depends on EXYNOS_ACPM_PROTOCOL + default ARCH_EXYNOS + help + Support for the Thermal Management Unit (TMU) on Samsung Exynos SoCs + utilizing the ACPM IPC protocol, such as the Google GS101. + + The TMU on these platforms is managed through a hybrid architecture. + This driver handles direct register access for thermal interrupt status + monitoring and communicates with the Alive Clock and Power Manager + (ACPM) firmware via the ACPM IPC protocol for functional sensor control + and configuration. + + Select this if you want to monitor device temperature and enable + thermal mitigation on Samsung Exynos ACPM based devices. diff --git a/drivers/thermal/samsung/Makefile b/drivers/thermal/samsung/Makefile index f139407150d269..daed80647c349b 100644 --- a/drivers/thermal/samsung/Makefile +++ b/drivers/thermal/samsung/Makefile @@ -4,3 +4,5 @@ # obj-$(CONFIG_EXYNOS_THERMAL) += exynos_thermal.o exynos_thermal-y := exynos_tmu.o +obj-$(CONFIG_EXYNOS_ACPM_THERMAL) += exynos_acpm_thermal.o +exynos_acpm_thermal-y := acpm-tmu.o diff --git a/drivers/thermal/samsung/acpm-tmu.c b/drivers/thermal/samsung/acpm-tmu.c new file mode 100644 index 00000000000000..f9802080acd7cb --- /dev/null +++ b/drivers/thermal/samsung/acpm-tmu.c @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2019 Samsung Electronics Co., Ltd. + * Copyright 2025 Google LLC. + * Copyright 2026 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_hwmon.h" + +#define EXYNOS_TMU_SENSOR(i) BIT(i) +#define EXYNOS_TMU_SENSORS_MAX_COUNT 16 + +#define GS101_CPUCL2_SENSOR_MASK (EXYNOS_TMU_SENSOR(0) | \ + EXYNOS_TMU_SENSOR(6) | \ + EXYNOS_TMU_SENSOR(7) | \ + EXYNOS_TMU_SENSOR(8) | \ + EXYNOS_TMU_SENSOR(9)) +#define GS101_CPUCL1_SENSOR_MASK (EXYNOS_TMU_SENSOR(4) | \ + EXYNOS_TMU_SENSOR(5)) +#define GS101_CPUCL0_SENSOR_MASK (EXYNOS_TMU_SENSOR(1) | \ + EXYNOS_TMU_SENSOR(2)) + +#define GS101_REG_INTPEND(i) ((i) * 0x50 + 0xf8) + +enum { + P0_INTPEND, + P1_INTPEND, + P2_INTPEND, + P3_INTPEND, + P4_INTPEND, + P5_INTPEND, + P6_INTPEND, + P7_INTPEND, + P8_INTPEND, + P9_INTPEND, + P10_INTPEND, + P11_INTPEND, + P12_INTPEND, + P13_INTPEND, + P14_INTPEND, + P15_INTPEND, + REG_INTPEND_COUNT, +}; + +struct acpm_tmu_sensor_group { + u16 mask; + u8 id; +}; + +struct acpm_tmu_sensor { + const struct acpm_tmu_sensor_group *group; + struct thermal_zone_device *tzd; + struct acpm_tmu_priv *priv; + struct mutex lock; /* protects sensor state */ + bool enabled; +}; + +struct acpm_tmu_priv { + struct regmap_field *regmap_fields[REG_INTPEND_COUNT]; + struct acpm_handle *handle; + struct device *dev; + struct clk *clk; + unsigned int mbox_chan_id; + unsigned int num_sensors; + int irq; + struct acpm_tmu_sensor sensors[] __counted_by(num_sensors); +}; + +struct acpm_tmu_driver_data { + const struct reg_field *reg_fields; + const struct acpm_tmu_sensor_group *sensor_groups; + unsigned int num_sensor_groups; + unsigned int mbox_chan_id; +}; + +#define ACPM_TMU_SENSOR_GROUP(_mask, _id) \ + { \ + .mask = _mask, \ + .id = _id, \ + } + +static const struct acpm_tmu_sensor_group gs101_sensor_groups[] = { + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL2_SENSOR_MASK, 0), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL1_SENSOR_MASK, 1), + ACPM_TMU_SENSOR_GROUP(GS101_CPUCL0_SENSOR_MASK, 2), +}; + +static const struct reg_field gs101_reg_fields[REG_INTPEND_COUNT] = { + [P0_INTPEND] = REG_FIELD(GS101_REG_INTPEND(0), 0, 31), + [P1_INTPEND] = REG_FIELD(GS101_REG_INTPEND(1), 0, 31), + [P2_INTPEND] = REG_FIELD(GS101_REG_INTPEND(2), 0, 31), + [P3_INTPEND] = REG_FIELD(GS101_REG_INTPEND(3), 0, 31), + [P4_INTPEND] = REG_FIELD(GS101_REG_INTPEND(4), 0, 31), + [P5_INTPEND] = REG_FIELD(GS101_REG_INTPEND(5), 0, 31), + [P6_INTPEND] = REG_FIELD(GS101_REG_INTPEND(6), 0, 31), + [P7_INTPEND] = REG_FIELD(GS101_REG_INTPEND(7), 0, 31), + [P8_INTPEND] = REG_FIELD(GS101_REG_INTPEND(8), 0, 31), + [P9_INTPEND] = REG_FIELD(GS101_REG_INTPEND(9), 0, 31), + [P10_INTPEND] = REG_FIELD(GS101_REG_INTPEND(10), 0, 31), + [P11_INTPEND] = REG_FIELD(GS101_REG_INTPEND(11), 0, 31), + [P12_INTPEND] = REG_FIELD(GS101_REG_INTPEND(12), 0, 31), + [P13_INTPEND] = REG_FIELD(GS101_REG_INTPEND(13), 0, 31), + [P14_INTPEND] = REG_FIELD(GS101_REG_INTPEND(14), 0, 31), + [P15_INTPEND] = REG_FIELD(GS101_REG_INTPEND(15), 0, 31), +}; + +static const struct regmap_config gs101_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .use_relaxed_mmio = true, + .max_register = GS101_REG_INTPEND(15), +}; + +static const struct acpm_tmu_driver_data acpm_tmu_gs101 = { + .reg_fields = gs101_reg_fields, + .sensor_groups = gs101_sensor_groups, + .num_sensor_groups = ARRAY_SIZE(gs101_sensor_groups), + .mbox_chan_id = 9, +}; + +static int acpm_tmu_op_tz_control(struct acpm_tmu_sensor *sensor, bool on) +{ + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret; + + ret = ops->tz_control(handle, priv->mbox_chan_id, sensor->group->id, + on); + if (ret) + return ret; + + sensor->enabled = on; + + return 0; +} + +static void acpm_tmu_control_rollback(struct acpm_tmu_priv *priv, int start_idx) +{ + int i; + + for (i = start_idx; i >= 0; i--) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + int ret; + + if (!sensor->tzd) + continue; + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, false); + mutex_unlock(&sensor->lock); + if (ret) + dev_err(priv->dev, "Rollback: Failed to disable sensor %d: %d\n", + i, ret); + } +} + +static int acpm_tmu_control(struct acpm_tmu_priv *priv, bool on, bool rollback) +{ + struct device *dev = priv->dev; + int i, ret, err = 0; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + /* Skip sensors that weren't found in DT */ + if (!sensor->tzd) + continue; + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, on); + mutex_unlock(&sensor->lock); + if (ret) { + if (!err) + err = ret; + + /* On enable, stop on first error if rollback is requested */ + if (on && rollback) { + acpm_tmu_control_rollback(priv, i - 1); + break; + } + } + } + + pm_runtime_put_sync(dev); + + return err; +} + +static int acpm_tmu_get_temp(struct thermal_zone_device *tz, int *temp) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + struct device *dev = priv->dev; + int acpm_temp = 0, ret; + + /* + * Fast path: check locklessly if the sensor is enabled to avoid + * expensive runtime PM operations when it is disabled. Any race with + * concurrent disabling is caught by the second check under the lock + * after PM resume. + */ + if (!sensor->enabled) + return -EAGAIN; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + scoped_guard(mutex, &sensor->lock) { + if (!sensor->enabled) { + ret = -EAGAIN; + } else { + ret = ops->read_temp(handle, priv->mbox_chan_id, + sensor->group->id, &acpm_temp); + } + } + + pm_runtime_put_autosuspend(dev); + + if (ret) + return ret; + + *temp = acpm_temp * MILLIDEGREE_PER_DEGREE; + + return 0; +} + +static int acpm_tmu_update_thresholds(struct acpm_tmu_sensor *sensor, + u8 thresholds[2], u8 inten) +{ + struct acpm_tmu_priv *priv = sensor->priv; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + unsigned int mbox_chan_id = priv->mbox_chan_id; + u8 acpm_sensor_id = sensor->group->id; + bool was_enabled; + int ret, restore_ret; + + guard(mutex)(&sensor->lock); + + was_enabled = sensor->enabled; + + if (was_enabled) { + ret = acpm_tmu_op_tz_control(sensor, false); + if (ret) + return ret; + } + + ret = ops->set_threshold(handle, mbox_chan_id, acpm_sensor_id, + thresholds, 2); + if (!ret) { + ret = ops->set_interrupt_enable(handle, mbox_chan_id, + acpm_sensor_id, inten); + } + + if (was_enabled) { + restore_ret = acpm_tmu_op_tz_control(sensor, true); + if (restore_ret) + dev_err(priv->dev, "Failed to restore sensor state: %d\n", + restore_ret); + if (!ret) + ret = restore_ret; + } + + return ret; +} + +static int acpm_tmu_set_trips(struct thermal_zone_device *tz, int low, int high) +{ + struct acpm_tmu_sensor *sensor = thermal_zone_device_priv(tz); + struct acpm_tmu_priv *priv = sensor->priv; + struct device *dev = priv->dev; + u8 thresholds[2] = {}; + u8 inten = 0; + int ret; + + /* If a valid lower bound exists, set the threshold and enable its interrupt */ + if (low > -INT_MAX) { + thresholds[0] = clamp_val(low / MILLIDEGREE_PER_DEGREE, 0, 255); + inten |= BIT(0); + } + + /* If a valid upper bound exists, set the threshold and enable its interrupt */ + if (high < INT_MAX) { + thresholds[1] = clamp_val(high / MILLIDEGREE_PER_DEGREE, 0, 255); + inten |= BIT(1); + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + ret = acpm_tmu_update_thresholds(sensor, thresholds, inten); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +static const struct thermal_zone_device_ops acpm_tmu_sensor_ops = { + .get_temp = acpm_tmu_get_temp, + .set_trips = acpm_tmu_set_trips, +}; + +static int acpm_tmu_has_pending_irq(struct acpm_tmu_sensor *sensor, + bool *pending_irq) +{ + struct acpm_tmu_priv *priv = sensor->priv; + unsigned long mask = sensor->group->mask; + int i, ret; + u32 val; + + guard(mutex)(&sensor->lock); + + for_each_set_bit(i, &mask, EXYNOS_TMU_SENSORS_MAX_COUNT) { + ret = regmap_field_read(priv->regmap_fields[i], &val); + if (ret) + return ret; + + if (val) { + *pending_irq = true; + break; + } + } + + return 0; +} + +static irqreturn_t acpm_tmu_thread_fn(int irq, void *id) +{ + struct acpm_tmu_priv *priv = id; + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + struct device *dev = priv->dev; + bool handled = false; + int i, ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "Failed to resume: %d\n", ret); + return IRQ_NONE; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + bool pending_irq = false; + + if (!sensor->tzd) + continue; + + ret = acpm_tmu_has_pending_irq(sensor, &pending_irq); + if (ret || !pending_irq) + continue; + + handled = true; + + scoped_guard(mutex, &sensor->lock) { + ret = ops->clear_tz_irq(handle, priv->mbox_chan_id, + sensor->group->id); + if (ret) + dev_err(priv->dev, "Sensor %d: failed to clear IRQ (%d)\n", + i, ret); + } + + thermal_zone_device_update(sensor->tzd, + THERMAL_EVENT_UNSPECIFIED); + } + + pm_runtime_put_autosuspend(dev); + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static const struct of_device_id acpm_tmu_match[] = { + { .compatible = "google,gs101-tmu-top" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, acpm_tmu_match); + +static int acpm_tmu_probe(struct platform_device *pdev) +{ + const struct acpm_tmu_driver_data *data = &acpm_tmu_gs101; + struct acpm_handle *acpm_handle; + struct device *dev = &pdev->dev; + struct acpm_tmu_priv *priv; + struct regmap *regmap; + void __iomem *base; + int i, ret; + + acpm_handle = devm_acpm_get_by_phandle(dev); + if (IS_ERR(acpm_handle)) + return dev_err_probe(dev, PTR_ERR(acpm_handle), + "Failed to get ACPM handle\n"); + + priv = devm_kzalloc(dev, + struct_size(priv, sensors, data->num_sensor_groups), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->handle = acpm_handle; + priv->mbox_chan_id = data->mbox_chan_id; + priv->num_sensors = data->num_sensor_groups; + + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return dev_err_probe(dev, PTR_ERR(base), "Failed to ioremap resource\n"); + + regmap = devm_regmap_init_mmio(dev, base, &gs101_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to init regmap\n"); + + ret = devm_regmap_field_bulk_alloc(dev, regmap, priv->regmap_fields, + data->reg_fields, REG_INTPEND_COUNT); + if (ret) + return dev_err_probe(dev, ret, + "Unable to map syscon registers\n"); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return dev_err_probe(dev, PTR_ERR(priv->clk), + "Failed to get the clock\n"); + + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) + return dev_err_probe(dev, priv->irq, "Failed to get irq\n"); + + pm_runtime_set_autosuspend_delay(dev, 100); + pm_runtime_use_autosuspend(dev); + + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to resume device\n"); + + ret = acpm_handle->ops->tmu.init(acpm_handle, priv->mbox_chan_id); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to init TMU\n"); + goto err_pm_put; + } + + for (i = 0; i < priv->num_sensors; i++) { + struct acpm_tmu_sensor *sensor = &priv->sensors[i]; + + mutex_init(&sensor->lock); + sensor->group = &data->sensor_groups[i]; + sensor->priv = priv; + + sensor->tzd = devm_thermal_of_zone_register(dev, i, sensor, + &acpm_tmu_sensor_ops); + if (IS_ERR(sensor->tzd)) { + ret = PTR_ERR(sensor->tzd); + if (ret == -ENODEV) { + sensor->tzd = NULL; + dev_dbg(dev, "Sensor %d not used in DT, skipping\n", i); + continue; + } + + ret = dev_err_probe(dev, ret, "Failed to register sensor %d\n", i); + goto err_rollback; + } + + mutex_lock(&sensor->lock); + ret = acpm_tmu_op_tz_control(sensor, true); + mutex_unlock(&sensor->lock); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to enable sensor %d\n", i); + goto err_rollback; + } + + thermal_zone_device_update(sensor->tzd, + THERMAL_EVENT_UNSPECIFIED); + + ret = devm_thermal_add_hwmon_sysfs(dev, sensor->tzd); + if (ret) + dev_warn(dev, "Failed to add hwmon sysfs!\n"); + } + + ret = devm_request_threaded_irq(dev, priv->irq, NULL, + acpm_tmu_thread_fn, IRQF_ONESHOT, + dev_name(dev), priv); + if (ret) { + ret = dev_err_probe(dev, ret, "Failed to request irq\n"); + goto err_rollback; + } + + pm_runtime_put_autosuspend(dev); + + return 0; + +err_rollback: + acpm_tmu_control_rollback(priv, i - 1); +err_pm_put: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_put_sync(dev); + return ret; +} + +static void acpm_tmu_remove(struct platform_device *pdev) +{ + struct acpm_tmu_priv *priv = platform_get_drvdata(pdev); + + /* Stop IRQ first to prevent race with thread_fn */ + disable_irq(priv->irq); + + /* + * Disable autosuspend to force the subsequent pm_runtime_put_sync() + * inside acpm_tmu_control() to synchronously suspend the device + * immediately, preventing clock leaks when the driver is removed. + */ + pm_runtime_dont_use_autosuspend(&pdev->dev); + acpm_tmu_control(priv, false, false); +} + +static int acpm_tmu_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret, restore_ret; + + ret = acpm_tmu_control(priv, false, false); + if (ret) + goto err_restore_sensors; + + /* APB clock not required for this specific msg */ + ret = ops->suspend(handle, priv->mbox_chan_id); + if (ret) + goto err_restore_sensors; + + ret = pm_runtime_force_suspend(dev); + if (ret) + goto err_resume_acpm; + + return 0; + +err_resume_acpm: + restore_ret = ops->resume(handle, priv->mbox_chan_id); + if (restore_ret) + dev_err(dev, "Failed to resume ACPM after force suspend failure: %d\n", + restore_ret); + +err_restore_sensors: + restore_ret = acpm_tmu_control(priv, true, false); + if (restore_ret) + dev_err(dev, "Failed to re-enable TMU after suspend failure: %d\n", + restore_ret); + + return ret; +} + +static int acpm_tmu_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + struct acpm_handle *handle = priv->handle; + const struct acpm_tmu_ops *ops = &handle->ops->tmu; + int ret, restore_ret; + + /* APB clock not required for this specific msg */ + ret = ops->resume(handle, priv->mbox_chan_id); + if (ret) + return ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + goto err_suspend_acpm; + + ret = acpm_tmu_control(priv, true, true); + if (ret) + goto err_suspend_pm; + + return 0; + +err_suspend_pm: + restore_ret = pm_runtime_force_suspend(dev); + if (restore_ret) + dev_err(dev, "Failed to force suspend during resume rollback: %d\n", + restore_ret); +err_suspend_acpm: + restore_ret = ops->suspend(handle, priv->mbox_chan_id); + if (restore_ret) + dev_err(dev, "Failed to suspend ACPM during resume rollback: %d\n", + restore_ret); + return ret; +} + +static int acpm_tmu_runtime_suspend(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int acpm_tmu_runtime_resume(struct device *dev) +{ + struct acpm_tmu_priv *priv = dev_get_drvdata(dev); + + return clk_prepare_enable(priv->clk); +} + +static const struct dev_pm_ops acpm_tmu_pm_ops = { + SYSTEM_SLEEP_PM_OPS(acpm_tmu_suspend, acpm_tmu_resume) + RUNTIME_PM_OPS(acpm_tmu_runtime_suspend, acpm_tmu_runtime_resume, NULL) +}; + +static struct platform_driver acpm_tmu_driver = { + .driver = { + .name = "gs-tmu", + .pm = pm_ptr(&acpm_tmu_pm_ops), + .of_match_table = acpm_tmu_match, + }, + .probe = acpm_tmu_probe, + .remove = acpm_tmu_remove, +}; +module_platform_driver(acpm_tmu_driver); + +MODULE_AUTHOR("Tudor Ambarus "); +MODULE_DESCRIPTION("Samsung Exynos ACPM TMU Driver"); +MODULE_LICENSE("GPL"); From 05657c7822f2e8f21ccd6c11f2eb207896f5f87a Mon Sep 17 00:00:00 2001 From: Tudor Ambarus Date: Wed, 3 Jun 2026 13:00:18 +0000 Subject: [PATCH 1826/2286] MAINTAINERS: Add entry for Samsung Exynos ACPM thermal driver Add a MAINTAINERS entry for the Samsung Exynos ACPM thermal driver. Signed-off-by: Tudor Ambarus Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Reviewed-by: Peter Griffin Link: https://patch.msgid.link/20260603-acpm-tmu-v8-3-0f1810a356e6@linaro.org --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3a19da74d00c9d..767bb3e862d123 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24298,6 +24298,14 @@ F: drivers/clk/samsung/clk-acpm.c F: drivers/firmware/samsung/exynos-acpm* F: include/linux/firmware/samsung/exynos-acpm-protocol.h +SAMSUNG EXYNOS ACPM THERMAL DRIVER +M: Tudor Ambarus +L: linux-kernel@vger.kernel.org +L: linux-samsung-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/thermal/google,gs101-tmu-top.yaml +F: drivers/thermal/samsung/acpm-tmu.c + SAMSUNG EXYNOS MAILBOX DRIVER M: Tudor Ambarus L: linux-samsung-soc@vger.kernel.org From f4428025c8b54d8a6e0fb15589e08b097b7b9a76 Mon Sep 17 00:00:00 2001 From: Liu Jing Date: Thu, 3 Sep 2026 16:28:58 +0800 Subject: [PATCH 1827/2286] tools/thermal/tmon: Fix signed shift UB in binding display In the thermal zone binding display loop, the expression `(1 << j)` performs a signed integer shift. When j reaches 31, this sets the sign bit of a 32-bit signed integer, which is undefined behavior. Fix it by using `(1U << j)` to perform an unsigned shift. Signed-off-by: Liu Jing Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260903082858.5273-1-liujing@cmss.chinamobile.com --- tools/thermal/tmon/tmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c index 7eb3216a27f45f..ef67cd1a48619b 100644 --- a/tools/thermal/tmon/tmon.c +++ b/tools/thermal/tmon/tmon.c @@ -171,7 +171,7 @@ static void prepare_logging(void) memset(binding_str, 0, sizeof(binding_str)); for (j = 0; j < 32; j++) - binding_str[j] = (ptdata.tzi[i].cdev_binding & (1 << j)) ? + binding_str[j] = (ptdata.tzi[i].cdev_binding & (1U << j)) ? '1' : '0'; fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n", From 481860eba83b3fe27ca9be6f7f5bb4c8febc6572 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Wed, 10 Jun 2026 11:20:37 +0200 Subject: [PATCH 1828/2286] thermal: gov_power_allocator: Use common error handling code in power_allocator_bind() Adjust labels so that a bit of exception handling can be better reused at the end of this function implementation. This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: Daniel Lezcano Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/a3dc82f0-7333-465d-85b4-8034b1591442@web.de --- drivers/thermal/gov_power_allocator.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/gov_power_allocator.c b/drivers/thermal/gov_power_allocator.c index 37f2e22a999e64..6b9450ffd5de3a 100644 --- a/drivers/thermal/gov_power_allocator.c +++ b/drivers/thermal/gov_power_allocator.c @@ -707,22 +707,20 @@ static int power_allocator_bind(struct thermal_zone_device *tz) ret = check_power_actors(tz, params); if (ret < 0) { dev_warn(&tz->device, "power_allocator: binding failed\n"); - kfree(params); - return ret; + goto free_params; } ret = allocate_actors_buffer(params, ret); if (ret) { dev_warn(&tz->device, "power_allocator: allocation failed\n"); - kfree(params); - return ret; + goto free_params; } if (!tz->tzp) { tz->tzp = kzalloc_obj(*tz->tzp); if (!tz->tzp) { ret = -ENOMEM; - goto free_params; + goto free_power; } params->allocated_tzp = true; @@ -746,8 +744,9 @@ static int power_allocator_bind(struct thermal_zone_device *tz) return 0; -free_params: +free_power: kfree(params->power); +free_params: kfree(params); return ret; From 51c262cf2aa9dfd97591e570d4e82af493ad1689 Mon Sep 17 00:00:00 2001 From: Georgi Djakov Date: Mon, 28 Sep 2026 16:28:23 +0300 Subject: [PATCH 1829/2286] thermal/drivers/qcom-spmi-temp-alarm: Fix temp_map indexing Fix the following issue noticed when booting on the db845c-dragonboard: BUG: KASAN: global-out-of-bounds in qpnp_tm_sync_thresholds+0x11c/0x180 [qcom_spmi_temp_alarm] Read of size 24 at addr ffffdd2495f01200 by task (udev-worker)/371 CPU: 3 UID: 0 PID: 371 Comm: (udev-worker) Not tainted 7.3.0-rc3 #116 PREEMPT Hardware name: Thundercomm Dragonboard 845c (DT) Call trace: show_stack+0x18/0x24 (C) dump_stack_lvl+0xa4/0xf4 print_report+0x1f4/0x5ac kasan_report+0x84/0xcc kasan_check_range+0xf0/0x1a4 memcpy+0x3c/0xa4 qpnp_tm_sync_thresholds+0x11c/0x180 [qcom_spmi_temp_alarm] qpnp_tm_probe+0x3dc/0xae8 [qcom_spmi_temp_alarm] platform_probe+0xc4/0x18c really_probe+0x17c/0x4fc ... The buggy address belongs to the variable: temp_map_gen1+0x60/0xc4 [qcom_spmi_temp_alarm] It looks like temp_map is a pointer to the complete temperature map. Indexing it directly advances the entire map, rather than selecting a threshold row. Fix this by dereferencing the temp_map before indexing it to select the requested threshold row. Fixes: 703f13285a6c ("thermal/drivers/qcom-spmi-temp-alarm: Add temp alarm data struct based on HW subtype") Fixes: 1f835c6a4c84 ("thermal/drivers/qcom-spmi-temp-alarm: Prepare to support additional Temp Alarm subtypes") Signed-off-by: Georgi Djakov Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260928132823.1426513-1-djakov@kernel.org --- drivers/thermal/qcom/qcom-spmi-temp-alarm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c index fb003ca9645466..911ebfd0cdeb65 100644 --- a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c +++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c @@ -345,8 +345,8 @@ static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip, } skip: - memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold], - sizeof(chip->temp_thresh_map)); + memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold], + sizeof(chip->temp_thresh_map)); reg |= threshold; if (disable_stage2_shutdown && !chip->require_stage2_shutdown) reg |= SHUTDOWN_CTRL1_OVERRIDE_STAGE2; @@ -535,8 +535,8 @@ static int qpnp_tm_sync_thresholds(struct qpnp_tm_chip *chip) return ret; threshold = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK; - memcpy(chip->temp_thresh_map, chip->data->temp_map[threshold], - sizeof(chip->temp_thresh_map)); + memcpy(chip->temp_thresh_map, (*chip->data->temp_map)[threshold], + sizeof(chip->temp_thresh_map)); return ret; } From aaa6c8dec49339710f9fd2a2af5646cf4111dbd4 Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 26 Sep 2026 22:49:31 +1000 Subject: [PATCH 1830/2286] thermal/drivers/ti-soc-thermal: Cancel pending alert work on remove On a threshold crossing, the talert IRQ handler calls ti_thermal_report_sensor_temperature(), which queues the work embedded in the sensor's ti_thermal_data on the system workqueue. Nothing ever cancels or flushes that work. ti_bandgap_remove() frees the talert IRQ last. free_irq() waits for a running handler, but not for the work the handler has queued. When remove returns, devres unregisters and frees the thermal zones and then frees the devm-allocated ti_thermal_data, so a work item that is still pending runs ti_thermal_work() on freed memory: ti_thermal_work() data = container_of(work, struct ti_thermal_data, thermal_wq); thermal_zone_device_update(data->ti_thermal, ...); Free the talert IRQ before removing the sensors, so that no new work can be queued, and cancel the work in ti_thermal_remove_sensor(). This needs an OMAP4460/4470 or OMAP5 SoC (DRA7 has TALERT but no ->report_temperature, so it never queues the work), a threshold crossing just before the driver is unbound or unloaded, and the work still pending when devres frees the data. Found by static analysis tool CodeQL. Fixes: 445eaf871bf9 ("staging: omap-thermal: common code to expose driver to thermal framework") Assisted-by: LLM codeql Signed-off-by: Weigang He Signed-off-by: Daniel Lezcano Acked-by: Keerthy Link: https://patch.msgid.link/20260926124931.3599746-1-geoffreyhe2@gmail.com --- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 7 ++++--- drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index ba43399d0b384c..507fe67eaffae4 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -1077,6 +1077,10 @@ void ti_bandgap_remove(struct platform_device *pdev) if (!soc_device_match(soc_no_cpu_notifier)) cpu_pm_unregister_notifier(&bgp->nb); + /* Stop the alerts first: they queue work on the sensors' data */ + if (TI_BANDGAP_HAS(bgp, TALERT)) + free_irq(bgp->irq, bgp); + /* Remove sensor interfaces */ for (i = 0; i < bgp->conf->sensor_count; i++) { if (bgp->conf->sensors[i].unregister_cooling) @@ -1093,9 +1097,6 @@ void ti_bandgap_remove(struct platform_device *pdev) clk_put(bgp->fclock); clk_put(bgp->div_clk); - if (TI_BANDGAP_HAS(bgp, TALERT)) - free_irq(bgp->irq, bgp); - if (TI_BANDGAP_HAS(bgp, TSHUT)) free_irq(gpiod_to_irq(bgp->tshut_gpiod), NULL); } diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c index 0cf0826b805a99..4bf3b2e0fc6821 100644 --- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c +++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c @@ -195,6 +195,10 @@ int ti_thermal_remove_sensor(struct ti_bandgap *bgp, int id) data = ti_bandgap_get_sensor_data(bgp, id); + /* Work queued by the talert IRQ must not outlive the data */ + if (!IS_ERR_OR_NULL(data)) + cancel_work_sync(&data->thermal_wq); + if (!IS_ERR_OR_NULL(data) && data->ti_thermal) { if (data->our_zone) thermal_zone_device_unregister(data->ti_thermal); From f9a2ad0b98d916debd35843735cc4ac81d48bf48 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 28 Sep 2026 11:11:07 +0200 Subject: [PATCH 1831/2286] iomap: use bio_complete_in_task for buffered read failures Use bio_complete_in_task to defer the bio completion handler to task context instead of the homegrown deferral. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260928091111.3986811-2-hch@lst.de Reviewed-by: "Darrick J. Wong" Reviewed-by: Andrey Albershteyn Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/bio.c | 46 ++-------------------------------------------- 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c index d46c2f8ea18ce0..153b779949e968 100644 --- a/fs/iomap/bio.c +++ b/fs/iomap/bio.c @@ -9,9 +9,6 @@ #include "internal.h" #include "trace.h" -static DEFINE_SPINLOCK(failed_read_lock); -static struct bio_list failed_read_list = BIO_EMPTY_LIST; - static u32 __iomap_read_end_io(struct bio *bio, int error) { struct folio_iter fi; @@ -27,50 +24,11 @@ static u32 __iomap_read_end_io(struct bio *bio, int error) return folio_count; } -static void -iomap_fail_reads( - struct work_struct *work) -{ - struct bio *bio; - struct bio_list tmp = BIO_EMPTY_LIST; - unsigned long flags; - - spin_lock_irqsave(&failed_read_lock, flags); - bio_list_merge_init(&tmp, &failed_read_list); - spin_unlock_irqrestore(&failed_read_lock, flags); - - while ((bio = bio_list_pop(&tmp)) != NULL) { - __iomap_read_end_io(bio, blk_status_to_errno(bio->bi_status)); - cond_resched(); - } -} - -static DECLARE_WORK(failed_read_work, iomap_fail_reads); - -static void iomap_fail_buffered_read(struct bio *bio) -{ - unsigned long flags; - - /* - * Bounce I/O errors to a workqueue to avoid nested i_lock acquisitions - * in the fserror code. The caller no longer owns the bio reference - * after the spinlock drops. - */ - spin_lock_irqsave(&failed_read_lock, flags); - if (bio_list_empty(&failed_read_list)) - WARN_ON_ONCE(!schedule_work(&failed_read_work)); - bio_list_add(&failed_read_list, bio); - spin_unlock_irqrestore(&failed_read_lock, flags); -} - static void iomap_read_end_io(struct bio *bio) { - if (bio->bi_status) { - iomap_fail_buffered_read(bio); + if (bio->bi_status && bio_complete_in_task(bio)) return; - } - - __iomap_read_end_io(bio, 0); + __iomap_read_end_io(bio, blk_status_to_errno(bio->bi_status)); } u32 iomap_finish_ioend_buffered_read(struct iomap_ioend *ioend) From f7abb3e3528bf181c6573c4f8c3dc465bccc8703 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 28 Sep 2026 11:11:08 +0200 Subject: [PATCH 1832/2286] iomap: use bio_complete_in_task for buffered write failures Use bio_complete_in_task to defer the bio completion handler to task context instead of the homegrown deferral. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260928091111.3986811-3-hch@lst.de Reviewed-by: "Darrick J. Wong" Reviewed-by: Andrey Albershteyn Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/ioend.c | 48 ++---------------------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index bbebecc3167083..46446518dbff07 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -75,57 +75,13 @@ static u32 iomap_finish_ioend_buffered_write(struct iomap_ioend *ioend) return folio_count; } -static DEFINE_SPINLOCK(failed_ioend_lock); -static LIST_HEAD(failed_ioend_list); - -static void -iomap_fail_ioends( - struct work_struct *work) -{ - struct iomap_ioend *ioend; - struct list_head tmp; - unsigned long flags; - - spin_lock_irqsave(&failed_ioend_lock, flags); - list_replace_init(&failed_ioend_list, &tmp); - spin_unlock_irqrestore(&failed_ioend_lock, flags); - - while ((ioend = list_first_entry_or_null(&tmp, struct iomap_ioend, - io_list))) { - list_del_init(&ioend->io_list); - iomap_finish_ioend_buffered_write(ioend); - cond_resched(); - } -} - -static DECLARE_WORK(failed_ioend_work, iomap_fail_ioends); - -static void iomap_fail_ioend_buffered(struct iomap_ioend *ioend) -{ - unsigned long flags; - - /* - * Bounce I/O errors to a workqueue to avoid nested i_lock acquisitions - * in the fserror code. The caller no longer owns the ioend reference - * after the spinlock drops. - */ - spin_lock_irqsave(&failed_ioend_lock, flags); - if (list_empty(&failed_ioend_list)) - WARN_ON_ONCE(!schedule_work(&failed_ioend_work)); - list_add_tail(&ioend->io_list, &failed_ioend_list); - spin_unlock_irqrestore(&failed_ioend_lock, flags); -} - static void ioend_writeback_end_bio(struct bio *bio) { struct iomap_ioend *ioend = iomap_ioend_from_bio(bio); - ioend->io_error = blk_status_to_errno(bio->bi_status); - if (ioend->io_error) { - iomap_fail_ioend_buffered(ioend); + if (bio->bi_status && bio_complete_in_task(bio)) return; - } - + ioend->io_error = blk_status_to_errno(bio->bi_status); iomap_finish_ioend_buffered_write(ioend); } From bcb0d867a601eb7e4b3164eb0cfa26ad273125a0 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 28 Sep 2026 11:11:09 +0200 Subject: [PATCH 1833/2286] iomap: use bio_complete_in_task for buffered simple dio read failures Use bio_complete_in_task to defer the bio completion handler to task context instead of the homegrown deferral. Signed-off-by: Christoph Hellwig Link: https://patch.msgid.link/20260928091111.3986811-4-hch@lst.de Reviewed-by: "Darrick J. Wong" Reviewed-by: Andrey Albershteyn Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/direct-io.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index a431ceda9ebdbd..0e8d7ddf4e5110 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -919,7 +919,6 @@ struct iomap_dio_simple { struct kiocb *iocb; size_t size; unsigned int dio_flags; - struct work_struct work; /* * Align @bio to a cacheline boundary so that, combined with the * front_pad passed to bioset_init(), the bio sits at the start of @@ -961,30 +960,14 @@ static ssize_t iomap_dio_simple_complete(struct iomap_dio_simple *sr) return ret; } -static void iomap_dio_simple_complete_work(struct work_struct *work) -{ - struct iomap_dio_simple *sr = - container_of(work, struct iomap_dio_simple, work); - struct kiocb *iocb = sr->iocb; - - WRITE_ONCE(iocb->private, NULL); - iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); -} - static void iomap_dio_simple_end_io(struct bio *bio) { struct iomap_dio_simple *sr = container_of(bio, struct iomap_dio_simple, bio); struct kiocb *iocb = sr->iocb; - if (unlikely(sr->bio.bi_status)) { - struct inode *inode = file_inode(iocb->ki_filp); - - INIT_WORK(&sr->work, iomap_dio_simple_complete_work); - queue_work(inode->i_sb->s_dio_done_wq, &sr->work); + if (unlikely(bio->bi_status) && bio_complete_in_task(bio)) return; - } - WRITE_ONCE(iocb->private, NULL); iocb->ki_complete(iocb, iomap_dio_simple_complete(sr)); } From 3f517370076f1fedaa38cd8c78ceb73616b79336 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Mon, 28 Sep 2026 10:48:06 +0200 Subject: [PATCH 1834/2286] iomap: don't resubmit an ioend after ->writeback_submit() failed iomap_add_to_ioend() submits the pending ioend through ->writeback_submit() before allocating a new one for the current range. When the submission fails the helper completes the ioend with an error, but iomap_add_to_ioend() returns the error without clearing wpc->wb_ctx. iomap_writepages() then submits whatever wpc->wb_ctx points to, so the already completed ioend is submitted a second time. For XFS the second bio_endio() lands in xfs_end_bio(), which list_add_tail()s the already linked ioend into ip->i_ioend_list. This corrupts the list and leaves a use-after-free/double-free window against the ioend completion worker. Reproduced with a fault-injected ->writeback_submit() failure on a reflinked XFS file with several CoW writeback ranges in flight: the unfixed kernel hits repeated "list_add double add" warnings from __list_add_valid_or_report(), the fixed kernel fails writeback cleanly. Clear wpc->wb_ctx when ->writeback_submit() fails. The old iomap_submit_ioend() cleared the context unconditionally; that clear was lost when submission moved to iomap_ioend_writeback_submit(). The final ->writeback_submit() call in iomap_writepages() needs no equivalent fix: it is the last thing the function does before returning, and every caller allocates its iomap_writepage_ctx on the stack for a single call, so wpc->wb_ctx is never read again afterwards. Fixes: f4fa7981fa26 ("iomap: hide ioends from the generic writeback code") Cc: stable@vger.kernel.org # v6.17 Reviewed-by: Brian Foster Reviewed-by: Christoph Hellwig Signed-off-by: Andrea Parri Link: https://patch.msgid.link/90fbd42df4f51306e57a95830d2d53873f630eb9.1790342457.git.parri.andrea@gmail.com Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/ioend.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 46446518dbff07..92b89fbf95b98c 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -199,8 +199,16 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio, new_ioend: if (ioend) { error = wpc->ops->writeback_submit(wpc, 0); - if (error) + if (error) { + /* + * ->writeback_submit() completed the ioend + * with an error, so drop the stale context; + * iomap_writepages() would otherwise submit + * it a second time. + */ + wpc->wb_ctx = NULL; return error; + } } wpc->wb_ctx = ioend = iomap_alloc_ioend(wpc, pos, ioend_flags); } From 1c3fe56094dd23b3446b93247172980037229dc9 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Mon, 28 Sep 2026 10:48:07 +0200 Subject: [PATCH 1835/2286] xfs: add an error tag to inject a ->writeback_submit() failure The only in-tree way for xfs_writeback_submit() to fail is a failing xfs_reflink_convert_cow(), which requires a shared (reflinked) CoW extent. There was previously no way to exercise that failure path from userspace, which made it hard to write an xfstest for it, e.g. for "iomap: don't resubmit an ioend after ->writeback_submit() failed". Add XFS_ERRTAG_WB_COW_CONVERT_ERROR to force xfs_reflink_convert_cow() to fail with -EIO, so that ->writeback_submit() failure and its callers' error handling can be exercised with error injection alone, without needing a corrupted or racy COW fork. Suggested-by: Christoph Hellwig Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrea Parri Link: https://patch.msgid.link/6c05aee81af9264ce6c19a436bc3503ecdab1e45.1790342457.git.parri.andrea@gmail.com Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- fs/xfs/libxfs/xfs_errortag.h | 6 ++++-- fs/xfs/xfs_reflink.c | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h index 2dc441da033344..ac5abee05d80e5 100644 --- a/fs/xfs/libxfs/xfs_errortag.h +++ b/fs/xfs/libxfs/xfs_errortag.h @@ -76,7 +76,8 @@ #define XFS_ERRTAG_FORCE_ZERO_RANGE 46 #define XFS_ERRTAG_ZONE_RESET 47 #define XFS_ERRTAG_BOUNCE_REREAD 48 -#define XFS_ERRTAG_MAX 49 +#define XFS_ERRTAG_WB_COW_CONVERT_ERROR 49 +#define XFS_ERRTAG_MAX 50 /* * Random factors for above tags, 1 means always, 2 means 1/2 time, etc. @@ -139,7 +140,8 @@ XFS_ERRTAG(EXCHMAPS_FINISH_ONE, exchmaps_finish_one, 1) \ XFS_ERRTAG(METAFILE_RESV_CRITICAL, metafile_resv_crit, 4) \ XFS_ERRTAG(FORCE_ZERO_RANGE, force_zero_range, 4) \ XFS_ERRTAG(ZONE_RESET, zone_reset, 1) \ -XFS_ERRTAG(BOUNCE_REREAD, bounce_reread, XFS_RANDOM_DEFAULT) +XFS_ERRTAG(BOUNCE_REREAD, bounce_reread, XFS_RANDOM_DEFAULT) \ +XFS_ERRTAG(WB_COW_CONVERT_ERROR, wb_cow_convert_error, 1) #endif /* XFS_ERRTAG */ #endif /* __XFS_ERRORTAG_H_ */ diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index 4801361366359b..104542b10d9e02 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -34,6 +34,8 @@ #include "xfs_rtalloc.h" #include "xfs_rtgroup.h" #include "xfs_metafile.h" +#include "xfs_errortag.h" +#include "xfs_error.h" /* * Copy on Write of Shared Blocks @@ -346,6 +348,9 @@ xfs_reflink_convert_cow( ASSERT(count != 0); + if (XFS_TEST_ERROR(mp, XFS_ERRTAG_WB_COW_CONVERT_ERROR)) + return -EIO; + xfs_ilock(ip, XFS_ILOCK_EXCL); error = xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); xfs_iunlock(ip, XFS_ILOCK_EXCL); From 0d0104adc2c8316433b660cdf6d182a09881864f Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Mon, 28 Sep 2026 10:48:08 +0200 Subject: [PATCH 1836/2286] iomap: don't lose a fiemap iteration error when emitting the last extent iomap_fiemap() emits extents one behind: iomap_fiemap_iter() flushes the previous extent and remembers the current one, and the remembered extent is written with FIEMAP_EXTENT_LAST after the iteration loop. That final flush overwrites ret, so when ->iomap_begin() fails partway through the iteration the error is replaced by the result of iomap_to_fiemap() (zero on success) and iomap_fiemap() returns success with a truncated extent list whose last entry is wrongly marked as the last extent in the file. The pre-iomap_iter code returned the error from inside the loop, before flushing the pending extent. Check for the iteration error before flushing the pending extent, so that real errors are propagated and only a successful iteration emits the final FIEMAP_EXTENT_LAST extent. -ENOENT (no mapping) is still not an error, and the pending extent is still emitted in that case. Fixes: 7892386d3571 ("iomap: switch iomap_fiemap to use iomap_iter") Cc: stable@vger.kernel.org # v5.15 Reviewed-by: Brian Foster Reviewed-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Andrea Parri Link: https://patch.msgid.link/250226e33b3b23c6b1714a45dc3c465c2a45cdc0.1790342457.git.parri.andrea@gmail.com Reviewed-by: "Darrick J. Wong" Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/fiemap.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/iomap/fiemap.c b/fs/iomap/fiemap.c index d11dadff828653..54b824b7edb5c6 100644 --- a/fs/iomap/fiemap.c +++ b/fs/iomap/fiemap.c @@ -76,15 +76,15 @@ int iomap_fiemap(struct inode *inode, struct fiemap_extent_info *fi, while ((ret = iomap_iter(&iter, ops)) > 0) iter.status = iomap_fiemap_iter(&iter, fi, &prev); + /* inode with no (attribute) mapping will give ENOENT */ + if (ret < 0 && ret != -ENOENT) + return ret; + if (prev.type != IOMAP_HOLE) { ret = iomap_to_fiemap(fi, &prev, FIEMAP_EXTENT_LAST); if (ret < 0) return ret; } - - /* inode with no (attribute) mapping will give ENOENT */ - if (ret < 0 && ret != -ENOENT) - return ret; return 0; } EXPORT_SYMBOL_GPL(iomap_fiemap); From a9fd24bcbe690ed4a5b8ea6a7516fa297fae9360 Mon Sep 17 00:00:00 2001 From: Andrea Parri Date: Mon, 28 Sep 2026 10:48:09 +0200 Subject: [PATCH 1837/2286] iomap: don't lose a failed direct I/O bio's error when zeroing the tail iomap_dio_bio_iter() falls through to the sub-block tail zeroing when the data bio submission fails, so that the rest of the block is still zeroed and stale data is not exposed. The zeroing result was assigned to ret, which overwrote the submission error with the successful zeroing result (zero) and the failed write was reported as success. iomap_dio_zero() can only return an error from a can't-happen WARN_ON_ONCE() (nr_vecs exceeding BIO_MAX_VECS, which the existing comment there says "shall never be reached" for any in-tree filesystem), so it isn't a real runtime failure worth reporting to userspace, let alone one worth losing the actual submission error for. Make iomap_dio_zero() return void and drop the error handling at both call sites instead of threading the result through a separate variable. Fixes: 10553a91652d ("iomap: fix iomap_dio_zero() for fs bs > system page size") Cc: stable@vger.kernel.org # v6.12 Reviewed-by: Darrick J. Wong Reviewed-by: Pankaj Raghav Signed-off-by: Andrea Parri Link: https://patch.msgid.link/0cbce1012f5333690be36a9adafb3e190fe2ca77.1790342457.git.parri.andrea@gmail.com Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/direct-io.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index 0e8d7ddf4e5110..b54540391cdece 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -304,7 +304,7 @@ u32 iomap_finish_ioend_direct(struct iomap_ioend *ioend) return vec_count; } -static int iomap_dio_zero(const struct iomap_iter *iter, struct iomap_dio *dio, +static void iomap_dio_zero(const struct iomap_iter *iter, struct iomap_dio *dio, loff_t pos, unsigned len) { struct inode *inode = file_inode(dio->iocb->ki_filp); @@ -313,14 +313,14 @@ static int iomap_dio_zero(const struct iomap_iter *iter, struct iomap_dio *dio, int nr_vecs = max(1, i_blocksize(inode) / folio_size(zero_folio)); if (!len) - return 0; + return; /* * This limit shall never be reached as most filesystems have a * maximum blocksize of 64k. */ if (WARN_ON_ONCE(nr_vecs > BIO_MAX_VECS)) - return -EINVAL; + return; bio = iomap_dio_alloc_bio(iter, dio, nr_vecs, REQ_OP_WRITE | REQ_SYNC | REQ_IDLE); @@ -336,8 +336,6 @@ static int iomap_dio_zero(const struct iomap_iter *iter, struct iomap_dio *dio, len -= io_len; } iomap_dio_submit_bio(iter, dio, bio, pos); - - return 0; } static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, @@ -541,10 +539,7 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) if (need_zeroout) { /* zero out from the start of the block to the write offset */ pad = pos & (fs_block_size - 1); - - ret = iomap_dio_zero(iter, dio, pos - pad, pad); - if (ret) - goto out; + iomap_dio_zero(iter, dio, pos - pad, pad); } do { @@ -582,8 +577,7 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) /* zero out from the end of the write to the end of the block */ pad = pos & (fs_block_size - 1); if (pad) - ret = iomap_dio_zero(iter, dio, pos, - fs_block_size - pad); + iomap_dio_zero(iter, dio, pos, fs_block_size - pad); } out: /* Undo iter limitation to current extent */ From 4025185cf953e3f52ab63cf5fec50c1485ee7bb7 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Wed, 5 Aug 2026 07:29:51 +0930 Subject: [PATCH 1838/2286] iomap: follow the alignment requirement for iomap_dio_hole_iter() [BUG] On the latest development branch, btrfs with 8K block size on 4K page sized systems will fail all test cases that run fsstress. One very short example would be: # $fsstress -n 4 -d $mnt -s 1785675805 -v 0/0: dwrite - no filename 0/1: creat f0 x:0 0 0 0/1: creat add id=0,parent=-1 0/2: write dontcache f0[259 1 0 0 0 0] [816411,3620] 0 0/3: dread - xfsctl(XFS_IOC_DIOINFO) f0[259 1 0 0 32 820031] return 25, fallback to stat() 0/3: dread f0[259 1 0 0 32 820031] [483328,81920] 0 Which triggered the following ASSERT(): assertion failed: IS_ALIGNED(state->start, blocksize) && IS_ALIGNED(state->end + 1, blocksize), in fs/btrfs/extent-io-tree.c:346 (unaligned extent state, blocksize=8192 start=487424 end=565247 state=0x0) ------------[ cut here ]------------ kernel BUG at fs/btrfs/extent-io-tree.c:346! Oops: invalid opcode: 0000 [#1] SMP CPU: 4 UID: 0 PID: 648 Comm: fsstress Tainted: G E 7.2.0-rc5-custom+ #431 PREEMPT(full) 7c507bd40d65e4d871e698b22d80f1306bbec543 Tainted: [E]=UNSIGNED_MODULE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:validate_extent_state.part.0.isra.0.cold+0x24/0x26 [btrfs] Call Trace: insert_state+0x34/0x1a0 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] set_extent_bit+0x440/0x8d0 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] btrfs_lock_extent_bits+0x58/0x380 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] btrfs_dio_iomap_begin+0x319/0xb70 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] iomap_iter+0x1a2/0x370 __iomap_dio_rw+0x236/0x8d0 iomap_dio_rw+0x12/0x30 btrfs_direct_read+0x15f/0x290 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] btrfs_file_read_iter+0x42/0x90 [btrfs 6924944583e4bc91a7c5183a1e7908e745a9b0c8] vfs_read+0x25e/0x380 ksys_read+0x73/0xe0 do_syscall_64+0xe1/0x790 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x7fc3f129318e [CAUSE] iomap_dio_hole_iter() is responsible for zeroing out the buffer for a hole, which calls iov_iter_zero() to zero the range. However btrfs disables page fault during its __iomap_dio_rw() call, so iov_iter_zero() can fail at any page boundary. When the fs block size is larger than page size, iov_iter_zero() may only have zeroed one page, which is not aligned to the fs block size. Such one page long range is passed back to btrfs, which triggers the above ASSERT(). [FIX] For iomap_dio_hole_iter() round down the copied length, and revert any excessive range that is beyond the aligned copied length. Also use @aligned_copied for the size increment and iter advancement. This should only affect btrfs, which is the only fs utilizing iomap dio with page fault disabled. Fixes: 001397f5ef49 ("iomap: add IOMAP_DIO_FSBLOCK_ALIGNED flag") Signed-off-by: Qu Wenruo Link: https://patch.msgid.link/73bd4d9b39e821453028ca8e567eaad2ebe0fc18.1785880740.git.wqu@suse.com Reviewed-by: Christoph Hellwig Reviewed-by: Pankaj Raghav Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/direct-io.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index b54540391cdece..45bb24934f3d2d 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -589,12 +589,19 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio) static int iomap_dio_hole_iter(struct iomap_iter *iter, struct iomap_dio *dio) { - loff_t length = iov_iter_zero(iomap_length(iter), dio->submit.iter); + loff_t copied = iov_iter_zero(iomap_length(iter), dio->submit.iter); + unsigned int bs = i_blocksize(iter->inode); + loff_t aligned_copied = round_down(copied, bs); - dio->size += length; - if (!length) + /* + * If fs block size is larger than page size, page fault failure + * can cause @copied to be page aligned but not fs block aligned. + */ + iov_iter_revert(dio->submit.iter, copied - aligned_copied); + dio->size += aligned_copied; + if (!aligned_copied) return -EFAULT; - return iomap_iter_advance(iter, length); + return iomap_iter_advance(iter, aligned_copied); } static int iomap_dio_inline_iter(struct iomap_iter *iomi, struct iomap_dio *dio) From a0f61a936b36a340998825cb945e5fdf03468988 Mon Sep 17 00:00:00 2001 From: Muhammad Mahdi Amirpour Date: Tue, 6 Oct 2026 19:10:05 +0330 Subject: [PATCH 1839/2286] fs/iomap: document ret parameter of iomap_iter_continue Document the ret parameter of iomap_iter_continue to avoid a kernel-doc warning. Signed-off-by: Muhammad Mahdi Amirpour Link: https://patch.msgid.link/20261006154005.232338-1-ampr.mhd@gmail.com Reviewed-by: Christoph Hellwig Reviewed-by: Jeremy Bingham Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/iter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/iomap/iter.c b/fs/iomap/iter.c index c445a38b6285dd..fd8791c4963bbc 100644 --- a/fs/iomap/iter.c +++ b/fs/iomap/iter.c @@ -51,6 +51,7 @@ static inline void iomap_iter_done(struct iomap_iter *iter) * @iter: iteration structure * @iomap: the mapping that was just processed * @srcmap: the source mapping that was just processed + * @ret: return value from the previous mapping's ->end() callback * * Helper normally called via iomap_iter_next(). Called after the previous * mapping has been finished to determine whether there is more of the file From 946b9dcff33bd4669464674ae1c00683b875be55 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 9 Oct 2026 17:21:31 +0200 Subject: [PATCH 1840/2286] s390/ptrace: Preserve ILC when updating system call number When an attached debugger changes gpr2 via PTRACE_POKEUSR while the traced process has PIF_SYSCALL set, the int_code in pt_regs is also updated. In case of an svc it contains the instruction length code (ILC), as well as the system call number. The ptrace code sets the ILC unconditionally to a value which matches the svc instruction. However the svc instruction also may have been executed via the execute or execute relative long instruction, which have a different length. Therefore preserve the ILC and only change the system call number using syscall_set_nr(). This makes sure that in case of a PSW rewind the instruction address points to the correct instruction. Fixes: 56e62a737028 ("s390: convert to generic entry") Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens --- arch/s390/kernel/ptrace.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c index 125ca4c4e30c25..d8d5e843859c8e 100644 --- a/arch/s390/kernel/ptrace.c +++ b/arch/s390/kernel/ptrace.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "entry.h" @@ -345,11 +346,9 @@ static int __poke_user(struct task_struct *child, addr_t addr, addr_t data) return -EINVAL; } - if (test_pt_regs_flag(regs, PIF_SYSCALL) && - addr == offsetof(struct user, regs.gprs[2])) { - struct pt_regs *regs = task_pt_regs(child); - - regs->int_code = 0x20000 | (data & 0xffff); + if (addr == offsetof(struct user, regs.gprs[2])) { + if (test_pt_regs_flag(regs, PIF_SYSCALL)) + syscall_set_nr(child, regs, data); } *(addr_t *)((addr_t) ®s->psw + addr) = data; } else if (addr < offsetof(struct user, regs.orig_gpr2)) { From 4044466ff004c7ad5fd47be717cd933077e6e874 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 2 Oct 2026 12:27:50 +0200 Subject: [PATCH 1841/2286] s390/extable: Fix mvcos instruction decoding If either the b1 or b2 field of the mvcos instruction is zero, register zero is not used for address computation; zero is used instead, according to the architecture. ex_handler_ua_mvcos() incorrectly uses the real contents of register zero. Fix this. This shouldn't be a problem for any generated code, since the compiler would only generate such code if it is possible to prove at compile time that either the source or destination address is within the first 4kb of the designated address space. Such code should not exist (also confirmed by an allyesconfig build and decoding all ~7000 mvcos instructions). Fixes: c488f5187a24 ("s390/uaccess: Shorten raw_copy_from_user() / raw_copy_to_user() inline assemblies") Cc: stable@vger.kernel.org Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens --- arch/s390/mm/extable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c index 7498e858c40195..848855da9a701c 100644 --- a/arch/s390/mm/extable.c +++ b/arch/s390/mm/extable.c @@ -106,9 +106,9 @@ static bool ex_handler_ua_mvcos(const struct exception_table_entry *ex, regs->psw.addr = extable_fixup(ex); insn = (struct insn_ssf *)regs->psw.addr; if (from) - uaddr = regs->gprs[insn->b2] + insn->d2; + uaddr = (insn->b2 ? regs->gprs[insn->b2] : 0) + insn->d2; else - uaddr = regs->gprs[insn->b1] + insn->d1; + uaddr = (insn->b1 ? regs->gprs[insn->b1] : 0) + insn->d1; remainder = PAGE_SIZE - (uaddr & (PAGE_SIZE - 1)); if (regs->gprs[insn->r3] <= remainder) remainder = 0; From 2ba01cf7b2598571486425a0605f1434909b532d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 9 Oct 2026 15:26:04 +0200 Subject: [PATCH 1842/2286] s390/uprobes: Fix off-by-one bug in emulated PER handling The uprobes code emulates some instructions, and with that also PER storage-alteration handling, in case a debugger is attached. The PER storage-alteration handling contains an off-by-one bug, which may lead to PER traps which should not happen. Adjust the comparison in sim_stor_event() to fix that. Fixes: 8d1a2427d8fc ("s390/uprobes: fix user space PER events") Reviewed-by: Sven Schnelle Signed-off-by: Heiko Carstens --- arch/s390/kernel/uprobes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/uprobes.c b/arch/s390/kernel/uprobes.c index c624f3361e4388..b2b8a377ced6be 100644 --- a/arch/s390/kernel/uprobes.c +++ b/arch/s390/kernel/uprobes.c @@ -241,7 +241,7 @@ static void sim_stor_event(struct pt_regs *regs, void *addr, int len) return; if (!(current->thread.per_user.control & PER_EVENT_STORE)) return; - if ((void *)current->thread.per_user.start > (addr + len)) + if ((void *)current->thread.per_user.start > (addr + len - 1)) return; if ((void *)current->thread.per_user.end < addr) return; From 31c66bd273303590fe69fad370e72a12faa0bf76 Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:48 +0200 Subject: [PATCH 1843/2286] s390/pci: Fix double-free and NULL deref in zpci MSI domain cleanup zpci_remove_parent_msi_domain() dereferences zbus->msi_parent_domain unconditionally and does not clear it afterwards. zpci_bus_create_pci_bus() removes the domain when pci_create_root_bus() fails, then zpci_bus_release() removes it again on the last kref_put(), reading ->fwnode from the freed irq_domain and freeing it twice. If zpci_alloc_domain() or zpci_create_parent_msi_domain() fails, no domain is created at all; zbus is kzalloc'd, so the same release path dereferences NULL. Return early when there is no domain, and clear the pointer after removing one. Fixes: f770950a4709 ("s390/pci: Migrate s390 IRQ logic to IRQ domain API") Cc: stable@vger.kernel.org Reviewed-by: Niklas Schnelle Signed-off-by: Tobias Schumacher Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index 9c9ed3d8d95974..c9520a16ca7535 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -533,9 +533,13 @@ void zpci_remove_parent_msi_domain(struct zpci_bus *zbus) { struct fwnode_handle *fn; + if (!zbus->msi_parent_domain) + return; + fn = zbus->msi_parent_domain->fwnode; irq_domain_remove(zbus->msi_parent_domain); irq_domain_free_fwnode(fn); + zbus->msi_parent_domain = NULL; } static void __init cpu_enable_directed_irq(void *unused) From 4a185d99179a307d856dc43610cfe72395d0c42a Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:49 +0200 Subject: [PATCH 1844/2286] s390/pci: Fix resource leak in zpci MSI setup If airq_iv_create() fails in __alloc_airq(), the zpci_sbv bit allocated by airq_iv_alloc_bit() is never freed. This permanently leaks one of the ZPCI_NR_DEVICES summary bits, reducing system capacity with each failed device hotplug. In systems with repeated device insertion failures or under memory pressure, all summary bits can be exhausted, preventing new PCI devices from being added until reboot. Add proper error handling to free the zpci_sbv bit and reset zdev->aisb if the AIBV creation fails. Fixes: f770950a4709 ("s390/pci: Migrate s390 IRQ logic to IRQ domain API") Cc: stable@vger.kernel.org Reviewed-by: Niklas Schnelle Signed-off-by: Tobias Schumacher Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index c9520a16ca7535..134f8f4a5cfa62 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -313,8 +313,11 @@ static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs, zdev->aibv = airq_iv_create(msi_vecs, AIRQ_IV_PTR | AIRQ_IV_DATA | AIRQ_IV_BITLOCK, NULL); - if (!zdev->aibv) + if (!zdev->aibv) { + airq_iv_free_bit(zpci_sbv, *bit); + zdev->aisb = -1UL; return -ENOMEM; + } /* Wire up shortcut pointer */ zpci_ibv[*bit] = zdev->aibv; From 1844913582fec337c54b54fdd3c92ce225930515 Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:50 +0200 Subject: [PATCH 1845/2286] s390/pci: Fix MSI directed-mode teardown IRQ bit count On s390 with directed interrupts enabled, zpci_msi_teardown_directed() frees the platform's maximum number of MSI bits (zdev->max_msi) instead of the actual allocated count (zdev->msi_nr_irqs). This corrupts the shared IRQ bitmap used by all PCI functions, causing lost interrupts and heap corruption. Fix zpci_msi_teardown_directed() to only free the actual allocated IRQ bit count. Fixes: f770950a4709 ("s390/pci: Migrate s390 IRQ logic to IRQ domain API") Cc: stable@vger.kernel.org Reviewed-by: Niklas Schnelle Signed-off-by: Tobias Schumacher Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index 134f8f4a5cfa62..d5763c5feb0971 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -342,7 +342,7 @@ static struct airq_struct zpci_airq = { static void zpci_msi_teardown_directed(struct zpci_dev *zdev) { - airq_iv_free(zpci_ibv[0], zdev->msi_first_bit, zdev->max_msi); + airq_iv_free(zpci_ibv[0], zdev->msi_first_bit, zdev->msi_nr_irqs); zdev->msi_first_bit = -1U; zdev->msi_nr_irqs = 0; } From 422d75ad5bc96b8eb9cb3cfd9d68fc685a0a9b75 Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:51 +0200 Subject: [PATCH 1846/2286] s390/pci: Fix use-after-free race in zpci floating interrupt cleanup zpci_clear_irq() stops the adapter from raising new interrupts for the function, but a zpci_floating_irq_handler() already running on another CPU can still be scanning zdev->aibv when zpci_msi_teardown_floating() releases it. Clear the zpci_ibv[] entry so no further handler picks the vector up, then wait for a grace period before releasing it. The handler runs inside the rcu_read_lock() section that do_airq_interrupt() holds across airq->handler(), so synchronize_rcu() drains any handler still in flight. Release the vector and free the summary bit only after the grace period, so neither can be reused while a reader still holds the old pointer. zpci_ibv served both delivery modes, indexed by function under FLOATING and by cpu under DIRECTED. Only the floating vectors are published to and torn down under the interrupt handler, so split the directed vectors out into zpci_dibv and annotate zpci_ibv __rcu, which lets sparse check the accessors above. Fixes: f770950a4709 ("s390/pci: Migrate s390 IRQ logic to IRQ domain API") Cc: stable@vger.kernel.org Signed-off-by: Tobias Schumacher Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index d5763c5feb0971..c5fabad3813957 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -22,12 +22,11 @@ static enum {FLOATING, DIRECTED} irq_delivery; */ static struct airq_iv *zpci_sbv; -/* - * interrupt bit vectors - * FLOATING - interrupt bit vector per function - * DIRECTED - interrupt bit vector per cpu - */ -static struct airq_iv **zpci_ibv; +/* FLOATING - interrupt bit vector per function */ +static struct airq_iv __rcu **zpci_ibv; + +/* DIRECTED - interrupt bit vector per cpu */ +static struct airq_iv **zpci_dibv; /* Modify PCI: Register floating adapter interruptions */ static int zpci_set_airq(struct zpci_dev *zdev) @@ -169,7 +168,7 @@ static struct irq_chip zpci_irq_chip = { static void zpci_handle_cpu_local_irq(bool rescan) { - struct airq_iv *dibv = zpci_ibv[smp_processor_id()]; + struct airq_iv *dibv = zpci_dibv[smp_processor_id()]; union zpci_sic_iib iib = {{0}}; struct irq_domain *msi_domain; irq_hw_number_t hwirq; @@ -279,7 +278,9 @@ static void zpci_floating_irq_handler(struct airq_struct *airq, } /* Scan the adapter interrupt vector for this device. */ - aibv = zpci_ibv[si]; + aibv = rcu_dereference(zpci_ibv[si]); + if (!aibv) + continue; for (ai = 0;;) { ai = airq_iv_scan(aibv, ai, airq_iv_end(aibv)); if (ai == -1UL) @@ -299,7 +300,7 @@ static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs, { if (irq_delivery == DIRECTED) { /* Allocate cpu vector bits */ - *bit = airq_iv_alloc(zpci_ibv[0], msi_vecs); + *bit = airq_iv_alloc(zpci_dibv[0], msi_vecs); if (*bit == -1UL) return -EIO; } else { @@ -320,7 +321,7 @@ static int __alloc_airq(struct zpci_dev *zdev, int msi_vecs, } /* Wire up shortcut pointer */ - zpci_ibv[*bit] = zdev->aibv; + rcu_assign_pointer(zpci_ibv[*bit], zdev->aibv); /* Each function has its own interrupt vector */ *bit = 0; } @@ -342,13 +343,16 @@ static struct airq_struct zpci_airq = { static void zpci_msi_teardown_directed(struct zpci_dev *zdev) { - airq_iv_free(zpci_ibv[0], zdev->msi_first_bit, zdev->msi_nr_irqs); + airq_iv_free(zpci_dibv[0], zdev->msi_first_bit, zdev->msi_nr_irqs); zdev->msi_first_bit = -1U; zdev->msi_nr_irqs = 0; } static void zpci_msi_teardown_floating(struct zpci_dev *zdev) { + rcu_assign_pointer(zpci_ibv[zdev->aisb], NULL); + synchronize_rcu(); + airq_iv_release(zdev->aibv); zdev->aibv = NULL; airq_iv_free_bit(zpci_sbv, zdev->aisb); @@ -428,9 +432,9 @@ static int zpci_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, if (irq_delivery == DIRECTED) { for_each_possible_cpu(cpu) { - airq_iv_set_ptr(zpci_ibv[cpu], bit + i, + airq_iv_set_ptr(zpci_dibv[cpu], bit + i, (unsigned long)zbus->msi_parent_domain); - airq_iv_set_data(zpci_ibv[cpu], bit + i, hwirq + i); + airq_iv_set_data(zpci_dibv[cpu], bit + i, hwirq + i); } } else { airq_iv_set_ptr(zdev->aibv, bit + i, @@ -455,8 +459,8 @@ static void zpci_msi_clear_airq(struct irq_data *d, int i) if (irq_delivery == DIRECTED) { for_each_possible_cpu(cpu) { - airq_iv_set_ptr(zpci_ibv[cpu], bit + i, 0); - airq_iv_set_data(zpci_ibv[cpu], bit + i, 0); + airq_iv_set_ptr(zpci_dibv[cpu], bit + i, 0); + airq_iv_set_data(zpci_dibv[cpu], bit + i, 0); } } else { airq_iv_set_ptr(zdev->aibv, bit + i, 0); @@ -550,7 +554,7 @@ static void __init cpu_enable_directed_irq(void *unused) union zpci_sic_iib iib = {{0}}; union zpci_sic_iib ziib = {{0}}; - iib.cdiib.dibv_addr = virt_to_phys(zpci_ibv[smp_processor_id()]->vector); + iib.cdiib.dibv_addr = virt_to_phys(zpci_dibv[smp_processor_id()]->vector); zpci_set_irq_ctrl(SIC_IRQ_MODE_SET_CPU, 0, &iib); zpci_set_irq_ctrl(SIC_IRQ_MODE_D_SINGLE, PCI_ISC, &ziib); @@ -570,8 +574,8 @@ static int __init zpci_directed_irq_init(void) iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector); zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib); - zpci_ibv = kzalloc_objs(*zpci_ibv, num_possible_cpus()); - if (!zpci_ibv) + zpci_dibv = kzalloc_objs(*zpci_dibv, num_possible_cpus()); + if (!zpci_dibv) return -ENOMEM; for_each_possible_cpu(cpu) { @@ -579,12 +583,12 @@ static int __init zpci_directed_irq_init(void) * Per CPU IRQ vectors look the same but bit-allocation * is only done on the first vector. */ - zpci_ibv[cpu] = airq_iv_create(cache_line_size() * BITS_PER_BYTE, - AIRQ_IV_PTR | - AIRQ_IV_DATA | - AIRQ_IV_CACHELINE | - (!cpu ? AIRQ_IV_ALLOC : 0), NULL); - if (!zpci_ibv[cpu]) + zpci_dibv[cpu] = airq_iv_create(cache_line_size() * BITS_PER_BYTE, + AIRQ_IV_PTR | + AIRQ_IV_DATA | + AIRQ_IV_CACHELINE | + (!cpu ? AIRQ_IV_ALLOC : 0), NULL); + if (!zpci_dibv[cpu]) return -ENOMEM; } on_each_cpu(cpu_enable_directed_irq, NULL, 1); @@ -660,10 +664,12 @@ void __init zpci_irq_exit(void) if (irq_delivery == DIRECTED) { for_each_possible_cpu(cpu) { - airq_iv_release(zpci_ibv[cpu]); + airq_iv_release(zpci_dibv[cpu]); } + kfree(zpci_dibv); + } else { + kfree(zpci_ibv); } - kfree(zpci_ibv); if (zpci_sbv) airq_iv_release(zpci_sbv); unregister_adapter_interrupt(&zpci_airq); From 769f66f595c11185c66913ea5d96420504bfebc3 Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:52 +0200 Subject: [PATCH 1847/2286] s390/pci: Add error cleanup in zpci_directed_irq_init() If per-CPU airq_iv allocation fails in the loop, previously allocated vectors and arrays leak. Add proper error path to release all resources on failure. The error path frees zpci_sbv, but SIC_IRQ_MODE_DIRECT has already programmed its address into the hardware. Set the directed interrupt mode only after all allocations have succeeded, so a failed init leaves no freed memory registered with the hardware. Fixes: e979ce7bced2 ("s390/pci: provide support for CPU directed interrupts") Cc: stable@vger.kernel.org Signed-off-by: Tobias Schumacher Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index c5fabad3813957..bcf11585df8d86 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -569,14 +569,9 @@ static int __init zpci_directed_irq_init(void) if (!zpci_sbv) return -ENOMEM; - iib.diib.isc = PCI_ISC; - iib.diib.nr_cpus = num_possible_cpus(); - iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector); - zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib); - zpci_dibv = kzalloc_objs(*zpci_dibv, num_possible_cpus()); if (!zpci_dibv) - return -ENOMEM; + goto out_free_sbv; for_each_possible_cpu(cpu) { /* @@ -589,13 +584,30 @@ static int __init zpci_directed_irq_init(void) AIRQ_IV_CACHELINE | (!cpu ? AIRQ_IV_ALLOC : 0), NULL); if (!zpci_dibv[cpu]) - return -ENOMEM; + goto out_free_dibv; } + + iib.diib.isc = PCI_ISC; + iib.diib.nr_cpus = num_possible_cpus(); + iib.diib.disb_addr = virt_to_phys(zpci_sbv->vector); + zpci_set_irq_ctrl(SIC_IRQ_MODE_DIRECT, 0, &iib); on_each_cpu(cpu_enable_directed_irq, NULL, 1); zpci_irq_chip.irq_set_affinity = zpci_set_irq_affinity; return 0; + +out_free_dibv: + for_each_possible_cpu(cpu) { + if (zpci_dibv[cpu]) + airq_iv_release(zpci_dibv[cpu]); + } + kfree(zpci_dibv); + zpci_dibv = NULL; +out_free_sbv: + airq_iv_release(zpci_sbv); + zpci_sbv = NULL; + return -ENOMEM; } static int __init zpci_floating_irq_init(void) From 343d1acd1abd0d9320f8b4252c736246ba764d4e Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:53 +0200 Subject: [PATCH 1848/2286] s390/pci: Set MSI_FLAG_NO_AFFINITY at IRQ init time MSI_FLAG_NO_AFFINITY is added to zpci_msi_parent_ops.required_flags from zpci_create_parent_msi_domain(), which runs for every new PCI bus, including buses created at runtime from a hotplug availability event. That is a non-atomic read-modify-write on a field which msi_lib_init_dev_msi_info() reads without a common lock while setting up MSI for a device on an already existing bus: required_flags = pops->required_flags; The stored value is always the same, so no caller observes a change, but the race need not exist: irq_delivery is decided once in zpci_irq_init() and never changes afterwards. Set the flag there instead, before any parent domain exists. Reviewed-by: Niklas Schnelle Signed-off-by: Tobias Schumacher Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index bcf11585df8d86..b890b55be87327 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -523,9 +523,6 @@ int zpci_create_parent_msi_domain(struct zpci_bus *zbus) return -ENOMEM; } - if (irq_delivery == FLOATING) - zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY; - zbus->msi_parent_domain = msi_create_parent_irq_domain(&info, &zpci_msi_parent_ops); if (!zbus->msi_parent_domain) { irq_domain_free_fwnode(info.fwnode); @@ -636,6 +633,9 @@ int __init zpci_irq_init(void) if (s390_pci_force_floating) irq_delivery = FLOATING; + if (irq_delivery == FLOATING) + zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY; + if (irq_delivery == DIRECTED) zpci_airq.handler = zpci_directed_irq_handler; From 9126b292d5e941c8bb192f4cd6f13980b624cd28 Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:54 +0200 Subject: [PATCH 1849/2286] s390/pci: Drop the unused index argument of zpci_msi_clear_airq() zpci_msi_domain_free() passes its loop index to zpci_msi_clear_airq(), which adds it to an offset that already accounts for it. zpci_msi_domain_alloc() stores hwirq + i for each vector, so zpci_decode_hwirq_msi_index() hands back msi_index + i and bit is already zdev->msi_first_bit + msi_index + i. The doubled index never selected a wrong entry. An irq domain's free() callback is only ever invoked from irq_domain_free_irqs_hierarchy(), which walks the range itself and passes a count of one. So, the loop in zpci_msi_domain_free() runs once with an index of zero. Drop the parameter and the addition. No functional change. Reviewed-by: Niklas Schnelle Signed-off-by: Tobias Schumacher Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index b890b55be87327..4b8ebb987580ba 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -446,7 +446,7 @@ static int zpci_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, return 0; } -static void zpci_msi_clear_airq(struct irq_data *d, int i) +static void zpci_msi_clear_airq(struct irq_data *d) { struct msi_desc *desc = irq_data_get_msi_desc(d); struct zpci_dev *zdev = to_zpci_dev(desc->dev); @@ -459,12 +459,12 @@ static void zpci_msi_clear_airq(struct irq_data *d, int i) if (irq_delivery == DIRECTED) { for_each_possible_cpu(cpu) { - airq_iv_set_ptr(zpci_dibv[cpu], bit + i, 0); - airq_iv_set_data(zpci_dibv[cpu], bit + i, 0); + airq_iv_set_ptr(zpci_dibv[cpu], bit, 0); + airq_iv_set_data(zpci_dibv[cpu], bit, 0); } } else { - airq_iv_set_ptr(zdev->aibv, bit + i, 0); - airq_iv_set_data(zdev->aibv, bit + i, 0); + airq_iv_set_ptr(zdev->aibv, bit, 0); + airq_iv_set_data(zdev->aibv, bit, 0); } } @@ -476,7 +476,7 @@ static void zpci_msi_domain_free(struct irq_domain *domain, unsigned int virq, for (i = 0; i < nr_irqs; i++) { d = irq_domain_get_irq_data(domain, virq + i); - zpci_msi_clear_airq(d, i); + zpci_msi_clear_airq(d); irq_domain_reset_irq_data(d); } } From 823e889478e423c18c2b35efa68234268b2338cb Mon Sep 17 00:00:00 2001 From: Tobias Schumacher Date: Fri, 9 Oct 2026 08:15:55 +0200 Subject: [PATCH 1850/2286] s390/pci: Unregister the adapter interrupt first in zpci_irq_exit() zpci_irq_exit() frees the interrupt bit vectors before unregistering the adapter interrupt whose handler uses them. This is only called from the pci_base_init() error path, where no handler is expected to run, but unregister first anyway so the vectors are never freed under a live handler. Signed-off-by: Tobias Schumacher Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_irq.c b/arch/s390/pci/pci_irq.c index 4b8ebb987580ba..16ae80ed2d1398 100644 --- a/arch/s390/pci/pci_irq.c +++ b/arch/s390/pci/pci_irq.c @@ -674,6 +674,7 @@ void __init zpci_irq_exit(void) { unsigned int cpu; + unregister_adapter_interrupt(&zpci_airq); if (irq_delivery == DIRECTED) { for_each_possible_cpu(cpu) { airq_iv_release(zpci_dibv[cpu]); @@ -684,5 +685,4 @@ void __init zpci_irq_exit(void) } if (zpci_sbv) airq_iv_release(zpci_sbv); - unregister_adapter_interrupt(&zpci_airq); } From fc9bb0ecd310b53af5116415952a1fb89d4660b9 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:16 +0200 Subject: [PATCH 1851/2286] s390/kprobes: Prevent kprobes on instructions with exception table entry The mvcos exception handler ex_handler_ua_mvcos() decodes the faulting instruction, assuming it is an mvcos instruction. In case the instruction is kprobed the decoded instruction is a breakpoint instruction instead, which leads to incorrect instruction decoding and potential register corruption. Fix this by simply preventing to set a kprobe on such instructions, similar like arm64 is doing it. Fixes: c488f5187a24 ("s390/uaccess: Shorten raw_copy_from_user() / raw_copy_to_user() inline assemblies") Cc: stable@vger.kernel.org Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/kprobes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/s390/kernel/kprobes.c b/arch/s390/kernel/kprobes.c index c450120b447496..e806b124e4ba9c 100644 --- a/arch/s390/kernel/kprobes.c +++ b/arch/s390/kernel/kprobes.c @@ -85,6 +85,9 @@ static bool can_probe(unsigned long paddr) if (!kallsyms_lookup_size_offset(paddr, NULL, &offset)) return false; + if (s390_search_extables(paddr)) + return false; + /* Decode instructions */ addr = paddr - offset; while (addr < paddr) { From 646dc01d634bc182529d7cd5f5346869aa7d58ee Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:17 +0200 Subject: [PATCH 1852/2286] s390/early: Fix early warning handling When adding support for __WARN_printf() the early program check handler was not taken into account. Add the missing support to the early program check handler. This avoids that the kernel crashes instead of printing a warning. Fixes: 04dabb4261c3 ("s390/bug: Implement __WARN_printf()") Fixes: 79996065cfa2 ("s390/bug: Skip __WARN_trap() in call traces") Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/early.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/early.c b/arch/s390/kernel/early.c index b27239c03d7992..1fc153d8385bbe 100644 --- a/arch/s390/kernel/early.c +++ b/arch/s390/kernel/early.c @@ -153,16 +153,25 @@ static __init void setup_topology(void) void __init __do_early_pgm_check(struct pt_regs *regs) { struct lowcore *lc = get_lowcore(); - unsigned long ip; regs->int_code = lc->pgm_int_code; regs->int_parm_long = lc->trans_exc_code; regs->last_break = lc->pgm_last_break; - ip = __rewind_psw(regs->psw, regs->int_code >> 16); + regs->monitor_code = lc->monitor_code; /* Monitor Event? Might be a warning */ if ((regs->int_code & PGM_INT_CODE_MASK) == 0x40) { - if (report_bug(ip, regs) == BUG_TRAP_TYPE_WARN) + enum bug_trap_type btt; + unsigned long ip; + + if (regs->monitor_code == MONCODE_BUG_ARG) { + regs->psw.addr = regs->gprs[14]; + btt = report_bug_entry((struct bug_entry *)regs->gprs[2], regs); + } else { + ip = __rewind_psw(regs->psw, regs->int_code >> 16); + btt = report_bug(ip, regs); + } + if (btt == BUG_TRAP_TYPE_WARN) return; } if (fixup_exception(regs)) From 6128f9cd1ae0d298d795269c4553bfd73aff954a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:18 +0200 Subject: [PATCH 1853/2286] s390: Clear pt_regs flags field Clear the pt_regs flags field in the early program check handler as well as in the stack_invalid handler. Without this the pt_regs flags field will have random values, depending on previous stack contents. This matters currently for bug reporting and disassembly, where flags like PIF_PSW_ADDR_ADJUSTED may then be incorrectly set, which could lead to a partially incorrect disassembly or wrong code markers. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/early.c | 1 + arch/s390/kernel/entry.S | 1 + 2 files changed, 2 insertions(+) diff --git a/arch/s390/kernel/early.c b/arch/s390/kernel/early.c index 1fc153d8385bbe..b8e3c065fbecbc 100644 --- a/arch/s390/kernel/early.c +++ b/arch/s390/kernel/early.c @@ -154,6 +154,7 @@ void __init __do_early_pgm_check(struct pt_regs *regs) { struct lowcore *lc = get_lowcore(); + regs->flags = 0; regs->int_code = lc->pgm_int_code; regs->int_parm_long = lc->trans_exc_code; regs->last_break = lc->pgm_last_break; diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 10dd9bbdf98566..f51ecd8e7d2168 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -621,6 +621,7 @@ SYM_CODE_START(stack_invalid) stmg %r0,%r7,__PT_R0(%r11) stmg %r8,%r9,__PT_PSW(%r11) mvc __PT_R8(64,%r11),0(%r14) + xc __PT_FLAGS(8,%r11),__PT_FLAGS(%r11) GET_LC %r2 mvc __PT_ORIG_GPR2(8,%r11),__LC_PGM_LAST_BREAK(%r2) xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) From 352cb5c6272043a9e1db2fce5cd8f5661e654a59 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:19 +0200 Subject: [PATCH 1854/2286] s390/bug: Indicate modified PSW in disassembly With [1] the PSW address is set to the return address of the calling __WARN_trap() function in order to provide a more useful disassembly of the caller rather than the helper itself. However, since regs->psw.addr is modified, regs->int_code has nothing to do with the exception. In order to make that more obvious change the disassembler output. Introduce a new PIF_WARN_TRAP_SKIPPED pt_regs flag and set it whenever __WARN_trap() is skipped. In the disassembler output print a "WARN RET" prefix to make it obvious that the PSW points to the return address of the warning trap function. Also do not print the interruption code and instruction length code for this case, since they have nothing to do with the modified PSW. Example output: Krnl Code: 000003ffe185bc1c: e3f0ff98ff71 lay %r15,-104(%r15) 000003ffe185bc22: e3e0f0980024 stg %r14,152(%r15) 000003ffe185bc28: c0e5ff45ed94 brasl %r14,000003ffe0119750 WARN RET >000003ffe185bc2e: c0e5ffa052b9 brasl %r14,000003ffe0c661a0 000003ffe185bc34: c020fffe86d6 larl %r2,000003ffe182c9e0 000003ffe185bc3a: e548f0a80006 mvghi 168(%r15),6 000003ffe185bc40: e548f0a00005 mvghi 160(%r15),5 000003ffe185bc46: a7690004 lghi %r6,4 [1] 79996065cfa2 ("s390/bug: Skip __WARN_trap() in call traces") Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/ptrace.h | 2 ++ arch/s390/kernel/dis.c | 23 ++++++++++------------- arch/s390/kernel/dumpstack.c | 7 +++++-- arch/s390/kernel/early.c | 9 +++++++-- arch/s390/kernel/traps.c | 1 + 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h index 6411e35842837d..c844b8aa0fd9db 100644 --- a/arch/s390/include/asm/ptrace.h +++ b/arch/s390/include/asm/ptrace.h @@ -17,11 +17,13 @@ #define PIF_PSW_ADDR_ADJUSTED 1 /* psw address has been adjusted */ #define PIF_SYSCALL_RET_SET 2 /* return value was set via ptrace */ #define PIF_FTRACE_FULL_REGS 3 /* all register contents valid (ftrace) */ +#define PIF_WARN_TRAP_SKIPPED 4 /* __WARN_trap() was skipped */ #define _PIF_SYSCALL BIT(PIF_SYSCALL) #define _PIF_ADDR_PSW_ADJUSTED BIT(PIF_PSW_ADDR_ADJUSTED) #define _PIF_SYSCALL_RET_SET BIT(PIF_SYSCALL_RET_SET) #define _PIF_FTRACE_FULL_REGS BIT(PIF_FTRACE_FULL_REGS) +#define _PIF_WARN_TRAP_SKIPPED BIT(PIF_WARN_TRAP_SKIPPED) #define PSW32_MASK_PER _AC(0x40000000, UL) #define PSW32_MASK_DAT _AC(0x04000000, UL) diff --git a/arch/s390/kernel/dis.c b/arch/s390/kernel/dis.c index 9c349eb884bfa1..f17c44e1740be3 100644 --- a/arch/s390/kernel/dis.c +++ b/arch/s390/kernel/dis.c @@ -540,34 +540,31 @@ void show_code(struct pt_regs *regs) start += 2; } /* Decode the instructions. */ - ptr = buffer; - ptr += sprintf(ptr, "%s Code:", mode); + printk("%s Code:\n", mode); hops = 0; while (start < end && hops < 8) { + ptr = buffer; opsize = insn_length(code[start]); - if (start + opsize == 32) - *ptr++ = '*'; + if (start == 32 && test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) + ptr += sprintf(ptr, " WARN RET >"); + else if (start + opsize == 32 && !test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) + ptr += sprintf(ptr, " *"); else if (start == 32) - *ptr++ = '>'; + ptr += sprintf(ptr, " >"); else - *ptr++ = ' '; + ptr += sprintf(ptr, " "); addr = pswaddr + start - 32; ptr += sprintf(ptr, "%px: ", (void *)addr); if (start + opsize >= end) break; for (i = 0; i < opsize; i++) ptr += sprintf(ptr, "%02x", code[start + i]); - *ptr++ = '\t'; - if (i < 6) - *ptr++ = '\t'; + ptr += sprintf(ptr, "%*s", 13 - 2 * opsize, " "); ptr += print_insn(ptr, code + start, addr); start += opsize; - pr_cont("%s", buffer); - ptr = buffer; - ptr += sprintf(ptr, "\n "); + pr_cont("%s\n", buffer); hops++; } - pr_cont("\n"); } void print_fn_code(unsigned char *code, unsigned long len) diff --git a/arch/s390/kernel/dumpstack.c b/arch/s390/kernel/dumpstack.c index f9d52e05e01e04..4ec08264b3efb5 100644 --- a/arch/s390/kernel/dumpstack.c +++ b/arch/s390/kernel/dumpstack.c @@ -203,8 +203,11 @@ void __noreturn die(struct pt_regs *regs, const char *str) console_verbose(); spin_lock_irq(&die_lock); bust_spinlocks(1); - printk("%s: %04x ilc:%d [#%d]", str, regs->int_code & 0xffff, - regs->int_code >> 17, ++die_counter); + if (test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) + printk("%s: [#%d]", str, ++die_counter); + else + printk("%s: %04x ilc:%d [#%d]", str, regs->int_code & 0xffff, + regs->int_code >> 17, ++die_counter); pr_cont("SMP "); if (debug_pagealloc_enabled()) pr_cont("DEBUG_PAGEALLOC"); diff --git a/arch/s390/kernel/early.c b/arch/s390/kernel/early.c index b8e3c065fbecbc..d875b3b304bea0 100644 --- a/arch/s390/kernel/early.c +++ b/arch/s390/kernel/early.c @@ -167,6 +167,7 @@ void __init __do_early_pgm_check(struct pt_regs *regs) if (regs->monitor_code == MONCODE_BUG_ARG) { regs->psw.addr = regs->gprs[14]; + set_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED); btt = report_bug_entry((struct bug_entry *)regs->gprs[2], regs); } else { ip = __rewind_psw(regs->psw, regs->int_code >> 16); @@ -183,8 +184,12 @@ void __init __do_early_pgm_check(struct pt_regs *regs) * some basic information in case it is too early for printk(). */ register_early_console(); - early_printk("PANIC: early exception %04x PSW: %016lx %016lx\n", - regs->int_code & 0xffff, regs->psw.mask, regs->psw.addr); + if (test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) + early_printk("PANIC: early exception PSW: %016lx %016lx\n", + regs->psw.mask, regs->psw.addr); + else + early_printk("PANIC: early exception %04x PSW: %016lx %016lx\n", + regs->int_code & 0xffff, regs->psw.mask, regs->psw.addr); show_regs(regs); disabled_wait(); } diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index dd051046170bbf..0187994712bcb1 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -267,6 +267,7 @@ static void monitor_event_exception(struct pt_regs *regs) return; if (regs->monitor_code == MONCODE_BUG_ARG) { regs->psw.addr = regs->gprs[14]; + set_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED); btt = report_bug_entry((struct bug_entry *)regs->gprs[2], regs); } else { btt = report_bug(regs->psw.addr - (regs->int_code >> 16), regs); From f9ef2f6c877a9faef8528058eb62fcefde83ed9a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:20 +0200 Subject: [PATCH 1855/2286] s390/tools: Rename gen_facilities to gen_bitmaps Rename gen_facilities.c to gen_bitmaps.c and make it more generic so it can be used for various bitmaps. Add command line parsing, and use it to explicitly specify that the facilities header should be generated. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/tools/.gitignore | 1 + arch/s390/tools/Makefile | 8 +-- .../tools/{gen_facilities.c => gen_bitmaps.c} | 49 +++++++++++++------ 3 files changed, 38 insertions(+), 20 deletions(-) rename arch/s390/tools/{gen_facilities.c => gen_bitmaps.c} (83%) diff --git a/arch/s390/tools/.gitignore b/arch/s390/tools/.gitignore index e6af51d9d18355..e07d27143172e8 100644 --- a/arch/s390/tools/.gitignore +++ b/arch/s390/tools/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only gen_facilities +gen_bitmaps gen_opcode_table relocs diff --git a/arch/s390/tools/Makefile b/arch/s390/tools/Makefile index f2862364fb4221..ac267bb4655054 100644 --- a/arch/s390/tools/Makefile +++ b/arch/s390/tools/Makefile @@ -10,17 +10,17 @@ PHONY += kapi kapi: $(kapi-hdrs-y) -hostprogs += gen_facilities +hostprogs += gen_bitmaps hostprogs += gen_opcode_table -HOSTCFLAGS_gen_facilities.o += $(LINUXINCLUDE) +HOSTCFLAGS_gen_bitmaps.o += $(LINUXINCLUDE) -filechk_facility-defs.h = $(obj)/gen_facilities +filechk_facility-defs.h = $(obj)/gen_bitmaps --facility filechk_dis-defs.h = \ $(obj)/gen_opcode_table < $(srctree)/arch/$(ARCH)/tools/opcodes.txt -$(kapi)/facility-defs.h: $(obj)/gen_facilities FORCE +$(kapi)/facility-defs.h: $(obj)/gen_bitmaps FORCE $(call filechk,facility-defs.h) $(kapi)/dis-defs.h: $(obj)/gen_opcode_table FORCE diff --git a/arch/s390/tools/gen_facilities.c b/arch/s390/tools/gen_bitmaps.c similarity index 83% rename from arch/s390/tools/gen_facilities.c rename to arch/s390/tools/gen_bitmaps.c index 32dd5a57240d1f..4bc5f19dd40573 100644 --- a/arch/s390/tools/gen_facilities.c +++ b/arch/s390/tools/gen_bitmaps.c @@ -1,24 +1,25 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Simple program to generate defines out of facility lists that use the bit - * numbering scheme from the Princples of Operations: most significant bit - * has bit number 0. + * Simple program to generate bitmaps, for example facility lists. * * Copyright IBM Corp. 2015, 2018 * */ +#include #include #include #include #include -struct facility_def { +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +struct bitmap_def { char *name; int *bits; }; -static struct facility_def facility_defs[] = { +static struct bitmap_def facility_defs[] = { { /* * FACILITIES_ALS contains the list of facilities that are @@ -124,7 +125,7 @@ static struct facility_def facility_defs[] = { }, }; -static void print_facility_list(struct facility_def *def) +static void print_bitmap(struct bitmap_def *def) { unsigned int high, bit, dword, i; unsigned long long *array; @@ -151,25 +152,41 @@ static void print_facility_list(struct facility_def *def) free(array); } -static void print_facility_lists(void) +static void print_header(const char *guard, struct bitmap_def *defs, size_t num) { unsigned int i; - for (i = 0; i < sizeof(facility_defs) / sizeof(facility_defs[0]); i++) - print_facility_list(&facility_defs[i]); -} - -int main(int argc, char **argv) -{ - printf("#ifndef __ASM_S390_FACILITY_DEFS__\n"); - printf("#define __ASM_S390_FACILITY_DEFS__\n"); + printf("#ifndef __ASM_S390_%s_DEFS__\n", guard); + printf("#define __ASM_S390_%s_DEFS__\n", guard); printf("/*\n"); printf(" * DO NOT MODIFY.\n"); printf(" *\n"); printf(" * This file was generated by %s\n", __FILE__); printf(" */\n\n"); printf("#include \n\n"); - print_facility_lists(); + for (i = 0; i < num; i++) + print_bitmap(&defs[i]); printf("\n#endif\n"); +} + +static const struct option long_options[] = { + { "facility", no_argument, NULL, 'f' }, + { NULL, 0, NULL, 0 } +}; + +int main(int argc, char **argv) +{ + int opt; + + opt = getopt_long(argc, argv, "", long_options, NULL); + if (opt == -1) + return 1; + switch (opt) { + case 'f': + print_header("FACILITY", facility_defs, ARRAY_SIZE(facility_defs)); + break; + default: + return 1; + } return 0; } From 319ccf083b9942abdb69ec6749a3cce08ebc62bf Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:21 +0200 Subject: [PATCH 1856/2286] s390/tools: Generate bitmap of nullified program checks Add PGM_CHECKS_NULLIFIED bitmap to gen_bitmaps and add a --pgmcheck command line option to generate the pgmcheck-defs.h header file. The bitmap specifies which program checks are nullified, and will be used with future changes. Change gen_bitmaps.c so it can create bitmaps with different bit numbering: MSB0 bit numbering like before (most significant bit is bit 0), and LSB0 bit numbering (least significant bit is bit 0), which is used for the new bitmap. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/Kbuild | 1 + arch/s390/tools/Makefile | 8 +++++- arch/s390/tools/gen_bitmaps.c | 54 +++++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/arch/s390/include/asm/Kbuild b/arch/s390/include/asm/Kbuild index 0c1fc47c3ba0a4..d13dd7e3dc01e2 100644 --- a/arch/s390/include/asm/Kbuild +++ b/arch/s390/include/asm/Kbuild @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 generated-y += dis-defs.h generated-y += facility-defs.h +generated-y += pgmcheck-defs.h generated-y += syscall_table.h generated-y += unistd_nr.h diff --git a/arch/s390/tools/Makefile b/arch/s390/tools/Makefile index ac267bb4655054..ba1e42d005e777 100644 --- a/arch/s390/tools/Makefile +++ b/arch/s390/tools/Makefile @@ -4,7 +4,9 @@ # kapi := arch/$(ARCH)/include/generated/asm -kapi-hdrs-y := $(kapi)/facility-defs.h $(kapi)/dis-defs.h +kapi-hdrs-y := $(kapi)/dis-defs.h +kapi-hdrs-y += $(kapi)/facility-defs.h +kapi-hdrs-y += $(kapi)/pgmcheck-defs.h PHONY += kapi @@ -16,6 +18,7 @@ hostprogs += gen_opcode_table HOSTCFLAGS_gen_bitmaps.o += $(LINUXINCLUDE) filechk_facility-defs.h = $(obj)/gen_bitmaps --facility +filechk_pgmcheck-defs.h = $(obj)/gen_bitmaps --pgmcheck filechk_dis-defs.h = \ $(obj)/gen_opcode_table < $(srctree)/arch/$(ARCH)/tools/opcodes.txt @@ -23,6 +26,9 @@ filechk_dis-defs.h = \ $(kapi)/facility-defs.h: $(obj)/gen_bitmaps FORCE $(call filechk,facility-defs.h) +$(kapi)/pgmcheck-defs.h: $(obj)/gen_bitmaps FORCE + $(call filechk,pgmcheck-defs.h) + $(kapi)/dis-defs.h: $(obj)/gen_opcode_table FORCE $(call filechk,dis-defs.h) diff --git a/arch/s390/tools/gen_bitmaps.c b/arch/s390/tools/gen_bitmaps.c index 4bc5f19dd40573..2e5dc491b19ef6 100644 --- a/arch/s390/tools/gen_bitmaps.c +++ b/arch/s390/tools/gen_bitmaps.c @@ -17,6 +17,8 @@ struct bitmap_def { char *name; int *bits; + int inverted; + int maxbit; }; static struct bitmap_def facility_defs[] = { @@ -27,6 +29,7 @@ static struct bitmap_def facility_defs[] = { * -march=. */ .name = "FACILITIES_ALS", + .inverted = 1, .bits = (int[]){ 0, /* N3 instructions */ 1, /* z/Arch mode installed */ @@ -71,6 +74,7 @@ static struct bitmap_def facility_defs[] = { * guest facilities supported by KVM. */ .name = "FACILITIES_KVM", + .inverted = 1, .bits = (int[]){ 0, /* N3 instructions */ 1, /* z/Arch mode installed */ @@ -116,6 +120,7 @@ static struct bitmap_def facility_defs[] = { */ .name = "FACILITIES_KVM_CPUMODEL", + .inverted = 1, .bits = (int[]){ 12, /* AP Query Configuration Information */ 15, /* AP Facilities Test */ @@ -125,17 +130,56 @@ static struct bitmap_def facility_defs[] = { }, }; +static struct bitmap_def pgm_check_defs[] = { + { + .name = "PGM_CHECKS_NULLIFIED", + .inverted = 0, + .maxbit = 127, + .bits = (int[]){ + 0x10, /* Segment Translation */ + 0x11, /* Page Translation */ + 0x16, /* Trace Table */ + 0x20, /* AFX Translation */ + 0x21, /* ASX Translation */ + 0x22, /* LX Translation */ + 0x23, /* EX Translation */ + 0x24, /* Primary Authority */ + 0x25, /* Secondary Authority */ + 0x26, /* LFX Translation */ + 0x27, /* LSX Translation */ + 0x29, /* ALEN Translation */ + 0x2a, /* ALE Sequence */ + 0x2b, /* ASTE Validity */ + 0x2c, /* ASTE Sequence */ + 0x2d, /* Extended Authority */ + 0x2e, /* LSTE Sequence */ + 0x2f, /* ASTE Instance */ + 0x30, /* Stack Full */ + 0x31, /* Stack Empty */ + 0x32, /* Stack Specification */ + 0x33, /* Stack Type */ + 0x34, /* Stack Operation */ + 0x38, /* ASCE Type */ + 0x39, /* Region First Translation */ + 0x3a, /* Region Second Translation */ + 0x3b, /* Region Third Translation */ + 0x3d, /* Secure Storage Access */ + -1 /* END */ + } + }, +}; + static void print_bitmap(struct bitmap_def *def) { unsigned int high, bit, dword, i; unsigned long long *array; - array = calloc(1, 8); + high = def->maxbit ? def->maxbit / 64 : 0; + array = calloc(high + 1, 8); if (!array) exit(EXIT_FAILURE); - high = 0; for (i = 0; def->bits[i] != -1; i++) { - bit = 63 - (def->bits[i] & 63); + bit = def->inverted ? 63 - (def->bits[i] & 63) : (def->bits[i] & 63); dword = def->bits[i] / 64; if (dword > high) { array = realloc(array, (dword + 1) * 8); @@ -171,6 +215,7 @@ static void print_header(const char *guard, struct bitmap_def *defs, size_t num) static const struct option long_options[] = { { "facility", no_argument, NULL, 'f' }, + { "pgmcheck", no_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; @@ -185,6 +230,9 @@ int main(int argc, char **argv) case 'f': print_header("FACILITY", facility_defs, ARRAY_SIZE(facility_defs)); break; + case 'p': + print_header("PGMCHECK", pgm_check_defs, ARRAY_SIZE(pgm_check_defs)); + break; default: return 1; } From 241c66ba76546d4d1ecd1a7e3cc2b54dc1c22ad1 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:22 +0200 Subject: [PATCH 1857/2286] s390/traps: Handle nullifying program checks in get_trap_ip() get_trap_ip() always rewinds the PSW address by the instruction length from the interruption code. For nullifying program checks the PSW already points at the faulting instruction, so rewinding is wrong. Add a nullified[] bitmap and return the unchanged PSW address for nullifying cases, or if PIF_PSW_ADDR_ADJUSTED is set. No functional change for existing callers, which only deal with suppressing, terminating, or completing program checks. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/traps.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index 0187994712bcb1..7f56d5a66de8cd 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -41,15 +42,19 @@ struct pgm_stat { static DEFINE_PER_CPU_SHARED_ALIGNED(struct pgm_stat, pgm_stat); -static inline void __user *get_trap_ip(struct pt_regs *regs) +static inline unsigned long get_trap_ip(struct pt_regs *regs) { - unsigned long address; + static const unsigned long nullified[2] = { PGM_CHECKS_NULLIFIED }; + psw_t psw = regs->psw; + if (test_pt_regs_flag(regs, PIF_PSW_ADDR_ADJUSTED)) + return psw.addr; if (regs->int_code & 0x200) - address = current->thread.trap_tdb.data[3]; - else - address = regs->psw.addr; - return (void __user *)(address - (regs->int_code >> 16)); + psw.addr = current->thread.trap_tdb.data[3]; + if (test_bit(regs->int_code & PGM_INT_CODE_MASK, nullified)) + return psw.addr; + psw.addr = __rewind_psw(psw, regs->int_code >> 16); + return psw.addr; } #ifdef CONFIG_GENERIC_BUG @@ -62,7 +67,7 @@ int is_valid_bugaddr(unsigned long addr) void do_report_trap(struct pt_regs *regs, int si_signo, int si_code, char *str) { if (user_mode(regs)) { - force_sig_fault(si_signo, si_code, get_trap_ip(regs)); + force_sig_fault(si_signo, si_code, (void __user *)get_trap_ip(regs)); report_user_fault(regs, si_signo, 0); } else { if (!fixup_exception(regs)) @@ -148,12 +153,12 @@ static void translation_specification_exception(struct pt_regs *regs) static void illegal_op(struct pt_regs *regs) { int is_uprobe_insn = 0; - u16 __user *location; int signal = 0; u16 opcode; - location = get_trap_ip(regs); if (user_mode(regs)) { + u16 __user *location = (void __user *)get_trap_ip(regs); + if (get_user(opcode, location)) return; if (opcode == S390_BREAKPOINT_U16) { From 04e326f59a2717b048591f14f021cd9d46e6406e Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:23 +0200 Subject: [PATCH 1858/2286] s390/dis: Improve show_code() output Replace the '>' and '*' instruction markers with explicit 'PSW >' and 'PGM >' markers which identify both the PSW address and the faulting instruction. Previously, '>' marked the PSW address, while '*' marked the preceding instruction as a guess for the faulting instruction for suppressing exceptions: Krnl Code: 000003ff8dc7bff8: e310f0080004 lg %r1,8(%r15) *000003ff8dc7bffe: e3a0e0000004 lg %r10,0(%r14) >000003ff8dc7c004: a7280000 lhi %r2,0 000003ff8dc7c008: eb9ff0900024 stmg %r9,%r15,144(%r15) Now 'PGM >' explicitly marks the faulting instruction without guessing and 'PSW >' marks the PSW address: Krnl Code: 000003ff8dc7bff8: e310f0080004 lg %r1,8(%r15) PGM >000003ff8dc7bffe: e3a0e0000004 lg %r10,0(%r14) PSW >000003ff8dc7c004: a7280000 lhi %r2,0 000003ff8dc7c008: eb9ff0900024 stmg %r9,%r15,144(%r15) For nullifying exceptions where both addresses are the same, 'PGM+PSW >' is printed instead: Krnl Code: 000003ff8dc7bff8: e310f0080004 lg %r1,8(%r15) PGM+PSW >000003ff8dc7bffe: e3a0e0000004 lg %r10,0(%r14) 000003ff8dc7c004: a7280000 lhi %r2,0 000003ff8dc7c008: eb9ff0900024 stmg %r9,%r15,144(%r15) show_code() may also be called in other contexts where pt_regs does not originate from a program check (e.g. system calls). Introduce and set PIF_PGM_CHECK to indicate when in program check context, and print the PGM marker only in such a context. Note that corner cases are not taken into account. Like before kprobes is not considered, which means that the disassembly may contain unexpected instructions, or that disassembly and/or instruction length count as well as interruption do not match. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/ptrace.h | 4 ++++ arch/s390/kernel/dis.c | 18 ++++++++++++------ arch/s390/kernel/early.c | 1 + arch/s390/kernel/traps.c | 4 +++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h index c844b8aa0fd9db..3e56ef8c268b79 100644 --- a/arch/s390/include/asm/ptrace.h +++ b/arch/s390/include/asm/ptrace.h @@ -18,12 +18,14 @@ #define PIF_SYSCALL_RET_SET 2 /* return value was set via ptrace */ #define PIF_FTRACE_FULL_REGS 3 /* all register contents valid (ftrace) */ #define PIF_WARN_TRAP_SKIPPED 4 /* __WARN_trap() was skipped */ +#define PIF_PGM_CHECK 5 /* inside a program check */ #define _PIF_SYSCALL BIT(PIF_SYSCALL) #define _PIF_ADDR_PSW_ADJUSTED BIT(PIF_PSW_ADDR_ADJUSTED) #define _PIF_SYSCALL_RET_SET BIT(PIF_SYSCALL_RET_SET) #define _PIF_FTRACE_FULL_REGS BIT(PIF_FTRACE_FULL_REGS) #define _PIF_WARN_TRAP_SKIPPED BIT(PIF_WARN_TRAP_SKIPPED) +#define _PIF_PGM_CHECK BIT(PIF_PGM_CHECK) #define PSW32_MASK_PER _AC(0x40000000, UL) #define PSW32_MASK_DAT _AC(0x04000000, UL) @@ -242,6 +244,8 @@ static inline void instruction_pointer_set(struct pt_regs *regs, regs->psw.addr = val; } +unsigned long get_trap_ip(struct pt_regs *regs); + int regs_query_register_offset(const char *name); const char *regs_query_register_name(unsigned int offset); diff --git a/arch/s390/kernel/dis.c b/arch/s390/kernel/dis.c index f17c44e1740be3..d2d299ba8b86ee 100644 --- a/arch/s390/kernel/dis.c +++ b/arch/s390/kernel/dis.c @@ -503,7 +503,8 @@ static int copy_from_regs(struct pt_regs *regs, void *dst, void *src, int len) void show_code(struct pt_regs *regs) { char *mode = user_mode(regs) ? "User" : "Krnl"; - unsigned long addr, pswaddr; + unsigned long addr, pswaddr, trapaddr; + bool pgm_check; unsigned char code[64]; char buffer[128], *ptr; int start, end, opsize, hops, i; @@ -511,6 +512,9 @@ void show_code(struct pt_regs *regs) pswaddr = regs->psw.addr; if (test_pt_regs_flag(regs, PIF_PSW_ADDR_ADJUSTED)) pswaddr = __forward_psw(regs->psw, regs->int_code >> 16); + pgm_check = test_pt_regs_flag(regs, PIF_PGM_CHECK); + if (pgm_check) + trapaddr = get_trap_ip(regs); /* Get a snapshot of the 64 bytes surrounding the fault address. */ for (start = 32; start && pswaddr >= 34 - start; start -= 2) { addr = pswaddr - 34 + start; @@ -545,15 +549,17 @@ void show_code(struct pt_regs *regs) while (start < end && hops < 8) { ptr = buffer; opsize = insn_length(code[start]); + addr = pswaddr + start - 32; if (start == 32 && test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) ptr += sprintf(ptr, " WARN RET >"); - else if (start + opsize == 32 && !test_pt_regs_flag(regs, PIF_WARN_TRAP_SKIPPED)) - ptr += sprintf(ptr, " *"); + else if (pgm_check && addr == trapaddr && start == 32) + ptr += sprintf(ptr, " PGM+PSW >"); + else if (pgm_check && addr == trapaddr) + ptr += sprintf(ptr, " PGM >"); else if (start == 32) - ptr += sprintf(ptr, " >"); + ptr += sprintf(ptr, " PSW >"); else ptr += sprintf(ptr, " "); - addr = pswaddr + start - 32; ptr += sprintf(ptr, "%px: ", (void *)addr); if (start + opsize >= end) break; @@ -562,7 +568,7 @@ void show_code(struct pt_regs *regs) ptr += sprintf(ptr, "%*s", 13 - 2 * opsize, " "); ptr += print_insn(ptr, code + start, addr); start += opsize; - pr_cont("%s\n", buffer); + printk("%s\n", buffer); hops++; } } diff --git a/arch/s390/kernel/early.c b/arch/s390/kernel/early.c index d875b3b304bea0..a603e034fa82f0 100644 --- a/arch/s390/kernel/early.c +++ b/arch/s390/kernel/early.c @@ -159,6 +159,7 @@ void __init __do_early_pgm_check(struct pt_regs *regs) regs->int_parm_long = lc->trans_exc_code; regs->last_break = lc->pgm_last_break; regs->monitor_code = lc->monitor_code; + set_pt_regs_flag(regs, PIF_PGM_CHECK); /* Monitor Event? Might be a warning */ if ((regs->int_code & PGM_INT_CODE_MASK) == 0x40) { diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index 7f56d5a66de8cd..e35e0018f37220 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -42,7 +42,7 @@ struct pgm_stat { static DEFINE_PER_CPU_SHARED_ALIGNED(struct pgm_stat, pgm_stat); -static inline unsigned long get_trap_ip(struct pt_regs *regs) +unsigned long get_trap_ip(struct pt_regs *regs) { static const unsigned long nullified[2] = { PGM_CHECKS_NULLIFIED }; psw_t psw = regs->psw; @@ -56,6 +56,7 @@ static inline unsigned long get_trap_ip(struct pt_regs *regs) psw.addr = __rewind_psw(psw, regs->int_code >> 16); return psw.addr; } +NOKPROBE_SYMBOL(get_trap_ip); #ifdef CONFIG_GENERIC_BUG int is_valid_bugaddr(unsigned long addr) @@ -353,6 +354,7 @@ void noinstr __do_pgm_check(struct pt_regs *regs, unsigned long flags) regs->int_code = lc->pgm_int_code; regs->int_parm_long = teid.val; regs->monitor_code = lc->monitor_code; + set_pt_regs_flag(regs, PIF_PGM_CHECK); trapnr = regs->int_code & PGM_INT_CODE_MASK; stat = this_cpu_ptr(&pgm_stat); From 70fdd89e316af48c037a3b9b0c267335158a669d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:24 +0200 Subject: [PATCH 1859/2286] s390/extable: Rework exception handling logic Add EX_TABLE_INSN macros which will replace the existing EX_TABLE macros. The macros come with new semantics for the whole exception handling: with the new exception handling the exception table entries always point to the instruction which may cause an exception. This is regardless of exception type, and whether an exception is nullifying, suppressing, completing, or terminating. This improves the current situation where exception table entries are required which point to the instruction (nullifying) and/or behind the instruction (suppressing, completing, or terminating), since depending on usage an instruction can generate different exceptions. The new EX_TABLE_INSN macros are aliases to the existing EX_TABLE macros. The exception handling logic is changed to first perform a lookup in the exception tables with get_trap_ip() / boot_get_trap_ip(). If that finds nothing, it falls back to looking up with instruction_pointer(). This is a temporary solution until all EX_TABLE usages have been converted to EX_TABLE_INSN. When that work is complete EX_TABLE will be removed, exception tables will only be searched by using the return value of get_trap_ip() / boot_get_trap_ip(). Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/boot/pgm_check.c | 35 ++++++++++++++++++++++++----- arch/s390/include/asm/asm-extable.h | 10 +++++++++ arch/s390/mm/extable.c | 4 +++- 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/arch/s390/boot/pgm_check.c b/arch/s390/boot/pgm_check.c index fa621fa5bc02ae..56d0be7e28232f 100644 --- a/arch/s390/boot/pgm_check.c +++ b/arch/s390/boot/pgm_check.c @@ -3,9 +3,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -40,19 +42,40 @@ static inline unsigned long extable_insn(const struct exception_table_entry *x) return (unsigned long)&x->insn + x->insn; } -static bool ex_handler(struct pt_regs *regs) +static unsigned long boot_get_trap_ip(struct pt_regs *regs) +{ + static const unsigned long nullified[2] = { PGM_CHECKS_NULLIFIED }; + + if (test_bit(regs->int_code & PGM_INT_CODE_MASK, nullified)) + return regs->psw.addr; + return __rewind_psw(regs->psw, regs->int_code >> 16); +} + +static const struct exception_table_entry *search_boot_extable(unsigned long ip) { const struct exception_table_entry *ex; for (ex = __start___ex_table; ex < __stop___ex_table; ex++) { - if (extable_insn(ex) != regs->psw.addr) + if (extable_insn(ex) != ip) continue; if (ex->type != EX_TYPE_FIXUP) - return false; - regs->psw.addr = extable_fixup(ex); - return true; + return NULL; + return ex; } - return false; + return NULL; +} + +static bool ex_handler(struct pt_regs *regs) +{ + const struct exception_table_entry *ex; + + ex = search_boot_extable(boot_get_trap_ip(regs)); + if (!ex) + ex = search_boot_extable(instruction_pointer(regs)); + if (!ex) + return false; + regs->psw.addr = extable_fixup(ex); + return true; } void do_pgm_check(struct pt_regs *regs) diff --git a/arch/s390/include/asm/asm-extable.h b/arch/s390/include/asm/asm-extable.h index d23ea0c94e4ea6..488bd10452b30c 100644 --- a/arch/s390/include/asm/asm-extable.h +++ b/arch/s390/include/asm/asm-extable.h @@ -92,4 +92,14 @@ #define EX_TABLE_UA_MVCOS_FROM(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_MVCOS_FROM, __stringify(%%r0), __stringify(%%r0), 0) +#define EX_TABLE_INSN EX_TABLE +#define EX_TABLE_INSN_AMODE31 EX_TABLE_AMODE31 +#define EX_TABLE_INSN_UA_FAULT EX_TABLE_UA_FAULT +#define EX_TABLE_INSN_UA_LOAD_REG EX_TABLE_UA_LOAD_REG +#define EX_TABLE_INSN_UA_LOAD_REGPAIR EX_TABLE_UA_LOAD_REGPAIR +#define EX_TABLE_INSN_ZEROPAD EX_TABLE_ZEROPAD +#define EX_TABLE_INSN_FPC EX_TABLE_FPC +#define EX_TABLE_INSN_UA_MVCOS_TO EX_TABLE_UA_MVCOS_TO +#define EX_TABLE_INSN_UA_MVCOS_FROM EX_TABLE_UA_MVCOS_FROM + #endif /* __ASM_EXTABLE_H */ diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c index 848855da9a701c..3d2ad6e853d1fd 100644 --- a/arch/s390/mm/extable.c +++ b/arch/s390/mm/extable.c @@ -120,7 +120,9 @@ bool fixup_exception(struct pt_regs *regs) { const struct exception_table_entry *ex; - ex = s390_search_extables(instruction_pointer(regs)); + ex = s390_search_extables(get_trap_ip(regs)); + if (!ex) + ex = s390_search_extables(instruction_pointer(regs)); if (!ex) return false; switch (ex->type) { From 7d32c0db67980550af06409e7eaee3a9ea311f88 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:25 +0200 Subject: [PATCH 1860/2286] s390/diag: Convert amode31 exception tables to EX_TABLE_INSN Convert all amode31 exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so named labels are replaced with numeric labels. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/text_amode31.S | 34 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/arch/s390/kernel/text_amode31.S b/arch/s390/kernel/text_amode31.S index f007d892d0c5fd..7761de4914436e 100644 --- a/arch/s390/kernel/text_amode31.S +++ b/arch/s390/kernel/text_amode31.S @@ -19,9 +19,9 @@ * affects a few functions that are not performance-relevant. */ .macro BR_EX_AMODE31_r14 - exrl 0,0f + exrl 0,555f j . -0: br %r14 +555: br %r14 .endm /* @@ -33,15 +33,13 @@ SYM_TYPED_FUNC_START(_diag14_amode31) lgr %r3,%r4 lhi %r5,-EIO sam31 - diag %r1,%r2,0x14 -.Ldiag14_ex: +0: diag %r1,%r2,0x14 ipm %r5 srl %r5,28 -.Ldiag14_fault: - sam64 +1: sam64 lgfr %r2,%r5 BR_EX_AMODE31_r14 - EX_TABLE_AMODE31(.Ldiag14_ex, .Ldiag14_fault) + EX_TABLE_INSN_AMODE31(0b, 1b) SYM_FUNC_END(_diag14_amode31) /* @@ -51,15 +49,13 @@ SYM_TYPED_FUNC_START(_diag210_amode31) lgr %r1,%r2 lhi %r2,-1 sam31 - diag %r1,%r0,0x210 -.Ldiag210_ex: +0: diag %r1,%r0,0x210 ipm %r2 srl %r2,28 -.Ldiag210_fault: - sam64 +1: sam64 lgfr %r2,%r2 BR_EX_AMODE31_r14 - EX_TABLE_AMODE31(.Ldiag210_ex, .Ldiag210_fault) + EX_TABLE_INSN_AMODE31(0b, 1b) SYM_FUNC_END(_diag210_amode31) /* @@ -68,12 +64,11 @@ SYM_FUNC_END(_diag210_amode31) SYM_TYPED_FUNC_START(_diag8c_amode31) llgf %r3,0(%r3) sam31 - diag %r2,%r4,0x8c -.Ldiag8c_ex: - sam64 +0: diag %r2,%r4,0x8c +1: sam64 lgfr %r2,%r3 BR_EX_AMODE31_r14 - EX_TABLE_AMODE31(.Ldiag8c_ex, .Ldiag8c_ex) + EX_TABLE_INSN_AMODE31(0b, 1b) SYM_FUNC_END(_diag8c_amode31) /* * int _diag26c_amode31(void *req, void *resp, enum diag26c_sc subcode) @@ -81,12 +76,11 @@ SYM_FUNC_END(_diag8c_amode31) SYM_TYPED_FUNC_START(_diag26c_amode31) lghi %r5,-EOPNOTSUPP sam31 - diag %r2,%r4,0x26c -.Ldiag26c_ex: - sam64 +0: diag %r2,%r4,0x26c +1: sam64 lgfr %r2,%r5 BR_EX_AMODE31_r14 - EX_TABLE_AMODE31(.Ldiag26c_ex, .Ldiag26c_ex) + EX_TABLE_INSN_AMODE31(0b, 1b) SYM_FUNC_END(_diag26c_amode31) /* From cd19c5f9f9f6f04331edf2eb986c3c47783c02ba Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:26 +0200 Subject: [PATCH 1861/2286] s390/uaccess: Convert inline assembly to EX_TABLE_INSN Convert all uaccess exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries and nopr padding are no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/uaccess.h | 45 +++++++++++---------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/arch/s390/include/asm/uaccess.h b/arch/s390/include/asm/uaccess.h index a9f32c53f699b8..ac99b32c6ceb59 100644 --- a/arch/s390/include/asm/uaccess.h +++ b/arch/s390/include/asm/uaccess.h @@ -43,10 +43,8 @@ raw_copy_from_user(void *to, const void __user *from, unsigned long size) asm_inline volatile( " lhi %%r0,%[spec]\n" "0: mvcos %[to],%[from],%[size]\n" - "1: nopr %%r7\n" CC_IPM(cc) - EX_TABLE_UA_MVCOS_FROM(0b, 0b) - EX_TABLE_UA_MVCOS_FROM(1b, 0b) + EX_TABLE_INSN_UA_MVCOS_FROM(0b, 0b) : CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char *)to) : [spec] "I" (0x81), [from] "Q" (*(const char __user *)from) : CC_CLOBBER_LIST("memory", "0")); @@ -71,10 +69,8 @@ raw_copy_to_user(void __user *to, const void *from, unsigned long size) asm_inline volatile( " llilh %%r0,%[spec]\n" "0: mvcos %[to],%[from],%[size]\n" - "1: nopr %%r7\n" CC_IPM(cc) - EX_TABLE_UA_MVCOS_TO(0b, 0b) - EX_TABLE_UA_MVCOS_TO(1b, 0b) + EX_TABLE_INSN_UA_MVCOS_TO(0b, 0b) : CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char __user *)to) : [spec] "I" (0x81), [from] "Q" (*(const char *)from) : CC_CLOBBER_LIST("memory", "0")); @@ -123,9 +119,7 @@ __put_user_##type##_noinstr(unsigned type __user *to, \ asm goto( \ " llilh %%r0,%[spec]\n" \ "0: mvcos %[to],%[from],%[size]\n" \ - "1: nopr %%r7\n" \ - EX_TABLE(0b, %l[Efault]) \ - EX_TABLE(1b, %l[Efault]) \ + EX_TABLE_INSN(0b, %l[Efault]) \ : [to] "+Q" (*to) \ : [size] "d" (size), [from] "Q" (*from), \ [spec] "I" (0x81) \ @@ -150,10 +144,9 @@ __put_user_##type##_noinstr(unsigned type __user *to, \ asm_inline volatile( \ " llilh %%r0,%[spec]\n" \ "0: mvcos %[to],%[from],%[size]\n" \ - "1: lhi %[rc],0\n" \ - "2:\n" \ - EX_TABLE_UA_FAULT(0b, 2b, %[rc]) \ - EX_TABLE_UA_FAULT(1b, 2b, %[rc]) \ + " lhi %[rc],0\n" \ + "1:\n" \ + EX_TABLE_INSN_UA_FAULT(0b, 1b, %[rc]) \ : [rc] "=d" (rc), [to] "+Q" (*to) \ : [size] "d" (size), [from] "Q" (*from), \ [spec] "I" (0x81) \ @@ -238,9 +231,7 @@ __get_user_##type##_noinstr(unsigned type *to, \ asm goto( \ " lhi %%r0,%[spec]\n" \ "0: mvcos %[to],%[from],%[size]\n" \ - "1: nopr %%r7\n" \ - EX_TABLE(0b, %l[Efault]) \ - EX_TABLE(1b, %l[Efault]) \ + EX_TABLE_INSN(0b, %l[Efault]) \ : [to] "=Q" (*to) \ : [size] "d" (size), [from] "Q" (*from), \ [spec] "I" (0x81) \ @@ -266,10 +257,9 @@ __get_user_##type##_noinstr(unsigned type *to, \ asm_inline volatile( \ " lhi %%r0,%[spec]\n" \ "0: mvcos %[to],%[from],%[size]\n" \ - "1: lhi %[rc],0\n" \ - "2:\n" \ - EX_TABLE_UA_FAULT(0b, 2b, %[rc]) \ - EX_TABLE_UA_FAULT(1b, 2b, %[rc]) \ + " lhi %[rc],0\n" \ + "1:\n" \ + EX_TABLE_INSN_UA_FAULT(0b, 1b, %[rc]) \ : [rc] "=d" (rc), [to] "=Q" (*to) \ : [size] "d" (size), [from] "Q" (*from), \ [spec] "I" (0x81) \ @@ -374,10 +364,8 @@ __clear_user(void __user *to, unsigned long size) asm_inline volatile( " llilh %%r0,%[spec]\n" "0: mvcos %[to],%[from],%[size]\n" - "1: nopr %%r7\n" CC_IPM(cc) - EX_TABLE_UA_MVCOS_TO(0b, 0b) - EX_TABLE_UA_MVCOS_TO(1b, 0b) + EX_TABLE_INSN_UA_MVCOS_TO(0b, 0b) : CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char __user *)to) : [spec] "I" (0x81), [from] "Q" (*(const char *)empty_zero_page) : CC_CLOBBER_LIST("memory", "0")); @@ -418,9 +406,7 @@ do { \ case 8: \ asm goto( \ "0: mvc %O[_dst](%[_len],%R[_dst]),%[_src]\n" \ - "1: nopr %%r7\n" \ - EX_TABLE(0b, %l[err_label]) \ - EX_TABLE(1b, %l[err_label]) \ + EX_TABLE_INSN(0b, %l[err_label]) \ : [_dst] "=Q" (*(type *)dst) \ : [_src] "Q" (*(type *)(src)), \ [_len] "I" (sizeof(type)) \ @@ -447,10 +433,9 @@ do { \ case 8: \ asm_inline volatile( \ "0: mvc 0(%[_len],%[_dst]),%[_src]\n" \ - "1: lhi %[_rc],0\n" \ - "2:\n" \ - EX_TABLE_UA_FAULT(0b, 2b, %[_rc]) \ - EX_TABLE_UA_FAULT(1b, 2b, %[_rc]) \ + " lhi %[_rc],0\n" \ + "1:\n" \ + EX_TABLE_INSN_UA_FAULT(0b, 1b, %[_rc]) \ : [_rc] "=d" (__rc), \ "=m" (*__dst) \ : [_src] "Q" (*(type *)(src)), \ From adddc28271c0b10eb7314086aeafb68d9e1005f6 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:27 +0200 Subject: [PATCH 1862/2286] s390/lib/uaccess: Convert inline assembly to EX_TABLE_INSN Convert all uaccess exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries are removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/lib/uaccess.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/arch/s390/lib/uaccess.c b/arch/s390/lib/uaccess.c index 0ac2f3998b1482..d3f462c12bbaf4 100644 --- a/arch/s390/lib/uaccess.c +++ b/arch/s390/lib/uaccess.c @@ -49,24 +49,22 @@ static nokprobe_inline int __cmpxchg_key_small(void *address, unsigned int *uval "20: spka 0(%[key])\n" " llill %[count],%[max_loops]\n" "0: l %[prev],%[address]\n" - "1: nr %[prev],%[mask]\n" + " nr %[prev],%[mask]\n" " xilf %[mask],0xffffffff\n" " or %[new],%[prev]\n" " or %[prev],%[tmp]\n" - "2: lr %[tmp],%[prev]\n" - "3: cs %[prev],%[new],%[address]\n" - "4: jnl 5f\n" + "1: lr %[tmp],%[prev]\n" + "2: cs %[prev],%[new],%[address]\n" + " jnl 3f\n" " xr %[tmp],%[prev]\n" " xr %[new],%[tmp]\n" " nr %[tmp],%[mask]\n" - " jnz 5f\n" - " brct %[count],2b\n" - "5: spka %[default_key]\n" + " jnz 3f\n" + " brct %[count],1b\n" + "3: spka %[default_key]\n" "21:\n" - EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev]) + EX_TABLE_INSN_UA_LOAD_REG(0b, 3b, %[rc], %[prev]) + EX_TABLE_INSN_UA_LOAD_REG(2b, 3b, %[rc], %[prev]) SKEY_REGION(20b, 21b) : [rc] "+&d" (rc), [prev] "=&d" (prev), @@ -133,8 +131,7 @@ int __kprobes __cmpxchg_key4(void *address, unsigned int *uval, unsigned int old "0: cs %[prev],%[new],%[address]\n" "1: spka %[default_key]\n" "21:\n" - EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev]) + EX_TABLE_INSN_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) SKEY_REGION(20b, 21b) : [rc] "+&d" (rc), [prev] "+&d" (prev), @@ -160,8 +157,7 @@ int __kprobes __cmpxchg_key8(void *address, unsigned long *uval, unsigned long o "0: csg %[prev],%[new],%[address]\n" "1: spka %[default_key]\n" "21:\n" - EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev]) + EX_TABLE_INSN_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) SKEY_REGION(20b, 21b) : [rc] "+&d" (rc), [prev] "+&d" (prev), @@ -187,8 +183,7 @@ int __kprobes __cmpxchg_key16(void *address, __uint128_t *uval, __uint128_t old, "0: cdsg %[prev],%[new],%[address]\n" "1: spka %[default_key]\n" "21:\n" - EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev]) - EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev]) + EX_TABLE_INSN_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev]) SKEY_REGION(20b, 21b) : [rc] "+&d" (rc), [prev] "+&d" (prev), From e2780ada7bdf0b84d8ad678b890b40e6e4564266 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:28 +0200 Subject: [PATCH 1863/2286] s390/futex: Convert inline assembly to EX_TABLE_INSN Convert all futex exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries are removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/futex.h | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/arch/s390/include/asm/futex.h b/arch/s390/include/asm/futex.h index 942f21c396973d..71e8060637be87 100644 --- a/arch/s390/include/asm/futex.h +++ b/arch/s390/include/asm/futex.h @@ -23,13 +23,11 @@ __futex_atomic_##name(int oparg, int *old, u32 __user *uaddr) \ "0: l %[old],%[uaddr]\n" \ "1:"insn \ "2: cs %[old],%[new],%[uaddr]\n" \ - "3: jl 1b\n" \ + " jl 1b\n" \ " lhi %[rc],0\n" \ - "4: sacf 768\n" \ - EX_TABLE_UA_FAULT(0b, 4b, %[rc]) \ - EX_TABLE_UA_FAULT(1b, 4b, %[rc]) \ - EX_TABLE_UA_FAULT(2b, 4b, %[rc]) \ - EX_TABLE_UA_FAULT(3b, 4b, %[rc]) \ + "3: sacf 768\n" \ + EX_TABLE_INSN_UA_FAULT(0b, 3b, %[rc]) \ + EX_TABLE_INSN_UA_FAULT(2b, 3b, %[rc]) \ : [rc] "=d" (rc), [old] "=&d" (*old), \ [new] "=&d" (new), [uaddr] "+Q" (*uaddr) \ : [oparg] "d" (oparg) \ @@ -86,10 +84,9 @@ int futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, u32 oldval, u32 asm_inline volatile( " sacf 256\n" "0: cs %[old],%[new],%[uaddr]\n" - "1: lhi %[rc],0\n" - "2: sacf 768\n" - EX_TABLE_UA_FAULT(0b, 2b, %[rc]) - EX_TABLE_UA_FAULT(1b, 2b, %[rc]) + " lhi %[rc],0\n" + "1: sacf 768\n" + EX_TABLE_INSN_UA_FAULT(0b, 1b, %[rc]) : [rc] "=d" (rc), [old] "+d" (oldval), [uaddr] "+Q" (*uaddr) : [new] "d" (newval) : "cc", "memory"); From 0f748fe1f2c6401672e4d5b0ea371df3da9a6a76 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:29 +0200 Subject: [PATCH 1864/2286] s390/diag: Convert inline assembly to EX_TABLE_INSN Convert all diag exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries and nopr padding are no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/diag.h | 5 ++--- arch/s390/include/asm/diag288.h | 6 +++--- arch/s390/kernel/diag/diag.c | 12 ++++++------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/arch/s390/include/asm/diag.h b/arch/s390/include/asm/diag.h index 8db8db3b10182a..1416ee1ad3231c 100644 --- a/arch/s390/include/asm/diag.h +++ b/arch/s390/include/asm/diag.h @@ -68,9 +68,8 @@ static inline void diag10_range(unsigned long start_pfn, unsigned long num_pfn) diag_stat_inc(DIAG_STAT_X010); asm_inline volatile( "0: diag %0,%1,0x10\n" - "1: nopr %%r7\n" - EX_TABLE(0b, 1b) - EX_TABLE(1b, 1b) + "1:\n" + EX_TABLE_INSN(0b, 1b) : : "a" (start_addr), "a" (end_addr)); } diff --git a/arch/s390/include/asm/diag288.h b/arch/s390/include/asm/diag288.h index 5e1b43cea9d64c..f0a37a80d672c4 100644 --- a/arch/s390/include/asm/diag288.h +++ b/arch/s390/include/asm/diag288.h @@ -28,10 +28,10 @@ static inline int __diag288(unsigned int func, unsigned int timeout, int rc = -EINVAL; asm volatile( - " diag %[r1],%[r3],0x288\n" - "0: lhi %[rc],0\n" + "0: diag %[r1],%[r3],0x288\n" + " lhi %[rc],0\n" "1:" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [rc] "+d" (rc) : [r1] "d" (r1.pair), [r3] "d" (r3.pair) : "cc", "memory"); diff --git a/arch/s390/kernel/diag/diag.c b/arch/s390/kernel/diag/diag.c index 56b862ba9be849..f23b3e2b070edb 100644 --- a/arch/s390/kernel/diag/diag.c +++ b/arch/s390/kernel/diag/diag.c @@ -196,9 +196,9 @@ static inline int __diag204(unsigned long *subcode, unsigned long size, void *ad union register_pair rp = { .even = *subcode, .odd = size }; asm_inline volatile( - " diag %[addr],%[rp],0x204\n" - "0: nopr %%r7\n" - EX_TABLE(0b,0b) + "0: diag %[addr],%[rp],0x204\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [rp] "+&d" (rp.pair) : [addr] "d" (addr) : "memory"); *subcode = rp.even; return rp.odd; @@ -287,10 +287,10 @@ int diag224(void *ptr) diag_stat_inc(DIAG_STAT_X224); asm_inline volatile("\n" - " diag %[type],%[addr],0x224\n" - "0: lhi %[rc],0\n" + "0: diag %[type],%[addr],0x224\n" + " lhi %[rc],0\n" "1:\n" - EX_TABLE(0b,1b) + EX_TABLE_INSN(0b, 1b) : [rc] "+d" (rc) , "=m" (*(struct { char buf[PAGE_SIZE]; } *)ptr) : [type] "d" (0), [addr] "d" (addr)); From 6934b8174347736404f91ae304e11a1c770a6502 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:30 +0200 Subject: [PATCH 1865/2286] s390/mm/pfault: Convert inline assembly to EX_TABLE_INSN Convert all pfault exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so nopr padding is no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/mm/pfault.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/mm/pfault.c b/arch/s390/mm/pfault.c index 6ecd6b0a22a8cb..8f37741471e2f1 100644 --- a/arch/s390/mm/pfault.c +++ b/arch/s390/mm/pfault.c @@ -58,9 +58,9 @@ int __pfault_init(void) return rc; diag_stat_inc(DIAG_STAT_X258); asm_inline volatile( - " diag %[refbk],%[rc],0x258\n" - "0: nopr %%r7\n" - EX_TABLE(0b, 0b) + "0: diag %[refbk],%[rc],0x258\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [rc] "+d" (rc) : [refbk] "a" (virt_to_phys(&pfault_init_refbk)), "m" (pfault_init_refbk) : "cc"); @@ -80,9 +80,9 @@ void __pfault_fini(void) return; diag_stat_inc(DIAG_STAT_X258); asm_inline volatile( - " diag %[refbk],0,0x258\n" - "0: nopr %%r7\n" - EX_TABLE(0b, 0b) + "0: diag %[refbk],0,0x258\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : : [refbk] "a" (virt_to_phys(&pfault_fini_refbk)), "m" (pfault_fini_refbk) : "cc"); From aafb54c544f58cc306cf91fb7409138e7a5bb57b Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:31 +0200 Subject: [PATCH 1866/2286] s390/cpu_mf: Convert inline assembly to EX_TABLE_INSN Convert all cpu_mf exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries are removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/cpu_mf.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/arch/s390/include/asm/cpu_mf.h b/arch/s390/include/asm/cpu_mf.h index 1798fbd59068ba..dd2991622e821a 100644 --- a/arch/s390/include/asm/cpu_mf.h +++ b/arch/s390/include/asm/cpu_mf.h @@ -173,9 +173,9 @@ static inline int qctri(struct cpumf_ctr_info *info) asm_inline volatile ( "0: qctri %1\n" - "1: lhi %0,0\n" - "2:\n" - EX_TABLE(1b, 2b) + " lhi %0,0\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : "+d" (rc), "=Q" (*info)); return rc; } @@ -257,9 +257,9 @@ static inline int qsi(struct hws_qsi_info_block *info) asm volatile( "0: qsi %1\n" - "1: lhi %0,0\n" - "2:\n" - EX_TABLE(0b, 2b) EX_TABLE(1b, 2b) + " lhi %0,0\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : "+d" (cc), "+Q" (*info)); return cc ? -EINVAL : 0; } @@ -272,10 +272,10 @@ static inline int lsctl(struct hws_lsctl_request_block *req) exception = 1; asm volatile( "0: lsctl %[req]\n" - "1: lhi %[exc],0\n" - "2:\n" + " lhi %[exc],0\n" + "1:\n" CC_IPM(cc) - EX_TABLE(0b, 2b) EX_TABLE(1b, 2b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [exc] "+d" (exception) : [req] "Q" (*req) : CC_CLOBBER); From 3e8b505bfb5d635c04e6cbdc36f5330496b16a94 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:32 +0200 Subject: [PATCH 1867/2286] s390/cert_store: Convert inline assembly to EX_TABLE_INSN Convert the diag320 exception table entry to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so nopr padding is no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/cert_store.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/cert_store.c b/arch/s390/kernel/cert_store.c index dc1992a675deee..998f2da5617500 100644 --- a/arch/s390/kernel/cert_store.c +++ b/arch/s390/kernel/cert_store.c @@ -236,9 +236,9 @@ static int __diag320(unsigned long subcode, void *addr) union register_pair rp = { .even = (unsigned long)addr, }; asm_inline volatile( - " diag %[rp],%[subcode],0x320\n" - "0: nopr %%r7\n" - EX_TABLE(0b, 0b) + "0: diag %[rp],%[subcode],0x320\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [rp] "+d" (rp.pair) : [subcode] "d" (subcode) : "cc", "memory"); From d8d1a740a7c80a7bced35f66b4851ea10f391a16 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:33 +0200 Subject: [PATCH 1868/2286] s390/ipl: Convert inline assembly to EX_TABLE_INSN Convert the diag308 exception table entry to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so nopr padding is no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/ipl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index b1e798f8e1dd67..c441b1357492f8 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -188,9 +188,9 @@ static inline int __diag308(unsigned long subcode, unsigned long addr) r1.even = addr; r1.odd = 0; asm_inline volatile( - " diag %[r1],%[subcode],0x308\n" - "0: nopr %%r7\n" - EX_TABLE(0b,0b) + "0: diag %[r1],%[subcode],0x308\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [r1] "+&d" (r1.pair) : [subcode] "d" (subcode) : "cc", "memory"); From 62d064756e0f0a62988a7fa3c3b952ef5d420d3d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:34 +0200 Subject: [PATCH 1869/2286] s390/hypfs: Convert inline assembly to EX_TABLE_INSN Convert the diag2fc exception table entry to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so nopr padding is no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/hypfs/hypfs_vm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/hypfs/hypfs_vm.c b/arch/s390/hypfs/hypfs_vm.c index 4db2895e4da339..bd47fce5973187 100644 --- a/arch/s390/hypfs/hypfs_vm.c +++ b/arch/s390/hypfs/hypfs_vm.c @@ -42,9 +42,9 @@ static int diag2fc(int size, char* query, void *addr) diag_stat_inc(DIAG_STAT_X2FC); asm volatile( - " diag %0,%1,0x2fc\n" - "0: nopr %%r7\n" - EX_TABLE(0b,0b) + "0: diag %0,%1,0x2fc\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : "=d" (residual_cnt), "+d" (rc) : "0" (&parm_list) : "memory"); if ((rc != 0 ) && (rc != -2)) From b8bdc2c77755aff3daf06e488428932748ed8abb Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:35 +0200 Subject: [PATCH 1870/2286] s390/ap: Convert inline assembly to EX_TABLE_INSN Convert all ap exception table entries to use EX_TABLE_INSN. Reviewed-by: Harald Freudenberger Signed-off-by: Heiko Carstens --- arch/s390/include/asm/ap.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/s390/include/asm/ap.h b/arch/s390/include/asm/ap.h index c91b6ace199d7d..b0f8e3946a43fd 100644 --- a/arch/s390/include/asm/ap.h +++ b/arch/s390/include/asm/ap.h @@ -91,10 +91,10 @@ static inline bool ap_instructions_available(void) " lgr 0,%[reg0]\n" /* qid into gr0 */ " lghi 1,0\n" /* 0 into gr1 */ " lghi 2,0\n" /* 0 into gr2 */ - " .insn rre,0xb2af0000,0,0\n" /* PQAP(TAPQ) */ - "0: la %[reg1],1\n" /* 1 into reg1 */ + "0: .insn rre,0xb2af0000,0,0\n" /* PQAP(TAPQ) */ + " la %[reg1],1\n" /* 1 into reg1 */ "1:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [reg1] "+&d" (reg1) : [reg0] "d" (reg0) : "cc", "0", "1", "2"); @@ -272,10 +272,10 @@ static inline int ap_qci(struct ap_config_info *config) asm volatile( " lgr 0,%[reg0]\n" /* QCI fc into gr0 */ " lgr 2,%[reg2]\n" /* ptr to config into gr2 */ - " .insn rre,0xb2af0000,0,0\n" /* PQAP(QCI) */ - "0: la %[reg1],0\n" /* good case, QCI fc available */ + "0: .insn rre,0xb2af0000,0,0\n" /* PQAP(QCI) */ + " la %[reg1],0\n" /* good case, QCI fc available */ "1:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [reg1] "+&d" (reg1) : [reg0] "d" (reg0), [reg2] "d" (reg2) : "cc", "memory", "0", "2"); From 558b4fe2572115878e4655fde7c1a610fffaba4d Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:36 +0200 Subject: [PATCH 1871/2286] s390/fpu: Convert inline assembly to EX_TABLE_INSN Convert the fpu_lfpc_safe exception table entry to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so nopr padding is no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/fpu-insn.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/fpu-insn.h b/arch/s390/include/asm/fpu-insn.h index ae8b7033cfd2c3..eeb0a233d5c705 100644 --- a/arch/s390/include/asm/fpu-insn.h +++ b/arch/s390/include/asm/fpu-insn.h @@ -104,9 +104,9 @@ static inline void fpu_lfpc_safe(unsigned int *fpc) { instrument_read(fpc, sizeof(*fpc)); asm_inline volatile( - " lfpc %[fpc]\n" - "0: nopr %%r7\n" - EX_TABLE_FPC(0b, 0b) + "0: lfpc %[fpc]\n" + "1:\n" + EX_TABLE_INSN_FPC(0b, 1b) : : [fpc] "Q" (*fpc) : "memory"); From 4ff587ae7fd92d6794a962f3776762d2de191c6b Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:37 +0200 Subject: [PATCH 1872/2286] s390/traps: Convert inline assembly to EX_TABLE_INSN Convert the monitor call exception table entry to use EX_TABLE_INSN. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/traps.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index e35e0018f37220..83fa4a79480753 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -312,10 +312,10 @@ static void __init test_monitor_call(void) if (!IS_ENABLED(CONFIG_BUG)) return; asm_inline volatile( - " mc %[monc](%%r0),0\n" - "0: lhi %[val],0\n" + "0: mc %[monc](%%r0),0\n" + " lhi %[val],0\n" "1:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [val] "+d" (val) : [monc] "i" (MONCODE_BUG)); if (!val) From 345a429724ec783254aa76c10d60bbf588ed5b02 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:38 +0200 Subject: [PATCH 1873/2286] s390/word-at-a-time: Convert inline assembly to EX_TABLE_INSN Convert the load_unaligned_zeropad exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so the duplicate entry and nopr padding are no longer needed and removed. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/include/asm/word-at-a-time.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h index eaa19dee769941..0b7779c8d5a4ef 100644 --- a/arch/s390/include/asm/word-at-a-time.h +++ b/arch/s390/include/asm/word-at-a-time.h @@ -54,9 +54,8 @@ static inline unsigned long load_unaligned_zeropad(const void *addr) asm_inline volatile( "0: lg %[data],0(%[addr])\n" - "1: nopr %%r7\n" - EX_TABLE_ZEROPAD(0b, 1b, %[data], %[addr]) - EX_TABLE_ZEROPAD(1b, 1b, %[data], %[addr]) + "1:\n" + EX_TABLE_INSN_ZEROPAD(0b, 1b, %[data], %[addr]) : [data] "=d" (data) : [addr] "a" (addr), "m" (*(unsigned long *)addr)); return data; From 4b73aa1666f8414d7a09eafc7f1efd3e16c7ce9a Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:39 +0200 Subject: [PATCH 1874/2286] s390/kvm: Convert inline assembly to EX_TABLE_INSN Convert the gaccess mvcos exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries are removed. Acked-by: Claudio Imbrenda Signed-off-by: Heiko Carstens --- arch/s390/kvm/s390/gaccess.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/arch/s390/kvm/s390/gaccess.c b/arch/s390/kvm/s390/gaccess.c index e5c064f263df8d..5184bac7f55258 100644 --- a/arch/s390/kvm/s390/gaccess.c +++ b/arch/s390/kvm/s390/gaccess.c @@ -838,12 +838,11 @@ static int mvcos_key(void *to, const void *from, unsigned long size, u8 dst_key, int exception = PGM_PROTECTION; asm_inline volatile( - " lr %%r0,%[spec]\n" - "0: mvcos %[to],%[from],%[size]\n" - "1: lhi %[exc],0\n" - "2:\n" - EX_TABLE(0b, 2b) - EX_TABLE(1b, 2b) + " lr %%r0,%[spec]\n" + "0: mvcos %[to],%[from],%[size]\n" + " lhi %[exc],0\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [size] "+d" (size), [to] "=Q" (*(char *)to), [exc] "+d" (exception) : [spec] "d" (spec.val), [from] "Q" (*(const char *)from) : "memory", "cc", "0"); From cb29b52dc5fb29cd6bb246ce85b43f3ed2eedfcd Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:40 +0200 Subject: [PATCH 1875/2286] s390/pci: Convert inline assembly to EX_TABLE_INSN Convert all pci exception table entries to use EX_TABLE_INSN. With EX_TABLE_INSN entries always point to the faulting instruction, so duplicate entries are removed. Reviewed-by: Niklas Schnelle Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_clp.c | 12 ++++++------ arch/s390/pci/pci_insn.c | 36 ++++++++++++++++++------------------ arch/s390/pci/pci_mmio.c | 28 +++++++++++++++------------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/arch/s390/pci/pci_clp.c b/arch/s390/pci/pci_clp.c index 177aa0214547d8..2d7eaf4d9d5937 100644 --- a/arch/s390/pci/pci_clp.c +++ b/arch/s390/pci/pci_clp.c @@ -55,11 +55,11 @@ static inline int clp_get_ilp(unsigned long *ilp) exception = 1; asm_inline volatile ( - " .insn rrf,0xb9a00000,%[mask],%[cmd],8,0\n" - "0: lhi %[exc],0\n" + "0: .insn rrf,0xb9a00000,%[mask],%[cmd],8,0\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [mask] "=d" (mask), [exc] "+d" (exception) : [cmd] "a" (1) : CC_CLOBBER); @@ -78,11 +78,11 @@ static __always_inline int clp_req(void *data, unsigned int lps) exception = 1; asm_inline volatile ( - " .insn rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n" - "0: lhi %[exc],0\n" + "0: .insn rrf,0xb9a00000,%[ign],%[req],0,%[lps]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [ign] "=d" (ignored), "+m" (*req), [exc] "+d" (exception) : [req] "a" (req), [lps] "i" (lps) : CC_CLOBBER); diff --git a/arch/s390/pci/pci_insn.c b/arch/s390/pci/pci_insn.c index 35ceb1bea1c619..ed0def408f18a1 100644 --- a/arch/s390/pci/pci_insn.c +++ b/arch/s390/pci/pci_insn.c @@ -161,11 +161,11 @@ static inline int ____pcilg(u64 *data, u64 req, u64 offset, u8 *status) exception = 1; asm_inline volatile ( - " .insn rre,0xb9d20000,%[data],%[req_off]\n" - "0: lhi %[exc],0\n" + "0: .insn rre,0xb9d20000,%[data],%[req_off]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [data] "=d" (__data), [req_off] "+d" (req_off.pair), [exc] "+d" (exception) : @@ -230,11 +230,11 @@ static inline int __pcilg_mio(u64 *data, u64 ioaddr, u64 len, u8 *status) exception = 1; asm_inline volatile ( - " .insn rre,0xb9d60000,%[data],%[ioaddr_len]\n" - "0: lhi %[exc],0\n" + "0: .insn rre,0xb9d60000,%[data],%[ioaddr_len]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [data] "=d" (__data), [ioaddr_len] "+d" (ioaddr_len.pair), [exc] "+d" (exception) : @@ -268,11 +268,11 @@ static inline int __pcistg(u64 data, u64 req, u64 offset, u8 *status) exception = 1; asm_inline volatile ( - " .insn rre,0xb9d00000,%[data],%[req_off]\n" - "0: lhi %[exc],0\n" + "0: .insn rre,0xb9d00000,%[data],%[req_off]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [req_off] "+d" (req_off.pair), [exc] "+d" (exception) : [data] "d" (data) : CC_CLOBBER); @@ -322,11 +322,11 @@ static inline int __pcistg_mio(u64 data, u64 ioaddr, u64 len, u8 *status) exception = 1; asm_inline volatile ( - " .insn rre,0xb9d40000,%[data],%[ioaddr_len]\n" - "0: lhi %[exc],0\n" + "0: .insn rre,0xb9d40000,%[data],%[ioaddr_len]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [ioaddr_len] "+d" (ioaddr_len.pair), [exc] "+d" (exception) : [data] "d" (data) : CC_CLOBBER_LIST("memory")); @@ -357,11 +357,11 @@ static inline int __pcistb(const u64 *data, u64 req, u64 offset, u8 *status) exception = 1; asm_inline volatile ( - " .insn rsy,0xeb00000000d0,%[req],%[offset],%[data]\n" - "0: lhi %[exc],0\n" + "0: .insn rsy,0xeb00000000d0,%[req],%[offset],%[data]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [req] "+d" (req), [exc] "+d" (exception) : [offset] "d" (offset), [data] "Q" (*data) : CC_CLOBBER); @@ -411,11 +411,11 @@ static inline int __pcistb_mio(const u64 *data, u64 ioaddr, u64 len, u8 *status) exception = 1; asm_inline volatile ( - " .insn rsy,0xeb00000000d4,%[len],%[ioaddr],%[data]\n" - "0: lhi %[exc],0\n" + "0: .insn rsy,0xeb00000000d4,%[len],%[ioaddr],%[data]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [len] "+d" (len), [exc] "+d" (exception) : [ioaddr] "d" (ioaddr), [data] "Q" (*data) : CC_CLOBBER); diff --git a/arch/s390/pci/pci_mmio.c b/arch/s390/pci/pci_mmio.c index f3f79ba78410a4..8b49175b0031ba 100644 --- a/arch/s390/pci/pci_mmio.c +++ b/arch/s390/pci/pci_mmio.c @@ -39,10 +39,10 @@ static inline int __pcistb_mio_inuser( asm_inline volatile ( " sacf 256\n" "0: .insn rsy,0xeb00000000d4,%[len],%[ioaddr],%[src]\n" - "1: lhi %[exc],0\n" - "2: sacf 768\n" + " lhi %[exc],0\n" + "1: sacf 768\n" CC_IPM(cc) - EX_TABLE(0b, 2b) EX_TABLE(1b, 2b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [len] "+d" (len), [exc] "+d" (exception) : [ioaddr] "a" (ioaddr), [src] "Q" (*((u8 __force *)src)) : CC_CLOBBER_LIST("memory")); @@ -72,15 +72,16 @@ static inline int __pcistg_mio_inuser( asm_inline volatile ( " sacf 256\n" "0: llgc %[tmp],0(%[src])\n" - "4: sllg %[val],%[val],8\n" + " sllg %[val],%[val],8\n" " aghi %[src],1\n" " ogr %[val],%[tmp]\n" " brctg %[cnt],0b\n" "1: .insn rre,0xb9d40000,%[val],%[ioaddr_len]\n" - "2: lhi %[exc],0\n" - "3: sacf 768\n" + " lhi %[exc],0\n" + "2: sacf 768\n" CC_IPM(cc) - EX_TABLE(0b, 3b) EX_TABLE(4b, 3b) EX_TABLE(1b, 3b) EX_TABLE(2b, 3b) + EX_TABLE_INSN(0b, 2b) + EX_TABLE_INSN(1b, 2b) : [src] "+a" (src), [cnt] "+d" (cnt), [val] "+d" (val), [tmp] "=d" (tmp), [exc] "+d" (exception), CC_OUT(cc, cc), [ioaddr_len] "+&d" (ioaddr_len.pair) @@ -227,13 +228,13 @@ static inline int __pcilg_mio_inuser( asm_inline volatile ( " sacf 256\n" "0: .insn rre,0xb9d60000,%[val],%[ioaddr_len]\n" - "1: lhi %[exc],0\n" + " lhi %[exc],0\n" " jne 4f\n" - "2: ahi %[shift],-8\n" + "1: ahi %[shift],-8\n" " srlg %[tmp],%[val],0(%[shift])\n" - "3: stc %[tmp],0(%[dst])\n" - "5: aghi %[dst],1\n" - " brctg %[cnt],2b\n" + "2: stc %[tmp],0(%[dst])\n" + " aghi %[dst],1\n" + " brctg %[cnt],1b\n" /* * Use xr to clear exc and set condition code to zero * to ensure flag output is correct for this branch. @@ -241,7 +242,8 @@ static inline int __pcilg_mio_inuser( " xr %[exc],%[exc]\n" "4: sacf 768\n" CC_IPM(cc) - EX_TABLE(0b, 4b) EX_TABLE(1b, 4b) EX_TABLE(3b, 4b) EX_TABLE(5b, 4b) + EX_TABLE_INSN(0b, 4b) + EX_TABLE_INSN(2b, 4b) : [ioaddr_len] "+&d" (ioaddr_len.pair), [exc] "+d" (exception), CC_OUT(cc, cc), [val] "=d" (val), [dst] "+a" (dst), [cnt] "+d" (cnt), [tmp] "=d" (tmp), From a1e2c6e8be6cf81a27aa7c5281a1c0c7ce14e8ce Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:41 +0200 Subject: [PATCH 1876/2286] s390/boot: Convert inline assembly to EX_TABLE_INSN Convert all boot exception table entries to use EX_TABLE_INSN. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/boot/ipl_parm.c | 6 +++--- arch/s390/boot/physmem_info.c | 18 +++++++++--------- arch/s390/boot/startup.c | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/arch/s390/boot/ipl_parm.c b/arch/s390/boot/ipl_parm.c index 637cb7b6343f95..8bbd102fa50f2c 100644 --- a/arch/s390/boot/ipl_parm.c +++ b/arch/s390/boot/ipl_parm.c @@ -40,9 +40,9 @@ static inline int __diag308(unsigned long subcode, void *addr) union register_pair r1 = { .even = (unsigned long)addr, .odd = 0 }; asm_inline volatile( - " diag %[r1],%[subcode],0x308\n" - "0:\n" - EX_TABLE(0b, 0b) + "0: diag %[r1],%[subcode],0x308\n" + "1:\n" + EX_TABLE_INSN(0b, 1b) : [r1] "+d" (r1.pair) : [subcode] "d" (subcode) : "cc", "memory"); diff --git a/arch/s390/boot/physmem_info.c b/arch/s390/boot/physmem_info.c index 0ebb2174713f22..e5248b637326b8 100644 --- a/arch/s390/boot/physmem_info.c +++ b/arch/s390/boot/physmem_info.c @@ -68,11 +68,11 @@ static int __diag260(unsigned long rx1, unsigned long rx2) ry = 0x10; /* storage configuration */ exception = 1; asm_inline volatile( - " diag %[rx],%[ry],0x260\n" - "0: lhi %[exc],0\n" + "0: diag %[rx],%[ry],0x260\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [exc] "+d" (exception), [ry] "+d" (ry) : [rx] "d" (rx.pair) : CC_CLOBBER_LIST("memory")); @@ -108,9 +108,9 @@ static int diag500_storage_limit(unsigned long *max_physmem_end) asm_inline volatile( " lghi %%r1,%[subcode]\n" " lghi %%r2,0\n" - " diag %%r2,%%r4,0x500\n" - "0: lgr %[slimit],%%r2\n" - EX_TABLE(0b, 0b) + "0: diag %%r2,%%r4,0x500\n" + "1: lgr %[slimit],%%r2\n" + EX_TABLE_INSN(0b, 1b) : [slimit] "=d" (storage_limit) : [subcode] "i" (DIAG500_SC_STOR_LIMIT) : "memory", "1", "2"); @@ -127,11 +127,11 @@ static int tprot(unsigned long addr) exception = 1; asm_inline volatile( - " tprot 0(%[addr]),0\n" - "0: lhi %[exc],0\n" + "0: tprot 0(%[addr]),0\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [exc] "+d" (exception) : [addr] "a" (addr) : CC_CLOBBER_LIST("memory")); diff --git a/arch/s390/boot/startup.c b/arch/s390/boot/startup.c index c1dcf8b1b5794c..5eb7a3e1f9f191 100644 --- a/arch/s390/boot/startup.c +++ b/arch/s390/boot/startup.c @@ -89,10 +89,10 @@ static void detect_diag9c(void) cpu = stap(); asm_inline volatile( - " diag %[cpu],%%r0,0x9c\n" - "0: lhi %[rc],0\n" + "0: diag %[cpu],%%r0,0x9c\n" + " lhi %[rc],0\n" "1:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [rc] "+d" (rc) : [cpu] "d" (cpu) : "cc", "memory"); @@ -150,10 +150,10 @@ static int cmma_test_essa(void) /* Test ESSA_GET_STATE */ asm_inline volatile( - " .insn rrf,0xb9ab0000,%[tmp],%[tmp],%[cmd],0\n" - "0: lhi %[rc],0\n" + "0: .insn rrf,0xb9ab0000,%[tmp],%[tmp],%[cmd],0\n" + " lhi %[rc],0\n" "1:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [rc] "+d" (rc), [tmp] "+d" (tmp) : [cmd] "i" (ESSA_GET_STATE) : "cc", "memory"); From 7e27bba542791cd553f2d3a5720092c86e3fc62c Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:42 +0200 Subject: [PATCH 1877/2286] s390/cio: Convert inline assembly to EX_TABLE_INSN Convert all cio exception table entries to use EX_TABLE_INSN. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- drivers/s390/cio/ioasm.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/s390/cio/ioasm.c b/drivers/s390/cio/ioasm.c index 8b06b234e1101c..4974e80b0568a6 100644 --- a/drivers/s390/cio/ioasm.c +++ b/drivers/s390/cio/ioasm.c @@ -24,11 +24,11 @@ static inline int __stsch(struct subchannel_id schid, struct schib *addr) exception = 1; asm_inline volatile( " lgr 1,%[r1]\n" - " stsch %[addr]\n" - "0: lhi %[exc],0\n" + "0: stsch %[addr]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, ccode), [addr] "=Q" (*addr), [exc] "+d" (exception) : [r1] "d" (r1) : CC_CLOBBER_LIST("1")); @@ -54,11 +54,11 @@ static inline int __msch(struct subchannel_id schid, struct schib *addr) exception = 1; asm_inline volatile( " lgr 1,%[r1]\n" - " msch %[addr]\n" - "0: lhi %[exc],0\n" + "0: msch %[addr]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, ccode), [exc] "+d" (exception) : [r1] "d" (r1), [addr] "Q" (*addr) : CC_CLOBBER_LIST("1")); @@ -108,11 +108,11 @@ static inline int __ssch(struct subchannel_id schid, union orb *addr) exception = 1; asm_inline volatile( " lgr 1,%[r1]\n" - " ssch %[addr]\n" - "0: lhi %[exc],0\n" + "0: ssch %[addr]\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, ccode), [exc] "+d" (exception) : [r1] "d" (r1), [addr] "Q" (*addr) : CC_CLOBBER_LIST("memory", "1")); @@ -179,11 +179,11 @@ int chsc(void *chsc_area) exception = 1; asm_inline volatile( - " .insn rre,0xb25f0000,%[chsc_area],0\n" - "0: lhi %[exc],0\n" + "0: .insn rre,0xb25f0000,%[chsc_area],0\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), "+m" (*(addr_type *)chsc_area), [exc] "+d" (exception) : [chsc_area] "d" (chsc_area) : CC_CLOBBER); From 4193d7529b90ea26ad68ea2ecb3c2a0b76ee39cf Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:43 +0200 Subject: [PATCH 1878/2286] s390/dasd: Convert inline assembly to EX_TABLE_INSN Convert the dasd_diag exception table entry to use EX_TABLE_INSN. Acked-by: Stefan Haberland Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- drivers/s390/block/dasd_diag.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/s390/block/dasd_diag.c b/drivers/s390/block/dasd_diag.c index af68c2ee659d08..ce391d35adf9b2 100644 --- a/drivers/s390/block/dasd_diag.c +++ b/drivers/s390/block/dasd_diag.c @@ -77,11 +77,11 @@ static inline int __dia250(void *iob, int cmd) exception = 1; asm_inline volatile( - " diag %[rx],%[cmd],0x250\n" - "0: lhi %[exc],0\n" + "0: diag %[rx],%[cmd],0x250\n" + " lhi %[exc],0\n" "1:\n" CC_IPM(cc) - EX_TABLE(0b,1b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [rx] "+d" (rx.pair), "+m" (*(addr_type *)iob), [exc] "+d" (exception) : [cmd] "d" (cmd) From 1faf2c15b9df7caa5757ffbce75518cfd35f9b2b Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:44 +0200 Subject: [PATCH 1879/2286] s390/diag_ftp: Convert inline assembly to EX_TABLE_INSN Convert the diag_ftp exception table entry to use EX_TABLE_INSN. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- drivers/s390/char/diag_ftp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/s390/char/diag_ftp.c b/drivers/s390/char/diag_ftp.c index a1e110c96f74f0..0a506559799ebc 100644 --- a/drivers/s390/char/diag_ftp.c +++ b/drivers/s390/char/diag_ftp.c @@ -106,11 +106,11 @@ static int diag_ftp_2c4(struct diag_ftp_ldfpl *fpl, diag_stat_inc(DIAG_STAT_X2C4); asm_inline volatile( - " diag %[addr],%[cmd],0x2c4\n" - "0: j 2f\n" + "0: diag %[addr],%[cmd],0x2c4\n" + " j 2f\n" "1: la %[rc],%[err]\n" "2:\n" - EX_TABLE(0b, 1b) + EX_TABLE_INSN(0b, 1b) : [rc] "=d" (rc), "+m" (*fpl) : [cmd] "0" (cmd), [addr] "d" (virt_to_phys(fpl)), [err] "i" (DIAG_FTP_RET_EPERM) From fcee842c0674c988255e906e8201c01659438f37 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:45 +0200 Subject: [PATCH 1880/2286] s390/sclp: Convert inline assembly to EX_TABLE_INSN Convert the sclp_service_call exception table entries to use EX_TABLE_INSN. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- drivers/s390/char/sclp.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/s390/char/sclp.h b/drivers/s390/char/sclp.h index b31a680e087147..b91d31f8e0ef22 100644 --- a/drivers/s390/char/sclp.h +++ b/drivers/s390/char/sclp.h @@ -320,11 +320,10 @@ static inline int sclp_service_call(sclp_cmdw_t command, void *sccb) exception = 1; asm_inline volatile( "0: .insn rre,0xb2200000,%[cmd],%[sccb]\n" /* servc */ - "1: lhi %[exc],0\n" - "2:\n" + " lhi %[exc],0\n" + "1:\n" CC_IPM(cc) - EX_TABLE(0b, 2b) - EX_TABLE(1b, 2b) + EX_TABLE_INSN(0b, 1b) : CC_OUT(cc, cc), [exc] "+d" (exception) : [cmd] "d" (command), [sccb] "a" (__pa(sccb)) : CC_CLOBBER_LIST("memory")); From aeca9e9d71a595b8f716d4a42ab5b0c7272c4862 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:46 +0200 Subject: [PATCH 1881/2286] s390/bpf: Simplify exception table entry handling With exception table lookup always pointing to the faulting instruction, only a single exception table entry per probe instruction is needed. Remove the second exception table entry, the extra nop instruction emitted between/after probe instructions, and simplify probe handling in the BPF JIT. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/net/bpf_jit_comp.c | 78 ++++++++++-------------------------- 1 file changed, 21 insertions(+), 57 deletions(-) diff --git a/arch/s390/net/bpf_jit_comp.c b/arch/s390/net/bpf_jit_comp.c index c4b47070bb590c..9f1632e068e12d 100644 --- a/arch/s390/net/bpf_jit_comp.c +++ b/arch/s390/net/bpf_jit_comp.c @@ -723,7 +723,6 @@ bool ex_handler_bpf(const struct exception_table_entry *x, struct pt_regs *regs) */ struct bpf_jit_probe { int prg; /* JITed instruction offset */ - int nop_prg; /* JITed nop offset */ int reg; /* Register to clear on exception */ int arena_reg; /* Register to use for arena addressing */ }; @@ -731,29 +730,10 @@ struct bpf_jit_probe { static void bpf_jit_probe_init(struct bpf_jit_probe *probe) { probe->prg = -1; - probe->nop_prg = -1; probe->reg = -1; probe->arena_reg = REG_0; } -/* - * Handlers of certain exceptions leave psw.addr pointing to the instruction - * directly after the failing one. Therefore, create two exception table - * entries and also add a nop in case two probing instructions come directly - * after each other. - */ -static void bpf_jit_probe_emit_nop(struct bpf_jit *jit, - struct bpf_jit_probe *probe) -{ - if (probe->prg == -1 || probe->nop_prg != -1) - /* The probe is not armed or nop is already emitted. */ - return; - - probe->nop_prg = jit->prg; - /* bcr 0,%0 */ - _EMIT2(0x0700); -} - static void bpf_jit_probe_load_pre(struct bpf_jit *jit, struct bpf_insn *insn, struct bpf_jit_probe *probe) { @@ -815,47 +795,36 @@ static int bpf_jit_probe_post(struct bpf_jit *jit, struct bpf_prog *fp, struct bpf_jit_probe *probe) { struct exception_table_entry *ex; - int i, prg; s64 delta; - u8 *insn; if (probe->prg == -1) /* The probe is not armed. */ return 0; - bpf_jit_probe_emit_nop(jit, probe); if (!fp->aux->extable) /* Do nothing during early JIT passes. */ return 0; - insn = jit->prg_buf + probe->prg; - if (WARN_ON_ONCE(probe->prg + insn_length(*insn) != probe->nop_prg)) - /* JIT bug - gap between probe and nop instructions. */ + if (WARN_ON_ONCE(jit->excnt >= fp->aux->num_exentries)) + /* Verifier bug - not enough entries. */ return -1; - for (i = 0; i < 2; i++) { - if (WARN_ON_ONCE(jit->excnt >= fp->aux->num_exentries)) - /* Verifier bug - not enough entries. */ - return -1; - ex = &fp->aux->extable[jit->excnt]; - /* Add extable entries for probe and nop instructions. */ - prg = i == 0 ? probe->prg : probe->nop_prg; - delta = jit->prg_buf + prg - (u8 *)&ex->insn; - if (WARN_ON_ONCE(delta < INT_MIN || delta > INT_MAX)) - /* JIT bug - code and extable must be close. */ - return -1; - ex->insn = delta; - /* - * Land on the current instruction. Note that the extable - * infrastructure ignores the fixup field; it is handled by - * ex_handler_bpf(). - */ - delta = jit->prg_buf + jit->prg - (u8 *)&ex->fixup; - if (WARN_ON_ONCE(delta < INT_MIN || delta > INT_MAX)) - /* JIT bug - landing pad and extable must be close. */ - return -1; - ex->fixup = delta; - ex->type = EX_TYPE_BPF; - ex->data = probe->reg; - jit->excnt++; - } + ex = &fp->aux->extable[jit->excnt]; + delta = jit->prg_buf + probe->prg - (u8 *)&ex->insn; + if (WARN_ON_ONCE(delta < INT_MIN || delta > INT_MAX)) + /* JIT bug - code and extable must be close. */ + return -1; + ex->insn = delta; + /* + * Land on the current instruction. Note that the extable + * infrastructure ignores the fixup field; it is handled by + * ex_handler_bpf(). + */ + delta = jit->prg_buf + jit->prg - (u8 *)&ex->fixup; + if (WARN_ON_ONCE(delta < INT_MIN || delta > INT_MAX)) + /* JIT bug - landing pad and extable must be close. */ + return -1; + ex->fixup = delta; + ex->type = EX_TYPE_BPF; + ex->data = probe->reg; + jit->excnt++; return 0; } @@ -1705,7 +1674,6 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp, EMIT6_DISP_LH(0xe3000000, is32 ? 0x0058 : 0x0004, REG_W0, REG_0, load_probe.arena_reg, off); - bpf_jit_probe_emit_nop(jit, &load_probe); /* Reuse {ly|lg}'s arena_reg for {csy|csg}. */ if (load_probe.prg != -1) { probe.prg = jit->prg; @@ -1716,7 +1684,6 @@ static noinline int bpf_jit_insn(struct bpf_jit *jit, struct bpf_prog *fp, /* 0: {csy|csg} %w0,%src,off(%arena) */ EMIT6_DISP_LH(0xeb000000, is32 ? 0x0014 : 0x0030, REG_W0, src_reg, probe.arena_reg, off); - bpf_jit_probe_emit_nop(jit, &probe); /* brc 4,0b */ EMIT4_PCREL_RIC(0xa7040000, 4, loop_start); /* {llgfr|lgr} %src,%w0 */ @@ -2364,9 +2331,6 @@ static struct bpf_binary_header *bpf_jit_alloc(struct bpf_jit *jit, */ fp->aux->num_exentries += 1; } - /* We need two entries per insn. */ - fp->aux->num_exentries *= 2; - code_size = roundup(jit->size, __alignof__(struct exception_table_entry)); extable_size = fp->aux->num_exentries * From 6d86f714aed7287e15178dee949d4b64bf5d7ed2 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Thu, 1 Oct 2026 12:32:47 +0200 Subject: [PATCH 1882/2286] s390/extable: Remove EX_TABLE macros and fallback extable search Now that all exception table entries point to the faulting instruction, rename all EX_TABLE* macros to EX_TABLE_INSN* and remove the EX_TABLE_INSN* aliases. Also remove the fallback extable search via instruction_pointer() which was only needed to handle the old EX_TABLE entries that pointed to the instruction after the faulting instruction. Add a comment above the EX_TABLE_INSN macros that documents the usage rule and describe the difference to the previous EX_TABLE macros. This might be helpful in case of backporting to old kernels. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/boot/pgm_check.c | 23 +++++------------ arch/s390/include/asm/asm-extable.h | 38 ++++++++++++++--------------- arch/s390/include/asm/extable.h | 25 ++++++------------- arch/s390/mm/extable.c | 2 -- 4 files changed, 32 insertions(+), 56 deletions(-) diff --git a/arch/s390/boot/pgm_check.c b/arch/s390/boot/pgm_check.c index 56d0be7e28232f..e5a454b2166974 100644 --- a/arch/s390/boot/pgm_check.c +++ b/arch/s390/boot/pgm_check.c @@ -51,31 +51,20 @@ static unsigned long boot_get_trap_ip(struct pt_regs *regs) return __rewind_psw(regs->psw, regs->int_code >> 16); } -static const struct exception_table_entry *search_boot_extable(unsigned long ip) +static bool ex_handler(struct pt_regs *regs) { const struct exception_table_entry *ex; + unsigned long ip = boot_get_trap_ip(regs); for (ex = __start___ex_table; ex < __stop___ex_table; ex++) { if (extable_insn(ex) != ip) continue; if (ex->type != EX_TYPE_FIXUP) - return NULL; - return ex; + return false; + regs->psw.addr = extable_fixup(ex); + return true; } - return NULL; -} - -static bool ex_handler(struct pt_regs *regs) -{ - const struct exception_table_entry *ex; - - ex = search_boot_extable(boot_get_trap_ip(regs)); - if (!ex) - ex = search_boot_extable(instruction_pointer(regs)); - if (!ex) - return false; - regs->psw.addr = extable_fixup(ex); - return true; + return false; } void do_pgm_check(struct pt_regs *regs) diff --git a/arch/s390/include/asm/asm-extable.h b/arch/s390/include/asm/asm-extable.h index 488bd10452b30c..fd43ecd3e4918b 100644 --- a/arch/s390/include/asm/asm-extable.h +++ b/arch/s390/include/asm/asm-extable.h @@ -65,41 +65,41 @@ stringify_in_c(.purgem extable_reg;) \ stringify_in_c(.previous) -#define EX_TABLE(_fault, _target) \ +/* + * EX_TABLE_INSN usage rules: + * + * _fault must point to the faulting instruction, never behind it. + * + * This differs from the former EX_TABLE convention, where usually two entries + * were needed. One that pointed to the faulting instruction (for nullifying + * exceptions), and a second one that pointed behind the instruction (for + * suppressing, completing, and terminating exceptions). + */ +#define EX_TABLE_INSN(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_FIXUP, __stringify(%%r0), __stringify(%%r0), 0) -#define EX_TABLE_AMODE31(_fault, _target) \ +#define EX_TABLE_INSN_AMODE31(_fault, _target) \ __EX_TABLE(.amode31.ex_table, _fault, _target, EX_TYPE_FIXUP, __stringify(%%r0), __stringify(%%r0), 0) -#define EX_TABLE_UA_FAULT(_fault, _target, _regerr) \ +#define EX_TABLE_INSN_UA_FAULT(_fault, _target, _regerr) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_FAULT, _regerr, _regerr, 0) -#define EX_TABLE_UA_LOAD_REG(_fault, _target, _regerr, _regzero) \ +#define EX_TABLE_INSN_UA_LOAD_REG(_fault, _target, _regerr, _regzero) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REG, _regerr, _regzero, 0) -#define EX_TABLE_UA_LOAD_REGPAIR(_fault, _target, _regerr, _regzero) \ +#define EX_TABLE_INSN_UA_LOAD_REGPAIR(_fault, _target, _regerr, _regzero) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REGPAIR, _regerr, _regzero, 0) -#define EX_TABLE_ZEROPAD(_fault, _target, _regdata, _regaddr) \ +#define EX_TABLE_INSN_ZEROPAD(_fault, _target, _regdata, _regaddr) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_ZEROPAD, _regdata, _regaddr, 0) -#define EX_TABLE_FPC(_fault, _target) \ +#define EX_TABLE_INSN_FPC(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_FPC, __stringify(%%r0), __stringify(%%r0), 0) -#define EX_TABLE_UA_MVCOS_TO(_fault, _target) \ +#define EX_TABLE_INSN_UA_MVCOS_TO(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_MVCOS_TO, __stringify(%%r0), __stringify(%%r0), 0) -#define EX_TABLE_UA_MVCOS_FROM(_fault, _target) \ +#define EX_TABLE_INSN_UA_MVCOS_FROM(_fault, _target) \ __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_MVCOS_FROM, __stringify(%%r0), __stringify(%%r0), 0) -#define EX_TABLE_INSN EX_TABLE -#define EX_TABLE_INSN_AMODE31 EX_TABLE_AMODE31 -#define EX_TABLE_INSN_UA_FAULT EX_TABLE_UA_FAULT -#define EX_TABLE_INSN_UA_LOAD_REG EX_TABLE_UA_LOAD_REG -#define EX_TABLE_INSN_UA_LOAD_REGPAIR EX_TABLE_UA_LOAD_REGPAIR -#define EX_TABLE_INSN_ZEROPAD EX_TABLE_ZEROPAD -#define EX_TABLE_INSN_FPC EX_TABLE_FPC -#define EX_TABLE_INSN_UA_MVCOS_TO EX_TABLE_UA_MVCOS_TO -#define EX_TABLE_INSN_UA_MVCOS_FROM EX_TABLE_UA_MVCOS_FROM - #endif /* __ASM_EXTABLE_H */ diff --git a/arch/s390/include/asm/extable.h b/arch/s390/include/asm/extable.h index af6ba52743e95f..5032c648781b14 100644 --- a/arch/s390/include/asm/extable.h +++ b/arch/s390/include/asm/extable.h @@ -5,25 +5,14 @@ #include #include -/* - * The exception table consists of three addresses: - * - * - Address of an instruction that is allowed to fault. - * - Address at which the program should continue. - * - Optional address of handler that takes pt_regs * argument and runs in - * interrupt context. - * - * No registers are modified, so it is entirely up to the continuation code - * to figure out what to do. - * - * All the routines below use bits of fixup code that are out of line - * with the main instruction path. This means when everything is well, - * we don't even have to jump over them. Further, they do not intrude - * on our cache or tlb entries. +/** + * struct exception_table_entry - entry in exception table + * @insn: Relative pointer to faulting instruction + * @fixup: Relative pointer to instruction where execution resumes + * @type: Exception type + * @data: handler-specific data */ - -struct exception_table_entry -{ +struct exception_table_entry { int insn, fixup; short type, data; }; diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c index 3d2ad6e853d1fd..d0a1ded21520c3 100644 --- a/arch/s390/mm/extable.c +++ b/arch/s390/mm/extable.c @@ -121,8 +121,6 @@ bool fixup_exception(struct pt_regs *regs) const struct exception_table_entry *ex; ex = s390_search_extables(get_trap_ip(regs)); - if (!ex) - ex = s390_search_extables(instruction_pointer(regs)); if (!ex) return false; switch (ex->type) { From 10f9cf286ec3e6f99569e5ce3e99afbeb77b962f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 13:42:46 +0200 Subject: [PATCH 1883/2286] s390/percpu: Use asm_inline for arch_this_cpu_add() When converting to preemptible percpu operations the arch_this_cpu_add() inline assemblies were not converted to use asm_inline instead of a plain asm statement. With all the macro usage the inline assemblies grow significantly, and the compiler may decide to not inline even small functions. This can be observed with gcc16 and a defconfig build where many calls to __srcu_read_lock_fast() are not inlined anymore, even though it is basically only a this_cpu_inc(). Use asm_inline to address this. Fixes: 18ec6c5d7ec6 ("s390/percpu: Use new percpu code section for arch_this_cpu_add()") Acked-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index c3995bd8d1de81..dfab41b43b8bcb 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -193,7 +193,7 @@ do { \ ptr__ = PERCPU_PTR(&(pcp)); \ if (__builtin_constant_p(val__) && \ ((szcast)val__ > -129) && ((szcast)val__ < 128)) { \ - asm volatile( \ + asm_inline volatile( \ __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ "%[pair__]","%N[pair__]") \ op2 " 0(%N[pair__]),%[val__]\n" \ @@ -206,7 +206,7 @@ do { \ "m" (((struct lowcore *)0)->percpu_offset) \ : "cc"); \ } else { \ - asm volatile( \ + asm_inline volatile( \ __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ "%[pair__]","%N[pair__]") \ op1 " %[old__],%[val__],0(%N[pair__])\n" \ From 3681abf4bfa29983ff6ff6a0f6b6e411d58337d4 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 13:42:47 +0200 Subject: [PATCH 1884/2286] s390/percpu: Drop unused output register from arch_this_cpu_add() The laa/laag instructions return the old memory content, but arch_this_cpu_add() does not need it. Instead of allocating a dedicated output register old__ just to discard the returned value, change the val__ constraint from "d" to "+&d" so it is used as input/output register. This reduces register pressure. Acked-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index dfab41b43b8bcb..fd0a3a9951ce7f 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -188,7 +188,7 @@ do { \ typedef typeof(pcp) pcp_op_T__; \ union register_pair rp__; \ pcp_op_T__ val__ = (val); \ - pcp_op_T__ old__, *ptr__; \ + pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ if (__builtin_constant_p(val__) && \ @@ -209,12 +209,12 @@ do { \ asm_inline volatile( \ __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ "%[pair__]","%N[pair__]") \ - op1 " %[old__],%[val__],0(%N[pair__])\n" \ + op1 " %[val__],%[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + : [val__] "+&d" (val__), [pair__] "=&a" (rp__.pair), \ "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), [ptr__] "a" (ptr__), \ + : [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ From 5af4c564803cd080361e9b99593c9cd6552cb73e Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 13:42:48 +0200 Subject: [PATCH 1885/2286] s390/percpu: Drop unused output register from arch_this_cpu_to_op() The lan/lang/lao/laog instructions return the old memory content, but arch_this_cpu_to_op() does not need it. Instead of allocating a dedicated output register old__ just to discard the returned value, change the val__ constraint from "d" to "+&d" so it is used as input/output register. This reduces register pressure. Acked-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index fd0a3a9951ce7f..7d87e0a262c372 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -257,18 +257,18 @@ do { \ typedef typeof(pcp) pcp_op_T__; \ union register_pair rp__; \ pcp_op_T__ val__ = (val); \ - pcp_op_T__ old__, *ptr__; \ + pcp_op_T__ *ptr__; \ \ ptr__ = PERCPU_PTR(&(pcp)); \ asm_inline volatile( \ __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ "%[pair__]","%N[pair__]") \ - op " %[old__],%[val__],0(%N[pair__])\n" \ + op " %[val__],%[val__],0(%N[pair__])\n" \ __PCPU_END("%[lcreg]") \ - : [old__] "=&d" (old__), [pair__] "=&a" (rp__.pair), \ + : [val__] "+&d" (val__), [pair__] "=&a" (rp__.pair), \ "+m" (*ptr__), \ "=m" (((struct lowcore *)0)->percpu_register) \ - : [val__] "d" (val__), [ptr__] "a" (ptr__), \ + : [ptr__] "a" (ptr__), \ [lcreg] "i" (LC_PERCPU_REGISTER), \ [lcoff] "i" (LC_PERCPU_OFFSET), \ "m" (((struct lowcore *)0)->percpu_offset) \ From 8f4778cf7f76e0da395b8015a25953f3ce05ab16 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 13:42:49 +0200 Subject: [PATCH 1886/2286] s390/percpu: Implement this_cpu_try_cmpxchg() Provide an s390 specific implementation of this_cpu_try_cmpxchg() for the four and eight byte cases, if the compiler supports flag output operands. This variant uses the condition code set by cs/csg, which avoids a compare instruction required by the generic variant and generates slightly better code. Acked-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/include/asm/percpu.h | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/arch/s390/include/asm/percpu.h b/arch/s390/include/asm/percpu.h index 7d87e0a262c372..7b7a8cb51daa23 100644 --- a/arch/s390/include/asm/percpu.h +++ b/arch/s390/include/asm/percpu.h @@ -5,6 +5,7 @@ #include #include #include +#include /* * s390 uses its own implementation for per cpu data, the offset of @@ -404,6 +405,44 @@ do { \ old__; \ }) +#ifdef __HAVE_ASM_FLAG_OUTPUTS__ + +#define arch_this_cpu_try_cmpxchg(pcp, ovalp, nval, op) \ +({ \ + typedef typeof(pcp) pcp_op_T__; \ + typeof(ovalp) ovalp__ = (ovalp); \ + pcp_op_T__ old__ = *ovalp__; \ + pcp_op_T__ new__ = (nval); \ + union register_pair rp__; \ + pcp_op_T__ *ptr__; \ + int cc__; \ + \ + ptr__ = PERCPU_PTR(&(pcp)); \ + asm_inline volatile( \ + __PCPU_BEGIN("%[lcreg]","%[lcoff]","%[ptr__]", \ + "%[pair__]","%N[pair__]") \ + op " %[old__],%[new__],0(%N[pair__])\n" \ + __PCPU_END("%[lcreg]") \ + : [old__] "+&d" (old__), [pair__] "=&a" (rp__.pair), \ + "+m" (*ptr__), "=@cc" (cc__), \ + "=m" (((struct lowcore *)0)->percpu_register) \ + : [new__] "d" (new__), [ptr__] "a" (ptr__), \ + [lcreg] "i" (LC_PERCPU_REGISTER), \ + [lcoff] "i" (LC_PERCPU_OFFSET), \ + "m" (((struct lowcore *)0)->percpu_offset) \ + : "memory"); \ + if (unlikely(cc__)) \ + *ovalp__ = old__; \ + likely(cc__ == 0); \ +}) + +#define this_cpu_try_cmpxchg_4(pcp, ovalp, nval) arch_this_cpu_try_cmpxchg(pcp, ovalp, nval, "cs") +#define this_cpu_try_cmpxchg_8(pcp, ovalp, nval) arch_this_cpu_try_cmpxchg(pcp, ovalp, nval, "csg") + +#define this_cpu_try_cmpxchg64(pcp, ovalp, nval) this_cpu_try_cmpxchg_8(pcp, ovalp, nval) + +#endif /* __HAVE_ASM_FLAG_OUTPUTS__ */ + #define arch_this_cpu_xchg_simple(pcp, nval) \ ({ \ typeof(pcp) *ptr__; \ From 737f5b4eaaa54edeb537687225ef14af2bde2589 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 2 Oct 2026 17:20:54 +0200 Subject: [PATCH 1887/2286] s390/entry: Last breaking event vs invalid kernel stack In case an invalid kernel stack is detected the last breaking event address is copied to the orig_gpr2 member of the generated pt_regs structure. Correct location would be last_break. Fix this. This adds help information to the printed messages: Before: Kernel stack pointer invalid ... Last Breaking-Event-Address: [<0000000000000000>] 0x0 After: Kernel stack pointer invalid ... Last Breaking-Event-Address: [<000001b802c1ae1c>] proc_reg_write+0x5c/0xd0 Reviewed-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/kernel/entry.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index f51ecd8e7d2168..459de56fb60839 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -623,7 +623,7 @@ SYM_CODE_START(stack_invalid) mvc __PT_R8(64,%r11),0(%r14) xc __PT_FLAGS(8,%r11),__PT_FLAGS(%r11) GET_LC %r2 - mvc __PT_ORIG_GPR2(8,%r11),__LC_PGM_LAST_BREAK(%r2) + mvc __PT_LAST_BREAK(8,%r11),__LC_PGM_LAST_BREAK(%r2) xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) lgr %r2,%r11 # pass pointer to pt_regs jg kernel_stack_invalid From 5cc8fe84f91175af10825d08c9b6fc88426d83ae Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Fri, 2 Oct 2026 17:20:55 +0200 Subject: [PATCH 1888/2286] s390/entry: Save lowcore on invalid kernel stack In case of an invalid kernel stack copy the current CPU's lowcore to stack_invalid_lowcore, so it is available to debug tools after a crash. The copy is helpful since the lowcore is modified e.g., when printing messages before a dump is taken. Keep it as unmodified as possible and as close to the event that caused register corruption. Reviewed-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- arch/s390/kernel/entry.S | 14 ++++++++++++++ arch/s390/kernel/entry.h | 4 ++++ arch/s390/kernel/traps.c | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 459de56fb60839..4242cd7ab2fe08 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -613,6 +613,8 @@ SYM_CODE_END(early_pgm_check_handler) * The synchronous or the asynchronous stack pointer is invalid. We are dead. * No need to properly save the registers, we are going to panic anyway. * Setup a pt_regs so that show_trace can provide a good call trace. + * Copy lowcore of the current CPU to stack_invalid_lowcore before it + * is modified by printing debug messages and crash handling. */ SYM_CODE_START(stack_invalid) GET_LC %r15 @@ -624,6 +626,18 @@ SYM_CODE_START(stack_invalid) xc __PT_FLAGS(8,%r11),__PT_FLAGS(%r11) GET_LC %r2 mvc __PT_LAST_BREAK(8,%r11),__LC_PGM_LAST_BREAK(%r2) + /* copy lowcore; skip if used by another CPU */ + larl %r4,stack_invalid_lowcore_used + lhi %r1,0 + lhi %r3,1 + cs %r1,%r3,0(%r4) + jne .Lskip + larl %r0,stack_invalid_lowcore + lghi %r1,LC_PAGES * PAGE_SIZE + lgr %r4,%r2 + lgr %r5,%r1 + mvcl %r0,%r4 +.Lskip: xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) lgr %r2,%r11 # pass pointer to pt_regs jg kernel_stack_invalid diff --git a/arch/s390/kernel/entry.h b/arch/s390/kernel/entry.h index dc84ab49725146..5d6685e5bf5f54 100644 --- a/arch/s390/kernel/entry.h +++ b/arch/s390/kernel/entry.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -64,6 +65,9 @@ long sys_s390_sthyi(unsigned long function_code, void __user *buffer, u64 __user unsigned long stack_alloc(void); void stack_free(unsigned long stack); +extern int stack_invalid_lowcore_used; +extern struct lowcore stack_invalid_lowcore; + extern char kprobes_insn_page[]; extern char _samode31[], _eamode31[]; diff --git a/arch/s390/kernel/traps.c b/arch/s390/kernel/traps.c index 83fa4a79480753..6b083b9584eb07 100644 --- a/arch/s390/kernel/traps.c +++ b/arch/s390/kernel/traps.c @@ -290,6 +290,12 @@ static void monitor_event_exception(struct pt_regs *regs) } } +/* + * Content of the faulting CPU's lowcore, copied by stack_invalid(). + */ +struct lowcore stack_invalid_lowcore; +int stack_invalid_lowcore_used; + void kernel_stack_invalid(struct pt_regs *regs) { /* From 8667061cf99394526dd9c0ea2662e6bbc7ae7848 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Tue, 13 Oct 2026 10:33:41 -0300 Subject: [PATCH 1889/2286] smb: client: require stable pages for SMB compression Like signing, SMB compression builds the write payload from the pagecache folios: should_compress() lets a write request through and smb_compress() compresses those folios on the way to the socket. If a buffered write mutates a folio while a write subrequest is still in flight, the compressed bytes no longer correspond to a consistent view of the data, and the server silently stores corrupted contents - worse than the signed case, which at least fails loudly with STATUS_ACCESS_DENIED. Mark the mapping for stable writes when a write may be compressed, i.e. the same condition should_compress() tests (compression negotiated and the share allows compressed data), so netfs_perform_write() waits for writeback to complete before modifying an in-flight folio. Fixes: f046d71e84e1 ("smb: client: insert compression check/call on write requests") Suggested-by: David Howells Signed-off-by: Paulo Alcantara Reviewed-by: David Howells Cc: Namjae Jeon Cc: Ronnie Sahlberg Cc: Shyam Prasad N Cc: Tom Talpey Cc: Bharath SM Cc: stable@vger.kernel.org --- fs/smb/client/inode.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 43a2d7982488aa..d94007deb5480a 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -88,7 +88,9 @@ static void cifs_set_ops(struct inode *inode) else inode->i_data.a_ops = &cifs_addr_ops; mapping_set_large_folios(inode->i_mapping); - if (tcon->ses->server->sign) + if (tcon->ses->server->sign || + (tcon->ses->server->compression.enabled && + (tcon->share_flags & SMB2_SHAREFLAG_COMPRESS_DATA))) mapping_set_stable_writes(inode->i_mapping); break; case S_IFDIR: From 1fc241cdeab276dc6c3ec14840faa706ed7712ff Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:20:35 -0700 Subject: [PATCH 1890/2286] xfs: check for termination during rmap btree walk LOLLM complains that we should check for a fatal signal *every* time we walk a record in the rmap btree as part of rebuilding the free space btrees. Fix that, and improve responsiveness to ^C of xfs_scrub. Cc: stable@vger.kernel.org # v6.8 Fixes: 4bdfd7d15747b1 ("xfs: repair free space btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 6e835240536181..9a09a9ea9c3089 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -224,7 +224,10 @@ xrep_abt_walk_rmap( void *priv) { struct xrep_abt *ra = priv; - int error; + int error = 0; + + if (xchk_should_terminate(ra->sc, &error)) + return error; /* Record all the OWN_AG blocks... */ if (rec->rm_owner == XFS_RMAP_OWN_AG) { From 4439b626fb5104204952aa2f169451688fb6be26 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:20:51 -0700 Subject: [PATCH 1891/2286] xfs: don't subtract excess usage twice when computing agf_btreeblks LOLLM noticed that the free space btree repair code calculates agf_btreeblks from the number of blocks we wrote to rebuild the free space btrees (minus the roots). That's correct. However, it observes that we subtract the "excess" reservation, which is not correct. Fix this accounting problem. Cc: stable@vger.kernel.org # v6.8 Fixes: 4bdfd7d15747b1 ("xfs: repair free space btrees") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 6 +----- fs/xfs/scrub/newbt.c | 13 ------------- fs/xfs/scrub/newbt.h | 1 - 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 9a09a9ea9c3089..05f27c927adfb9 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -660,15 +660,11 @@ xrep_abt_reset_counters( /* * Compute the contribution to agf_btreeblks for the new free space - * btrees. This is the computed btree size minus anything we didn't - * use. + * btrees. This is the computed btree size minus the root block. */ freesp_btreeblks += ra->new_bnobt.bload.nr_blocks - 1; freesp_btreeblks += ra->new_cntbt.bload.nr_blocks - 1; - freesp_btreeblks -= xrep_newbt_unused_blocks(&ra->new_bnobt); - freesp_btreeblks -= xrep_newbt_unused_blocks(&ra->new_cntbt); - /* * The AGF header contains extra information related to the free space * btrees, so we must update those fields here. diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index 584076b2a6eed9..a7554f925b7440 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -598,16 +598,3 @@ xrep_newbt_claim_block( /* Relog all the EFIs. */ return xrep_defer_finish(xnr->sc); } - -/* How many reserved blocks are unused? */ -unsigned int -xrep_newbt_unused_blocks( - struct xrep_newbt *xnr) -{ - struct xrep_newbt_resv *resv; - unsigned int unused = 0; - - list_for_each_entry(resv, &xnr->resv_list, list) - unused += resv->len - resv->used; - return unused; -} diff --git a/fs/xfs/scrub/newbt.h b/fs/xfs/scrub/newbt.h index 5ce785599287be..e5ebd3200c1849 100644 --- a/fs/xfs/scrub/newbt.h +++ b/fs/xfs/scrub/newbt.h @@ -71,6 +71,5 @@ void xrep_newbt_cancel(struct xrep_newbt *xnr); int xrep_newbt_commit(struct xrep_newbt *xnr); int xrep_newbt_claim_block(struct xfs_btree_cur *cur, struct xrep_newbt *xnr, union xfs_btree_ptr *ptr); -unsigned int xrep_newbt_unused_blocks(struct xrep_newbt *xnr); #endif /* __XFS_SCRUB_NEWBT_H__ */ From 4582327e7068194d55941b49dd66be211b9e3f90 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:21:07 -0700 Subject: [PATCH 1892/2286] xfs: abort directory tree repair whenever dl->aborted is set LOLLM observed that if the live update hooks for the directory tree repair code ever fail, they'll set dl->aborted, but then the main repair code path won't check it to exit early. This can cause incorrect repairs, so let's check for hook errors and abort the repair. Cc: stable@vger.kernel.org # v6.10 Fixes: 3f31406aef493b ("xfs: fix corruptions in the directory tree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree_repair.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c index 717ebbe4f91916..1de4d9f1b61657 100644 --- a/fs/xfs/scrub/dirtree_repair.c +++ b/fs/xfs/scrub/dirtree_repair.c @@ -575,6 +575,8 @@ xrep_dirtree_delete_path( if (!error && dl->stale) error = -ESTALE; + if (!error && dl->aborted) + error = -EIO; return error; } @@ -720,6 +722,11 @@ xrep_dirtree_adopt( error = -ESTALE; goto out_trans; } + if (dl->aborted) { + mutex_unlock(&dl->lock); + error = -EIO; + goto out_trans; + } error = xrep_dirtree_create_adoption_path(dl); mutex_unlock(&dl->lock); if (error) @@ -780,6 +787,8 @@ xrep_dirtree_move_to_orphanage( if (!error && dl->stale) error = -ESTALE; + if (!error && dl->aborted) + error = -EIO; return error; } From 2f528e4a8f89daa6d0cc64b044f6f94c018fe9ae Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:21:22 -0700 Subject: [PATCH 1893/2286] xfs: reattach dquots after changing inode ids LOLLM noticed that xrep_inode_ids can change the user, group, or project id of any given file. When this happens, we need to detach any existing incore dquot and attach the correct ones. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 133 +++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index e1a7a2729b479d..c81dfb0485e219 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -41,6 +41,8 @@ #include "xfs_rtgroup.h" #include "xfs_rtrmap_btree.h" #include "xfs_rtrefcount_btree.h" +#include "xfs_dquot_item.h" +#include "xfs_dquot.h" #include "scrub/xfs_scrub.h" #include "scrub/scrub.h" #include "scrub/common.h" @@ -1757,6 +1759,20 @@ xrep_inode_ids( trace_xrep_inode_ids(sc); + /* + * We changed one of the file IDs, so we must detach the dquots to + * prevent further changes from being accounted to them, and try to + * attach the dquot for the new id to absorb any other block usage + * changes. We can't do that right now because the existing dquots + * might be attached to the scrub transaction and we're in the middle + * of fixing the inode, so we'll switch the incore dquot pointers + * later. + * + * Schedule a quotacheck so the accounting discrepancies will get fixed + * eventually. We'll have to repeat this later when we do the actual + * switch. + */ + if (!uid_valid(VFS_I(sc->ip)->i_uid)) { i_uid_write(VFS_I(sc->ip), 0); dirty = true; @@ -2066,6 +2082,113 @@ xrep_inode_unlinked( return 0; } +#ifdef CONFIG_XFS_QUOTA +static inline bool +xrep_inode_need_reset_dquots( + struct xfs_scrub *sc) +{ + struct xfs_mount *mp = sc->mp; + struct xfs_inode *ip = sc->ip; + + if (!XFS_IS_QUOTA_ON(mp)) + return false; + if (xfs_is_quota_inode(&mp->m_sb, I_INO(ip))) + return false; + if (xfs_is_metadir_inode(ip)) + return false; + + if (XFS_IS_UQUOTA_ON(sc->mp) && + (!ip->i_udquot || ip->i_udquot->q_id != i_uid_read(VFS_I(ip)))) + return true; + if (XFS_IS_GQUOTA_ON(sc->mp) && + (!ip->i_gdquot || ip->i_gdquot->q_id != i_gid_read(VFS_I(ip)))) + return true; + if (XFS_IS_PQUOTA_ON(sc->mp) && + (!ip->i_pdquot || ip->i_pdquot->q_id != ip->i_projid)) + return true; + + return false; +} + +/* Switch dquot, return dqtype if we need quotacheck */ +static xfs_dqtype_t +xrep_inode_switch_dquot( + struct xfs_scrub *sc, + xfs_dqtype_t type, + struct xfs_dquot **dqp) +{ + xfs_dqtype_t ret = 0; + xfs_dqid_t id = xfs_qm_id_for_quotatype(sc->ip, type); + + if (*dqp != NULL && (*dqp)->q_id != id) { + xfs_qm_dqrele(*dqp); + *dqp = NULL; + } + + if (!*dqp) { + xfs_qm_dqget_inode(sc->ip, type, true, dqp); + ret = type; + } + return ret; +} + +static int +xrep_inode_reset_dquots( + struct xfs_scrub *sc) +{ + xfs_dqtype_t need = 0; + int error; + + /* + * If we changed the file IDs, then we need to drop the transaction so + * that we can detach the dquots and attach new ones. + * + * Commit the scrub transaction so that there are no dangling + * references to any dquots that might get detached, and retain the + * ILOCK, as required by the dqattach code. + * + * Note that dqattach can cycle the ILOCK when creating a new dquot, so + * this must be the very last thing that the repair function does. + */ + error = xrep_trans_commit(sc); + if (error) + return error; + + if (XFS_IS_UQUOTA_ON(sc->mp)) + need |= xrep_inode_switch_dquot(sc, XFS_DQTYPE_USER, + &sc->ip->i_udquot); + if (XFS_IS_GQUOTA_ON(sc->mp)) + need |= xrep_inode_switch_dquot(sc, XFS_DQTYPE_GROUP, + &sc->ip->i_gdquot); + if (XFS_IS_PQUOTA_ON(sc->mp)) + need |= xrep_inode_switch_dquot(sc, XFS_DQTYPE_PROJ, + &sc->ip->i_pdquot); + + xchk_iunlock(sc, XFS_ILOCK_EXCL); + + /* + * Now that we've updated the dquots, force quotacheck once again. + * In theory we could be racing against a concurrent quotacheck. + * Note that we can't hold the ILOCK when allocating a transaction. + */ + error = xchk_trans_alloc(sc, 0); + if (error) + return error; + + if (need & XFS_DQTYPE_USER) + xrep_force_quotacheck(sc, XFS_DQTYPE_USER); + if (need & XFS_DQTYPE_GROUP) + xrep_force_quotacheck(sc, XFS_DQTYPE_GROUP); + if (need & XFS_DQTYPE_PROJ) + xrep_force_quotacheck(sc, XFS_DQTYPE_PROJ); + + return 0; +} +#else +# define xrep_inode_need_reset_dquots(sc) (false) +# define xrep_inode_reset_dquots(sc) (0) +#endif + /* Repair an inode's fields. */ int xrep_inode( @@ -2120,5 +2243,13 @@ xrep_inode( if (error) return error; - return xrep_defer_finish(sc); + error = xrep_defer_finish(sc); + if (error) + return error; + + /* Must come last */ + if (xrep_inode_need_reset_dquots(sc)) + return xrep_inode_reset_dquots(sc); + + return 0; } From ddf4fe30c3fd7d37cf9bad3887ddb69984954cf4 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:21:38 -0700 Subject: [PATCH 1894/2286] xfs: never reset the default quota timer LOLLM notices that xrep_quota_fix_timer doesn't check the dquot id, which means that it can inadvertently reset the default quota timer when we really meant to ensure that a (non-root) quota overage timeout has a sane timestamp. Fix that. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index fd14d81ee4e9e8..69d5ba85479189 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -272,6 +272,9 @@ xrep_quota_fix_timer( time64_t new_timer; uint32_t t; + /* never reset the default quota timer */ + if (ddq->d_id == 0) + return; if (!soft || count <= soft || *timer != 0) return; From 6f2792720673f569fa3e1f68181b9d6fd26802b5 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:21:54 -0700 Subject: [PATCH 1895/2286] xfs: fix correction of unwritten extents in quota file data fork LOLLM noticed a couple of problems with the code that fixes unwritten mappings in the quota file's data fork: first, we should zero the ondisk contents as part of converting them to "unwritten" just in case we crash shortly after the conversion. Second, and more importantly, it noticed that we fail to resynchronize the iext tree cursor after modifying the iext tree. If the tree shape changes, we could be in a world of hurt. Third, we should force a quotacheck if we reset an ondisk dquot. Fix all issues. Cc: stable@vger.kernel.org # v6.8 Fixes: a5b91555403e3a ("xfs: repair quotas") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/quota_repair.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/quota_repair.c b/fs/xfs/scrub/quota_repair.c index 69d5ba85479189..88e341d6d1af3d 100644 --- a/fs/xfs/scrub/quota_repair.c +++ b/fs/xfs/scrub/quota_repair.c @@ -440,7 +440,9 @@ xrep_quota_data_fork( /* Check for data fork problems that apply only to quota files. */ max_dqid_off = XFS_DQ_ID_MAX / qi->qi_dqperchunk; ifp = xfs_ifork_ptr(sc->ip, XFS_DATA_FORK); - for_each_xfs_iext(ifp, &icur, &irec) { + xfs_iext_first(ifp, &icur); + + while (xfs_iext_get_extent(ifp, &icur, &irec)) { if (isnullstartblock(irec.br_startblock)) { error = -EFSCORRUPTED; goto out; @@ -464,16 +466,35 @@ xrep_quota_data_fork( error = xfs_bmapi_write(sc->tp, sc->ip, irec.br_startoff, irec.br_blockcount, - XFS_BMAPI_CONVERT, 0, &nrec, &nmap); + XFS_BMAPI_CONVERT | XFS_BMAPI_ZERO, 0, + &nrec, &nmap); if (error) goto out; ASSERT(nrec.br_startoff == irec.br_startoff); ASSERT(nrec.br_blockcount == irec.br_blockcount); + /* + * We just zeroed the ondisk dquot, so counts could be + * wrong. Schedule a quotacheck to fix that. + */ + xrep_force_quotacheck(sc, dqtype); error = xfs_defer_finish(&sc->tp); if (error) goto out; + + /* + * Set cursor to the next block past the extent we just + * converted because bmapi write could have merged + * records. + */ + if (!xfs_iext_lookup_extent(sc->ip, ifp, + irec.br_startoff + irec.br_blockcount, + &icur, &irec)) + break; + continue; } + + xfs_iext_next(ifp, &icur); } if (!joined) { From ce9098338a7e1a77246684242d10779fc4d4a539 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:22:09 -0700 Subject: [PATCH 1896/2286] xfs: always report inode repair setup failures LOLLM noticed that xchk_setup_inode silently discards errors being returned by xrep_setup_inode. Fix that, and fix the stub macro so that it actually "returns" a value. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode.c | 2 +- fs/xfs/scrub/repair.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 46e9bf4a43179e..ac42867acbc4c9 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -212,7 +212,7 @@ xchk_setup_inode( * make repairs to the ondisk inode buffer. */ if (xchk_could_repair(sc)) - xrep_setup_inode(sc, &imap); + return xrep_setup_inode(sc, &imap); return 0; out_cancel: diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index c1ba462e4426f6..a248f9f039241b 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -240,7 +240,7 @@ xrep_setup_nothing( #define xrep_setup_rtrmapbt xrep_setup_nothing #define xrep_setup_rtrefcountbt xrep_setup_nothing -#define xrep_setup_inode(sc, imap) ((void)0) +#define xrep_setup_inode(sc, imap) (0) static inline int xrep_setup_symlink(struct xfs_scrub *sc, unsigned int *x) { From 73b7276b8a4b5eb272f3c3b36ea3042e786f4741 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:22:25 -0700 Subject: [PATCH 1897/2286] xfs: range-check bc_levels array access in scrub tracepoints LOLLM noticed xchk_btree()'s odd habit of passing cur->bc_nlevels to xchk_btree_ptr_ok when we're checking the pointer to the root block at the start of the btree scan. This enables us to skip checking the pointer for an inode btree's root (since it doesn't have one) but doesn't handle per-AG btrees quite correctly. There isn't a problem for __xfs_btree_check_ptr itself, but the corruption tracepoint naively walks off the end of bc_level[]. Fix that by adding a helper to do an implicit range check. Cc: stable@vger.kernel.org # v5.16 Fixes: 6ca444cfd66354 ("xfs: prepare xfs_btree_cur for dynamic cursor heights") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/trace.c | 13 +++++++++++++ fs/xfs/scrub/trace.h | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/fs/xfs/scrub/trace.c b/fs/xfs/scrub/trace.c index e5efc680651fce..92a70e4b19d99a 100644 --- a/fs/xfs/scrub/trace.c +++ b/fs/xfs/scrub/trace.c @@ -42,6 +42,9 @@ xchk_btree_cur_fsbno( struct xfs_btree_cur *cur, int level) { + if (level < 0) + return NULLFSBLOCK; + if (level < cur->bc_nlevels && cur->bc_levels[level].bp) return XFS_DADDR_TO_FSB(cur->bc_mp, xfs_buf_daddr(cur->bc_levels[level].bp)); @@ -53,6 +56,16 @@ xchk_btree_cur_fsbno( return NULLFSBLOCK; } +static inline int +xchk_btree_cur_ptr( + const struct xfs_btree_cur *cur, + int level) +{ + if (level >= 0 && level < cur->bc_nlevels) + return cur->bc_levels[level].ptr; + return -1; +} + /* * We include this last to have the helpers above available for the trace * event implementations. diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index cb85f75ce10180..6b14b149b6000f 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -577,7 +577,7 @@ TRACE_EVENT(xchk_btree_op_error, __entry->level = level; __entry->agno = XFS_FSB_TO_AGNO(cur->bc_mp, fsbno); __entry->bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno); - __entry->ptr = cur->bc_levels[level].ptr; + __entry->ptr = xchk_btree_cur_ptr(cur, level); __entry->error = error; __entry->ret_ip = ret_ip; ), @@ -618,7 +618,7 @@ TRACE_EVENT(xchk_ifork_btree_op_error, __entry->type = sc->sm->sm_type; __assign_str(name); __entry->level = level; - __entry->ptr = cur->bc_levels[level].ptr; + __entry->ptr = xchk_btree_cur_ptr(cur, level); __entry->agno = XFS_FSB_TO_AGNO(cur->bc_mp, fsbno); __entry->bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno); __entry->error = error; @@ -660,7 +660,7 @@ TRACE_EVENT(xchk_btree_error, __entry->level = level; __entry->agno = XFS_FSB_TO_AGNO(cur->bc_mp, fsbno); __entry->bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno); - __entry->ptr = cur->bc_levels[level].ptr; + __entry->ptr = xchk_btree_cur_ptr(cur, level); __entry->ret_ip = ret_ip; ), TP_printk("dev %d:%d type %s %sbt level %d ptr %d agno 0x%x agbno 0x%x ret_ip %pS", @@ -700,7 +700,7 @@ TRACE_EVENT(xchk_ifork_btree_error, __entry->level = level; __entry->agno = XFS_FSB_TO_AGNO(cur->bc_mp, fsbno); __entry->bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno); - __entry->ptr = cur->bc_levels[level].ptr; + __entry->ptr = xchk_btree_cur_ptr(cur, level); __entry->ret_ip = ret_ip; ), TP_printk("dev %d:%d ino 0x%llx fork %s type %s %sbt level %d ptr %d agno 0x%x agbno 0x%x ret_ip %pS", @@ -740,7 +740,7 @@ DECLARE_EVENT_CLASS(xchk_sbtree_class, __entry->bno = XFS_FSB_TO_AGBNO(cur->bc_mp, fsbno); __entry->level = level; __entry->nlevels = cur->bc_nlevels; - __entry->ptr = cur->bc_levels[level].ptr; + __entry->ptr = xchk_btree_cur_ptr(cur, level); ), TP_printk("dev %d:%d type %s %sbt agno 0x%x agbno 0x%x level %d nlevels %d ptr %d", MAJOR(__entry->dev), MINOR(__entry->dev), From fd012955641881169a9f01a00f656f6ca73842ec Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:22:41 -0700 Subject: [PATCH 1898/2286] xfs: reject absurdly large xattr value lengths in xchk_xattr_actor LOLLM suggests that we shouldn't try to allocate absurdly large buffers for xattr value checks because that will push reclaim very hard, never succeed, and is pointless because nobody can create xattrs that large. Since the xattr repair code ignores values larger than XATTR_SIZE_MAX, we can flag the corruption and return early. Cc: stable@vger.kernel.org # v5.3 Fixes: 6d6ccedd76823c ("xfs: only allocate memory for scrubbing attributes when we need it") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr.c | 6 ++++++ fs/xfs/scrub/parent_repair.c | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c index 7600c711362583..842d65834afbc9 100644 --- a/fs/xfs/scrub/attr.c +++ b/fs/xfs/scrub/attr.c @@ -216,6 +216,12 @@ xchk_xattr_actor( return -ECANCELED; } + /* Don't even try to handle a bizarrely large xattr. */ + if (valuelen > XATTR_SIZE_MAX) { + xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, args.blkno); + return -ECANCELED; + } + /* * Try to allocate enough memory to extract the attr value. If that * doesn't work, return -EDEADLOCK as a signal to try again with a diff --git a/fs/xfs/scrub/parent_repair.c b/fs/xfs/scrub/parent_repair.c index f643724910f954..6a3c0a190ff995 100644 --- a/fs/xfs/scrub/parent_repair.c +++ b/fs/xfs/scrub/parent_repair.c @@ -910,6 +910,10 @@ xrep_parent_fetch_xattr_remote( }; int error; + /* Don't even try to handle a bizarrely large xattr. */ + if (valuelen > XATTR_SIZE_MAX) + return -EFSCORRUPTED; + /* * If we need a larger value buffer, try to allocate one. If that * fails, return with -EDEADLOCK to try harder. From 9eb04a6d41c7e474d4665118895c3c2d8e5a2741 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:22:56 -0700 Subject: [PATCH 1899/2286] xfs: break out xattr entry checks into separate functions We're going to add more xattr entry checking code in xchk_xattr_entry so break these out into separate functions so that we don't go crazy indenting. There's a slight behavior change here where we skip updating the usedmap if we've decided the xattr structure is corrupt. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr.c | 90 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c index 842d65834afbc9..f7c00ea1af5d31 100644 --- a/fs/xfs/scrub/attr.c +++ b/fs/xfs/scrub/attr.c @@ -293,6 +293,66 @@ xchk_xattr_set_map( return ret; } +static unsigned int +xchk_xattr_local_entry( + struct xchk_da_btree *ds, + int level, + char *buf_end, + struct xfs_attr_leafblock *leaf, + int idx) +{ + struct xfs_attr_leaf_name_local *lentry = + xfs_attr3_leaf_name_local(leaf, idx); + char *name_end; + unsigned int namesize; + + namesize = xfs_attr_leaf_entsize_local(lentry->namelen, + be16_to_cpu(lentry->valuelen)); + name_end = (char *)lentry + namesize; + if (lentry->namelen == 0) { + xchk_da_set_corrupt(ds, level); + return 0; + } + if (name_end > buf_end) { + xchk_da_set_corrupt(ds, level); + return 0; + } + + return namesize; +} + +static unsigned int +xchk_xattr_remote_entry( + struct xchk_da_btree *ds, + int level, + char *buf_end, + struct xfs_attr_leafblock *leaf, + int idx, + struct xfs_attr_leaf_entry *ent) +{ + struct xfs_attr_leaf_name_remote *rentry = + xfs_attr3_leaf_name_remote(leaf, idx); + char *name_end; + unsigned int namesize; + + namesize = xfs_attr_leaf_entsize_remote(rentry->namelen); + name_end = (char *)rentry + namesize; + if (rentry->namelen == 0) { + xchk_da_set_corrupt(ds, level); + return 0; + } + if (rentry->valueblk == 0 && !(ent->flags & XFS_ATTR_INCOMPLETE)) { + xchk_da_set_corrupt(ds, level); + return 0; + } + if (name_end > buf_end) { + xchk_da_set_corrupt(ds, level); + return 0; + } + + return namesize; +} + /* * Check this leaf entry's relations to everything else. * Returns the number of bytes used for the name/value data. @@ -311,9 +371,6 @@ xchk_xattr_entry( { struct xfs_mount *mp = ds->state->mp; struct xchk_xattr_buf *ab = ds->sc->buf; - char *name_end; - struct xfs_attr_leaf_name_local *lentry; - struct xfs_attr_leaf_name_remote *rentry; unsigned int nameidx; unsigned int namesize; @@ -333,25 +390,14 @@ xchk_xattr_entry( } /* Check the name information. */ - if (ent->flags & XFS_ATTR_LOCAL) { - lentry = xfs_attr3_leaf_name_local(leaf, idx); - namesize = xfs_attr_leaf_entsize_local(lentry->namelen, - be16_to_cpu(lentry->valuelen)); - name_end = (char *)lentry + namesize; - if (lentry->namelen == 0) - xchk_da_set_corrupt(ds, level); - } else { - rentry = xfs_attr3_leaf_name_remote(leaf, idx); - namesize = xfs_attr_leaf_entsize_remote(rentry->namelen); - name_end = (char *)rentry + namesize; - if (rentry->namelen == 0) - xchk_da_set_corrupt(ds, level); - if (rentry->valueblk == 0 && - !(ent->flags & XFS_ATTR_INCOMPLETE)) - xchk_da_set_corrupt(ds, level); - } - if (name_end > buf_end) - xchk_da_set_corrupt(ds, level); + if (ent->flags & XFS_ATTR_LOCAL) + namesize = xchk_xattr_local_entry(ds, level, buf_end, leaf, + idx); + else + namesize = xchk_xattr_remote_entry(ds, level, buf_end, leaf, + idx, ent); + if (!namesize) + return; if (!xchk_xattr_set_map(ds->sc, ab->usedmap, nameidx, namesize)) xchk_da_set_corrupt(ds, level); From 91c41f9524906a8ebd2ae68c98a07b4bd5255ddb Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:23:12 -0700 Subject: [PATCH 1900/2286] xfs: check for buffer overruns when examining local/remote xattr entries LOLLM noticed that the xattr scrub and repair code can walk off the end of the buffer if the xattr entry record starts so close to the end of the buffer that there's not even space for the entry structure itself. Strengthen the bounds checking here. Cc: stable@vger.kernel.org # v4.15 Fixes: 13791d3b833428 ("xfs: scrub extended attribute leaf space") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/attr.c | 12 ++++++++++++ fs/xfs/scrub/attr_repair.c | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/fs/xfs/scrub/attr.c b/fs/xfs/scrub/attr.c index f7c00ea1af5d31..f909e2b25158a8 100644 --- a/fs/xfs/scrub/attr.c +++ b/fs/xfs/scrub/attr.c @@ -306,6 +306,12 @@ xchk_xattr_local_entry( char *name_end; unsigned int namesize; + name_end = (char *)lentry + offsetof(typeof(*lentry), nameval[0]); + if (name_end > buf_end) { + xchk_da_set_corrupt(ds, level); + return 0; + } + namesize = xfs_attr_leaf_entsize_local(lentry->namelen, be16_to_cpu(lentry->valuelen)); name_end = (char *)lentry + namesize; @@ -335,6 +341,12 @@ xchk_xattr_remote_entry( char *name_end; unsigned int namesize; + name_end = (char *)rentry + offsetof(typeof(*rentry), name[0]); + if (name_end > buf_end) { + xchk_da_set_corrupt(ds, level); + return 0; + } + namesize = xfs_attr_leaf_entsize_remote(rentry->namelen); name_end = (char *)rentry + namesize; if (rentry->namelen == 0) { diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c index f2868fb4d27ab0..720090895c20f5 100644 --- a/fs/xfs/scrub/attr_repair.c +++ b/fs/xfs/scrub/attr_repair.c @@ -285,6 +285,7 @@ xrep_xattr_salvage_local_attr( struct xfs_attr_leaf_name_local *lentry) { struct xchk_xattr_buf *ab = rx->sc->buf; + char *name_end; unsigned char *value; unsigned int valuelen; unsigned int namesize; @@ -293,10 +294,14 @@ xrep_xattr_salvage_local_attr( * Decode the leaf local entry format. If something seems wrong, we * junk the attribute. */ + name_end = (char *)lentry + offsetof(typeof(*lentry), nameval[0]); + if (name_end > buf_end) + return 0; value = &lentry->nameval[lentry->namelen]; valuelen = be16_to_cpu(lentry->valuelen); namesize = xfs_attr_leaf_entsize_local(lentry->namelen, valuelen); - if ((char *)lentry + namesize > buf_end) + name_end = (char *)lentry + namesize; + if (name_end > buf_end) return 0; if (!xrep_xattr_want_salvage(rx, ent->flags, lentry->nameval, lentry->namelen, value, valuelen)) @@ -333,8 +338,8 @@ xrep_xattr_salvage_remote_attr( .attr_filter = ent->flags & XFS_ATTR_NSP_ONDISK_MASK, .namelen = rentry->namelen, .name = rentry->name, - .valuelen = be32_to_cpu(rentry->valuelen), }; + char *name_end; unsigned int namesize; int error; @@ -343,9 +348,14 @@ xrep_xattr_salvage_remote_attr( * junk the attribute. Note that we should never find a zero-length * remote attribute value. */ + name_end = (char *)rentry + offsetof(typeof(*rentry), name[0]); + if (name_end > buf_end) + return 0; namesize = xfs_attr_leaf_entsize_remote(rentry->namelen); - if ((char *)rentry + namesize > buf_end) + name_end = (char *)rentry + namesize; + if (name_end > buf_end) return 0; + args.valuelen = be32_to_cpu(rentry->valuelen); if (args.valuelen == 0 || !xrep_xattr_want_salvage(rx, ent->flags, rentry->name, rentry->namelen, NULL, args.valuelen)) From 00f32fdf54983371d39162efef7df693e2842ffb Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:23:27 -0700 Subject: [PATCH 1901/2286] xfs: clarify various parts of the bitmap API LOLLM pointed out some underspecified parts of the xbitmap{32,64} API. Specifically, the behavior of set/clear/test functions when the length parameter is zero is not well defined, nor is what happens when someone tries to disunion a bitmap with itself. None of these situations are expected, but we'll define zero-length set and clear to be nops, and the rest to be checked by assertions. Cc: stable@vger.kernel.org # v6.8 Fixes: 6ece924b952262 ("xfs: create separate structures and code for u32 bitmaps") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bitmap.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fs/xfs/scrub/bitmap.c b/fs/xfs/scrub/bitmap.c index 08f216d26ec632..1da99e179baa42 100644 --- a/fs/xfs/scrub/bitmap.c +++ b/fs/xfs/scrub/bitmap.c @@ -77,6 +77,9 @@ xbitmap64_clear( struct xbitmap64_node *new_bn; uint64_t last = start + len - 1; + if (!len) + return 0; + while ((bn = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last))) { if (bn->bn_start < start && bn->bn_last > last) { uint64_t old_last = bn->bn_last; @@ -127,6 +130,9 @@ xbitmap64_set( uint64_t last = start + len - 1; int error; + if (!len) + return 0; + /* Is this whole range already set? */ left = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) @@ -223,6 +229,8 @@ xbitmap64_disunion( struct xbitmap64_node *bn; int error; + ASSERT(bitmap != sub); + if (xbitmap64_empty(bitmap) || xbitmap64_empty(sub)) return 0; @@ -287,6 +295,8 @@ xbitmap64_test( struct xbitmap64_node *bn; uint64_t last = start + *len - 1; + ASSERT(*len > 0); + bn = xbitmap64_tree_iter_first(&bitmap->xb_root, start, last); if (!bn) return false; @@ -357,6 +367,9 @@ xbitmap32_clear( struct xbitmap32_node *new_bn; uint32_t last = start + len - 1; + if (!len) + return 0; + while ((bn = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last))) { if (bn->bn_start < start && bn->bn_last > last) { uint32_t old_last = bn->bn_last; @@ -407,6 +420,9 @@ xbitmap32_set( uint32_t last = start + len - 1; int error; + if (!len) + return 0; + /* Is this whole range already set? */ left = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (left && left->bn_start <= start && left->bn_last >= last) @@ -503,6 +519,8 @@ xbitmap32_disunion( struct xbitmap32_node *bn; int error; + ASSERT(bitmap != sub); + if (xbitmap32_empty(bitmap) || xbitmap32_empty(sub)) return 0; @@ -567,6 +585,8 @@ xbitmap32_test( struct xbitmap32_node *bn; uint32_t last = start + *len - 1; + ASSERT(*len > 0); + bn = xbitmap32_tree_iter_first(&bitmap->xb_root, start, last); if (!bn) return false; From e09438cab72c2c6a32665829d4186155346bfee8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 08:23:43 -0700 Subject: [PATCH 1902/2286] xfs: complain loudly when dirtree checker finds broken links LOLLM observed that the directory tree walk code bails out if it finds broken parent pointers along the way. However, it flattens the return code to zero, which means that we can end up in the repair code having not completed the scan. We should never proceed with a repair in this state, so let's mark the scrub incomplete and exit. Cc: stable@vger.kernel.org # v6.10 Fixes: 928b721a11789a ("xfs: teach online scrub to find directory tree structure problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dirtree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dirtree.c b/fs/xfs/scrub/dirtree.c index 887383d2e94104..11a9dea03d87ea 100644 --- a/fs/xfs/scrub/dirtree.c +++ b/fs/xfs/scrub/dirtree.c @@ -860,7 +860,8 @@ xchk_dirtree_find_paths_to_root( * A parent pointer of @sc->ip is bad, don't * bother continuing. */ - break; + xchk_set_incomplete(sc); + return error; } if (error == -ESTALE) { /* This had better be an invalidation. */ @@ -981,6 +982,7 @@ xchk_dirtree( * parent pointers are corrupt; this scan cannot be completed * without full information. */ + xchk_set_incomplete(sc); xchk_ip_xref_set_corrupt(sc, sc->ip); error = 0; goto out_scanlock; From b17d799ce49bfaad68be5ff3df995b3bd021a9d7 Mon Sep 17 00:00:00 2001 From: Weigang He Date: Sat, 26 Sep 2026 14:27:57 +1000 Subject: [PATCH 1903/2286] thermal/drivers/sprd: validate the sensor id from the device tree sprd_thm_probe() reads each sensor child node's "reg" property into sen->id and uses it without validation: to compute the sensor's register offsets in sprd_thm_sensor_init(), as the thermal zone id, and finally as an index into thm->sensor[], which has SPRD_THM_MAX_SENSOR (8) entries: ret = of_property_read_u32(sen_child, "reg", &sen->id); ... thm->sensor[sen->id] = sen; Only the number of sensor nodes is checked against SPRD_THM_MAX_SENSOR. A "reg" value of 8 or more, or one that is negative once stored in the int sen->id, makes the register offsets point outside the sensor range and the final store land outside thm->sensor[], overwriting the fields that follow it in struct sprd_thermal_data or other memory. Reject an out-of-range id right after reading it, before it is used. This does not address sparse ids, such as a single sensor with id 7: they leave holes in thm->sensor[] that the later loops over 0..nr_sensors-1 dereference. That needs a separate fix. Found by static analysis tool CodeQL. Fixes: 554fdbaf19b1 ("thermal: sprd: Add Spreadtrum thermal driver support") Assisted-by: LLM codeql Signed-off-by: Weigang He Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260926042757.2109927-1-geoffreyhe2@gmail.com --- drivers/thermal/sprd_thermal.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thermal/sprd_thermal.c b/drivers/thermal/sprd_thermal.c index d683fcb0f8ab88..b57277e2eac01b 100644 --- a/drivers/thermal/sprd_thermal.c +++ b/drivers/thermal/sprd_thermal.c @@ -396,6 +396,12 @@ static int sprd_thm_probe(struct platform_device *pdev) goto of_put; } + if (sen->id < 0 || sen->id >= SPRD_THM_MAX_SENSOR) { + dev_err(&pdev->dev, "invalid sensor id %d\n", sen->id); + ret = -EINVAL; + goto of_put; + } + ret = sprd_thm_sensor_calibration(sen_child, thm, sen); if (ret) { dev_err(&pdev->dev, "efuse cal analysis failed"); From 4a0f3193c68eb41882ccf7c298f5cd0b17efb8ab Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:06 +0300 Subject: [PATCH 1904/2286] thermal/drivers/airoha: Fix AN7583 ADC mux field mapping On AN7583, bits 3:1 of 0x2e4 select the sensor (0 bandgap, 5 GbE, 6 CPU) and bits 3:2 of 0x2a0 select the diode (0 D0, 1 ZERO, 2 D1). The driver names them the other way round: MUX_TADC is 0x2e4, MUX_SENSOR is 0x2a0. v5 of the AN7583 series swapped the airoha_set_thermal_mux() arguments to match these names. The diode index now goes to the sensor selector, so the zone reads mux positions 1, 0 and 2 (PAD_AVS, bandgap, core voltage) instead of the bandgap diodes. On a Nokia XG-040G-MF the result crosses the critical trip and the board powers off during boot. Swap the field definitions. MUX_TADC then selects the diode, as on EN7581, and the existing call order is correct. Fixes: b20d9782756a ("thermal/drivers/airoha: Add support for AN7583 Thermal Sensor") Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-2-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index ea3e2649c280c9..1b187c41baeacf 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -18,10 +18,10 @@ #define EN7581_DOUT_TADC 0x2f8 #define EN7581_DOUT_TADC_MASK GENMASK(15, 0) -#define AN7583_MUX_SENSOR 0x2a0 +#define AN7583_MUX_TADC 0x2a0 #define AN7583_LOAD_ADJ GENMASK(3, 2) -#define AN7583_MUX_TADC 0x2e4 -#define AN7583_MUX_TADC_MASK GENMASK(3, 1) +#define AN7583_MUX_SENSOR 0x2e4 +#define AN7583_MUX_SENSOR_MASK GENMASK(3, 1) #define AN7583_DOUT_TADC 0x2f0 /* PTP_THERMAL regs */ @@ -636,8 +636,8 @@ static const struct thermal_zone_device_ops an7583_tz_ops = { static const struct reg_field an7583_chip_scu_fields[AIROHA_THERMAL_FIELD_MAX] = { [AIROHA_THERMAL_DOUT_TADC] = REG_FIELD(AN7583_DOUT_TADC, 0, 31), - [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 1, 3), - [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 2, 3), + [AIROHA_THERMAL_MUX_TADC] = REG_FIELD(AN7583_MUX_TADC, 2, 3), + [AIROHA_THERMAL_MUX_SENSOR] = REG_FIELD(AN7583_MUX_SENSOR, 1, 3), }; static int an7583_thermal_probe(struct platform_device *pdev, From b1bf55053abed0d72738bcc12297bfa09f45eeb4 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:07 +0300 Subject: [PATCH 1905/2286] thermal/drivers/airoha: Serialise access to the shared ADC A read selects the sensor and walks three diodes with a 10 ms settle on each. The zone lock serialises reads of one zone. With a zone per AN7583 sensor, added later in this series, reads of different zones interleave and convert samples taken at another mux position. Mux writes are dropped silently unless PLLRG_PROTECT is lifted, and its save and restore do not nest. The cached TADC value also skips a write after another reader has moved the mux. With the AN7583 sensors read as parallel zones during CPU frequency changes, a zone crossed its critical trip on a chip at 55 C and the board shut down. Serialise the sequence, drop the cache, check the selection after writing and after sampling, and return -EAGAIN if it does not hold. Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-3-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 85 +++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 1b187c41baeacf..0f0e6f3af3cd8d 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -191,6 +192,7 @@ #define AN7583_NUM_SENSOR 3 #define AIROHA_THERMAL_NO_MUX_SENSOR -1 +#define AIROHA_THERMAL_MUX_TRIES 3 /* Convert temp to raw value as read from ADC ((((temp / 100) - init) * slope) / 1000) + offset */ #define TEMP_TO_RAW(priv, temp) ((((((temp) / 100) - (priv)->init_temp) * \ @@ -250,7 +252,8 @@ struct airoha_thermal_priv { struct resource scu_adc_res; u32 pllrg_protect; - int current_adc; + /* Serialises mux selection and ADC sampling */ + struct mutex lock; struct thermal_zone_device *tz; int init_temp; @@ -294,9 +297,26 @@ static int airoha_get_thermal_ADC(struct airoha_thermal_priv *priv) return val; } +static bool airoha_thermal_mux_holds(struct airoha_thermal_priv *priv, + int tdac_idx, int sensor_idx) +{ + unsigned int val; + + if (regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], &val) || + val != tdac_idx) + return false; + + if (sensor_idx == AIROHA_THERMAL_NO_MUX_SENSOR) + return true; + + return !regmap_field_read(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], &val) && + val == sensor_idx; +} + static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, int tdac_idx, int sensor_idx) { + int tries = AIROHA_THERMAL_MUX_TRIES; u32 pllrg; /* Save PLLRG current value */ @@ -307,19 +327,17 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, priv->pllrg_protect); /* - * Configure Thermal Sensor mux to sensor_idx. + * Configure Thermal Sensor mux to sensor_idx and Thermal ADC mux to + * tdac_idx. Writes can be dropped silently, so read them back. * (if not supported, sensor_idx is AIROHA_THERMAL_NO_MUX_SENSOR) */ - if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR) - regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], - sensor_idx); - - /* Configure Thermal ADC mux to tdac_idx */ - if (priv->current_adc != tdac_idx) { + do { + if (sensor_idx != AIROHA_THERMAL_NO_MUX_SENSOR) + regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_SENSOR], + sensor_idx); regmap_field_write(priv->chip_scu_fields[AIROHA_THERMAL_MUX_TADC], tdac_idx); - priv->current_adc = tdac_idx; - } + } while (!airoha_thermal_mux_holds(priv, tdac_idx, sensor_idx) && --tries); /* Restore PLLRG value on exit */ regmap_write(priv->chip_scu, EN7581_PLLRG_PROTECT, pllrg); @@ -596,12 +614,40 @@ static int en7581_thermal_post_probe(struct platform_device *pdev) return 0; } +static int an7583_thermal_read_diode(struct airoha_thermal_priv *priv, + int diode, int sensor_idx, int *val) +{ + airoha_set_thermal_mux(priv, diode, sensor_idx); + *val = airoha_get_thermal_ADC(priv); + + return airoha_thermal_mux_holds(priv, diode, sensor_idx) ? 0 : -EAGAIN; +} + +static int an7583_thermal_read_diodes(struct airoha_thermal_priv *priv, + int sensor_idx, int *zero, int *d0, + int *d1) +{ + int ret; + + ret = an7583_thermal_read_diode(priv, AN7583_ZERO_TADC, sensor_idx, zero); + if (ret) + return ret; + + ret = an7583_thermal_read_diode(priv, AN7583_D0_TADC, sensor_idx, d0); + if (ret) + return ret; + + return an7583_thermal_read_diode(priv, AN7583_D1_TADC, sensor_idx, d1); +} + static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); int sensor_idx; int delta_diode, delta_gain; int coeff, slope, offset; + int tries = AIROHA_THERMAL_MUX_TRIES; + int ret; int diode_zero, diode_d0, diode_d1; @@ -612,12 +658,14 @@ static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) slope = an7583_thermal_slope[sensor_idx]; offset = an7583_thermal_offset[sensor_idx]; - airoha_set_thermal_mux(priv, AN7583_ZERO_TADC, sensor_idx); - diode_zero = airoha_get_thermal_ADC(priv); - airoha_set_thermal_mux(priv, AN7583_D0_TADC, sensor_idx); - diode_d0 = airoha_get_thermal_ADC(priv); - airoha_set_thermal_mux(priv, AN7583_D1_TADC, sensor_idx); - diode_d1 = airoha_get_thermal_ADC(priv); + mutex_lock(&priv->lock); + do { + ret = an7583_thermal_read_diodes(priv, sensor_idx, &diode_zero, + &diode_d0, &diode_d1); + } while (ret == -EAGAIN && --tries); + mutex_unlock(&priv->lock); + if (ret) + return ret; delta_diode = diode_d1 - diode_d0; delta_gain = (delta_diode * coeff) / 100 + (diode_zero - diode_d1); @@ -678,7 +726,10 @@ static int airoha_thermal_probe(struct platform_device *pdev) return -ENOMEM; priv->pllrg_protect = soc_data->pllrg_protect; - priv->current_adc = -1; + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; if (!soc_data->probe) return -EINVAL; From 0cc14454eb4a1478ad23644668f8fb19075fd46f Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:08 +0300 Subject: [PATCH 1906/2286] dt-bindings: arm: airoha: Use one thermal sensor cell for AN7583 AN7583 has three temperature sensors behind the chip SCU: bandgap, GbE and CPU. With no cells a thermal zone can only refer to the bandgap sensor. Use one cell to select the sensor. The AN7583 compatible is new in 7.3-rc1 and no device tree uses the property yet. Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260929165209.69669-4-sochnev.v.74@gmail.com --- .../devicetree/bindings/arm/airoha,en7581-chip-scu.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml index cc564dc7b414d7..4a10d024f5fa5a 100644 --- a/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml +++ b/Documentation/devicetree/bindings/arm/airoha,en7581-chip-scu.yaml @@ -26,7 +26,8 @@ properties: maxItems: 1 '#thermal-sensor-cells': - const: 0 + description: Sensor index, 0 bandgap, 1 GbE, 2 CPU. + const: 1 required: - compatible From 06a169d840a0d6ce03c230b115cef7d7cebcc2d4 Mon Sep 17 00:00:00 2001 From: Vitaliy Sochnev Date: Tue, 29 Sep 2026 19:52:09 +0300 Subject: [PATCH 1907/2286] thermal/drivers/airoha: Register a thermal zone per AN7583 sensor AN7583 has three temperature sensors and the driver has constants for all of them, but it registers one zone and reads only the bandgap sensor. Register a zone per sensor; the thermal sensor cell selects it. A device tree may describe fewer zones than sensors. EN7581 still registers one zone. Idle and with both cores loaded on a Nokia XG-040G-MF: bandgap 56.9 C -> 59.3 C GbE 58.7 C -> 61.5 C CPU 57.0 C -> 61.1 C Signed-off-by: Vitaliy Sochnev Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929165209.69669-5-sochnev.v.74@gmail.com --- drivers/thermal/airoha_thermal.c | 64 +++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/drivers/thermal/airoha_thermal.c b/drivers/thermal/airoha_thermal.c index 0f0e6f3af3cd8d..7c63a1b44a6b93 100644 --- a/drivers/thermal/airoha_thermal.c +++ b/drivers/thermal/airoha_thermal.c @@ -245,6 +245,11 @@ enum airoha_thermal_chip_scu_field { AIROHA_THERMAL_FIELD_MAX, }; +struct airoha_thermal_zone { + struct airoha_thermal_priv *priv; + int sensor; +}; + struct airoha_thermal_priv { struct regmap *map; struct regmap *chip_scu; @@ -263,6 +268,9 @@ struct airoha_thermal_priv { struct airoha_thermal_soc_data { u32 pllrg_protect; + /* Sensor mux value per zone, NULL without a sensor mux */ + const int *sensors; + int num_sensors; const struct thermal_zone_device_ops *thdev_ops; int (*probe)(struct platform_device *pdev, @@ -348,7 +356,8 @@ static void airoha_set_thermal_mux(struct airoha_thermal_priv *priv, static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; int min_value, max_value, avg_value, value; int i; @@ -374,7 +383,8 @@ static int en7581_thermal_get_temp(struct thermal_zone_device *tz, int *temp) static int en7581_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; bool enable_monitor = false; if (high != INT_MAX) { @@ -642,8 +652,9 @@ static int an7583_thermal_read_diodes(struct airoha_thermal_priv *priv, static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) { - struct airoha_thermal_priv *priv = thermal_zone_device_priv(tz); - int sensor_idx; + struct airoha_thermal_zone *zone = thermal_zone_device_priv(tz); + struct airoha_thermal_priv *priv = zone->priv; + int sensor_idx = zone->sensor; int delta_diode, delta_gain; int coeff, slope, offset; int tries = AIROHA_THERMAL_MUX_TRIES; @@ -651,9 +662,6 @@ static int an7583_thermal_get_temp(struct thermal_zone_device *tz, int *temp) int diode_zero, diode_d0, diode_d1; - /* Always read sensor AN7583_BGP_TEMP_SENSOR */ - sensor_idx = AN7583_BGP_TEMP_SENSOR; - coeff = an7583_thermal_coeff[sensor_idx]; slope = an7583_thermal_slope[sensor_idx]; offset = an7583_thermal_offset[sensor_idx]; @@ -717,7 +725,7 @@ static int airoha_thermal_probe(struct platform_device *pdev) const struct airoha_thermal_soc_data *soc_data; struct airoha_thermal_priv *priv; struct device *dev = &pdev->dev; - int ret; + int ret, i; soc_data = device_get_match_data(dev); @@ -739,11 +747,31 @@ static int airoha_thermal_probe(struct platform_device *pdev) return ret; /* register of thermal sensor and get info from DT */ - priv->tz = devm_thermal_of_zone_register(dev, 0, priv, - soc_data->thdev_ops); - if (IS_ERR(priv->tz)) { - dev_err(dev, "register thermal zone sensor failed\n"); - return PTR_ERR(priv->tz); + for (i = 0; i < soc_data->num_sensors; i++) { + struct airoha_thermal_zone *zone; + struct thermal_zone_device *tz; + + zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); + if (!zone) + return -ENOMEM; + + zone->priv = priv; + zone->sensor = soc_data->sensors ? soc_data->sensors[i] : + AIROHA_THERMAL_NO_MUX_SENSOR; + + tz = devm_thermal_of_zone_register(dev, i, zone, + soc_data->thdev_ops); + if (IS_ERR(tz)) { + /* A DT may describe fewer zones than sensors */ + if (i && PTR_ERR(tz) == -ENODEV) + continue; + + dev_err(dev, "register thermal zone %d failed\n", i); + return PTR_ERR(tz); + } + + if (!i) + priv->tz = tz; } platform_set_drvdata(pdev, priv); @@ -751,7 +779,15 @@ static int airoha_thermal_probe(struct platform_device *pdev) return soc_data->post_probe ? soc_data->post_probe(pdev) : 0; } +/* The bandgap sensor stays zone 0 */ +static const int an7583_zone_sensors[AN7583_NUM_SENSOR] = { + AN7583_BGP_TEMP_SENSOR, + AN7583_GBE_TEMP_SENSOR, + AN7583_CPU_TEMP_SENSOR, +}; + static const struct airoha_thermal_soc_data en7581_data = { + .num_sensors = 1, .pllrg_protect = EN7581_SCU_THERMAL_PROTECT_KEY, .thdev_ops = &en7581_thdev_ops, .probe = &en7581_thermal_probe, @@ -759,6 +795,8 @@ static const struct airoha_thermal_soc_data en7581_data = { }; static const struct airoha_thermal_soc_data an7583_data = { + .sensors = an7583_zone_sensors, + .num_sensors = AN7583_NUM_SENSOR, .pllrg_protect = AN7583_SCU_THERMAL_PROTECT_KEY, .thdev_ops = &an7583_tz_ops, .probe = &an7583_thermal_probe, From 602da762f7a7c494a95cb204ac005965b214e556 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 29 Sep 2026 13:19:51 -0700 Subject: [PATCH 1908/2286] tools/thermal/tmon: Fix string truncation warning in optarg copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GCC warns about possible string truncation with -Wstringop-truncation: tmon.c: In function ‘main’: tmon.c:233:25: warning: ‘__builtin_strncpy’ specified bound 20 equals destination size [-Wstringop-truncation] 233 | strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Replace strncpy() with snprintf() to guarantee NUL-termination and silence the compiler warning. Fixes: 94f69966faf8 ("tools/thermal: Introduce tmon, a tool for thermal subsystem") Signed-off-by: Florian Fainelli Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260929201951.408456-1-florian.fainelli@broadcom.com --- tools/thermal/tmon/tmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c index ef67cd1a48619b..70a2ec50a46166 100644 --- a/tools/thermal/tmon/tmon.c +++ b/tools/thermal/tmon/tmon.c @@ -230,7 +230,7 @@ int main(int argc, char **argv) switch (c) { case 'c': no_control = 0; - strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE); + snprintf(ctrl_cdev, sizeof(ctrl_cdev), "%s", optarg); break; case 'd': start_daemon_mode(); From 8195b945794ca3a917131b8a5368bb61e687c927 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Wed, 23 Sep 2026 16:37:50 +0200 Subject: [PATCH 1909/2286] dt-bindings: thermal: amlogic: describe bus clock for t7-thermal The t7 thermal sensor needs a bus clock, in addition to the existing functional clock, to gate register access to the sensor. This bus clock is specific to each sensor, unlike the functional clock which is shared between sensors. The current binding only describes a single unnamed clock and therefore cannot represent this hardware requirement, so add a second clock entry and clock-names for clarity. Make the second clock mandatory for the t7-thermal compatible only, via an additional if/then/else, to avoid breaking other Amlogic SoCs that still use a single clock. Update the t7 example to match. Fixes: b1c8ccdbd4e9 ("dt-bindings: thermal: amlogic: Add support for T7") Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260923-fix-t7-thermal-clock-v2-1-bff7a09eb487@aliel.fr --- .../bindings/thermal/amlogic,thermal.yaml | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml index 8cfa44dcda58cf..e72cb39ac635d8 100644 --- a/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml +++ b/Documentation/devicetree/bindings/thermal/amlogic,thermal.yaml @@ -32,7 +32,15 @@ properties: maxItems: 1 clocks: - maxItems: 1 + minItems: 1 + items: + - description: Bus clock, gates register access to the sensor. + - description: Functional clock of the temperature sensor, shared between sensors. + + clock-names: + items: + - const: bus + - const: core power-domains: maxItems: 1 @@ -72,6 +80,22 @@ allOf: else: required: - amlogic,secure-monitor + - if: + properties: + compatible: + contains: + const: amlogic,t7-thermal + then: + properties: + clocks: + minItems: 2 + required: + - clock-names + else: + properties: + clocks: + maxItems: 1 + clock-names: false unevaluatedProperties: false @@ -94,7 +118,8 @@ examples: compatible = "amlogic,t7-thermal"; reg = <0x20000 0x50>; interrupts = ; - clocks = <&clkc_periphs CLKID_TS>; + clocks = <&clkc_periphs CLKID_SYS_TS_A73>, <&clkc_periphs CLKID_TS>; + clock-names = "bus", "core"; #thermal-sensor-cells = <0>; amlogic,secure-monitor = <&sm 1>; }; From 44b39b4fb0f069a3bbe6bfb21a34d2d3730f393a Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Wed, 23 Sep 2026 16:37:51 +0200 Subject: [PATCH 1910/2286] thermal/drivers/amlogic: Switch to clk_bulk API to support multiple clocks The t7-thermal variant now exposes two clocks (bus and core) in its devicetree binding, but the driver only knows how to get and manage a single struct clk. Using devm_clk_get() would silently ignore the second clock, so switch to devm_clk_bulk_get_all() and the matching clk_bulk_prepare_enable()/clk_bulk_disable_unprepare() calls. This lets the driver transparently handle however many clocks a given compatible declares, without needing per-variant clock-handling code, and keeps working for existing SoCs that only have one. Fixes: 18d65de8157c ("thermal/drivers/amlogic: Add support for secure monitor calibration readout") Signed-off-by: Ronald Claveau Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260923-fix-t7-thermal-clock-v2-2-bff7a09eb487@aliel.fr --- drivers/thermal/amlogic_thermal.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/thermal/amlogic_thermal.c b/drivers/thermal/amlogic_thermal.c index a0b530624b60c9..60728de6968ac0 100644 --- a/drivers/thermal/amlogic_thermal.c +++ b/drivers/thermal/amlogic_thermal.c @@ -100,7 +100,8 @@ struct amlogic_thermal { const struct amlogic_thermal_data *data; struct regmap *regmap; struct regmap *sec_ao_map; - struct clk *clk; + struct clk_bulk_data *clks; + int num_clks; struct thermal_zone_device *tzd; u32 trim_info; struct meson_sm_firmware *sm_fw; @@ -142,7 +143,7 @@ static int amlogic_thermal_enable(struct amlogic_thermal *data) { int ret; - ret = clk_prepare_enable(data->clk); + ret = clk_bulk_prepare_enable(data->num_clks, data->clks); if (ret) return ret; @@ -156,7 +157,7 @@ static void amlogic_thermal_disable(struct amlogic_thermal *data) { regmap_update_bits(data->regmap, TSENSOR_CFG_REG1, TSENSOR_CFG_REG1_ENABLE, 0); - clk_disable_unprepare(data->clk); + clk_bulk_disable_unprepare(data->num_clks, data->clks); } static int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp) @@ -323,9 +324,10 @@ static int amlogic_thermal_probe(struct platform_device *pdev) if (IS_ERR(pdata->regmap)) return PTR_ERR(pdata->regmap); - pdata->clk = devm_clk_get(dev, NULL); - if (IS_ERR(pdata->clk)) - return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n"); + ret = devm_clk_bulk_get_all(dev, &pdata->clks); + if (ret <= 0) + return dev_err_probe(dev, ret ?: -ENOENT, "failed to get clocks\n"); + pdata->num_clks = ret; if (pdata->data->use_sm) ret = amlogic_thermal_probe_sm(pdev, pdata); From 200265cfa885553da708a4e677ec10df98d3f68b Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Sun, 11 Oct 2026 19:51:25 +0300 Subject: [PATCH 1911/2286] dt-bindings: thermal: tsens: Add MSM8952 Add compatible string for tsens block found in MSM8952. Signed-off-by: Muzaffer Kadir Signed-off-by: Daniel Lezcano Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20261011-msm8952-initial-support-v5-3-1857074992ec@mainlining.org --- Documentation/devicetree/bindings/thermal/qcom-tsens.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml index 5a8f7673e73039..c78074fbcecfaf 100644 --- a/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml +++ b/Documentation/devicetree/bindings/thermal/qcom-tsens.yaml @@ -46,6 +46,7 @@ properties: items: - enum: - qcom,msm8937-tsens + - qcom,msm8952-tsens - qcom,msm8956-tsens - qcom,msm8976-tsens - qcom,qcs404-tsens From 8951eb8a781ea4704898ea61bbf52435ff1ee3a4 Mon Sep 17 00:00:00 2001 From: Muzaffer Kadir Date: Sun, 11 Oct 2026 19:51:26 +0300 Subject: [PATCH 1912/2286] thermal/qcom/tsens: Add support for MSM8952 tsens Add support for tsens v1 block that exists on MSM8952 with 11 tsens sensors. Sensor Offsets are taken from downstream. Signed-off-by: Muzaffer Kadir Signed-off-by: Daniel Lezcano Reviewed-by: Dmitry Baryshkov Link: https://android.googlesource.com/kernel/msm.git/+/refs/tags/android-wear-7.0.0_r0.3/drivers/thermal/msm-tsens.c#1621 Link: https://patch.msgid.link/20261011-msm8952-initial-support-v5-4-1857074992ec@mainlining.org --- drivers/thermal/qcom/tsens-v1.c | 41 +++++++++++++++++++++++++++++++++ drivers/thermal/qcom/tsens.c | 3 +++ drivers/thermal/qcom/tsens.h | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c index faa5d00788ca6f..7bda8e6087c400 100644 --- a/drivers/thermal/qcom/tsens-v1.c +++ b/drivers/thermal/qcom/tsens-v1.c @@ -145,6 +145,34 @@ static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = { [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0), }; +static int __init init_8952(struct tsens_priv *priv) +{ + priv->sensor[0].p1_calib_offset = 2; + priv->sensor[0].p2_calib_offset = -1; + priv->sensor[1].p1_calib_offset = -4; + priv->sensor[1].p2_calib_offset = -4; + priv->sensor[2].p1_calib_offset = 4; + priv->sensor[2].p2_calib_offset = 5; + priv->sensor[3].p1_calib_offset = 1; + priv->sensor[3].p2_calib_offset = 1; + priv->sensor[4].p1_calib_offset = 2; + priv->sensor[4].p2_calib_offset = 3; + priv->sensor[5].p1_calib_offset = -1; + priv->sensor[5].p2_calib_offset = -1; + priv->sensor[6].p1_calib_offset = 0; + priv->sensor[6].p2_calib_offset = -1; + priv->sensor[7].p1_calib_offset = 3; + priv->sensor[7].p2_calib_offset = 4; + priv->sensor[8].p1_calib_offset = 2; + priv->sensor[8].p2_calib_offset = 4; + priv->sensor[9].p1_calib_offset = 0; + priv->sensor[9].p2_calib_offset = 0; + priv->sensor[10].p1_calib_offset = -3; + priv->sensor[10].p2_calib_offset = -2; + + return init_common(priv); +} + static int __init init_8956(struct tsens_priv *priv) { priv->sensor[0].slope = 3313; priv->sensor[1].slope = 3275; @@ -223,6 +251,19 @@ struct tsens_plat_data data_8937 = { .fields = tsens_v1_regfields, }; +static const struct tsens_ops ops_8952 = { + .init = init_8952, + .calibrate = tsens_calibrate_common, + .get_temp = get_temp_tsens_valid, +}; + +struct tsens_plat_data data_8952 = { + .num_sensors = 11, + .ops = &ops_8952, + .feat = &tsens_v1_feat, + .fields = tsens_v1_regfields, +}; + static const struct tsens_ops ops_8956 = { .init = init_8956, .calibrate = tsens_calibrate_common, diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c index b5ec70201e2f39..4d7013fc79d63f 100644 --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -1191,6 +1191,9 @@ static const struct of_device_id tsens_table[] = { }, { .compatible = "qcom,msm8939-tsens", .data = &data_8939, + }, { + .compatible = "qcom,msm8952-tsens", + .data = &data_8952, }, { .compatible = "qcom,msm8956-tsens", .data = &data_8956, diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h index e8376accdff3d2..a35b15312961f7 100644 --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -669,7 +669,7 @@ extern struct tsens_plat_data data_8960; extern struct tsens_plat_data data_8226, data_8909, data_8916, data_8939, data_8974, data_9607; /* TSENS v1 targets */ -extern struct tsens_plat_data data_tsens_v1, data_8937, data_8976, data_8956; +extern struct tsens_plat_data data_tsens_v1, data_8937, data_8952, data_8976, data_8956; /* TSENS v1 with no RPM targets */ extern const struct tsens_plat_data data_ipq5018; From 379ae6766d1a7a1f78f1d97b6c72a262f14960e8 Mon Sep 17 00:00:00 2001 From: Aryan Sinha Date: Mon, 12 Oct 2026 20:50:32 +0530 Subject: [PATCH 1913/2286] thermal/fw: Fix kernel-doc parameter names The kernel-doc for thermal_fwnode_cooling_device_register() still names the old device tree arguments np and of_index, and devm_thermal_fwnode_cooling_device_register() documents fw_index rather than fwn_index. Rename them to match the code and reword the device tree reference as a firmware node one. Signed-off-by: Aryan Sinha Link: https://patch.msgid.link/20261012152037.33461-1-sinha.aryan03@gmail.com Signed-off-by: Daniel Lezcano --- drivers/thermal/thermal_fw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/thermal/thermal_fw.c b/drivers/thermal/thermal_fw.c index e8bd2049cf74de..7c28bfada386ca 100644 --- a/drivers/thermal/thermal_fw.c +++ b/drivers/thermal/thermal_fw.c @@ -11,13 +11,13 @@ /** * thermal_fwnode_cooling_device_register() - register an thermal cooling device - * @np: a pointer to a device tree node. - * @of_index: a cooling device index in the cooling controller + * @fwnode: a pointer to a firmware node. + * @fwn_index: a cooling device index in the cooling controller * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. * - * This function will register a cooling device with device tree node reference. + * This function will register a cooling device with a firmware node reference. * This interface function adds a new thermal cooling device (fan/processor/...) * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself * to all the thermal zone devices registered at the same time. @@ -71,7 +71,7 @@ __devm_thermal_fwnode_cooling_device_register(struct device *dev, struct fwnode_ /** * devm_thermal_fwnode_cooling_device_register() - register a thermal cooling device * @dev: a valid struct device pointer of a sensor device. - * @fw_index: a cooling device index in the cooling controller + * @fwn_index: a cooling device index in the cooling controller * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. From 82bb005cd9cf5a9df4cfe4a52db90346ac5fd17a Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 08:22:52 +0200 Subject: [PATCH 1914/2286] thermal: ti-soc-thermal: Fix the return value in ti_bandgap_get_sensor_data() kernel-doc ti_bandgap_get_sensor_data() returns an ERR_PTR() if the bandgap instance or sensor ID is invalid. Otherwise it returns the stored sensor data, which can be NULL if no data has been set or the stored value has been cleared. This mismatch was already present in omap_bandgap_get_sensor_data() when the driver was introduced in staging. Document the validation errors while retaining NULL as a possible stored value. Fixes: 8feaf0ce1a04 ("staging: OMAP4+: thermal: introduce bandgap temperature sensor") Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Daniel Lezcano Link: https://patch.msgid.link/20260912062252.42762-1-kmehltretter@gmail.com --- drivers/thermal/ti-soc-thermal/ti-bandgap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thermal/ti-soc-thermal/ti-bandgap.c b/drivers/thermal/ti-soc-thermal/ti-bandgap.c index 507fe67eaffae4..815ca0786c5457 100644 --- a/drivers/thermal/ti-soc-thermal/ti-bandgap.c +++ b/drivers/thermal/ti-soc-thermal/ti-bandgap.c @@ -576,7 +576,8 @@ int ti_bandgap_set_sensor_data(struct ti_bandgap *bgp, int id, void *data) * @bgp: pointer to bandgap instance * @id: sensor id * - * Return: data stored by set function with sensor id on success or NULL + * Return: the stored sensor data, which may be %NULL, or an ERR_PTR() + * if @bgp or @id is invalid. */ void *ti_bandgap_get_sensor_data(struct ti_bandgap *bgp, int id) { From 2dc1f296b5754dd2441fc8b6613a362a47f6b98f Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 14 Oct 2026 11:18:14 +0800 Subject: [PATCH 1915/2286] thermal/drivers/rzg3e: Propagate reset assertion failure from suspend rzg3e_thermal_suspend() powers off an active sensor and then ignores failure to assert its reset. System sleep can therefore continue without the clean reset state expected by resume. Return the reset error and, when the sensor had been active, try to restore power before failing suspend. This is best-effort recovery: reset assertion failure may leave partial hardware state, so restoring power is not guaranteed to succeed. Preserve the original reset error. The issue was found by our static-analysis tool and manually reviewed. Fixes: dc67521c20b7 ("thermal/drivers/renesas/rzg3e: Fix add thermal driver for the Renesas RZ/G3E SoC") Assisted-by: gpt 5 Signed-off-by: Pengpeng Hou Signed-off-by: Daniel Lezcano Reviewed-by: Claudiu Beznea Link: https://patch.msgid.link/20261014031814.7230-1-hppiscas@163.com --- drivers/thermal/renesas/rzg3e_thermal.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/renesas/rzg3e_thermal.c b/drivers/thermal/renesas/rzg3e_thermal.c index a1ceff79e83e6d..e806d130dc53df 100644 --- a/drivers/thermal/renesas/rzg3e_thermal.c +++ b/drivers/thermal/renesas/rzg3e_thermal.c @@ -512,15 +512,19 @@ static int rzg3e_thermal_runtime_resume(struct device *dev) static int rzg3e_thermal_suspend(struct device *dev) { struct rzg3e_thermal_priv *priv = dev_get_drvdata(dev); + bool active = pm_runtime_active(dev); + int ret; /* If device is active, power it off */ - if (pm_runtime_active(dev)) + if (active) rzg3e_thermal_power_off(priv); /* Assert reset to ensure clean state after resume */ - reset_control_assert(priv->rstc); + ret = reset_control_assert(priv->rstc); + if (ret && active) + rzg3e_thermal_power_on(priv); - return 0; + return ret; } static int rzg3e_thermal_resume(struct device *dev) From c28b4b48deac17749ed1ebe4289a4ba73541ddc6 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:05:28 -0700 Subject: [PATCH 1916/2286] xfs: don't overrun incore inode when cleaning attr forks LOLLM noticed that xrep_dinode_check_afork doesn't check for a di_forkoff value that is so large that the attr fork would begin after the end of the inode space. This causes afork_{ptr,size} to be nonsense. What we really ought to do here is zap the attr fork. Cc: stable@vger.kernel.org # v6.8 Fixes: e744cef2060559 ("xfs: zap broken inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index c81dfb0485e219..21e7d64b7f7054 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1243,6 +1243,8 @@ xrep_dinode_check_afork( if (XFS_DFORK_BOFF(dip) == 0) return dip->di_aformat != XFS_DINODE_FMT_EXTENTS || xfs_dfork_attr_extents(dip) != 0; + if (XFS_DFORK_BOFF(dip) >= XFS_LITINO(sc->mp)) + return true; afork_size = XFS_DFORK_SIZE(dip, sc->mp, XFS_ATTR_FORK); afork_ptr = XFS_DFORK_PTR(dip, XFS_ATTR_FORK); From 147f9f8300cb284923e08bb8a7b32ba9681c45ed Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:05:43 -0700 Subject: [PATCH 1917/2286] xfs: fix data fork block count check in xrep_inode_blockcounts LOLLM notices that for non-reflink realtime files, the @count output of xchk_inode_count_blocks includes both the file blocks on the realtime volume and the bmap btree blocks on the data volume. Therefore, we must compare this value to rblocks + dblocks to avoid unnecessary failure. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 21e7d64b7f7054..c9a9253c0b8f1c 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1719,7 +1719,7 @@ xrep_inode_blockcounts( */ ; } else if (XFS_IS_REALTIME_INODE(sc->ip)) { - if (count >= sc->mp->m_sb.sb_rblocks) + if (count >= sc->mp->m_sb.sb_rblocks + sc->mp->m_sb.sb_dblocks) return -EFSCORRUPTED; } else { if (count >= sc->mp->m_sb.sb_dblocks) From ca12256b7f9820aafb361ac496f048d2d4a71c23 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:05:59 -0700 Subject: [PATCH 1918/2286] xfs: avoid oversized space allocations when regenerating metadata LOLLM notices that the btree block preallocation code can be asked to allocate more than XFS_MAX_BMBT_EXTLEN blocks. If the allocation succeeds, the reservation will be stored in a single autoreap record. Unfortunately, the autoreap code uses deferred frees to get rid of unused space and that code complains if it's given too much space to get rid of all once. Fix this theoretical problem by constraining the allocation size since it's unlikely that we're going to need that much space for a btree. We don't fix the core code because the runtime users of deferred frees are freeing file space or single btree blocks and we want to catch problems there. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/newbt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index a7554f925b7440..1f28ca1647f8d3 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -280,7 +280,7 @@ xrep_newbt_alloc_ag_blocks( .mp = mp, .oinfo = xnr->oinfo, .minlen = 1, - .maxlen = nr_blocks, + .maxlen = min(XFS_MAX_BMBT_EXTLEN, nr_blocks), .prod = 1, .resv = xnr->resv, }; @@ -354,7 +354,7 @@ xrep_newbt_alloc_file_blocks( .mp = mp, .oinfo = xnr->oinfo, .minlen = 1, - .maxlen = nr_blocks, + .maxlen = min(XFS_MAX_BMBT_EXTLEN, nr_blocks), .prod = 1, .resv = xnr->resv, }; From 80f13dc7225eec318da7499c7a562d7a34d25e6a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:06:14 -0700 Subject: [PATCH 1919/2286] xfs: reduce new btree slack factor if free space is negative LOLLM points out that if the incore fdblocks counter is negative, the assignment to uint64_t will mask this possibly low space situation and we don't pack the btree blocks as tightly as we probably ought to. If it's the case that there's some free space and fdblocks is too low due to an error in the superblock field, then we can end up failing btree rebuilds due to ENOSPC unnecessarily. This is a corner case, but let's fix it anyway. Cc: stable@vger.kernel.org # v6.8 Fixes: be408417630427 ("xfs: implement block reservation accounting for btrees we're staging") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/newbt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/scrub/newbt.c b/fs/xfs/scrub/newbt.c index 1f28ca1647f8d3..9baf08bc54cd0f 100644 --- a/fs/xfs/scrub/newbt.c +++ b/fs/xfs/scrub/newbt.c @@ -71,7 +71,7 @@ xrep_newbt_estimate_slack( free = sc->sa.pag->pagf_freeblks; sz = xfs_ag_block_count(sc->mp, pag_agno(sc->sa.pag)); } else { - free = xfs_sum_freecounter_raw(sc->mp, XC_FREE_BLOCKS); + free = xfs_sum_freecounter(sc->mp, XC_FREE_BLOCKS); sz = sc->mp->m_sb.sb_dblocks; } From 219ff8a734d8438e24eb346a847c9b1ad9b7ab5d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:06:30 -0700 Subject: [PATCH 1920/2286] xfs: don't queue rmap update twice when reaping a fork LOLLM noticed that xrep_reap_bmapi_iter calls xfs_bmap_unmap_extent and xfs_rmap_unmap_extent to remove a mapping from a file fork and its reverse mapping. However, xfs_bmap_del_extent_real also queues a deferred rmap removal log item, which means that we incorrectly queue two rmap removal operations. Fix that by removing the extra call and updating the comment to mention this subtlety that I forgot, having written the code. :P Cc: stable@vger.kernel.org # v6.10 Fixes: 5befb047b9f4de ("xfs: add the ability to reap entire inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/reap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/reap.c b/fs/xfs/scrub/reap.c index 235e5e7e16c415..a0bee8c7abc0e5 100644 --- a/fs/xfs/scrub/reap.c +++ b/fs/xfs/scrub/reap.c @@ -1480,12 +1480,12 @@ xrep_reap_bmapi_iter( /* * t0: Schedule removal of the mapping from the fork. We use * deferred log intents in this function to control the exact - * sequence of metadata updates. + * sequence of metadata updates. The BUI queues a RUI to + * remove the reverse mapping. */ xfs_bmap_unmap_extent(sc->tp, rs->ip, rs->whichfork, imap); xfs_trans_mod_dquot_byino(sc->tp, rs->ip, XFS_TRANS_DQ_BCOUNT, -(int64_t)imap->br_blockcount); - xfs_rmap_unmap_extent(sc->tp, rs->ip, rs->whichfork, imap); return 0; } From 634b63baf05062988456a0bd5df2696c14ea2949 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:06:46 -0700 Subject: [PATCH 1921/2286] xfs: only written file data blocks can be shared LOLLM noticed that xchk_refcountbt_rmap_check (and its rt equivalent) will count any rmap owner towards the shared count. However, the only blocks that can be shared between xfs files are written file data blocks; and the only blocks that can have single-refcount entries are COW staging extents. Fix the checking code. Cc: stable@vger.kernel.org # v4.16 Fixes: dbde19da963706 ("xfs: cross-reference the rmapbt data with the refcountbt") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index c18bb89325a5ec..c1b71f5b65ba60 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -112,6 +112,18 @@ xchk_refcountbt_rmap_check( return 0; } + /* + * The refcount btree should only have records for written file data + * blocks or for COW staging blocks. + */ + if ((XFS_RMAP_NON_INODE_OWNER(rec->rm_owner) && + rec->rm_owner != XFS_RMAP_OWN_COW) || + (rec->rm_flags & (XFS_RMAP_ATTR_FORK | XFS_RMAP_BMBT_BLOCK | + XFS_RMAP_UNWRITTEN))) { + xchk_btree_xref_set_corrupt(refchk->sc, cur, 0); + return 0; + } + if (rec->rm_startblock <= refchk->bno && rm_last >= rc_last) { /* * The rmap overlaps the refcount record, so we can confirm From 95e213c1ddb40dfb0a8178ed35c9f29992a320e1 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:07:01 -0700 Subject: [PATCH 1922/2286] xfs: check for fragments when full-extent sharing matches LOLLM noticed that xchk_refcountbt_process_rmap_fragments returns early if there are enough rmap records that overlap the space referenced by a refcountbt record to match the refcount in that refcountbt record. Unfortunately, it never checks the fragment list in this case, so it won't find the discrepancy between the rmap and refcount btrees. Cc: stable@vger.kernel.org # v4.16 Fixes: dbde19da963706 ("xfs: cross-reference the rmapbt data with the refcountbt") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/refcount.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/refcount.c b/fs/xfs/scrub/refcount.c index c1b71f5b65ba60..ddbe0da6f665a8 100644 --- a/fs/xfs/scrub/refcount.c +++ b/fs/xfs/scrub/refcount.c @@ -168,8 +168,11 @@ xchk_refcountbt_process_rmap_fragments( xfs_nlink_t target_nr; target_nr = refchk->refcount - refchk->seen; - if (target_nr == 0) + if (target_nr == 0) { + if (!list_empty(&refchk->fragments)) + refchk->seen = 0; return; + } /* * There are (refchk->rc.rc_refcount - refchk->nr refcount) From 3978b1f3109dfd5c648442bb040f9abd58608226 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:07:17 -0700 Subject: [PATCH 1923/2286] xfs: don't clear COWEXTSIZE on directories on reflink filesystems LOLLM noticed that xrep_dinode_flags clears the COWEXTSIZE flag unnecessarily on directories on reflink filesystems. Directories just pass the hint along to new children, so it's valid to leave the flag set. Cc: stable@vger.kernel.org # v6.8 Fixes: 2d295fe65776d1 ("xfs: repair inode records") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index c9a9253c0b8f1c..cbd1b0524f5b8b 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -565,6 +565,8 @@ xrep_dinode_flags( */ if (xfs_has_reflink(mp) && S_ISREG(mode)) flags2 |= XFS_DIFLAG2_REFLINK; + else if (xfs_has_reflink(mp) && S_ISDIR(mode)) + flags2 &= ~XFS_DIFLAG2_REFLINK; else flags2 &= ~(XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE); if (!xfs_has_bigtime(mp)) From eb20a959760c21e3547cd811d0d9b0b29df98959 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:07:33 -0700 Subject: [PATCH 1924/2286] xfs: clear set[ug]id mode bits when zapping attr fork LOLLM observes that zapping a file's attr fork could result in ACLs being removed from the file. Because we have no idea what we're deleting, we should clear the setuid and setgid bits in addition to the access mode bits so that nobody can access the file. Cc: stable@vger.kernel.org # v6.8 Fixes: e744cef2060559 ("xfs: zap broken inode forks") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/inode_repair.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index cbd1b0524f5b8b..c3842145082f9e 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1313,7 +1313,12 @@ xrep_dinode_zap_afork( */ if (dip->di_format != XFS_DINODE_FMT_BTREE) dip->di_forkoff = 0; - dip->di_mode = cpu_to_be16(mode & ~0777); + + /* + * Zapping the attr fork could remove ACLs, so we clear suid/sgid and + * all access mode bits. + */ + dip->di_mode = cpu_to_be16(mode & ~(S_ISUID | S_ISGID | 0777)); dip->di_uid = 0; dip->di_gid = 0; } From 4ce244b234c251963fca6dff9de6edf26a209d08 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:07:48 -0700 Subject: [PATCH 1925/2286] xfs: opportunistically skip btree block checks LOLLM notices that both callers of xchk_btree_get_block will return early if *pblock is NULL after a successful return. However, xchk_btree_get_block itself can return 0 after flagging a corruption. There's no point in continuing after we've found a corruption, so null the pointer out. Cc: stable@vger.kernel.org # v4.15 Fixes: cc3e0948d2686f ("xfs: scrub the shape of a metadata btree") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/btree.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c index 786b1e7c4dc37f..17bd8505be0411 100644 --- a/fs/xfs/scrub/btree.c +++ b/fs/xfs/scrub/btree.c @@ -602,6 +602,14 @@ xchk_btree_get_block( xfs_btree_get_block(bs->cur, level, pbp); if (__xfs_btree_check_block(bs->cur, *pblock, level, *pbp)) { xchk_btree_set_corrupt(bs->sc, bs->cur, level); + /* + * The btree block fails basic checks, so we know it's corrupt. + * There's no need to continue looking for corruptions, so we + * null out pblock. If *pbp is non-null then it's also + * attached to the transaction so we won't leak it despite the + * 0 return code. + */ + *pblock = NULL; return 0; } if (*pbp) From 1ccc8f553d885d49c1bfe141ea1f331511940b3a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:08:04 -0700 Subject: [PATCH 1926/2286] xfs: always report scrub errors in tracepoints LOLLM complains that the error processing functions in scrub will sometimes zero out *error before recording a tracepoint. This makes diagnosing problems harder, so let's fix that. Cc: stable@vger.kernel.org # v4.15 Fixes: 4700d22980d459 ("xfs: create helpers to record and deal with scrub problems") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/btree.c | 8 +++++++- fs/xfs/scrub/common.c | 7 +++++-- fs/xfs/scrub/dabtree.c | 6 +++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c index 17bd8505be0411..43a7ea7308eea9 100644 --- a/fs/xfs/scrub/btree.c +++ b/fs/xfs/scrub/btree.c @@ -46,8 +46,14 @@ __xchk_btree_process_error( case -ENODATA: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + if (cur->bc_ops->type == XFS_BTREE_TYPE_INODE) + trace_xchk_ifork_btree_op_error(sc, cur, level, + *error, ret_ip); + else + trace_xchk_btree_op_error(sc, cur, level, + *error, ret_ip); *error = 0; - fallthrough; + break; default: if (cur->bc_ops->type == XFS_BTREE_TYPE_INODE) trace_xchk_ifork_btree_op_error(sc, cur, level, diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c index e5233e31abb750..a61895872072d1 100644 --- a/fs/xfs/scrub/common.c +++ b/fs/xfs/scrub/common.c @@ -107,8 +107,9 @@ __xchk_process_error( case -ENODATA: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + trace_xchk_op_error(sc, agno, bno, *error, ret_ip); *error = 0; - fallthrough; + break; default: trace_xchk_op_error(sc, agno, bno, *error, ret_ip); break; @@ -183,8 +184,10 @@ __xchk_fblock_process_error( case -ENODATA: /* Note the badness but don't abort. */ sc->sm->sm_flags |= errflag; + trace_xchk_file_op_error(sc, whichfork, offset, *error, + ret_ip); *error = 0; - fallthrough; + break; default: trace_xchk_file_op_error(sc, whichfork, offset, *error, ret_ip); diff --git a/fs/xfs/scrub/dabtree.c b/fs/xfs/scrub/dabtree.c index bbf6ece58828ca..02c8544d891861 100644 --- a/fs/xfs/scrub/dabtree.c +++ b/fs/xfs/scrub/dabtree.c @@ -49,8 +49,12 @@ xchk_da_process_error( case -ENODATA: /* Note the badness but don't abort. */ sc->sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT; + trace_xchk_file_op_error(sc, ds->dargs.whichfork, + xfs_dir2_da_to_db(ds->dargs.geo, + ds->state->path.blk[level].blkno), + *error, __return_address); *error = 0; - fallthrough; + break; default: trace_xchk_file_op_error(sc, ds->dargs.whichfork, xfs_dir2_da_to_db(ds->dargs.geo, From 03ce4ca43c00394364493865f417d646e252bac2 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:08:20 -0700 Subject: [PATCH 1927/2286] xfs: don't scan across metadata/regular directory trees for dirents LOLLM points out that the directory repair code should never scan files in the regular directory tree when fixing a metadata directory and vice-versa. Amend the scan predicate to check this condition. Cc: stable@vger.kernel.org # v6.13 Fixes: 4f3d4dd1b04b2b ("xfs: define the on-disk format for the metadir feature") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 5db1ec32440908..6dae79668d319d 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1189,7 +1189,9 @@ xrep_dir_want_scan( struct xrep_dir *rd, const struct xfs_inode *ip) { - return ip != rd->sc->ip && !xrep_is_tempfile(ip); + return ip != rd->sc->ip && + xfs_is_metadir_inode(ip) == xfs_is_metadir_inode(rd->sc->ip) && + !xrep_is_tempfile(ip); } /* From c9f98b0f4f70c8cfd617dc2886bf2fc0a36ab9da Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:08:35 -0700 Subject: [PATCH 1928/2286] xfs: don't set a metadata directory's parent to sb_rootino LOLLM complains that it's weird to set a metadata directory's parent inumber to the regular directory tree root. Fix this even if nobody's ever going to notice this because metadata path lookups don't access dotdot. Cc: stable@vger.kernel.org # v6.13 Fixes: 4f3d4dd1b04b2b ("xfs: define the on-disk format for the metadir feature") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir_repair.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index 6dae79668d319d..c851d62b1d9855 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -1662,7 +1662,10 @@ xrep_dir_swap( ASSERT(sc->ilock_flags & XFS_ILOCK_EXCL); if (rd->pscan.parent_ino == NULLFSINO) { rd->needs_adoption = true; - rd->pscan.parent_ino = rd->sc->mp->m_sb.sb_rootino; + if (xfs_is_metadir_inode(rd->sc->ip)) + rd->pscan.parent_ino = rd->sc->mp->m_sb.sb_metadirino; + else + rd->pscan.parent_ino = rd->sc->mp->m_sb.sb_rootino; } /* From 18028768415e3c05ab7edb22d740e2f4dc52836a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:08:51 -0700 Subject: [PATCH 1929/2286] xfs: don't rescan ifree and icount when frozen LOLLM points out that it's pointless to rescan the AGs if we've already frozen the filesystem because there won't be any more perturbations. Fix this to mark the scan incomplete and exit. Cc: stable@vger.kernel.org # v5.2 Fixes: 75efa57d0bf5fc ("xfs: add online scrub for superblock counters") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/fscounters.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index eb1a095a0fa9b9..5984fe13a973b8 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -379,6 +379,11 @@ xchk_fscount_aggregate_agcounts( * to maintain ifree <= icount before giving up. */ if (fsc->ifree > fsc->icount) { + if (fsc->frozen) { + xchk_set_incomplete(sc); + return -EFSCORRUPTED; + } + if (tries--) goto retry; return -EDEADLOCK; From 47b1e941bb55f735d82ba2649412260da78b98f8 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:09:07 -0700 Subject: [PATCH 1930/2286] xfs: rescan the AGs if incore delalloc counter is negative LOLLM complains that percpu_counter_sum can return a negative number but we instead misinterpret that into a huge unsigned value and do bad things to fdblocks. It shouldn't do that, so let's add a retry loop and an attempt to freeze the filesystem to (hopefully) zero out the incore delalloc counter. It probably won't work, but at least failing the repair beats doing the wrong thing. Cc: stable@vger.kernel.org # v5.2 Fixes: 75efa57d0bf5fc ("xfs: add online scrub for superblock counters") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/fscounters.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index 5984fe13a973b8..67e159f39951ac 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -297,7 +297,7 @@ xchk_fscount_aggregate_agcounts( { struct xfs_mount *mp = sc->mp; struct xfs_perag *pag = NULL; - uint64_t delayed; + int64_t delayed; int tries = 8; int error = 0; @@ -359,6 +359,11 @@ xchk_fscount_aggregate_agcounts( * of the computation. */ delayed = percpu_counter_sum(&mp->m_delalloc_blks); + if (delayed < 0) { + ASSERT(delayed >= 0); + xchk_set_incomplete(sc); + return -EFSCORRUPTED; + } fsc->fdblocks -= delayed; trace_xchk_fscounters_calc(mp, fsc->icount, fsc->ifree, fsc->fdblocks, @@ -417,6 +422,7 @@ xchk_fscount_count_frextents( { struct xfs_mount *mp = sc->mp; struct xfs_rtgroup *rtg = NULL; + int64_t delayed; int error; fsc->frextents = 0; @@ -442,7 +448,14 @@ xchk_fscount_count_frextents( } } - fsc->frextents_delayed = percpu_counter_sum(&mp->m_delalloc_rtextents); + delayed = percpu_counter_sum(&mp->m_delalloc_rtextents); + if (delayed < 0) { + ASSERT(delayed >= 0); + xchk_set_incomplete(sc); + return -EFSCORRUPTED; + } + fsc->frextents_delayed = delayed; + return 0; } #else From 6e82b7641889ad0198f3bf17a5e4c6fe7aab0850 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:09:22 -0700 Subject: [PATCH 1931/2286] xfs: don't try to repair rtsummary if the summary file isn't computed LOLLM observed that we can erroneously try to repair the rtsummary file if we've not yet computed its contents. That results in a corrupt summary file, so let's add a flag to track whether or not the incore data is complete. Cc: stable@vger.kernel.org # v6.10 Fixes: abf039e2e4afde ("xfs: online repair of realtime summaries") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/rtsummary.c | 9 ++++++++- fs/xfs/scrub/rtsummary.h | 1 + fs/xfs/scrub/rtsummary_repair.c | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/xfs/scrub/rtsummary.c b/fs/xfs/scrub/rtsummary.c index 546b335ade1354..fa358746beaf5c 100644 --- a/fs/xfs/scrub/rtsummary.c +++ b/fs/xfs/scrub/rtsummary.c @@ -212,13 +212,20 @@ xchk_rtsum_compute( { struct xfs_mount *mp = sc->mp; struct xfs_rtgroup *rtg = sc->sr.rtg; + struct xchk_rtsummary *rts = sc->buf; + int error; /* If the bitmap size doesn't match the computed size, bail. */ if (XFS_FSB_TO_B(mp, xfs_rtbitmap_blockcount(mp)) != rtg_bitmap(rtg)->i_disk_size) return -EFSCORRUPTED; - return xfs_rtalloc_query_all(rtg, sc->tp, xchk_rtsum_record_free, sc); + error = xfs_rtalloc_query_all(rtg, sc->tp, xchk_rtsum_record_free, sc); + if (error) + return error; + + rts->summary_computed = true; + return 0; } /* Compare the rtsummary file against the one we computed. */ diff --git a/fs/xfs/scrub/rtsummary.h b/fs/xfs/scrub/rtsummary.h index e44b04cb6e2d5e..af1ab64c3f2125 100644 --- a/fs/xfs/scrub/rtsummary.h +++ b/fs/xfs/scrub/rtsummary.h @@ -17,6 +17,7 @@ struct xchk_rtsummary { xfs_filblks_t rsumblocks; unsigned int rsumlevels; unsigned int resblks; + bool summary_computed; /* suminfo position of xfile as we write buffers to disk. */ xfs_rtsumoff_t prep_wordoff; diff --git a/fs/xfs/scrub/rtsummary_repair.c b/fs/xfs/scrub/rtsummary_repair.c index e91dbad25d6de3..1aa5e81626936a 100644 --- a/fs/xfs/scrub/rtsummary_repair.c +++ b/fs/xfs/scrub/rtsummary_repair.c @@ -109,7 +109,7 @@ xrep_rtsummary( return -EOPNOTSUPP; /* Walk away if we disagree on the size of the rt bitmap. */ - if (rts->rbmblocks != mp->m_sb.sb_rbmblocks) + if (rts->rbmblocks != mp->m_sb.sb_rbmblocks || !rts->summary_computed) return 0; /* Make sure any problems with the fork are fixed. */ From 9d9ea6e6cbc4cb1a36fb79f873d97e1ac2fe1a64 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:09:38 -0700 Subject: [PATCH 1932/2286] xfs: update quota flags on secondary supers when metadir enabled xfs/085 exposes an interesting bug when metadir is enabled, quotas are enabled via mount option, and the primary superblock crc fails validation: Phase 1 - find and verify superblock... bad primary superblock - bad CRC in superblock !!! attempting to find secondary superblock... .found candidate secondary superblock... verified secondary superblock... quota accounting and enforcement flags lost Phase 3 - for each AG... - scan and clear agi unlinked lists... - process known inodes and perform inode discovery... - agno = 0 - agno = 1 - agno = 2 unexpected regular file inode 16908418 with metadata flag will zap cleared inode 16908418 unexpected regular file inode 16908419 with metadata flag will zap cleared inode 16908419 unexpected regular file inode 16908420 with metadata flag will zap cleared inode 16908420 - agno = 3 - process newly discovered inodes... Quota files have been created, but xfs_repair thinks that quotas aren't enabled and trashes the quota files. Looking at the filesystem reveals that the quota accounting flags were indeed persisted to the primary superblock but not the secondary: # xfs_db -c 'sb 0' -c 'print qflags' -c 'sb 1' -c 'print qflags' /dev/sdf Metadata corruption detected at 0x561c68436080, inode 0x81 dinode xfs_db: Failed to load metadir root directory, error 117 Metadata corruption detected at 0x561c683f7c80, xfs_agf block 0x1/0x200 xfs_db: cannot init perag data (117). Continuing anyway. Metadata CRC error detected at 0x561c6844d1b5, xfs_sb block 0x0/0x200 qflags = 0x7cf qflags = 0 On a metadir filesystem, quota state is supposed to persist across mount cycles if the sysadmin doesn't provide any quota mount options. Therefore, any time we update the quota flags on a metadir filesystem, we should write the same change to the secondary supers so that xfs_repair will find the quota data if it ever has to restore from a secondary. Cc: stable@vger.kernel.org # v6.13 Fixes: d5d9dd5b3026a8 ("xfs: persist quota flags with metadir") Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_mount.c | 9 ++++++++- fs/xfs/xfs_qm.c | 36 +++++++++++++++++++++++++++++++++++- fs/xfs/xfs_quota.h | 9 +++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/fs/xfs/xfs_mount.c b/fs/xfs/xfs_mount.c index be90c7b0399484..8838eff1f054d5 100644 --- a/fs/xfs/xfs_mount.c +++ b/fs/xfs/xfs_mount.c @@ -446,6 +446,8 @@ int xfs_mount_reset_sbqflags( struct xfs_mount *mp) { + int error; + mp->m_qflags = 0; /* It is OK to look at sb_qflags in the mount path without m_sb_lock. */ @@ -458,7 +460,12 @@ xfs_mount_reset_sbqflags( if (!xfs_fs_writable(mp, SB_FREEZE_WRITE)) return 0; - return xfs_sync_sb(mp, false); + error = xfs_sync_sb(mp, false); + if (error) + return error; + + xfs_qm_update_secondary_sb(mp); + return 0; } static const char *const xfs_free_pool_name[] = { diff --git a/fs/xfs/xfs_qm.c b/fs/xfs/xfs_qm.c index 008fed8624be2c..dd5011f578a63b 100644 --- a/fs/xfs/xfs_qm.c +++ b/fs/xfs/xfs_qm.c @@ -693,6 +693,32 @@ xfs_qm_create_metadir_qinos( return 0; } +/* + * For metadir filesystems, quota flag state persists across mounts, so update + * secondary superblocks in case xfs_repair ever has to recover the primary + * from a secondary. growfs also updates backup supers so lock against that. + * Note that the secondary backups are opportunistic, so we log but do not + * return write errors. + */ +void +__xfs_qm_update_secondary_sb( + struct xfs_mount *mp, + const char *func) +{ + int error; + + if (!xfs_has_metadir(mp)) + return; + + mutex_lock(&mp->m_growlock); + error = xfs_update_secondary_sbs(mp); + mutex_unlock(&mp->m_growlock); + + if (error) + xfs_alert(mp, "%s: Secondary super update failed, err=%d!", + func, error); +} + /* * Add QUOTABIT to sb_versionnum and initialize qflags in preparation for * creating quota files on a metadir filesystem. @@ -718,7 +744,12 @@ xfs_qm_prep_metadir_sb( spin_unlock(&mp->m_sb_lock); xfs_log_sb(tp); - return xfs_trans_commit(tp); + error = xfs_trans_commit(tp); + if (error) + return error; + + xfs_qm_update_secondary_sb(mp); + return 0; } /* @@ -1683,6 +1714,9 @@ xfs_qm_mount_quotas( ASSERT(!(XFS_IS_QUOTA_ON(mp))); xfs_alert(mp, "%s: Superblock update failed!", __func__); + } else { + /* Primary update succeeded, try to write secondary */ + xfs_qm_update_secondary_sb(mp); } } diff --git a/fs/xfs/xfs_quota.h b/fs/xfs/xfs_quota.h index 105e6eb5762011..b323a72dafbcde 100644 --- a/fs/xfs/xfs_quota.h +++ b/fs/xfs/xfs_quota.h @@ -151,6 +151,8 @@ void xfs_dqtrx_hook_setup(struct xfs_dqtrx_hook *hook, notifier_fn_t mod_fn, xfs_trans_mod_dquot((tp), (dqp), (field), (delta)) # endif /* CONFIG_XFS_LIVE_HOOKS */ +void __xfs_qm_update_secondary_sb(struct xfs_mount *mp, + const char *func); #else static inline int xfs_qm_vop_dqalloc(struct xfs_inode *ip, kuid_t kuid, kgid_t kgid, @@ -216,8 +218,15 @@ static inline int xfs_quota_reserve_blkres(struct xfs_inode *ip, int64_t blocks) # define xfs_dqtrx_hook_disable() ((void)0) # endif /* CONFIG_XFS_LIVE_HOOKS */ +static inline void __xfs_qm_update_secondary_sb(struct xfs_mount *mp, + const char *func) +{ +} #endif /* CONFIG_XFS_QUOTA */ +#define xfs_qm_update_secondary_sb(mp) \ + __xfs_qm_update_secondary_sb((mp), __func__) + static inline void xfs_quota_unreserve_blkres(struct xfs_inode *ip, uint64_t blocks) { From 19b5653cc870788df71e7fcf2dd7b6131d8868dc Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:09:54 -0700 Subject: [PATCH 1933/2286] xfs: refactor directory data block walking loops in scrub Hoist the inner loop code of the directory data block walking code in the directory scrub and repair code to separate functions in preparation for strengthening the validation in a subsequent patch. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 56 ++++++++++++++++++++++++++---- fs/xfs/scrub/dir_repair.c | 73 ++++++++++++++++++++++++++++----------- 2 files changed, 102 insertions(+), 27 deletions(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index a63eebf39fd496..8de593c5382d11 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -315,6 +315,45 @@ xchk_dir_actor( return error; } +/* + * Deal with an unused directory entry. Returns a positive number of bytes to + * advance in the directory block, zero to stop processing, or -1 if corruption + * was found. + */ +static int +xchk_dir_check_unused( + struct xchk_da_btree *ds, + struct xfs_buf *bp, + unsigned int iter_off, + unsigned int end) +{ + struct xfs_dir2_data_unused *dup = bp->b_addr + iter_off; + + return be16_to_cpu(dup->length); +} + +/* + * Check a directory entry. Returns a positive number of bytes to advance + * in the directory block, zero to stop processing, or -1 if corruption was + * found. + */ +static int +xchk_dir_check_entry( + struct xchk_da_btree *ds, + struct xfs_buf *bp, + unsigned int iter_off, + unsigned int end, + struct xfs_dir2_data_entry *dent) +{ + struct xfs_dir2_data_entry *dep = bp->b_addr + iter_off; + struct xfs_mount *mp = ds->state->mp; + + if (dep == dent) + return 0; + + return xfs_dir2_data_entsize(mp, dep->namelen); +} + /* Scrub a directory btree record. */ STATIC int xchk_dir_rec( @@ -391,21 +430,24 @@ xchk_dir_rec( goto out_relse; } for (;;) { - struct xfs_dir2_data_entry *dep = bp->b_addr + iter_off; struct xfs_dir2_data_unused *dup = bp->b_addr + iter_off; + int advance; if (iter_off >= end) { xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); goto out_relse; } - if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { - iter_off += be16_to_cpu(dup->length); - continue; - } - if (dep == dent) + if (dup->freetag == cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) + advance = xchk_dir_check_unused(ds, bp, iter_off, end); + else + advance = xchk_dir_check_entry(ds, bp, iter_off, end, + dent); + if (advance < 0) + goto out_relse; + if (!advance) break; - iter_off += xfs_dir2_data_entsize(mp, dep->namelen); + iter_off += advance; } /* Retrieve the entry, sanity check it, and compare hashes. */ diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index c851d62b1d9855..ea4e729ec03662 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -462,6 +462,52 @@ xrep_dir_salvage_data_entry( return xrep_dir_salvage_entry(rd, dep->name, dep->namelen, ino); } +/* + * Deal with an unused directory entry. Returns a positive number of bytes to + * advance in the directory block, or zero to stop processing. + */ +static int +xrep_dir_recover_unused( + struct xfs_buf *bp, + unsigned int offset, + unsigned int end) +{ + struct xfs_dir2_data_unused *dup = bp->b_addr + offset; + + return be16_to_cpu(dup->length); +} + +/* + * Salvage a directory entry. Returns a positive number of bytes to advance + * in the directory block, zero to stop processing, or a negative errno. + */ +static int +xrep_dir_recover_entry( + struct xrep_dir *rd, + struct xfs_buf *bp, + unsigned int offset, + unsigned int end) +{ + struct xfs_dir2_data_entry *dep = bp->b_addr + offset; + unsigned int advance; + int error; + + /* Don't walk off the end of the block. */ + advance = xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + if (!advance) + return 0; + if (offset + advance > end) + return 0; + + /* Ok, let's save this entry. */ + error = xrep_dir_salvage_data_entry(rd, dep); + if (error < 0) + return error; + + return advance; +} + + /* Try to recover block/data format directory entries. */ STATIC int xrep_dir_recover_data( @@ -483,33 +529,20 @@ xrep_dir_recover_data( while (offset < end) { struct xfs_dir2_data_unused *dup = bp->b_addr + offset; - struct xfs_dir2_data_entry *dep = bp->b_addr + offset; - unsigned int advance; + int advance; if (xchk_should_terminate(rd->sc, &error)) return error; - /* Skip unused entries. */ - if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) { - if (!dup->length) - break; - offset += be16_to_cpu(dup->length); - continue; - } - - /* Don't walk off the end of the block. */ - advance = xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); + if (dup->freetag == cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) + advance = xrep_dir_recover_unused(bp, offset, end); + else + advance = xrep_dir_recover_entry(rd, bp, offset, end); + if (advance < 0) + return advance; if (!advance) break; offset += advance; - if (offset > end) - break; - - /* Ok, let's save this entry. */ - error = xrep_dir_salvage_data_entry(rd, dep); - if (error) - return error; - } return 0; From 5d470234d04dae6fe2ca2cf4b9ef277b78c7e05a Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:10:09 -0700 Subject: [PATCH 1934/2286] xfs: improve dirent bounds checking in scrub and repair LOLLM complains that we don't do enough bounds checking of the directory entries in directory blocks when we're looking for errors or trying to salvage entries. Let's improve that with more detailed checks. Cc: stable@vger.kernel.org # v4.16 Fixes: ce92d29ddf9908 ("xfs: directory scrubber must walk through data block to offset") Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/dir.c | 68 +++++++++++++++++++++++++++++++++------ fs/xfs/scrub/dir_repair.c | 37 ++++++++++++++++++--- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/fs/xfs/scrub/dir.c b/fs/xfs/scrub/dir.c index 8de593c5382d11..d99e2a73272306 100644 --- a/fs/xfs/scrub/dir.c +++ b/fs/xfs/scrub/dir.c @@ -325,11 +325,24 @@ xchk_dir_check_unused( struct xchk_da_btree *ds, struct xfs_buf *bp, unsigned int iter_off, - unsigned int end) + unsigned int end, + xfs_dablk_t rec_bno) { struct xfs_dir2_data_unused *dup = bp->b_addr + iter_off; + unsigned int advance; + + if (iter_off + sizeof(*dup) > end) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + return -1; + } + + advance = xfs_dir2_data_unusedsize(be16_to_cpu(dup->length)); + if (!advance || iter_off + advance > end) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + return -1; + } - return be16_to_cpu(dup->length); + return advance; } /* @@ -343,15 +356,31 @@ xchk_dir_check_entry( struct xfs_buf *bp, unsigned int iter_off, unsigned int end, - struct xfs_dir2_data_entry *dent) + struct xfs_dir2_data_entry *dent, + xfs_dablk_t rec_bno) { struct xfs_dir2_data_entry *dep = bp->b_addr + iter_off; struct xfs_mount *mp = ds->state->mp; + unsigned int advance; + + /* must have namelen */ + advance = offsetof(typeof(*dep), name); + if (iter_off + advance >= end) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + return -1; + } + + /* Don't walk off the end of the block. */ + advance = xfs_dir2_data_entsize(mp, dep->namelen); + if (!advance || iter_off + advance > end) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + return -1; + } if (dep == dent) return 0; - return xfs_dir2_data_entsize(mp, dep->namelen); + return advance; } /* Scrub a directory btree record. */ @@ -379,6 +408,7 @@ xchk_dir_rec( xfs_dahash_t hash; struct xfs_dir3_icleaf_hdr hdr; unsigned int tag; + bool foundit = false; int error; ASSERT(blk->magic == XFS_DIR2_LEAF1_MAGIC || @@ -423,33 +453,44 @@ xchk_dir_rec( dent = bp->b_addr + off; /* Make sure we got a real directory entry. */ - iter_off = geo->data_entry_offset; end = xfs_dir3_data_end_offset(geo, bp->b_addr); if (!end) { xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); goto out_relse; } - for (;;) { + iter_off = geo->data_entry_offset; + while (iter_off < end) { struct xfs_dir2_data_unused *dup = bp->b_addr + iter_off; int advance; - if (iter_off >= end) { + /* must have freetag */ + advance = offsetof(typeof(*dup), length); + if (iter_off + advance >= end) { xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); goto out_relse; } if (dup->freetag == cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) - advance = xchk_dir_check_unused(ds, bp, iter_off, end); + advance = xchk_dir_check_unused(ds, bp, iter_off, end, + rec_bno); else advance = xchk_dir_check_entry(ds, bp, iter_off, end, - dent); + dent, rec_bno); if (advance < 0) goto out_relse; - if (!advance) + if (!advance) { + foundit = true; break; + } iter_off += advance; } + /* Hash tree must point to the exact dirent */ + if (!foundit) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + goto out_relse; + } + /* Retrieve the entry, sanity check it, and compare hashes. */ ino = be64_to_cpu(dent->inumber); hash = be32_to_cpu(ent->hashval); @@ -461,6 +502,13 @@ xchk_dir_rec( goto out_relse; } + /* Name must not overflow end of block */ + if ((char *)dent->name + dent->namelen > + (char *)bp->b_addr + BBTOB(bp->b_length)) { + xchk_fblock_set_corrupt(ds->sc, XFS_DATA_FORK, rec_bno); + goto out_relse; + } + /* Does the directory hash match? */ dname.name = dent->name; dname.len = dent->namelen; diff --git a/fs/xfs/scrub/dir_repair.c b/fs/xfs/scrub/dir_repair.c index ea4e729ec03662..518e15d93d3b42 100644 --- a/fs/xfs/scrub/dir_repair.c +++ b/fs/xfs/scrub/dir_repair.c @@ -473,8 +473,16 @@ xrep_dir_recover_unused( unsigned int end) { struct xfs_dir2_data_unused *dup = bp->b_addr + offset; + unsigned int advance; + + if (offset + sizeof(*dup) > end) + return 0; + + advance = xfs_dir2_data_unusedsize(be16_to_cpu(dup->length)); + if (!advance || offset + advance > end) + return 0; - return be16_to_cpu(dup->length); + return advance; } /* @@ -492,11 +500,14 @@ xrep_dir_recover_entry( unsigned int advance; int error; + /* must have namelen */ + advance = offsetof(typeof(*dep), name); + if (offset + advance >= end) + return 0; + /* Don't walk off the end of the block. */ advance = xfs_dir2_data_entsize(rd->sc->mp, dep->namelen); - if (!advance) - return 0; - if (offset + advance > end) + if (!advance || offset + advance > end) return 0; /* Ok, let's save this entry. */ @@ -534,6 +545,11 @@ xrep_dir_recover_data( if (xchk_should_terminate(rd->sc, &error)) return error; + /* must have freetag */ + advance = offsetof(typeof(*dup), length); + if (offset + advance >= end) + break; + if (dup->freetag == cpu_to_be16(XFS_DIR2_DATA_FREE_TAG)) advance = xrep_dir_recover_unused(bp, offset, end); else @@ -565,6 +581,19 @@ xrep_dir_recover_sf( hdr = ifp->if_data; end = (unsigned char *)ifp->if_data + ifp->if_bytes; + /* sf header must be big enough for count/i8count */ + if (!hdr || ifp->if_bytes < 2) + return 0; + + /* and large enough for the parent inumber */ + if (hdr->i8count) { + if (ifp->if_bytes < offsetof(struct xfs_dir2_sf_hdr, parent[8])) + return 0; + } else { + if (ifp->if_bytes < offsetof(struct xfs_dir2_sf_hdr, parent[4])) + return 0; + } + ino = xfs_dir2_sf_get_parent_ino(hdr); trace_xrep_dir_salvaged_parent(rd->sc->ip, ino); From a55686b2f50500d0a453b43d45677c9824fb4437 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:10:25 -0700 Subject: [PATCH 1935/2286] xfs: drop unnecessary sa parameter from the xrep_ag init functions LOLLM went down a crazy path complaining about the horrible things that happen in these functions if sa != &sc->sa. However, I observe that the sa argument is never not &sc->sa, so let's just drop the parameter. There's no bug here, but let's get rid of some stupid. Signed-off-by: Darrick J. Wong Assisted-by: LOLLM # finding obvious bugs Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/bmap_repair.c | 2 +- fs/xfs/scrub/cow_repair.c | 2 +- fs/xfs/scrub/ialloc_repair.c | 2 +- fs/xfs/scrub/refcount_repair.c | 2 +- fs/xfs/scrub/repair.c | 10 +++++----- fs/xfs/scrub/repair.h | 5 ++--- fs/xfs/scrub/rmap_repair.c | 4 ++-- fs/xfs/scrub/rtrefcount_repair.c | 2 +- fs/xfs/scrub/rtrmap_repair.c | 2 +- 10 files changed, 16 insertions(+), 17 deletions(-) diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 05f27c927adfb9..854cfbbd9ecf4f 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -370,7 +370,7 @@ xrep_abt_find_freespace( xagb_bitmap_init(&ra->not_allocbt_blocks); - xrep_ag_btcur_init(sc, &sc->sa); + xrep_ag_btcur_init(sc); /* * Iterate all the reverse mappings to find gaps in the physical diff --git a/fs/xfs/scrub/bmap_repair.c b/fs/xfs/scrub/bmap_repair.c index 03af6cb92fcf5e..2fcaf9c5aed9b6 100644 --- a/fs/xfs/scrub/bmap_repair.c +++ b/fs/xfs/scrub/bmap_repair.c @@ -359,7 +359,7 @@ xrep_bmap_scan_ag( struct xfs_scrub *sc = rb->sc; int error; - error = xrep_ag_init(sc, pag, &sc->sa); + error = xrep_ag_init(sc, pag); if (error) return error; diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index e49d9396edc527..3d4e94757de040 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -280,7 +280,7 @@ xrep_cow_find_bad( if (!pag) return -EFSCORRUPTED; - error = xrep_ag_init(sc, pag, &sc->sa); + error = xrep_ag_init(sc, pag); if (error) goto out_pag; diff --git a/fs/xfs/scrub/ialloc_repair.c b/fs/xfs/scrub/ialloc_repair.c index 43846b3bbf4ed4..f0f2232a9686d8 100644 --- a/fs/xfs/scrub/ialloc_repair.c +++ b/fs/xfs/scrub/ialloc_repair.c @@ -473,7 +473,7 @@ xrep_ibt_find_inodes( ri->rie.ir_startino = NULLAGINO; /* Collect all reverse mappings for inode blocks. */ - xrep_ag_btcur_init(sc, &sc->sa); + xrep_ag_btcur_init(sc); error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_ibt_walk_rmap, ri); xchk_ag_btcur_free(&sc->sa); if (error) diff --git a/fs/xfs/scrub/refcount_repair.c b/fs/xfs/scrub/refcount_repair.c index 72de263117a75a..dfb7817ef3f27f 100644 --- a/fs/xfs/scrub/refcount_repair.c +++ b/fs/xfs/scrub/refcount_repair.c @@ -412,7 +412,7 @@ xrep_refc_find_refcounts( bool have; int error; - xrep_ag_btcur_init(sc, &sc->sa); + xrep_ag_btcur_init(sc); /* * Set up a bag to store all the rmap records that we're tracking to diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index dd00d4ed8c17ba..f721e0dad1942a 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -919,10 +919,10 @@ xrep_reset_fork_to_extents( */ void xrep_ag_btcur_init( - struct xfs_scrub *sc, - struct xchk_ag *sa) + struct xfs_scrub *sc) { struct xfs_mount *mp = sc->mp; + struct xchk_ag *sa = &sc->sa; /* Set up a bnobt cursor for cross-referencing. */ if (sc->sm->sm_type != XFS_SCRUB_TYPE_BNOBT && @@ -1021,9 +1021,9 @@ xrep_reinit_pagi( int xrep_ag_init( struct xfs_scrub *sc, - struct xfs_perag *pag, - struct xchk_ag *sa) + struct xfs_perag *pag) { + struct xchk_ag *sa = &sc->sa; int error; ASSERT(!sa->pag); @@ -1038,7 +1038,7 @@ xrep_ag_init( /* Grab our own passive reference from the caller's ref. */ sa->pag = xfs_perag_hold(pag); - xrep_ag_btcur_init(sc, sa); + xrep_ag_btcur_init(sc); return 0; } diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index a248f9f039241b..6d9be1e8b2a0da 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -101,9 +101,8 @@ int xrep_setup_ag_allocbt(struct xfs_scrub *sc); struct xfs_imap; int xrep_setup_inode(struct xfs_scrub *sc, const struct xfs_imap *imap); -void xrep_ag_btcur_init(struct xfs_scrub *sc, struct xchk_ag *sa); -int xrep_ag_init(struct xfs_scrub *sc, struct xfs_perag *pag, - struct xchk_ag *sa); +void xrep_ag_btcur_init(struct xfs_scrub *sc); +int xrep_ag_init(struct xfs_scrub *sc, struct xfs_perag *pag); #ifdef CONFIG_XFS_RT int xrep_rtgroup_init(struct xfs_scrub *sc, struct xfs_rtgroup *rtg, struct xchk_rt *sr, unsigned int rtglock_flags); diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index 725035bf490329..aac9e534df798f 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -910,7 +910,7 @@ xrep_rmap_find_rmaps( int error; /* Find all the per-AG metadata. */ - xrep_ag_btcur_init(sc, &sc->sa); + xrep_ag_btcur_init(sc); error = xrep_rmap_find_inode_rmaps(rr); if (error) @@ -1167,7 +1167,7 @@ xrep_rmap_reserve_space( } while (!done); /* Emit rmaps for everything in the free space bitmap. */ - xrep_ag_btcur_init(rr->sc, &rr->sc->sa); + xrep_ag_btcur_init(rr->sc); error = xrep_rmap_stash_bitmap(rr, &freesp_blocks, &XFS_RMAP_OINFO_AG); xchk_ag_btcur_free(&rr->sc->sa); diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index 5ca9959dfacc40..13b559e506f2cd 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -385,7 +385,7 @@ xrep_rtrefc_scan_ag( struct xfs_scrub *sc = rr->sc; int error; - error = xrep_ag_init(sc, pag, &sc->sa); + error = xrep_ag_init(sc, pag); if (error) return error; diff --git a/fs/xfs/scrub/rtrmap_repair.c b/fs/xfs/scrub/rtrmap_repair.c index 5cfa4470c57c0f..83b5bb7db6351a 100644 --- a/fs/xfs/scrub/rtrmap_repair.c +++ b/fs/xfs/scrub/rtrmap_repair.c @@ -413,7 +413,7 @@ xrep_rtrmap_scan_ag( struct xfs_scrub *sc = rr->sc; int error; - error = xrep_ag_init(sc, pag, &sc->sa); + error = xrep_ag_init(sc, pag); if (error) return error; From ec72fccbc852fdc30bebb31146374fc99ae0b87d Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:10:40 -0700 Subject: [PATCH 1936/2286] xfs: drop unnecessary sr parameter from xrep_rtgroup init functions Similar to the last patch, drop this unnecessary sr parameter instead of dealing with pointer aliasing. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap_repair.c | 7 +++---- fs/xfs/scrub/cow_repair.c | 4 ++-- fs/xfs/scrub/inode_repair.c | 2 +- fs/xfs/scrub/repair.c | 9 +++++---- fs/xfs/scrub/repair.h | 6 +++--- fs/xfs/scrub/rtbitmap_repair.c | 2 +- fs/xfs/scrub/rtrefcount_repair.c | 2 +- fs/xfs/scrub/rtrmap_repair.c | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/fs/xfs/scrub/bmap_repair.c b/fs/xfs/scrub/bmap_repair.c index 2fcaf9c5aed9b6..a4130b03814b89 100644 --- a/fs/xfs/scrub/bmap_repair.c +++ b/fs/xfs/scrub/bmap_repair.c @@ -456,10 +456,9 @@ xrep_bmap_scan_rtgroup( if (!xfs_has_rtrmapbt(sc->mp)) return 0; - error = xrep_rtgroup_init(sc, rtg, &sc->sr, - XFS_RTGLOCK_RMAP | - XFS_RTGLOCK_REFCOUNT | - XFS_RTGLOCK_BITMAP_SHARED); + error = xrep_rtgroup_init(sc, rtg, XFS_RTGLOCK_RMAP | + XFS_RTGLOCK_REFCOUNT | + XFS_RTGLOCK_BITMAP_SHARED); if (error) return error; diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index 3d4e94757de040..f2e650c6f7afdf 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -371,8 +371,8 @@ xrep_cow_find_bad_rt( if (!rtg) return -EFSCORRUPTED; - error = xrep_rtgroup_init(sc, rtg, &sc->sr, - XFS_RTGLOCK_RMAP | XFS_RTGLOCK_REFCOUNT); + error = xrep_rtgroup_init(sc, rtg, XFS_RTGLOCK_RMAP | + XFS_RTGLOCK_REFCOUNT); if (error) goto out_rtg; diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index c3842145082f9e..32aeacfd8da9ae 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -816,7 +816,7 @@ xrep_dinode_count_rtgroup_rmaps( struct xfs_scrub *sc = ri->sc; int error; - error = xrep_rtgroup_init(sc, rtg, &sc->sr, XFS_RTGLOCK_RMAP); + error = xrep_rtgroup_init(sc, rtg, XFS_RTGLOCK_RMAP); if (error) return error; diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index f721e0dad1942a..3c3820315400f8 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -1046,10 +1046,10 @@ xrep_ag_init( /* Initialize all the btree cursors for a RT repair. */ void xrep_rtgroup_btcur_init( - struct xfs_scrub *sc, - struct xchk_rt *sr) + struct xfs_scrub *sc) { struct xfs_mount *mp = sc->mp; + struct xchk_rt *sr = &sc->sr; ASSERT(sr->rtg != NULL); @@ -1072,9 +1072,10 @@ int xrep_rtgroup_init( struct xfs_scrub *sc, struct xfs_rtgroup *rtg, - struct xchk_rt *sr, unsigned int rtglock_flags) { + struct xchk_rt *sr = &sc->sr; + ASSERT(sr->rtg == NULL); xfs_rtgroup_lock(rtg, rtglock_flags); @@ -1082,7 +1083,7 @@ xrep_rtgroup_init( /* Grab our own passive reference from the caller's ref. */ sr->rtg = xfs_rtgroup_hold(rtg); - xrep_rtgroup_btcur_init(sc, sr); + xrep_rtgroup_btcur_init(sc); return 0; } diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index 6d9be1e8b2a0da..842f811491be28 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -105,13 +105,13 @@ void xrep_ag_btcur_init(struct xfs_scrub *sc); int xrep_ag_init(struct xfs_scrub *sc, struct xfs_perag *pag); #ifdef CONFIG_XFS_RT int xrep_rtgroup_init(struct xfs_scrub *sc, struct xfs_rtgroup *rtg, - struct xchk_rt *sr, unsigned int rtglock_flags); -void xrep_rtgroup_btcur_init(struct xfs_scrub *sc, struct xchk_rt *sr); + unsigned int rtglock_flags); +void xrep_rtgroup_btcur_init(struct xfs_scrub *sc); int xrep_require_rtext_inuse(struct xfs_scrub *sc, xfs_rgblock_t rgbno, xfs_filblks_t len); xfs_extlen_t xrep_calc_rtgroup_resblks(struct xfs_scrub *sc); #else -# define xrep_rtgroup_init(sc, rtg, sr, lockflags) (-ENOSYS) +# define xrep_rtgroup_init(sc, rtg, lockflags) (-ENOSYS) # define xrep_calc_rtgroup_resblks(sc) (0) #endif /* CONFIG_XFS_RT */ diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index 34f5017b4ecba1..054acbf025f54a 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -339,7 +339,7 @@ xrep_rtbitmap_find_freespace( /* Prepare a buffer of ones so that we can accelerate bulk setting. */ memset(rtb->words, 0xFF, mp->m_sb.sb_blocksize); - xrep_rtgroup_btcur_init(sc, &sc->sr); + xrep_rtgroup_btcur_init(sc); error = xfs_rmap_query_all(sc->sr.rmap_cur, xrep_rtbitmap_walk_rtrmap, rtb); if (error) diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index 13b559e506f2cd..8c384e2df37691 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -418,7 +418,7 @@ xrep_rtrefc_find_refcounts( } } - xrep_rtgroup_btcur_init(sc, &sc->sr); + xrep_rtgroup_btcur_init(sc); /* * Set up a bag to store all the rmap records that we're tracking to diff --git a/fs/xfs/scrub/rtrmap_repair.c b/fs/xfs/scrub/rtrmap_repair.c index 83b5bb7db6351a..503d29c44bd537 100644 --- a/fs/xfs/scrub/rtrmap_repair.c +++ b/fs/xfs/scrub/rtrmap_repair.c @@ -554,7 +554,7 @@ xrep_rtrmap_find_rmaps( } /* Find CoW staging extents. */ - xrep_rtgroup_btcur_init(sc, &sc->sr); + xrep_rtgroup_btcur_init(sc); error = xrep_rtrmap_find_refcount_rmaps(rr); xchk_rtgroup_btcur_free(&sc->sr); if (error) From 49869d5d0356c9468621de24d49bac43b7893782 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:10:56 -0700 Subject: [PATCH 1937/2286] xfs: drop unnecessary sa parameter from xchk_ag init functions Drop the sa parameter because it's always the same value. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/agheader.c | 16 ++++++++-------- fs/xfs/scrub/alloc_repair.c | 2 +- fs/xfs/scrub/bmap.c | 4 ++-- fs/xfs/scrub/bmap_repair.c | 2 +- fs/xfs/scrub/btree.c | 4 ++-- fs/xfs/scrub/common.c | 28 +++++++++++++++------------- fs/xfs/scrub/common.h | 17 +++++++---------- fs/xfs/scrub/cow_repair.c | 2 +- fs/xfs/scrub/fscounters.c | 4 ++-- fs/xfs/scrub/ialloc_repair.c | 2 +- fs/xfs/scrub/inode.c | 4 ++-- fs/xfs/scrub/refcount_repair.c | 2 +- fs/xfs/scrub/repair.c | 2 +- fs/xfs/scrub/rmap_repair.c | 4 ++-- fs/xfs/scrub/rtrefcount.c | 4 ++-- fs/xfs/scrub/rtrefcount_repair.c | 2 +- fs/xfs/scrub/rtrmap_repair.c | 2 +- fs/xfs/scrub/scrub.c | 2 +- 18 files changed, 51 insertions(+), 52 deletions(-) diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c index a765d3e5e0686b..be59b0b495472f 100644 --- a/fs/xfs/scrub/agheader.c +++ b/fs/xfs/scrub/agheader.c @@ -47,7 +47,7 @@ xchk_superblock_xref( agbno = XFS_SB_BLOCK(mp); - error = xchk_ag_init_existing(sc, agno, &sc->sa); + error = xchk_ag_init_existing(sc, agno); if (!xchk_xref_process_error(sc, agno, agbno, &error)) return; @@ -56,7 +56,7 @@ xchk_superblock_xref( xchk_xref_is_only_owned_by(sc, agbno, 1, &XFS_RMAP_OINFO_FS); xchk_xref_is_not_shared(sc, agbno, 1); xchk_xref_is_not_cow_staging(sc, agbno, 1); - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); } /* @@ -615,7 +615,7 @@ xchk_agf_xref( agbno = XFS_AGF_BLOCK(mp); - xchk_ag_btcur_init(sc, &sc->sa); + xchk_ag_btcur_init(sc); xchk_xref_is_used_space(sc, agbno, 1); xchk_agf_xref_freeblks(sc); @@ -648,7 +648,7 @@ xchk_agf( int level; int error = 0; - error = xchk_ag_read_headers(sc, agno, &sc->sa); + error = xchk_ag_read_headers(sc, agno); if (!xchk_process_error(sc, agno, XFS_AGF_BLOCK(sc->mp), &error)) goto out; xchk_buffer_recheck(sc, sc->sa.agf_bp); @@ -803,7 +803,7 @@ xchk_agfl_xref( agbno = XFS_AGFL_BLOCK(mp); - xchk_ag_btcur_init(sc, &sc->sa); + xchk_ag_btcur_init(sc); xchk_xref_is_used_space(sc, agbno, 1); xchk_xref_is_not_inode_chunk(sc, agbno, 1); @@ -831,7 +831,7 @@ xchk_agfl( int error; /* Lock the AGF and AGI so that nobody can touch this AG. */ - error = xchk_ag_read_headers(sc, agno, &sc->sa); + error = xchk_ag_read_headers(sc, agno); if (!xchk_process_error(sc, agno, XFS_AGFL_BLOCK(sc->mp), &error)) return error; if (!sc->sa.agf_bp) @@ -958,7 +958,7 @@ xchk_agi_xref( agbno = XFS_AGI_BLOCK(mp); - xchk_ag_btcur_init(sc, &sc->sa); + xchk_ag_btcur_init(sc); xchk_xref_is_used_space(sc, agbno, 1); xchk_xref_is_not_inode_chunk(sc, agbno, 1); @@ -1072,7 +1072,7 @@ xchk_agi( int level; int error = 0; - error = xchk_ag_read_headers(sc, agno, &sc->sa); + error = xchk_ag_read_headers(sc, agno); if (!xchk_process_error(sc, agno, XFS_AGI_BLOCK(sc->mp), &error)) goto out; xchk_buffer_recheck(sc, sc->sa.agi_bp); diff --git a/fs/xfs/scrub/alloc_repair.c b/fs/xfs/scrub/alloc_repair.c index 854cfbbd9ecf4f..2f60e4b517596e 100644 --- a/fs/xfs/scrub/alloc_repair.c +++ b/fs/xfs/scrub/alloc_repair.c @@ -406,7 +406,7 @@ xrep_abt_find_freespace( err_agfl: xfs_trans_brelse(sc->tp, agfl_bp); err: - xchk_ag_btcur_free(&sc->sa); + xchk_ag_btcur_free(sc); xagb_bitmap_destroy(&ra->not_allocbt_blocks); return error; } diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 4dff40dd426ae5..268f8f56faab87 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -396,7 +396,7 @@ xchk_bmap_iextent_xref( agbno = XFS_FSB_TO_AGBNO(mp, irec->br_startblock); len = irec->br_blockcount; - error = xchk_ag_init_existing(info->sc, agno, &info->sc->sa); + error = xchk_ag_init_existing(info->sc, agno); if (!xchk_fblock_process_error(info->sc, info->whichfork, irec->br_startoff, &error)) goto out_free; @@ -440,7 +440,7 @@ xchk_bmap_iextent_xref( } out_free: - xchk_ag_free(info->sc, &info->sc->sa); + xchk_ag_free(info->sc); } /* diff --git a/fs/xfs/scrub/bmap_repair.c b/fs/xfs/scrub/bmap_repair.c index a4130b03814b89..781ce3b29dca92 100644 --- a/fs/xfs/scrub/bmap_repair.c +++ b/fs/xfs/scrub/bmap_repair.c @@ -364,7 +364,7 @@ xrep_bmap_scan_ag( return error; error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_bmap_walk_rmap, rb); - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); return error; } diff --git a/fs/xfs/scrub/btree.c b/fs/xfs/scrub/btree.c index 43a7ea7308eea9..9c2c55ebe344b8 100644 --- a/fs/xfs/scrub/btree.c +++ b/fs/xfs/scrub/btree.c @@ -397,7 +397,7 @@ xchk_btree_check_block_owner( */ init_sa = bs->cur->bc_ops->type != XFS_BTREE_TYPE_AG; if (init_sa) { - error = xchk_ag_init_existing(bs->sc, agno, &bs->sc->sa); + error = xchk_ag_init_existing(bs->sc, agno); if (!xchk_btree_xref_process_error(bs->sc, bs->cur, level, &error)) goto out_free; @@ -418,7 +418,7 @@ xchk_btree_check_block_owner( out_free: if (init_sa) - xchk_ag_free(bs->sc, &bs->sc->sa); + xchk_ag_free(bs->sc); return error; } diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c index a61895872072d1..82cda70e4169ae 100644 --- a/fs/xfs/scrub/common.c +++ b/fs/xfs/scrub/common.c @@ -578,10 +578,10 @@ xchk_perag_drain_and_lock( int xchk_ag_read_headers( struct xfs_scrub *sc, - xfs_agnumber_t agno, - struct xchk_ag *sa) + xfs_agnumber_t agno) { struct xfs_mount *mp = sc->mp; + struct xchk_ag *sa = &sc->sa; ASSERT(!sa->pag); sa->pag = xfs_perag_get(mp, agno); @@ -594,8 +594,10 @@ xchk_ag_read_headers( /* Release all the AG btree cursors. */ void xchk_ag_btcur_free( - struct xchk_ag *sa) + struct xfs_scrub *sc) { + struct xchk_ag *sa = &sc->sa; + if (sa->refc_cur) xfs_btree_del_cursor(sa->refc_cur, XFS_BTREE_ERROR); if (sa->rmap_cur) @@ -620,10 +622,10 @@ xchk_ag_btcur_free( /* Initialize all the btree cursors for an AG. */ void xchk_ag_btcur_init( - struct xfs_scrub *sc, - struct xchk_ag *sa) + struct xfs_scrub *sc) { struct xfs_mount *mp = sc->mp; + struct xchk_ag *sa = &sc->sa; if (sa->agf_bp) { /* Set up a bnobt cursor for cross-referencing. */ @@ -675,10 +677,11 @@ xchk_ag_btcur_init( /* Release the AG header context and btree cursors. */ void xchk_ag_free( - struct xfs_scrub *sc, - struct xchk_ag *sa) + struct xfs_scrub *sc) { - xchk_ag_btcur_free(sa); + struct xchk_ag *sa = &sc->sa; + + xchk_ag_btcur_free(sc); xrep_reset_perag_resv(sc); if (sa->agf_bp) { xfs_trans_brelse(sc->tp, sa->agf_bp); @@ -704,16 +707,15 @@ xchk_ag_free( int xchk_ag_init( struct xfs_scrub *sc, - xfs_agnumber_t agno, - struct xchk_ag *sa) + xfs_agnumber_t agno) { int error; - error = xchk_ag_read_headers(sc, agno, sa); + error = xchk_ag_read_headers(sc, agno); if (error) return error; - xchk_ag_btcur_init(sc, sa); + xchk_ag_btcur_init(sc); return 0; } @@ -958,7 +960,7 @@ xchk_setup_ag_btree( if (error) return error; - return xchk_ag_init(sc, sc->sm->sm_agno, &sc->sa); + return xchk_ag_init(sc, sc->sm->sm_agno); } /* Push everything out of the log onto disk. */ diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index f0f073a9341337..8478f3a9bed33d 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -109,9 +109,8 @@ xchk_ino_dqattach(struct xfs_scrub *sc) int xchk_setup_fscounters(struct xfs_scrub *sc); int xchk_setup_nlinks(struct xfs_scrub *sc); -void xchk_ag_free(struct xfs_scrub *sc, struct xchk_ag *sa); -int xchk_ag_init(struct xfs_scrub *sc, xfs_agnumber_t agno, - struct xchk_ag *sa); +void xchk_ag_free(struct xfs_scrub *sc); +int xchk_ag_init(struct xfs_scrub *sc, xfs_agnumber_t agno); int xchk_perag_drain_and_lock(struct xfs_scrub *sc); /* @@ -122,10 +121,9 @@ int xchk_perag_drain_and_lock(struct xfs_scrub *sc); static inline int xchk_ag_init_existing( struct xfs_scrub *sc, - xfs_agnumber_t agno, - struct xchk_ag *sa) + xfs_agnumber_t agno) { - int error = xchk_ag_init(sc, agno, sa); + int error = xchk_ag_init(sc, agno); return error == -ENOENT ? -EFSCORRUPTED : error; } @@ -165,10 +163,9 @@ void xchk_rtgroup_free(struct xfs_scrub *sc, struct xchk_rt *sr); # define xchk_rtgroup_free(sc, sr) do { } while (0) #endif /* CONFIG_XFS_RT */ -int xchk_ag_read_headers(struct xfs_scrub *sc, xfs_agnumber_t agno, - struct xchk_ag *sa); -void xchk_ag_btcur_free(struct xchk_ag *sa); -void xchk_ag_btcur_init(struct xfs_scrub *sc, struct xchk_ag *sa); +int xchk_ag_read_headers(struct xfs_scrub *sc, xfs_agnumber_t agno); +void xchk_ag_btcur_free(struct xfs_scrub *sc); +void xchk_ag_btcur_init(struct xfs_scrub *sc); int xchk_count_rmap_ownedby_ag(struct xfs_scrub *sc, struct xfs_btree_cur *cur, const struct xfs_owner_info *oinfo, xfs_filblks_t *blocks); diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index f2e650c6f7afdf..5a209194ba97eb 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -340,7 +340,7 @@ xrep_cow_find_bad( xc->irec.br_blockcount); out_sa: - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); out_pag: xfs_perag_put(pag); return error; diff --git a/fs/xfs/scrub/fscounters.c b/fs/xfs/scrub/fscounters.c index 67e159f39951ac..310b4abf104f9a 100644 --- a/fs/xfs/scrub/fscounters.c +++ b/fs/xfs/scrub/fscounters.c @@ -265,7 +265,7 @@ xchk_fscount_btreeblks( xfs_filblks_t blocks; int error; - error = xchk_ag_init_existing(sc, agno, &sc->sa); + error = xchk_ag_init_existing(sc, agno); if (error) goto out_free; @@ -280,7 +280,7 @@ xchk_fscount_btreeblks( fsc->fdblocks += blocks - 1; out_free: - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); return error; } diff --git a/fs/xfs/scrub/ialloc_repair.c b/fs/xfs/scrub/ialloc_repair.c index f0f2232a9686d8..146b3fec6d347c 100644 --- a/fs/xfs/scrub/ialloc_repair.c +++ b/fs/xfs/scrub/ialloc_repair.c @@ -475,7 +475,7 @@ xrep_ibt_find_inodes( /* Collect all reverse mappings for inode blocks. */ xrep_ag_btcur_init(sc); error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_ibt_walk_rmap, ri); - xchk_ag_btcur_free(&sc->sa); + xchk_ag_btcur_free(sc); if (error) return error; diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index ac42867acbc4c9..6cded1eb6c2b6f 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -740,7 +740,7 @@ xchk_inode_xref( agno = XFS_INO_TO_AGNO(sc->mp, ino); agbno = XFS_INO_TO_AGBNO(sc->mp, ino); - error = xchk_ag_init_existing(sc, agno, &sc->sa); + error = xchk_ag_init_existing(sc, agno); if (!xchk_xref_process_error(sc, agno, agbno, &error)) goto out_free; @@ -752,7 +752,7 @@ xchk_inode_xref( xchk_inode_xref_bmap(sc, dip); out_free: - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); } /* diff --git a/fs/xfs/scrub/refcount_repair.c b/fs/xfs/scrub/refcount_repair.c index dfb7817ef3f27f..0aa01ad8971389 100644 --- a/fs/xfs/scrub/refcount_repair.c +++ b/fs/xfs/scrub/refcount_repair.c @@ -504,7 +504,7 @@ xrep_refc_find_refcounts( out_bag: rcbag_free(&rcstack); out_cur: - xchk_ag_btcur_free(&sc->sa); + xchk_ag_btcur_free(sc); return error; } diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index 3c3820315400f8..b1d9ab530d300b 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -68,7 +68,7 @@ xrep_attempt( trace_xrep_attempt(XFS_I(file_inode(sc->file)), sc->sm, error); - xchk_ag_btcur_free(&sc->sa); + xchk_ag_btcur_free(sc); xchk_rtgroup_btcur_free(&sc->sr); /* Repair whatever's broken. */ diff --git a/fs/xfs/scrub/rmap_repair.c b/fs/xfs/scrub/rmap_repair.c index aac9e534df798f..f88d7b62098b2b 100644 --- a/fs/xfs/scrub/rmap_repair.c +++ b/fs/xfs/scrub/rmap_repair.c @@ -926,7 +926,7 @@ xrep_rmap_find_rmaps( error = xrep_rmap_find_log_rmaps(rr); end_agscan: - xchk_ag_btcur_free(&sc->sa); + xchk_ag_btcur_free(sc); if (error) return error; @@ -1169,7 +1169,7 @@ xrep_rmap_reserve_space( /* Emit rmaps for everything in the free space bitmap. */ xrep_ag_btcur_init(rr->sc); error = xrep_rmap_stash_bitmap(rr, &freesp_blocks, &XFS_RMAP_OINFO_AG); - xchk_ag_btcur_free(&rr->sc->sa); + xchk_ag_btcur_free(rr->sc); out_bitmap: xagb_bitmap_destroy(&freesp_blocks); diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index b78784051308f3..3d439249cbd62d 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -269,7 +269,7 @@ xchk_rtrefcount_count_agblocks( xfs_filblks_t agblocks = 0; int error; - error = xchk_ag_init_existing(sc, agno, &sc->sa); + error = xchk_ag_init_existing(sc, agno); if (error) goto out_free; @@ -290,7 +290,7 @@ xchk_rtrefcount_count_agblocks( *blocks += agblocks; out_free: - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); return error; } diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index 8c384e2df37691..1dc894263d2b5a 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -390,7 +390,7 @@ xrep_rtrefc_scan_ag( return error; error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_rtrefc_walk_rmap, rr); - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); return error; } diff --git a/fs/xfs/scrub/rtrmap_repair.c b/fs/xfs/scrub/rtrmap_repair.c index 503d29c44bd537..b8837793878ebe 100644 --- a/fs/xfs/scrub/rtrmap_repair.c +++ b/fs/xfs/scrub/rtrmap_repair.c @@ -418,7 +418,7 @@ xrep_rtrmap_scan_ag( return error; error = xfs_rmap_query_all(sc->sa.rmap_cur, xrep_rtrmap_walk_rmap, rr); - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); return error; } diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 12c228b7f4770d..9dbd16546e8c8b 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -229,7 +229,7 @@ xchk_teardown( struct xfs_scrub *sc, int error) { - xchk_ag_free(sc, &sc->sa); + xchk_ag_free(sc); xchk_rtgroup_btcur_free(&sc->sr); if (sc->tp) { From 02c8e8d78792164df615a8c54411501e91fc7b64 Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:11:12 -0700 Subject: [PATCH 1938/2286] xfs: drop unnecessary sr parameter from xchk_rtgroup init functions Drop the sr parameter from the scrub rtgroup functions because the value is always the same. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/scrub/bmap.c | 9 ++++----- fs/xfs/scrub/bmap_repair.c | 4 ++-- fs/xfs/scrub/common.c | 22 ++++++++++++++-------- fs/xfs/scrub/common.h | 29 +++++++++++++---------------- fs/xfs/scrub/cow_repair.c | 4 ++-- fs/xfs/scrub/inode_repair.c | 4 ++-- fs/xfs/scrub/repair.c | 2 +- fs/xfs/scrub/rgsuper.c | 4 ++-- fs/xfs/scrub/rtbitmap.c | 4 ++-- fs/xfs/scrub/rtbitmap_repair.c | 2 +- fs/xfs/scrub/rtrefcount.c | 4 ++-- fs/xfs/scrub/rtrefcount_repair.c | 2 +- fs/xfs/scrub/rtrmap.c | 4 ++-- fs/xfs/scrub/rtrmap_repair.c | 6 +++--- fs/xfs/scrub/rtsummary.c | 4 ++-- fs/xfs/scrub/scrub.c | 4 ++-- 16 files changed, 55 insertions(+), 53 deletions(-) diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c index 268f8f56faab87..df6653f3477fc3 100644 --- a/fs/xfs/scrub/bmap.c +++ b/fs/xfs/scrub/bmap.c @@ -329,13 +329,12 @@ xchk_bmap_rt_iextent_xref( int error; error = xchk_rtgroup_init_existing(info->sc, - xfs_rtb_to_rgno(ip->i_mount, irec->br_startblock), - &info->sc->sr); + xfs_rtb_to_rgno(ip->i_mount, irec->br_startblock)); if (!xchk_fblock_process_error(info->sc, info->whichfork, irec->br_startoff, &error)) return; - error = xchk_rtgroup_lock(info->sc, &info->sc->sr, XCHK_RTGLOCK_ALL); + error = xchk_rtgroup_lock(info->sc, XCHK_RTGLOCK_ALL); if (!xchk_fblock_process_error(info->sc, info->whichfork, irec->br_startoff, &error)) goto out_free; @@ -373,9 +372,9 @@ xchk_bmap_rt_iextent_xref( break; } out_cur: - xchk_rtgroup_btcur_free(&info->sc->sr); + xchk_rtgroup_btcur_free(info->sc); out_free: - xchk_rtgroup_free(info->sc, &info->sc->sr); + xchk_rtgroup_free(info->sc); } /* Cross-reference a single datadev extent record. */ diff --git a/fs/xfs/scrub/bmap_repair.c b/fs/xfs/scrub/bmap_repair.c index 781ce3b29dca92..182b4ad6ccca61 100644 --- a/fs/xfs/scrub/bmap_repair.c +++ b/fs/xfs/scrub/bmap_repair.c @@ -463,8 +463,8 @@ xrep_bmap_scan_rtgroup( return error; error = xfs_rmap_query_all(sc->sr.rmap_cur, xrep_bmap_walk_rtrmap, rb); - xchk_rtgroup_btcur_free(&sc->sr); - xchk_rtgroup_free(sc, &sc->sr); + xchk_rtgroup_btcur_free(sc); + xchk_rtgroup_free(sc); return error; } #else diff --git a/fs/xfs/scrub/common.c b/fs/xfs/scrub/common.c index 82cda70e4169ae..cbfe5e6f779dfc 100644 --- a/fs/xfs/scrub/common.c +++ b/fs/xfs/scrub/common.c @@ -730,9 +730,10 @@ xchk_ag_init( int xchk_rtgroup_init( struct xfs_scrub *sc, - xfs_rgnumber_t rgno, - struct xchk_rt *sr) + xfs_rgnumber_t rgno) { + struct xchk_rt *sr = &sc->sr; + ASSERT(sr->rtg == NULL); ASSERT(sr->rtlock_flags == 0); @@ -746,9 +747,9 @@ xchk_rtgroup_init( int xchk_rtgroup_lock( struct xfs_scrub *sc, - struct xchk_rt *sr, unsigned int rtglock_flags) { + struct xchk_rt *sr = &sc->sr; int error = 0; ASSERT(sr->rtg != NULL); @@ -831,8 +832,10 @@ xchk_rtgroup_lock( */ void xchk_rtgroup_btcur_free( - struct xchk_rt *sr) + struct xfs_scrub *sc) { + struct xchk_rt *sr = &sc->sr; + if (sr->rmap_cur) xfs_btree_del_cursor(sr->rmap_cur, XFS_BTREE_ERROR); if (sr->refc_cur) @@ -848,8 +851,10 @@ xchk_rtgroup_btcur_free( */ void xchk_rtgroup_unlock( - struct xchk_rt *sr) + struct xfs_scrub *sc) { + struct xchk_rt *sr = &sc->sr; + ASSERT(sr->rtg != NULL); if (sr->rtlock_flags) { @@ -864,12 +869,13 @@ xchk_rtgroup_unlock( */ void xchk_rtgroup_free( - struct xfs_scrub *sc, - struct xchk_rt *sr) + struct xfs_scrub *sc) { + struct xchk_rt *sr = &sc->sr; + ASSERT(sr->rtg != NULL); - xchk_rtgroup_unlock(sr); + xchk_rtgroup_unlock(sc); xfs_rtgroup_put(sr->rtg); sr->rtg = NULL; diff --git a/fs/xfs/scrub/common.h b/fs/xfs/scrub/common.h index 8478f3a9bed33d..181028f0c65526 100644 --- a/fs/xfs/scrub/common.h +++ b/fs/xfs/scrub/common.h @@ -135,32 +135,29 @@ xchk_ag_init_existing( XFS_RTGLOCK_RMAP | \ XFS_RTGLOCK_REFCOUNT) -int xchk_rtgroup_init(struct xfs_scrub *sc, xfs_rgnumber_t rgno, - struct xchk_rt *sr); +int xchk_rtgroup_init(struct xfs_scrub *sc, xfs_rgnumber_t rgno); static inline int xchk_rtgroup_init_existing( struct xfs_scrub *sc, - xfs_rgnumber_t rgno, - struct xchk_rt *sr) + xfs_rgnumber_t rgno) { - int error = xchk_rtgroup_init(sc, rgno, sr); + int error = xchk_rtgroup_init(sc, rgno); return error == -ENOENT ? -EFSCORRUPTED : error; } -int xchk_rtgroup_lock(struct xfs_scrub *sc, struct xchk_rt *sr, - unsigned int rtglock_flags); -void xchk_rtgroup_unlock(struct xchk_rt *sr); -void xchk_rtgroup_btcur_free(struct xchk_rt *sr); -void xchk_rtgroup_free(struct xfs_scrub *sc, struct xchk_rt *sr); +int xchk_rtgroup_lock(struct xfs_scrub *sc, unsigned int rtglock_flags); +void xchk_rtgroup_unlock(struct xfs_scrub *sc); +void xchk_rtgroup_btcur_free(struct xfs_scrub *sc); +void xchk_rtgroup_free(struct xfs_scrub *sc); #else -# define xchk_rtgroup_init(sc, rgno, sr) (-EFSCORRUPTED) -# define xchk_rtgroup_init_existing(sc, rgno, sr) (-EFSCORRUPTED) -# define xchk_rtgroup_lock(sc, sr, lockflags) (-EFSCORRUPTED) -# define xchk_rtgroup_unlock(sr) do { } while (0) -# define xchk_rtgroup_btcur_free(sr) do { } while (0) -# define xchk_rtgroup_free(sc, sr) do { } while (0) +# define xchk_rtgroup_init(sc, rgno) (-EFSCORRUPTED) +# define xchk_rtgroup_init_existing(sc, rgno) (-EFSCORRUPTED) +# define xchk_rtgroup_lock(sc, lockflags) (-EFSCORRUPTED) +# define xchk_rtgroup_unlock(sc) do { } while (0) +# define xchk_rtgroup_btcur_free(sc) do { } while (0) +# define xchk_rtgroup_free(sc) do { } while (0) #endif /* CONFIG_XFS_RT */ int xchk_ag_read_headers(struct xfs_scrub *sc, xfs_agnumber_t agno); diff --git a/fs/xfs/scrub/cow_repair.c b/fs/xfs/scrub/cow_repair.c index 5a209194ba97eb..2b1d1701cb539f 100644 --- a/fs/xfs/scrub/cow_repair.c +++ b/fs/xfs/scrub/cow_repair.c @@ -433,8 +433,8 @@ xrep_cow_find_bad_rt( xc->irec.br_blockcount); out_sr: - xchk_rtgroup_btcur_free(&sc->sr); - xchk_rtgroup_free(sc, &sc->sr); + xchk_rtgroup_btcur_free(sc); + xchk_rtgroup_free(sc); out_rtg: xfs_rtgroup_put(rtg); return error; diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index 32aeacfd8da9ae..b961fe94301ebf 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -822,8 +822,8 @@ xrep_dinode_count_rtgroup_rmaps( error = xfs_rmap_query_all(sc->sr.rmap_cur, xrep_dinode_walk_rtrmap, ri); - xchk_rtgroup_btcur_free(&sc->sr); - xchk_rtgroup_free(sc, &sc->sr); + xchk_rtgroup_btcur_free(sc); + xchk_rtgroup_free(sc); return error; } diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index b1d9ab530d300b..99bc47f5a688fc 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -69,7 +69,7 @@ xrep_attempt( trace_xrep_attempt(XFS_I(file_inode(sc->file)), sc->sm, error); xchk_ag_btcur_free(sc); - xchk_rtgroup_btcur_free(&sc->sr); + xchk_rtgroup_btcur_free(sc); /* Repair whatever's broken. */ ASSERT(sc->ops->repair); diff --git a/fs/xfs/scrub/rgsuper.c b/fs/xfs/scrub/rgsuper.c index 102bd92e9003db..591c5767208e6e 100644 --- a/fs/xfs/scrub/rgsuper.c +++ b/fs/xfs/scrub/rgsuper.c @@ -65,7 +65,7 @@ xchk_rgsuperblock( * signal that the group no longer exists. Take the rtbitmap in shared * mode so that the group can't change while we're doing things. */ - error = xchk_rtgroup_init_existing(sc, rgno, &sc->sr); + error = xchk_rtgroup_init_existing(sc, rgno); if (!xchk_xref_process_error(sc, 0, 0, &error)) return error; @@ -74,7 +74,7 @@ xchk_rgsuperblock( else flags = XFS_RTGLOCK_BITMAP_SHARED; - error = xchk_rtgroup_lock(sc, &sc->sr, flags); + error = xchk_rtgroup_lock(sc, flags); if (error) return error; diff --git a/fs/xfs/scrub/rtbitmap.c b/fs/xfs/scrub/rtbitmap.c index 52c24d3d4be6ce..88a53c6ed6c531 100644 --- a/fs/xfs/scrub/rtbitmap.c +++ b/fs/xfs/scrub/rtbitmap.c @@ -48,7 +48,7 @@ xchk_setup_rtbitmap( sc->buf = rtb; rtb->sc = sc; - error = xchk_rtgroup_init(sc, sc->sm->sm_agno, &sc->sr); + error = xchk_rtgroup_init(sc, sc->sm->sm_agno); if (error) return error; @@ -70,7 +70,7 @@ xchk_setup_rtbitmap( if (error) return error; - error = xchk_rtgroup_lock(sc, &sc->sr, XCHK_RTGLOCK_ALL); + error = xchk_rtgroup_lock(sc, XCHK_RTGLOCK_ALL); if (error) return error; diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c index 054acbf025f54a..bdfe39457c6de7 100644 --- a/fs/xfs/scrub/rtbitmap_repair.c +++ b/fs/xfs/scrub/rtbitmap_repair.c @@ -357,7 +357,7 @@ xrep_rtbitmap_find_freespace( } out: - xchk_rtgroup_btcur_free(&sc->sr); + xchk_rtgroup_btcur_free(sc); return error; } diff --git a/fs/xfs/scrub/rtrefcount.c b/fs/xfs/scrub/rtrefcount.c index 3d439249cbd62d..d5d523b6dc6b6f 100644 --- a/fs/xfs/scrub/rtrefcount.c +++ b/fs/xfs/scrub/rtrefcount.c @@ -43,7 +43,7 @@ xchk_setup_rtrefcountbt( return error; } - error = xchk_rtgroup_init(sc, sc->sm->sm_agno, &sc->sr); + error = xchk_rtgroup_init(sc, sc->sm->sm_agno); if (error) return error; @@ -55,7 +55,7 @@ xchk_setup_rtrefcountbt( if (error) return error; - return xchk_rtgroup_lock(sc, &sc->sr, XCHK_RTGLOCK_ALL); + return xchk_rtgroup_lock(sc, XCHK_RTGLOCK_ALL); } /* Realtime Reference count btree scrubber. */ diff --git a/fs/xfs/scrub/rtrefcount_repair.c b/fs/xfs/scrub/rtrefcount_repair.c index 1dc894263d2b5a..0cb0cd1ea69e4f 100644 --- a/fs/xfs/scrub/rtrefcount_repair.c +++ b/fs/xfs/scrub/rtrefcount_repair.c @@ -510,7 +510,7 @@ xrep_rtrefc_find_refcounts( out_bag: rcbag_free(&rcstack); out_cur: - xchk_rtgroup_btcur_free(&sc->sr); + xchk_rtgroup_btcur_free(sc); return error; } diff --git a/fs/xfs/scrub/rtrmap.c b/fs/xfs/scrub/rtrmap.c index 564d19a97a2f8f..85cc0513d688c9 100644 --- a/fs/xfs/scrub/rtrmap.c +++ b/fs/xfs/scrub/rtrmap.c @@ -46,7 +46,7 @@ xchk_setup_rtrmapbt( return error; } - error = xchk_rtgroup_init(sc, sc->sm->sm_agno, &sc->sr); + error = xchk_rtgroup_init(sc, sc->sm->sm_agno); if (error) return error; @@ -58,7 +58,7 @@ xchk_setup_rtrmapbt( if (error) return error; - return xchk_rtgroup_lock(sc, &sc->sr, XCHK_RTGLOCK_ALL); + return xchk_rtgroup_lock(sc, XCHK_RTGLOCK_ALL); } /* Realtime reverse mapping. */ diff --git a/fs/xfs/scrub/rtrmap_repair.c b/fs/xfs/scrub/rtrmap_repair.c index b8837793878ebe..08f850cdc46630 100644 --- a/fs/xfs/scrub/rtrmap_repair.c +++ b/fs/xfs/scrub/rtrmap_repair.c @@ -556,7 +556,7 @@ xrep_rtrmap_find_rmaps( /* Find CoW staging extents. */ xrep_rtgroup_btcur_init(sc); error = xrep_rtrmap_find_refcount_rmaps(rr); - xchk_rtgroup_btcur_free(&sc->sr); + xchk_rtgroup_btcur_free(sc); if (error) return error; @@ -576,7 +576,7 @@ xrep_rtrmap_find_rmaps( * do not take sb_internal. */ xchk_trans_cancel(sc); - xchk_rtgroup_unlock(&sc->sr); + xchk_rtgroup_unlock(sc); xchk_trans_alloc_empty(sc); while ((error = xchk_iscan_iter(&rr->iscan, &ip)) == 1) { @@ -600,7 +600,7 @@ xrep_rtrmap_find_rmaps( error = xchk_setup_rt(sc); if (error) return error; - error = xchk_rtgroup_lock(sc, &sc->sr, XCHK_RTGLOCK_ALL); + error = xchk_rtgroup_lock(sc, XCHK_RTGLOCK_ALL); if (error) return error; diff --git a/fs/xfs/scrub/rtsummary.c b/fs/xfs/scrub/rtsummary.c index fa358746beaf5c..b72c27ef45acbb 100644 --- a/fs/xfs/scrub/rtsummary.c +++ b/fs/xfs/scrub/rtsummary.c @@ -54,7 +54,7 @@ xchk_setup_rtsummary( return -ENOMEM; sc->buf = rts; - error = xchk_rtgroup_init(sc, sc->sm->sm_agno, &sc->sr); + error = xchk_rtgroup_init(sc, sc->sm->sm_agno); if (error) return error; @@ -85,7 +85,7 @@ xchk_setup_rtsummary( if (error) return error; - error = xchk_rtgroup_lock(sc, &sc->sr, XFS_RTGLOCK_BITMAP); + error = xchk_rtgroup_lock(sc, XFS_RTGLOCK_BITMAP); if (error) return error; diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 9dbd16546e8c8b..4a2d9bdc087b5f 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -230,7 +230,7 @@ xchk_teardown( int error) { xchk_ag_free(sc); - xchk_rtgroup_btcur_free(&sc->sr); + xchk_rtgroup_btcur_free(sc); if (sc->tp) { if (error == 0 && (sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) @@ -240,7 +240,7 @@ xchk_teardown( sc->tp = NULL; } if (sc->sr.rtg) - xchk_rtgroup_free(sc, &sc->sr); + xchk_rtgroup_free(sc); if (sc->ip) { if (sc->ilock_flags) xchk_iunlock(sc, sc->ilock_flags); From ce58cff1077dfb9ec3d0e6f5da1b06a85be035bb Mon Sep 17 00:00:00 2001 From: "Darrick J. Wong" Date: Mon, 12 Oct 2026 23:11:27 -0700 Subject: [PATCH 1939/2286] xfs: refactor inode forkoff to byte conversions A reviewer suggested that I formalize the conversion between the inode forkoff field and actual byte count, so let's add a few conversion macros and a helper function to compute the default forkoff; and then adjust the codebase to use them. No functional changes intended. Signed-off-by: Darrick J. Wong Reviewed-by: Christoph Hellwig Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_attr_leaf.c | 12 ++++++------ fs/xfs/libxfs/xfs_bmap.c | 4 ++-- fs/xfs/libxfs/xfs_bmap.h | 6 ++++++ fs/xfs/libxfs/xfs_format.h | 6 +++++- fs/xfs/libxfs/xfs_inode_buf.c | 4 ++-- fs/xfs/libxfs/xfs_inode_util.c | 2 +- fs/xfs/scrub/inode.c | 2 +- fs/xfs/scrub/inode_repair.c | 4 ++-- fs/xfs/xfs_inode.h | 2 +- 9 files changed, 26 insertions(+), 16 deletions(-) diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 02ca50442b4665..6068fad9dc9e84 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -733,10 +733,10 @@ xfs_attr_shortform_bytesfit( return 0; /* rounded down */ - offset = (XFS_LITINO(mp) - bytes) >> 3; + offset = XFS_B_TO_FORKOFFT(XFS_LITINO(mp) - bytes); if (dp->i_df.if_format == XFS_DINODE_FMT_DEV) { - minforkoff = roundup(sizeof(xfs_dev_t), 8) >> 3; + minforkoff = XFS_B_TO_FORKOFF(sizeof(xfs_dev_t)); return (offset >= minforkoff) ? minforkoff : 0; } @@ -792,12 +792,12 @@ xfs_attr_shortform_bytesfit( * A data fork btree root must have space for at least * MINDBTPTRS key/ptr pairs if the data fork is small or empty. */ - minforkoff = max_t(int64_t, dsize, xfs_bmdr_space_calc(MINDBTPTRS)); - minforkoff = roundup(minforkoff, 8) >> 3; + minforkoff = XFS_B_TO_FORKOFF( + max_t(int64_t, dsize, xfs_bmdr_space_calc(MINDBTPTRS))); /* attr fork btree root can have at least this many key/ptr pairs */ - maxforkoff = XFS_LITINO(mp) - xfs_bmdr_space_calc(MINABTPTRS); - maxforkoff = maxforkoff >> 3; /* rounded down */ + maxforkoff = XFS_B_TO_FORKOFFT( + XFS_LITINO(mp) - xfs_bmdr_space_calc(MINABTPTRS)); if (offset >= maxforkoff) return maxforkoff; diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index 9bffde1484a743..ca49bf4a50c5f2 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -221,7 +221,7 @@ xfs_bmap_forkoff_reset( if (whichfork == XFS_ATTR_FORK && ip->i_df.if_format != XFS_DINODE_FMT_DEV && ip->i_df.if_format != XFS_DINODE_FMT_BTREE) { - uint dfl_forkoff = xfs_default_attroffset(ip) >> 3; + uint dfl_forkoff = xfs_inode_default_forkoff(ip); if (dfl_forkoff > ip->i_forkoff) ip->i_forkoff = dfl_forkoff; @@ -997,7 +997,7 @@ xfs_bmap_set_attrforkoff( struct xfs_inode *ip, int size) { - int default_size = xfs_default_attroffset(ip) >> 3; + int default_size = xfs_inode_default_forkoff(ip); switch (ip->i_df.if_format) { case XFS_DINODE_FMT_DEV: diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h index 60f6df4ce0579a..e9f5416e4c050d 100644 --- a/fs/xfs/libxfs/xfs_bmap.h +++ b/fs/xfs/libxfs/xfs_bmap.h @@ -213,6 +213,12 @@ void xfs_bmap_del_extent_cow(struct xfs_inode *ip, struct xfs_iext_cursor *cur, struct xfs_bmbt_irec *got, struct xfs_bmbt_irec *del); uint xfs_default_attroffset(struct xfs_inode *ip); + +static inline uint xfs_inode_default_forkoff(struct xfs_inode *ip) +{ + return XFS_B_TO_FORKOFFT(xfs_default_attroffset(ip)); +} + int xfs_bmap_collapse_extents(struct xfs_trans *tp, struct xfs_inode *ip, xfs_fileoff_t *next_fsb, xfs_fileoff_t offset_shift_fsb, bool *done); diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h index 1a7a7e60a170c4..469e879149ac21 100644 --- a/fs/xfs/libxfs/xfs_format.h +++ b/fs/xfs/libxfs/xfs_format.h @@ -1096,7 +1096,11 @@ enum xfs_dinode_fmt { /* * Inode data & attribute fork sizes, per inode. */ -#define XFS_DFORK_BOFF(dip) ((int)((dip)->di_forkoff << 3)) +#define XFS_B_TO_FORKOFFT(b) ((b) >> 3) +#define XFS_B_TO_FORKOFF(b) XFS_B_TO_FORKOFFT((b) + 7) +#define XFS_FORKOFF_TO_B(f) ((int)(f) << 3) + +#define XFS_DFORK_BOFF(dip) XFS_FORKOFF_TO_B((dip)->di_forkoff) #define XFS_DFORK_DSIZE(dip,mp) \ ((dip)->di_forkoff ? XFS_DFORK_BOFF(dip) : XFS_LITINO(mp)) diff --git a/fs/xfs/libxfs/xfs_inode_buf.c b/fs/xfs/libxfs/xfs_inode_buf.c index 282e78e5e3e82a..8c9d296ebfe97f 100644 --- a/fs/xfs/libxfs/xfs_inode_buf.c +++ b/fs/xfs/libxfs/xfs_inode_buf.c @@ -487,7 +487,7 @@ xfs_dinode_verify_forkoff( switch (dip->di_format) { case XFS_DINODE_FMT_DEV: - if (dip->di_forkoff != (roundup(sizeof(xfs_dev_t), 8) >> 3)) + if (dip->di_forkoff != XFS_B_TO_FORKOFF(sizeof(xfs_dev_t))) return __this_address; break; case XFS_DINODE_FMT_META_BTREE: @@ -497,7 +497,7 @@ xfs_dinode_verify_forkoff( case XFS_DINODE_FMT_LOCAL: /* fall through ... */ case XFS_DINODE_FMT_EXTENTS: /* fall through ... */ case XFS_DINODE_FMT_BTREE: - if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) + if (dip->di_forkoff >= XFS_B_TO_FORKOFFT(XFS_LITINO(mp))) return __this_address; break; default: diff --git a/fs/xfs/libxfs/xfs_inode_util.c b/fs/xfs/libxfs/xfs_inode_util.c index 16c4c2ccdbb83b..f486b23a9b5230 100644 --- a/fs/xfs/libxfs/xfs_inode_util.c +++ b/fs/xfs/libxfs/xfs_inode_util.c @@ -345,7 +345,7 @@ xfs_inode_init( } if (xfs_icreate_want_attrfork(mp, args)) { - ip->i_forkoff = xfs_default_attroffset(ip) >> 3; + ip->i_forkoff = xfs_inode_default_forkoff(ip); xfs_ifork_init_attr(ip, XFS_DINODE_FMT_EXTENTS, 0); if (!xfs_has_attr(mp)) { diff --git a/fs/xfs/scrub/inode.c b/fs/xfs/scrub/inode.c index 6cded1eb6c2b6f..bb8d2bc008e019 100644 --- a/fs/xfs/scrub/inode.c +++ b/fs/xfs/scrub/inode.c @@ -607,7 +607,7 @@ xchk_dinode( } /* di_forkoff */ - if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3)) + if (dip->di_forkoff >= XFS_B_TO_FORKOFFT(XFS_LITINO(mp))) xchk_ino_set_corrupt(sc, ino); if (naextents != 0 && dip->di_forkoff == 0) xchk_ino_set_corrupt(sc, ino); diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c index b961fe94301ebf..a9ccf763c05969 100644 --- a/fs/xfs/scrub/inode_repair.c +++ b/fs/xfs/scrub/inode_repair.c @@ -1490,7 +1490,7 @@ xrep_dinode_ensure_forkoff( /* Otherwise, just slide the attr fork up. */ before = XFS_DFORK_APTR(dip); - dip->di_forkoff = bmdr_minsz >> 3; + dip->di_forkoff = XFS_B_TO_FORKOFFT(bmdr_minsz); after = XFS_DFORK_APTR(dip); memmove(after, before, XFS_DFORK_ASIZE(dip, sc->mp)); } @@ -1522,7 +1522,7 @@ xrep_dinode_ensure_forkoff( * fork is empty, so we don't have any old contents to * move here. */ - dip->di_forkoff = (lit_sz - bmdr_minsz) >> 3; + dip->di_forkoff = XFS_B_TO_FORKOFFT(lit_sz - bmdr_minsz); } } } diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 1602027cd0aa5d..53a1997b98660b 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -132,7 +132,7 @@ xfs_ifork_ptr( static inline unsigned int xfs_inode_fork_boff(struct xfs_inode *ip) { - return ip->i_forkoff << 3; + return XFS_FORKOFF_TO_B(ip->i_forkoff); } static inline unsigned int xfs_inode_data_fork_size(struct xfs_inode *ip) From 90388d46b08ac8dd2c179f63f8739b4ac514d926 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 13 Oct 2026 13:57:55 +0200 Subject: [PATCH 1940/2286] xfs: handle NULL open_zone for merged ioends in xfs_end_ioend_write_zoned The NULL checks on the merged ioends added in commit ae285611891f ("xfs: handle NULL open_zone for merged ioends in xfs_ioend_put_open_zones") got lost when the code was moved to a new file. Reinstate the check, and also update the comments to be a little more coherent. Fixes: 6b855256eb9e ("xfs: split ioend handling into a separate source file") Signed-off-by: Christoph Hellwig Reviewed-by: Carlos Maiolino Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_ioend.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c index f0e01ac34de8cc..48becc3bf60889 100644 --- a/fs/xfs/xfs_ioend.c +++ b/fs/xfs/xfs_ioend.c @@ -182,15 +182,15 @@ xfs_end_ioend_write_zoned( error = xfs_setfilesize(ip, ioend->io_offset, ioend->io_size); done: /* - * Put the open zone for all ioends merged into this one (if any). - */ - list_for_each_entry(tmp, &ioend->io_list, io_list) - xfs_open_zone_put(tmp->io_private); - - /* - * The main ioend might not have an open zone if the submission failed - * before xfs_zone_alloc_and_submit got called. + * Put the open zone references for all ioends merged into this one in + * addition to the main one they were merged into. If the submission + * failed before xfs_zone_alloc_and_submit got called, there is no open + * zone and at least in theory that could also happen for merged ioends. */ + list_for_each_entry(tmp, &ioend->io_list, io_list) { + if (tmp->io_private) + xfs_open_zone_put(tmp->io_private); + } if (oz) xfs_open_zone_put(oz); iomap_finish_ioends(ioend, error); From 5beb41df46f20d8ab5591c5c1f27e79e8a761616 Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 13 Oct 2026 19:16:39 -0500 Subject: [PATCH 1941/2286] xfs: add missing cursor status check after xfs_inobt_get_rec The xfs_inobt_get_rec() call in xfs_ialloc_count_ondisk fails to check the &has_record return - if the record is not found, the next conditional will read an uninitialized or stale irec. Fix that. Signed-off-by: Eric Sandeen Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ialloc.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index e705f681ed4424..17699f7c009f39 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -2843,6 +2843,11 @@ xfs_ialloc_count_ondisk( error = xfs_inobt_get_rec(cur, &irec, &has_record); if (error) return error; + if (XFS_IS_CORRUPT(cur->bc_mp, has_record != 1)) { + xfs_btree_mark_sick(cur); + return -EFSCORRUPTED; + } + if (irec.ir_startino > high) break; From b47fb1ed0221fd21e696b468ed9eb40dd3c21f4d Mon Sep 17 00:00:00 2001 From: Eric Sandeen Date: Tue, 13 Oct 2026 16:07:47 -0500 Subject: [PATCH 1942/2286] xfs: add missing cursor stat checks after xfs_btree_delete Three xfs_btree_delete() calls in xfs_ialloc.c fail to check the status returns indicating whether a record was actually deleted. If the cursor was pointing to something that couldn't be deleted or didn't exist, something has gone wrong, so mark the fs as sick and return EFSCORRUPTED. Signed-off-by: Eric Sandeen Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/libxfs/xfs_ialloc.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/fs/xfs/libxfs/xfs_ialloc.c b/fs/xfs/libxfs/xfs_ialloc.c index 17699f7c009f39..bb6e684f5467c3 100644 --- a/fs/xfs/libxfs/xfs_ialloc.c +++ b/fs/xfs/libxfs/xfs_ialloc.c @@ -1626,12 +1626,20 @@ xfs_dialloc_ag( */ rec.ir_free &= ~XFS_INOBT_MASK(offset); rec.ir_freecount--; - if (rec.ir_freecount) + if (rec.ir_freecount) { error = xfs_inobt_update(cur, &rec); - else + if (error) + goto error_cur; + } else { error = xfs_btree_delete(cur, &i); - if (error) - goto error_cur; + if (error) + goto error_cur; + if (XFS_IS_CORRUPT(mp, i != 1)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto error_cur; + } + } /* * The finobt has now been updated appropriately. We haven't updated the @@ -2164,6 +2172,11 @@ xfs_difree_inobt( __func__, error); goto error0; } + if (XFS_IS_CORRUPT(mp, i != 1)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto error0; + } error = xfs_difree_inode_chunk(tp, pag, &rec); if (error) @@ -2290,7 +2303,11 @@ xfs_difree_finobt( error = xfs_btree_delete(cur, &i); if (error) goto error; - ASSERT(i == 1); + if (XFS_IS_CORRUPT(mp, i != 1)) { + xfs_btree_mark_sick(cur); + error = -EFSCORRUPTED; + goto error; + } } else { error = xfs_inobt_update(cur, &rec); if (error) From ec8f62cc109a3f596b3c65dc0f1011c209239051 Mon Sep 17 00:00:00 2001 From: Pankaj Raghav Date: Thu, 8 Oct 2026 13:11:47 +0200 Subject: [PATCH 1943/2286] xfs: don't limit software atomic writes by the group alignment xfs_calc_group_awu_max() clamps the atomic write unit maximum to the greatest power-of-two factor of the group size when the device advertises atomic writes, so that allocations can be made naturally aligned for REQ_ATOMIC. But that value is the software limit: it is only used when reflink is enabled, and out of place writes through the COW fork have no alignment requirement. Compute the software limit from the group size alone, and apply the alignment constraint in xfs_get_atomic_write_max_opt() instead, which is what reports the size that can be offloaded to the hardware. Results on a 14TB device with 16k hardware atomic support with 16k blocksize: Without patches: [nix-shell:~]# trace-cmd report cpus=16 mount-617 [009] 1184.719159: xfs_calc_atomic_write_unit_max: dev 259:2 ag max_write 131072 max_ioend 4096 max_gsize 2 awu_max 2 [nix-shell:~]# ./statx /media/test/hello.txt /media/test/hello.txt: stx_atomic_write_unit_min: 16384 stx_atomic_write_unit_max: 32768 stx_atomic_write_unit_max_opt: 16384 stx_atomic_write_segments_max: 1 With patches: [nix-shell:~]# trace-cmd report cpus=16 mount-598 [005] 319.692000: xfs_calc_atomic_write_unit_max: dev 259:2 ag max_write 131072 max_ioend 4096 max_gsize 33554432 awu_max 4096 [nix-shell:~]# ./statx /media/test/hello.txt /media/test/hello.txt: stx_atomic_write_unit_min: 16384 stx_atomic_write_unit_max: 67108864 stx_atomic_write_unit_max_opt: 16384 stx_atomic_write_segments_max: 1 Fixes: 0c438dcc3150 ("xfs: add xfs_calc_atomic_write_unit_max()") Assisted-by: LLM Signed-off-by: Pankaj Raghav Reviewed-by: John Garry Reviewed-by: Darrick J. Wong Signed-off-by: Carlos Maiolino --- fs/xfs/xfs_inode.h | 9 +++++++++ fs/xfs/xfs_iops.c | 30 ++++++++++++++++++++++++------ fs/xfs/xfs_mount.c | 35 ++++++++++++++++++++++++----------- fs/xfs/xfs_mount.h | 2 ++ 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h index 53a1997b98660b..1fa442b646dbb1 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -353,6 +353,15 @@ static inline bool xfs_inode_has_bigrtalloc(const struct xfs_inode *ip) return XFS_IS_REALTIME_INODE(ip) && ip->i_mount->m_sb.sb_rextsize > 1; } +/* + * Return the type of the group that data for a given inode is allocated from. + */ +static inline enum xfs_group_type +xfs_inode_group_type(const struct xfs_inode *ip) +{ + return XFS_IS_REALTIME_INODE(ip) ? XG_TYPE_RTG : XG_TYPE_AG; +} + /* * Return the buftarg used for data allocations on a given inode. */ diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index d1306e723899cd..13d61f3a1a24aa 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -642,19 +642,30 @@ xfs_get_atomic_write_max( * then advertise a maximum size of whatever we can complete through * that means. Hardware support is reported via max_opt, not here. */ - if (XFS_IS_REALTIME_INODE(ip)) - return XFS_FSB_TO_B(mp, mp->m_groups[XG_TYPE_RTG].awu_max); - return XFS_FSB_TO_B(mp, mp->m_groups[XG_TYPE_AG].awu_max); + return XFS_FSB_TO_B(mp, mp->m_groups[xfs_inode_group_type(ip)].awu_max); +} + +static inline xfs_fsize_t xfs_get_atomic_hw_write_align( + const struct xfs_inode *ip) +{ + struct xfs_mount *mp = ip->i_mount; + + return XFS_FSB_TO_B( + mp, xfs_calc_group_awu_align_max(mp, xfs_inode_group_type(ip))); } unsigned int xfs_get_atomic_write_max_opt( struct xfs_inode *ip) { - unsigned int awu_max = xfs_get_atomic_write_max(ip); + struct xfs_mount *mp = ip->i_mount; + unsigned int max_opt; + const unsigned int sw_awu_max = xfs_get_atomic_write_max(ip); + const unsigned int hw_awu_max = xfs_inode_buftarg(ip)->bt_awu_max; + const xfs_fsize_t hw_align_max = xfs_get_atomic_hw_write_align(ip); /* if the max is 1x block, then just keep behaviour that opt is 0 */ - if (awu_max <= ip->i_mount->m_sb.sb_blocksize) + if (sw_awu_max <= mp->m_sb.sb_blocksize) return 0; /* @@ -663,7 +674,14 @@ xfs_get_atomic_write_max_opt( * less than our out of place write limit, but we don't want to exceed * the awu_max. */ - return min(awu_max, xfs_inode_buftarg(ip)->bt_awu_max); + max_opt = min(sw_awu_max, hw_awu_max); + + /* + * HW atomic writes also have to be naturally aligned on disk, so we + * cannot promise more than the largest extent that the allocator is + * able to align within a group. + */ + return min_t(xfs_fsize_t, max_opt, hw_align_max); } static void diff --git a/fs/xfs/xfs_mount.c b/fs/xfs/xfs_mount.c index 8838eff1f054d5..60931cb0081710 100644 --- a/fs/xfs/xfs_mount.c +++ b/fs/xfs/xfs_mount.c @@ -681,15 +681,13 @@ static inline xfs_extlen_t xfs_calc_atomic_write_max(struct xfs_mount *mp) } /* - * If the underlying device advertises atomic write support, limit the size of - * atomic writes to the greatest power-of-two factor of the group size so - * that every atomic write unit aligns with the start of every group. This is - * required so that the allocations for an atomic write will always be - * aligned compatibly with the alignment requirements of the storage. + * The largest out of place write is the number of blocks that user files can + * allocate from any group. * - * If the device doesn't advertise atomic writes, then there are no alignment - * restrictions and the largest out-of-place write we can do ourselves is the - * number of blocks that user files can allocate from any group. + * This is a software limit, so it deliberately does not take the alignment + * requirements of the storage into account. An atomic write that cannot be + * handed to the device as a single naturally aligned REQ_ATOMIC bio is + * completed through the COW fork instead, which has no such requirement. */ static xfs_extlen_t xfs_calc_group_awu_max( @@ -697,15 +695,30 @@ xfs_calc_group_awu_max( enum xfs_group_type type) { struct xfs_groups *g = &mp->m_groups[type]; - struct xfs_buftarg *btp = xfs_group_type_buftarg(mp, type); if (g->blocks == 0) return 0; - if (btp && btp->bt_awu_min > 0) - return max_pow_of_two_factor(g->blocks); return rounddown_pow_of_two(g->blocks); } +/* + * Compute the largest atomic write unit for which the allocator can guarantee + * naturally aligned extents in this group type. + * + * Hardware atomic writes have to be naturally aligned on disk. + */ +xfs_extlen_t +xfs_calc_group_awu_align_max( + struct xfs_mount *mp, + enum xfs_group_type type) +{ + struct xfs_groups *g = &mp->m_groups[type]; + + if (g->blocks == 0) + return 0; + return max_pow_of_two_factor(g->blocks); +} + /* Compute the maximum atomic write unit size for each section. */ static inline void xfs_calc_atomic_write_unit_max( diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h index 894ff2f4ecbd9a..cbcfb89c67a440 100644 --- a/fs/xfs/xfs_mount.h +++ b/fs/xfs/xfs_mount.h @@ -820,6 +820,8 @@ static inline void xfs_mod_sb_delalloc(struct xfs_mount *mp, int64_t delta) int xfs_set_max_atomic_write_opt(struct xfs_mount *mp, unsigned long long new_max_bytes); +xfs_extlen_t xfs_calc_group_awu_align_max(struct xfs_mount *mp, + enum xfs_group_type type); static inline struct xfs_buftarg * xfs_group_type_buftarg( From c83237edf0adb8f8cbd9859841bbca5acc00fc11 Mon Sep 17 00:00:00 2001 From: Kohei Enju Date: Wed, 14 Oct 2026 11:43:23 +0900 Subject: [PATCH 1944/2286] arm64: rsi: Correct type of realm_config::hash_algo The RMM specification (DEN0137, B5.5.10 and B5.5.19) defines RsiHashAlgorithm as an 8-bit enumeration, stored at offset 0x8 of RsiRealmConfig, with the unused bits of the structure being MBZ. However, realm_config::hash_algo is declared as unsigned long. For an RMM that zeroes the unused bits, the wider type gives the same value. An RMM that only writes the low byte (as TF-RMM currently does) leaves bytes 1-7 with whatever the guest buffer contained, so a comparison of the 64-bit field against RSI_HASH_SHA_* can fail. Nothing in mainline reads the field yet, but the proposed Realm measurement register support does. Declare the field as u8 so the guest only depends on the byte the specification defines. The struct size and the offsets of the other fields do not change. Link: https://lore.kernel.org/all/20260413084957.327661-4-sami.mujawar@arm.com/ Fixes: b880a80011f5 ("arm64: rsi: Add RSI definitions") Signed-off-by: Kohei Enju Reviewed-by: Suzuki K Poulose Signed-off-by: Catalin Marinas --- include/linux/arm-smccc-rsi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/arm-smccc-rsi.h b/include/linux/arm-smccc-rsi.h index fddb77986f706c..cca881ddfdbabf 100644 --- a/include/linux/arm-smccc-rsi.h +++ b/include/linux/arm-smccc-rsi.h @@ -128,7 +128,7 @@ struct realm_config { union { struct { unsigned long ipa_bits; /* Width of IPA in bits */ - unsigned long hash_algo; /* Hash algorithm */ + u8 hash_algo; /* Hash algorithm */ }; u8 pad[0x200]; }; From 72add8bf4d57af2358e42753a39b068ddaf486bd Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 9 Oct 2026 11:07:16 +0100 Subject: [PATCH 1945/2286] arm64: cpufeature: Check arm64_ftr_regs[] before the first store sort_ftr_regs() sorts nothing. It checks that arm64_ftr_regs[] is in sys_id order, which the binary search in get_arm64_ftr_reg() relies on, and that no register's fields overlap. It runs at the top of init_cpu_features(), after the boot CPU's __cpuinfo_store_cpu(). A subsequent patch makes __cpuinfo_store_cpu() look registers up in the table. Rename it validate_ftr_regs() and call it from smp_prepare_boot_cpu(), ahead of cpuinfo_store_boot_cpu(). Suggested-by: Will Deacon Signed-off-by: Fuad Tabba Signed-off-by: Will Deacon Reviewed-by: Jinjie Ruan Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/cpu.h | 1 + arch/arm64/kernel/cpufeature.c | 5 +---- arch/arm64/kernel/smp.c | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h index 3c008821219c26..f332fbd8e46d79 100644 --- a/arch/arm64/include/asm/cpu.h +++ b/arch/arm64/include/asm/cpu.h @@ -75,6 +75,7 @@ DECLARE_PER_CPU(struct cpuinfo_arm64, cpu_data); void cpuinfo_store_cpu(void); void __init cpuinfo_store_boot_cpu(void); +void __init validate_ftr_regs(void); void __init init_cpu_features(struct cpuinfo_arm64 *info); void update_cpu_features(int cpu, struct cpuinfo_arm64 *info, struct cpuinfo_arm64 *boot); diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 32102c3912fa7b..727e1665c98738 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -962,7 +962,7 @@ s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, return ret; } -static void __init sort_ftr_regs(void) +void __init validate_ftr_regs(void) { unsigned int i; @@ -1207,9 +1207,6 @@ bool gmid_el1_accessible(const struct cpuinfo_arm64 *info) void __init init_cpu_features(struct cpuinfo_arm64 *info) { - /* Before we start using the tables, make sure it is sorted */ - sort_ftr_regs(); - init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr); init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid); init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq); diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index a61dc3016a117d..eac217b9ebf1e6 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -455,6 +455,8 @@ void __init smp_prepare_boot_cpu(void) */ set_my_cpu_offset(per_cpu_offset(smp_processor_id())); + /* Check arm64_ftr_regs[] before anything looks registers up in it. */ + validate_ftr_regs(); cpuinfo_store_boot_cpu(); setup_boot_cpu_features(); From cd9df8b01977929b46759376fd418621be9251f0 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 9 Oct 2026 11:07:17 +0100 Subject: [PATCH 1946/2286] arm64: cpufeature: Add read_cpuid_with_overrides() __cpuinfo_store_cpu() stores the ID registers raw. Its gates on reading a feature's register test those raw values. The exception is gmid_el1_accessible(), which open-codes MTE's command-line override. It checks the override with arm64_ftr_safe_value(), since init_cpu_features() hasn't yet removed an unsafe one. The other gates still read a feature's register when the command line disabled it. That's the read that trapped on GMID_EL1. Add read_cpuid_with_overrides(), which applies every valid override to any register in arm64_ftr_regs[]. An override is valid when arm64_ftr_safe_value() returns it unchanged. validate_ftr_regs() has checked the table by the first store, so the lookup is safe there. arm64_apply_feature_override() can't do this: it takes one field and no descriptor to check the override against. Store ID_AA64PFR1_EL1 through it, and replace gmid_el1_accessible() with a test on the stored value at its three callers, as for SME. Other readers of the stored value see the override too. With arm64.nosme, __cpuinfo_store_cpu() no longer reads SMIDR_EL1. CPUs that differ only in an ID_AA64PFR1_EL1 field the command line turned off no longer taint the kernel. init_cpu_ftr_reg() logs a valid override as "already set" rather than "forced". The helper is Suzuki's override_cpu_ftr_reg(), without its other two call sites. Co-developed-by: Suzuki K Poulose Signed-off-by: Suzuki K Poulose Suggested-by: Will Deacon Link: https://lore.kernel.org/all/afc5bd00-28ca-413b-b047-ee53589c285d@arm.com/ Signed-off-by: Fuad Tabba Signed-off-by: Will Deacon Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/cpu.h | 1 - arch/arm64/include/asm/cpufeature.h | 11 ++++++ arch/arm64/kernel/cpufeature.c | 57 ++++++++++++++--------------- arch/arm64/kernel/cpuinfo.c | 4 +- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h index f332fbd8e46d79..6d2a5ac4c2f8fd 100644 --- a/arch/arm64/include/asm/cpu.h +++ b/arch/arm64/include/asm/cpu.h @@ -79,6 +79,5 @@ void __init validate_ftr_regs(void); void __init init_cpu_features(struct cpuinfo_arm64 *info); void update_cpu_features(int cpu, struct cpuinfo_arm64 *info, struct cpuinfo_arm64 *boot); -bool gmid_el1_accessible(const struct cpuinfo_arm64 *info); #endif /* __ASM_CPU_H */ diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h index 4f04ad82ea340a..5a4d25325856b6 100644 --- a/arch/arm64/include/asm/cpufeature.h +++ b/arch/arm64/include/asm/cpufeature.h @@ -627,6 +627,13 @@ static inline bool id_aa64pfr1_mpamfrac(u64 pfr1) return val > 0; } +static inline bool id_aa64pfr1_mte(u64 pfr1) +{ + u32 val = cpuid_feature_extract_unsigned_field(pfr1, ID_AA64PFR1_EL1_MTE_SHIFT); + + return val >= ID_AA64PFR1_EL1_MTE_MTE2; +} + void __init setup_boot_cpu_features(void); void __init setup_system_features(void); void __init setup_user_features(void); @@ -957,6 +964,10 @@ static inline unsigned int get_vmid_bits(u64 mmfr1) s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur); struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id); +u64 arm64_ftr_reg_override(u32 sys_id, u64 val); + +/* Read this CPU's ID register with each valid command-line override applied. */ +#define read_cpuid_with_overrides(reg) arm64_ftr_reg_override(SYS_##reg, read_cpuid(reg)) extern struct arm64_ftr_override id_aa64mmfr0_override; extern struct arm64_ftr_override id_aa64mmfr1_override; diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 727e1665c98738..3161531ad40121 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -962,6 +962,32 @@ s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, return ret; } +u64 arm64_ftr_reg_override(u32 sys_id, u64 val) +{ + struct arm64_ftr_reg *reg = get_arm64_ftr_reg(sys_id); + const struct arm64_ftr_bits *ftrp; + + if (!reg || !reg->override->mask) + return val; + + for (ftrp = reg->ftr_bits; ftrp->width; ftrp++) { + u64 ftr_mask = arm64_ftr_mask(ftrp); + s64 ftr_new, ftr_ovr; + + if ((ftr_mask & reg->override->mask) != ftr_mask) + continue; + + ftr_new = arm64_ftr_value(ftrp, val); + ftr_ovr = arm64_ftr_value(ftrp, reg->override->val); + + /* An unsafe override is left for init_cpu_ftr_reg() to remove. */ + if (arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new) == ftr_ovr) + val = arm64_ftr_set_value(ftrp, val, ftr_ovr); + } + + return val; +} + void __init validate_ftr_regs(void) { unsigned int i; @@ -1178,33 +1204,6 @@ static bool detect_ftr_has_mpam(void) return id_aa64pfr0_mpam(pfr0) || id_aa64pfr1_mpamfrac(pfr1); } -bool gmid_el1_accessible(const struct cpuinfo_arm64 *info) -{ - const struct arm64_ftr_bits *ftrp; - s64 mte, ovr; - u64 ftr_mask; - - /* No ID register reflects CONFIG_ARM64_MTE. */ - if (!IS_ENABLED(CONFIG_ARM64_MTE)) - return false; - - for (ftrp = ftr_id_aa64pfr1; ftrp->width; ftrp++) { - if (ftrp->shift == ID_AA64PFR1_EL1_MTE_SHIFT) - break; - } - - ftr_mask = arm64_ftr_mask(ftrp); - mte = arm64_ftr_value(ftrp, info->reg_id_aa64pfr1); - - /* The boot CPU runs before init_cpu_ftr_reg() strips unsafe overrides. */ - if ((id_aa64pfr1_override.mask & ftr_mask) == ftr_mask) { - ovr = arm64_ftr_value(ftrp, id_aa64pfr1_override.val); - mte = arm64_ftr_safe_value(ftrp, ovr, mte); - } - - return mte >= ID_AA64PFR1_EL1_MTE_MTE2; -} - void __init init_cpu_features(struct cpuinfo_arm64 *info) { init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr); @@ -1254,7 +1253,7 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info) init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr); } - if (gmid_el1_accessible(info)) + if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid); } @@ -1516,7 +1515,7 @@ void update_cpu_features(int cpu, * they read/write depends on the GMID_EL1.BS field. Check that the * value is the same on all CPUs. */ - if (gmid_el1_accessible(info)) + if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu, info->reg_gmid, boot->reg_gmid); diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index 45c63f3d75c53d..d48167fe421844 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -496,13 +496,13 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1); info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1); info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1); - info->reg_id_aa64pfr1 = read_cpuid(ID_AA64PFR1_EL1); + info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1); info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1); info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1); info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1); info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1); - if (gmid_el1_accessible(info)) + if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) info->reg_gmid = read_cpuid(GMID_EL1); if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) From 342e62ded77c6ee16d80d48b8e1d2c4ba3b42ab5 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 9 Oct 2026 11:07:18 +0100 Subject: [PATCH 1947/2286] arm64: cpufeature: Read MPAMIDR_EL1 in __cpuinfo_store_cpu() MPAMIDR_EL1 is the one ID register __cpuinfo_store_cpu() doesn't read. Its read was deferred to init_cpu_features() and update_cpu_features() because it traps on firmware that fails to enable MPAM. Only the sanitised ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 carried the arm64.nompam override for such firmware. Store ID_AA64PFR0_EL1 through read_cpuid_with_overrides() as well. Then read MPAMIDR_EL1 in __cpuinfo_store_cpu() again, beside GMID_EL1, gated on this CPU's overridden ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1. cpu_data is then complete before init_cpu_features() runs. Other readers of the stored ID_AA64PFR0_EL1 see the override too. With arm64.no32bit_el0, __cpuinfo_store_cpu() no longer reads the AArch32 ID registers. Adding allow_mismatched_32bit_el0 no longer enables 32-bit EL0 either. detect_ftr_has_mpam(), a system-wide test on the sanitised values, stays for the ARM64_MPAM capability. Suggested-by: Will Deacon Signed-off-by: Fuad Tabba Signed-off-by: Will Deacon Reviewed-by: Ben Horgan Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpufeature.c | 10 +++------- arch/arm64/kernel/cpuinfo.c | 11 ++++------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 3161531ad40121..9212f10c1d1ac9 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1248,10 +1248,8 @@ void __init init_cpu_features(struct cpuinfo_arm64 *info) cpacr_restore(cpacr); } - if (detect_ftr_has_mpam()) { - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1); + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) init_cpu_ftr_reg(SYS_MPAMIDR_EL1, info->reg_mpamidr); - } if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) init_cpu_ftr_reg(SYS_GMID_EL1, info->reg_gmid); @@ -1504,11 +1502,9 @@ void update_cpu_features(int cpu, cpacr_restore(cpacr); } - if (detect_ftr_has_mpam()) { - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1); + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu, - info->reg_mpamidr, boot->reg_mpamidr); - } + info->reg_mpamidr, boot->reg_mpamidr); /* * The kernel uses the LDGM/STGM instructions and the number of tags diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index d48167fe421844..0ae40b0c7b2f0e 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -495,7 +495,7 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1); info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1); info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1); - info->reg_id_aa64pfr0 = read_cpuid(ID_AA64PFR0_EL1); + info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1); info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1); info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1); info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1); @@ -505,15 +505,12 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) info->reg_gmid = read_cpuid(GMID_EL1); + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) + info->reg_mpamidr = read_cpuid(MPAMIDR_EL1); + if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) __cpuinfo_store_cpu_32bit(&info->aarch32); - /* - * info->reg_mpamidr deferred to {init,update}_cpu_features because we - * don't want to read it (and trigger a trap on buggy firmware) if - * using an aa64pfr0_el1 override to unconditionally disable MPAM. - */ - if (IS_ENABLED(CONFIG_ARM64_SME) && id_aa64pfr1_sme(info->reg_id_aa64pfr1)) { /* From fbb6e57083114b1bd3ba6b35f4cb6a1146b5e453 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Fri, 9 Oct 2026 11:07:19 +0100 Subject: [PATCH 1948/2286] arm64: cpufeature: Store every ID register with its overrides applied __cpuinfo_store_cpu() stores ID_AA64PFR0_EL1 and ID_AA64PFR1_EL1 with their overrides applied and every other register raw. Store the rest the same way. Everything read with read_cpuid() goes through read_cpuid_with_overrides(). CTR_EL0 and CNTFRQ_EL0, which are read otherwise, go through arm64_ftr_reg_override(). It returns a register with no override unchanged, so an override added to one later takes effect without a change here. init_cpu_ftr_reg() then gets values that already carry every valid override, so its code to apply one is dead. Remove that code. init_cpu_ftr_reg() still removes an unsafe override, so the same overrides are rejected and sys_val is unchanged. Only its log changes. The raw value is gone, so it logs an override equal to that value as "forced" rather than "already set". Secondaries are compared with the boot CPU on their stored values, so CPUs that differ only in a field the command line turned off no longer taint the kernel. Suggested-by: Will Deacon Signed-off-by: Fuad Tabba Signed-off-by: Will Deacon Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpufeature.c | 17 ++----- arch/arm64/kernel/cpuinfo.c | 84 +++++++++++++++++----------------- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 9212f10c1d1ac9..d69a5b8304b68b 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1043,6 +1043,8 @@ void __init validate_ftr_regs(void) * Also initialises the strict_mask for the register. * Any bits that are not covered by an arm64_ftr_bits entry are considered * RES0 for the system-wide value, and must strictly match. + * @new has been through arm64_ftr_reg_override(), so a valid override is + * already applied to it. Only an unsafe one is left to remove here. */ static void init_cpu_ftr_reg(u32 sys_reg, u64 new) { @@ -1063,29 +1065,20 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new) s64 ftr_ovr = arm64_ftr_value(ftrp, reg->override->val); if ((ftr_mask & reg->override->mask) == ftr_mask) { - s64 tmp = arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new); - char *str = NULL; + char *str = "forced"; - if (ftr_ovr != tmp) { + if (arm64_ftr_safe_value(ftrp, ftr_ovr, ftr_new) != ftr_ovr) { /* Unsafe, remove the override */ reg->override->mask &= ~ftr_mask; reg->override->val &= ~ftr_mask; - tmp = ftr_ovr; str = "ignoring override"; - } else if (ftr_new != tmp) { - /* Override was valid */ - ftr_new = tmp; - str = "forced"; - } else { - /* Override was the safe value */ - str = "already set"; } pr_warn("%s[%d:%d]: %s to %llx\n", reg->name, ftrp->shift + ftrp->width - 1, ftrp->shift, str, - tmp & (BIT(ftrp->width) - 1)); + ftr_ovr & (BIT(ftrp->width) - 1)); } else if ((ftr_mask & reg->override->val) == ftr_mask) { reg->override->val &= ~ftr_mask; pr_warn("%s[%d:%d]: impossible override, ignored\n", diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index 0ae40b0c7b2f0e..1b92e52989b1d2 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -443,33 +443,33 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info) static void __cpuinfo_store_cpu_32bit(struct cpuinfo_32bit *info) { - info->reg_id_dfr0 = read_cpuid(ID_DFR0_EL1); - info->reg_id_dfr1 = read_cpuid(ID_DFR1_EL1); - info->reg_id_isar0 = read_cpuid(ID_ISAR0_EL1); - info->reg_id_isar1 = read_cpuid(ID_ISAR1_EL1); - info->reg_id_isar2 = read_cpuid(ID_ISAR2_EL1); - info->reg_id_isar3 = read_cpuid(ID_ISAR3_EL1); - info->reg_id_isar4 = read_cpuid(ID_ISAR4_EL1); - info->reg_id_isar5 = read_cpuid(ID_ISAR5_EL1); - info->reg_id_isar6 = read_cpuid(ID_ISAR6_EL1); - info->reg_id_mmfr0 = read_cpuid(ID_MMFR0_EL1); - info->reg_id_mmfr1 = read_cpuid(ID_MMFR1_EL1); - info->reg_id_mmfr2 = read_cpuid(ID_MMFR2_EL1); - info->reg_id_mmfr3 = read_cpuid(ID_MMFR3_EL1); - info->reg_id_mmfr4 = read_cpuid(ID_MMFR4_EL1); - info->reg_id_mmfr5 = read_cpuid(ID_MMFR5_EL1); - info->reg_id_pfr0 = read_cpuid(ID_PFR0_EL1); - info->reg_id_pfr1 = read_cpuid(ID_PFR1_EL1); - info->reg_id_pfr2 = read_cpuid(ID_PFR2_EL1); - - info->reg_mvfr0 = read_cpuid(MVFR0_EL1); - info->reg_mvfr1 = read_cpuid(MVFR1_EL1); - info->reg_mvfr2 = read_cpuid(MVFR2_EL1); + info->reg_id_dfr0 = read_cpuid_with_overrides(ID_DFR0_EL1); + info->reg_id_dfr1 = read_cpuid_with_overrides(ID_DFR1_EL1); + info->reg_id_isar0 = read_cpuid_with_overrides(ID_ISAR0_EL1); + info->reg_id_isar1 = read_cpuid_with_overrides(ID_ISAR1_EL1); + info->reg_id_isar2 = read_cpuid_with_overrides(ID_ISAR2_EL1); + info->reg_id_isar3 = read_cpuid_with_overrides(ID_ISAR3_EL1); + info->reg_id_isar4 = read_cpuid_with_overrides(ID_ISAR4_EL1); + info->reg_id_isar5 = read_cpuid_with_overrides(ID_ISAR5_EL1); + info->reg_id_isar6 = read_cpuid_with_overrides(ID_ISAR6_EL1); + info->reg_id_mmfr0 = read_cpuid_with_overrides(ID_MMFR0_EL1); + info->reg_id_mmfr1 = read_cpuid_with_overrides(ID_MMFR1_EL1); + info->reg_id_mmfr2 = read_cpuid_with_overrides(ID_MMFR2_EL1); + info->reg_id_mmfr3 = read_cpuid_with_overrides(ID_MMFR3_EL1); + info->reg_id_mmfr4 = read_cpuid_with_overrides(ID_MMFR4_EL1); + info->reg_id_mmfr5 = read_cpuid_with_overrides(ID_MMFR5_EL1); + info->reg_id_pfr0 = read_cpuid_with_overrides(ID_PFR0_EL1); + info->reg_id_pfr1 = read_cpuid_with_overrides(ID_PFR1_EL1); + info->reg_id_pfr2 = read_cpuid_with_overrides(ID_PFR2_EL1); + + info->reg_mvfr0 = read_cpuid_with_overrides(MVFR0_EL1); + info->reg_mvfr1 = read_cpuid_with_overrides(MVFR1_EL1); + info->reg_mvfr2 = read_cpuid_with_overrides(MVFR2_EL1); } static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) { - info->reg_cntfrq = arch_timer_get_cntfrq(); + info->reg_cntfrq = arm64_ftr_reg_override(SYS_CNTFRQ_EL0, arch_timer_get_cntfrq()); /* * Use the effective value of the CTR_EL0 than the raw value * exposed by the CPU. CTR_EL0.IDC field value must be interpreted @@ -478,35 +478,35 @@ static void __cpuinfo_store_cpu(struct cpuinfo_arm64 *info) * effective value of the CTR_EL0 in our internal records for * accurate sanity check and feature enablement. */ - info->reg_ctr = read_cpuid_effective_cachetype(); - info->reg_dczid = read_cpuid(DCZID_EL0); + info->reg_ctr = arm64_ftr_reg_override(SYS_CTR_EL0, read_cpuid_effective_cachetype()); + info->reg_dczid = read_cpuid_with_overrides(DCZID_EL0); info->reg_midr = read_cpuid_id(); info->reg_revidr = read_cpuid(REVIDR_EL1); info->reg_aidr = read_cpuid(AIDR_EL1); - info->reg_id_aa64dfr0 = read_cpuid(ID_AA64DFR0_EL1); - info->reg_id_aa64dfr1 = read_cpuid(ID_AA64DFR1_EL1); - info->reg_id_aa64isar0 = read_cpuid(ID_AA64ISAR0_EL1); - info->reg_id_aa64isar1 = read_cpuid(ID_AA64ISAR1_EL1); - info->reg_id_aa64isar2 = read_cpuid(ID_AA64ISAR2_EL1); - info->reg_id_aa64isar3 = read_cpuid(ID_AA64ISAR3_EL1); - info->reg_id_aa64mmfr0 = read_cpuid(ID_AA64MMFR0_EL1); - info->reg_id_aa64mmfr1 = read_cpuid(ID_AA64MMFR1_EL1); - info->reg_id_aa64mmfr2 = read_cpuid(ID_AA64MMFR2_EL1); - info->reg_id_aa64mmfr3 = read_cpuid(ID_AA64MMFR3_EL1); - info->reg_id_aa64mmfr4 = read_cpuid(ID_AA64MMFR4_EL1); + info->reg_id_aa64dfr0 = read_cpuid_with_overrides(ID_AA64DFR0_EL1); + info->reg_id_aa64dfr1 = read_cpuid_with_overrides(ID_AA64DFR1_EL1); + info->reg_id_aa64isar0 = read_cpuid_with_overrides(ID_AA64ISAR0_EL1); + info->reg_id_aa64isar1 = read_cpuid_with_overrides(ID_AA64ISAR1_EL1); + info->reg_id_aa64isar2 = read_cpuid_with_overrides(ID_AA64ISAR2_EL1); + info->reg_id_aa64isar3 = read_cpuid_with_overrides(ID_AA64ISAR3_EL1); + info->reg_id_aa64mmfr0 = read_cpuid_with_overrides(ID_AA64MMFR0_EL1); + info->reg_id_aa64mmfr1 = read_cpuid_with_overrides(ID_AA64MMFR1_EL1); + info->reg_id_aa64mmfr2 = read_cpuid_with_overrides(ID_AA64MMFR2_EL1); + info->reg_id_aa64mmfr3 = read_cpuid_with_overrides(ID_AA64MMFR3_EL1); + info->reg_id_aa64mmfr4 = read_cpuid_with_overrides(ID_AA64MMFR4_EL1); info->reg_id_aa64pfr0 = read_cpuid_with_overrides(ID_AA64PFR0_EL1); info->reg_id_aa64pfr1 = read_cpuid_with_overrides(ID_AA64PFR1_EL1); - info->reg_id_aa64pfr2 = read_cpuid(ID_AA64PFR2_EL1); - info->reg_id_aa64zfr0 = read_cpuid(ID_AA64ZFR0_EL1); - info->reg_id_aa64smfr0 = read_cpuid(ID_AA64SMFR0_EL1); - info->reg_id_aa64fpfr0 = read_cpuid(ID_AA64FPFR0_EL1); + info->reg_id_aa64pfr2 = read_cpuid_with_overrides(ID_AA64PFR2_EL1); + info->reg_id_aa64zfr0 = read_cpuid_with_overrides(ID_AA64ZFR0_EL1); + info->reg_id_aa64smfr0 = read_cpuid_with_overrides(ID_AA64SMFR0_EL1); + info->reg_id_aa64fpfr0 = read_cpuid_with_overrides(ID_AA64FPFR0_EL1); if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) - info->reg_gmid = read_cpuid(GMID_EL1); + info->reg_gmid = read_cpuid_with_overrides(GMID_EL1); if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) - info->reg_mpamidr = read_cpuid(MPAMIDR_EL1); + info->reg_mpamidr = read_cpuid_with_overrides(MPAMIDR_EL1); if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) __cpuinfo_store_cpu_32bit(&info->aarch32); From 9ddba588450f0a9d92e92fd00a8627a8c828709a Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 9 Oct 2026 11:07:20 +0100 Subject: [PATCH 1949/2286] arm64: smp: Tidy up smp_prepare_cpus() smp_prepare_cpus() is always run on the boot CPU (i.e. CPU 0) but goes to great lengths to support running on a CPU where smp_processor_id() is non-zero. Clean up the code a little by hardcoding zero for the boot CPU ID. Reviewed-by: Jinjie Ruan Signed-off-by: Will Deacon Tested-by: Pankaj Patil Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/smp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index eac217b9ebf1e6..d2aa949e33a37e 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -781,16 +781,14 @@ void __init smp_init_cpus(void) void __init smp_prepare_cpus(unsigned int max_cpus) { const struct cpu_operations *ops; - int err; unsigned int cpu; - unsigned int this_cpu; + int err; init_cpu_topology(); - this_cpu = smp_processor_id(); - store_cpu_topology(this_cpu); - numa_store_cpu_info(this_cpu); - numa_add_cpu(this_cpu); + store_cpu_topology(0); + numa_store_cpu_info(0); + numa_add_cpu(0); /* * If UP is mandated by "nosmp" (which implies "maxcpus=0"), don't set @@ -805,8 +803,7 @@ void __init smp_prepare_cpus(unsigned int max_cpus) * secondaries from the bootloader. */ for_each_possible_cpu(cpu) { - - if (cpu == smp_processor_id()) + if (cpu == 0) continue; ops = get_cpu_ops(cpu); From f0a14972b5b5080272a5410f6e2da173de397711 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 9 Oct 2026 11:07:21 +0100 Subject: [PATCH 1950/2286] arm64: smp: Tidy up cpuinfo init and cpufeature updates Populating the 'cpuinfo_arm64' structure during CPU bringup and subsequently checking/updating cpufeature structures is slightly convoluted and differs unnecessarily between the boot CPU and secondary CPUs. Rework the code so that cpuinfo_store_cpu() is used to populate the 'cpuinfo_arm64' structure for each CPU, with secondary CPUs then calling update_cpu_features() to update the global view of the available features. This allows us to internalise init_cpu_features() and the 'boot_cpu_data' in cpufeature.c and paves the way for parallelising the ID register probing during bring-up of secondary CPUs. Reviewed-by: Jinjie Ruan Signed-off-by: Will Deacon Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/include/asm/cpu.h | 5 +---- arch/arm64/kernel/cpufeature.c | 21 +++++++++++++++++---- arch/arm64/kernel/cpuinfo.c | 11 ----------- arch/arm64/kernel/smp.c | 3 ++- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/arch/arm64/include/asm/cpu.h b/arch/arm64/include/asm/cpu.h index 6d2a5ac4c2f8fd..3d1693aaf4e01a 100644 --- a/arch/arm64/include/asm/cpu.h +++ b/arch/arm64/include/asm/cpu.h @@ -73,11 +73,8 @@ struct cpuinfo_arm64 { DECLARE_PER_CPU(struct cpuinfo_arm64, cpu_data); void cpuinfo_store_cpu(void); -void __init cpuinfo_store_boot_cpu(void); +void update_cpu_features(int cpu); void __init validate_ftr_regs(void); -void __init init_cpu_features(struct cpuinfo_arm64 *info); -void update_cpu_features(int cpu, struct cpuinfo_arm64 *info, - struct cpuinfo_arm64 *boot); #endif /* __ASM_CPU_H */ diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index d69a5b8304b68b..5db456884ffae6 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -117,6 +117,7 @@ EXPORT_SYMBOL(system_cpucaps); static struct arm64_cpu_capabilities const __ro_after_init *cpucap_ptrs[ARM64_NCAPS]; DECLARE_BITMAP(boot_cpucaps, ARM64_NCAPS); +static struct cpuinfo_arm64 boot_cpu_data; /* * arm64_use_ng_mappings must be placed in the .data section, otherwise it @@ -1197,8 +1198,16 @@ static bool detect_ftr_has_mpam(void) return id_aa64pfr0_mpam(pfr0) || id_aa64pfr1_mpamfrac(pfr1); } -void __init init_cpu_features(struct cpuinfo_arm64 *info) +static void __init init_cpu_features(void) { + struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0); + + /* + * We keep a copy of the boot CPU registers so that physical hotplug + * of CPU 0 can still be properly checked. + */ + boot_cpu_data = *info; + init_cpu_ftr_reg(SYS_CTR_EL0, info->reg_ctr); init_cpu_ftr_reg(SYS_DCZID_EL0, info->reg_dczid); init_cpu_ftr_reg(SYS_CNTFRQ_EL0, info->reg_cntfrq); @@ -1391,12 +1400,14 @@ static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info, * non-boot CPU. Also performs SANITY checks to make sure that there * aren't any insane variations from that of the boot CPU. */ -void update_cpu_features(int cpu, - struct cpuinfo_arm64 *info, - struct cpuinfo_arm64 *boot) +void update_cpu_features(int cpu) { + struct cpuinfo_arm64 *boot, *info; int taint = 0; + boot = &boot_cpu_data; + info = per_cpu_ptr(&cpu_data, cpu); + /* * The kernel can handle differing I-cache policies, but otherwise * caches should look identical. Userspace JITs will make use of @@ -3963,6 +3974,8 @@ static void __init setup_boot_cpu_capabilities(void) void __init setup_boot_cpu_features(void) { + init_cpu_features(); + /* * Initialize the indirect array of CPU capabilities pointers before we * handle the boot CPU. diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index 1b92e52989b1d2..dfb21bf5541dc2 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -31,7 +31,6 @@ * values depending on configuration at or after reset. */ DEFINE_PER_CPU(struct cpuinfo_arm64, cpu_data); -static struct cpuinfo_arm64 boot_cpu_data; static inline const char *icache_policy_str(int l1ip) { @@ -528,14 +527,4 @@ void cpuinfo_store_cpu(void) { struct cpuinfo_arm64 *info = this_cpu_ptr(&cpu_data); __cpuinfo_store_cpu(info); - update_cpu_features(smp_processor_id(), info, &boot_cpu_data); -} - -void __init cpuinfo_store_boot_cpu(void) -{ - struct cpuinfo_arm64 *info = &per_cpu(cpu_data, 0); - __cpuinfo_store_cpu(info); - - boot_cpu_data = *info; - init_cpu_features(&boot_cpu_data); } diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index d2aa949e33a37e..e1b15cab9ed606 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -235,6 +235,7 @@ asmlinkage notrace void secondary_start_kernel(void) * Log the CPU info before it is marked online and might get read. */ cpuinfo_store_cpu(); + update_cpu_features(cpu); store_cpu_topology(cpu); /* @@ -457,7 +458,7 @@ void __init smp_prepare_boot_cpu(void) /* Check arm64_ftr_regs[] before anything looks registers up in it. */ validate_ftr_regs(); - cpuinfo_store_boot_cpu(); + cpuinfo_store_cpu(); setup_boot_cpu_features(); /* Conditionally switch to GIC PMR for interrupt masking */ From c8491bbf35e5e1e30f349e7629bae2e5e2eeddda Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 9 Oct 2026 11:07:22 +0100 Subject: [PATCH 1951/2286] arm64: smp: Defer update of secondary CPU capabilities check_local_cpu_capabilities() runs relatively early during the boot of each secondary CPU and, despite its name, calls update_cpu_capabilities() to manipulate the global 'system_cpucaps' based on the features detected by the incoming CPU. In preparation for parallel bringup of secondary CPUs, move the call to update_cpu_capabilities() into update_cpu_features(), allowing check_local_cpu_capabilities() to run concurrently in future, as it now only performs local verification of the incoming CPU. Reviewed-by: Jinjie Ruan Signed-off-by: Will Deacon Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpufeature.c | 307 +++++++++++++++++---------------- 1 file changed, 155 insertions(+), 152 deletions(-) diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index 5db456884ffae6..1cc18994414157 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -1395,154 +1395,6 @@ static int update_32bit_cpu_features(int cpu, struct cpuinfo_32bit *info, return taint; } -/* - * Update system wide CPU feature registers with the values from a - * non-boot CPU. Also performs SANITY checks to make sure that there - * aren't any insane variations from that of the boot CPU. - */ -void update_cpu_features(int cpu) -{ - struct cpuinfo_arm64 *boot, *info; - int taint = 0; - - boot = &boot_cpu_data; - info = per_cpu_ptr(&cpu_data, cpu); - - /* - * The kernel can handle differing I-cache policies, but otherwise - * caches should look identical. Userspace JITs will make use of - * *minLine. - */ - taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu, - info->reg_ctr, boot->reg_ctr); - - /* - * Userspace may perform DC ZVA instructions. Mismatched block sizes - * could result in too much or too little memory being zeroed if a - * process is preempted and migrated between CPUs. - */ - taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu, - info->reg_dczid, boot->reg_dczid); - - /* If different, timekeeping will be broken (especially with KVM) */ - taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu, - info->reg_cntfrq, boot->reg_cntfrq); - - /* - * The kernel uses self-hosted debug features and expects CPUs to - * support identical debug features. We presently need CTX_CMPs, WRPs, - * and BRPs to be identical. - * ID_AA64DFR1 is currently RES0. - */ - taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu, - info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0); - taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu, - info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1); - /* - * Even in big.LITTLE, processors should be identical instruction-set - * wise. - */ - taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu, - info->reg_id_aa64isar0, boot->reg_id_aa64isar0); - taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu, - info->reg_id_aa64isar1, boot->reg_id_aa64isar1); - taint |= check_update_ftr_reg(SYS_ID_AA64ISAR2_EL1, cpu, - info->reg_id_aa64isar2, boot->reg_id_aa64isar2); - taint |= check_update_ftr_reg(SYS_ID_AA64ISAR3_EL1, cpu, - info->reg_id_aa64isar3, boot->reg_id_aa64isar3); - - /* - * Differing PARange support is fine as long as all peripherals and - * memory are mapped within the minimum PARange of all CPUs. - * Linux should not care about secure memory. - */ - taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu, - info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0); - taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu, - info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1); - taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu, - info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2); - taint |= check_update_ftr_reg(SYS_ID_AA64MMFR3_EL1, cpu, - info->reg_id_aa64mmfr3, boot->reg_id_aa64mmfr3); - taint |= check_update_ftr_reg(SYS_ID_AA64MMFR4_EL1, cpu, - info->reg_id_aa64mmfr4, boot->reg_id_aa64mmfr4); - - taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu, - info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0); - taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu, - info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1); - taint |= check_update_ftr_reg(SYS_ID_AA64PFR2_EL1, cpu, - info->reg_id_aa64pfr2, boot->reg_id_aa64pfr2); - - taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu, - info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0); - - taint |= check_update_ftr_reg(SYS_ID_AA64SMFR0_EL1, cpu, - info->reg_id_aa64smfr0, boot->reg_id_aa64smfr0); - - taint |= check_update_ftr_reg(SYS_ID_AA64FPFR0_EL1, cpu, - info->reg_id_aa64fpfr0, boot->reg_id_aa64fpfr0); - - /* Probe vector lengths */ - if (IS_ENABLED(CONFIG_ARM64_SVE) && - id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1))) { - if (!system_capabilities_finalized()) { - unsigned long cpacr = cpacr_save_enable_kernel_sve(); - - vec_update_vq_map(ARM64_VEC_SVE); - - cpacr_restore(cpacr); - } - } - - if (IS_ENABLED(CONFIG_ARM64_SME) && - id_aa64pfr1_sme(read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1))) { - unsigned long cpacr = cpacr_save_enable_kernel_sme(); - - /* Probe vector lengths */ - if (!system_capabilities_finalized()) - vec_update_vq_map(ARM64_VEC_SME); - - cpacr_restore(cpacr); - } - - if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) - taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu, - info->reg_mpamidr, boot->reg_mpamidr); - - /* - * The kernel uses the LDGM/STGM instructions and the number of tags - * they read/write depends on the GMID_EL1.BS field. Check that the - * value is the same on all CPUs. - */ - if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) - taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu, - info->reg_gmid, boot->reg_gmid); - - /* - * If we don't have AArch32 at all then skip the checks entirely - * as the register values may be UNKNOWN and we're not going to be - * using them for anything. - * - * This relies on a sanitised view of the AArch64 ID registers - * (e.g. SYS_ID_AA64PFR0_EL1), so we call it last. - */ - if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) { - lazy_init_32bit_cpu_features(info, boot); - taint |= update_32bit_cpu_features(cpu, &info->aarch32, - &boot->aarch32); - } - - /* - * Mismatched CPU features are a recipe for disaster. Don't even - * pretend to support them. - */ - if (taint) { - pr_warn_once("Unsupported CPU feature variation detected.\n"); - add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK); - } -} - u64 read_sanitised_ftr_reg(u32 id) { struct arm64_ftr_reg *regp = get_arm64_ftr_reg(id); @@ -3887,16 +3739,167 @@ void check_local_cpu_capabilities(void) */ check_early_cpu_features(); + /* + * Verify that this CPU has all the system advertised + * capabilities. + */ + if (system_capabilities_finalized()) + verify_local_cpu_capabilities(); +} + +/* + * Update system wide CPU feature registers with the values from a + * non-boot CPU. Also performs SANITY checks to make sure that there + * aren't any insane variations from that of the boot CPU. + */ +void update_cpu_features(int cpu) +{ + struct cpuinfo_arm64 *boot, *info; + int taint = 0; + /* * If we haven't finalised the system capabilities, this CPU gets * a chance to update the errata work arounds and local features. - * Otherwise, this CPU should verify that it has all the system - * advertised capabilities. */ if (!system_capabilities_finalized()) update_cpu_capabilities(SCOPE_LOCAL_CPU); - else - verify_local_cpu_capabilities(); + + boot = &boot_cpu_data; + info = per_cpu_ptr(&cpu_data, cpu); + + /* + * The kernel can handle differing I-cache policies, but otherwise + * caches should look identical. Userspace JITs will make use of + * *minLine. + */ + taint |= check_update_ftr_reg(SYS_CTR_EL0, cpu, + info->reg_ctr, boot->reg_ctr); + + /* + * Userspace may perform DC ZVA instructions. Mismatched block sizes + * could result in too much or too little memory being zeroed if a + * process is preempted and migrated between CPUs. + */ + taint |= check_update_ftr_reg(SYS_DCZID_EL0, cpu, + info->reg_dczid, boot->reg_dczid); + + /* If different, timekeeping will be broken (especially with KVM) */ + taint |= check_update_ftr_reg(SYS_CNTFRQ_EL0, cpu, + info->reg_cntfrq, boot->reg_cntfrq); + + /* + * The kernel uses self-hosted debug features and expects CPUs to + * support identical debug features. We presently need CTX_CMPs, WRPs, + * and BRPs to be identical. + * ID_AA64DFR1 is currently RES0. + */ + taint |= check_update_ftr_reg(SYS_ID_AA64DFR0_EL1, cpu, + info->reg_id_aa64dfr0, boot->reg_id_aa64dfr0); + taint |= check_update_ftr_reg(SYS_ID_AA64DFR1_EL1, cpu, + info->reg_id_aa64dfr1, boot->reg_id_aa64dfr1); + /* + * Even in big.LITTLE, processors should be identical instruction-set + * wise. + */ + taint |= check_update_ftr_reg(SYS_ID_AA64ISAR0_EL1, cpu, + info->reg_id_aa64isar0, boot->reg_id_aa64isar0); + taint |= check_update_ftr_reg(SYS_ID_AA64ISAR1_EL1, cpu, + info->reg_id_aa64isar1, boot->reg_id_aa64isar1); + taint |= check_update_ftr_reg(SYS_ID_AA64ISAR2_EL1, cpu, + info->reg_id_aa64isar2, boot->reg_id_aa64isar2); + taint |= check_update_ftr_reg(SYS_ID_AA64ISAR3_EL1, cpu, + info->reg_id_aa64isar3, boot->reg_id_aa64isar3); + + /* + * Differing PARange support is fine as long as all peripherals and + * memory are mapped within the minimum PARange of all CPUs. + * Linux should not care about secure memory. + */ + taint |= check_update_ftr_reg(SYS_ID_AA64MMFR0_EL1, cpu, + info->reg_id_aa64mmfr0, boot->reg_id_aa64mmfr0); + taint |= check_update_ftr_reg(SYS_ID_AA64MMFR1_EL1, cpu, + info->reg_id_aa64mmfr1, boot->reg_id_aa64mmfr1); + taint |= check_update_ftr_reg(SYS_ID_AA64MMFR2_EL1, cpu, + info->reg_id_aa64mmfr2, boot->reg_id_aa64mmfr2); + taint |= check_update_ftr_reg(SYS_ID_AA64MMFR3_EL1, cpu, + info->reg_id_aa64mmfr3, boot->reg_id_aa64mmfr3); + taint |= check_update_ftr_reg(SYS_ID_AA64MMFR4_EL1, cpu, + info->reg_id_aa64mmfr4, boot->reg_id_aa64mmfr4); + + taint |= check_update_ftr_reg(SYS_ID_AA64PFR0_EL1, cpu, + info->reg_id_aa64pfr0, boot->reg_id_aa64pfr0); + taint |= check_update_ftr_reg(SYS_ID_AA64PFR1_EL1, cpu, + info->reg_id_aa64pfr1, boot->reg_id_aa64pfr1); + taint |= check_update_ftr_reg(SYS_ID_AA64PFR2_EL1, cpu, + info->reg_id_aa64pfr2, boot->reg_id_aa64pfr2); + + taint |= check_update_ftr_reg(SYS_ID_AA64ZFR0_EL1, cpu, + info->reg_id_aa64zfr0, boot->reg_id_aa64zfr0); + + taint |= check_update_ftr_reg(SYS_ID_AA64SMFR0_EL1, cpu, + info->reg_id_aa64smfr0, boot->reg_id_aa64smfr0); + + taint |= check_update_ftr_reg(SYS_ID_AA64FPFR0_EL1, cpu, + info->reg_id_aa64fpfr0, boot->reg_id_aa64fpfr0); + + /* Probe vector lengths */ + if (IS_ENABLED(CONFIG_ARM64_SVE) && + id_aa64pfr0_sve(read_sanitised_ftr_reg(SYS_ID_AA64PFR0_EL1))) { + if (!system_capabilities_finalized()) { + unsigned long cpacr = cpacr_save_enable_kernel_sve(); + + vec_update_vq_map(ARM64_VEC_SVE); + + cpacr_restore(cpacr); + } + } + + if (IS_ENABLED(CONFIG_ARM64_SME) && + id_aa64pfr1_sme(read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1))) { + unsigned long cpacr = cpacr_save_enable_kernel_sme(); + + /* Probe vector lengths */ + if (!system_capabilities_finalized()) + vec_update_vq_map(ARM64_VEC_SME); + + cpacr_restore(cpacr); + } + + if (id_aa64pfr0_mpam(info->reg_id_aa64pfr0) || id_aa64pfr1_mpamfrac(info->reg_id_aa64pfr1)) + taint |= check_update_ftr_reg(SYS_MPAMIDR_EL1, cpu, + info->reg_mpamidr, boot->reg_mpamidr); + + /* + * The kernel uses the LDGM/STGM instructions and the number of tags + * they read/write depends on the GMID_EL1.BS field. Check that the + * value is the same on all CPUs. + */ + if (IS_ENABLED(CONFIG_ARM64_MTE) && id_aa64pfr1_mte(info->reg_id_aa64pfr1)) + taint |= check_update_ftr_reg(SYS_GMID_EL1, cpu, + info->reg_gmid, boot->reg_gmid); + + /* + * If we don't have AArch32 at all then skip the checks entirely + * as the register values may be UNKNOWN and we're not going to be + * using them for anything. + * + * This relies on a sanitised view of the AArch64 ID registers + * (e.g. SYS_ID_AA64PFR0_EL1), so we call it last. + */ + if (id_aa64pfr0_32bit_el0(info->reg_id_aa64pfr0)) { + lazy_init_32bit_cpu_features(info, boot); + taint |= update_32bit_cpu_features(cpu, &info->aarch32, + &boot->aarch32); + } + + /* + * Mismatched CPU features are a recipe for disaster. Don't even + * pretend to support them. + */ + if (taint) { + pr_warn_once("Unsupported CPU feature variation detected.\n"); + add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK); + } } bool this_cpu_has_cap(unsigned int n) From 6a52689f698a1755b0fa3cafb7bdf2b6356da731 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 9 Oct 2026 11:07:23 +0100 Subject: [PATCH 1952/2286] arm64: smp: Don't bother printing the I-cache policy for each CPU The I-cache policy isn't particularly interesting but printing it early can result in unnecessary serialisation of onlining CPUs. Remove the pointless print. Reviewed-by: Jinjie Ruan Signed-off-by: Will Deacon Tested-by: Pankaj Patil Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/cpuinfo.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c index dfb21bf5541dc2..94e6cd211d70e6 100644 --- a/arch/arm64/kernel/cpuinfo.c +++ b/arch/arm64/kernel/cpuinfo.c @@ -31,19 +31,6 @@ * values depending on configuration at or after reset. */ DEFINE_PER_CPU(struct cpuinfo_arm64, cpu_data); - -static inline const char *icache_policy_str(int l1ip) -{ - switch (l1ip) { - case CTR_EL0_L1Ip_VIPT: - return "VIPT"; - case CTR_EL0_L1Ip_PIPT: - return "PIPT"; - default: - return "RESERVED/UNKNOWN"; - } -} - unsigned long __icache_flags; static const char *const hwcap_str[] = { @@ -424,7 +411,6 @@ device_initcall(cpuinfo_regs_init); static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info) { - unsigned int cpu = smp_processor_id(); u32 l1ip = CTR_L1IP(info->reg_ctr); switch (l1ip) { @@ -436,8 +422,6 @@ static void cpuinfo_detect_icache_policy(struct cpuinfo_arm64 *info) set_bit(ICACHEF_ALIASING, &__icache_flags); break; } - - pr_info("Detected %s I-cache on CPU%d\n", icache_policy_str(l1ip), cpu); } static void __cpuinfo_store_cpu_32bit(struct cpuinfo_32bit *info) From e21f4de88acaa9f417b8bde54a9c33b4cec33f10 Mon Sep 17 00:00:00 2001 From: Will Deacon Date: Fri, 9 Oct 2026 11:07:24 +0100 Subject: [PATCH 1953/2286] arm64: smp: Defer RCU registration during secondary CPU bringup Calling rcutree_report_cpu_starting() early during boot can lead to livelocks with the generic CPU hotplug mechanism if the boot CPU blocks on an RCU grace period while the CPU being onlined is spinning in cpuhp_ap_sync_alive(). In preparation for enabling the generic CPU hotplug code on arm64, split up the trace_hardirqs_off() call during secondary CPU bringup so that we update lockdep early but defer the tracing updates until after the new CPU has been registered with RCU, which itself is deferred until after the early CPU compatibility checks, allowing us to drop the explicit call to rcutree_report_cpu_dead() entirely. Signed-off-by: Will Deacon Tested-by: Michael Kelley Tested-by: Jinjie Ruan Signed-off-by: Catalin Marinas --- arch/arm64/kernel/smp.c | 6 +++--- include/linux/rcutree.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/arm64/kernel/smp.c b/arch/arm64/kernel/smp.c index e1b15cab9ed606..014b163ce6584e 100644 --- a/arch/arm64/kernel/smp.c +++ b/arch/arm64/kernel/smp.c @@ -217,8 +217,7 @@ asmlinkage notrace void secondary_start_kernel(void) if (system_uses_irq_prio_masking()) init_gic_priority_masking(); - rcutree_report_cpu_starting(cpu); - trace_hardirqs_off(); + lockdep_hardirqs_off(CALLER_ADDR0); /* * If the system has established the capabilities, make sure @@ -235,6 +234,7 @@ asmlinkage notrace void secondary_start_kernel(void) * Log the CPU info before it is marked online and might get read. */ cpuinfo_store_cpu(); + rcutree_report_cpu_starting(cpu); update_cpu_features(cpu); store_cpu_topology(cpu); @@ -242,6 +242,7 @@ asmlinkage notrace void secondary_start_kernel(void) * Enable GIC and timers. */ notify_cpu_starting(cpu); + trace_hardirqs_off_finish(); ipi_setup(cpu); @@ -411,7 +412,6 @@ void __noreturn cpu_die_early(void) /* Mark this CPU absent */ set_cpu_present(cpu, 0); - rcutree_report_cpu_dead(); if (IS_ENABLED(CONFIG_HOTPLUG_CPU)) { update_cpu_boot_status(CPU_KILL_ME); diff --git a/include/linux/rcutree.h b/include/linux/rcutree.h index 16a04202888b45..e6ad4f2a475c40 100644 --- a/include/linux/rcutree.h +++ b/include/linux/rcutree.h @@ -116,7 +116,7 @@ int rcutree_offline_cpu(unsigned int cpu); void rcutree_migrate_callbacks(int cpu); -/* Called from hotplug and also arm64 early secondary boot failure */ +/* Called from hotplug */ void rcutree_report_cpu_dead(void); #endif /* __LINUX_RCUTREE_H */ From af0010f2961083bf32370846556ef9e5c4773994 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 13 Oct 2026 14:02:44 +0100 Subject: [PATCH 1954/2286] arm64: sysreg: Fix three incorrect enum values Three enum values in the sysreg file don't match DDI0601 2026-09. Outer Shareable in VTCR_EL2.SH0 is 0b10, not 0b01, as TCR_EL1.SH0 in the same file already has it. Thumb-2 in ID_PFR0_EL1.State1 is 0b0011, not 0b0010, and the M-profile model in ID_PFR1_EL1.MProgMod is 0b0010, not 0b0001. The spec lists all three old values as reserved. There are currently no users of these values. The VTCR_EL2 one came in with commit a035001dea37 ("arm64: Convert VTCR_EL2 to sysreg infratructure"), the ID_PFR0_EL1 one with commit fb0b8d1a24d8 ("arm64/sysreg: Convert ID_PFR0_EL1 to automatic generation") and the ID_PFR1_EL1 one with commit 1224308075f1 ("arm64/sysreg: Convert ID_PFR1_EL1 to automatic generation"). Signed-off-by: Fuad Tabba Reviewed-by: Mark Brown Signed-off-by: Catalin Marinas --- arch/arm64/tools/sysreg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index e2d37ee221b84a..f2812545f336e4 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -434,7 +434,7 @@ EndEnum UnsignedEnum 7:4 State1 0b0000 NI 0b0001 THUMB - 0b0010 THUMB2 + 0b0011 THUMB2 EndEnum UnsignedEnum 3:0 State0 0b0000 NI @@ -469,7 +469,7 @@ UnsignedEnum 15:12 Virtualization EndEnum UnsignedEnum 11:8 MProgMod 0b0000 NI - 0b0001 IMP + 0b0010 IMP EndEnum Enum 7:4 Security 0b0000 NI @@ -4652,7 +4652,7 @@ Enum 15:14 TG0 EndEnum Enum 13:12 SH0 0b00 NONE - 0b01 OUTER + 0b10 OUTER 0b11 INNER EndEnum Enum 11:10 ORGN0 From 5fee8b920a06eb0e1475613872b8e47d65787a6e Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 13 Oct 2026 14:02:45 +0100 Subject: [PATCH 1955/2286] arm64: sysreg: Fix the names of two misspelt fields Two fields in the sysreg file have the wrong name. In DDI0601 2026-09, bit 10 of HFGRTR2_EL2 and HFGWTR2_EL2, which traps SCTLR2ALIAS_EL1, is nSCTLR2ALIAS_EL1, not nSCTLRALIAS2_EL1, and bits [39:36] of SMPRIMAP_EL2 are P9, between P8 and P10, not F9. Rename both, along with the three KVM users of the first. The second has no users. No functional change intended. Signed-off-by: Fuad Tabba Reviewed-by: Mark Brown Signed-off-by: Catalin Marinas --- arch/arm64/kvm/config.c | 4 ++-- arch/arm64/kvm/emulate-nested.c | 2 +- arch/arm64/tools/sysreg | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c index 1053676551aff9..e93919cadf8600 100644 --- a/arch/arm64/kvm/config.c +++ b/arch/arm64/kvm/config.c @@ -804,7 +804,7 @@ static const struct reg_bits_to_feat_map hfgrtr2_feat_map[] = { HFGRTR2_EL2_nCPACRALIAS_EL1 | HFGRTR2_EL2_nCPACRMASK_EL1 | HFGRTR2_EL2_nSCTLR2MASK_EL1 | - HFGRTR2_EL2_nSCTLRALIAS2_EL1 | + HFGRTR2_EL2_nSCTLR2ALIAS_EL1 | HFGRTR2_EL2_nSCTLRALIAS_EL1 | HFGRTR2_EL2_nSCTLRMASK_EL1 | HFGRTR2_EL2_nTCR2ALIAS_EL1 | @@ -825,7 +825,7 @@ static const struct reg_bits_to_feat_map hfgwtr2_feat_map[] = { HFGWTR2_EL2_nCPACRALIAS_EL1 | HFGWTR2_EL2_nCPACRMASK_EL1 | HFGWTR2_EL2_nSCTLR2MASK_EL1 | - HFGWTR2_EL2_nSCTLRALIAS2_EL1 | + HFGWTR2_EL2_nSCTLR2ALIAS_EL1 | HFGWTR2_EL2_nSCTLRALIAS_EL1 | HFGWTR2_EL2_nSCTLRMASK_EL1 | HFGWTR2_EL2_nTCR2ALIAS_EL1 | diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c index 625604019fb321..bf22fe229783a9 100644 --- a/arch/arm64/kvm/emulate-nested.c +++ b/arch/arm64/kvm/emulate-nested.c @@ -1431,7 +1431,7 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = { SR_FGT(SYS_CPACRMASK_EL1, HFGRTR2, nCPACRMASK_EL1, 0), SR_FGT(SYS_PFAR_EL1, HFGRTR2, nPFAR_EL1, 0), SR_FGT(SYS_RCWSMASK_EL1, HFGRTR2, nRCWSMASK_EL1, 0), - SR_FGT(SYS_SCTLR2ALIAS_EL1, HFGRTR2, nSCTLRALIAS2_EL1, 0), + SR_FGT(SYS_SCTLR2ALIAS_EL1, HFGRTR2, nSCTLR2ALIAS_EL1, 0), SR_FGT(SYS_SCTLR2MASK_EL1, HFGRTR2, nSCTLR2MASK_EL1, 0), SR_FGT(SYS_SCTLRALIAS_EL1, HFGRTR2, nSCTLRALIAS_EL1, 0), SR_FGT(SYS_SCTLRMASK_EL1, HFGRTR2, nSCTLRMASK_EL1, 0), diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index f2812545f336e4..341db8e9b6c372 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -4336,7 +4336,7 @@ Field 14 nACTLRALIAS_EL1 Field 13 nACTLRMASK_EL1 Field 12 nTCR2ALIAS_EL1 Field 11 nTCRALIAS_EL1 -Field 10 nSCTLRALIAS2_EL1 +Field 10 nSCTLR2ALIAS_EL1 Field 9 nSCTLRALIAS_EL1 Field 8 nCPACRALIAS_EL1 Field 7 nTCR2MASK_EL1 @@ -4355,7 +4355,7 @@ Field 14 nACTLRALIAS_EL1 Field 13 nACTLRMASK_EL1 Field 12 nTCR2ALIAS_EL1 Field 11 nTCRALIAS_EL1 -Field 10 nSCTLRALIAS2_EL1 +Field 10 nSCTLR2ALIAS_EL1 Field 9 nSCTLRALIAS_EL1 Field 8 nCPACRALIAS_EL1 Field 7 nTCR2MASK_EL1 @@ -4592,7 +4592,7 @@ Field 55:52 P13 Field 51:48 P12 Field 47:44 P11 Field 43:40 P10 -Field 39:36 F9 +Field 39:36 P9 Field 35:32 P8 Field 31:28 P7 Field 27:24 P6 From cbaca3920f6489bdf40bd7fb1e7c5b53dd39c28e Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 13 Oct 2026 14:02:46 +0100 Subject: [PATCH 1956/2286] arm64: sysreg: Mark fields the architecture dropped as RES0 Commit d65bf6e317e7 ("KVM: arm64: Remove all traces of FEAT_TME") turned the TME fields of SCTLR_EL1 into RES0, but left their copies in SCTLRMASK_EL1. PMICFILTR_EL0.T and BRBFCR_EL1.LASTFAILED have gone from the architecture as well. All six fields are RES0 in DDI0601 2026-09. Mark them RES0 to match the spec. Nothing in the tree refers to them. Signed-off-by: Fuad Tabba Signed-off-by: Catalin Marinas --- arch/arm64/tools/sysreg | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index 341db8e9b6c372..99af1c39478be9 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -1420,8 +1420,7 @@ Field 17 DIRECT Field 16 EnI Res0 15:8 Field 7 PAUSED -Field 6 LASTFAILED -Res0 5:0 +Res0 6:0 EndSysreg Sysreg BRBTS_EL1 2 1 9 0 2 @@ -2663,11 +2662,7 @@ Field 57 EPAN Field 56 EnALS Field 55 EnAS0 Field 54 EnASR -Field 53 TME -Field 52 TME0 -Field 51 TMT -Field 50 TMT0 -Res0 49:47 +Res0 53:47 Field 46 TWEDEL Field 45 TWEDEn Field 44 DSSBS @@ -2985,7 +2980,7 @@ Field 27 NSH Field 26 M Res0 25 Field 24 SH -Field 23 T +Res0 23 Field 22 RLK Field 21 RLU Field 20 RLH From 4a90fba44e3b0c86fc61b749ccb08b06a08d6288 Mon Sep 17 00:00:00 2001 From: Fuad Tabba Date: Tue, 13 Oct 2026 14:02:47 +0100 Subject: [PATCH 1957/2286] perf: arm_brbe: Remove the check for TME transaction states When an event samples branch flags, the BRBE driver warns once if a branch record has BRBINFx_EL1.T or LASTFAILED set, which its comment calls TME-related information. FEAT_TME has been withdrawn from the architecture, and both fields are RES0 in DDI0601 2026-09, so the check tests fields that no longer exist. Remove the check and its two accessors, which were the only users of the fields' defines, and mark both fields RES0 in the sysreg file. Signed-off-by: Fuad Tabba Signed-off-by: Catalin Marinas --- arch/arm64/tools/sysreg | 5 +---- drivers/perf/arm_brbe.c | 18 ------------------ 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg index 99af1c39478be9..b15a59b9e95dd1 100644 --- a/arch/arm64/tools/sysreg +++ b/arch/arm64/tools/sysreg @@ -1338,10 +1338,7 @@ Res0 63:47 Field 46 CCU Field 45:40 CC_EXP Field 39:32 CC_MANT -Res0 31:18 -Field 17 LASTFAILED -Field 16 T -Res0 15:14 +Res0 31:14 Enum 13:8 TYPE 0b000000 DIRECT_UNCOND 0b000001 INDIRECT diff --git a/drivers/perf/arm_brbe.c b/drivers/perf/arm_brbe.c index ba554e0c846c4f..df1354e2017d72 100644 --- a/drivers/perf/arm_brbe.c +++ b/drivers/perf/arm_brbe.c @@ -166,21 +166,11 @@ static bool brbe_record_is_target_only(u64 brbinf) return brbe_record_valid(brbinf) == BRBINFx_EL1_VALID_TARGET; } -static int brbinf_get_in_tx(u64 brbinf) -{ - return FIELD_GET(BRBINFx_EL1_T_MASK, brbinf); -} - static int brbinf_get_mispredict(u64 brbinf) { return FIELD_GET(BRBINFx_EL1_MPRED_MASK, brbinf); } -static int brbinf_get_lastfailed(u64 brbinf) -{ - return FIELD_GET(BRBINFx_EL1_LASTFAILED_MASK, brbinf); -} - static u16 brbinf_get_cycles(u64 brbinf) { u32 exp, mant, cycles; @@ -627,14 +617,6 @@ static bool perf_entry_from_brbe_regset(int index, struct perf_branch_entry *ent entry->mispred = brbinf_get_mispredict(brbinf); entry->predicted = !entry->mispred; } - - /* - * Currently TME feature is neither implemented in any hardware - * nor it is being supported in the kernel. Just warn here once - * if TME related information shows up rather unexpectedly. - */ - if (brbinf_get_lastfailed(brbinf) || brbinf_get_in_tx(brbinf)) - pr_warn_once("Unknown transaction states\n"); } /* From cfadcb9c0fb6025950c2891b808b0e23efa827e6 Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Tue, 13 Oct 2026 00:04:02 +0900 Subject: [PATCH 1958/2286] s390/sclp: Remove redundant memset after memblock_alloc_low() memblock_alloc_low() returns zeroed memory, so the memset after the allocation in sclp_early_get_core_info() is redundant. So remove the memset. Signed-off-by: Sang-Heon Jeon Acked-by: Alexander Gordeev Signed-off-by: Heiko Carstens --- drivers/s390/char/sclp_early.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c index 22dd797e62291f..821b61727d1f63 100644 --- a/drivers/s390/char/sclp_early.c +++ b/drivers/s390/char/sclp_early.c @@ -138,7 +138,6 @@ int __init sclp_early_get_core_info(struct sclp_core_info *info) if (!sccb) return -ENOMEM; - memset(sccb, 0, length); sccb->header.length = length; sccb->header.control_mask[2] = 0x80; if (sclp_early_cmd(SCLP_CMDW_READ_CPU_INFO, sccb)) { From 9bfcede4926054b8ee154e92e30dfabecf238ca4 Mon Sep 17 00:00:00 2001 From: Gerd Bayer Date: Mon, 12 Oct 2026 16:21:49 +0200 Subject: [PATCH 1959/2286] s390/pci: Adjust alignment in insn trace Currently we have 64bit wide fields start at odd byte-offsets in the insn traces for pci_error in the s390dbf debug area, which are hard to decode in hex dumps. At the same time, trace entries are padded at the end. Drop the packed attribute on the struct definition to exploit the compiler's natural alignment, introduce explicit padding before and after the 64bit fields for humans, and add a static assertion to avoid accidental overruns of 2 lines in the s390dbf trace. Signed-off-by: Gerd Bayer Reviewed-by: Benjamin Block Reviewed-by: Matthew Rosato Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_insn.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/s390/pci/pci_insn.c b/arch/s390/pci/pci_insn.c index ed0def408f18a1..6afabc5b68aa85 100644 --- a/arch/s390/pci/pci_insn.c +++ b/arch/s390/pci/pci_insn.c @@ -18,11 +18,13 @@ #include #define ZPCI_INSN_BUSY_DELAY 1 /* 1 microsecond */ +#define ZPCI_ERR_INSN_DATA_SIZE 32 struct zpci_err_insn_data { u8 insn; u8 cc; u8 status; + u8 pad0[5]; union { struct { u64 req; @@ -33,7 +35,10 @@ struct zpci_err_insn_data { u64 len; }; }; -} __packed; + u64 pad1; +}; + +static_assert(sizeof(struct zpci_err_insn_data) <= ZPCI_ERR_INSN_DATA_SIZE); static inline void zpci_err_insn_req(int lvl, u8 insn, u8 cc, u8 status, u64 req, u64 offset) From 75c1c3b702910be7b15ad1145a97180e060b042c Mon Sep 17 00:00:00 2001 From: Gerd Bayer Date: Mon, 12 Oct 2026 16:21:50 +0200 Subject: [PATCH 1960/2286] s390/pci: Use 32-bit fh outside of inline asm zpci_refresh_trans() declared the PCI function handle as a u64 type just because the actual RPCIT machine instruction required that in a 64bit register, while the function handle is architected as a 32bit type and defined as such in the zpci_dev struct. Push the extension from u32 to u64 down in the call chain to just before calling the RPCIT wrapper function. Since the struct of the typical req's matches the format expected by RPCIT, too, create one of those to pass along. Signed-off-by: Gerd Bayer Reviewed-by: Niklas Schnelle Reviewed-by: Matthew Rosato Signed-off-by: Heiko Carstens --- arch/s390/include/asm/pci_insn.h | 2 +- arch/s390/pci/pci_insn.c | 5 +++-- drivers/iommu/s390-iommu.c | 8 +++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/arch/s390/include/asm/pci_insn.h b/arch/s390/include/asm/pci_insn.h index 025c6dcbf89331..e6613b037f936f 100644 --- a/arch/s390/include/asm/pci_insn.h +++ b/arch/s390/include/asm/pci_insn.h @@ -147,7 +147,7 @@ union zpci_sic_iib { DECLARE_STATIC_KEY_FALSE(have_mio); u8 zpci_mod_fc(u64 req, struct zpci_fib *fib, u8 *status); -int zpci_refresh_trans(u64 fn, u64 addr, u64 range); +int zpci_refresh_trans(u32 fh, u64 addr, u64 range); int __zpci_load(u64 *data, u64 req, u64 offset); int zpci_load(u64 *data, const volatile void __iomem *addr, unsigned long len); int __zpci_store(u64 data, u64 req, u64 offset); diff --git a/arch/s390/pci/pci_insn.c b/arch/s390/pci/pci_insn.c index 6afabc5b68aa85..b65135a94642ac 100644 --- a/arch/s390/pci/pci_insn.c +++ b/arch/s390/pci/pci_insn.c @@ -116,13 +116,14 @@ static inline u8 __rpcit(u64 fn, u64 addr, u64 range, u8 *status) return CC_TRANSFORM(cc); } -int zpci_refresh_trans(u64 fn, u64 addr, u64 range) +int zpci_refresh_trans(u32 fh, u64 addr, u64 range) { + u64 req = ZPCI_CREATE_REQ(fh, 0, 0); bool retried = false; u8 cc, status; do { - cc = __rpcit(fn, addr, range, &status); + cc = __rpcit(req, addr, range, &status); if (cc == 2) { udelay(ZPCI_INSN_BUSY_DELAY); if (!retried) { diff --git a/drivers/iommu/s390-iommu.c b/drivers/iommu/s390-iommu.c index f148f559ac5640..77c195fc752f17 100644 --- a/drivers/iommu/s390-iommu.c +++ b/drivers/iommu/s390-iommu.c @@ -788,7 +788,7 @@ static struct iommu_device *s390_iommu_probe_device(struct device *dev) static int zpci_refresh_all(struct zpci_dev *zdev) { - return zpci_refresh_trans((u64)zdev->fh << 32, zdev->start_dma, + return zpci_refresh_trans(zdev->fh, zdev->start_dma, zdev->end_dma - zdev->start_dma + 1); } @@ -819,8 +819,7 @@ static void s390_iommu_iotlb_sync(struct iommu_domain *domain, rcu_read_lock(); list_for_each_entry_rcu(zdev, &s390_domain->devices, iommu_list) { atomic64_inc(&s390_domain->ctrs.sync_rpcits); - zpci_refresh_trans((u64)zdev->fh << 32, gather->start, - size); + zpci_refresh_trans(zdev->fh, gather->start, size); } rcu_read_unlock(); } @@ -837,8 +836,7 @@ static int s390_iommu_iotlb_sync_map(struct iommu_domain *domain, if (!zdev->tlb_refresh) continue; atomic64_inc(&s390_domain->ctrs.sync_map_rpcits); - ret = zpci_refresh_trans((u64)zdev->fh << 32, - iova, size); + ret = zpci_refresh_trans(zdev->fh, iova, size); /* * let the hypervisor discover invalidated entries * allowing it to free IOVAs and unpin pages From 824c652322ae659cc1c24778fa3604831575de8f Mon Sep 17 00:00:00 2001 From: Gerd Bayer Date: Mon, 12 Oct 2026 16:21:51 +0200 Subject: [PATCH 1961/2286] s390/pci: Add function handle to RPCIT insn trace In certain debug situations it would be helpful, if the insn trace for a failing RPCIT would reveal the PCI function on which this was attempted. Introduce a variant of zpci_err_insn_req() called zpci_err_insn_rpcit() that accepts an IO virtual address and a range parameter together with the req conveying the function handle. Switch zpci_refresh_trans() to use that new error trace. Signed-off-by: Gerd Bayer Reviewed-by: Niklas Schnelle Reviewed-by: Matthew Rosato Signed-off-by: Heiko Carstens --- arch/s390/pci/pci_insn.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/arch/s390/pci/pci_insn.c b/arch/s390/pci/pci_insn.c index b65135a94642ac..b5b3ea11ea426a 100644 --- a/arch/s390/pci/pci_insn.c +++ b/arch/s390/pci/pci_insn.c @@ -28,18 +28,40 @@ struct zpci_err_insn_data { union { struct { u64 req; - u64 offset; + union { + /* PCILG/PCISTG/MPCIFC */ + struct { + u64 offset; + u64 pad1; + }; + /* RPCIT */ + struct { + u64 iova; + u64 range; + }; + }; }; + /* PCILGI/PCISTGI */ struct { u64 addr; u64 len; + u64 pad2; }; }; - u64 pad1; }; static_assert(sizeof(struct zpci_err_insn_data) <= ZPCI_ERR_INSN_DATA_SIZE); +static inline void zpci_err_insn_rpcit(int lvl, u8 insn, u8 cc, u8 status, + u64 req, u64 iova, u64 range) +{ + struct zpci_err_insn_data data = { + .insn = insn, .cc = cc, .status = status, + .req = req, .iova = iova, .range = range}; + + zpci_err_hex_level(lvl, &data, sizeof(data)); +} + static inline void zpci_err_insn_req(int lvl, u8 insn, u8 cc, u8 status, u64 req, u64 offset) { @@ -127,16 +149,16 @@ int zpci_refresh_trans(u32 fh, u64 addr, u64 range) if (cc == 2) { udelay(ZPCI_INSN_BUSY_DELAY); if (!retried) { - zpci_err_insn_addr(1, 'R', cc, status, addr, range); + zpci_err_insn_rpcit(1, 'R', cc, status, req, addr, range); retried = true; } } } while (cc == 2); if (cc) - zpci_err_insn_addr(0, 'R', cc, status, addr, range); + zpci_err_insn_rpcit(0, 'R', cc, status, req, addr, range); else if (retried) - zpci_err_insn_addr(1, 'R', cc, status, addr, range); + zpci_err_insn_rpcit(1, 'R', cc, status, req, addr, range); if (cc == 1 && (status == 4 || status == 16)) return -ENOMEM; From d61903049923242744cfe7009ed60bb7d3708630 Mon Sep 17 00:00:00 2001 From: Alexander Gordeev Date: Wed, 7 Oct 2026 13:41:53 +0200 Subject: [PATCH 1962/2286] s390/mm: Cleanup pXXp_flush_lazy() routines Avoid unnecessary pgtable entry read in ptep_flush_lazy() and pmdp_flush_lazy() routines. Use the already obtained value of a pgtable entry instead of dereferencing it again. It might appear that the optimized out second dereferencing happens under mm_context_t::flush_count lock and therefore a race condition is possible. However, both routines write invalid entry bits to the pgtable entries and therefore no reader is allowed to interpret the rest of the value afterwards. Signed-off-by: Alexander Gordeev Reviewed-by: Gerald Schaefer Signed-off-by: Heiko Carstens --- arch/s390/mm/pgtable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/s390/mm/pgtable.c b/arch/s390/mm/pgtable.c index 4acd8b140c4bd1..da076928b63ba2 100644 --- a/arch/s390/mm/pgtable.c +++ b/arch/s390/mm/pgtable.c @@ -107,7 +107,7 @@ static inline pte_t ptep_flush_lazy(struct mm_struct *mm, atomic_inc(&mm->context.flush_count); if (cpumask_equal(&mm->context.cpu_attach_mask, cpumask_of(smp_processor_id()))) { - set_pte(ptep, set_pte_bit(*ptep, __pgprot(_PAGE_INVALID))); + set_pte(ptep, set_pte_bit(old, __pgprot(_PAGE_INVALID))); mm->context.flush_mm = 1; } else ptep_ipte_global(mm, addr, ptep, nodat); @@ -227,7 +227,7 @@ static inline pmd_t pmdp_flush_lazy(struct mm_struct *mm, atomic_inc(&mm->context.flush_count); if (cpumask_equal(&mm->context.cpu_attach_mask, cpumask_of(smp_processor_id()))) { - set_pmd(pmdp, set_pmd_bit(*pmdp, __pgprot(_SEGMENT_ENTRY_INVALID))); + set_pmd(pmdp, set_pmd_bit(old, __pgprot(_SEGMENT_ENTRY_INVALID))); mm->context.flush_mm = 1; } else { pmdp_idte_global(mm, addr, pmdp); From 89fb88215875248f6981a5b168c50538d7f9c751 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Wed, 14 Oct 2026 13:50:06 +0200 Subject: [PATCH 1963/2286] s390: Select ARCH_MEMORY_ORDER_TSO s390 is a Total Store Order (TSO) architecture. Therefore select ARCH_MEMORY_ORDER_TSO which was introduced with [1]. As of now this has only a small effect on futex_robust_unlock(), but that might change in the future. [1] commit 6149fc36c09b ("uaccess: Provide unsafe_atomic_store_release_user()") Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 4b51bc6e8948d7..ebdf9c8049af40 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -145,6 +145,7 @@ config S390 select ARCH_INLINE_WRITE_UNLOCK_BH select ARCH_INLINE_WRITE_UNLOCK_IRQ select ARCH_INLINE_WRITE_UNLOCK_IRQRESTORE + select ARCH_MEMORY_ORDER_TSO select ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE select ARCH_STACKWALK select ARCH_SUPPORTS_ATOMIC_RMW From d7ccab11a13475e656b9d2277900422c1bfc12c5 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 10:36:27 +0200 Subject: [PATCH 1964/2286] s390/probes: Fix wrong opcode for cgrb fixup The last byte of the cgrb instruction is 0xe4, however the probes code compares against 0xe6. Fix it. Fixes: 6b169ac9b434 ("s390/kprobes: add support for compare and branch instructions") Cc: stable@vger.kernel.org Signed-off-by: Heiko Carstens --- arch/s390/lib/probes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/lib/probes.c b/arch/s390/lib/probes.c index 1e184a03442d46..049300b68ddd76 100644 --- a/arch/s390/lib/probes.c +++ b/arch/s390/lib/probes.c @@ -99,8 +99,8 @@ int probe_get_fixup_type(u16 *insn) break; case 0xec: switch (insn[2] & 0xff) { + case 0xe4: /* cgrb */ case 0xe5: /* clgrb */ - case 0xe6: /* cgrb */ case 0xf6: /* crb */ case 0xf7: /* clrb */ case 0xfc: /* cgib */ From ebac476520afb06028c5e539f47badb2415cd85f Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 10:36:28 +0200 Subject: [PATCH 1965/2286] s390/probes: Add missing fixup for bctgr and bic For the bctgr and bic instructions the probes fixup is missing. For both instructions the instruction address must only be fixed up if the branch is not taken. If the branch is taken the instruction address is already correct. Return FIXUP_BRANCH_NOT_TAKEN instead of the default FIXUP_PSW_NORMAL to reflect that. Signed-off-by: Heiko Carstens --- arch/s390/lib/probes.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/arch/s390/lib/probes.c b/arch/s390/lib/probes.c index 049300b68ddd76..3e7654223b05af 100644 --- a/arch/s390/lib/probes.c +++ b/arch/s390/lib/probes.c @@ -77,6 +77,10 @@ int probe_get_fixup_type(u16 *insn) if ((insn[0] & 0xff) == 0xb2) fixup = FIXUP_NOT_REQUIRED; break; + case 0xb9: /* bctgr */ + if ((insn[0] & 0xff) == 0x46) + fixup = FIXUP_BRANCH_NOT_TAKEN; + break; case 0xa7: /* bras */ if ((insn[0] & 0x0f) == 0x05) fixup |= FIXUP_RETURN_REGISTER; @@ -85,6 +89,14 @@ int probe_get_fixup_type(u16 *insn) if ((insn[0] & 0x0f) == 0x05) /* brasl */ fixup |= FIXUP_RETURN_REGISTER; break; + case 0xe3: + switch (insn[2] & 0xff) { + case 0x46: /* bctg */ + case 0x47: /* bic */ + fixup = FIXUP_BRANCH_NOT_TAKEN; + break; + } + break; case 0xeb: switch (insn[2] & 0xff) { case 0x44: /* bxhg */ @@ -93,10 +105,6 @@ int probe_get_fixup_type(u16 *insn) break; } break; - case 0xe3: /* bctg */ - if ((insn[2] & 0xff) == 0x46) - fixup = FIXUP_BRANCH_NOT_TAKEN; - break; case 0xec: switch (insn[2] & 0xff) { case 0xe4: /* cgrb */ From 450c172f0e59c58a2e623974dae69cda51493720 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Mon, 12 Oct 2026 10:36:29 +0200 Subject: [PATCH 1966/2286] s390/probes: Fix wrong mnemonic comment for clgib There is no cglib instruction; the correct mnemonic is clgib. Fix the comment. Fixes: 6b169ac9b434 ("s390/kprobes: add support for compare and branch instructions") Signed-off-by: Heiko Carstens --- arch/s390/lib/probes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/s390/lib/probes.c b/arch/s390/lib/probes.c index 3e7654223b05af..66221ec1682270 100644 --- a/arch/s390/lib/probes.c +++ b/arch/s390/lib/probes.c @@ -112,7 +112,7 @@ int probe_get_fixup_type(u16 *insn) case 0xf6: /* crb */ case 0xf7: /* clrb */ case 0xfc: /* cgib */ - case 0xfd: /* cglib */ + case 0xfd: /* clgib */ case 0xfe: /* cib */ case 0xff: /* clib */ fixup = FIXUP_BRANCH_NOT_TAKEN; From adc9e872f7350e3ac29c1818d8c68b0760b4ba23 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 13 Oct 2026 15:34:52 +0200 Subject: [PATCH 1967/2286] s390/signal: Fix incorrect return address in signal frame exit_to_user_mode_loop() can call arch_do_signal_or_restart() multiple times when signals are pending. If the first call set up a vDSO system call restart by saving the real user return address and adjusting psw.addr so it points to the vDSO stub, a second call builds the signal frame with the wrong return address. Restore psw.addr from arch_data, and set the return code to -EINTR before building the signal frame. This requires restart_block.arch_data to be properly initialized for all tasks. Move the initialization in copy_thread() in front of the kernel thread check, so that it also covers kernel threads and their children (PID 1, usermode helpers). Only PID 0 is left uninitialized, since it never returns to user space. Fixes: df29a7440c4b ("s390/signal: switch to using vdso for sigreturn and syscall restart") Cc: stable@vger.kernel.org Signed-off-by: Heiko Carstens --- arch/s390/kernel/process.c | 12 ++++++------ arch/s390/kernel/signal.c | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arch/s390/kernel/process.c b/arch/s390/kernel/process.c index 4c73d2528fcb22..3092ea0347a1cc 100644 --- a/arch/s390/kernel/process.c +++ b/arch/s390/kernel/process.c @@ -130,6 +130,12 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) p->thread.hardirq_timer = 0; p->thread.softirq_timer = 0; p->thread.last_break = 1; + /* + * s390 stores the svc return address in arch_data when calling + * sigreturn()/restart_syscall() via vdso. 1 means no valid address + * stored. + */ + p->restart_block.arch_data = 1; frame->sf.back_chain = 0; frame->sf.gprs[11 - 6] = (unsigned long)&frame->childregs; @@ -167,12 +173,6 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) p->thread.acrs[0] = (unsigned int)(tls >> 32); p->thread.acrs[1] = (unsigned int)tls; } - /* - * s390 stores the svc return address in arch_data when calling - * sigreturn()/restart_syscall() via vdso. 1 means no valid address - * stored. - */ - p->restart_block.arch_data = 1; return 0; } diff --git a/arch/s390/kernel/signal.c b/arch/s390/kernel/signal.c index 4874de5edea0a7..b59b0c49a1aaac 100644 --- a/arch/s390/kernel/signal.c +++ b/arch/s390/kernel/signal.c @@ -475,7 +475,11 @@ void arch_do_signal_or_restart(struct pt_regs *regs) } /* No longer in a system call */ clear_pt_regs_flag(regs, PIF_SYSCALL); - + if (!(current->restart_block.arch_data & 1)) { + regs->psw.addr = current->restart_block.arch_data; + current->restart_block.arch_data = 1; + regs->gprs[2] = -EINTR; + } rseq_signal_deliver(&ksig, regs); handle_signal(&ksig, oldset, regs); return; From 3e30b96fab780110f62156d5a8cd5240152b3c59 Mon Sep 17 00:00:00 2001 From: Heiko Carstens Date: Tue, 13 Oct 2026 15:34:53 +0200 Subject: [PATCH 1968/2286] s390/signal: Cleanup coding style Only whitespace, comment, indentation and other trivial changes to adopt signal.c to current coding style. No functional change. Reviewed-by: Ilya Leoshkevich Signed-off-by: Heiko Carstens --- arch/s390/kernel/signal.c | 224 +++++++++++++++----------------------- 1 file changed, 90 insertions(+), 134 deletions(-) diff --git a/arch/s390/kernel/signal.c b/arch/s390/kernel/signal.c index b59b0c49a1aaac..deeef053758c38 100644 --- a/arch/s390/kernel/signal.c +++ b/arch/s390/kernel/signal.c @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -61,8 +61,7 @@ * have a fixed position and moves if gprs_high or vxrs exist. * Future extensions will be added to _sigregs_ext. */ -struct sigframe -{ +struct sigframe { __u8 callee_used_stack[__SIGNAL_FRAMESIZE]; struct sigcontext sc; _sigregs sregs; @@ -96,22 +95,21 @@ struct sigframe * ----------------------------------------- * Future extensions will be added to _sigregs_ext. */ -struct rt_sigframe -{ +struct rt_sigframe { __u8 callee_used_stack[__SIGNAL_FRAMESIZE]; __u16 svc_insn; struct siginfo info; struct ucontext_extended uc; }; -/* Store registers needed to create the signal frame */ +/* Store registers needed to create the signal frame. */ static void store_sigregs(void) { save_access_regs(current->thread.acrs); save_user_fpu_regs(); } -/* Load registers after signal return */ +/* Load registers after signal return. */ static void load_sigregs(void) { restore_access_regs(current->thread.acrs); @@ -122,14 +120,14 @@ static int save_sigregs(struct pt_regs *regs, _sigregs __user *sregs) { _sigregs user_sregs; - /* Copy a 'clean' PSW mask to the user to avoid leaking - information about whether PER is currently on. */ - user_sregs.regs.psw.mask = PSW_USER_BITS | - (regs->psw.mask & (PSW_MASK_USER | PSW_MASK_RI)); + /* + * Copy a 'clean' PSW mask to the user to avoid leaking + * information about whether PER is currently on. + */ + user_sregs.regs.psw.mask = PSW_USER_BITS | (regs->psw.mask & (PSW_MASK_USER | PSW_MASK_RI)); user_sregs.regs.psw.addr = regs->psw.addr; memcpy(&user_sregs.regs.gprs, ®s->gprs, sizeof(sregs->regs.gprs)); - memcpy(&user_sregs.regs.acrs, current->thread.acrs, - sizeof(user_sregs.regs.acrs)); + memcpy(&user_sregs.regs.acrs, current->thread.acrs, sizeof(user_sregs.regs.acrs)); fpregs_store(&user_sregs.fpregs, ¤t->thread.ufpu); if (__copy_to_user(sregs, &user_sregs, sizeof(_sigregs))) return -EFAULT; @@ -140,44 +138,37 @@ static int restore_sigregs(struct pt_regs *regs, _sigregs __user *sregs) { _sigregs user_sregs; - /* Always make any pending restarted system call return -EINTR */ + /* Always make any pending restarted system call return -EINTR. */ current->restart_block.fn = do_no_restart_syscall; - if (__copy_from_user(&user_sregs, sregs, sizeof(user_sregs))) return -EFAULT; - if (!is_ri_task(current) && (user_sregs.regs.psw.mask & PSW_MASK_RI)) return -EINVAL; - /* Use regs->psw.mask instead of PSW_USER_BITS to preserve PER bit. */ - regs->psw.mask = (regs->psw.mask & ~(PSW_MASK_USER | PSW_MASK_RI)) | - (user_sregs.regs.psw.mask & (PSW_MASK_USER | PSW_MASK_RI)); + regs->psw.mask &= ~(PSW_MASK_USER | PSW_MASK_RI); + regs->psw.mask |= user_sregs.regs.psw.mask & (PSW_MASK_USER | PSW_MASK_RI); /* Check for invalid user address space control. */ if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_HOME) - regs->psw.mask = PSW_ASC_PRIMARY | - (regs->psw.mask & ~PSW_MASK_ASC); - /* Check for invalid amode */ + regs->psw.mask = PSW_ASC_PRIMARY | (regs->psw.mask & ~PSW_MASK_ASC); + /* Check for invalid amode. */ if (regs->psw.mask & PSW_MASK_EA) regs->psw.mask |= PSW_MASK_BA; regs->psw.addr = user_sregs.regs.psw.addr; memcpy(®s->gprs, &user_sregs.regs.gprs, sizeof(sregs->regs.gprs)); - memcpy(¤t->thread.acrs, &user_sregs.regs.acrs, - sizeof(current->thread.acrs)); - + memcpy(¤t->thread.acrs, &user_sregs.regs.acrs, sizeof(current->thread.acrs)); fpregs_load(&user_sregs.fpregs, ¤t->thread.ufpu); - - clear_pt_regs_flag(regs, PIF_SYSCALL); /* No longer in a system call */ + /* No longer in a system call. */ + clear_pt_regs_flag(regs, PIF_SYSCALL); return 0; } /* Returns non-zero on fault. */ -static int save_sigregs_ext(struct pt_regs *regs, - _sigregs_ext __user *sregs_ext) +static int save_sigregs_ext(struct pt_regs *regs, _sigregs_ext __user *sregs_ext) { __u64 vxrs[__NUM_VXRS_LOW]; int i; - /* Save vector registers to signal stack */ + /* Save vector registers to signal stack. */ if (cpu_has_vx()) { for (i = 0; i < __NUM_VXRS_LOW; i++) vxrs[i] = current->thread.ufpu.vxrs[i].low; @@ -191,13 +182,12 @@ static int save_sigregs_ext(struct pt_regs *regs, return 0; } -static int restore_sigregs_ext(struct pt_regs *regs, - _sigregs_ext __user *sregs_ext) +static int restore_sigregs_ext(struct pt_regs *regs, _sigregs_ext __user *sregs_ext) { __u64 vxrs[__NUM_VXRS_LOW]; int i; - /* Restore vector registers from signal stack */ + /* Restore vector registers from signal stack. */ if (cpu_has_vx()) { if (__copy_from_user(vxrs, &sregs_ext->vxrs_low, sizeof(sregs_ext->vxrs_low)) || @@ -213,11 +203,12 @@ static int restore_sigregs_ext(struct pt_regs *regs, SYSCALL_DEFINE0(sigreturn) { - struct pt_regs *regs = task_pt_regs(current); - struct sigframe __user *frame = - (struct sigframe __user *) regs->gprs[15]; + struct sigframe __user *frame; + struct pt_regs *regs; sigset_t set; + regs = task_pt_regs(current); + frame = (struct sigframe __user *)regs->gprs[15]; if (__copy_from_user(&set.sig, &frame->sc.oldmask, _SIGMASK_COPY_SIZE)) goto badframe; set_current_blocked(&set); @@ -235,11 +226,12 @@ SYSCALL_DEFINE0(sigreturn) SYSCALL_DEFINE0(rt_sigreturn) { - struct pt_regs *regs = task_pt_regs(current); - struct rt_sigframe __user *frame = - (struct rt_sigframe __user *)regs->gprs[15]; + struct rt_sigframe __user *frame; + struct pt_regs *regs; sigset_t set; + regs = task_pt_regs(current); + frame = (struct rt_sigframe __user *)regs->gprs[15]; if (__copy_from_user(&set.sig, &frame->uc.uc_sigmask, sizeof(set))) goto badframe; set_current_blocked(&set); @@ -257,94 +249,76 @@ SYSCALL_DEFINE0(rt_sigreturn) return 0; } -/* - * Determine which stack to use.. - */ -static inline void __user * -get_sigframe(struct k_sigaction *ka, struct pt_regs * regs, size_t frame_size) +/* Determine which stack to use. */ +static inline void __user *get_sigframe(struct k_sigaction *ka, struct pt_regs *regs, size_t frame_size) { unsigned long sp; - /* Default to using normal stack */ + /* Default to using normal stack. */ sp = regs->gprs[15]; - /* Overflow on alternate signal stack gives SIGSEGV. */ if (on_sig_stack(sp) && !on_sig_stack((sp - frame_size) & -8UL)) - return (void __user *) -1UL; - - /* This is the X/Open sanctioned signal stack switching. */ + return (void __user *)-1UL; + /* This is the X/Open sanctioned signal stack switching. */ if (ka->sa.sa_flags & SA_ONSTACK) { - if (! sas_ss_flags(sp)) + if (!sas_ss_flags(sp)) sp = current->sas_ss_sp + current->sas_ss_size; } - - return (void __user *)((sp - frame_size) & -8ul); + return (void __user *)((sp - frame_size) & -8UL); } -static int setup_frame(int sig, struct k_sigaction *ka, - sigset_t *set, struct pt_regs * regs) +static int setup_frame(int sig, struct k_sigaction *ka, sigset_t *set, struct pt_regs *regs) { struct sigframe __user *frame; - struct sigcontext sc; unsigned long restorer; + struct sigcontext sc; size_t frame_size; frame_size = sizeof(*frame) - sizeof(frame->sregs_ext); if (cpu_has_vx()) frame_size += sizeof(frame->sregs_ext); frame = get_sigframe(ka, regs, frame_size); - if (frame == (void __user *) -1UL) + if (frame == (void __user *)-1UL) return -EFAULT; - /* Set up backchain. */ - if (__put_user(regs->gprs[15], (addr_t __user *) frame)) + if (__put_user(regs->gprs[15], (addr_t __user *)frame)) return -EFAULT; - - /* Create struct sigcontext on the signal stack */ + /* Create struct sigcontext on the signal stack. */ memcpy(&sc.oldmask, &set->sig, _SIGMASK_COPY_SIZE); - sc.sregs = (_sigregs __user __force *) &frame->sregs; + sc.sregs = (_sigregs __user __force *)&frame->sregs; if (__copy_to_user(&frame->sc, &sc, sizeof(frame->sc))) return -EFAULT; - - /* Store registers needed to create the signal frame */ + /* Store registers needed to create the signal frame. */ store_sigregs(); - - /* Create _sigregs on the signal stack */ + /* Create _sigregs on the signal stack. */ if (save_sigregs(regs, &frame->sregs)) return -EFAULT; - - /* Place signal number on stack to allow backtrace from handler. */ - if (__put_user(regs->gprs[2], (int __user *) &frame->signo)) + /* Place signal number on stack to allow backtrace from handler. */ + if (__put_user(regs->gprs[2], (int __user *)&frame->signo)) return -EFAULT; - - /* Create _sigregs_ext on the signal stack */ + /* Create _sigregs_ext on the signal stack. */ if (save_sigregs_ext(regs, &frame->sregs_ext)) return -EFAULT; - - /* Set up to return from userspace. If provided, use a stub - already in userspace. */ + /* + * Set up to return from userspace. If provided, use a stub + * already in userspace. + */ if (ka->sa.sa_flags & SA_RESTORER) - restorer = (unsigned long) ka->sa.sa_restorer; + restorer = (unsigned long)ka->sa.sa_restorer; else restorer = VDSO_SYMBOL(current, sigreturn); - - /* Set up registers for signal handler */ + /* Set up registers for signal handler. */ regs->gprs[14] = restorer; - regs->gprs[15] = (unsigned long) frame; + regs->gprs[15] = (unsigned long)frame; /* Force default amode and default user address space control. */ - regs->psw.mask = PSW_MASK_EA | PSW_MASK_BA | - (PSW_USER_BITS & PSW_MASK_ASC) | - (regs->psw.mask & ~PSW_MASK_ASC); - regs->psw.addr = (unsigned long) ka->sa.sa_handler; - + regs->psw.mask &= ~PSW_MASK_ASC; + regs->psw.mask |= (PSW_USER_BITS & PSW_MASK_ASC) | PSW_MASK_EA | PSW_MASK_BA; + regs->psw.addr = (unsigned long)ka->sa.sa_handler; regs->gprs[2] = sig; - regs->gprs[3] = (unsigned long) &frame->sc; - - /* We forgot to include these in the sigcontext. - To avoid breaking binary compatibility, they are passed as args. */ - if (sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || - sig == SIGTRAP || sig == SIGFPE) { - /* set extra registers only for synchronous signals */ + regs->gprs[3] = (unsigned long)&frame->sc; + /* Missing in sigcontext. Keep binary compatibility by passing as args. */ + if (sig == SIGSEGV || sig == SIGBUS || sig == SIGILL || sig == SIGTRAP || sig == SIGFPE) { + /* Set extra registers only for synchronous signals. */ regs->gprs[4] = regs->int_code & 127; regs->gprs[5] = regs->int_parm_long; regs->gprs[6] = current->thread.last_break; @@ -352,8 +326,7 @@ static int setup_frame(int sig, struct k_sigaction *ka, return 0; } -static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, - struct pt_regs *regs) +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, struct pt_regs *regs) { struct rt_sigframe __user *frame; unsigned long uc_flags, restorer; @@ -366,27 +339,24 @@ static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, uc_flags |= UC_VXRS; } frame = get_sigframe(&ksig->ka, regs, frame_size); - if (frame == (void __user *) -1UL) + if (frame == (void __user *)-1UL) return -EFAULT; - /* Set up backchain. */ - if (__put_user(regs->gprs[15], (addr_t __user *) frame)) + if (__put_user(regs->gprs[15], (addr_t __user *)frame)) return -EFAULT; - - /* Set up to return from userspace. If provided, use a stub - already in userspace. */ + /* + * Set up to return from userspace. If provided, use a stub + * already in userspace. + */ if (ksig->ka.sa.sa_flags & SA_RESTORER) - restorer = (unsigned long) ksig->ka.sa.sa_restorer; + restorer = (unsigned long)ksig->ka.sa.sa_restorer; else restorer = VDSO_SYMBOL(current, rt_sigreturn); - - /* Create siginfo on the signal stack */ + /* Create siginfo on the signal stack. */ if (copy_siginfo_to_user(&frame->info, &ksig->info)) return -EFAULT; - - /* Store registers needed to create the signal frame */ + /* Store registers needed to create the signal frame. */ store_sigregs(); - /* Create ucontext on the signal stack. */ if (__put_user(uc_flags, &frame->uc.uc_flags) || __put_user(NULL, &frame->uc.uc_link) || @@ -395,34 +365,29 @@ static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)) || save_sigregs_ext(regs, &frame->uc.uc_mcontext_ext)) return -EFAULT; - - /* Set up registers for signal handler */ + /* Set up registers for signal handler. */ regs->gprs[14] = restorer; - regs->gprs[15] = (unsigned long) frame; + regs->gprs[15] = (unsigned long)frame; /* Force default amode and default user address space control. */ - regs->psw.mask = PSW_MASK_EA | PSW_MASK_BA | - (PSW_USER_BITS & PSW_MASK_ASC) | - (regs->psw.mask & ~PSW_MASK_ASC); - regs->psw.addr = (unsigned long) ksig->ka.sa.sa_handler; - + regs->psw.mask &= ~PSW_MASK_ASC; + regs->psw.mask |= (PSW_USER_BITS & PSW_MASK_ASC) | PSW_MASK_EA | PSW_MASK_BA; + regs->psw.addr = (unsigned long)ksig->ka.sa.sa_handler; regs->gprs[2] = ksig->sig; - regs->gprs[3] = (unsigned long) &frame->info; - regs->gprs[4] = (unsigned long) &frame->uc; + regs->gprs[3] = (unsigned long)&frame->info; + regs->gprs[4] = (unsigned long)&frame->uc; regs->gprs[5] = current->thread.last_break; return 0; } -static void handle_signal(struct ksignal *ksig, sigset_t *oldset, - struct pt_regs *regs) +static void handle_signal(struct ksignal *ksig, sigset_t *oldset, struct pt_regs *regs) { int ret; - /* Set up the stack frame */ + /* Set up the stack frame. */ if (ksig->ka.sa.sa_flags & SA_SIGINFO) ret = setup_rt_frame(ksig, oldset, regs); else ret = setup_frame(ksig->sig, &ksig->ka, oldset, regs); - signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLE_STEP)); } @@ -435,22 +400,19 @@ static void handle_signal(struct ksignal *ksig, sigset_t *oldset, * the kernel can handle, and then we build all the user-level signal handling * stack-frames in one go after that. */ - void arch_do_signal_or_restart(struct pt_regs *regs) { - struct ksignal ksig; sigset_t *oldset = sigmask_to_save(); + struct ksignal ksig; /* * Get signal to deliver. When running under ptrace, at this point * the debugger may change all our registers, including the system * call information. */ - current->thread.system_call = - test_pt_regs_flag(regs, PIF_SYSCALL) ? regs->int_code : 0; - + current->thread.system_call = test_pt_regs_flag(regs, PIF_SYSCALL) ? regs->int_code : 0; if (get_signal(&ksig)) { - /* Whee! Actually deliver the signal. */ + /* Whee! Actually deliver the signal. */ if (current->thread.system_call) { regs->int_code = current->thread.system_call; /* Check for system call restarting. */ @@ -467,13 +429,11 @@ void arch_do_signal_or_restart(struct pt_regs *regs) fallthrough; case -ERESTARTNOINTR: regs->gprs[2] = regs->orig_gpr2; - regs->psw.addr = - __rewind_psw(regs->psw, - regs->int_code >> 16); + regs->psw.addr = __rewind_psw(regs->psw, regs->int_code >> 16); break; } } - /* No longer in a system call */ + /* No longer in a system call. */ clear_pt_regs_flag(regs, PIF_SYSCALL); if (!(current->restart_block.arch_data & 1)) { regs->psw.addr = current->restart_block.arch_data; @@ -484,14 +444,13 @@ void arch_do_signal_or_restart(struct pt_regs *regs) handle_signal(&ksig, oldset, regs); return; } - - /* No handlers present - check for system call restart */ + /* No handlers present - check for system call restart. */ clear_pt_regs_flag(regs, PIF_SYSCALL); if (current->thread.system_call) { regs->int_code = current->thread.system_call; switch (regs->gprs[2]) { case -ERESTART_RESTARTBLOCK: - /* Restart with sys_restart_syscall */ + /* Restart with sys_restart_syscall. */ regs->gprs[2] = regs->orig_gpr2; current->restart_block.arch_data = regs->psw.addr; regs->psw.addr = VDSO_SYMBOL(current, restart_syscall); @@ -508,9 +467,6 @@ void arch_do_signal_or_restart(struct pt_regs *regs) break; } } - - /* - * If there's no signal to deliver, we just put the saved sigmask back. - */ + /* If there's no signal to deliver, put the saved sigmask back. */ restore_saved_sigmask(); } From 1e035297ffdadf2184c3c773b8c098e372324aaf Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Tue, 13 Oct 2026 08:15:01 -0400 Subject: [PATCH 1969/2286] docs: remove Assisted-by references Commit 78d979db6cef ("docs: add AI Coding Assistants documentation") asked contributors to add an Assisted-by tag to patches developed with help from an LLM, and commit 816d9992d9ed ("coding-assistants: simplify attribution") later reduced the tag to "Assisted-by: LLM". A proposal to drop the tag was turned down in July [1]. The main argument for keeping it was that maintainers wanted a signal that a patch was LLM-assisted, so that they could review it accordingly. The tag came up again at the 2026 Maintainers Summit [2]. Roughly 10% of the 18,533 non-merge commits in 7.3 through -rc7 carry an Assisted-by tag, while actual LLM use is far more widespread. Some contributors do not know about the rule, others leave the tag off on purpose, and few maintainers ask about it. As a result, the tag does not provide the signal it was kept for: its absence says nothing about how a patch was made, and for developers who use these tools for almost everything, its presence says little more. The choice was between finding a way to enforce the requirement and dropping it, and there was a strong, though not unanimous, consensus in the room to drop it. Greg Kroah-Hartman, who argued for keeping the tag in July, supports removing it now [3]. Maintainers do still want to know how a patch was created, but the view at the Summit was that this belongs in the cover letter, where it can describe how the tools were used, rather than in the changelog. Documentation/process/generated-content.rst already asks contributors to be transparent about tool use and is not changed here. The rules on the DCO and on the submitter's responsibility for the patch stay as they are. Remove the Assisted-by sections from coding-assistants.rst, submitting-patches.rst, and the Italian, Brazilian Portuguese and Traditional Chinese translations, and drop the handling of the tag from checkpatch. [1] https://lore.kernel.org/workflows/2026070224-unholy-commode-cf45@gregkh/ [2] https://lwn.net/Articles/1099594/ [3] https://lore.kernel.org/all/2026101327-cupid-legal-6cf6@gregkh/ Acked-by: Lorenzo Stoakes (ARM) Reviewed-by: Kees Cook Reviewed-by: Greg Kroah-Hartman Signed-off-by: Sasha Levin Reviewed-by: Jeff Layton Reviewed-by: Jori Koolstra Acked-by: Krzysztof Kozlowski Acked-by: SJ Park Acked-by: David Hildenbrand (Arm) Acked-by: Mark Brown Acked-by: Daniel Pereira Acked-by: Shuah Khan Acked-by: Weijie Yuan Reviewed-by: Miguel Ojeda Signed-off-by: Jonathan Corbet Message-ID: <20261013121501.3738437-1-sashal@kernel.org> --- Documentation/process/coding-assistants.rst | 21 +------------------ Documentation/process/submitting-patches.rst | 10 --------- .../it_IT/process/submitting-patches.rst | 10 --------- .../pt_BR/process/coding-assistants.rst | 20 ------------------ .../pt_BR/process/submitting-patches.rst | 10 --------- .../zh_TW/process/submitting-patches.rst | 7 ------- scripts/checkpatch.pl | 11 ---------- 7 files changed, 1 insertion(+), 88 deletions(-) diff --git a/Documentation/process/coding-assistants.rst b/Documentation/process/coding-assistants.rst index 051f0d819f68e4..509dbf23a099bd 100644 --- a/Documentation/process/coding-assistants.rst +++ b/Documentation/process/coding-assistants.rst @@ -40,24 +40,6 @@ is responsible for: * Adding their own Signed-off-by tag to certify the DCO * Taking full responsibility for the contribution -Attribution -=========== - -When AI tools contribute to kernel development, proper attribution -helps track the evolving role of AI in the development process. -Contributions should include an Assisted-by tag in the following format:: - - Assisted-by: LLM [TOOL1] [TOOL2] - -* ``[TOOL1] [TOOL2]`` are optional specialized analysis tools used - (e.g., coccinelle, sparse, smatch, clang-tidy) - -Basic development tools (git, gcc, make, editors) should not be listed. - -Example:: - - Assisted-by: LLM coccinelle sparse - Procedure for finding and fixing bugs ===================================== @@ -82,8 +64,7 @@ these steps: another one. The fix must not add build warnings and must pass the checkpatch.pl checks (see submitting-patches.rst). 6. Commit the working fix with a detailed message describing the problem, the - solution and a Fixes tag. Do not add a Signed-off-by tag, and add an - Assisted-by tag, as described above. + solution and a Fixes tag. Do not add a Signed-off-by tag. 7. Identify the maintainers and lists using scripts/get_maintainer.pl. Documentation/process/security-bugs.rst shows how to do that. 8. Indicate what could not be done. If the fix could not be built or tested, or diff --git a/Documentation/process/submitting-patches.rst b/Documentation/process/submitting-patches.rst index 4c28a239e8cb58..f271d7413f72cf 100644 --- a/Documentation/process/submitting-patches.rst +++ b/Documentation/process/submitting-patches.rst @@ -632,16 +632,6 @@ bugzilla.kernel.org is a public place in this sense, but email addresses used there are private; so do not expose them in tags, unless the person used them in earlier contributions. -Using Assisted-by: ------------------- - -If you used any sort of advanced coding tool in the creation of your patch, -you need to acknowledge that use by adding an Assisted-by tag. Failure to -do so may impede the acceptance of your work. Please see -Documentation/process/coding-assistants.rst for details regarding the -acknowledgment of coding assistants. - - .. _the_canonical_patch_format: The canonical patch format diff --git a/Documentation/translations/it_IT/process/submitting-patches.rst b/Documentation/translations/it_IT/process/submitting-patches.rst index a742610cb0709f..d0e0d6c9ecc2e5 100644 --- a/Documentation/translations/it_IT/process/submitting-patches.rst +++ b/Documentation/translations/it_IT/process/submitting-patches.rst @@ -691,16 +691,6 @@ un luogo pubblico, ma gli indirizzi email usati lì sono privati; quindi non esponeteli nelle etichette, a meno che la persona non li abbia già usati in contributi precedenti. -Usare Assisted-by: -------------------- - -Se avete usato un qualsiasi tipo di strumento di assistenza avanzata alla -scrittura del codice per la creazione della vostra patch, dovete darne atto -aggiungendo un'etichetta Assisted-by. Non farlo può ostacolare -l'accettazione del vostro lavoro. Fate riferimento a -Documentation/process/coding-assistants.rst per i dettagli su come dare -atto dell'uso di assistenti alla scrittura del codice. - .. _it_the_canonical_patch_format: Il formato canonico delle patch diff --git a/Documentation/translations/pt_BR/process/coding-assistants.rst b/Documentation/translations/pt_BR/process/coding-assistants.rst index 2055af34a90819..f6062b22a8fd80 100644 --- a/Documentation/translations/pt_BR/process/coding-assistants.rst +++ b/Documentation/translations/pt_BR/process/coding-assistants.rst @@ -38,23 +38,3 @@ A pessoa que envia é responsável por: * Garantir conformidade com os requisitos de licença * Acrescentar sua própria tag Signed-off-by para certificar o DCO * Assumir toda responsabilidade pela contribuição - -Atribuições -=========== - -Quando ferramentas de IA contribuírem para o desenvolvimento do kernel, -a atribuição adequada ajuda a rastrear a função da IA no processo de -desenvolvimento. Contribuições devem incluir a tag de assistência seguindo este -formato:: - - Assisted-by: LLM [FERRAMENTA1] [FERRAMENTA2] - -* ``[FERRAMENTA1] [FERRAMENTA2]`` são ferramentas de análise especializada - opcionais (Exemplo: coccinelle, sparse, smatch, clang-tidy) - -Ferramentas básicas de desenvolvimento (git, gcc, make, editors) não são -listadas. - -Exemplo:: - - Assisted-by: LLM coccinelle sparse \ No newline at end of file diff --git a/Documentation/translations/pt_BR/process/submitting-patches.rst b/Documentation/translations/pt_BR/process/submitting-patches.rst index aad926c17fb454..4d1e75935745bf 100644 --- a/Documentation/translations/pt_BR/process/submitting-patches.rst +++ b/Documentation/translations/pt_BR/process/submitting-patches.rst @@ -650,16 +650,6 @@ bugzilla.kernel.org é um local público nesse sentido, mas os endereços de e-m usados lá são privados; portanto, não os exponha em tags, a menos que a pessoa os tenha usado em contribuições anteriores. -Usando Assisted-by: -------------------- - -Se você usou qualquer tipo de ferramenta avançada de codificação na criação do seu patch, -você precisa reconhecer esse uso adicionando uma tag Assisted-by. A falha em -fazer isso pode impedir a aceitação do seu trabalho. Por favor, veja -Documentation/process/coding-assistants.rst para detalhes sobre o -reconhecimento de assistentes de codificação. - - .. _pt_BR_the_canonical_patch_format: O formato canônico do patch diff --git a/Documentation/translations/zh_TW/process/submitting-patches.rst b/Documentation/translations/zh_TW/process/submitting-patches.rst index b780f1539b46b2..4a7c046b23894c 100644 --- a/Documentation/translations/zh_TW/process/submitting-patches.rst +++ b/Documentation/translations/zh_TW/process/submitting-patches.rst @@ -503,13 +503,6 @@ Fixes: 指示補丁修復了之前提交中的一個缺陷。它可以便於確 的。注意,就此而言bugzilla.kernel.org是公開場所,但其中使用的電子郵件地址 是私密的;因此不要在標籤中暴露它們,除非該人在先前的貢獻中使用過。 -使用Assisted-by: ----------------- - -如果您在建立補丁的過程中使用了任何進階編碼工具,您需要透過添加Assisted-by -標籤來聲明這一使用。不這樣做可能會妨礙您的工作被接受。關於聲明編碼助手的 -細節,請參見 Documentation/process/coding-assistants.rst 。 - .. _tw_the_canonical_patch_format: 標準補丁格式 diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl index 8a7787d228a63d..c9840f5219c2a9 100755 --- a/scripts/checkpatch.pl +++ b/scripts/checkpatch.pl @@ -655,7 +655,6 @@ sub hash_show_words { Reviewed-by:| Reported-by:| Suggested-by:| - Assisted-by:| To:| Cc: )}; @@ -3124,16 +3123,6 @@ sub process { } } - # Assisted-by uses a free-form value (e.g. "LLM"), not an - # email address, so skip the email format checks below. - if ($sign_off =~ /^Assisted-by:/i) { - if ($email =~ /^\s*$/) { - WARN("BAD_SIGN_OFF", - "Assisted-by requires a value\n" . $herecurr); - } - next; - } - my ($email_name, $name_comment, $email_address, $comment) = parse_email($email); my $suggested_email = format_email(($email_name, $name_comment, $email_address, $comment)); if ($suggested_email eq "") { From 14acc1f60509b9baac2874e3e029edba7ec141d3 Mon Sep 17 00:00:00 2001 From: Akiyoshi Kurita Date: Wed, 7 Oct 2026 09:47:15 +0900 Subject: [PATCH 1970/2286] docs/ja_JP: translate submitting-patches.rst (review and report tags) Translate the section describing Reported-by, Tested-by, Reviewed-by, Suggested-by, and Fixes tags into Japanese. This includes the Reviewer's Statement and the guidance on adding or removing review and testing tags in subsequent patch versions. Keep the Reviewer's Statement in English and add a Japanese tentative translation in a formal style. Signed-off-by: Akiyoshi Kurita Reviewed-by: Akira Yokosawa Signed-off-by: Jonathan Corbet Message-ID: <20261007004715.107860-1-weibu@redadmin.org> --- .../ja_JP/process/submitting-patches.rst | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/Documentation/translations/ja_JP/process/submitting-patches.rst b/Documentation/translations/ja_JP/process/submitting-patches.rst index 797b70b97fa73c..cc0b348086daf1 100644 --- a/Documentation/translations/ja_JP/process/submitting-patches.rst +++ b/Documentation/translations/ja_JP/process/submitting-patches.rst @@ -573,3 +573,118 @@ Acked-by:、Cc:、Co-developed-by: を使用する場合 Signed-off-by: From Author Co-developed-by: Submitting Co-Author Signed-off-by: Submitting Co-Author + + +Reported-by:、Tested-by:、Reviewed-by:、Suggested-by:、Fixes: の使用 +--------------------------------------------------------------------- + +``Reported-by:`` タグは、バグを発見して報告した人の功績を示すものであり、 +今後も再び協力してもらうきっかけになることが期待されます。このタグは +バグを対象としています。機能追加の要望に対する功績を示すためには +使用しないでください。報告が Web 上で参照できない場合を除き、 +``Reported-by:`` タグの後には、その報告を指す ``Closes:`` タグを +付けてください。報告された問題の一部だけを修正するパッチの場合は、 +``Closes:`` の代わりに ``Link:`` タグを使用できます。なお、 +``Reported-by:`` は、名前を記載される本人の明示的な許可なしに +使用できる可能性がある三つのタグのうちの一つです。詳細については、 +後述の「人をタグ付けするには許可が必要」を参照してください。 + +``Tested-by:`` タグは、記載された人が(何らかの環境で)パッチを +正常にテストしたことを示します。このタグは、何らかのテストが +実施されたことをメンテナーに知らせ、将来のパッチでテスターを +探す手段を提供するとともに、テスターの功績を記録します。 + +一方、``Reviewed-by:`` は、パッチがレビューされ、以下の +レビューアの声明に従って受け入れ可能と判断されたことを示します:: + + Reviewer's statement of oversight + + By offering my Reviewed-by: tag, I state that: + + (a) I have carried out a technical review of this patch to + evaluate its appropriateness and readiness for inclusion into + the mainline kernel. + + (b) Any problems, concerns, or questions relating to the patch + have been communicated back to the submitter. I am satisfied + with the submitter's response to my comments. + + (c) While there may be things that could be improved with this + submission, I believe that it is, at this time, (1) a + worthwhile modification to the kernel, and (2) free of known + issues which would argue against its inclusion. + + (d) While I have reviewed the patch and believe it to be sound, I + do not (unless explicitly stated elsewhere) make any + warranties or guarantees that it will achieve its stated + purpose or function properly in any given situation. + +.. note:: + + 〖試訳〗 :: + + レビューアによる監督声明 + + Reviewed-by: タグを付けることにより、私は以下を表明する。 + + (a) このパッチがメインラインカーネルへの取り込みに適切であり、 + その準備が整っているかを評価するため、技術的なレビューを + 実施した。 + + (b) パッチに関する問題、懸念、または質問はすべて提出者に + 伝えられている。私は、自分のコメントに対する提出者の + 回答に満足している。 + + (c) この提出物には改善できる点が残っている可能性があるが、 + 現時点において、(1) カーネルに対する価値のある変更であり、 + かつ (2) その取り込みに反対する理由となる既知の問題が + 存在しないと考える。 + + (d) 私はこのパッチをレビューし、妥当であると考えているが、 + 別途明示されていない限り、そのパッチが記載された目的を + 達成すること、またはいかなる状況においても正しく動作する + ことについて、いかなる保証も行わない。 + +``Reviewed-by:`` タグは、そのパッチが重大な技術的問題を残していない、 +カーネルに対する適切な変更であるという意見を表明するものです。 +必要なレビューを行った既知の身元を持つ人であれば、関心のある +レビューアは誰でもパッチに ``Reviewed-by:`` タグを付けられます。 +このタグは、レビューアの功績を示すとともに、どの程度のレビューが +行われたかをメンテナーに知らせる役割を果たします。対象分野を理解し、 +十分なレビューを行うことで知られているレビューアが付けた +``Reviewed-by:`` タグは、通常、パッチがカーネルに取り込まれる +可能性を高めます。 + +``Tested-by:`` と ``Reviewed-by:`` の両方のタグは、メーリングリスト上で +テスターまたはレビューアから受け取った後、次のバージョンを +送信するときに、作者が該当するパッチへ追加する必要があります。 +ただし、次のバージョンでパッチが大幅に変更された場合は、これらのタグが +適用できなくなる可能性があるため、削除する必要があります。 +通常、誰かの ``Acked-by:``、``Tested-by:``、または ``Reviewed-by:`` タグを +削除した場合は、その理由をパッチの変更履歴で説明する必要があります +(``---`` 区切りの後)。 + +``Suggested-by:`` タグは、パッチのアイデアが記載された人によって +提案されたことを示し、そのアイデアに対する功績を記録します。 +アイデアを提案した人の功績をきちんと記録すれば、今後も再び協力して +もらうきっかけになることが期待されます。なお、これは、名前を +記載される本人の明示的な許可なしに使用できる可能性がある三つの +タグのうちの一つです。詳細については、後述の +「人をタグ付けするには許可が必要」を参照してください。 + +``Fixes:`` タグは、そのパッチが以前のコミットにあるバグを修正する +ことを示します。これは、問題がどこで発生したのかを容易に特定できる +ようにし、バグ修正のレビューに役立ちます。また、このタグは stable +kernel チームが、どの stable kernel バージョンに修正を適用すべきかを +判断する際にも役立ちます。パッチが修正するバグを示す方法としては、 +これが推奨されています。詳細については `変更内容を記述する`_ を +参照してください。 + +注意: ``Fixes:`` タグを付けても、stable kernel rules の手順や、 +stable 向けパッチ候補すべてで ``Cc: stable@vger.kernel.org`` を +指定する要件がなくなるわけではありません。詳細については、 +Documentation/process/stable-kernel-rules.rst を参照してください。 + +最後に、タグを付けることは歓迎され、通常は非常にありがたいものですが、 +署名者(すなわち、提出者およびメンテナー)は、提示されたタグを +適用するかどうかを自身の判断で決めることができます。 From b4f053ce51ee4b9490923dfffc80e9d5d124a01d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 3 Oct 2026 14:35:08 -0700 Subject: [PATCH 1971/2286] docs: kernel-parameters: don't mix cmdline options & build options Some kernel command line descriptions mix, mingle, or conflate an optional part of the command line with the build options that are applicable to those command line options. Specifically, a few command line options that use "KMG" to specify a memory size mix that string with build options like "X86,EARLY". These should be separate fields although they are both enclosed in square brackets, so separate the conflated fields. Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Corbet Message-ID: <20261003213508.96424-1-rdunlap@infradead.org> --- Documentation/admin-guide/kernel-parameters.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 4b715d5dd8ce27..bc15ea86426e1b 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3945,8 +3945,8 @@ Kernel parameters like Grub2, otherwise '$' and the following number will be eaten. - memmap=nn[KMG]!ss[KMG,EARLY] - [KNL,X86] Mark specific memory as protected. + memmap=nn[KMG]!ss[KMG] + [KNL,X86,EARLY] Mark specific memory as protected. Region of memory to be used, from ss to ss+nn. The memory region may be marked as e820 type 12 (0xc) and is NVDIMM or ADR memory. @@ -4236,11 +4236,11 @@ Kernel parameters Enable printing debug information related to MTRR registers at boot time. - mtrr_chunk_size=nn[KMG,X86,EARLY] + mtrr_chunk_size=nn[KMG] [X86,EARLY] used for mtrr cleanup. It is largest continuous chunk that could hold holes aka. UC entries. - mtrr_gran_size=nn[KMG,X86,EARLY] + mtrr_gran_size=nn[KMG] [X86,EARLY] Used for mtrr cleanup. It is granularity of mtrr block. Default is 1. Large value could prevent small alignment from From 3179c85ef46e9a1e0e47f22074cbfbc90b554fe9 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 3 Oct 2026 14:34:57 -0700 Subject: [PATCH 1972/2286] docs: kernel-parameters: adjust reserve_mem build option "[RAM]" means that the command line option is applicable when "RAM disk support is enabled." However, the reserve_mem command line option is not related to RAM disk support, so modify its build option relevance to "[MM]" instead. Signed-off-by: Randy Dunlap Acked-by: Mike Rapoport (Microsoft) Acked-by: SJ Park Signed-off-by: Jonathan Corbet Message-ID: <20261003213457.96408-1-rdunlap@infradead.org> --- Documentation/admin-guide/kernel-parameters.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index bc15ea86426e1b..f5e87ddce3e867 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -6572,7 +6572,7 @@ Kernel parameters them. If is less than 0x10000, the region is assumed to be I/O ports; otherwise it is memory. - reserve_mem= [RAM] + reserve_mem= [MM] Format: nn[KMG]::

Commits whose hunk context moved (own +/- lines identical)" + echo + while read -r c d; do + echo "- \`$(oneline "$c")\` → \`$(git rev-parse --short "$d")\`" + done <"$work/moved" + echo + echo "
" +fi +if [ -s "$work/changed" ]; then + echo + echo "
Commits lost, added, or whose own +/- lines changed" + echo + echo "| Before | After |" + echo "| --- | --- |" + while read -r c d; do + echo "| \`$(oneline "$c")\` | \`$(oneline "$d")\` |" + done <"$work/changed" + echo + echo "
" +fi + +if ! cmp -s "$work/rebase.lines" "$work/upstream.lines" || + ! cmp -s "$work/rebase.files" "$work/upstream.files"; then + echo + echo "
Drift (changed lines: upstream delta vs. actual change)" + echo + echo '```diff' + { + diff -u --label upstream-files --label actual-files \ + "$work/upstream.files" "$work/rebase.files" || true + diff -u --label upstream-lines --label actual-lines \ + "$work/upstream.lines" "$work/rebase.lines" || true + } | head -n 400 + echo '```' + echo + echo "
" +fi + +exit "$status" diff --git a/.github/workflows/automation-selftest.yml b/.github/workflows/automation-selftest.yml new file mode 100644 index 00000000000000..fc7305654ec509 --- /dev/null +++ b/.github/workflows/automation-selftest.yml @@ -0,0 +1,60 @@ +name: Automation self-test + +# Tests the scripts the nightly rebase relies on to decide whether to +# force-push edera/mainline, so a change that weakens either must not land +# quietly: +# +# - verify-upstream-rebase.sh: honest replays pass, including one that +# flattens a merged series; edited, dropped, added and merged commits and +# a stale base are all refused; +# - report-needs-decision.sh: any question in Claude's report, or anything +# marked UNSURE, stops the automatic push. +# +# It runs only when the automation itself changes. + +on: + pull_request: + types: [opened, synchronize, ready_for_review] + branches: ['edera/**'] + paths: + - '.github/scripts/**' + - '.github/workflows/upstream-rebase.yml' + - '.github/workflows/kernel-nightly.yml' + - '.github/workflows/rebase-land.yml' + - '.github/workflows/automation-selftest.yml' + - '.automation/**' + +permissions: + contents: read + +concurrency: + group: automation-selftest-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + selftest: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + # Only the automation is needed, not the kernel. + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + persist-credentials: false + sparse-checkout: | + .github + .automation + + - name: Rebase checker tests + run: bash .github/scripts/tests/verify-upstream-rebase.test.sh + + - name: Decision detector tests + run: bash .github/scripts/tests/report-needs-decision.test.sh + + - name: Shellcheck + run: shellcheck .github/scripts/*.sh .github/scripts/tests/*.sh diff --git a/.github/workflows/kernel-nightly.yml b/.github/workflows/kernel-nightly.yml new file mode 100644 index 00000000000000..3e9737567feb02 --- /dev/null +++ b/.github/workflows/kernel-nightly.yml @@ -0,0 +1,58 @@ +name: Nightly kernel maintenance + +# Rebases edera/mainline onto torvalds master every night +# (upstream-rebase.yml). +# +# Claude does the rebase. Independent checks decide whether the result is +# pushed or proposed as a pull request; see upstream-rebase.yml. +# +# GitHub runs scheduled workflows only from the default branch, which is +# edera/6.18-lts. Until this file is there too, the schedule does not fire. +# +# Repository configuration this needs: +# vars.REBASE_APP_CLIENT_ID, secrets.REBASE_APP_PRIVATE_KEY +# A GitHub App installed on this repository with contents, pull requests +# and workflows write, allowed to bypass the protection rules on +# edera/mainline. GITHUB_TOKEN cannot do this job: it cannot push +# commits that touch .github/workflows/ (every rebase rewrites the ones +# that add them), and pull requests it opens do not trigger other +# workflows. +# vars.ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, +# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID +# Organization-wide workload identity federation for the Claude API. The +# rule must accept this workflow's OIDC subject (a scheduled run on +# edera/6.18-lts), which a rule written for pull_request events will +# not. + +on: + schedule: + - cron: '23 3 * * *' # 03:23 UTC nightly + workflow_dispatch: + inputs: + force: + description: Rebase even if an open rebase pull request already covers this upstream tip + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: kernel-nightly + cancel-in-progress: false + +jobs: + mainline: + uses: ./.github/workflows/upstream-rebase.yml + permissions: + contents: read + pull-requests: read + issues: write + id-token: write + with: + downstream: edera/mainline + key: mainline + upstream_url: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git + upstream_branch: master + force: ${{ inputs.force == true }} + secrets: inherit diff --git a/.github/workflows/rebase-land.yml b/.github/workflows/rebase-land.yml new file mode 100644 index 00000000000000..ce3e39ff627cd1 --- /dev/null +++ b/.github/workflows/rebase-land.yml @@ -0,0 +1,129 @@ +name: Land upstream rebase + +# Lands a nightly rebase that needed review (see upstream-rebase.yml) once a +# maintainer approves its pull request. +# +# A rebase replaces the tree rather than merging into it, so the merge button +# is the wrong tool: it would graft a second copy of the whole series onto the +# old one. Approving instead force-pushes the tree to the exact commit that +# was approved, and GitHub then marks the pull request merged. +# +# A review runs this workflow from the pull request's merge ref, which is why +# it lives on edera/mainline. +# +# What has to hold before anything is pushed: +# - the pull request is a rebase/edera-mainline/... branch from this +# repository, targeting edera/mainline; +# - the review approved the current head, not an earlier one; +# - the approver has write access; +# - the tree is still the tip the branch was made from. The branch name +# records that tip, and branch names cannot be edited on an open pull +# request, so this is not something the pull request body can change. If +# the tree moved, nothing is pushed; the next nightly run starts from the +# new tip. + +on: + pull_request_review: + types: [submitted] + +permissions: + contents: read + +concurrency: + # Shared with the nightly's publish jobs, so nothing else moves the tree + # while this runs. + group: push-${{ github.event.pull_request.base.ref }} + cancel-in-progress: false + +jobs: + land: + if: >- + github.event.review.state == 'approved' + && github.event.pull_request.head.repo.full_name == github.repository + && github.event.pull_request.base.ref == 'edera/mainline' + && startsWith(github.event.pull_request.head.ref, 'rebase/edera-mainline/') + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: App token + id: app + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.REBASE_APP_CLIENT_ID }} + private-key: ${{ secrets.REBASE_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + permission-workflows: write + + - name: Land + env: + APP_TOKEN: ${{ steps.app.outputs.token }} + JOB_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.number }} + BASE: ${{ github.event.pull_request.base.ref }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + REVIEWED_SHA: ${{ github.event.review.commit_id }} + APPROVER: ${{ github.event.review.user.login }} + run: | + set -euo pipefail + say() { + GH_TOKEN=$JOB_TOKEN gh pr comment "$PR" --repo "$GITHUB_REPOSITORY" --body "$1" + } + + if [ "$REVIEWED_SHA" != "$HEAD_SHA" ]; then + say "Not landed: the approval was for \`${REVIEWED_SHA:0:12}\`, but the head is now \`${HEAD_SHA:0:12}\`. Approve the current head to land it." + exit 1 + fi + + perm=$(GH_TOKEN=$JOB_TOKEN gh api "repos/$GITHUB_REPOSITORY/collaborators/$APPROVER/permission" -q .permission) + case $perm in + admin|maintain|write) ;; + *) + say "Not landed: @$APPROVER does not have write access, so their approval cannot land a rebase." + exit 1 ;; + esac + + # Name: rebase//--. + rest=${HEAD_REF#"rebase/${BASE//\//-}/"} + IFS=- read -r _date old12 up12 extra <<<"$rest" + if [ -n "${extra:-}" ] || ! [[ $old12 =~ ^[0-9a-f]{12}$ && $up12 =~ ^[0-9a-f]{12}$ ]]; then + say "Not landed: \`$HEAD_REF\` is not a branch the nightly automation made." + exit 1 + fi + + origin="https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + cur=$(git ls-remote "$origin" "refs/heads/$BASE" | cut -f1) + if [ "${cur:0:12}" != "$old12" ]; then + say "Not landed: \`$BASE\` has moved to \`${cur:0:12}\` since this branch was made from \`$old12\`. Tonight's run starts from the new tip; this pull request will be closed as superseded." + exit 1 + fi + + # Both commits are already on GitHub, so this needs neither the + # kernel's history nor its files, only the two commit objects to + # name in the push. + git init -q work && cd work + git fetch -q --no-tags --depth=1 --filter=tree:0 "$origin" "$HEAD_SHA" "$cur" + + # Created through the API, and before the tree moves: a push from + # this shallow clone is refused once the old tip is unreferenced. + backup="backup/${BASE//\//-}-pre-rebase-$(date -u +%Y%m%d)-$old12" + GH_TOKEN=$APP_TOKEN gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" \ + -f ref="refs/heads/$backup" -f sha="$cur" >/dev/null + + git push --force-with-lease="refs/heads/$BASE:$cur" "$origin" "$HEAD_SHA:refs/heads/$BASE" + now=$(git ls-remote "$origin" "refs/heads/$BASE" | cut -f1) + if [ "$now" != "$HEAD_SHA" ]; then + say "Push reported success but \`$BASE\` is \`${now:0:12}\`, not \`${HEAD_SHA:0:12}\`. Check the run log." + exit 1 + fi + say "Landed by @$APPROVER's approval: \`$BASE\` is now \`${HEAD_SHA:0:12}\` (was \`${cur:0:12}\`, kept as \`$backup\`)." + git push "$origin" --delete "$HEAD_REF" || true diff --git a/.github/workflows/upstream-rebase.yml b/.github/workflows/upstream-rebase.yml new file mode 100644 index 00000000000000..fdbdacbd19fa4c --- /dev/null +++ b/.github/workflows/upstream-rebase.yml @@ -0,0 +1,665 @@ +name: Upstream rebase + +# Rebases an Edera kernel integration tree onto its upstream. Called nightly +# by kernel-nightly.yml for edera/mainline, onto torvalds master. +# +# Claude does the rebase, following .automation/skills/upstream-rebase/ +# SKILL.md: it replays the series, resolves conflicts, fixes what the replay +# broke, builds, audits upstream fixes that meet the series, and writes a +# report. +# +# Nothing Claude says about its own result decides what happens to it. The +# model runs in a job that holds a read-only token and cannot push. Its branch +# leaves that job as a git bundle, and two things then judge it +# independently, with scripts taken from the commit this run started from +# (the default branch), never from the tree under test: +# +# - verify-upstream-rebase.sh proves the series sits on the upstream tip, is +# linear, lost or gained no commit, and changed the tree by exactly the +# upstream delta; +# - kernel-build.sh builds x86_64 and arm64 vmlinux with every downstream +# feature built in. +# +# If both pass and Claude's report asks for no decision +# (report-needs-decision.sh), the tree is force-pushed to the result, pinned +# by lease to the tip the rebase started from, with the old tip kept as a +# backup branch. Otherwise, or if that push is refused, the result goes up as +# a rebase//... branch with a pull request carrying Claude's report; +# approving that pull request lands it (rebase-land.yml). A night where +# Claude could not produce a branch at all opens an issue instead. +# +# Nights with nothing new upstream stop in the first job and never start the +# model. +# +# Every job clones with --filter=blob:none: the kernel's full history with +# blobs is several gigabytes, and the jobs need the commit graph but only the +# contents of the files the series and the upstream delta touch. + +on: + workflow_call: + inputs: + downstream: + description: The tree to rebase, e.g. edera/mainline + type: string + required: true + key: + description: A short name for the tree, unique per run, for artifact names + type: string + required: true + upstream_url: + type: string + required: true + upstream_branch: + type: string + required: true + force: + description: Rebase even if an open rebase pull request already covers this upstream tip + type: boolean + default: false + +permissions: + contents: read + +env: + DOWNSTREAM: ${{ inputs.downstream }} + UPSTREAM_URL: ${{ inputs.upstream_url }} + UPSTREAM_BRANCH: ${{ inputs.upstream_branch }} + +jobs: + prepare: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + pull-requests: read + outputs: + run: ${{ steps.plan.outputs.run }} + old: ${{ steps.plan.outputs.old }} + up: ${{ steps.plan.outputs.up }} + prefix: ${{ steps.plan.outputs.prefix }} + candidate: ${{ steps.plan.outputs.candidate }} + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ env.DOWNSTREAM }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + - name: Decide whether tonight has work + id: plan + env: + GH_TOKEN: ${{ github.token }} + FORCE: ${{ inputs.force }} + run: | + set -euo pipefail + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + old=$(git rev-parse HEAD) + up=$(git rev-parse FETCH_HEAD) + prefix="rebase/${DOWNSTREAM//\//-}/" + candidate="${prefix}$(date -u +%Y%m%d)-${old:0:12}-${up:0:12}" + { + echo "old=$old" + echo "up=$up" + echo "prefix=$prefix" + echo "candidate=$candidate" + } >> "$GITHUB_OUTPUT" + + skip() { + echo "run=false" >> "$GITHUB_OUTPUT" + echo "### Upstream rebase of \`$DOWNSTREAM\`" >> "$GITHUB_STEP_SUMMARY" + echo "$1" >> "$GITHUB_STEP_SUMMARY" + } + + if git merge-base --is-ancestor "$up" "$old"; then + skip "Nothing to do: \`$DOWNSTREAM\` (\`${old:0:12}\`) already contains \`$UPSTREAM_BRANCH\` (\`${up:0:12}\`)." + exit 0 + fi + + # The candidate name records both tips, so an open pull request for + # the same pair means last night's result is still awaiting review. + if [ "$FORCE" != "true" ]; then + pending=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --base "$DOWNSTREAM" \ + --json headRefName,url \ + -q ".[] | select(.headRefName | startswith(\"$prefix\") and endswith(\"-${old:0:12}-${up:0:12}\")) | .url") + if [ -n "$pending" ]; then + skip "A rebase of these exact tips is already awaiting review: $pending" + exit 0 + fi + fi + echo "run=true" >> "$GITHUB_OUTPUT" + + rebase: + needs: prepare + if: needs.prepare.outputs.run == 'true' + runs-on: ubuntu-latest + timeout-minutes: 330 + # Read-only on purpose: the model runs here. It reaches the Claude API + # through OIDC federation, and that is the only thing id-token is for. + permissions: + contents: read + id-token: write + outputs: + produced: ${{ steps.collect.outputs.produced }} + result: ${{ steps.collect.outputs.result }} + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ needs.prepare.outputs.old }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + # The skill and scripts come from the commit this run started from, + # never from the tree under test. The kernel's .gitignore ignores + # dot-directories, so .rebase-tools/ cannot end up in a commit. + - name: Trusted tools + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" + mkdir .rebase-tools + git archive "$GITHUB_SHA" .github/scripts .automation | tar -x -C .rebase-tools --strip-components=1 + + - name: Fetch upstream + env: + UP: ${{ needs.prepare.outputs.up }} + run: | + set -euo pipefail + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + git merge-base --is-ancestor "$UP" FETCH_HEAD + git branch upstream-target "$UP" + # The rebase commits; this identity is what the committer field shows. + git config --global user.name "edera-upstream-rebase" + git config --global user.email "noreply@edera.dev" + + - name: Install toolchains + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential bc bison flex libelf-dev libssl-dev python3 \ + gcc-aarch64-linux-gnu + + - name: Normalize identifiers + id: ids + env: + RULE: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + ORG: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} + SVC: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} + WS: ${{ vars.ANTHROPIC_WORKSPACE_ID }} + run: | + set -euo pipefail + strip() { printf '%s' "$1" | tr -d '[:space:]'; } + { + echo "rule=$(strip "$RULE")" + echo "org=$(strip "$ORG")" + echo "svc=$(strip "$SVC")" + echo "ws=$(strip "$WS")" + } >> "$GITHUB_OUTPUT" + # An unset variable otherwise surfaces as an opaque API failure a + # second into the model step, with nothing in the log to say why. + missing=0 + for n in FEDERATION_RULE_ID:RULE ORGANIZATION_ID:ORG SERVICE_ACCOUNT_ID:SVC WORKSPACE_ID:WS; do + eval "v=\$${n#*:}" + if [ -z "$(strip "$v")" ]; then + echo "::error::vars.ANTHROPIC_${n%%:*} is empty or not visible to this repository." + missing=1 + fi + done + exit "$missing" + + - name: Rebase + id: claude + continue-on-error: true + uses: anthropics/claude-code-action@6fed3ca145920b639991cb756090506e1bcaf515 # v1.0.245 + with: + # The job token, which is read-only here. Without it the action + # would exchange OIDC for its own app token, which can write. + github_token: ${{ github.token }} + anthropic_federation_rule_id: '${{ steps.ids.outputs.rule }}' + anthropic_organization_id: '${{ steps.ids.outputs.org }}' + anthropic_service_account_id: '${{ steps.ids.outputs.svc }}' + anthropic_workspace_id: '${{ steps.ids.outputs.ws }}' + prompt: | + Rebase the downstream kernel series onto upstream by following + .rebase-tools/skills/upstream-rebase/SKILL.md exactly. Read it in + full before you start. + + DOWNSTREAM=${{ env.DOWNSTREAM }} + OLD_TIP=${{ needs.prepare.outputs.old }} (${{ env.DOWNSTREAM }}, checked out) + UPSTREAM=${{ needs.prepare.outputs.up }} (${{ env.UPSTREAM_BRANCH }}, local branch upstream-target) + RESULT=rebase-result + REPORT=${{ runner.temp }}/rebase-report.md + + You cannot push and must not try. The workflow verifies and + builds your branch independently afterwards and decides what + happens to it. + claude_args: | + --model claude-opus-5-5 + --max-turns 400 + --allowedTools "Read,Grep,Glob,Edit,Write,Bash(git:*),Bash(make:*),Bash(bash .rebase-tools/scripts/verify-upstream-rebase.sh:*),Bash(bash .rebase-tools/scripts/kernel-build.sh:*),Bash(scripts/config:*),Bash(mkdir -p .rebase-scratch:*),Bash(diff:*),Bash(comm:*),Bash(sort:*),Bash(grep:*),Bash(sed:*),Bash(awk:*),Bash(cat:*),Bash(head:*),Bash(tail:*),Bash(wc:*),Bash(ls:*),Bash(cut:*),Bash(tee:*)" + + # Take only what Claude left in git and the report file. A branch that + # does not exist or does not sit on the upstream tip is a failed night. + - name: Collect the result + id: collect + if: always() + env: + REPORT: ${{ runner.temp }}/rebase-report.md + UP: ${{ needs.prepare.outputs.up }} + OUTCOME: ${{ steps.claude.outcome }} + EXECUTION_FILE: ${{ steps.claude.outputs.execution_file }} + run: | + set -euo pipefail + out=$RUNNER_TEMP/out + mkdir -p "$out" + if [ -s "$REPORT" ]; then + cp "$REPORT" "$out/report.md" + else + # The action hides the session to keep tool output out of a + # public log, which also hides why a run ended early. Take only + # the API error messages and the final result text from it: + # neither carries tool output. + why= + f=${EXECUTION_FILE:-$RUNNER_TEMP/claude-execution-output.json} + if [ -s "$f" ]; then + why=$(jq -r ' + (if type == "array" then .[] else . end) + | if .type == "assistant" and (.error != null) then + "API error (\(.error)): " + ([.message.content[]? | .text? // empty] | join(" ")) + elif .type == "result" then + ([.result // empty] + (.errors // [] | map(tostring))) | join("\n") + else empty end' "$f" 2>/dev/null | head -c 2000 || true) + fi + { + printf 'Claude left no report (rebase step: %s).\n' "$OUTCOME" + if [ -n "$why" ]; then + # shellcheck disable=SC2016 # a literal Markdown fence + printf '\n```\n%s\n```\n' "$why" + else + printf 'The run log has the cause.\n' + fi + } >"$out/report.md" + [ -z "$why" ] || echo "::error::Claude ended without a result: ${why//$'\n'/ }" + fi + if git rev-parse --verify -q refs/heads/rebase-result >/dev/null \ + && git merge-base --is-ancestor "$UP" rebase-result \ + && [ "$(git rev-parse rebase-result)" != "$UP" ]; then + git bundle create "$out/rebase.bundle" refs/heads/rebase-result "^$UP" + echo "produced=true" >> "$GITHUB_OUTPUT" + echo "result=$(git rev-parse rebase-result)" >> "$GITHUB_OUTPUT" + else + echo "produced=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: rebase-${{ inputs.key }} + path: ${{ runner.temp }}/out/ + if-no-files-found: error + retention-days: 30 + + verify: + needs: [prepare, rebase] + if: needs.rebase.outputs.produced == 'true' + runs-on: ubuntu-latest + timeout-minutes: 45 + outputs: + status: ${{ steps.check.outputs.status }} + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ needs.prepare.outputs.old }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + # The checker comes from the commit this run started from, never from + # the branch under test. + - name: Trusted tools + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" + mkdir "$RUNNER_TEMP/tools" + git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + + - name: Download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: rebase-${{ inputs.key }} + path: ${{ runner.temp }}/in + + - name: Check + id: check + env: + OLD: ${{ needs.prepare.outputs.old }} + UP: ${{ needs.prepare.outputs.up }} + RESULT: ${{ needs.rebase.outputs.result }} + run: | + set -euo pipefail + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + git merge-base --is-ancestor "$UP" FETCH_HEAD + git fetch "$RUNNER_TEMP/in/rebase.bundle" rebase-result:rebase-result + [ "$(git rev-parse rebase-result)" = "$RESULT" ] || { echo "bundle does not hold $RESULT"; exit 1; } + # Actions runs this step under bash -e, so a bare call would end the + # step on the checker's first non-zero exit, before its verdict is + # recorded: drift would then reach the pull request as "the checker + # did not run". Capture the status instead. + rc=0 + bash "$RUNNER_TEMP/tools/scripts/verify-upstream-rebase.sh" "$OLD" "$RESULT" "$UP" >"$RUNNER_TEMP/verify.md" || rc=$? + case $rc in + 0) status=clean ;; + 1) status=drift ;; + *) status=broken ;; + esac + echo "status=$status" >> "$GITHUB_OUTPUT" + cat "$RUNNER_TEMP/verify.md" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: verify-${{ inputs.key }} + path: ${{ runner.temp }}/verify.md + retention-days: 30 + + build: + needs: [prepare, rebase] + if: needs.rebase.outputs.produced == 'true' + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + arch: [x86_64, arm64] + name: build (${{ matrix.arch }}) + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ needs.prepare.outputs.old }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + - name: Trusted tools + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" + mkdir "$RUNNER_TEMP/tools" + git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + + - name: Download + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: rebase-${{ inputs.key }} + path: ${{ runner.temp }}/in + + - name: Check out the result + env: + UP: ${{ needs.prepare.outputs.up }} + RESULT: ${{ needs.rebase.outputs.result }} + run: | + set -euo pipefail + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + git merge-base --is-ancestor "$UP" FETCH_HEAD + git fetch "$RUNNER_TEMP/in/rebase.bundle" rebase-result:rebase-result + git checkout --detach "$RESULT" + + - name: Install toolchains + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential bc bison flex libelf-dev libssl-dev python3 \ + gcc-aarch64-linux-gnu + + - name: Build + env: + ARCH: ${{ matrix.arch }} + run: bash "$RUNNER_TEMP/tools/scripts/kernel-build.sh" "$ARCH" "$RUNNER_TEMP/obj" + + publish: + needs: [prepare, rebase, verify, build] + if: always() && needs.prepare.outputs.run == 'true' && needs.rebase.result != 'cancelled' + runs-on: ubuntu-latest + timeout-minutes: 45 + concurrency: + group: push-${{ inputs.downstream }} + cancel-in-progress: false + permissions: + contents: read + issues: write + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + with: + ref: ${{ needs.prepare.outputs.old }} + fetch-depth: 0 + filter: blob:none + persist-credentials: false + + - name: Trusted tools + run: | + set -euo pipefail + git fetch --no-tags --filter=blob:none origin "$GITHUB_SHA" + mkdir "$RUNNER_TEMP/tools" + git archive "$GITHUB_SHA" .github/scripts | tar -x -C "$RUNNER_TEMP/tools" --strip-components=1 + + - name: Download the rebase + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: rebase-${{ inputs.key }} + path: ${{ runner.temp }}/in/rebase + + # Absent when Claude produced no branch and the checker never ran. + - name: Download the checker report + if: needs.verify.result == 'success' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: verify-${{ inputs.key }} + path: ${{ runner.temp }}/in/verify + + - name: App token + id: app + if: needs.rebase.outputs.produced == 'true' + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.REBASE_APP_CLIENT_ID }} + private-key: ${{ secrets.REBASE_APP_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write + permission-workflows: write + + - name: Push or propose + env: + APP_TOKEN: ${{ steps.app.outputs.token }} + JOB_TOKEN: ${{ github.token }} + PRODUCED: ${{ needs.rebase.outputs.produced }} + VERIFY: ${{ needs.verify.outputs.status }} + BUILD: ${{ needs.build.result }} + OLD: ${{ needs.prepare.outputs.old }} + UP: ${{ needs.prepare.outputs.up }} + RESULT: ${{ needs.rebase.outputs.result }} + PREFIX: ${{ needs.prepare.outputs.prefix }} + CANDIDATE: ${{ needs.prepare.outputs.candidate }} + KEY: ${{ inputs.key }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + set -euo pipefail + in=$RUNNER_TEMP/in + report=$in/rebase/report.md + verify=$in/verify/verify.md + summary() { cat >> "$GITHUB_STEP_SUMMARY"; } + title="Nightly upstream rebase of $DOWNSTREAM failed" + + # One issue per tree that stays open until someone deals with it, + # rather than one per night. + report_issue() { + existing=$(GH_TOKEN=$JOB_TOKEN gh issue list --repo "$GITHUB_REPOSITORY" --state open \ + --search "in:title \"$title\"" --json number -q '.[0].number // empty') + if [ -n "$existing" ]; then + GH_TOKEN=$JOB_TOKEN gh issue comment "$existing" --repo "$GITHUB_REPOSITORY" --body-file "$1" + else + GH_TOKEN=$JOB_TOKEN gh issue create --repo "$GITHUB_REPOSITORY" --title "$title" --body-file "$1" + fi + } + + if [ "$PRODUCED" != "true" ]; then + body=$RUNNER_TEMP/issue.md + { + echo "Claude could not produce a rebase of \`$DOWNSTREAM\` (\`${OLD:0:12}\`) onto \`$UPSTREAM_BRANCH\` (\`${UP:0:12}\`). [Run log]($RUN_URL)." + echo + cat "$report" + } | head -c 60000 >"$body" + report_issue "$body" + echo "No branch produced; reported in an issue." | summary + exit 1 + fi + + git fetch --no-tags "$UPSTREAM_URL" "$UPSTREAM_BRANCH" + git merge-base --is-ancestor "$UP" FETCH_HEAD + git fetch "$in/rebase/rebase.bundle" rebase-result:rebase-result + [ "$(git rev-parse rebase-result)" = "$RESULT" ] + + # Workflows triggered by a pull request, including the land workflow, + # run that branch's own copy of .github/ with this repository's + # secrets. So the automation paths must change by exactly what + # upstream changed there, or the branch never leaves this job: not + # as the tree and not as a pull request. + mb=$(git merge-base "$OLD" "$UP") + guarded=(.github .automation) + lines() { git diff "$@" -- "${guarded[@]}" | grep -E '^([+-]|diff )' || true; } + if [ "$(lines "$OLD" "$RESULT")" != "$(lines "$mb" "$UP")" ]; then + body=$RUNNER_TEMP/issue.md + { + echo "**Refused to publish** the rebase of \`$DOWNSTREAM\` (\`${OLD:0:12}\`) onto \`$UPSTREAM_BRANCH\` (\`${UP:0:12}\`): it changes \`${guarded[*]}\` beyond what upstream changed there. Those paths run with repository secrets, so the branch was not pushed anywhere. It is in the run's \`rebase-${KEY}\` artifact. [Run log]($RUN_URL)." + echo + echo '```diff' + git diff --stat "$OLD" "$RESULT" -- "${guarded[@]}" + echo '```' + } >"$body" + report_issue "$body" + echo "::error::Rebase changes automation paths beyond the upstream delta; not published." + exit 1 + fi + + origin="https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + export GH_TOKEN=$APP_TOKEN + + close_superseded() { + gh pr list --repo "$GITHUB_REPOSITORY" --state open --base "$DOWNSTREAM" \ + --json number,headRefName \ + -q ".[] | select(.headRefName | startswith(\"$PREFIX\")) | select(.headRefName != \"$CANDIDATE\") | .number" | + while read -r n; do + gh pr close "$n" --repo "$GITHUB_REPOSITORY" --delete-branch \ + --comment "Superseded by $1." + done + } + + # Reasons a human has to look before the tree moves. The checker + # and the builds judge the branch; the report can still raise a + # question they cannot see, such as an upstream change that meets a + # downstream feature without touching the same lines. + why=() + [ "$VERIFY" = "clean" ] || why+=("the checker found ${VERIFY:-no result (it did not run)}") + [ "$BUILD" = "success" ] || why+=("a build ${BUILD}") + drc=0 + bash "$RUNNER_TEMP/tools/scripts/report-needs-decision.sh" "$report" >/dev/null || drc=$? + case $drc in + 0) why+=("Claude's report asks for a decision") ;; + 1) ;; + *) why+=("Claude's report could not be read") ;; + esac + + if [ "${#why[@]}" -eq 0 ]; then + backup="backup/${DOWNSTREAM//\//-}-pre-rebase-$(date -u +%Y%m%d)-${OLD:0:12}" + git push "$origin" "$OLD:refs/heads/$backup" + # The lease pins the tip this rebase started from: if anything + # landed on the tree since, the push fails and nothing is lost. + if git push --force-with-lease="refs/heads/$DOWNSTREAM:$OLD" "$origin" \ + "$RESULT:refs/heads/$DOWNSTREAM" 2>"$RUNNER_TEMP/push.err"; then + cat "$RUNNER_TEMP/push.err" >&2 + now=$(git ls-remote "$origin" "refs/heads/$DOWNSTREAM" | cut -f1) + [ "$now" = "$RESULT" ] || { echo "::error::$DOWNSTREAM is $now after the push, expected $RESULT"; exit 1; } + close_superseded "the clean rebase pushed to \`$DOWNSTREAM\` as \`${RESULT:0:12}\`" + { + echo "### Pushed \`$DOWNSTREAM\` → \`${RESULT:0:12}\`" + echo "Was \`${OLD:0:12}\`, kept as \`$backup\`." + echo + cat "$verify" + echo + cat "$report" + } | summary + exit 0 + fi + # Refused: a branch rule, or the tree moved under the lease. A + # clean result is still worth a reviewer's approval, so propose + # it rather than drop it; the land workflow re-checks the tip. + cat "$RUNNER_TEMP/push.err" >&2 + reason=$(grep -m1 -E 'GH0[0-9]+|\[(remote )?rejected\]|error:' "$RUNNER_TEMP/push.err" | + sed -E 's/^remote: *//; s/^[[:space:]]+//; s/[[:space:]]+$//' || true) + why+=("the push to \`$DOWNSTREAM\` was refused${reason:+ (\`$reason\`)}") + git push "$origin" --delete "$backup" || true + fi + + # Not pushed: propose it. + git push "$origin" "$RESULT:refs/heads/$CANDIDATE" + because=$(IFS=';'; echo "${why[*]}") + because=${because//;/ and } + body=$RUNNER_TEMP/pr.md + { + echo "Nightly rebase of \`$DOWNSTREAM\` (\`${OLD:0:12}\`) onto \`$UPSTREAM_BRANCH\` (\`${UP:0:12}\`), driven by Claude. **Not pushed automatically because ${because}.** [Run log]($RUN_URL)." + echo + echo "> [!IMPORTANT]" + echo "> Do not use the merge button: this branch replaces \`$DOWNSTREAM\` rather than merging into it. **Approve** the pull request to land it; the land workflow force-pushes \`$DOWNSTREAM\` to the approved head, pinned to \`${OLD:0:12}\`, and keeps a backup." + echo + echo "## Independent checks" + echo + echo "Builds: **${BUILD}** (x86_64 and arm64 vmlinux with the Edera features built in; per-build results in the run)." + echo + if [ -s "$verify" ]; then cat "$verify"; else echo "The checker did not run."; fi + echo + echo "# Claude's report" + echo + cat "$report" + } >"$body" + if [ "$(wc -c <"$body")" -gt 60000 ]; then + head -c 59000 "$body" >"$body.cut" + printf '\n\n_Truncated; the full report is in the run artifacts._\n' >>"$body.cut" + mv "$body.cut" "$body" + fi + url=$(gh pr create --repo "$GITHUB_REPOSITORY" --base "$DOWNSTREAM" --head "$CANDIDATE" \ + --title "Rebase $DOWNSTREAM onto $UPSTREAM_BRANCH @ ${UP:0:12}" --body-file "$body") + close_superseded "$url" + echo "### Proposed: $url" | summary From 8569a1e16d844bae65297150c710140e9da2ce70 Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Thu, 8 Oct 2026 15:37:11 -0700 Subject: [PATCH 2272/2286] automation: use REBASE_FEDERATION_RULE_ID for the rebase The rebase runs from a scheduled or dispatched event and needs its own federation rule rather than the organization-wide one, so read it from vars.REBASE_FEDERATION_RULE_ID. --- .github/workflows/kernel-nightly.yml | 12 ++++++------ .github/workflows/upstream-rebase.yml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/kernel-nightly.yml b/.github/workflows/kernel-nightly.yml index 3e9737567feb02..09be7062f90f45 100644 --- a/.github/workflows/kernel-nightly.yml +++ b/.github/workflows/kernel-nightly.yml @@ -17,12 +17,12 @@ name: Nightly kernel maintenance # commits that touch .github/workflows/ (every rebase rewrites the ones # that add them), and pull requests it opens do not trigger other # workflows. -# vars.ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORGANIZATION_ID, -# ANTHROPIC_SERVICE_ACCOUNT_ID, ANTHROPIC_WORKSPACE_ID -# Organization-wide workload identity federation for the Claude API. The -# rule must accept this workflow's OIDC subject (a scheduled run on -# edera/6.18-lts), which a rule written for pull_request events will -# not. +# vars.REBASE_FEDERATION_RULE_ID +# The workload identity federation rule for the rebase. It must accept +# this workflow's OIDC subject (a scheduled or dispatched run). +# vars.ANTHROPIC_ORGANIZATION_ID, ANTHROPIC_SERVICE_ACCOUNT_ID, +# ANTHROPIC_WORKSPACE_ID +# The rest of the Claude API federation identity. on: schedule: diff --git a/.github/workflows/upstream-rebase.yml b/.github/workflows/upstream-rebase.yml index fdbdacbd19fa4c..75056019338fdf 100644 --- a/.github/workflows/upstream-rebase.yml +++ b/.github/workflows/upstream-rebase.yml @@ -195,7 +195,7 @@ jobs: - name: Normalize identifiers id: ids env: - RULE: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }} + RULE: ${{ vars.REBASE_FEDERATION_RULE_ID }} ORG: ${{ vars.ANTHROPIC_ORGANIZATION_ID }} SVC: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }} WS: ${{ vars.ANTHROPIC_WORKSPACE_ID }} @@ -211,10 +211,10 @@ jobs: # An unset variable otherwise surfaces as an opaque API failure a # second into the model step, with nothing in the log to say why. missing=0 - for n in FEDERATION_RULE_ID:RULE ORGANIZATION_ID:ORG SERVICE_ACCOUNT_ID:SVC WORKSPACE_ID:WS; do + for n in REBASE_FEDERATION_RULE_ID:RULE ANTHROPIC_ORGANIZATION_ID:ORG ANTHROPIC_SERVICE_ACCOUNT_ID:SVC ANTHROPIC_WORKSPACE_ID:WS; do eval "v=\$${n#*:}" if [ -z "$(strip "$v")" ]; then - echo "::error::vars.ANTHROPIC_${n%%:*} is empty or not visible to this repository." + echo "::error::vars.${n%%:*} is empty or not visible to this repository." missing=1 fi done From 8ea377c2b1076e35b157b92e53ffb4754344265f Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 9 Oct 2026 12:17:45 -0700 Subject: [PATCH 2273/2286] automation: trigger the edera/6.18-lts rebase nightly edera/6.18-lts carries its own rebase automation, but scheduled workflows run only from the default branch. Add a workflow here whose only job is to dispatch edera/6.18-lts's kernel-nightly.yml each night. --- .github/workflows/lts-rebase-trigger.yml | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/lts-rebase-trigger.yml diff --git a/.github/workflows/lts-rebase-trigger.yml b/.github/workflows/lts-rebase-trigger.yml new file mode 100644 index 00000000000000..7bc4e64f5c5a2c --- /dev/null +++ b/.github/workflows/lts-rebase-trigger.yml @@ -0,0 +1,35 @@ +name: Trigger edera/6.18-lts rebase + +# Starts edera/6.18-lts's own nightly rebase, and does nothing else. +# +# edera/6.18-lts drives its own rebase with its own copy of +# kernel-nightly.yml, but GitHub runs scheduled workflows only from the +# default branch, edera/mainline. So this schedule lives here and dispatches +# that copy: a workflow is identified by its path, and --ref picks the +# branch whose file runs. +# +# GITHUB_TOKEN can do this: workflow_dispatch is one of the events it is +# allowed to trigger runs with. + +on: + schedule: + - cron: '23 3 * * *' # 03:23 UTC nightly + +permissions: {} + +jobs: + dispatch: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + actions: write + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Dispatch + env: + GH_TOKEN: ${{ github.token }} + run: gh workflow run kernel-nightly.yml --repo "$GITHUB_REPOSITORY" --ref edera/6.18-lts From 11d31522e8da100891278bebbcc4d9750a89252b Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 26 Sep 2026 13:09:58 -0700 Subject: [PATCH 2274/2286] security: openpax: disable OpenPaX by default OPENPAX currently defaults to y, which means OpenPaX gets enabled for any configuration that picks up the new symbol, including ones that never asked for it. Default it to n so it has to be selected explicitly. While at it, make OPENPAX_SOFTMODE depend on OPENPAX. Soft mode does nothing without OpenPaX enabled, so there is no reason for it to be selectable (and default y) on its own. Signed-off-by: Alex Zenla (cherry picked from commit 514cd60bedffac5e9cd8c42888fd18434b409a86) --- security/Kconfig.openpax | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/Kconfig.openpax b/security/Kconfig.openpax index 76ee145094d93b..df65b38ed396e4 100644 --- a/security/Kconfig.openpax +++ b/security/Kconfig.openpax @@ -6,7 +6,7 @@ menu "OpenPaX options" config OPENPAX bool "Enable OpenPaX features" - default y + default n help This configuration setting enables OpenPaX features. OpenPaX adds memory safety-related defenses to the kernel which @@ -14,6 +14,7 @@ config OPENPAX config OPENPAX_SOFTMODE bool "Support PaX soft mode" + depends on OPENPAX default y help Enabling this option will allow you to configure OpenPaX From 9f60053f262c5085a3e3931269c890067d08093b Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Fri, 9 Oct 2026 13:46:06 -0700 Subject: [PATCH 2275/2286] automation: trigger the edera/6.18-lts backport when a PR merges edera/6.18-lts proposes a backport of each pull request merged into edera/mainline, driven by its own workflows. Add a workflow here whose only job is to dispatch that, skipping pull requests labelled no-backport and the nightly's rebase pull requests. --- .github/workflows/lts-backport-trigger.yml | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/lts-backport-trigger.yml diff --git a/.github/workflows/lts-backport-trigger.yml b/.github/workflows/lts-backport-trigger.yml new file mode 100644 index 00000000000000..f77e0ed1e11771 --- /dev/null +++ b/.github/workflows/lts-backport-trigger.yml @@ -0,0 +1,43 @@ +name: Trigger edera/6.18-lts backport + +# Starts edera/6.18-lts's backport of a pull request that just merged into +# edera/mainline, and does nothing else. +# +# edera/6.18-lts drives its own backports with its own copy of +# kernel-nightly.yml (backport_pr= runs lts-backport.yml there). Pull +# requests labelled no-backport, and the nightly's own rebase pull requests, +# are not dispatched; edera/6.18-lts decides everything else, including +# whether there is anything to backport at all. +# +# pull_request_target runs this file as it is on edera/mainline, with a token +# that can dispatch even for a pull request from a fork. Nothing here checks +# out or runs the pull request's code. + +on: + pull_request_target: + types: [closed] + branches: [edera/mainline] + +permissions: {} + +jobs: + dispatch: + if: >- + github.event.pull_request.merged + && !startsWith(github.event.pull_request.head.ref, 'rebase/') + && !contains(github.event.pull_request.labels.*.name, 'no-backport') + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + actions: write + steps: + - name: Harden runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Dispatch + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.number }} + run: gh workflow run kernel-nightly.yml --repo "$GITHUB_REPOSITORY" --ref edera/6.18-lts -f backport_pr="$PR" From c07705ba80128c70baef4ee61506836dbd5112d4 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 10 Oct 2026 16:52:18 +0000 Subject: [PATCH 2276/2286] iommu/xen: register before xen-pciback binds its devices xen-pciback seizes a hidden device in an fs_initcall, but the Xen IOMMU registered from a device_initcall. Registering probes every PCI device that already exists, so a hidden device was found with a driver bound and the IOMMU core warned about a late probe at driver bind. Register from subsys_initcall_sync instead, so the IOMMU is in place before pciback binds anything. Signed-off-by: Alex Zenla --- drivers/iommu/xen-iommu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c index 255da427824606..986cf874319eb8 100644 --- a/drivers/iommu/xen-iommu.c +++ b/drivers/iommu/xen-iommu.c @@ -569,4 +569,5 @@ static int __init xen_iommu_init(void) return 0; } -module_init(xen_iommu_init); +/* Must precede xen-pciback's fs_initcall bind, or the IOMMU core sees a bound device. */ +subsys_initcall_sync(xen_iommu_init); From f3f533f37ede477750cfdf2e47d217506e8892b2 Mon Sep 17 00:00:00 2001 From: "Alexander M. Merritt" Date: Thu, 15 Oct 2026 19:25:36 -0500 Subject: [PATCH 2277/2286] fix(xen/balloon): register the OOM deflate notifier only in domUs Signed-off-by: Alexander M. Merritt --- drivers/xen/balloon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index 088fd294515167..35fc2c654369fa 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -1142,7 +1142,8 @@ static int __init balloon_init(void) /* Init the xen-balloon driver. */ xen_balloon_init(); - register_oom_notifier(&balloon_oom_nb); + if (!xen_initial_domain()) + register_oom_notifier(&balloon_oom_nb); return 0; From fcbe0c20572993e4ff44d097edd7ceefa18a9866 Mon Sep 17 00:00:00 2001 From: "Alexander M. Merritt" Date: Thu, 15 Oct 2026 22:45:10 -0500 Subject: [PATCH 2278/2286] xen/balloon: add boot-time extra memory as 2 MiB blocks Signed-off-by: Alexander M. Merritt --- drivers/xen/balloon.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/xen/balloon.c b/drivers/xen/balloon.c index 35fc2c654369fa..1f2997cdd6c074 100644 --- a/drivers/xen/balloon.c +++ b/drivers/xen/balloon.c @@ -1024,10 +1024,26 @@ static struct notifier_block balloon_oom_nb = { .notifier_call = balloon_oom_notify, }; +static void __init balloon_append_range(unsigned long pfn, + unsigned long end_pfn) +{ + while (pfn < end_pfn) { + if (balloon_blocks_usable() && + IS_ALIGNED(pfn, BALLOON_BLOCK_NR_PAGES) && + end_pfn - pfn >= BALLOON_BLOCK_NR_PAGES) { + balloon_append_block(pfn_to_page(pfn)); + pfn += BALLOON_BLOCK_NR_PAGES; + } else { + balloon_append(pfn_to_page(pfn)); + pfn++; + } + } +} + static int __init balloon_add_regions(bool append) { unsigned long start_pfn, pages; - unsigned long pfn, extra_pfn_end; + unsigned long extra_pfn_end; unsigned int i; for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) { @@ -1044,8 +1060,7 @@ static int __init balloon_add_regions(bool append) */ extra_pfn_end = min(max_pfn, start_pfn + pages); - for (pfn = start_pfn; pfn < extra_pfn_end; pfn++) - balloon_append(pfn_to_page(pfn)); + balloon_append_range(start_pfn, extra_pfn_end); /* * There are two different use-cases depending on how the From 9e4f6e3d4bb8d591c24a40c817ab11bc5a466ecf Mon Sep 17 00:00:00 2001 From: "Alexander M. Merritt" Date: Thu, 15 Oct 2026 22:46:10 -0500 Subject: [PATCH 2279/2286] x86/xen: keep dom0's growth room on the balloon All of the extra memory Linux reserves at boot goes to the mapping pool, so dom0's balloon starts empty. Growing dom0 at runtime then means memory hotplug, which places the new range at the first free physical address: on PVH that is the host's own map, where nothing guarantees the range is not a device's MMIO window. Keep the part of the extra memory dom0 may grow into on the balloon instead. Xen's ceiling for dom0 (dom0_mem=...,max:) sizes that part, so a host booted without a max: keeps today's behaviour. Every region gives up the same fraction, which spreads the growth over the NUMA nodes on PVH and leaves each node's mapping pool its share, and the last memory section stays with the pool so that early grant mappings never fall back to holes in the physical map. Signed-off-by: Alexander M. Merritt --- arch/x86/xen/enlighten.c | 54 ++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/arch/x86/xen/enlighten.c b/arch/x86/xen/enlighten.c index cf061ed45ce844..3883b4cb432548 100644 --- a/arch/x86/xen/enlighten.c +++ b/arch/x86/xen/enlighten.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -441,8 +442,34 @@ void __init xen_add_extra_mem(unsigned long start_pfn, unsigned long n_pfns) } #ifdef CONFIG_XEN_UNPOPULATED_ALLOC +/* + * How much of the extra memory dom0 keeps on the balloon, to grow into up + * to the ceiling Xen set for it (dom0_mem=...,max:). Without a max: Xen + * reports UINT_MAX and dom0 keeps nothing, so growing stays opt-in. The + * last memory section always stays with the mapping pool, so that the + * grant table and other early users never fall back to holes in the + * physical map before PCI devices have claimed their MMIO windows. + */ +static unsigned long __init xen_dom0_growth_pages(unsigned long extra_pages) +{ + domid_t domid = DOMID_SELF; + long max_pages, cur_pages; + + if (!xen_initial_domain() || extra_pages <= PAGES_PER_SECTION) + return 0; + + max_pages = HYPERVISOR_memory_op(XENMEM_maximum_reservation, &domid); + cur_pages = HYPERVISOR_memory_op(XENMEM_current_reservation, &domid); + if (cur_pages <= 0 || max_pages <= cur_pages || max_pages >= UINT_MAX) + return 0; + + return min_t(unsigned long, max_pages - cur_pages, + extra_pages - PAGES_PER_SECTION); +} + int __init arch_xen_unpopulated_init(struct resource **res) { + unsigned long extra_pages = 0, growth_pages; unsigned int i; if (!xen_domain()) @@ -451,16 +478,27 @@ int __init arch_xen_unpopulated_init(struct resource **res) /* Must be set strictly before calling xen_free_unpopulated_pages(). */ *res = &iomem_resource; + for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) + extra_pages += xen_extra_mem[i].n_pfns; + growth_pages = xen_dom0_growth_pages(extra_pages); + /* * Initialize with pages from the extra memory regions (see - * arch/x86/xen/setup.c). + * arch/x86/xen/setup.c), less what dom0 keeps to grow into. Every + * region gives up the same fraction: on PVH that spreads dom0's + * growth over the NUMA nodes the regions belong to, and leaves each + * node's pool its share of the rest. */ for (i = 0; i < XEN_EXTRA_MEM_MAX_REGIONS; i++) { - unsigned int j; + struct xen_memory_region *region = &xen_extra_mem[i]; + unsigned long keep = 0, j; + + if (growth_pages) + keep = mult_frac(region->n_pfns, growth_pages, + extra_pages); - for (j = 0; j < xen_extra_mem[i].n_pfns; j++) { - struct page *pg = - pfn_to_page(xen_extra_mem[i].start_pfn + j); + for (j = keep; j < region->n_pfns; j++) { + struct page *pg = pfn_to_page(region->start_pfn + j); xen_free_unpopulated_pages(1, &pg); } @@ -471,9 +509,9 @@ int __init arch_xen_unpopulated_init(struct resource **res) * driver to know how much of the physmap is unpopulated and * set an accurate initial memory target. */ - xen_unpopulated_pages += xen_extra_mem[i].n_pfns; - /* Zero so region is not also added to the balloon driver. */ - xen_extra_mem[i].n_pfns = 0; + xen_unpopulated_pages += region->n_pfns - keep; + /* Only the part dom0 keeps is added to the balloon driver. */ + region->n_pfns = keep; } return 0; From 7efa595481e8a01a6314a037010e3b49f3f869fa Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 17 Oct 2026 17:14:01 -0700 Subject: [PATCH 2280/2286] 9p/xen: wake ring-space waiters with wake_up() A request that does not fit in its ring sleeps in io_wait_event_killable() until the backend frees space, but the event handler wakes the ring with wake_up_interruptible(), which only wakes TASK_INTERRUPTIBLE sleepers. A TASK_KILLABLE waiter therefore sleeps until it is killed, which hangs a guest that has enough large requests in flight to fill a ring. Wake the ring with wake_up() so killable waiters see the freed space. Fixes: 9523feac272c ("net/9p: Switch to wait_event_killable()") Signed-off-by: Alex Zenla --- net/9p/trans_xen.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 9d51f18c6a5ee5..9e9475d9f60f45 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -255,7 +255,7 @@ static irqreturn_t xen_9pfs_front_event_handler(int irq, void *r) return IRQ_HANDLED; } - wake_up_interruptible(&ring->wq); + wake_up(&ring->wq); schedule_work(&ring->work); return IRQ_HANDLED; From 172d420f0af64058eb5a71091e58dae98f776860 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 17 Oct 2026 17:17:01 -0700 Subject: [PATCH 2281/2286] 9p/xen: reconnect to the backend on resume The frontend's resume handler only warned that suspend and resume are unsupported. After a restore its rings, grant references and event channels are gone, yet the mounted client kept writing requests into them, so every request on the mount hung. The next InitWait from the backend then allocated a second frontend instance for the same device, which no client used. On resume, collect any replies the backend left in the rings, free the rings, fail the requests that are still unanswered with -EIO, and publish Initialising so that a backend which still serves the old rings starts over. When the backend reaches InitWait, allocate and publish new rings for the existing frontend, keeping its clients. Requests that arrive while the frontend is not connected wait until it is, and a rw_semaphore keeps them off the rings while a resume replaces them. Signed-off-by: Alex Zenla --- net/9p/trans_xen.c | 340 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 263 insertions(+), 77 deletions(-) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 9e9475d9f60f45..97988d4a46d5b4 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -56,10 +56,18 @@ struct xen_9pfs_front_priv { struct p9_client *client; struct xen_9pfs_dataring *rings; + unsigned int ring_order; + /* Requests hold it for read while they use the rings; a restore + * holds it for write to replace them. + */ + struct rw_semaphore rings_sem; + bool connected; }; static LIST_HEAD(xen_9pfs_devs); static DEFINE_RWLOCK(xen_9pfs_lock); +/* Requests that find their frontend disconnected wait here for it. */ +static DECLARE_WAIT_QUEUE_HEAD(xen_9pfs_connect_wq); /* We don't currently allow canceling of requests */ static int p9_xen_cancel(struct p9_client *client, struct p9_req_t *req) @@ -120,6 +128,23 @@ static bool p9_xen_write_todo(struct xen_9pfs_dataring *ring, RING_IDX size) xen_9pfs_queued(prod, cons, XEN_9PFS_RING_SIZE(ring)) >= size; } +/* True once @client's frontend is connected, or gone. */ +static bool p9_xen_ready(struct p9_client *client) +{ + struct xen_9pfs_front_priv *priv; + bool ready = true; + + read_lock(&xen_9pfs_lock); + list_for_each_entry(priv, &xen_9pfs_devs, list) { + if (priv->client == client) { + ready = READ_ONCE(priv->connected); + break; + } + } + read_unlock(&xen_9pfs_lock); + return ready; +} + static int p9_xen_request(struct p9_client *client, struct p9_req_t *p9_req) { struct xen_9pfs_front_priv *priv; @@ -129,6 +154,7 @@ static int p9_xen_request(struct p9_client *client, struct p9_req_t *p9_req) struct xen_9pfs_dataring *ring; int num; +lookup: read_lock(&xen_9pfs_lock); list_for_each_entry(priv, &xen_9pfs_devs, list) { if (priv->client == client) @@ -138,13 +164,27 @@ static int p9_xen_request(struct p9_client *client, struct p9_req_t *p9_req) if (list_entry_is_head(priv, &xen_9pfs_devs, list)) return -EINVAL; + down_read(&priv->rings_sem); + if (!READ_ONCE(priv->connected)) { + up_read(&priv->rings_sem); + if (wait_event_killable(xen_9pfs_connect_wq, + p9_xen_ready(client))) + return -ERESTARTSYS; + goto lookup; + } + num = p9_req->tc.tag % XEN_9PFS_NUM_RINGS; ring = &priv->rings[num]; again: while (io_wait_event_killable(ring->wq, + !READ_ONCE(priv->connected) || p9_xen_write_todo(ring, size)) != 0) ; + if (!READ_ONCE(priv->connected)) { + up_read(&priv->rings_sem); + goto lookup; + } spin_lock_irqsave(&ring->lock, flags); cons = ring->intf->out_cons; @@ -170,6 +210,7 @@ static int p9_xen_request(struct p9_client *client, struct p9_req_t *p9_req) ring->intf->out_prod = prod; spin_unlock_irqrestore(&ring->lock, flags); notify_remote_via_irq(ring->irq); + up_read(&priv->rings_sem); p9_req_put(client, p9_req); return 0; @@ -280,44 +321,48 @@ static const struct xenbus_device_id xen_9pfs_front_ids[] = { { "" } }; -static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv) +static void xen_9pfs_front_free_rings(struct xen_9pfs_front_priv *priv) { int i, j; - if (priv->rings) { - for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { - struct xen_9pfs_dataring *ring = &priv->rings[i]; + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + struct xen_9pfs_dataring *ring = &priv->rings[i]; - cancel_work_sync(&ring->work); + cancel_work_sync(&ring->work); - if (!ring->intf) - break; - if (ring->irq >= 0) { - unbind_from_irqhandler(ring->irq, ring); - ring->irq = -1; - } - if (ring->data.in) { - for (j = 0; j < (1 << ring->intf->ring_order); - j++) { - grant_ref_t ref; - - ref = ring->intf->ref[j]; - gnttab_end_foreign_access(ref, NULL); - ring->intf->ref[j] = INVALID_GRANT_REF; - } - free_pages_exact(ring->data.in, - 1UL << (ring->intf->ring_order + - XEN_PAGE_SHIFT)); - ring->data.in = NULL; - ring->data.out = NULL; - } - if (ring->ref != INVALID_GRANT_REF) { - gnttab_end_foreign_access(ring->ref, NULL); - ring->ref = INVALID_GRANT_REF; + if (!ring->intf) + break; + if (ring->irq >= 0) { + unbind_from_irqhandler(ring->irq, ring); + ring->irq = -1; + } + if (ring->data.in) { + for (j = 0; j < (1 << ring->intf->ring_order); j++) { + grant_ref_t ref; + + ref = ring->intf->ref[j]; + gnttab_end_foreign_access(ref, NULL); + ring->intf->ref[j] = INVALID_GRANT_REF; } - free_page((unsigned long)ring->intf); - ring->intf = NULL; + free_pages_exact(ring->data.in, + 1UL << (ring->intf->ring_order + + XEN_PAGE_SHIFT)); + ring->data.in = NULL; + ring->data.out = NULL; + } + if (ring->ref != INVALID_GRANT_REF) { + gnttab_end_foreign_access(ring->ref, NULL); + ring->ref = INVALID_GRANT_REF; } + free_page((unsigned long)ring->intf); + ring->intf = NULL; + } +} + +static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv) +{ + if (priv->rings) { + xen_9pfs_front_free_rings(priv); kfree(priv->rings); } kfree(priv->tag); @@ -337,10 +382,65 @@ static void xen_9pfs_front_remove(struct xenbus_device *dev) dev_set_drvdata(&dev->dev, NULL); list_del(&priv->list); write_unlock(&xen_9pfs_lock); + wake_up_all(&xen_9pfs_connect_wq); xen_9pfs_front_free(priv); } +/* Fail the requests the backend held when the rings went away; nothing + * will answer them now, and whether they took effect is unknown. + */ +static void xen_9pfs_front_fail_sent(struct p9_client *client) +{ + struct p9_req_t *req, *next; + unsigned long flags; + LIST_HEAD(sent); + int id; + + spin_lock_irqsave(&client->lock, flags); + idr_for_each_entry(&client->reqs, req, id) { + if (READ_ONCE(req->status) == REQ_STATUS_SENT && + p9_req_try_get(req)) + list_add_tail(&req->req_list, &sent); + } + spin_unlock_irqrestore(&client->lock, flags); + + list_for_each_entry_safe(req, next, &sent, req_list) { + list_del(&req->req_list); + req->t_err = -EIO; + p9_client_cb(client, req, REQ_STATUS_ERROR); + } +} + +static void xen_9pfs_front_disconnect(struct xen_9pfs_front_priv *priv) +{ + int i; + + WRITE_ONCE(priv->connected, false); + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + if (priv->rings[i].intf) + wake_up(&priv->rings[i].wq); + } + + down_write(&priv->rings_sem); + if (priv->client) { + /* Collect the replies the backend wrote before it went away. */ + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + struct xen_9pfs_dataring *ring = &priv->rings[i]; + + if (!ring->intf) + break; + schedule_work(&ring->work); + flush_work(&ring->work); + } + } + xen_9pfs_front_free_rings(priv); + up_write(&priv->rings_sem); + + if (priv->client) + xen_9pfs_front_fail_sent(priv->client); +} + static int xen_9pfs_front_alloc_dataring(struct xenbus_device *dev, struct xen_9pfs_dataring *ring, unsigned int order) @@ -415,10 +515,68 @@ static int xen_9pfs_front_alloc_dataring(struct xenbus_device *dev, return ret; } -static int xen_9pfs_front_init(struct xenbus_device *dev) +static int xen_9pfs_front_publish(struct xenbus_device *dev, + struct xen_9pfs_front_priv *priv) { int ret, i; struct xenbus_transaction xbt; + + again: + ret = xenbus_transaction_start(&xbt); + if (ret) { + xenbus_dev_fatal(dev, ret, "starting transaction"); + return ret; + } + ret = xenbus_printf(xbt, dev->nodename, "version", "%u", 1); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "num-rings", "%u", + XEN_9PFS_NUM_RINGS); + if (ret) + goto error_xenbus; + + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + char str[16]; + + BUILD_BUG_ON(XEN_9PFS_NUM_RINGS > 9); + sprintf(str, "ring-ref%d", i); + ret = xenbus_printf(xbt, dev->nodename, str, "%d", + priv->rings[i].ref); + if (ret) + goto error_xenbus; + + sprintf(str, "event-channel-%d", i); + ret = xenbus_printf(xbt, dev->nodename, str, "%u", + priv->rings[i].evtchn); + if (ret) + goto error_xenbus; + } + if (!priv->tag) { + priv->tag = xenbus_read(xbt, dev->nodename, "tag", NULL); + if (IS_ERR(priv->tag)) { + ret = PTR_ERR(priv->tag); + priv->tag = NULL; + goto error_xenbus; + } + } + ret = xenbus_transaction_end(xbt, 0); + if (ret) { + if (ret == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, ret, "completing transaction"); + return ret; + } + return 0; + + error_xenbus: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, ret, "writing xenstore"); + return ret; +} + +static int xen_9pfs_front_init(struct xenbus_device *dev) +{ + int ret, i; struct xen_9pfs_front_priv *priv; char *versions, *v, *token; bool version_1 = false; @@ -457,6 +615,8 @@ static int xen_9pfs_front_init(struct xenbus_device *dev) if (!priv) return -ENOMEM; priv->dev = dev; + priv->ring_order = max_ring_order; + init_rwsem(&priv->rings_sem); priv->rings = kzalloc_objs(*priv->rings, XEN_9PFS_NUM_RINGS); if (!priv->rings) { kfree(priv); @@ -471,48 +631,9 @@ static int xen_9pfs_front_init(struct xenbus_device *dev) goto error; } - again: - ret = xenbus_transaction_start(&xbt); - if (ret) { - xenbus_dev_fatal(dev, ret, "starting transaction"); - goto error; - } - ret = xenbus_printf(xbt, dev->nodename, "version", "%u", 1); - if (ret) - goto error_xenbus; - ret = xenbus_printf(xbt, dev->nodename, "num-rings", "%u", - XEN_9PFS_NUM_RINGS); + ret = xen_9pfs_front_publish(dev, priv); if (ret) - goto error_xenbus; - - for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { - char str[16]; - - BUILD_BUG_ON(XEN_9PFS_NUM_RINGS > 9); - sprintf(str, "ring-ref%d", i); - ret = xenbus_printf(xbt, dev->nodename, str, "%d", - priv->rings[i].ref); - if (ret) - goto error_xenbus; - - sprintf(str, "event-channel-%d", i); - ret = xenbus_printf(xbt, dev->nodename, str, "%u", - priv->rings[i].evtchn); - if (ret) - goto error_xenbus; - } - priv->tag = xenbus_read(xbt, dev->nodename, "tag", NULL); - if (IS_ERR(priv->tag)) { - ret = PTR_ERR(priv->tag); - goto error_xenbus; - } - ret = xenbus_transaction_end(xbt, 0); - if (ret) { - if (ret == -EAGAIN) - goto again; - xenbus_dev_fatal(dev, ret, "completing transaction"); goto error; - } write_lock(&xen_9pfs_lock); dev_set_drvdata(&dev->dev, priv); @@ -522,14 +643,47 @@ static int xen_9pfs_front_init(struct xenbus_device *dev) xenbus_switch_state(dev, XenbusStateInitialised); return 0; - error_xenbus: - xenbus_transaction_end(xbt, 1); - xenbus_dev_fatal(dev, ret, "writing xenstore"); error: xen_9pfs_front_free(priv); return ret; } +/* Give a restored frontend new rings, keeping its clients and their fids. */ +static int xen_9pfs_front_reconnect(struct xenbus_device *dev, + struct xen_9pfs_front_priv *priv) +{ + int ret, i; + + if (xenbus_read_unsigned(dev->otherend, "max-rings", 0) < + XEN_9PFS_NUM_RINGS || + xenbus_read_unsigned(dev->otherend, "max-ring-page-order", 0) < + priv->ring_order) { + xenbus_dev_fatal(dev, -EINVAL, + "backend cannot serve the rings in use"); + return -EINVAL; + } + + down_write(&priv->rings_sem); + for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { + ret = xen_9pfs_front_alloc_dataring(dev, &priv->rings[i], + priv->ring_order); + if (ret < 0) + goto error; + } + ret = xen_9pfs_front_publish(dev, priv); + if (ret) + goto error; + up_write(&priv->rings_sem); + + xenbus_switch_state(dev, XenbusStateInitialised); + return 0; + + error: + xen_9pfs_front_free_rings(priv); + up_write(&priv->rings_sem); + return ret; +} + static int xen_9pfs_front_probe(struct xenbus_device *dev, const struct xenbus_device_id *id) { @@ -538,13 +692,30 @@ static int xen_9pfs_front_probe(struct xenbus_device *dev, static int xen_9pfs_front_resume(struct xenbus_device *dev) { - dev_warn(&dev->dev, "suspend/resume unsupported\n"); + struct xen_9pfs_front_priv *priv = dev_get_drvdata(&dev->dev); + int ret; + + if (!priv) + return 0; + + xen_9pfs_front_disconnect(priv); + /* xenbus set dev->state back to Initialising without publishing it, + * and a backend that still serves the old rings only goes back to + * InitWait once it sees that. + */ + ret = xenbus_printf(XBT_NIL, dev->nodename, "state", "%d", + XenbusStateInitialising); + if (ret) + dev_warn(&dev->dev, "publishing Initialising failed: %d\n", + ret); return 0; } static void xen_9pfs_front_changed(struct xenbus_device *dev, enum xenbus_state backend_state) { + struct xen_9pfs_front_priv *priv; + switch (backend_state) { case XenbusStateReconfiguring: case XenbusStateReconfigured: @@ -557,10 +728,25 @@ static void xen_9pfs_front_changed(struct xenbus_device *dev, if (dev->state != XenbusStateInitialising) break; - xen_9pfs_front_init(dev); + priv = dev_get_drvdata(&dev->dev); + if (priv) + xen_9pfs_front_reconnect(dev, priv); + else + xen_9pfs_front_init(dev); break; case XenbusStateConnected: + /* Until we publish rings, a Connected backend is one still + * serving the rings a resume dropped. + */ + if (dev->state != XenbusStateInitialised) + break; + + priv = dev_get_drvdata(&dev->dev); + if (priv) { + WRITE_ONCE(priv->connected, true); + wake_up_all(&xen_9pfs_connect_wq); + } xenbus_switch_state(dev, XenbusStateConnected); break; From e8b234667bbed338016b91667b341e3516142aab Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 17 Oct 2026 17:20:22 -0700 Subject: [PATCH 2282/2286] 9p/xen: carry the 9P session across a restore A forked, restored or imported domain resumes against a new backend whose fid table is empty, while the guest's 9P clients still hold the fids of the session they were suspended with. With the transport reconnected every request on those fids still fails, so even an idle mount comes back unusable. When the backend advertises feature-session-transfer, wait at suspend for the requests in flight to be answered, then ask the backend for its fid table with Tsession_save and keep the pages in guest memory, where they survive into whatever domain the guest resumes in. Once a restored frontend's new backend connects, replay the pages with Tsession_restore before any request can use the rings. The pages are opaque to the frontend; the backend validates every entry as it would a walk or open. Signed-off-by: Alex Zenla --- net/9p/trans_xen.c | 279 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 276 insertions(+), 3 deletions(-) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 97988d4a46d5b4..945ca96526451d 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -24,6 +26,28 @@ #define XEN_9PFS_RING_ORDER 9 #define XEN_9PFS_RING_SIZE(ring) XEN_FLEX_RING_SIZE(ring->intf->ring_order) +/* Edera backend extension that hands the fid table to the guest at suspend + * and takes it back after a restore, advertised as feature-session-transfer. + * The ids sit outside the 9P2000.L range. Tsession_save offset[4] answers + * Rsession_save total[4] next[4] count[4] entry[count]; Tsession_restore + * msize[4] flags[4] count[4] entry[count] answers Rsession_restore + * restored[4]. The frontend stores the count[4] entry[count] tails without + * looking inside them. + */ +#define P9_XEN_TSESSION_SAVE 200 +#define P9_XEN_RSESSION_SAVE 201 +#define P9_XEN_TSESSION_RESTORE 202 +#define P9_XEN_RSESSION_RESTORE 203 +#define P9_XEN_SESSION_RESET 1 +#define XEN_9PFS_CTL_TIMEOUT (5 * HZ) +#define XEN_9PFS_DRAIN_TIMEOUT (2 * HZ) + +struct xen_9pfs_session_chunk { + struct list_head list; + u32 len; + u8 data[]; +}; + struct xen_9pfs_header { uint32_t size; uint8_t id; @@ -62,6 +86,17 @@ struct xen_9pfs_front_priv { */ struct rw_semaphore rings_sem; bool connected; + + /* Session chunks saved at the last suspend, in guest memory so that + * they survive into a restored or forked domain. + */ + struct list_head session; + /* A control request on ring 0, answered with tag P9_NOTAG. */ + bool ctl_pending; + struct completion ctl_done; + u8 *ctl_reply; + u32 ctl_reply_cap; + u32 ctl_reply_len; }; static LIST_HEAD(xen_9pfs_devs); @@ -247,6 +282,29 @@ static void p9_xen_response(struct work_struct *work) masked_prod, &masked_cons, XEN_9PFS_RING_SIZE(ring)); + /* A client's own P9_NOTAG request, Tversion, never maps to + * ring 0; see xen_9pfs_front_ctl(). + */ + if (ring == priv->rings && h.tag == P9_NOTAG && + READ_ONCE(priv->ctl_pending)) { + if (h.size <= priv->ctl_reply_cap) { + u32 size = XEN_9PFS_RING_SIZE(ring); + + masked_cons = xen_9pfs_mask(cons, size); + xen_9pfs_read_packet(priv->ctl_reply, + ring->data.in, h.size, + masked_prod, &masked_cons, + size); + priv->ctl_reply_len = h.size; + } + virt_mb(); /* read ring before freeing it */ + cons += h.size; + ring->intf->in_cons = cons; + WRITE_ONCE(priv->ctl_pending, false); + complete(&priv->ctl_done); + continue; + } + req = p9_tag_lookup(priv->client, h.tag); if (!req || req->status != REQ_STATUS_SENT) { dev_warn(&priv->dev->dev, "Wrong req tag=%x\n", h.tag); @@ -359,12 +417,23 @@ static void xen_9pfs_front_free_rings(struct xen_9pfs_front_priv *priv) } } +static void xen_9pfs_front_session_free(struct xen_9pfs_front_priv *priv) +{ + struct xen_9pfs_session_chunk *chunk, *next; + + list_for_each_entry_safe(chunk, next, &priv->session, list) { + list_del(&chunk->list); + kvfree(chunk); + } +} + static void xen_9pfs_front_free(struct xen_9pfs_front_priv *priv) { if (priv->rings) { xen_9pfs_front_free_rings(priv); kfree(priv->rings); } + xen_9pfs_front_session_free(priv); kfree(priv->tag); kfree(priv); } @@ -441,6 +510,159 @@ static void xen_9pfs_front_disconnect(struct xen_9pfs_front_priv *priv) xen_9pfs_front_fail_sent(priv->client); } +/* Send control request @req on ring 0 and wait for the backend's answer in + * @reply, which must carry @id and at least @body bytes after the header. + * Returns the reply length or a negative errno. Callers hold rings_sem. + */ +static int xen_9pfs_front_ctl(struct xen_9pfs_front_priv *priv, + const u8 *req, u32 len, u8 *reply, u32 cap, + u8 id, u32 body) +{ + struct xen_9pfs_dataring *ring = &priv->rings[0]; + RING_IDX cons, prod, masked_cons, masked_prod; + unsigned long flags; + u32 reply_len; + + BUILD_BUG_ON(P9_NOTAG % XEN_9PFS_NUM_RINGS == 0); + if (len > XEN_9PFS_RING_SIZE(ring)) + return -E2BIG; + if (!wait_event_timeout(ring->wq, p9_xen_write_todo(ring, len), + XEN_9PFS_CTL_TIMEOUT)) + return -ETIMEDOUT; + + reinit_completion(&priv->ctl_done); + priv->ctl_reply = reply; + priv->ctl_reply_cap = cap; + priv->ctl_reply_len = 0; + WRITE_ONCE(priv->ctl_pending, true); + + spin_lock_irqsave(&ring->lock, flags); + cons = ring->intf->out_cons; + prod = ring->intf->out_prod; + virt_mb(); /* read indexes before the ring */ + if (XEN_9PFS_RING_SIZE(ring) - + xen_9pfs_queued(prod, cons, XEN_9PFS_RING_SIZE(ring)) < len) { + spin_unlock_irqrestore(&ring->lock, flags); + WRITE_ONCE(priv->ctl_pending, false); + return -EBUSY; + } + masked_prod = xen_9pfs_mask(prod, XEN_9PFS_RING_SIZE(ring)); + masked_cons = xen_9pfs_mask(cons, XEN_9PFS_RING_SIZE(ring)); + xen_9pfs_write_packet(ring->data.out, req, len, &masked_prod, + masked_cons, XEN_9PFS_RING_SIZE(ring)); + virt_wmb(); /* write ring before updating pointer */ + prod += len; + ring->intf->out_prod = prod; + spin_unlock_irqrestore(&ring->lock, flags); + notify_remote_via_irq(ring->irq); + + if (!wait_for_completion_timeout(&priv->ctl_done, + XEN_9PFS_CTL_TIMEOUT)) { + WRITE_ONCE(priv->ctl_pending, false); + /* A response worker may already be copying into @reply. */ + flush_work(&ring->work); + return -ETIMEDOUT; + } + + reply_len = priv->ctl_reply_len; + if (reply_len < P9_HDRSZ || get_unaligned_le32(reply) != reply_len) + return -EPROTO; + if (reply[4] == P9_RLERROR && reply_len >= P9_HDRSZ + 4) + return -(int)get_unaligned_le32(reply + P9_HDRSZ); + if (reply[4] != id || reply_len < P9_HDRSZ + body) + return -EPROTO; + return reply_len; +} + +static int xen_9pfs_front_suspend(struct xenbus_device *dev) +{ + struct xen_9pfs_front_priv *priv = dev_get_drvdata(&dev->dev); + struct xen_9pfs_session_chunk *chunk; + u32 offset = 0, total, next, len; + struct p9_client *client; + u8 req[P9_HDRSZ + 4]; + unsigned long deadline; + struct p9_req_t *p9_req; + unsigned long flags; + bool idle; + u8 *reply; + int ret, id; + + if (!priv) + return 0; + xen_9pfs_front_session_free(priv); + if (!xenbus_read_unsigned(dev->otherend, "feature-session-transfer", 0)) + return 0; + + down_read(&priv->rings_sem); + client = priv->client; + if (!READ_ONCE(priv->connected) || !client) + goto out; + + /* Let what is in flight finish first, so that the saved fids match + * what the guest has seen answered. + */ + deadline = jiffies + XEN_9PFS_DRAIN_TIMEOUT; + do { + idle = true; + spin_lock_irqsave(&client->lock, flags); + idr_for_each_entry(&client->reqs, p9_req, id) { + if (READ_ONCE(p9_req->status) == REQ_STATUS_SENT) { + idle = false; + break; + } + } + spin_unlock_irqrestore(&client->lock, flags); + if (idle) + break; + usleep_range(1000, 2000); + } while (time_before(jiffies, deadline)); + + reply = kvmalloc(XEN_9PFS_RING_SIZE(priv->rings), GFP_KERNEL); + if (!reply) { + ret = -ENOMEM; + goto failed; + } + do { + put_unaligned_le32(sizeof(req), req); + req[4] = P9_XEN_TSESSION_SAVE; + put_unaligned_le16(P9_NOTAG, req + 5); + put_unaligned_le32(offset, req + P9_HDRSZ); + ret = xen_9pfs_front_ctl(priv, req, sizeof(req), reply, + XEN_9PFS_RING_SIZE(priv->rings), + P9_XEN_RSESSION_SAVE, 12); + if (ret < 0) + break; + total = get_unaligned_le32(reply + P9_HDRSZ); + next = get_unaligned_le32(reply + P9_HDRSZ + 4); + len = ret - (P9_HDRSZ + 8); + ret = 0; + if (next <= offset && next < total) { + ret = -EPROTO; + break; + } + chunk = kvmalloc(struct_size(chunk, data, len), GFP_KERNEL); + if (!chunk) { + ret = -ENOMEM; + break; + } + chunk->len = len; + memcpy(chunk->data, reply + P9_HDRSZ + 8, len); + list_add_tail(&chunk->list, &priv->session); + offset = next; + } while (offset < total); + kvfree(reply); + + failed: + if (ret) { + dev_warn(&dev->dev, "saving the 9P session failed: %d\n", ret); + xen_9pfs_front_session_free(priv); + } + out: + up_read(&priv->rings_sem); + return 0; +} + static int xen_9pfs_front_alloc_dataring(struct xenbus_device *dev, struct xen_9pfs_dataring *ring, unsigned int order) @@ -617,6 +839,8 @@ static int xen_9pfs_front_init(struct xenbus_device *dev) priv->dev = dev; priv->ring_order = max_ring_order; init_rwsem(&priv->rings_sem); + INIT_LIST_HEAD(&priv->session); + init_completion(&priv->ctl_done); priv->rings = kzalloc_objs(*priv->rings, XEN_9PFS_NUM_RINGS); if (!priv->rings) { kfree(priv); @@ -714,7 +938,12 @@ static int xen_9pfs_front_resume(struct xenbus_device *dev) static void xen_9pfs_front_changed(struct xenbus_device *dev, enum xenbus_state backend_state) { + struct xen_9pfs_session_chunk *chunk; struct xen_9pfs_front_priv *priv; + u8 reply[P9_HDRSZ + 4]; + u32 len, flags; + int ret; + u8 *req; switch (backend_state) { case XenbusStateReconfiguring: @@ -743,10 +972,53 @@ static void xen_9pfs_front_changed(struct xenbus_device *dev, break; priv = dev_get_drvdata(&dev->dev); - if (priv) { - WRITE_ONCE(priv->connected, true); - wake_up_all(&xen_9pfs_connect_wq); + if (!priv) { + xenbus_switch_state(dev, XenbusStateConnected); + break; + } + + /* Replay the fids saved at suspend before any request can use + * them. + */ + down_read(&priv->rings_sem); + ret = 0; + flags = P9_XEN_SESSION_RESET; + if (!list_empty(&priv->session) && priv->client && + xenbus_read_unsigned(dev->otherend, + "feature-session-transfer", 0)) { + list_for_each_entry(chunk, &priv->session, list) { + len = P9_HDRSZ + 8 + chunk->len; + req = kvmalloc(len, GFP_KERNEL); + if (!req) { + ret = -ENOMEM; + break; + } + put_unaligned_le32(len, req); + req[4] = P9_XEN_TSESSION_RESTORE; + put_unaligned_le16(P9_NOTAG, req + 5); + put_unaligned_le32(priv->client->msize, + req + P9_HDRSZ); + put_unaligned_le32(flags, req + P9_HDRSZ + 4); + memcpy(req + P9_HDRSZ + 8, chunk->data, + chunk->len); + ret = xen_9pfs_front_ctl(priv, req, len, reply, + sizeof(reply), + P9_XEN_RSESSION_RESTORE, + 4); + kvfree(req); + if (ret < 0) + break; + flags = 0; + } } + up_read(&priv->rings_sem); + if (ret < 0) + dev_warn(&dev->dev, "restoring the 9P session failed: %d\n", + ret); + xen_9pfs_front_session_free(priv); + + WRITE_ONCE(priv->connected, true); + wake_up_all(&xen_9pfs_connect_wq); xenbus_switch_state(dev, XenbusStateConnected); break; @@ -764,6 +1036,7 @@ static struct xenbus_driver xen_9pfs_front_driver = { .ids = xen_9pfs_front_ids, .probe = xen_9pfs_front_probe, .remove = xen_9pfs_front_remove, + .suspend = xen_9pfs_front_suspend, .resume = xen_9pfs_front_resume, .otherend_changed = xen_9pfs_front_changed, }; From 667c410403e21c239a7d1f7c0432d540658f48bf Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sun, 18 Oct 2026 10:06:22 -0700 Subject: [PATCH 2283/2286] 9p/xen: advertise session transfer to the toolstack The toolstack has to know whether a guest can carry its 9P session into a new backend before it exports the domain, because a guest that cannot comes back with every open file on its mounts unusable. Until now only the backend advertised the feature, and the frontend's support was invisible from dom0. Publish feature-session-transfer = 1 in the frontend's own xenstore directory, next to version and num-rings. Signed-off-by: Alex Zenla --- net/9p/trans_xen.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/net/9p/trans_xen.c b/net/9p/trans_xen.c index 945ca96526451d..bb25fa7c41b96d 100644 --- a/net/9p/trans_xen.c +++ b/net/9p/trans_xen.c @@ -756,6 +756,13 @@ static int xen_9pfs_front_publish(struct xenbus_device *dev, XEN_9PFS_NUM_RINGS); if (ret) goto error_xenbus; + /* Lets the toolstack tell, before it exports a domain, whether its + * open files can follow it into a new backend. + */ + ret = xenbus_printf(xbt, dev->nodename, "feature-session-transfer", + "%u", 1); + if (ret) + goto error_xenbus; for (i = 0; i < XEN_9PFS_NUM_RINGS; i++) { char str[16]; From 80f81261f17b0ff6576230fc766a65ab38490343 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sun, 18 Oct 2026 17:50:23 -0700 Subject: [PATCH 2284/2286] x86/xen: include the Xen types before interface/memory.h arch/x86/xen/enlighten.c includes xen/interface/memory.h for XENMEM_maximum_reservation, but memory.h uses xen_ulong_t, domid_t and GUEST_HANDLE without including where they are defined, xen/interface/xen.h. Other configurations reach those through an earlier include. The zone-tiny configuration does not, and fails to build with CONFIG_WERROR=y: include/xen/interface/physdev.h:41:5: error: unknown type name 'xen_ulong_t' Include xen/interface/xen.h first, as arch/arm/xen/enlighten.c does. Signed-off-by: Alex Zenla --- arch/x86/xen/enlighten.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/x86/xen/enlighten.c b/arch/x86/xen/enlighten.c index 3883b4cb432548..33b92b3961f199 100644 --- a/arch/x86/xen/enlighten.c +++ b/arch/x86/xen/enlighten.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include From ae8152a66779cf5b880b76708bcb69fb09dc7aef Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 19 Oct 2026 11:13:52 -0700 Subject: [PATCH 2285/2286] xen/unpopulated-alloc: borrow from other nodes before carving a region A node whose pool is short called fill_list() even when other nodes still held free pages. fill_list() takes new address space from iomem_resource, which on a PVH dom0 can overlap MMIO that Xen has identity mapped. A later grant map onto such a gfn makes Xen refuse the p2m change and crash dom0. Carve a new region only when every node's pool is short, and otherwise take pages from the preferred node first, then the others. Return pages to the pool of their own node when the PV p2m allocation fails. --- drivers/xen/unpopulated-alloc.c | 38 +++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/drivers/xen/unpopulated-alloc.c b/drivers/xen/unpopulated-alloc.c index eda66145ddcac7..2db29d7c6e1c53 100644 --- a/drivers/xen/unpopulated-alloc.c +++ b/drivers/xen/unpopulated-alloc.c @@ -190,14 +190,15 @@ static int fill_list(unsigned int nr_pages, int node) * memremap_pages() against @node, so page_to_nid() reports @node for * every returned page. Callers that know the host node of a foreign * frame should pass it here to keep page_to_nid in agreement with the - * actual host placement after a subsequent grant-map. + * actual host placement after a subsequent grant-map. When the pool of + * @node is short, pages come from the other nodes' pools instead. * * Returns 0 on success, error otherwise. */ int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, int node) { - unsigned int i; + unsigned int i, total = 0; int ret = 0; /* @@ -211,18 +212,30 @@ int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, node = xen_unpopulated_clamp_node(node); mutex_lock(&list_lock); - if (list_count[node] < nr_pages) { - ret = fill_list(nr_pages - list_count[node], node); + + /* + * A new region comes from free address space, which can overlap MMIO + * the hypervisor has mapped into the guest. Only carve one when every + * node's pool is short, and borrow from other nodes before that. + */ + for (i = 0; i < MAX_NUMNODES; i++) + total += list_count[i]; + if (total < nr_pages) { + ret = fill_list(nr_pages - total, node); if (ret) goto out; } for (i = 0; i < nr_pages; i++) { - struct page *pg = page_list[node]; + int src = node; + struct page *pg; + + while (!page_list[src]) + src = (src + 1) % MAX_NUMNODES; - BUG_ON(!pg); - page_list[node] = pg->zone_device_data; - list_count[node]--; + pg = page_list[src]; + page_list[src] = pg->zone_device_data; + list_count[src]--; pages[i] = pg; #ifdef CONFIG_XEN_HAVE_PVMMU @@ -232,9 +245,12 @@ int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, unsigned int j; for (j = 0; j <= i; j++) { - pages[j]->zone_device_data = page_list[node]; - page_list[node] = pages[j]; - list_count[node]++; + int dst = xen_unpopulated_clamp_node( + page_to_nid(pages[j])); + + pages[j]->zone_device_data = page_list[dst]; + page_list[dst] = pages[j]; + list_count[dst]++; } goto out; } From f34bd0f7ff4df5ec291b7a3226c4bbeeb0d45fce Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 19 Oct 2026 11:29:23 -0700 Subject: [PATCH 2286/2286] xen/unpopulated-alloc: never hand out another node's pages from the node variant Borrowing across nodes made xen_alloc_unpopulated_pages_node() best effort, so a caller that asked for a node could get pages of another. The node variant now takes pages only from the requested node's pool and fails with -ENOMEM when it is short. It no longer carves a region. xen_alloc_unpopulated_pages() has no node requirement: it prefers the local pool, borrows from other nodes, and carves a region only when every pool is short. xenbus falls back to it when the node's pool is short, and gnttab_alloc_pages_node() uses it for NUMA_NO_NODE. --- drivers/xen/grant-table.c | 5 +- drivers/xen/unpopulated-alloc.c | 115 ++++++++++++++++++----------- drivers/xen/xenbus/xenbus_client.c | 4 + 3 files changed, 78 insertions(+), 46 deletions(-) diff --git a/drivers/xen/grant-table.c b/drivers/xen/grant-table.c index bbb7ca2db54ddb..f05730253dc8f2 100644 --- a/drivers/xen/grant-table.c +++ b/drivers/xen/grant-table.c @@ -985,7 +985,10 @@ int gnttab_alloc_pages_node(int nr_pages, struct page **pages, int node) { int ret; - ret = xen_alloc_unpopulated_pages_node(nr_pages, pages, node); + if (node == NUMA_NO_NODE) + ret = xen_alloc_unpopulated_pages(nr_pages, pages); + else + ret = xen_alloc_unpopulated_pages_node(nr_pages, pages, node); if (ret < 0) return ret; diff --git a/drivers/xen/unpopulated-alloc.c b/drivers/xen/unpopulated-alloc.c index 2db29d7c6e1c53..e0a2b5cd1423de 100644 --- a/drivers/xen/unpopulated-alloc.c +++ b/drivers/xen/unpopulated-alloc.c @@ -180,51 +180,19 @@ static int fill_list(unsigned int nr_pages, int node) return ret; } -/** - * xen_alloc_unpopulated_pages_node - alloc unpopulated pages on a node - * @nr_pages: Number of pages - * @pages: pages returned - * @node: Preferred Linux node id, or NUMA_NO_NODE for current CPU's node - * - * The returned pages are drawn from a per-node pool registered with - * memremap_pages() against @node, so page_to_nid() reports @node for - * every returned page. Callers that know the host node of a foreign - * frame should pass it here to keep page_to_nid in agreement with the - * actual host placement after a subsequent grant-map. When the pool of - * @node is short, pages come from the other nodes' pools instead. - * - * Returns 0 on success, error otherwise. +/* + * Take @nr_pages from the pool of @node, or from any node's pool when @any is + * set. Called with list_lock held. Never carves a new region. */ -int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, - int node) +static int pop_pages(unsigned int nr_pages, struct page **pages, int node, + bool any) { unsigned int i, total = 0; - int ret = 0; - - /* - * Fallback to default behavior if we do not have any suitable resource - * to allocate required region from and as the result we won't be able to - * construct pages. - */ - if (!target_resource) - return xen_alloc_ballooned_pages(nr_pages, pages); - - node = xen_unpopulated_clamp_node(node); - - mutex_lock(&list_lock); - /* - * A new region comes from free address space, which can overlap MMIO - * the hypervisor has mapped into the guest. Only carve one when every - * node's pool is short, and borrow from other nodes before that. - */ for (i = 0; i < MAX_NUMNODES; i++) total += list_count[i]; - if (total < nr_pages) { - ret = fill_list(nr_pages - total, node); - if (ret) - goto out; - } + if ((any ? total : list_count[node]) < nr_pages) + return -ENOMEM; for (i = 0; i < nr_pages; i++) { int src = node; @@ -240,7 +208,8 @@ int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, #ifdef CONFIG_XEN_HAVE_PVMMU if (xen_pv_domain()) { - ret = xen_alloc_p2m_entry(page_to_pfn(pg)); + int ret = xen_alloc_p2m_entry(page_to_pfn(pg)); + if (ret < 0) { unsigned int j; @@ -252,14 +221,53 @@ int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, page_list[dst] = pages[j]; list_count[dst]++; } - goto out; + return ret; } } #endif } -out: + return 0; +} + +/** + * xen_alloc_unpopulated_pages_node - alloc unpopulated pages on a node + * @nr_pages: Number of pages + * @pages: pages returned + * @node: Linux node id, or NUMA_NO_NODE for current CPU's node + * + * The returned pages are drawn from the per-node pool registered with + * memremap_pages() against @node, so page_to_nid() reports @node for + * every returned page. Callers that know the host node of a foreign + * frame should pass it here to keep page_to_nid in agreement with the + * actual host placement after a subsequent grant-map. + * + * Fails with -ENOMEM when the pool of @node is short rather than returning + * pages of another node. A new region is not carved here: it comes from free + * address space, which can overlap MMIO the hypervisor has mapped into the + * guest, so only xen_alloc_unpopulated_pages() does it. + * + * Returns 0 on success, error otherwise. + */ +int xen_alloc_unpopulated_pages_node(unsigned int nr_pages, struct page **pages, + int node) +{ + int ret; + + /* + * Fallback to default behavior if we do not have any suitable resource + * to allocate required region from and as the result we won't be able to + * construct pages. + */ + if (!target_resource) + return xen_alloc_ballooned_pages(nr_pages, pages); + + node = xen_unpopulated_clamp_node(node); + + mutex_lock(&list_lock); + ret = pop_pages(nr_pages, pages, node, false); mutex_unlock(&list_lock); + return ret; } EXPORT_SYMBOL(xen_alloc_unpopulated_pages_node); @@ -270,12 +278,29 @@ EXPORT_SYMBOL(xen_alloc_unpopulated_pages_node); * @pages: pages returned * @return 0 on success, error otherwise * - * Equivalent to xen_alloc_unpopulated_pages_node() with the current - * CPU's node as the preference. + * Prefers the pool of the current CPU's node, then takes from the other + * nodes' pools. A new region is carved only when every pool is short. */ int xen_alloc_unpopulated_pages(unsigned int nr_pages, struct page **pages) { - return xen_alloc_unpopulated_pages_node(nr_pages, pages, numa_node_id()); + unsigned int i, total = 0; + int node = numa_node_id(); + int ret; + + if (!target_resource) + return xen_alloc_ballooned_pages(nr_pages, pages); + + mutex_lock(&list_lock); + for (i = 0; i < MAX_NUMNODES; i++) + total += list_count[i]; + ret = 0; + if (total < nr_pages) + ret = fill_list(nr_pages - total, node); + if (!ret) + ret = pop_pages(nr_pages, pages, node, true); + mutex_unlock(&list_lock); + + return ret; } EXPORT_SYMBOL(xen_alloc_unpopulated_pages); diff --git a/drivers/xen/xenbus/xenbus_client.c b/drivers/xen/xenbus/xenbus_client.c index 704bcd6373ffe8..382912ed53ddea 100644 --- a/drivers/xen/xenbus/xenbus_client.c +++ b/drivers/xen/xenbus/xenbus_client.c @@ -834,6 +834,10 @@ static int xenbus_map_ring_hvm(struct xenbus_device *dev, err = xen_alloc_unpopulated_pages_node(nr_pages, node->hvm.pages, host_node); + /* The node's pool is short; the ring still works from any node. */ + if (err) + err = xen_alloc_unpopulated_pages(nr_pages, + node->hvm.pages); if (err) { /* * Pages already gone; clear the array so the